2107 lines
		
	
	
		
			75 KiB
		
	
	
	
		
			Plaintext
		
	
	
	
	
	
		
		
			
		
	
	
			2107 lines
		
	
	
		
			75 KiB
		
	
	
	
		
			Plaintext
		
	
	
	
	
	
| 
								 | 
							
								#!/usr/bin/env perl
							 | 
						||
| 
								 | 
							
								#===============================================================================
							 | 
						||
| 
								 | 
							
								#
							 | 
						||
| 
								 | 
							
								#         FILE: make_shownotes
							 | 
						||
| 
								 | 
							
								#
							 | 
						||
| 
								 | 
							
								#        USAGE: ./make_shownotes [-help] [-documentation] [-debug=N]
							 | 
						||
| 
								 | 
							
								#               [-from=DATE] [-[no]comments] [-[no]markcomments] [-[no]ctext]
							 | 
						||
| 
								 | 
							
								#               [-lastrecording=DATETIME] [-[no]silent] [-out=FILE]
							 | 
						||
| 
								 | 
							
								#               [-episode=[N|auto]] [-[no]overwrite] [-mailnotes[=FILE]]
							 | 
						||
| 
								 | 
							
								#               [-anyotherbusiness=FILE] [-template=FILE] [-config=FILE]
							 | 
						||
| 
								 | 
							
								#               [-interlock=PASSWORD]
							 | 
						||
| 
								 | 
							
								#
							 | 
						||
| 
								 | 
							
								#  DESCRIPTION: Builds shownotes for a Community News show from the HPR
							 | 
						||
| 
								 | 
							
								#               database using a template.  Writes the result to STDOUT or to
							 | 
						||
| 
								 | 
							
								#               a file. Also writes to the database if requested.
							 | 
						||
| 
								 | 
							
								#
							 | 
						||
| 
								 | 
							
								#      OPTIONS: ---
							 | 
						||
| 
								 | 
							
								# REQUIREMENTS: ---
							 | 
						||
| 
								 | 
							
								#         BUGS: ---
							 | 
						||
| 
								 | 
							
								#        NOTES: To view the entire documentation use:
							 | 
						||
| 
								 | 
							
								#
							 | 
						||
| 
								 | 
							
								#               ./make_shownotes -documentation
							 | 
						||
| 
								 | 
							
								#
							 | 
						||
| 
								 | 
							
								#               To create a PDF version of the documentation:
							 | 
						||
| 
								 | 
							
								#
							 | 
						||
| 
								 | 
							
								#               pod2pdf make_shownotes --out=make_shownotes.pdf
							 | 
						||
| 
								 | 
							
								#
							 | 
						||
| 
								 | 
							
								#               Where pod2pdf comes from App::pod2pdf
							 | 
						||
| 
								 | 
							
								#
							 | 
						||
| 
								 | 
							
								#       AUTHOR: Dave Morriss (djm), Dave.Morriss@gmail.com
							 | 
						||
| 
								 | 
							
								#      VERSION: 0.2.2
							 | 
						||
| 
								 | 
							
								#      CREATED: 2014-04-24 16:08:30
							 | 
						||
| 
								 | 
							
								#     REVISION: 2023-06-30 23:52:06
							 | 
						||
| 
								 | 
							
								#
							 | 
						||
| 
								 | 
							
								#===============================================================================
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								use 5.010;
							 | 
						||
| 
								 | 
							
								use strict;
							 | 
						||
| 
								 | 
							
								use warnings;
							 | 
						||
| 
								 | 
							
								use utf8;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								use Carp;
							 | 
						||
| 
								 | 
							
								use Getopt::Long;
							 | 
						||
| 
								 | 
							
								use Pod::Usage;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								use Config::General;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								use Date::Parse;
							 | 
						||
| 
								 | 
							
								use Date::Calc qw{:all};
							 | 
						||
| 
								 | 
							
								use DateTime;
							 | 
						||
| 
								 | 
							
								use DateTime::Duration;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								use Template;
							 | 
						||
| 
								 | 
							
								use Template::Filters;
							 | 
						||
| 
								 | 
							
								Template::Filters->use_html_entities;    # Use HTML::Entities in the template
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								use HTML::Entities;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								use DBI;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								use Data::Dumper;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								#
							 | 
						||
| 
								 | 
							
								# Version number (manually incremented)
							 | 
						||
| 
								 | 
							
								#
							 | 
						||
| 
								 | 
							
								our $VERSION = '0.2.2';
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								#
							 | 
						||
| 
								 | 
							
								# Various constants
							 | 
						||
| 
								 | 
							
								#
							 | 
						||
| 
								 | 
							
								( my $PROG = $0 ) =~ s|.*/||mx;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								#-------------------------------------------------------------------------------
							 | 
						||
| 
								 | 
							
								# Declarations
							 | 
						||
| 
								 | 
							
								#-------------------------------------------------------------------------------
							 | 
						||
| 
								 | 
							
								#
							 | 
						||
| 
								 | 
							
								# Constants and other declarations
							 | 
						||
| 
								 | 
							
								#
							 | 
						||
| 
								 | 
							
								my $basedir    = "$ENV{HOME}/HPR/Community_News";
							 | 
						||
| 
								 | 
							
								my $configfile = "$basedir/.hpr_db.cfg";
							 | 
						||
| 
								 | 
							
								my $bpfile     = "$basedir/shownote_template.tpl";
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								my $title_template = 'HPR Community News for %s %s';
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								#
							 | 
						||
| 
								 | 
							
								# Needed to allow an older episode to have its notes regenerated. This is an
							 | 
						||
| 
								 | 
							
								# 'apg'-generated password which is just hard to remember and requires some
							 | 
						||
| 
								 | 
							
								# thought to use. The idea is to prevent older shownote rewriting by accident.
							 | 
						||
| 
								 | 
							
								#
							 | 
						||
| 
								 | 
							
								my $interlock_password = 'lumRacboikac';
							 | 
						||
| 
								 | 
							
								my $interlock_enabled = 0;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								my ( $dbh, $sth1, $h1 );
							 | 
						||
| 
								 | 
							
								my ( @startdate, $hosts, $shows );
							 | 
						||
| 
								 | 
							
								my ( @dc_lr, $dt_lr, @dc_lm, $dt_lm );
							 | 
						||
| 
								 | 
							
								my ( $t_days, $missed_comments, $missed_count );
							 | 
						||
| 
								 | 
							
								my ( $comments, $comment_count, $past_count, $ignore_count );
							 | 
						||
| 
								 | 
							
								my ( %past, %current );
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								#
							 | 
						||
| 
								 | 
							
								# The normal recording time (UTC). Any change should be copied in the POD
							 | 
						||
| 
								 | 
							
								# documentation below.
							 | 
						||
| 
								 | 
							
								# TODO: Should this be in a configuration file?
							 | 
						||
| 
								 | 
							
								#
							 | 
						||
| 
								 | 
							
								my @deftime = (15, 00, 00);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								#
							 | 
						||
| 
								 | 
							
								# Earliest comment release time
							 | 
						||
| 
								 | 
							
								#
							 | 
						||
| 
								 | 
							
								my @releasetime = (19, 00, 00);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								#
							 | 
						||
| 
								 | 
							
								# Enable Unicode mode
							 | 
						||
| 
								 | 
							
								#
							 | 
						||
| 
								 | 
							
								binmode STDOUT, ":encoding(UTF-8)";
							 | 
						||
| 
								 | 
							
								binmode STDERR, ":encoding(UTF-8)";
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								#-------------------------------------------------------------------------------
							 | 
						||
| 
								 | 
							
								# Options and arguments
							 | 
						||
| 
								 | 
							
								#-------------------------------------------------------------------------------
							 | 
						||
| 
								 | 
							
								#
							 | 
						||
| 
								 | 
							
								# Option defaults
							 | 
						||
| 
								 | 
							
								#
							 | 
						||
| 
								 | 
							
								my $DEFDEBUG = 0;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								#
							 | 
						||
| 
								 | 
							
								# Process options
							 | 
						||
| 
								 | 
							
								#
							 | 
						||
| 
								 | 
							
								my %options;
							 | 
						||
| 
								 | 
							
								Options( \%options );
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								#
							 | 
						||
| 
								 | 
							
								# Default help is just the USAGE section
							 | 
						||
| 
								 | 
							
								#
							 | 
						||
| 
								 | 
							
								pod2usage( -msg => "$PROG version $VERSION\n", -verbose => 0, -exitval => 1 )
							 | 
						||
| 
								 | 
							
								    if ( $options{'help'} );
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								#
							 | 
						||
| 
								 | 
							
								# Full documentation if requested with -documentation
							 | 
						||
| 
								 | 
							
								#
							 | 
						||
| 
								 | 
							
								pod2usage( -msg => "$PROG version $VERSION\n", -verbose => 2, -exitval => 1 )
							 | 
						||
| 
								 | 
							
								    if ( $options{'documentation'} );
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								#
							 | 
						||
| 
								 | 
							
								# Collect options
							 | 
						||
| 
								 | 
							
								#
							 | 
						||
| 
								 | 
							
								my $DEBUG = ( defined( $options{debug} ) ? $options{debug} : $DEFDEBUG );
							 | 
						||
| 
								 | 
							
								my $cfgfile
							 | 
						||
| 
								 | 
							
								    = ( defined( $options{config} ) ? $options{config} : $configfile );
							 | 
						||
| 
								 | 
							
								my $silent = ( defined( $options{silent} ) ? $options{silent} : 0 );
							 | 
						||
| 
								 | 
							
								my $show_comments
							 | 
						||
| 
								 | 
							
								    = ( defined( $options{comments} ) ? $options{comments} : 0 );
							 | 
						||
| 
								 | 
							
								my $mark_comments
							 | 
						||
| 
								 | 
							
								    = ( defined( $options{markcomments} ) ? $options{markcomments} : 0 );
							 | 
						||
| 
								 | 
							
								my $ctext         = ( defined( $options{ctext} ) ? $options{ctext} : 0 );
							 | 
						||
| 
								 | 
							
								my $lastrecording = $options{lastrecording};
							 | 
						||
| 
								 | 
							
								my $outfile       = $options{out};
							 | 
						||
| 
								 | 
							
								my $episode       = $options{episode};
							 | 
						||
| 
								 | 
							
								my $overwrite = ( defined( $options{overwrite} ) ? $options{overwrite} : 0 );
							 | 
						||
| 
								 | 
							
								my $template
							 | 
						||
| 
								 | 
							
								    = ( defined( $options{template} ) ? $options{template} : $bpfile );
							 | 
						||
| 
								 | 
							
								my $mailnotes = $options{mailnotes};
							 | 
						||
| 
								 | 
							
								my $aobfile   = $options{anyotherbusiness};
							 | 
						||
| 
								 | 
							
								my $interlock = $options{interlock};
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								#
							 | 
						||
| 
								 | 
							
								# Sanity checks
							 | 
						||
| 
								 | 
							
								#
							 | 
						||
| 
								 | 
							
								die "Unable to find configuration file $cfgfile\n" unless ( -e $cfgfile );
							 | 
						||
| 
								 | 
							
								if ( defined($episode) ) {
							 | 
						||
| 
								 | 
							
								    if ( $episode =~ /^\d+$/ ) {
							 | 
						||
| 
								 | 
							
								        die "Episode number must be greater than zero\n"
							 | 
						||
| 
								 | 
							
								            unless ( $episode > 0 );
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								    else {
							 | 
						||
| 
								 | 
							
								        die "Episode must be a number or 'auto'\n"
							 | 
						||
| 
								 | 
							
								            unless ( $episode eq 'auto' );
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								die "Error: Unable to find template $template\n" unless -r $template;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								#
							 | 
						||
| 
								 | 
							
								# We accept '-mailnotes' meaning we want to use a default set of mail notes,
							 | 
						||
| 
								 | 
							
								# or '-mailnotes=FILE' which means the notes are in a file for inclusion. If
							 | 
						||
| 
								 | 
							
								# the option is omitted then we don't include mail notes (and the template is
							 | 
						||
| 
								 | 
							
								# expected to do the right thing).
							 | 
						||
| 
								 | 
							
								#
							 | 
						||
| 
								 | 
							
								if (defined($mailnotes)) {
							 | 
						||
| 
								 | 
							
								    if ($mailnotes =~ /^$/) {
							 | 
						||
| 
								 | 
							
								        #
							 | 
						||
| 
								 | 
							
								        # The default mail inclusion is provided in a named BLOCK directive in
							 | 
						||
| 
								 | 
							
								        # the template. The name is hard-wired here
							 | 
						||
| 
								 | 
							
								        #
							 | 
						||
| 
								 | 
							
								        # FIXME: there's a dependency between the script and the template here
							 | 
						||
| 
								 | 
							
								        # which is inflexible.
							 | 
						||
| 
								 | 
							
								        #
							 | 
						||
| 
								 | 
							
								        $mailnotes = 'default_mail';
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								    else {
							 | 
						||
| 
								 | 
							
								        die "Error: Unable to find includefile '$mailnotes'\n" unless -r $mailnotes;
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								#
							 | 
						||
| 
								 | 
							
								# The -anyotherbusiness=FILE or -aob=FILE options provide an HTML file to be
							 | 
						||
| 
								 | 
							
								# added to the end of the notes.
							 | 
						||
| 
								 | 
							
								#
							 | 
						||
| 
								 | 
							
								if (defined($aobfile)) {
							 | 
						||
| 
								 | 
							
								    die "Error: Unable to find includefile '$aobfile'\n" unless -r $aobfile;
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								#
							 | 
						||
| 
								 | 
							
								# Use the date provided or the default
							 | 
						||
| 
								 | 
							
								#
							 | 
						||
| 
								 | 
							
								if ( defined( $options{from} ) ) {
							 | 
						||
| 
								 | 
							
								    #
							 | 
						||
| 
								 | 
							
								    # Parse and perform rudimentary validation on the -from option
							 | 
						||
| 
								 | 
							
								    #
							 | 
						||
| 
								 | 
							
								#    my @parsed = strptime( $options{from} );
							 | 
						||
| 
								 | 
							
								#    die "Invalid -from=DATE option '$options{from}'\n"
							 | 
						||
| 
								 | 
							
								#        unless ( defined( $parsed[3] )
							 | 
						||
| 
								 | 
							
								#        && defined( $parsed[4] )
							 | 
						||
| 
								 | 
							
								#        && defined( $parsed[5] ) );
							 | 
						||
| 
								 | 
							
								#
							 | 
						||
| 
								 | 
							
								#    $parsed[5] += 1900;
							 | 
						||
| 
								 | 
							
								#    $parsed[4] += 1;
							 | 
						||
| 
								 | 
							
								#    @startdate = @parsed[ 5, 4, 3 ];
							 | 
						||
| 
								 | 
							
								    @startdate = parse_to_dc($options{from}, undef);
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								else {
							 | 
						||
| 
								 | 
							
								    #
							 | 
						||
| 
								 | 
							
								    # Default to the current date
							 | 
						||
| 
								 | 
							
								    #
							 | 
						||
| 
								 | 
							
								    @startdate = Today();
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								#
							 | 
						||
| 
								 | 
							
								# If -interlock=PASSWORD was provided is the password valid?
							 | 
						||
| 
								 | 
							
								#
							 | 
						||
| 
								 | 
							
								if ( defined($interlock) ) {
							 | 
						||
| 
								 | 
							
								    $interlock_enabled = $interlock eq $interlock_password;
							 | 
						||
| 
								 | 
							
								    emit( $silent, "Interlock ",
							 | 
						||
| 
								 | 
							
								        ( $interlock_enabled ? "accepted" : "rejected" ), "\n" );
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								#-------------------------------------------------------------------------------
							 | 
						||
| 
								 | 
							
								# Configuration file - load data
							 | 
						||
| 
								 | 
							
								#-------------------------------------------------------------------------------
							 | 
						||
| 
								 | 
							
								emit( $silent, "Configuration file: ", $cfgfile, "\n" );
							 | 
						||
| 
								 | 
							
								my $conf = Config::General->new(
							 | 
						||
| 
								 | 
							
								    -ConfigFile      => $cfgfile,
							 | 
						||
| 
								 | 
							
								    -InterPolateVars => 1,
							 | 
						||
| 
								 | 
							
								    -ExtendedAccess  => 1,
							 | 
						||
| 
								 | 
							
								);
							 | 
						||
| 
								 | 
							
								my %config = $conf->getall();
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								#-------------------------------------------------------------------------------
							 | 
						||
| 
								 | 
							
								# Date setup
							 | 
						||
| 
								 | 
							
								#-------------------------------------------------------------------------------
							 | 
						||
| 
								 | 
							
								#
							 | 
						||
| 
								 | 
							
								# Transfer Date::Calc values into a hash for initialising a DateTime object.
							 | 
						||
| 
								 | 
							
								# Force the day to 1
							 | 
						||
| 
								 | 
							
								#
							 | 
						||
| 
								 | 
							
								my ( @sd, $dt );
							 | 
						||
| 
								 | 
							
								@sd = ( @startdate, 0, 0, 0 );
							 | 
						||
| 
								 | 
							
								$sd[2] = 1;
							 | 
						||
| 
								 | 
							
								$dt = dc_to_dt(\@sd);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								emit( $silent, "Start of month: ", $dt->ymd, "\n" );
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								#-------------------------------------------------------------------------------
							 | 
						||
| 
								 | 
							
								# Connect to the database
							 | 
						||
| 
								 | 
							
								#-------------------------------------------------------------------------------
							 | 
						||
| 
								 | 
							
								my $dbhost = $config{database}->{host} // '127.0.0.1';
							 | 
						||
| 
								 | 
							
								my $dbport = $config{database}->{port} // 3306;
							 | 
						||
| 
								 | 
							
								my $dbname = $config{database}->{name};
							 | 
						||
| 
								 | 
							
								my $dbuser = $config{database}->{user};
							 | 
						||
| 
								 | 
							
								my $dbpwd  = $config{database}->{password};
							 | 
						||
| 
								 | 
							
								$dbh = DBI->connect( "dbi:mysql:host=$dbhost;port=$dbport;database=$dbname",
							 | 
						||
| 
								 | 
							
								    $dbuser, $dbpwd, { AutoCommit => 1 } )
							 | 
						||
| 
								 | 
							
								    or croak $DBI::errstr;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								#
							 | 
						||
| 
								 | 
							
								# Enable client-side UTF8
							 | 
						||
| 
								 | 
							
								#
							 | 
						||
| 
								 | 
							
								$dbh->{mysql_enable_utf8} = 1;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								#
							 | 
						||
| 
								 | 
							
								# Set the local timezone to UTC for this connection
							 | 
						||
| 
								 | 
							
								#
							 | 
						||
| 
								 | 
							
								$dbh->do("set time_zone = '+00:00'") or carp $dbh->errstr;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								#-------------------------------------------------------------------------------
							 | 
						||
| 
								 | 
							
								# Open the output file (or STDOUT) - we may need the date to do it
							 | 
						||
| 
								 | 
							
								#-------------------------------------------------------------------------------
							 | 
						||
| 
								 | 
							
								my $outfh;
							 | 
						||
| 
								 | 
							
								if ($outfile) {
							 | 
						||
| 
								 | 
							
								    $outfile
							 | 
						||
| 
								 | 
							
								        = sprintf( $outfile, sprintf( "%d-%02d", $dt->year, $dt->month ) )
							 | 
						||
| 
								 | 
							
								        if ( $outfile =~ /%s/ );
							 | 
						||
| 
								 | 
							
								    emit( $silent, "Output: ", $outfile, "\n" );
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    open( $outfh, ">:encoding(UTF-8)", $outfile )
							 | 
						||
| 
								 | 
							
								        or croak "Unable to open $outfile for writing: $!";
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								else {
							 | 
						||
| 
								 | 
							
								    open( $outfh, ">&", \*STDOUT )
							 | 
						||
| 
								 | 
							
								        or croak "Unable to initialise for writing: $!";
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								#-------------------------------------------------------------------------------
							 | 
						||
| 
								 | 
							
								# Check the episode specification if given
							 | 
						||
| 
								 | 
							
								#-------------------------------------------------------------------------------
							 | 
						||
| 
								 | 
							
								if ( defined($episode) ) {
							 | 
						||
| 
								 | 
							
								    my $title = sprintf( $title_template, $dt->month_name, $dt->year );
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    emit( $silent, "\n" );
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    #
							 | 
						||
| 
								 | 
							
								    # Is it a number?
							 | 
						||
| 
								 | 
							
								    #
							 | 
						||
| 
								 | 
							
								    if ( $episode =~ /^\d+$/ ) {
							 | 
						||
| 
								 | 
							
								        emit( $silent, "Writing to numbered episode option selected\n" );
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        #
							 | 
						||
| 
								 | 
							
								        # Does the number exist in the database?
							 | 
						||
| 
								 | 
							
								        #
							 | 
						||
| 
								 | 
							
								        $sth1 = $dbh->prepare(q{SELECT * FROM eps WHERE id = ?});
							 | 
						||
| 
								 | 
							
								        $sth1->execute($episode);
							 | 
						||
| 
								 | 
							
								        if ( $dbh->err ) {
							 | 
						||
| 
								 | 
							
								            carp $dbh->errstr;
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								        if ( $h1 = $sth1->fetchrow_hashref() ) {
							 | 
						||
| 
								 | 
							
								            #
							 | 
						||
| 
								 | 
							
								            # Episode exists, do more checks
							 | 
						||
| 
								 | 
							
								            #
							 | 
						||
| 
								 | 
							
								            emit( $silent, "Found episode $episode\n" );
							 | 
						||
| 
								 | 
							
								            emit( $silent, "Title: ", $h1->{title}, "\n" );
							 | 
						||
| 
								 | 
							
								            die "Error: wrong show selected\n"
							 | 
						||
| 
								 | 
							
								                unless ( $h1->{title} eq $title );
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								            unless (validate_date($h1->{date}) || $interlock_enabled) {
							 | 
						||
| 
								 | 
							
								                die "Error: show $episode has a date in the past\n";
							 | 
						||
| 
								 | 
							
								            }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								            unless ($overwrite) {
							 | 
						||
| 
								 | 
							
								                die "Error: show $episode already has notes\n"
							 | 
						||
| 
								 | 
							
								                    unless length( $h1->{notes} ) == 0;
							 | 
						||
| 
								 | 
							
								            }
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								        else {
							 | 
						||
| 
								 | 
							
								            die "Error: episode $episode does not exist in the database\n";
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								    else {
							 | 
						||
| 
								 | 
							
								        #
							 | 
						||
| 
								 | 
							
								        # The required episode is 'auto' (we already checked). Now we actually
							 | 
						||
| 
								 | 
							
								        # find the episode number corresponding to the month we're processing.
							 | 
						||
| 
								 | 
							
								        # We do this by searching for the relevant title in the database.
							 | 
						||
| 
								 | 
							
								        #
							 | 
						||
| 
								 | 
							
								        emit( $silent, "Searching for show title: '$title'\n" );
							 | 
						||
| 
								 | 
							
								        $sth1 = $dbh->prepare(q{SELECT * FROM eps WHERE title = ?});
							 | 
						||
| 
								 | 
							
								        $sth1->execute($title);
							 | 
						||
| 
								 | 
							
								        if ( $dbh->err ) {
							 | 
						||
| 
								 | 
							
								            carp $dbh->errstr;
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								        if ( $h1 = $sth1->fetchrow_hashref() ) {
							 | 
						||
| 
								 | 
							
								            #
							 | 
						||
| 
								 | 
							
								            # Found the episode by title
							 | 
						||
| 
								 | 
							
								            #
							 | 
						||
| 
								 | 
							
								            $episode = $h1->{id};
							 | 
						||
| 
								 | 
							
								            emit( $silent, "Found episode $episode\n" );
							 | 
						||
| 
								 | 
							
								            emit( $silent, "Title: ", $h1->{title}, "\n" );
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								            unless (validate_date($h1->{date}) || $interlock_enabled) {
							 | 
						||
| 
								 | 
							
								                die "Error: show $episode has a date in the past\n";
							 | 
						||
| 
								 | 
							
								            }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								            unless ($overwrite) {
							 | 
						||
| 
								 | 
							
								                die "Error: show $episode already has notes\n"
							 | 
						||
| 
								 | 
							
								                    unless length( $h1->{notes} ) == 0;
							 | 
						||
| 
								 | 
							
								            }
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								        else {
							 | 
						||
| 
								 | 
							
								            die 'Error: Unable to find an episode '
							 | 
						||
| 
								 | 
							
								                . "for the selected month's notes\n";
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								#-------------------------------------------------------------------------------
							 | 
						||
| 
								 | 
							
								# If asked (-comments -markcomments) compute the last recording date
							 | 
						||
| 
								 | 
							
								#-------------------------------------------------------------------------------
							 | 
						||
| 
								 | 
							
								if ($show_comments && $mark_comments) {
							 | 
						||
| 
								 | 
							
								    #
							 | 
						||
| 
								 | 
							
								    # We're marking comments so need to find the date of the recording of the
							 | 
						||
| 
								 | 
							
								    # last show.
							 | 
						||
| 
								 | 
							
								    #
							 | 
						||
| 
								 | 
							
								    # It's possible to specify date and time of the last recording via option
							 | 
						||
| 
								 | 
							
								    # '-lastrecording=DATETIME' (in case we recorded early or something), but
							 | 
						||
| 
								 | 
							
								    # it needs to be parsed.
							 | 
						||
| 
								 | 
							
								    #
							 | 
						||
| 
								 | 
							
								    if ( defined( $options{lastrecording} ) ) {
							 | 
						||
| 
								 | 
							
								        #
							 | 
						||
| 
								 | 
							
								        # Parse and perform rudimentary validation on the -lastrecording option
							 | 
						||
| 
								 | 
							
								        #
							 | 
						||
| 
								 | 
							
								        @dc_lr = parse_to_dc( $options{lastrecording}, \@deftime );
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								    else {
							 | 
						||
| 
								 | 
							
								        #
							 | 
						||
| 
								 | 
							
								        # Otherwise we assume the recording was on a Saturday and compute when
							 | 
						||
| 
								 | 
							
								        # that was. We get back the date and time of the recording as
							 | 
						||
| 
								 | 
							
								        # a Date::Calc object.
							 | 
						||
| 
								 | 
							
								        #
							 | 
						||
| 
								 | 
							
								        @dc_lr = find_last_recording( \@startdate, \@deftime );
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    #
							 | 
						||
| 
								 | 
							
								    # Convert the D::C datetime to a DateTime object
							 | 
						||
| 
								 | 
							
								    #
							 | 
						||
| 
								 | 
							
								    $dt_lr = dc_to_dt(\@dc_lr);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    # Report it for checking (since this algorithm is complex)
							 | 
						||
| 
								 | 
							
								    emit(
							 | 
						||
| 
								 | 
							
								        $silent,
							 | 
						||
| 
								 | 
							
								        sprintf("* %-100s *\n",
							 | 
						||
| 
								 | 
							
								            ( defined( $options{lastrecording} ) ? 'Given' : 'Found' ) .
							 | 
						||
| 
								 | 
							
								            ' last recording date on ' .
							 | 
						||
| 
								 | 
							
								            $dt_lr->datetime . ' time zone ' . $dt_lr->strftime('%Z') .
							 | 
						||
| 
								 | 
							
								            ' (' . $dt_lr->epoch . ')'
							 | 
						||
| 
								 | 
							
								        )
							 | 
						||
| 
								 | 
							
								    );
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    #
							 | 
						||
| 
								 | 
							
								    # Also, we need to know the last month for comment marking
							 | 
						||
| 
								 | 
							
								    #
							 | 
						||
| 
								 | 
							
								    @dc_lm = find_last_month(\@startdate);
							 | 
						||
| 
								 | 
							
								    $dt_lm = dc_to_dt(\@dc_lm);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    # Report it for checking (since this algorithm is complex)
							 | 
						||
| 
								 | 
							
								    emit(
							 | 
						||
| 
								 | 
							
								        $silent,
							 | 
						||
| 
								 | 
							
								        sprintf("* %-100s *\n",
							 | 
						||
| 
								 | 
							
								            'Last month computed to be ' .
							 | 
						||
| 
								 | 
							
								            $dt_lm->datetime . ' time zone ' . $dt_lm->strftime('%Z') .
							 | 
						||
| 
								 | 
							
								            ' (' . $dt_lm->epoch . ')'
							 | 
						||
| 
								 | 
							
								        )
							 | 
						||
| 
								 | 
							
								    );
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    #
							 | 
						||
| 
								 | 
							
								    # Work out if the the recording date was before the end of the last
							 | 
						||
| 
								 | 
							
								    # reviewed month.
							 | 
						||
| 
								 | 
							
								    #
							 | 
						||
| 
								 | 
							
								    $t_days = trailing_days(\@dc_lr, \@dc_lm);
							 | 
						||
| 
								 | 
							
								    emit(
							 | 
						||
| 
								 | 
							
								        $silent,
							 | 
						||
| 
								 | 
							
								        sprintf("* %-100s *\n",
							 | 
						||
| 
								 | 
							
								            'Recording was in the reviewed month and not on the ' .
							 | 
						||
| 
								 | 
							
								            'last day, so comments may have been missed'
							 | 
						||
| 
								 | 
							
								        )
							 | 
						||
| 
								 | 
							
								    ) if $t_days;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    _debug( $DEBUG > 2, '@dc_lr = (' . join(',',@dc_lr) .')' );
							 | 
						||
| 
								 | 
							
								    _debug( $DEBUG > 2, '$dt_lr->ymd = ' . $dt_lr->ymd );
							 | 
						||
| 
								 | 
							
								    _debug( $DEBUG > 2, '$dt_lr->hms = ' . $dt_lr->hms );
							 | 
						||
| 
								 | 
							
								    _debug( $DEBUG > 2, '@dc_lm = (' . join(',',@dc_lm) .')' );
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								else {
							 | 
						||
| 
								 | 
							
								    #
							 | 
						||
| 
								 | 
							
								    # We now need a default for $dt_lr in all cases because the query has been
							 | 
						||
| 
								 | 
							
								    # changed.
							 | 
						||
| 
								 | 
							
								    #
							 | 
						||
| 
								 | 
							
								    @dc_lr = find_last_recording( \@startdate, \@deftime );
							 | 
						||
| 
								 | 
							
								    $dt_lr = dc_to_dt(\@dc_lr);
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								#-------------------------------------------------------------------------------
							 | 
						||
| 
								 | 
							
								# Data collection
							 | 
						||
| 
								 | 
							
								#-------------------------------------------------------------------------------
							 | 
						||
| 
								 | 
							
								#
							 | 
						||
| 
								 | 
							
								# Prepare to get any new hosts for the required month. We let MySQL compute
							 | 
						||
| 
								 | 
							
								# the end of the month. Order by date of first show.
							 | 
						||
| 
								 | 
							
								#
							 | 
						||
| 
								 | 
							
								$sth1 = $dbh->prepare(
							 | 
						||
| 
								 | 
							
								    q{SELECT ho.host, ho.hostid, md.mindate
							 | 
						||
| 
								 | 
							
								        FROM hosts ho
							 | 
						||
| 
								 | 
							
								        JOIN (SELECT hostid, MIN(date) mindate FROM eps GROUP BY hostid) AS md
							 | 
						||
| 
								 | 
							
								            ON ho.hostid = md.hostid
							 | 
						||
| 
								 | 
							
								        WHERE md.mindate >= ? AND md.mindate < last_day(?) + interval 1 day
							 | 
						||
| 
								 | 
							
								        ORDER BY mindate}
							 | 
						||
| 
								 | 
							
								);
							 | 
						||
| 
								 | 
							
								$sth1->execute( $dt->ymd, $dt->ymd );
							 | 
						||
| 
								 | 
							
								if ( $dbh->err ) {
							 | 
						||
| 
								 | 
							
								    carp $dbh->errstr;
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								#
							 | 
						||
| 
								 | 
							
								# Grab the data as an arrayref of hashrefs
							 | 
						||
| 
								 | 
							
								#
							 | 
						||
| 
								 | 
							
								$hosts = $sth1->fetchall_arrayref( {} );
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								#
							 | 
						||
| 
								 | 
							
								# Prepare to get the episodes for the required month. We let MySQL compute the
							 | 
						||
| 
								 | 
							
								# end of the month. We include every column here just in case they'll be
							 | 
						||
| 
								 | 
							
								# useful in the template, though this requires some aliasing.
							 | 
						||
| 
								 | 
							
								# 2015-04-05 The date field has been reformatted so that the 'date' plugin in
							 | 
						||
| 
								 | 
							
								# the form is happy with it.
							 | 
						||
| 
								 | 
							
								#
							 | 
						||
| 
								 | 
							
								$sth1 = $dbh->prepare(
							 | 
						||
| 
								 | 
							
								    q{SELECT    eps.id AS eps_id,
							 | 
						||
| 
								 | 
							
								--              eps.type,
							 | 
						||
| 
								 | 
							
								--              date_format(eps.date,'%a %Y-%m-%d') AS date,
							 | 
						||
| 
								 | 
							
								                date_format(eps.date,'00:00:00 %d/%m/%Y') AS date,
							 | 
						||
| 
								 | 
							
								--              eps.date,
							 | 
						||
| 
								 | 
							
								                eps.title,
							 | 
						||
| 
								 | 
							
								                sec_to_time(eps.duration) as length,
							 | 
						||
| 
								 | 
							
								                eps.summary,
							 | 
						||
| 
								 | 
							
								                eps.notes,
							 | 
						||
| 
								 | 
							
								--              eps.host AS eps_host,
							 | 
						||
| 
								 | 
							
								                eps.hostid AS eps_hostid,
							 | 
						||
| 
								 | 
							
								                eps.series,
							 | 
						||
| 
								 | 
							
								                eps.explicit,
							 | 
						||
| 
								 | 
							
								                eps.license AS eps_license,
							 | 
						||
| 
								 | 
							
								                eps.tags,
							 | 
						||
| 
								 | 
							
								                eps.version,
							 | 
						||
| 
								 | 
							
								                eps.valid AS eps_valid,
							 | 
						||
| 
								 | 
							
								                ho.hostid AS ho_hostid,
							 | 
						||
| 
								 | 
							
								                ho.host AS ho_host,
							 | 
						||
| 
								 | 
							
								                ho.email,
							 | 
						||
| 
								 | 
							
								                ho.profile, -- was website,
							 | 
						||
| 
								 | 
							
								                ho.license AS ho_license,
							 | 
						||
| 
								 | 
							
								--              ho.repeat,
							 | 
						||
| 
								 | 
							
								                ho.valid AS ho_valid
							 | 
						||
| 
								 | 
							
								            FROM eps
							 | 
						||
| 
								 | 
							
								            JOIN hosts ho ON eps.hostid = ho.hostid
							 | 
						||
| 
								 | 
							
								        WHERE eps.date >= ?
							 | 
						||
| 
								 | 
							
								        AND eps.date < last_day(?) + interval 1 day
							 | 
						||
| 
								 | 
							
								        ORDER BY id}
							 | 
						||
| 
								 | 
							
								);
							 | 
						||
| 
								 | 
							
								$sth1->execute( $dt->ymd, $dt->ymd );
							 | 
						||
| 
								 | 
							
								if ( $dbh->err ) {
							 | 
						||
| 
								 | 
							
								    carp $dbh->errstr;
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								#
							 | 
						||
| 
								 | 
							
								# Grab the data as an arrayref of hashrefs
							 | 
						||
| 
								 | 
							
								#
							 | 
						||
| 
								 | 
							
								$shows = $sth1->fetchall_arrayref( {} );
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								#
							 | 
						||
| 
								 | 
							
								# Collect the comments if requested
							 | 
						||
| 
								 | 
							
								#
							 | 
						||
| 
								 | 
							
								if ($show_comments) {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    #
							 | 
						||
| 
								 | 
							
								    # Grab the comments for the selected period. These have weird \' sequences
							 | 
						||
| 
								 | 
							
								    # in the author, title and text, which doesn't seem right, so we strip
							 | 
						||
| 
								 | 
							
								    # them.  Note that the end date in date ranges seems only to work as
							 | 
						||
| 
								 | 
							
								    # 'last_day(?) + interval 1 day' presumably because without it the date is
							 | 
						||
| 
								 | 
							
								    # interpreted as midnight the previous day (e.g. 2014-06-30 is early on
							 | 
						||
| 
								 | 
							
								    # this day, not early on 2014-07-01 whereas adding 1 day gets this right).
							 | 
						||
| 
								 | 
							
								    #
							 | 
						||
| 
								 | 
							
								    # The logic here was rewritten 2015-03-04 and consists of:
							 | 
						||
| 
								 | 
							
								    # - the sub-select collects the id numbers of all comments that have
							 | 
						||
| 
								 | 
							
								    #   occurred in the selected period
							 | 
						||
| 
								 | 
							
								    # - because all comments relating to a given show have the same id number
							 | 
						||
| 
								 | 
							
								    #   this identifies all comment groups (comments relating to a given show)
							 | 
						||
| 
								 | 
							
								    #   with members in the period
							 | 
						||
| 
								 | 
							
								    # - the main query then selects all comments in these groups and joins all
							 | 
						||
| 
								 | 
							
								    #   the required tables to them to get the other data we need; it computes
							 | 
						||
| 
								 | 
							
								    #   the boolean 'in_range' to indicate whether a comment within a group
							 | 
						||
| 
								 | 
							
								    #   should be displayed; it returns *all* comments so we can number them
							 | 
						||
| 
								 | 
							
								    # - there might have been a way to generate a comment sequence number
							 | 
						||
| 
								 | 
							
								    #   within the SQL but it was decided not to and to do this in the script
							 | 
						||
| 
								 | 
							
								    #
							 | 
						||
| 
								 | 
							
								    # Update 2015-03-26: the sort order of the final result used
							 | 
						||
| 
								 | 
							
								    # 'comment_identifier_id' which didn't seem to work reliably. Changed this
							 | 
						||
| 
								 | 
							
								    # to 'episode' which seems to work fine. The use of the former was only
							 | 
						||
| 
								 | 
							
								    # arrived at by guesswork. The guess was obviously wrong.
							 | 
						||
| 
								 | 
							
								    #
							 | 
						||
| 
								 | 
							
								    # Update 2015-06-04: the test for whether a comment is approved was
							 | 
						||
| 
								 | 
							
								    # failing because it was in the wrong place. Also, the sub-select seemed
							 | 
						||
| 
								 | 
							
								    # wrong, and running through EXPLAIN EXTENDED showed some flaws.
							 | 
						||
| 
								 | 
							
								    # Redesigned this and the whole query to be more in keeping with the
							 | 
						||
| 
								 | 
							
								    # algorithm sketched out above. This seems to have solved the problems.
							 | 
						||
| 
								 | 
							
								    #
							 | 
						||
| 
								 | 
							
								    # Update 2015-08-17: Still not right; somehow the 'in_range' check got
							 | 
						||
| 
								 | 
							
								    # mangled and the process of counting comments where a recent one was made
							 | 
						||
| 
								 | 
							
								    # to an old show was messed up. Dropped the two queries with a UNION and
							 | 
						||
| 
								 | 
							
								    # went back to the check for comment_identifier_id in a sub-query. This
							 | 
						||
| 
								 | 
							
								    # time the sub-query is cleverer and returns identifiers where the
							 | 
						||
| 
								 | 
							
								    # `comment_timestamp` is in the target month *and* the episode date is in
							 | 
						||
| 
								 | 
							
								    # the target month or before the start of the target month. This works for
							 | 
						||
| 
								 | 
							
								    # July 2015 (where there's a comment made about an August show) and August
							 | 
						||
| 
								 | 
							
								    # 2015 (where there's a comment made about a July show).
							 | 
						||
| 
								 | 
							
								    #
							 | 
						||
| 
								 | 
							
								    # Update 2023-05-02: Issues with comments received on the day of
							 | 
						||
| 
								 | 
							
								    # recording. This matter has been addresed earlier in the year but not all
							 | 
						||
| 
								 | 
							
								    # that well. The way this is all handled is not very clever so tried
							 | 
						||
| 
								 | 
							
								    # adjusting the main comment query to return such comments since we had
							 | 
						||
| 
								 | 
							
								    # one on 2023-04-29 which wasn't in the report.
							 | 
						||
| 
								 | 
							
								    #
							 | 
						||
| 
								 | 
							
								    #{{{
							 | 
						||
| 
								 | 
							
								    #
							 | 
						||
| 
								 | 
							
								    # Graveyard of old failed queries...
							 | 
						||
| 
								 | 
							
								    # ==================================
							 | 
						||
| 
								 | 
							
								#    $sth1 = $dbh->prepare(
							 | 
						||
| 
								 | 
							
								#        q{
							 | 
						||
| 
								 | 
							
								#        SELECT
							 | 
						||
| 
								 | 
							
								#            cc.comment_identifier_id,
							 | 
						||
| 
								 | 
							
								#            eps.id AS episode,
							 | 
						||
| 
								 | 
							
								#--          ci.identifier_url,
							 | 
						||
| 
								 | 
							
								#            substr(ci.identifier_url,1,locate('/eps.php?id=',ci.identifier_url)+15)
							 | 
						||
| 
								 | 
							
								#                AS identifier_url,
							 | 
						||
| 
								 | 
							
								#            eps.title,
							 | 
						||
| 
								 | 
							
								#            eps.date,
							 | 
						||
| 
								 | 
							
								#            ho.host,
							 | 
						||
| 
								 | 
							
								#            ho.hostid,
							 | 
						||
| 
								 | 
							
								#            from_unixtime(cc.comment_timestamp,'%Y-%m-%dT%TZ') AS timestamp,
							 | 
						||
| 
								 | 
							
								#            replace(cc.comment_author_name,'\\\\','') AS comment_author_name,
							 | 
						||
| 
								 | 
							
								#            replace(cc.comment_title,'\\\\','') AS comment_title,
							 | 
						||
| 
								 | 
							
								#            replace(cc.comment_text,'\\\\','') AS comment_text,
							 | 
						||
| 
								 | 
							
								#            cc.comment_timestamp,
							 | 
						||
| 
								 | 
							
								#            (CASE WHEN (cc.comment_timestamp >= unix_timestamp(?)
							 | 
						||
| 
								 | 
							
								#                    AND cc.comment_timestamp < unix_timestamp(last_day(?) + interval 1 day)) THEN
							 | 
						||
| 
								 | 
							
								#                1 ELSE 0 END) AS in_range
							 | 
						||
| 
								 | 
							
								#        FROM c5t_comment cc
							 | 
						||
| 
								 | 
							
								#        JOIN c5t_identifier ci ON ci.identifier_id = cc.comment_identifier_id
							 | 
						||
| 
								 | 
							
								#        JOIN eps ON eps.id =
							 | 
						||
| 
								 | 
							
								#            substr(ci.identifier_url,locate('/eps.php?id=',ci.identifier_url)+12)
							 | 
						||
| 
								 | 
							
								#        JOIN hosts ho ON eps.hostid = ho.hostid
							 | 
						||
| 
								 | 
							
								#        WHERE cc.comment_status = 0
							 | 
						||
| 
								 | 
							
								#        AND cc.comment_identifier_id IN (
							 | 
						||
| 
								 | 
							
								#            SELECT DISTINCT cc2.comment_identifier_id FROM c5t_comment cc2
							 | 
						||
| 
								 | 
							
								#                WHERE (
							 | 
						||
| 
								 | 
							
								#                    cc2.comment_timestamp >= unix_timestamp(?)
							 | 
						||
| 
								 | 
							
								#                        AND cc2.comment_timestamp < unix_timestamp(last_day(?) + interval 1 day)
							 | 
						||
| 
								 | 
							
								#                    )
							 | 
						||
| 
								 | 
							
								#            )
							 | 
						||
| 
								 | 
							
								#        AND (eps.date < (last_day(?) + interval 1 day))
							 | 
						||
| 
								 | 
							
								#        ORDER BY eps.id ASC, comment_timestamp ASC
							 | 
						||
| 
								 | 
							
								#        }
							 | 
						||
| 
								 | 
							
								#    );
							 | 
						||
| 
								 | 
							
								#
							 | 
						||
| 
								 | 
							
								# This one is nuts...
							 | 
						||
| 
								 | 
							
								#    $sth1 = $dbh->prepare( q{
							 | 
						||
| 
								 | 
							
								#        (
							 | 
						||
| 
								 | 
							
								#            SELECT
							 | 
						||
| 
								 | 
							
								#                cc.comment_identifier_id,
							 | 
						||
| 
								 | 
							
								#                eps.id AS episode,
							 | 
						||
| 
								 | 
							
								#                substr(ci.identifier_url,1,locate('/eps.php?id=',ci.identifier_url)+15)
							 | 
						||
| 
								 | 
							
								#                    AS identifier_url,
							 | 
						||
| 
								 | 
							
								#                eps.title,
							 | 
						||
| 
								 | 
							
								#                eps.date,
							 | 
						||
| 
								 | 
							
								#                ho.host,
							 | 
						||
| 
								 | 
							
								#                ho.hostid,
							 | 
						||
| 
								 | 
							
								#                from_unixtime(cc.comment_timestamp,'%Y-%m-%dT%TZ') AS timestamp,
							 | 
						||
| 
								 | 
							
								#                replace(cc.comment_author_name,'\\\\','') AS comment_author_name,
							 | 
						||
| 
								 | 
							
								#                replace(cc.comment_title,'\\\\','') AS comment_title,
							 | 
						||
| 
								 | 
							
								#                replace(cc.comment_text,'\\\\','') AS comment_text,
							 | 
						||
| 
								 | 
							
								#                cc.comment_timestamp,
							 | 
						||
| 
								 | 
							
								#                (CASE WHEN (cc.comment_timestamp >= unix_timestamp(?)
							 | 
						||
| 
								 | 
							
								#                        AND cc.comment_timestamp < unix_timestamp(last_day(?) + interval 1 day)) THEN
							 | 
						||
| 
								 | 
							
								#                    1 ELSE 0 END) AS in_range
							 | 
						||
| 
								 | 
							
								#            FROM c5t_comment cc
							 | 
						||
| 
								 | 
							
								#            JOIN c5t_identifier ci ON ci.identifier_id = cc.comment_identifier_id
							 | 
						||
| 
								 | 
							
								#            JOIN eps ON eps.id =
							 | 
						||
| 
								 | 
							
								#                substr(ci.identifier_url,locate('/eps.php?id=',ci.identifier_url)+12,4)
							 | 
						||
| 
								 | 
							
								#            JOIN hosts ho ON eps.hostid = ho.hostid
							 | 
						||
| 
								 | 
							
								#            WHERE eps.date >= ? AND eps.date < (last_day(?) + interval 1 day)
							 | 
						||
| 
								 | 
							
								#        )
							 | 
						||
| 
								 | 
							
								#        UNION
							 | 
						||
| 
								 | 
							
								#        (
							 | 
						||
| 
								 | 
							
								#            SELECT
							 | 
						||
| 
								 | 
							
								#                cc.comment_identifier_id,
							 | 
						||
| 
								 | 
							
								#                eps.id AS episode,
							 | 
						||
| 
								 | 
							
								#                substr(ci.identifier_url,1,locate('/eps.php?id=',ci.identifier_url)+15)
							 | 
						||
| 
								 | 
							
								#                    AS identifier_url,
							 | 
						||
| 
								 | 
							
								#                eps.title,
							 | 
						||
| 
								 | 
							
								#                eps.date,
							 | 
						||
| 
								 | 
							
								#                ho.host,
							 | 
						||
| 
								 | 
							
								#                ho.hostid,
							 | 
						||
| 
								 | 
							
								#                from_unixtime(cc.comment_timestamp,'%Y-%m-%dT%TZ') AS timestamp,
							 | 
						||
| 
								 | 
							
								#                replace(cc.comment_author_name,'\\\\','') AS comment_author_name,
							 | 
						||
| 
								 | 
							
								#                replace(cc.comment_title,'\\\\','') AS comment_title,
							 | 
						||
| 
								 | 
							
								#                replace(cc.comment_text,'\\\\','') AS comment_text,
							 | 
						||
| 
								 | 
							
								#                cc.comment_timestamp,
							 | 
						||
| 
								 | 
							
								#                (CASE WHEN (cc.comment_timestamp >= unix_timestamp(?)
							 | 
						||
| 
								 | 
							
								#                        AND cc.comment_timestamp < unix_timestamp(last_day(?) + interval 1 day)) THEN
							 | 
						||
| 
								 | 
							
								#                    1 ELSE 0 END) AS in_range
							 | 
						||
| 
								 | 
							
								#            FROM c5t_comment cc
							 | 
						||
| 
								 | 
							
								#            JOIN c5t_identifier ci ON ci.identifier_id = cc.comment_identifier_id
							 | 
						||
| 
								 | 
							
								#            JOIN eps ON eps.id =
							 | 
						||
| 
								 | 
							
								#                substr(ci.identifier_url,locate('/eps.php?id=',ci.identifier_url)+12,4)
							 | 
						||
| 
								 | 
							
								#            JOIN hosts ho ON eps.hostid = ho.hostid
							 | 
						||
| 
								 | 
							
								#            WHERE (cc.comment_timestamp >= unix_timestamp(?)
							 | 
						||
| 
								 | 
							
								#                AND cc.comment_timestamp < unix_timestamp(last_day(?) + interval 1 day))
							 | 
						||
| 
								 | 
							
								#            AND eps.date < (last_day(?) + interval 1 day)
							 | 
						||
| 
								 | 
							
								#        )
							 | 
						||
| 
								 | 
							
								#        ORDER BY episode ASC, comment_timestamp ASC
							 | 
						||
| 
								 | 
							
								#    });
							 | 
						||
| 
								 | 
							
								#
							 | 
						||
| 
								 | 
							
								# This one worked fine - after much messing around admittedly:
							 | 
						||
| 
								 | 
							
								#    $sth1 = $dbh->prepare( q{
							 | 
						||
| 
								 | 
							
								#        SELECT
							 | 
						||
| 
								 | 
							
								#            cc.comment_identifier_id,
							 | 
						||
| 
								 | 
							
								#            eps.id AS episode,
							 | 
						||
| 
								 | 
							
								#            substr(ci.identifier_url,1,locate('/eps.php?id=',ci.identifier_url)+15)
							 | 
						||
| 
								 | 
							
								#                AS identifier_url,
							 | 
						||
| 
								 | 
							
								#            eps.title,
							 | 
						||
| 
								 | 
							
								#            eps.date,
							 | 
						||
| 
								 | 
							
								#            ho.host,
							 | 
						||
| 
								 | 
							
								#            ho.hostid,
							 | 
						||
| 
								 | 
							
								#            from_unixtime(cc.comment_timestamp,'%Y-%m-%dT%TZ') AS timestamp,
							 | 
						||
| 
								 | 
							
								#            replace(cc.comment_author_name,'\\\\','') AS comment_author_name,
							 | 
						||
| 
								 | 
							
								#            replace(cc.comment_title,'\\\\','') AS comment_title,
							 | 
						||
| 
								 | 
							
								#            replace(cc.comment_text,'\\\\','') AS comment_text,
							 | 
						||
| 
								 | 
							
								#            cc.comment_timestamp,
							 | 
						||
| 
								 | 
							
								#            (CASE WHEN
							 | 
						||
| 
								 | 
							
								#                (
							 | 
						||
| 
								 | 
							
								#                    (cc.comment_timestamp >= unix_timestamp(?)
							 | 
						||
| 
								 | 
							
								#                        AND cc.comment_timestamp < unix_timestamp(last_day(?) + interval 1 day))
							 | 
						||
| 
								 | 
							
								#                    AND eps.date < (last_day(?) + interval 1 day)
							 | 
						||
| 
								 | 
							
								#                    OR (cc.comment_timestamp < unix_timestamp(last_day(?) + interval 1 day)
							 | 
						||
| 
								 | 
							
								#                        AND (eps.date >= ? AND eps.date < (last_day(?) + interval 1 day)))
							 | 
						||
| 
								 | 
							
								#                )
							 | 
						||
| 
								 | 
							
								#                THEN 1 ELSE 0 END) AS in_range
							 | 
						||
| 
								 | 
							
								#        FROM c5t_comment cc
							 | 
						||
| 
								 | 
							
								#        JOIN c5t_identifier ci ON ci.identifier_id = cc.comment_identifier_id
							 | 
						||
| 
								 | 
							
								#        JOIN eps ON eps.id =
							 | 
						||
| 
								 | 
							
								#            substr(ci.identifier_url,locate('/eps.php?id=',ci.identifier_url)+12,4)
							 | 
						||
| 
								 | 
							
								#        JOIN hosts ho ON eps.hostid = ho.hostid
							 | 
						||
| 
								 | 
							
								#        WHERE cc.comment_status = 0
							 | 
						||
| 
								 | 
							
								#        AND cc.comment_identifier_id IN
							 | 
						||
| 
								 | 
							
								#        (
							 | 
						||
| 
								 | 
							
								#            SELECT DISTINCT
							 | 
						||
| 
								 | 
							
								#                cc.comment_identifier_id
							 | 
						||
| 
								 | 
							
								#            FROM c5t_comment cc
							 | 
						||
| 
								 | 
							
								#            JOIN c5t_identifier ci ON ci.identifier_id = cc.comment_identifier_id
							 | 
						||
| 
								 | 
							
								#            JOIN eps ON eps.id =
							 | 
						||
| 
								 | 
							
								#                substr(ci.identifier_url,locate('/eps.php?id=',ci.identifier_url)+12,4)
							 | 
						||
| 
								 | 
							
								#            WHERE
							 | 
						||
| 
								 | 
							
								#                (eps.date >= ? AND eps.date < (last_day(?) + interval 1 day))
							 | 
						||
| 
								 | 
							
								#                OR (
							 | 
						||
| 
								 | 
							
								#                    ( (cc.comment_timestamp >= unix_timestamp(?)
							 | 
						||
| 
								 | 
							
								#                        AND cc.comment_timestamp < unix_timestamp(last_day(?) + interval 1 day)) )
							 | 
						||
| 
								 | 
							
								#                    AND (eps.date >= ? AND eps.date < (last_day(?) + interval 1 day)
							 | 
						||
| 
								 | 
							
								#                        OR (eps.date < ?))
							 | 
						||
| 
								 | 
							
								#                )
							 | 
						||
| 
								 | 
							
								#        )
							 | 
						||
| 
								 | 
							
								#        ORDER BY episode ASC, comment_timestamp ASC
							 | 
						||
| 
								 | 
							
								#    });
							 | 
						||
| 
								 | 
							
								    #}}}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    #-------------------------------------------------------------------------------
							 | 
						||
| 
								 | 
							
								    # Main comment query
							 | 
						||
| 
								 | 
							
								    #-------------------------------------------------------------------------------
							 | 
						||
| 
								 | 
							
								    $sth1 = $dbh->prepare( q{
							 | 
						||
| 
								 | 
							
								        SELECT
							 | 
						||
| 
								 | 
							
								            eps.id AS episode,
							 | 
						||
| 
								 | 
							
								            concat('https://hackerpublicradio.org/eps/hpr',
							 | 
						||
| 
								 | 
							
								                lpad(eps_id,4,0),'/index.html') AS identifier_url,
							 | 
						||
| 
								 | 
							
								            eps.title,
							 | 
						||
| 
								 | 
							
								            eps.summary,
							 | 
						||
| 
								 | 
							
								            eps.date,
							 | 
						||
| 
								 | 
							
								            ho.host,
							 | 
						||
| 
								 | 
							
								            ho.hostid,
							 | 
						||
| 
								 | 
							
								            date_format(co.comment_timestamp,'%Y-%m-%dT%TZ') AS timestamp,
							 | 
						||
| 
								 | 
							
								            co.comment_author_name,
							 | 
						||
| 
								 | 
							
								            co.comment_title,
							 | 
						||
| 
								 | 
							
								            co.comment_text,
							 | 
						||
| 
								 | 
							
								            unix_timestamp(co.comment_timestamp) AS comment_timestamp_ut,
							 | 
						||
| 
								 | 
							
								            unix_timestamp(
							 | 
						||
| 
								 | 
							
								                cast(concat(date(co.comment_timestamp),' ',?) AS DATETIME)
							 | 
						||
| 
								 | 
							
								            ) AS comment_released_ut,
							 | 
						||
| 
								 | 
							
								            (CASE WHEN
							 | 
						||
| 
								 | 
							
								                (
							 | 
						||
| 
								 | 
							
								                    (co.comment_timestamp >= ?
							 | 
						||
| 
								 | 
							
								                        AND co.comment_timestamp < (last_day(?) + interval 1 day))
							 | 
						||
| 
								 | 
							
								                    OR (co.comment_timestamp >= ?)
							 | 
						||
| 
								 | 
							
								                    AND eps.date < (last_day(?) + interval 1 day)
							 | 
						||
| 
								 | 
							
								                    OR (co.comment_timestamp < (last_day(?) + interval 1 day)
							 | 
						||
| 
								 | 
							
								                        AND (eps.date >= ? AND eps.date < (last_day(?) + interval 1 day)))
							 | 
						||
| 
								 | 
							
								                )
							 | 
						||
| 
								 | 
							
								                THEN 1 ELSE 0 END) AS in_range
							 | 
						||
| 
								 | 
							
								        FROM comments co
							 | 
						||
| 
								 | 
							
								        JOIN eps ON eps.id = co.eps_id
							 | 
						||
| 
								 | 
							
								        JOIN hosts ho ON eps.hostid = ho.hostid
							 | 
						||
| 
								 | 
							
								        WHERE eps.id IN
							 | 
						||
| 
								 | 
							
								        (
							 | 
						||
| 
								 | 
							
								            SELECT DISTINCT
							 | 
						||
| 
								 | 
							
								                eps.id
							 | 
						||
| 
								 | 
							
								            FROM eps
							 | 
						||
| 
								 | 
							
								            JOIN comments co ON (eps.id = co.eps_id)
							 | 
						||
| 
								 | 
							
								            WHERE
							 | 
						||
| 
								 | 
							
								                (eps.date >= ? AND eps.date < (last_day(?) + interval 1 day))
							 | 
						||
| 
								 | 
							
								                OR (co.comment_timestamp >= ?)
							 | 
						||
| 
								 | 
							
								                OR (
							 | 
						||
| 
								 | 
							
								                    ( (co.comment_timestamp >= ?
							 | 
						||
| 
								 | 
							
								                            AND co.comment_timestamp < (last_day(?) + interval 1 day)) )
							 | 
						||
| 
								 | 
							
								                    AND (eps.date >= ? AND eps.date < (last_day(?) + interval 1 day)
							 | 
						||
| 
								 | 
							
								                        OR (eps.date < ?))
							 | 
						||
| 
								 | 
							
								                )
							 | 
						||
| 
								 | 
							
								        )
							 | 
						||
| 
								 | 
							
								        ORDER BY episode ASC, comment_timestamp ASC
							 | 
						||
| 
								 | 
							
								    });
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    $sth1->execute(
							 | 
						||
| 
								 | 
							
								        $dt_lr->hms,
							 | 
						||
| 
								 | 
							
								        ( $dt->ymd ) x 2,
							 | 
						||
| 
								 | 
							
								        $dt_lr->ymd,
							 | 
						||
| 
								 | 
							
								        ( $dt->ymd ) x 6,
							 | 
						||
| 
								 | 
							
								        $dt_lr->ymd,
							 | 
						||
| 
								 | 
							
								        ( $dt->ymd ) x 5
							 | 
						||
| 
								 | 
							
								    );
							 | 
						||
| 
								 | 
							
								    if ( $dbh->err ) {
							 | 
						||
| 
								 | 
							
								        carp $dbh->errstr;
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    #
							 | 
						||
| 
								 | 
							
								    # Grab the data as an arrayref of hashrefs
							 | 
						||
| 
								 | 
							
								    #
							 | 
						||
| 
								 | 
							
								    $comments = $sth1->fetchall_arrayref( {} );
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    #-------------------------------------------------------------------------------
							 | 
						||
| 
								 | 
							
								    # Post-process the results of the query
							 | 
						||
| 
								 | 
							
								    #-------------------------------------------------------------------------------
							 | 
						||
| 
								 | 
							
								    # The comment structure needs some further work because it contains all
							 | 
						||
| 
								 | 
							
								    # comments for a given show but no indexes we can use (because the query
							 | 
						||
| 
								 | 
							
								    # didn't generate any - this is a lot easier in PostgreSQL (and possibly
							 | 
						||
| 
								 | 
							
								    # SQLite)). We also need to know how many comments we have within the
							 | 
						||
| 
								 | 
							
								    # target period, which is usually a smaller number than the number of
							 | 
						||
| 
								 | 
							
								    # comments we got back. The query has marked the ones we want to count
							 | 
						||
| 
								 | 
							
								    # (and display) using 'in_range'.
							 | 
						||
| 
								 | 
							
								    #
							 | 
						||
| 
								 | 
							
								    my ( $ep, $lastep, $index ) = ( 0, 0, 0 );
							 | 
						||
| 
								 | 
							
								    $comment_count = $past_count = $ignore_count = 0;
							 | 
						||
| 
								 | 
							
								    for my $row (@$comments) {
							 | 
						||
| 
								 | 
							
								        #
							 | 
						||
| 
								 | 
							
								        # Give each comment an index whether in_range or not. The indexes
							 | 
						||
| 
								 | 
							
								        # start from 1 when the episode number changes otherwise they
							 | 
						||
| 
								 | 
							
								        # increment.
							 | 
						||
| 
								 | 
							
								        #
							 | 
						||
| 
								 | 
							
								        $ep = $row->{episode};
							 | 
						||
| 
								 | 
							
								        if ( $ep == $lastep ) {
							 | 
						||
| 
								 | 
							
								            $index++;
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								        else {
							 | 
						||
| 
								 | 
							
								            $index = 1;
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								        #print "$ep $index ", $row->{in_range}, "\n";
							 | 
						||
| 
								 | 
							
								        _debug(
							 | 
						||
| 
								 | 
							
								            $DEBUG > 2,
							 | 
						||
| 
								 | 
							
								            sprintf(
							 | 
						||
| 
								 | 
							
								                "Index generation: episode=%s, index=%s, in_range=%s",
							 | 
						||
| 
								 | 
							
								                $ep, $index, $row->{in_range}
							 | 
						||
| 
								 | 
							
								            )
							 | 
						||
| 
								 | 
							
								        );
							 | 
						||
| 
								 | 
							
								        $lastep = $ep;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        #
							 | 
						||
| 
								 | 
							
								        # Save the index for the template
							 | 
						||
| 
								 | 
							
								        #
							 | 
						||
| 
								 | 
							
								        $row->{index} = $index;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        #
							 | 
						||
| 
								 | 
							
								        # Count the valid ones so the template doesn't have to to give a total
							 | 
						||
| 
								 | 
							
								        # for this month
							 | 
						||
| 
								 | 
							
								        #
							 | 
						||
| 
								 | 
							
								        $comment_count++ if $row->{in_range};
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        #
							 | 
						||
| 
								 | 
							
								        # Make the comment text cleaner by removing carriage returns (not sure
							 | 
						||
| 
								 | 
							
								        # why they are there in the first place)
							 | 
						||
| 
								 | 
							
								        #
							 | 
						||
| 
								 | 
							
								        $row->{comment_text} =~ s/\r+//g;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        #
							 | 
						||
| 
								 | 
							
								        # Dates from the database look like '2016-08-01', we compare them to
							 | 
						||
| 
								 | 
							
								        # the month we're processing to see if the comments are attached to
							 | 
						||
| 
								 | 
							
								        # old shows.
							 | 
						||
| 
								 | 
							
								        # FIXME: test for 'in_range' before setting 'past'
							 | 
						||
| 
								 | 
							
								        #
							 | 
						||
| 
								 | 
							
								        if ( validate_date($row->{date},$dt->ymd) ) {
							 | 
						||
| 
								 | 
							
								            $row->{past} = 0;
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								        else {
							 | 
						||
| 
								 | 
							
								            $row->{past} = 1;
							 | 
						||
| 
								 | 
							
								            $past_count++ if $row->{in_range};
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    #
							 | 
						||
| 
								 | 
							
								    # Now prune all of the comments which are not in_range to give the
							 | 
						||
| 
								 | 
							
								    # template an easier job
							 | 
						||
| 
								 | 
							
								    #
							 | 
						||
| 
								 | 
							
								    @$comments = grep { $_->{in_range} } @$comments;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    # Explanation of the resulting structure {{{
							 | 
						||
| 
								 | 
							
								    #
							 | 
						||
| 
								 | 
							
								    # Restructure the comments into two hashes keyed by episode number where
							 | 
						||
| 
								 | 
							
								    # each comment to that episode is stored in sequence in an array. The two
							 | 
						||
| 
								 | 
							
								    # hashes %past and %current hold comments for shows in the past and for
							 | 
						||
| 
								 | 
							
								    # the current month. These can be dealt with separately in the template,
							 | 
						||
| 
								 | 
							
								    # making the logic therein somewhat simpler and clearer.
							 | 
						||
| 
								 | 
							
								    #
							 | 
						||
| 
								 | 
							
								    # The hash (of arrays of hashes) can be visualised thus:
							 | 
						||
| 
								 | 
							
								    # %past = {
							 | 
						||
| 
								 | 
							
								    #          '2457' => [
							 | 
						||
| 
								 | 
							
								    #                      {
							 | 
						||
| 
								 | 
							
								    #                        'past' => 1,
							 | 
						||
| 
								 | 
							
								    #                        'hostid' => 111,
							 | 
						||
| 
								 | 
							
								    #                        'identifier_url' => 'https://hackerpublicradio.org/eps.php?id=2457',
							 | 
						||
| 
								 | 
							
								    #                        'comment_timestamp_ut' => 1556192523,
							 | 
						||
| 
								 | 
							
								    #                        'date' => '2018-01-02',
							 | 
						||
| 
								 | 
							
								    #                        'comment_text' => [snip],
							 | 
						||
| 
								 | 
							
								    #                        'comment_title' => 'aren\'t you forgetting a hub?',
							 | 
						||
| 
								 | 
							
								    #                        'timestamp' => '2019-04-25T11:42:03Z',
							 | 
						||
| 
								 | 
							
								    #                        'in_range' => 1,
							 | 
						||
| 
								 | 
							
								    #                        'index' => 1,
							 | 
						||
| 
								 | 
							
								    #                        'host' => 'knightwise',
							 | 
						||
| 
								 | 
							
								    #                        'title' => 'Getting ready for my new Macbook Pro',
							 | 
						||
| 
								 | 
							
								    #                        'comment_author_name' => 'Bart',
							 | 
						||
| 
								 | 
							
								    #                        'episode' => 2457,
							 | 
						||
| 
								 | 
							
								    #                        'ignore' => 0
							 | 
						||
| 
								 | 
							
								    #                      }
							 | 
						||
| 
								 | 
							
								    #                    ]
							 | 
						||
| 
								 | 
							
								    #          }
							 | 
						||
| 
								 | 
							
								    #}}}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    #
							 | 
						||
| 
								 | 
							
								    # Also, as we go, mark and count the comments in the %past hash which were
							 | 
						||
| 
								 | 
							
								    # likely to have been read in the last show, so by this means simplify
							 | 
						||
| 
								 | 
							
								    # what the template has to do.
							 | 
						||
| 
								 | 
							
								    #
							 | 
						||
| 
								 | 
							
								    for my $row (@$comments) {
							 | 
						||
| 
								 | 
							
								        my $ep = $row->{episode};
							 | 
						||
| 
								 | 
							
								        if ($row->{past}) {
							 | 
						||
| 
								 | 
							
								            if ( $show_comments && $mark_comments ) {
							 | 
						||
| 
								 | 
							
								               #if ( $row->{comment_timestamp_ut} <= $dt_lr->epoch
							 | 
						||
| 
								 | 
							
								                if ( $row->{comment_released_ut} <= $dt_lr->epoch
							 | 
						||
| 
								 | 
							
								                    && substr( $row->{date}, 0, 7 ) eq
							 | 
						||
| 
								 | 
							
								                    substr( $dt_lm->ymd, 0, 7 ) )
							 | 
						||
| 
								 | 
							
								                {
							 | 
						||
| 
								 | 
							
								                    $row->{ignore} = 1;
							 | 
						||
| 
								 | 
							
								                    $ignore_count++;
							 | 
						||
| 
								 | 
							
								                }
							 | 
						||
| 
								 | 
							
								                else {
							 | 
						||
| 
								 | 
							
								                    $row->{ignore} = 0;
							 | 
						||
| 
								 | 
							
								                }
							 | 
						||
| 
								 | 
							
								            }
							 | 
						||
| 
								 | 
							
								            else {
							 | 
						||
| 
								 | 
							
								                $row->{ignore} = 0;
							 | 
						||
| 
								 | 
							
								            }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								            if (exists($past{$ep})) {
							 | 
						||
| 
								 | 
							
								                push(@{$past{$ep}},$row);
							 | 
						||
| 
								 | 
							
								            }
							 | 
						||
| 
								 | 
							
								            else {
							 | 
						||
| 
								 | 
							
								                $past{$ep} = [$row];
							 | 
						||
| 
								 | 
							
								            }
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								        else {
							 | 
						||
| 
								 | 
							
								            if (exists($current{$ep})) {
							 | 
						||
| 
								 | 
							
								                push(@{$current{$ep}},$row);
							 | 
						||
| 
								 | 
							
								            }
							 | 
						||
| 
								 | 
							
								            else {
							 | 
						||
| 
								 | 
							
								                $current{$ep} = [$row];
							 | 
						||
| 
								 | 
							
								            }
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    _debug ($DEBUG > 2,
							 | 
						||
| 
								 | 
							
								        '%past: ' . Dumper(\%past),
							 | 
						||
| 
								 | 
							
								        '%current: ' . Dumper(\%current)
							 | 
						||
| 
								 | 
							
								    );
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    #-------------------------------------------------------------------------------
							 | 
						||
| 
								 | 
							
								    # Make another data structure of missed coments *if* $t_days is true
							 | 
						||
| 
								 | 
							
								    #-------------------------------------------------------------------------------
							 | 
						||
| 
								 | 
							
								    # If $t_days is true then there might be comments from the previous month
							 | 
						||
| 
								 | 
							
								    # that weren't covered in the recording. So we add them to the notes just
							 | 
						||
| 
								 | 
							
								    # for the recording. Trouble is, if they exist they aren't in the comments
							 | 
						||
| 
								 | 
							
								    # we have gathered, so we'll have to go and search for them with this
							 | 
						||
| 
								 | 
							
								    # special query.
							 | 
						||
| 
								 | 
							
								    #
							 | 
						||
| 
								 | 
							
								    if ($t_days) {
							 | 
						||
| 
								 | 
							
								        $sth1 = $dbh->prepare( q{
							 | 
						||
| 
								 | 
							
								            SELECT
							 | 
						||
| 
								 | 
							
								                eps.id AS episode,
							 | 
						||
| 
								 | 
							
								                concat('https://hackerpublicradio.org/eps/hpr',
							 | 
						||
| 
								 | 
							
								                    lpad(eps_id,4,0),'/index.html') AS identifier_url,
							 | 
						||
| 
								 | 
							
								                eps.title,
							 | 
						||
| 
								 | 
							
								                eps.summary,
							 | 
						||
| 
								 | 
							
								                eps.date,
							 | 
						||
| 
								 | 
							
								                ho.host,
							 | 
						||
| 
								 | 
							
								                ho.hostid,
							 | 
						||
| 
								 | 
							
								                date_format(co.comment_timestamp,'%Y-%m-%dT%TZ') AS timestamp,
							 | 
						||
| 
								 | 
							
								                co.comment_author_name,
							 | 
						||
| 
								 | 
							
								                co.comment_title,
							 | 
						||
| 
								 | 
							
								                co.comment_text,
							 | 
						||
| 
								 | 
							
								                unix_timestamp(co.comment_timestamp) AS comment_timestamp_ut
							 | 
						||
| 
								 | 
							
								            FROM comments co
							 | 
						||
| 
								 | 
							
								            JOIN eps ON eps.id = co.eps_id
							 | 
						||
| 
								 | 
							
								            JOIN hosts ho ON eps.hostid = ho.hostid
							 | 
						||
| 
								 | 
							
								            WHERE
							 | 
						||
| 
								 | 
							
								                co.comment_timestamp >= ?
							 | 
						||
| 
								 | 
							
								                AND co.comment_timestamp < (last_day(?)+ interval 1 day)
							 | 
						||
| 
								 | 
							
								            ORDER BY episode ASC, comment_timestamp ASC
							 | 
						||
| 
								 | 
							
								        });
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        #
							 | 
						||
| 
								 | 
							
								        # Need the date and time of the last recording and the start of the
							 | 
						||
| 
								 | 
							
								        # last month we reviewed to perform the query.
							 | 
						||
| 
								 | 
							
								        #
							 | 
						||
| 
								 | 
							
								        # $sth1->execute( $dt_lr->datetime . 'Z', $dt_lm->ymd );
							 | 
						||
| 
								 | 
							
								        $sth1->execute( $dt_lr->ymd, $dt_lm->ymd );
							 | 
						||
| 
								 | 
							
								        if ( $dbh->err ) {
							 | 
						||
| 
								 | 
							
								            carp $dbh->errstr;
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        #
							 | 
						||
| 
								 | 
							
								        # Grab the data as an arrayref of hashrefs
							 | 
						||
| 
								 | 
							
								        #
							 | 
						||
| 
								 | 
							
								        $missed_comments = $sth1->fetchall_arrayref( {} );
							 | 
						||
| 
								 | 
							
								        $missed_count = (
							 | 
						||
| 
								 | 
							
								            defined($missed_comments)
							 | 
						||
| 
								 | 
							
								            ? scalar(@$missed_comments)
							 | 
						||
| 
								 | 
							
								            : 0
							 | 
						||
| 
								 | 
							
								        );
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        #
							 | 
						||
| 
								 | 
							
								        # Make the comment text cleaner by removing carriage returns (not sure
							 | 
						||
| 
								 | 
							
								        # why they are there in the first place)
							 | 
						||
| 
								 | 
							
								        #
							 | 
						||
| 
								 | 
							
								        for my $ch (@$missed_comments) {
							 | 
						||
| 
								 | 
							
								            $ch->{comment_text} =~ s/\r+//g;
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        _debug ($DEBUG > 2,
							 | 
						||
| 
								 | 
							
								            '@missed_comments: ' . Dumper($missed_comments)
							 | 
						||
| 
								 | 
							
								        );
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        #
							 | 
						||
| 
								 | 
							
								        # After a change in design around 2023-05-02 there may be duplicates
							 | 
						||
| 
								 | 
							
								        # in the %past hash and the @$missed_comments array. We need to hide
							 | 
						||
| 
								 | 
							
								        # the former for now. They will not be hidden when $t_days is false
							 | 
						||
| 
								 | 
							
								        # because we're not bothered about missed comments!
							 | 
						||
| 
								 | 
							
								        #
							 | 
						||
| 
								 | 
							
								        if ( $past_count > 0 ) {
							 | 
						||
| 
								 | 
							
								            my @missed_episodes = map { $_->{episode} } @$missed_comments;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								            _debug( $DEBUG > 2,
							 | 
						||
| 
								 | 
							
								                '@missed_episodes: ' . Dumper( \@missed_episodes ) );
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								            delete( @past{@missed_episodes} );
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								            my $old_pc = $past_count;
							 | 
						||
| 
								 | 
							
								            $past_count = scalar( keys(%past) );
							 | 
						||
| 
								 | 
							
								            $comment_count -= ($old_pc - $past_count);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								            _debug(
							 | 
						||
| 
								 | 
							
								                $DEBUG > 2,
							 | 
						||
| 
								 | 
							
								                '%past (edited): ' . Dumper( \%past ),
							 | 
						||
| 
								 | 
							
								                "\$past_count: $past_count",
							 | 
						||
| 
								 | 
							
								                "\$comment_count: $comment_count"
							 | 
						||
| 
								 | 
							
								            );
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								#-------------------------------------------------------------------------------
							 | 
						||
| 
								 | 
							
								# Fill and print the template
							 | 
						||
| 
								 | 
							
								#-------------------------------------------------------------------------------
							 | 
						||
| 
								 | 
							
								my $tt = Template->new(
							 | 
						||
| 
								 | 
							
								    {   ABSOLUTE     => 1,
							 | 
						||
| 
								 | 
							
								        ENCODING     => 'utf8',
							 | 
						||
| 
								 | 
							
								        INCLUDE_PATH => $basedir,
							 | 
						||
| 
								 | 
							
								        FILTERS      => {
							 | 
						||
| 
								 | 
							
								            # For HTML->ASCII in comments_only.tpl, decode HTML entities
							 | 
						||
| 
								 | 
							
								            'decode_entities' => \&my_decode_entities,
							 | 
						||
| 
								 | 
							
								        },
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								my $vars = {
							 | 
						||
| 
								 | 
							
								    review_month    => $dt->month_name,
							 | 
						||
| 
								 | 
							
								    review_year     => $dt->year,
							 | 
						||
| 
								 | 
							
								    hosts           => $hosts,
							 | 
						||
| 
								 | 
							
								    shows           => $shows,
							 | 
						||
| 
								 | 
							
								    comment_count   => $comment_count,
							 | 
						||
| 
								 | 
							
								    past_count      => $past_count,
							 | 
						||
| 
								 | 
							
								    ignore_count    => $ignore_count,
							 | 
						||
| 
								 | 
							
								    missed_count    => $missed_count,
							 | 
						||
| 
								 | 
							
								    missed_comments => $missed_comments,
							 | 
						||
| 
								 | 
							
								    comments        => $comments,                                # legacy
							 | 
						||
| 
								 | 
							
								    past            => \%past,
							 | 
						||
| 
								 | 
							
								    current         => \%current,
							 | 
						||
| 
								 | 
							
								    skip_comments   => ( $show_comments ? 0 : 1 ),
							 | 
						||
| 
								 | 
							
								    mark_comments   => $mark_comments,
							 | 
						||
| 
								 | 
							
								    ctext           => $ctext,
							 | 
						||
| 
								 | 
							
								    last_recording  => ( $mark_comments ? $dt_lr->epoch : 0 ),
							 | 
						||
| 
								 | 
							
								    last_month      => (
							 | 
						||
| 
								 | 
							
								        $mark_comments ? sprintf( "%d-%02d", $dt_lm->year, $dt_lm->month ) : 0
							 | 
						||
| 
								 | 
							
								    ),
							 | 
						||
| 
								 | 
							
								    includefile     => $mailnotes,
							 | 
						||
| 
								 | 
							
								    aob             => defined($aobfile),
							 | 
						||
| 
								 | 
							
								    aobfile         => $aobfile,
							 | 
						||
| 
								 | 
							
								};
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								#print Dumper($vars),"\n";
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								my $document;
							 | 
						||
| 
								 | 
							
								$tt->process( $template, $vars, \$document, { binmode => ':utf8' } )
							 | 
						||
| 
								 | 
							
								    || die $tt->error(), "\n";
							 | 
						||
| 
								 | 
							
								print $outfh $document;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								#
							 | 
						||
| 
								 | 
							
								# The episode number tests passed earlier on, so add the notes to the database
							 | 
						||
| 
								 | 
							
								# if so requested
							 | 
						||
| 
								 | 
							
								#
							 | 
						||
| 
								 | 
							
								if ($episode) {
							 | 
						||
| 
								 | 
							
								    emit( $silent,
							 | 
						||
| 
								 | 
							
								        "Writing shownotes to the database for episode $episode\n" );
							 | 
						||
| 
								 | 
							
								    $sth1 = $dbh->prepare(q{UPDATE eps SET notes = ? WHERE id = ?});
							 | 
						||
| 
								 | 
							
								    $sth1->execute( $document, $episode );
							 | 
						||
| 
								 | 
							
								    if ( $dbh->err ) {
							 | 
						||
| 
								 | 
							
								        carp $dbh->errstr;
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								$dbh->disconnect;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								exit;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								#===  FUNCTION  ================================================================
							 | 
						||
| 
								 | 
							
								#         NAME: parse_to_dc
							 | 
						||
| 
								 | 
							
								#      PURPOSE: Parse a textual date (and optional time) to a Date::Calc
							 | 
						||
| 
								 | 
							
								#               datetime
							 | 
						||
| 
								 | 
							
								#   PARAMETERS: $datetime       Datetime as a string
							 | 
						||
| 
								 | 
							
								#               $deftime        Arrayref default time (as a Date::Calc array
							 | 
						||
| 
								 | 
							
								#                               or undef)
							 | 
						||
| 
								 | 
							
								#      RETURNS: A Date::Calc date (and possibly time) as a list
							 | 
						||
| 
								 | 
							
								#  DESCRIPTION: The $datetime argument is parsed ewith Date::Parse. The year
							 | 
						||
| 
								 | 
							
								#               and month need to be adjusted. If a default time has been
							 | 
						||
| 
								 | 
							
								#               supplied then the parsed time is checked and the default time
							 | 
						||
| 
								 | 
							
								#               used if nothing was found, otherwise the parsed time is used
							 | 
						||
| 
								 | 
							
								#               and a full 6-component time returned.
							 | 
						||
| 
								 | 
							
								#               If the default time us undefined this means we don't care
							 | 
						||
| 
								 | 
							
								#               about the time and so we just return the parsed date as
							 | 
						||
| 
								 | 
							
								#               a 3-component list.
							 | 
						||
| 
								 | 
							
								#       THROWS: No exceptions
							 | 
						||
| 
								 | 
							
								#     COMMENTS: None
							 | 
						||
| 
								 | 
							
								#     SEE ALSO: N/A
							 | 
						||
| 
								 | 
							
								#===============================================================================
							 | 
						||
| 
								 | 
							
								sub parse_to_dc {
							 | 
						||
| 
								 | 
							
								    my ( $datetime, $deftime ) = @_;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    # What strptime returns:
							 | 
						||
| 
								 | 
							
								    #  0   1   2   3    4      5     6
							 | 
						||
| 
								 | 
							
								    # ($ss,$mm,$hh,$day,$month,$year,$zone)
							 | 
						||
| 
								 | 
							
								    #
							 | 
						||
| 
								 | 
							
								    my @parsed = strptime($datetime);
							 | 
						||
| 
								 | 
							
								    die "Invalid DATE or DATETIME '$datetime'\n"
							 | 
						||
| 
								 | 
							
								        unless ( defined( $parsed[3] )
							 | 
						||
| 
								 | 
							
								        && defined( $parsed[4] )
							 | 
						||
| 
								 | 
							
								        && defined( $parsed[5] ) );
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    $parsed[5] += 1900;
							 | 
						||
| 
								 | 
							
								    $parsed[4] += 1;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    if ( defined($deftime) ) {
							 | 
						||
| 
								 | 
							
								        #
							 | 
						||
| 
								 | 
							
								        # If no time was supplied add a default one
							 | 
						||
| 
								 | 
							
								        #
							 | 
						||
| 
								 | 
							
								        unless ( defined( $parsed[2] )
							 | 
						||
| 
								 | 
							
								            && defined( $parsed[1] )
							 | 
						||
| 
								 | 
							
								            && defined( $parsed[0] ) )
							 | 
						||
| 
								 | 
							
								        {
							 | 
						||
| 
								 | 
							
								            @parsed[ 2, 1, 0 ] = @$deftime;
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        #
							 | 
						||
| 
								 | 
							
								        # Return a list
							 | 
						||
| 
								 | 
							
								        #
							 | 
						||
| 
								 | 
							
								        return ( @parsed[ 5, 4, 3, 2, 1, 0 ] );
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								    else {
							 | 
						||
| 
								 | 
							
								        return ( @parsed[ 5, 4, 3 ] );
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								#===  FUNCTION  ================================================================
							 | 
						||
| 
								 | 
							
								#         NAME: dc_to_dt
							 | 
						||
| 
								 | 
							
								#      PURPOSE: Converts a Date::Calc datetime into a DateTime equivalent
							 | 
						||
| 
								 | 
							
								#   PARAMETERS: $refdt          Reference to an array holding a Date::Calc
							 | 
						||
| 
								 | 
							
								#                               date and time
							 | 
						||
| 
								 | 
							
								#      RETURNS: Returns a DateTime object converted from the input
							 | 
						||
| 
								 | 
							
								#  DESCRIPTION: 
							 | 
						||
| 
								 | 
							
								#       THROWS: No exceptions
							 | 
						||
| 
								 | 
							
								#     COMMENTS: None
							 | 
						||
| 
								 | 
							
								#     SEE ALSO: N/A
							 | 
						||
| 
								 | 
							
								#===============================================================================
							 | 
						||
| 
								 | 
							
								sub dc_to_dt {
							 | 
						||
| 
								 | 
							
								    my ($refdt) = @_;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    #
							 | 
						||
| 
								 | 
							
								    # Check we got a 6-element array
							 | 
						||
| 
								 | 
							
								    #
							 | 
						||
| 
								 | 
							
								    if (scalar(@$refdt) != 6) {
							 | 
						||
| 
								 | 
							
								        print  "Invalid Date::Calc date and time (@$refdt)\n";
							 | 
						||
| 
								 | 
							
								        die "Aborted\n";
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    #
							 | 
						||
| 
								 | 
							
								    # Convert to DateTime to get access to formatting stuff
							 | 
						||
| 
								 | 
							
								    #
							 | 
						||
| 
								 | 
							
								    my ( %dtargs, $dt );
							 | 
						||
| 
								 | 
							
								    @dtargs{ 'year', 'month', 'day', 'hour', 'minute', 'second', 'time_zone' }
							 | 
						||
| 
								 | 
							
								        = ( @$refdt, 'UTC' );
							 | 
						||
| 
								 | 
							
								    $dt = DateTime->new(%dtargs);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    #
							 | 
						||
| 
								 | 
							
								    # Return the date as a DateTime object
							 | 
						||
| 
								 | 
							
								    #
							 | 
						||
| 
								 | 
							
								    return $dt;
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								#===  FUNCTION  ================================================================
							 | 
						||
| 
								 | 
							
								#         NAME: find_last_recording
							 | 
						||
| 
								 | 
							
								#      PURPOSE: Finds the recording date of the Community News episode
							 | 
						||
| 
								 | 
							
								#               relating to the target month
							 | 
						||
| 
								 | 
							
								#   PARAMETERS: $refdate        Reference to an array holding a Date::Calc
							 | 
						||
| 
								 | 
							
								#                               date - the first of the selected month
							 | 
						||
| 
								 | 
							
								#               $reftime        Reference to an array holding a Date::Calc
							 | 
						||
| 
								 | 
							
								#                               time (UTC)
							 | 
						||
| 
								 | 
							
								#      RETURNS: A Date::Calc object containing the date and time of the last
							 | 
						||
| 
								 | 
							
								#               Community News recording in the UTC time zone
							 | 
						||
| 
								 | 
							
								#  DESCRIPTION: We want to find the date of the last Community News recording
							 | 
						||
| 
								 | 
							
								#               to determine whether a given comment preceded it. The scenario
							 | 
						||
| 
								 | 
							
								#               is that we're using these notes while making such a recording
							 | 
						||
| 
								 | 
							
								#               and want to know if comments occured before the last one. If
							 | 
						||
| 
								 | 
							
								#               they did we should have read them during that show and don't
							 | 
						||
| 
								 | 
							
								#               need to do so now. We want to pass the date generated here to
							 | 
						||
| 
								 | 
							
								#               the template so it can compare it with comment dates. To
							 | 
						||
| 
								 | 
							
								#               complete the story, the template will mark such comments, but
							 | 
						||
| 
								 | 
							
								#               we'll turn of the marking before the notes are released - they
							 | 
						||
| 
								 | 
							
								#               are just for use by the people recording the episode.
							 | 
						||
| 
								 | 
							
								#       THROWS: No exceptions
							 | 
						||
| 
								 | 
							
								#     COMMENTS: None
							 | 
						||
| 
								 | 
							
								#     SEE ALSO: N/A
							 | 
						||
| 
								 | 
							
								#===============================================================================
							 | 
						||
| 
								 | 
							
								sub find_last_recording {
							 | 
						||
| 
								 | 
							
								    my ($refdate, $reftime) = @_;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    my $monday = 1;                    # Day of week number 1-7, Monday-Sunday
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    #
							 | 
						||
| 
								 | 
							
								    # Using the given date (the requested month), ensure it's the first day of
							 | 
						||
| 
								 | 
							
								    # the month
							 | 
						||
| 
								 | 
							
								    #
							 | 
						||
| 
								 | 
							
								    my @lastmonth = @$refdate;
							 | 
						||
| 
								 | 
							
								    $lastmonth[2] = 1;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    #
							 | 
						||
| 
								 | 
							
								    # Work out the recording date in the target month
							 | 
						||
| 
								 | 
							
								    #
							 | 
						||
| 
								 | 
							
								    @lastmonth = Nth_Weekday_of_Month_Year( @$refdate[ 0, 1 ], $monday, 1 );
							 | 
						||
| 
								 | 
							
								    @lastmonth = Add_Delta_Days( @lastmonth, -2 );
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    #
							 | 
						||
| 
								 | 
							
								    # Return the date as a DateTime object
							 | 
						||
| 
								 | 
							
								    #
							 | 
						||
| 
								 | 
							
								    return (@lastmonth,@$reftime);
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								#===  FUNCTION  ================================================================
							 | 
						||
| 
								 | 
							
								#         NAME: find_last_month
							 | 
						||
| 
								 | 
							
								#      PURPOSE: Finds the previous month for working out marks
							 | 
						||
| 
								 | 
							
								#   PARAMETERS: $refdate        Reference to an array holding a Date::Calc
							 | 
						||
| 
								 | 
							
								#                               date - the first of the selected month
							 | 
						||
| 
								 | 
							
								#      RETURNS: A DateTime object containing the first day of the last month.
							 | 
						||
| 
								 | 
							
								#  DESCRIPTION: We need the details of the last month because if we're marking
							 | 
						||
| 
								 | 
							
								#               comments we have already read out in the previous Community
							 | 
						||
| 
								 | 
							
								#               News show, but don't want to mark comments to shows before
							 | 
						||
| 
								 | 
							
								#               that month, even if the comments fell within the target month.
							 | 
						||
| 
								 | 
							
								#               This is a complex edge condition that wasn't appreciated in the
							 | 
						||
| 
								 | 
							
								#               first implementation.
							 | 
						||
| 
								 | 
							
								#       THROWS: No exceptions
							 | 
						||
| 
								 | 
							
								#     COMMENTS: None
							 | 
						||
| 
								 | 
							
								#     SEE ALSO: N/A
							 | 
						||
| 
								 | 
							
								#===============================================================================
							 | 
						||
| 
								 | 
							
								sub find_last_month {
							 | 
						||
| 
								 | 
							
								    my ($refdate) = @_;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    my $monday = 1;                    # Day of week number 1-7, Monday-Sunday
							 | 
						||
| 
								 | 
							
								    my @starttime = ( 00, 00, 00 );    # UTC
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    #
							 | 
						||
| 
								 | 
							
								    # Using the given date (the requested month for the notes), ensure it's
							 | 
						||
| 
								 | 
							
								    # the first day of the month
							 | 
						||
| 
								 | 
							
								    #
							 | 
						||
| 
								 | 
							
								    my @lastmonth = @$refdate;
							 | 
						||
| 
								 | 
							
								    $lastmonth[2] = 1;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    #
							 | 
						||
| 
								 | 
							
								    # Subtract one day to enter the previous month and force the first day of
							 | 
						||
| 
								 | 
							
								    # the resulting month
							 | 
						||
| 
								 | 
							
								    #
							 | 
						||
| 
								 | 
							
								    @lastmonth = Add_Delta_Days( @lastmonth, -1 );
							 | 
						||
| 
								 | 
							
								    $lastmonth[2] = 1;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    #
							 | 
						||
| 
								 | 
							
								    # Return the date as a DateTime object
							 | 
						||
| 
								 | 
							
								    #
							 | 
						||
| 
								 | 
							
								    return (@lastmonth,@starttime);
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								#===  FUNCTION  ================================================================
							 | 
						||
| 
								 | 
							
								#         NAME: trailing_days
							 | 
						||
| 
								 | 
							
								#      PURPOSE: Determines if the last month had 'trailing' days - those after
							 | 
						||
| 
								 | 
							
								#               the recording date - during which unread comments could have
							 | 
						||
| 
								 | 
							
								#               been posted.
							 | 
						||
| 
								 | 
							
								#   PARAMETERS: $dc_lr          reference to an array containing a Date::Calc
							 | 
						||
| 
								 | 
							
								#                               date of the last recording
							 | 
						||
| 
								 | 
							
								#               $dc_lm          reference to an array containing a Date::Calc
							 | 
						||
| 
								 | 
							
								#                               date of the first day of last month
							 | 
						||
| 
								 | 
							
								#      RETURNS: A true/false result - 1 if there were trailing days,
							 | 
						||
| 
								 | 
							
								#               0 otherwise
							 | 
						||
| 
								 | 
							
								#  DESCRIPTION: If the recording of a Community News show was during the month
							 | 
						||
| 
								 | 
							
								#               being reviewed (e.g. March 2019; recording on 2019-03-30,
							 | 
						||
| 
								 | 
							
								#               release on 2019-04-01) then a comment could have been made
							 | 
						||
| 
								 | 
							
								#               after (or during!) the recording and probably would not have
							 | 
						||
| 
								 | 
							
								#               been read during the show. We want to spot such a case and
							 | 
						||
| 
								 | 
							
								#               highlight it in the *next* recording!
							 | 
						||
| 
								 | 
							
								#               Yes, I know this is obsessive, but it needs a solution!!!
							 | 
						||
| 
								 | 
							
								#       THROWS: No exceptions
							 | 
						||
| 
								 | 
							
								#     COMMENTS: None
							 | 
						||
| 
								 | 
							
								#     SEE ALSO: N/A
							 | 
						||
| 
								 | 
							
								#===============================================================================
							 | 
						||
| 
								 | 
							
								sub trailing_days {
							 | 
						||
| 
								 | 
							
								    my ( $dc_lr, $dc_lm ) = @_;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    my $offset;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    #
							 | 
						||
| 
								 | 
							
								    # Are the last month and the recording month the same?
							 | 
						||
| 
								 | 
							
								    #
							 | 
						||
| 
								 | 
							
								    if ( $dc_lm->[1] eq $dc_lr->[1] ) {
							 | 
						||
| 
								 | 
							
								        #
							 | 
						||
| 
								 | 
							
								        # Compute the offset as Delta_Days((First day of last month + days in
							 | 
						||
| 
								 | 
							
								        # month), recording date). A positive offset (not sure if we'd get
							 | 
						||
| 
								 | 
							
								        # a negative one) means there's some of the month still to go.
							 | 
						||
| 
								 | 
							
								        #
							 | 
						||
| 
								 | 
							
								        $offset
							 | 
						||
| 
								 | 
							
								            = Delta_Days( @$dc_lr[0..2],
							 | 
						||
| 
								 | 
							
								            Add_Delta_Days( @$dc_lm[0..2], Days_in_Month( @$dc_lm[ 0, 1 ] ) ) );
							 | 
						||
| 
								 | 
							
								        return 1 if $offset gt 0;
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    return 0;
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								#===  FUNCTION  ================================================================
							 | 
						||
| 
								 | 
							
								#         NAME: validate_date
							 | 
						||
| 
								 | 
							
								#      PURPOSE: Checks the date found in the database ($date) is before the
							 | 
						||
| 
								 | 
							
								#               reference date ($refdate)
							 | 
						||
| 
								 | 
							
								#   PARAMETERS: $date           Textual date from the database
							 | 
						||
| 
								 | 
							
								#               $refdate        Optional textual date to compare with. If
							 | 
						||
| 
								 | 
							
								#                               omitted then we use 'Today'
							 | 
						||
| 
								 | 
							
								#      RETURNS: True (1) if the $date is later than the $refdate, false (0)
							 | 
						||
| 
								 | 
							
								#               otherwise
							 | 
						||
| 
								 | 
							
								#  DESCRIPTION: We need to check that the script is not being used to change
							 | 
						||
| 
								 | 
							
								#               the notes for a Community News show in the past. This is
							 | 
						||
| 
								 | 
							
								#               because sometimes the generated notes are edited after they
							 | 
						||
| 
								 | 
							
								#               have been created to add other elements, and we do not want to
							 | 
						||
| 
								 | 
							
								#               accidentally destroy such changes. We just compute the
							 | 
						||
| 
								 | 
							
								#               difference between today and the date of the target episode.
							 | 
						||
| 
								 | 
							
								#               If the difference in days is greater than 0 then it's OK.
							 | 
						||
| 
								 | 
							
								#               We also want to be able to use this routine to check whether
							 | 
						||
| 
								 | 
							
								#               comments relate to old shows or this month's.
							 | 
						||
| 
								 | 
							
								#       THROWS: No exceptions
							 | 
						||
| 
								 | 
							
								#     COMMENTS: None
							 | 
						||
| 
								 | 
							
								#     SEE ALSO: N/A
							 | 
						||
| 
								 | 
							
								#===============================================================================
							 | 
						||
| 
								 | 
							
								sub validate_date {
							 | 
						||
| 
								 | 
							
								    my ($date, $refdate) = @_;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    my @refdate;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    unless (defined($date)) {
							 | 
						||
| 
								 | 
							
								        warn "check_date: invalid argument\n";
							 | 
						||
| 
								 | 
							
								        return 0;
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    my @date = ($date =~ /^(\d{4})-(\d{2})-(\d{2})$/);
							 | 
						||
| 
								 | 
							
								    if (defined($refdate)) {
							 | 
						||
| 
								 | 
							
								        @refdate = ($refdate =~ /^(\d{4})-(\d{2})-(\d{2})$/);
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								    else {
							 | 
						||
| 
								 | 
							
								        @refdate = Today();
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    return (Delta_Days(@refdate,@date) >= 0);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								#===  FUNCTION  ================================================================
							 | 
						||
| 
								 | 
							
								#         NAME: my_decode_entities
							 | 
						||
| 
								 | 
							
								#      PURPOSE: Call 'HTML::Entities::decode_entities' as a filter in a template
							 | 
						||
| 
								 | 
							
								#   PARAMETERS: $text           The text string to process
							 | 
						||
| 
								 | 
							
								#      RETURNS: The text string with all HTML entities decoded to Unicode
							 | 
						||
| 
								 | 
							
								#  DESCRIPTION: This is a local filter to be called in a template. The name
							 | 
						||
| 
								 | 
							
								#               it's called by is defined in the 'FILTERS' definition in the
							 | 
						||
| 
								 | 
							
								#               call to Template::new. Maybe this function is redundant and
							 | 
						||
| 
								 | 
							
								#               the real 'decode_entities' could have been called directly,
							 | 
						||
| 
								 | 
							
								#               but this is slightly easier to understand (I think).
							 | 
						||
| 
								 | 
							
								#       THROWS: No exceptions
							 | 
						||
| 
								 | 
							
								#     COMMENTS: None
							 | 
						||
| 
								 | 
							
								#     SEE ALSO: N/A
							 | 
						||
| 
								 | 
							
								#===============================================================================
							 | 
						||
| 
								 | 
							
								sub my_decode_entities {
							 | 
						||
| 
								 | 
							
								    my $text = shift;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    decode_entities($text);
							 | 
						||
| 
								 | 
							
								    return $text;
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								#===  FUNCTION  ================================================================
							 | 
						||
| 
								 | 
							
								#         NAME: emit
							 | 
						||
| 
								 | 
							
								#      PURPOSE: Print text on STDERR unless silent mode has been selected
							 | 
						||
| 
								 | 
							
								#   PARAMETERS: - Boolean indicating whether to be silent or not
							 | 
						||
| 
								 | 
							
								#               - list of arguments to 'print'
							 | 
						||
| 
								 | 
							
								#      RETURNS: Nothing
							 | 
						||
| 
								 | 
							
								#  DESCRIPTION: This is a wrapper around 'print' to determine whether to send
							 | 
						||
| 
								 | 
							
								#               a message to STDERR depending on a boolean. We need this to be
							 | 
						||
| 
								 | 
							
								#               able to make the script silent when the -silent option is
							 | 
						||
| 
								 | 
							
								#               selected
							 | 
						||
| 
								 | 
							
								#       THROWS: No exceptions
							 | 
						||
| 
								 | 
							
								#     COMMENTS: None
							 | 
						||
| 
								 | 
							
								#     SEE ALSO: N/A
							 | 
						||
| 
								 | 
							
								#===============================================================================
							 | 
						||
| 
								 | 
							
								sub emit {
							 | 
						||
| 
								 | 
							
								    unless (shift) {
							 | 
						||
| 
								 | 
							
								        print STDERR @_;
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								#===  FUNCTION  ================================================================
							 | 
						||
| 
								 | 
							
								#         NAME: _debug
							 | 
						||
| 
								 | 
							
								#      PURPOSE: Prints debug reports
							 | 
						||
| 
								 | 
							
								#   PARAMETERS: $active         Boolean: 1 for print, 0 for no print
							 | 
						||
| 
								 | 
							
								#               @messages       List of messages to print
							 | 
						||
| 
								 | 
							
								#      RETURNS: Nothing
							 | 
						||
| 
								 | 
							
								#  DESCRIPTION: Outputs messages to STDERR if $active is true. For each
							 | 
						||
| 
								 | 
							
								#               message it removes any trailing newline and then adds one in
							 | 
						||
| 
								 | 
							
								#               the 'print' so the caller doesn't have to bother. Prepends
							 | 
						||
| 
								 | 
							
								#               each message with 'D> ' to show it's a debug message.
							 | 
						||
| 
								 | 
							
								#       THROWS: No exceptions
							 | 
						||
| 
								 | 
							
								#     COMMENTS: None
							 | 
						||
| 
								 | 
							
								#     SEE ALSO: N/A
							 | 
						||
| 
								 | 
							
								#===============================================================================
							 | 
						||
| 
								 | 
							
								sub _debug {
							 | 
						||
| 
								 | 
							
								    my ( $active, @messages ) = @_;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    return unless $active;
							 | 
						||
| 
								 | 
							
								    foreach my $msg (@messages) {
							 | 
						||
| 
								 | 
							
								        chomp($msg);
							 | 
						||
| 
								 | 
							
								        print STDERR "D> $msg\n";
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								#===  FUNCTION  ================================================================
							 | 
						||
| 
								 | 
							
								#         NAME: Options
							 | 
						||
| 
								 | 
							
								#      PURPOSE: Processes command-line options
							 | 
						||
| 
								 | 
							
								#   PARAMETERS: $optref     Hash reference to hold the options
							 | 
						||
| 
								 | 
							
								#      RETURNS: Undef
							 | 
						||
| 
								 | 
							
								#  DESCRIPTION:
							 | 
						||
| 
								 | 
							
								#       THROWS: no exceptions
							 | 
						||
| 
								 | 
							
								#     COMMENTS: none
							 | 
						||
| 
								 | 
							
								#     SEE ALSO: n/a
							 | 
						||
| 
								 | 
							
								#===============================================================================
							 | 
						||
| 
								 | 
							
								sub Options {
							 | 
						||
| 
								 | 
							
								    my ($optref) = @_;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    my @options = (
							 | 
						||
| 
								 | 
							
								        "help",                   "documentation|man",
							 | 
						||
| 
								 | 
							
								        "debug=i",                "from=s",
							 | 
						||
| 
								 | 
							
								        "out=s",                  "template=s",
							 | 
						||
| 
								 | 
							
								        "comments!",              "markcomments|mc!",
							 | 
						||
| 
								 | 
							
								        "ctext!",                 "lastrecording|lr=s",
							 | 
						||
| 
								 | 
							
								        "silent!",                "episode=s",
							 | 
						||
| 
								 | 
							
								        "overwrite!",             "mailnotes:s",
							 | 
						||
| 
								 | 
							
								        "anyotherbusiness|aob=s", "config=s",
							 | 
						||
| 
								 | 
							
								        "interlock=s"
							 | 
						||
| 
								 | 
							
								    );
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    if ( !GetOptions( $optref, @options ) ) {
							 | 
						||
| 
								 | 
							
								        pod2usage( -msg => "$PROG version $VERSION\n", -exitval => 1 );
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    return;
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								__END__
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
							 | 
						||
| 
								 | 
							
								#  Application Documentation
							 | 
						||
| 
								 | 
							
								#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
							 | 
						||
| 
								 | 
							
								#{{{
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								=head1 NAME
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								make_shownotes - Make HTML show notes for the Hacker Public Radio Community News show
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								=head1 VERSION
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								This documentation refers to B<make_shownotes> version 0.2.2
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								=head1 USAGE
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    make_shownotes [-help] [-documentation] [-from=DATE] [-[no]comments]
							 | 
						||
| 
								 | 
							
								        [-[no]markcomments] [-[no]ctext] [-lastrecording=DATETIME]
							 | 
						||
| 
								 | 
							
								        [-[no]silent] [-out=FILE] [-episode=[N|auto]] [-[no]overwrite]
							 | 
						||
| 
								 | 
							
								        [-mailnotes[=FILE]] [-anyotherbusiness=FILE] [-template=FILE]
							 | 
						||
| 
								 | 
							
								        [-config=FILE] [-interlock=PASSWORD]
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								=head1 OPTIONS
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								=over 8
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								=item B<-help>
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								Displays a brief help message describing the usage of the program, and then exits.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								=item B<-documentation>
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								Displays the entirety of the documentation (using a pager), and then exits. To
							 | 
						||
| 
								 | 
							
								generate a PDF version use:
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    pod2pdf make_shownotes --out=make_shownotes.pdf
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								=item B<-from=DATE>
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								This option is used to indicate the month for which the shownotes are to be
							 | 
						||
| 
								 | 
							
								generated. The script is able to parse a variety of date formats, but it is
							 | 
						||
| 
								 | 
							
								recommended that ISO8601 YYYY-MM-DD format be used (for example 2014-06-30).
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								The day part of the date must be present but is ignored and only the month and
							 | 
						||
| 
								 | 
							
								year parts are used.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								If this option is omitted the current month is used.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								=item B<-[no]comments>
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								This option controls whether the comments pertaining to the selected month are
							 | 
						||
| 
								 | 
							
								included in the output. If the option is omitted then no comments are included
							 | 
						||
| 
								 | 
							
								(B<-nocomments>).
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								=item B<-[no]markcomments> or B<-[no]mc>
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								This option controls whether certain comments are marked in the HTML. The
							 | 
						||
| 
								 | 
							
								default is B<-nomarkcomments>. The option can be abbreviated to B<-mc> and
							 | 
						||
| 
								 | 
							
								B<-nomc>.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								The scenario is that we want to use the notes the script generates while
							 | 
						||
| 
								 | 
							
								making a Community News recording and we also want them to be the show notes
							 | 
						||
| 
								 | 
							
								in the database once the show has been released.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								Certain comments relating to shows earlier than this month were already
							 | 
						||
| 
								 | 
							
								discussed last month, because they were made before that show was recorded. We
							 | 
						||
| 
								 | 
							
								don't want to read them again during this show, so a means of marking them is
							 | 
						||
| 
								 | 
							
								needed.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								The script determines the date of the last recording (or it can be specified
							 | 
						||
| 
								 | 
							
								with the B<-lastrecording=DATETIME> option, or its abbreviation
							 | 
						||
| 
								 | 
							
								B<-lr=DATETIME>) and passes it to the template. The template can then compare
							 | 
						||
| 
								 | 
							
								this date with the dates of the relevant comments and take action to highlight
							 | 
						||
| 
								 | 
							
								those we don't want to re-read. It is up to the template to do what is
							 | 
						||
| 
								 | 
							
								necessary to highlight them.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								The idea is that we will turn off the marking before the notes are released
							 | 
						||
| 
								 | 
							
								- they are just for use by the people recording the episode.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								Another action is taken during the processing of comments when this option is
							 | 
						||
| 
								 | 
							
								on. On some months of the year the recording is made during the month itself
							 | 
						||
| 
								 | 
							
								because the first Monday of the next month is in the first few days of that
							 | 
						||
| 
								 | 
							
								month. For example, in March 2019 the date of recording is the 30th, and the
							 | 
						||
| 
								 | 
							
								show is released on April 1st. Between the recording and the release of the
							 | 
						||
| 
								 | 
							
								show there is time during which more comments could be submitted.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								Such comments should be in the notes for March (and these can be regenerated
							 | 
						||
| 
								 | 
							
								to make sure this is so) but they will not have been read on the March
							 | 
						||
| 
								 | 
							
								recording. The B<make_shownotes> script detects this problem and, if
							 | 
						||
| 
								 | 
							
								B<-markcomments> is set (and comments enabled) will show a list of any
							 | 
						||
| 
								 | 
							
								eligible comments in a red highlighted box. This is so that the volunteers
							 | 
						||
| 
								 | 
							
								recording the show can ensure they read comments that have slipped through
							 | 
						||
| 
								 | 
							
								this loophole. The display shows the entire comment including the contents,
							 | 
						||
| 
								 | 
							
								but disappears when the notes are refreshed with B<-nomarkcomments> (the
							 | 
						||
| 
								 | 
							
								default).
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								In this mode the preamble warning about comments to be ignored used to be
							 | 
						||
| 
								 | 
							
								included, but now it is skipped if there are no such comments. This means one
							 | 
						||
| 
								 | 
							
								switch can serve two purposes.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								=item B<-lastrecording=DATETIME> or B<-lr=DATETIME>
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								As mentioned for B<-markcomments>, the date of the last recording can be
							 | 
						||
| 
								 | 
							
								computed in the assumption that it's on the Saturday before the first Monday
							 | 
						||
| 
								 | 
							
								of the month at 15:00. However, on rare occasions it may be necessary to
							 | 
						||
| 
								 | 
							
								record on an earlier date and time, which cannot be computed. This value can
							 | 
						||
| 
								 | 
							
								be defined with this option.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								The format can be an ISO 8601 date followed by a 24-hour time, such as
							 | 
						||
| 
								 | 
							
								'2020-01-25 15:00'. If the time is omitted it defaults to 15:00.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								=item B<-[no]ctext>
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								This option controls whether the comment text itself is listed with comments.
							 | 
						||
| 
								 | 
							
								This is controlled by the template, but the current default template only
							 | 
						||
| 
								 | 
							
								shows the text in the B<Past shows> section of the output. The default
							 | 
						||
| 
								 | 
							
								state is B<-noctext> in which the comment texts are not written.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								=item B<-[no]silent>
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								This option controls whether the script reports details of its progress
							 | 
						||
| 
								 | 
							
								to STDERR. If the option is omitted the report is generated (B<-nosilent>).
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								The script reports: the month it is working on, the name of the output file
							 | 
						||
| 
								 | 
							
								(if appropriate) and details of the process of writing notes to the database
							 | 
						||
| 
								 | 
							
								(if the B<-episode=[N|auto]> option is selected).
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								=item B<-mailnotes[=FILE]>
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								If desired, the show notes may include a section about recent discussions on
							 | 
						||
| 
								 | 
							
								the HPR mailing list. Obviously, this text will change every month, so this
							 | 
						||
| 
								 | 
							
								option provides a way in which an external file can be included in the show
							 | 
						||
| 
								 | 
							
								notes.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								The filename may be omitted which is a way in which a B<BLOCK> directive can
							 | 
						||
| 
								 | 
							
								be placed in the template and used rather than the file. The B<BLOCK> must be
							 | 
						||
| 
								 | 
							
								named B<default_mail> because this is the name the script uses in this
							 | 
						||
| 
								 | 
							
								circumstance. See B<shownote_template8.tpl> for an example of its use.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								The template must contain instructions to include the file or block. The file
							 | 
						||
| 
								 | 
							
								name is stored in a variable 'B<includefile>' in the template. Directives of
							 | 
						||
| 
								 | 
							
								the following form may be added to achive this:
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    [%- IF includefile.defined %]
							 | 
						||
| 
								 | 
							
								    Constant header, preamble, etc
							 | 
						||
| 
								 | 
							
								    [%- INCLUDE $includefile %]
							 | 
						||
| 
								 | 
							
								    Other constant text or tags
							 | 
						||
| 
								 | 
							
								    [%- END %]
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								The first directive causes the whole block to be ignored if there is no
							 | 
						||
| 
								 | 
							
								B<-mailnotes> option. The use of the B<INCLUDE> directive means that the
							 | 
						||
| 
								 | 
							
								included file may contain Template directives itself if desired.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								See existing templates for examples of how this is done.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								=item B<-anyotherbusiness=FILE> or B<-aob=FILE>
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								If desired the shownotes may contain an 'Any other business' section. This is
							 | 
						||
| 
								 | 
							
								implemented in a template thus:
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    [% IF aob == 1 -%]
							 | 
						||
| 
								 | 
							
								    <h2>Any other business</h2>
							 | 
						||
| 
								 | 
							
								    [% INCLUDE $aobfile -%]
							 | 
						||
| 
								 | 
							
								    [%- END %]
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								The template variable B<aob> is set to 1 if a (valid) file has been provided,
							 | 
						||
| 
								 | 
							
								and the name of the file is in B<aobfile>.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								The included file is assumed to be HTML.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								=item B<-out=FILE>
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								This option defines an output file to receive the show notes. If the option is
							 | 
						||
| 
								 | 
							
								omitted the notes are written to STDOUT, allowing them to be redirected if
							 | 
						||
| 
								 | 
							
								required.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								The output file name may contain the characters 'B<%s>'. This denotes the point
							 | 
						||
| 
								 | 
							
								at which the year and month in the format B<YYYY-MM> are inserted. For example
							 | 
						||
| 
								 | 
							
								if the script is being run for July 2014 the option:
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    -out=shownotes_%s.html
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								will cause the generation of the file:
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    shownotes_2014-07.html
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								=item B<-episode=[N|auto]>
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								This option provides a means of specifying an episode number in the database to
							 | 
						||
| 
								 | 
							
								receive the show notes.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								It either takes a number, or it takes the string 'B<auto>' which makes the
							 | 
						||
| 
								 | 
							
								script find the correct show number.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								First the episode number has to have been reserved in the database. This is
							 | 
						||
| 
								 | 
							
								done by running the script 'B<reserve_cnews>'. This makes a reservation with
							 | 
						||
| 
								 | 
							
								the title "HPR Community News for <monthname> <year>". Normally Community News
							 | 
						||
| 
								 | 
							
								slots are reserved several months in advance.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								Close to the date of the Community News show recording this script can be run
							 | 
						||
| 
								 | 
							
								to write show notes to the database. For example:
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    ./make_shownotes -from=1-Dec-2014 -out=/dev/null \
							 | 
						||
| 
								 | 
							
								        -comm -tem=shownote_template5.tpl -ep=auto
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								This will search for the episode with the title "HPR Community News for
							 | 
						||
| 
								 | 
							
								December 2014" and will add notes if the field is empty. Note that it is
							 | 
						||
| 
								 | 
							
								necessary to direct the output to /dev/null since the script needs to write
							 | 
						||
| 
								 | 
							
								a copy of the notes to STDOUT or to a file. In this case we request comments
							 | 
						||
| 
								 | 
							
								to be added to the notes, and we use the template file
							 | 
						||
| 
								 | 
							
								B<shownote_template5.tpl> which generates an HTML snippet suitable for the
							 | 
						||
| 
								 | 
							
								database.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								The writing of the notes to the database will fail if the field is not empty.
							 | 
						||
| 
								 | 
							
								See the B<-overwrite> option for how to force the notes to be written.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								If the B<-episode=[N|auto]> option is omitted no attempt is made to write to
							 | 
						||
| 
								 | 
							
								the database.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								=item B<-[no]overwrite>
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								This option is only relevant in conjunction with the B<-episode=[N|auto]>
							 | 
						||
| 
								 | 
							
								option. If B<-overwrite> is chosen the new show notes will overwrite any notes
							 | 
						||
| 
								 | 
							
								already in the database. If B<-nooverwrite> is selected, or the option is
							 | 
						||
| 
								 | 
							
								omitted, no over writing will take place - it will only be possible to write
							 | 
						||
| 
								 | 
							
								notes to the database if the field is empty.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								=item B<-template=FILE>
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								This option defines the template used to generate the notes. The template is
							 | 
						||
| 
								 | 
							
								written using the B<Template> toolkit language.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								If the option is omitted then the script uses the file
							 | 
						||
| 
								 | 
							
								B<shownote_template.tpl> in the same directory as the script. If this file
							 | 
						||
| 
								 | 
							
								does not exist then the script will exit with an error message.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								For convenience B<shownote_template.tpl> is a soft link which points to the
							 | 
						||
| 
								 | 
							
								file which is the current default. This allows the development of versions
							 | 
						||
| 
								 | 
							
								without changing the usual way this script is run.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								=item B<-config=FILE>
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								This option allows an alternative configuration file to be used. This file
							 | 
						||
| 
								 | 
							
								defines the location of the database, its port, its name and the username and
							 | 
						||
| 
								 | 
							
								password to be used to access it. This feature was added to allow the script
							 | 
						||
| 
								 | 
							
								to access alternative databases or the live database over an SSH tunnel.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								See the CONFIGURATION AND ENVIRONMENT section below for the file format.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								If the option is omitted the default file is used: B<.hpr_db.cfg>
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								=item B<-interlock=PASSWORD>
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								This option was added to handle the case where the notes for a Community News
							 | 
						||
| 
								 | 
							
								episode have been posted after the show was recorded, but, since the recording
							 | 
						||
| 
								 | 
							
								date was not the last day of the month further comments could be added after
							 | 
						||
| 
								 | 
							
								upload. Logically these comments belong in the previous month's shownotes, so
							 | 
						||
| 
								 | 
							
								we'd need to add them retrospecively.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								Up until the addition of this option the script would not allow the
							 | 
						||
| 
								 | 
							
								regeneration of the notes. This option requires a password to enable the
							 | 
						||
| 
								 | 
							
								feature, but the password is in a constant inside the script. This means that
							 | 
						||
| 
								 | 
							
								it's difficult to run in this mode by accident, but not particulary difficult
							 | 
						||
| 
								 | 
							
								if it's really needed.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								Take care not to run in this mode if the notes have been edited after they
							 | 
						||
| 
								 | 
							
								were generated!
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								=back
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								=head1 DESCRIPTION
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								=head2 Overview
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								This script generates notes for the next Hacker Public Radio I<Community News>
							 | 
						||
| 
								 | 
							
								show. It does this by collecting various details of activity from the HPR
							 | 
						||
| 
								 | 
							
								database and passing them to a template. The default template is called
							 | 
						||
| 
								 | 
							
								B<shownote_template.tpl> and this generates HTML, but any suitable textual
							 | 
						||
| 
								 | 
							
								format could be generated if required, by using a different template.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								=head2 Data Gathering
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								Four types of information are collected by the script:
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								=over 4
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								=item -
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								Details of new hosts who have released new shows in the selected month
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								=item -
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								Details of shows which have been released in the selected month
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								=item -
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								Details of topics on the mailing list in the past month can be included. This
							 | 
						||
| 
								 | 
							
								is only done if the B<-mailnotes=FILE> option is used. This option must
							 | 
						||
| 
								 | 
							
								reference a file of HTML, which may contain Template directives if required.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								=item -
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								Comments which have been submitted to the HPR website in the selected month.
							 | 
						||
| 
								 | 
							
								These need to be related to shows in the current period or in the past.
							 | 
						||
| 
								 | 
							
								Comments made about shows which have not yet been released (but are visible on
							 | 
						||
| 
								 | 
							
								the website) are not included even though they are made in the current month.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								Comments are only gathered if the B<-comments> option is selected.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								=back
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								=head2 Report Generation
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								The four components listed above are formatted in the following way by the
							 | 
						||
| 
								 | 
							
								default template.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								=over 4
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								=item B<New Hosts>
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								These are formatted as a list of links to the hostid with the host's name.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								=item B<Shows>
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								These are formatted into an HTML table containing the show number, title and
							 | 
						||
| 
								 | 
							
								host name. The show title is a link to the show page on the HPR website. The
							 | 
						||
| 
								 | 
							
								host name is a link to the host page on the website.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								=item B<Mailing list discussions>
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								If there have been significant topics on the mailing list in the month in
							 | 
						||
| 
								 | 
							
								question then these can be summarised in this section. This is done by
							 | 
						||
| 
								 | 
							
								preparing an external HTML file and referring to it with the
							 | 
						||
| 
								 | 
							
								B<-mailnotes=FILE> option. If this is done then the file is included into the
							 | 
						||
| 
								 | 
							
								template.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								See the explanation of the B<-mailnotes> option for more details.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								=item B<Comments>
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								These are formatted with <article> tags separated by horizontal lines.
							 | 
						||
| 
								 | 
							
								A <header> shows the author name and title and a <footer> displays a link to
							 | 
						||
| 
								 | 
							
								the show and the show's host and the show title is also included. The body of
							 | 
						||
| 
								 | 
							
								the article contains the comment text with line breaks.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								=back
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								=head2 Variable, Field and Hash names
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								If you wish to write your own template refer to the following lists for the
							 | 
						||
| 
								 | 
							
								names of items. Also refer to the default template B<shownote_template.tpl>
							 | 
						||
| 
								 | 
							
								for the techniques used there. (Note that B<shownote_template.tpl> is a link
							 | 
						||
| 
								 | 
							
								to the current default template, such as B<shownote_template8.tpl>).
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								The hash and field names available to the template are as follows
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								=over 4
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								=item B<Global variables>
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								 Variable Name          Details
							 | 
						||
| 
								 | 
							
								 -------------          -------
							 | 
						||
| 
								 | 
							
								 review_month           The month name of the report date
							 | 
						||
| 
								 | 
							
								 review_year            The year of the report date
							 | 
						||
| 
								 | 
							
								 comment_count          The number of comments in total
							 | 
						||
| 
								 | 
							
								 past_count             The number of comments on old shows
							 | 
						||
| 
								 | 
							
								 skip_comments          Set when -comments is omitted
							 | 
						||
| 
								 | 
							
								 mark_comments          Set when -markcomments is used
							 | 
						||
| 
								 | 
							
								 ctext                  Set when the comment bodies in the 'Past shows'
							 | 
						||
| 
								 | 
							
								                        section are to be shown
							 | 
						||
| 
								 | 
							
								 last_recording         The date the last recording was made
							 | 
						||
| 
								 | 
							
								                         (computed if -markcomments is selected) in
							 | 
						||
| 
								 | 
							
								                         Unixtime format
							 | 
						||
| 
								 | 
							
								 last_month             The month prior to the month for which the notes are
							 | 
						||
| 
								 | 
							
								                        being generated (computed if -markcomments is
							 | 
						||
| 
								 | 
							
								                        selected) in 'YYYY-MM' format
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								=item B<New Hosts>
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								The name of the hash in the template is B<hosts>. The hash might be empty if
							 | 
						||
| 
								 | 
							
								there are no new hosts in the month. See the default template for how to
							 | 
						||
| 
								 | 
							
								handle this.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								 Field Name     Details
							 | 
						||
| 
								 | 
							
								 ----------     -------
							 | 
						||
| 
								 | 
							
								 host           Name of host
							 | 
						||
| 
								 | 
							
								 hostid         Host id number
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								=item B<Show Details>
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								The name of the hash in the template is B<shows>. Note that there are more
							 | 
						||
| 
								 | 
							
								fields available than are used in the default template. Note also that certain
							 | 
						||
| 
								 | 
							
								field names are aliases to avoid clashes (e.g. eps_hostid and ho_hostid).
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								 Field Name     Details
							 | 
						||
| 
								 | 
							
								 ----------     -------
							 | 
						||
| 
								 | 
							
								 eps_id         Episode number
							 | 
						||
| 
								 | 
							
								 date           Episode date
							 | 
						||
| 
								 | 
							
								 title          Episode title
							 | 
						||
| 
								 | 
							
								 length         Episode duration
							 | 
						||
| 
								 | 
							
								 summary        Episode summary
							 | 
						||
| 
								 | 
							
								 notes          Episode show notes
							 | 
						||
| 
								 | 
							
								 eps_hostid     The numerical host id from the 'eps' table
							 | 
						||
| 
								 | 
							
								 series         The series number from the 'eps' table
							 | 
						||
| 
								 | 
							
								 explicit       The explicit marker for the show
							 | 
						||
| 
								 | 
							
								 eps_license    The license for the show
							 | 
						||
| 
								 | 
							
								 tags           The show's tags as a comma-delimited string
							 | 
						||
| 
								 | 
							
								 version        ?Obsolete?
							 | 
						||
| 
								 | 
							
								 eps_valid      The valid value from the 'eps' table
							 | 
						||
| 
								 | 
							
								 ho_hostid      The host id number form the 'hosts' table
							 | 
						||
| 
								 | 
							
								 ho_host        The host name
							 | 
						||
| 
								 | 
							
								 email          The hosts's email address (true address - caution)
							 | 
						||
| 
								 | 
							
								 profile        The host's profile
							 | 
						||
| 
								 | 
							
								 ho_license     The default license for the host
							 | 
						||
| 
								 | 
							
								 ho_valid       The valid value from the 'hosts' table
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								=item B<Mailing List Notes>
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								The variable B<includefile> contains the path to the file (which may only be
							 | 
						||
| 
								 | 
							
								located in the same directory as the script).
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								=item B<Comment Details>
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								Two hashes are created for comments. The hash named B<past> contains comments
							 | 
						||
| 
								 | 
							
								to shows before the current month, and B<current> contains comments to this
							 | 
						||
| 
								 | 
							
								month's shows. Note that these hashes are only populated if the B<-comments>
							 | 
						||
| 
								 | 
							
								option is provided. Both hashes have the same structure.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								 Field Name             Details
							 | 
						||
| 
								 | 
							
								 ----------             -------
							 | 
						||
| 
								 | 
							
								 episode                Episode number
							 | 
						||
| 
								 | 
							
								 identifier_url         Full show URL
							 | 
						||
| 
								 | 
							
								 title                  Episode title
							 | 
						||
| 
								 | 
							
								 date                   Episode date
							 | 
						||
| 
								 | 
							
								 host                   Host name
							 | 
						||
| 
								 | 
							
								 hostid                 Host id number
							 | 
						||
| 
								 | 
							
								 timestamp              Comment timestamp in ISO8601 format
							 | 
						||
| 
								 | 
							
								 comment_author_name    Name of the commenter
							 | 
						||
| 
								 | 
							
								 comment_title          Title of comment
							 | 
						||
| 
								 | 
							
								 comment_text           Text of the comment
							 | 
						||
| 
								 | 
							
								 comment_timestamp_ut   Comment timestamp in Unixtime format
							 | 
						||
| 
								 | 
							
								 in_range               Boolean (0/1) denoting whether the comment was made
							 | 
						||
| 
								 | 
							
								                        in the target month
							 | 
						||
| 
								 | 
							
								 index                  The numerical index of the comment for a given show
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								The purpose of the B<in_range> value is to denote whether a comment was made
							 | 
						||
| 
								 | 
							
								in the target month. This is used in the script to split the comments into the
							 | 
						||
| 
								 | 
							
								B<past> and B<current> hashes. It is therefore of little use in the template,
							 | 
						||
| 
								 | 
							
								but is retained in case it might be useful. The B<index> value can be used in
							 | 
						||
| 
								 | 
							
								the template to refer to the comment, make linking URLs etc. It is generated
							 | 
						||
| 
								 | 
							
								by the script (unfortunately it couldn't be done in the SQL).
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								=back
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								=head2 Filters
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								A filter called B<decode_entities> is available to the template. The reason
							 | 
						||
| 
								 | 
							
								for creating this was when the HTML of a comment is being listed as text
							 | 
						||
| 
								 | 
							
								(Unicode actually). Since comment text is stored in the database as HTML with
							 | 
						||
| 
								 | 
							
								entities when appropriate this is needed to prevent the plain text showing
							 | 
						||
| 
								 | 
							
								I<&> and the like verbatim. It is currently used in B<comments_only.tpl>.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								=head1 DIAGNOSTICS
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								=over 4
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								=item B<Unable to find configuration file ...>
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								The nominated configuration file in B<-config=FILE> (or the default file)
							 | 
						||
| 
								 | 
							
								cannot be found.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								=item B<Episode number must be greater than zero>
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								The B<-episode=N> option must use a positive number.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								=item B<Episode must be a number or 'auto'>
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								The B<-episode=> option must be followed by a number or the word 'auto'
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								=item B<Error: Unable to find includefile ...>
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								The include file referred to in the error message is missing.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								=item B<Error: Unable to find template ...>
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								The template file referred to in the error message is missing.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								=item B<Invalid -from=DATE option '...'>
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								The date provided through the B<-from=DATE> option is invalid. Use an ISO8601
							 | 
						||
| 
								 | 
							
								date in the format YYYY-MM-DD.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								=item B<Unable to open ... for writing: ...>
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								The file specified in the B<-out=FILE> option cannot be written to. This may
							 | 
						||
| 
								 | 
							
								be because you do not have permission to write to the file or directory.
							 | 
						||
| 
								 | 
							
								Further information about why this failed should be included in the message.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								=item B<Unable to initialise for writing: ...>
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								The script was unable to open STDOUT for writing the report. Further
							 | 
						||
| 
								 | 
							
								information about why this failed should be included in the message.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								=item B<Error: wrong show selected>
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								The B<-episode=N> option has been selected and the script is checking the
							 | 
						||
| 
								 | 
							
								numbered show but has not found a Community News title.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								=item B<Error: show ... has a date in the past>
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								The B<-episode=> option has been selected and a Community News show entry has
							 | 
						||
| 
								 | 
							
								been found in the database. However, this entry is for today's show or is in
							 | 
						||
| 
								 | 
							
								the past, which is not permitted. It is possible to override this restriction
							 | 
						||
| 
								 | 
							
								by using the B<-interlock=PASSWORD> option. See the relevant documentation for
							 | 
						||
| 
								 | 
							
								details.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								=item B<Error: show ... already has notes>
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								The B<-episode=> option has been selected and a Community News show entry has
							 | 
						||
| 
								 | 
							
								been found in the database. However, this entry already has notes associated
							 | 
						||
| 
								 | 
							
								with it and the B<-overwrite> option has not been specified.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								=item B<Error: episode ... does not exist in the database>
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								The B<-episode=N> option has been selected but the script cannot find this
							 | 
						||
| 
								 | 
							
								episode number in the database.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								=item B<Error: Unable to find an episode for this month's notes>
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								The B<-episode=auto> option has been selected but the script cannot find the
							 | 
						||
| 
								 | 
							
								episode for the month being processed.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								Possible reasons for this are that the show has not been reserved in the
							 | 
						||
| 
								 | 
							
								database or that the title is not as expected. Use B<reserve_cnews> to reserve
							 | 
						||
| 
								 | 
							
								the slot. The title should be "HPR Community News for <monthname> <year>".
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								=back
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								=head1 CONFIGURATION AND ENVIRONMENT
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								The script obtains the credentials it requires to open the HPR database from
							 | 
						||
| 
								 | 
							
								a configuration file. The name of the file it expects is B<.hpr_db.cfg> in the
							 | 
						||
| 
								 | 
							
								directory holding the script. To change this will require changing the script.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								The configuration file format is as follows:
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								 <database>
							 | 
						||
| 
								 | 
							
								     host = 127.0.0.1
							 | 
						||
| 
								 | 
							
								     port = PORT
							 | 
						||
| 
								 | 
							
								     name = DATABASE
							 | 
						||
| 
								 | 
							
								     user = USERNAME
							 | 
						||
| 
								 | 
							
								     password = PASSWORD
							 | 
						||
| 
								 | 
							
								 </database>
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								=head1 DEPENDENCIES
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								 Carp
							 | 
						||
| 
								 | 
							
								 Config::General
							 | 
						||
| 
								 | 
							
								 Date::Calc
							 | 
						||
| 
								 | 
							
								 Date::Parse
							 | 
						||
| 
								 | 
							
								 DateTime
							 | 
						||
| 
								 | 
							
								 DateTime::Duration
							 | 
						||
| 
								 | 
							
								 DBI
							 | 
						||
| 
								 | 
							
								 Getopt::Long
							 | 
						||
| 
								 | 
							
								 Pod::Usage
							 | 
						||
| 
								 | 
							
								 Template
							 | 
						||
| 
								 | 
							
								 Template::Filters
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								=head1 BUGS AND LIMITATIONS
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								There are no known bugs in this module.
							 | 
						||
| 
								 | 
							
								Please report problems to Dave Morriss (Dave.Morriss@gmail.com)
							 | 
						||
| 
								 | 
							
								Patches are welcome.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								=head1 AUTHOR
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								Dave Morriss  (Dave.Morriss@gmail.com)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								=head1 LICENCE AND COPYRIGHT
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								Copyright (c) 2014-2019 Dave Morriss (Dave.Morriss@gmail.com). All rights reserved.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								This module is free software; you can redistribute it and/or
							 | 
						||
| 
								 | 
							
								modify it under the same terms as Perl itself. See perldoc perlartistic.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								This program is distributed in the hope that it will be useful
							 | 
						||
| 
								 | 
							
								but WITHOUT ANY WARRANTY; without even the implied warranty of
							 | 
						||
| 
								 | 
							
								MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								=cut
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								#}}}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								# [zo to open fold, zc to close or za to toggle]
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								# vim: syntax=perl:ts=8:sw=4:et:ai:tw=78:fo=tcrqn21:fdm=marker
							 |