1579 lines
44 KiB
Plaintext
1579 lines
44 KiB
Plaintext
|
#!/usr/bin/env perl
|
||
|
#===============================================================================
|
||
|
#
|
||
|
# FILE: make_email
|
||
|
#
|
||
|
# USAGE: ./make_email [-debug=N] [-month=DATE] [-from=ADDRESS]
|
||
|
# [-to=ADDRESS] [-[no]mail] [-date=DATE] [-start=START_TIME]
|
||
|
# [-end=END_TIME] [-config=FILE] [-dbconfig=FILE]
|
||
|
#
|
||
|
# DESCRIPTION: Make and send an invitation email for the next Community News
|
||
|
# with times per timezone.
|
||
|
#
|
||
|
# The configuration file (.make_email.cfg) defines the name of
|
||
|
# the email template and the defaults used when generating the
|
||
|
# message. The date of the recording is computed from the
|
||
|
# current month (Saturday before the first Monday of the month
|
||
|
# when the show will be posted). It can also be specified
|
||
|
# through the -date=DATE option.
|
||
|
#
|
||
|
# The month the email relates to can be changed through the
|
||
|
# -month=DATA option, though this is rarely used. Use a date of
|
||
|
# the format 'YYYY-MM-DD' here. The day is ignored but the year
|
||
|
# and month are used in the computation. The month specified
|
||
|
# must be in the future.
|
||
|
#
|
||
|
# The database configuration file defines the database to be
|
||
|
# used to compute the date and show number. Use .hpr_db.cfg for
|
||
|
# the local MariaDB copy (for testing) and .hpr_livedb.cfg for
|
||
|
# the live database (over the ssh tunnel, which must have been
|
||
|
# opened already).
|
||
|
#
|
||
|
# OPTIONS: ---
|
||
|
# REQUIREMENTS: ---
|
||
|
# BUGS: ---
|
||
|
#
|
||
|
# NOTES: Does not send the email at present. Needs work
|
||
|
# 2022-02-28: DBD::MariaDB has vanished, had to revert to MySQL
|
||
|
# again
|
||
|
#
|
||
|
# AUTHOR: Dave Morriss (djm), Dave.Morriss@gmail.com
|
||
|
# VERSION: 0.2.7
|
||
|
# CREATED: 2013-10-28 20:35:22
|
||
|
# REVISION: 2024-05-24 18:53:17
|
||
|
#
|
||
|
#===============================================================================
|
||
|
|
||
|
use 5.010;
|
||
|
use strict;
|
||
|
use warnings;
|
||
|
|
||
|
use Getopt::Long;
|
||
|
use Pod::Usage;
|
||
|
|
||
|
use Config::General;
|
||
|
|
||
|
use Date::Parse;
|
||
|
|
||
|
use DateTime;
|
||
|
use DateTime::TimeZone;
|
||
|
use DateTime::Format::Duration;
|
||
|
|
||
|
use Date::Calc qw{:all};
|
||
|
|
||
|
use Template;
|
||
|
|
||
|
use Mail::Mailer;
|
||
|
|
||
|
use DBI;
|
||
|
|
||
|
use Data::Dumper;
|
||
|
|
||
|
#
|
||
|
# Version number (manually incremented)
|
||
|
#
|
||
|
our $VERSION = '0.2.7';
|
||
|
|
||
|
#
|
||
|
# Script name
|
||
|
#
|
||
|
( my $PROG = $0 ) =~ s|.*/||mx;
|
||
|
|
||
|
#-------------------------------------------------------------------------------
|
||
|
# Declarations
|
||
|
#-------------------------------------------------------------------------------
|
||
|
#
|
||
|
# Constants and other declarations
|
||
|
#
|
||
|
my $basedir = "$ENV{HOME}/HPR/Community_News";
|
||
|
my $configfile1 = "$basedir/.${PROG}.cfg";
|
||
|
my $configfile2 = "$basedir/.hpr_db.cfg";
|
||
|
|
||
|
my ( $dbh, $sth1, $sth2, $sth3, $h1, $h2, $rv );
|
||
|
|
||
|
#
|
||
|
# The timezones we want to report. These were generated with
|
||
|
# DateTime::TimeZone->all_names(). Just uncomment the desired elements.
|
||
|
#
|
||
|
my @zones = (
|
||
|
#{{{
|
||
|
#'Africa/Abidjan',
|
||
|
#'Africa/Accra',
|
||
|
#'Africa/Addis_Ababa',
|
||
|
#'Africa/Algiers',
|
||
|
#'Africa/Asmara',
|
||
|
#'Africa/Bamako',
|
||
|
#'Africa/Bangui',
|
||
|
#'Africa/Banjul',
|
||
|
#'Africa/Bissau',
|
||
|
#'Africa/Blantyre',
|
||
|
#'Africa/Brazzaville',
|
||
|
#'Africa/Bujumbura',
|
||
|
#'Africa/Cairo',
|
||
|
#'Africa/Casablanca',
|
||
|
#'Africa/Ceuta',
|
||
|
#'Africa/Conakry',
|
||
|
#'Africa/Dakar',
|
||
|
#'Africa/Dar_es_Salaam',
|
||
|
#'Africa/Djibouti',
|
||
|
#'Africa/Douala',
|
||
|
#'Africa/El_Aaiun',
|
||
|
#'Africa/Freetown',
|
||
|
#'Africa/Gaborone',
|
||
|
#'Africa/Harare',
|
||
|
#'Africa/Johannesburg',
|
||
|
#'Africa/Kampala',
|
||
|
#'Africa/Khartoum',
|
||
|
#'Africa/Kigali',
|
||
|
#'Africa/Kinshasa',
|
||
|
#'Africa/Lagos',
|
||
|
#'Africa/Libreville',
|
||
|
#'Africa/Lome',
|
||
|
#'Africa/Luanda',
|
||
|
#'Africa/Lubumbashi',
|
||
|
#'Africa/Lusaka',
|
||
|
#'Africa/Malabo',
|
||
|
#'Africa/Maputo',
|
||
|
#'Africa/Maseru',
|
||
|
#'Africa/Mbabane',
|
||
|
#'Africa/Mogadishu',
|
||
|
#'Africa/Monrovia',
|
||
|
#'Africa/Nairobi',
|
||
|
#'Africa/Ndjamena',
|
||
|
#'Africa/Niamey',
|
||
|
#'Africa/Nouakchott',
|
||
|
#'Africa/Ouagadougou',
|
||
|
#'Africa/Porto-Novo',
|
||
|
#'Africa/Sao_Tome',
|
||
|
#'Africa/Tripoli',
|
||
|
#'Africa/Tunis',
|
||
|
#'Africa/Windhoek',
|
||
|
#'America/Adak',
|
||
|
#'America/Anchorage',
|
||
|
#'America/Antigua',
|
||
|
#'America/Araguaina',
|
||
|
#'America/Argentina/Buenos_Aires',
|
||
|
#'America/Argentina/Catamarca',
|
||
|
#'America/Argentina/Cordoba',
|
||
|
#'America/Argentina/Jujuy',
|
||
|
#'America/Argentina/La_Rioja',
|
||
|
#'America/Argentina/Mendoza',
|
||
|
#'America/Argentina/Rio_Gallegos',
|
||
|
#'America/Argentina/Salta',
|
||
|
#'America/Argentina/San_Juan',
|
||
|
#'America/Argentina/San_Luis',
|
||
|
#'America/Argentina/Tucuman',
|
||
|
#'America/Argentina/Ushuaia',
|
||
|
#'America/Asuncion',
|
||
|
#'America/Atikokan',
|
||
|
#'America/Bahia',
|
||
|
#'America/Bahia_Banderas',
|
||
|
#'America/Barbados',
|
||
|
#'America/Belem',
|
||
|
#'America/Belize',
|
||
|
#'America/Blanc-Sablon',
|
||
|
#'America/Boa_Vista',
|
||
|
#'America/Bogota',
|
||
|
#'America/Boise',
|
||
|
#'America/Cambridge_Bay',
|
||
|
#'America/Campo_Grande',
|
||
|
#'America/Cancun',
|
||
|
#'America/Caracas',
|
||
|
#'America/Cayenne',
|
||
|
#'America/Cayman',
|
||
|
'America/Chicago',
|
||
|
#'America/Chihuahua',
|
||
|
#'America/Costa_Rica',
|
||
|
#'America/Creston',
|
||
|
#'America/Cuiaba',
|
||
|
#'America/Curacao',
|
||
|
#'America/Danmarkshavn',
|
||
|
#'America/Dawson',
|
||
|
#'America/Dawson_Creek',
|
||
|
#'America/Denver',
|
||
|
#'America/Detroit',
|
||
|
#'America/Edmonton',
|
||
|
#'America/Eirunepe',
|
||
|
#'America/El_Salvador',
|
||
|
#'America/Fortaleza',
|
||
|
#'America/Glace_Bay',
|
||
|
#'America/Godthab',
|
||
|
#'America/Goose_Bay',
|
||
|
#'America/Grand_Turk',
|
||
|
#'America/Guatemala',
|
||
|
#'America/Guayaquil',
|
||
|
#'America/Guyana',
|
||
|
#'America/Halifax',
|
||
|
#'America/Havana',
|
||
|
#'America/Hermosillo',
|
||
|
#'America/Indiana/Indianapolis',
|
||
|
#'America/Indiana/Knox',
|
||
|
#'America/Indiana/Marengo',
|
||
|
#'America/Indiana/Petersburg',
|
||
|
#'America/Indiana/Tell_City',
|
||
|
#'America/Indiana/Vevay',
|
||
|
#'America/Indiana/Vincennes',
|
||
|
#'America/Indiana/Winamac',
|
||
|
#'America/Inuvik',
|
||
|
#'America/Iqaluit',
|
||
|
#'America/Jamaica',
|
||
|
#'America/Juneau',
|
||
|
#'America/Kentucky/Louisville',
|
||
|
#'America/Kentucky/Monticello',
|
||
|
#'America/La_Paz',
|
||
|
#'America/Lima',
|
||
|
'America/Los_Angeles',
|
||
|
#'America/Maceio',
|
||
|
#'America/Managua',
|
||
|
#'America/Manaus',
|
||
|
#'America/Martinique',
|
||
|
#'America/Matamoros',
|
||
|
#'America/Mazatlan',
|
||
|
#'America/Menominee',
|
||
|
#'America/Merida',
|
||
|
#'America/Metlakatla',
|
||
|
#'America/Mexico_City',
|
||
|
#'America/Miquelon',
|
||
|
#'America/Moncton',
|
||
|
#'America/Monterrey',
|
||
|
#'America/Montevideo',
|
||
|
#'America/Montreal',
|
||
|
#'America/Nassau',
|
||
|
'America/New_York',
|
||
|
#'America/Nipigon',
|
||
|
#'America/Nome',
|
||
|
#'America/Noronha',
|
||
|
#'America/North_Dakota/Beulah',
|
||
|
#'America/North_Dakota/Center',
|
||
|
#'America/North_Dakota/New_Salem',
|
||
|
#'America/Ojinaga',
|
||
|
#'America/Panama',
|
||
|
#'America/Pangnirtung',
|
||
|
#'America/Paramaribo',
|
||
|
#'America/Phoenix',
|
||
|
#'America/Port-au-Prince',
|
||
|
#'America/Port_of_Spain',
|
||
|
#'America/Porto_Velho',
|
||
|
#'America/Puerto_Rico',
|
||
|
#'America/Rainy_River',
|
||
|
#'America/Rankin_Inlet',
|
||
|
#'America/Recife',
|
||
|
#'America/Regina',
|
||
|
#'America/Resolute',
|
||
|
#'America/Rio_Branco',
|
||
|
#'America/Santa_Isabel',
|
||
|
#'America/Santarem',
|
||
|
#'America/Santiago',
|
||
|
#'America/Santo_Domingo',
|
||
|
#'America/Sao_Paulo',
|
||
|
#'America/Scoresbysund',
|
||
|
#'America/Sitka',
|
||
|
#'America/St_Johns',
|
||
|
#'America/Swift_Current',
|
||
|
#'America/Tegucigalpa',
|
||
|
#'America/Thule',
|
||
|
#'America/Thunder_Bay',
|
||
|
#'America/Tijuana',
|
||
|
#'America/Toronto',
|
||
|
#'America/Vancouver',
|
||
|
#'America/Whitehorse',
|
||
|
#'America/Winnipeg',
|
||
|
#'America/Yakutat',
|
||
|
#'America/Yellowknife',
|
||
|
#'Antarctica/Casey',
|
||
|
#'Antarctica/Davis',
|
||
|
#'Antarctica/DumontDUrville',
|
||
|
#'Antarctica/Macquarie',
|
||
|
#'Antarctica/Mawson',
|
||
|
#'Antarctica/Palmer',
|
||
|
#'Antarctica/Rothera',
|
||
|
#'Antarctica/Syowa',
|
||
|
#'Antarctica/Vostok',
|
||
|
#'Asia/Aden',
|
||
|
#'Asia/Almaty',
|
||
|
#'Asia/Amman',
|
||
|
#'Asia/Anadyr',
|
||
|
#'Asia/Aqtau',
|
||
|
#'Asia/Aqtobe',
|
||
|
#'Asia/Ashgabat',
|
||
|
#'Asia/Baghdad',
|
||
|
#'Asia/Bahrain',
|
||
|
#'Asia/Baku',
|
||
|
#'Asia/Bangkok',
|
||
|
#'Asia/Beirut',
|
||
|
#'Asia/Bishkek',
|
||
|
#'Asia/Brunei',
|
||
|
#'Asia/Choibalsan',
|
||
|
#'Asia/Chongqing',
|
||
|
#'Asia/Colombo',
|
||
|
#'Asia/Damascus',
|
||
|
#'Asia/Dhaka',
|
||
|
#'Asia/Dili',
|
||
|
#'Asia/Dubai',
|
||
|
#'Asia/Dushanbe',
|
||
|
#'Asia/Gaza',
|
||
|
#'Asia/Harbin',
|
||
|
#'Asia/Hebron',
|
||
|
#'Asia/Ho_Chi_Minh',
|
||
|
'Asia/Hong_Kong',
|
||
|
#'Asia/Hovd',
|
||
|
#'Asia/Irkutsk',
|
||
|
#'Asia/Jakarta',
|
||
|
#'Asia/Jayapura',
|
||
|
#'Asia/Jerusalem',
|
||
|
#'Asia/Kabul',
|
||
|
#'Asia/Kamchatka',
|
||
|
#'Asia/Karachi',
|
||
|
#'Asia/Kashgar',
|
||
|
#'Asia/Kathmandu',
|
||
|
#'Asia/Khandyga',
|
||
|
#'Asia/Kolkata',
|
||
|
#'Asia/Krasnoyarsk',
|
||
|
#'Asia/Kuala_Lumpur',
|
||
|
#'Asia/Kuching',
|
||
|
#'Asia/Kuwait',
|
||
|
#'Asia/Macau',
|
||
|
#'Asia/Magadan',
|
||
|
#'Asia/Makassar',
|
||
|
#'Asia/Manila',
|
||
|
#'Asia/Muscat',
|
||
|
#'Asia/Nicosia',
|
||
|
#'Asia/Novokuznetsk',
|
||
|
#'Asia/Novosibirsk',
|
||
|
#'Asia/Omsk',
|
||
|
#'Asia/Oral',
|
||
|
#'Asia/Phnom_Penh',
|
||
|
#'Asia/Pontianak',
|
||
|
#'Asia/Pyongyang',
|
||
|
#'Asia/Qatar',
|
||
|
#'Asia/Qyzylorda',
|
||
|
#'Asia/Rangoon',
|
||
|
#'Asia/Riyadh',
|
||
|
#'Asia/Sakhalin',
|
||
|
#'Asia/Samarkand',
|
||
|
#'Asia/Seoul',
|
||
|
#'Asia/Shanghai',
|
||
|
#'Asia/Singapore',
|
||
|
#'Asia/Taipei',
|
||
|
#'Asia/Tashkent',
|
||
|
#'Asia/Tbilisi',
|
||
|
#'Asia/Tehran',
|
||
|
#'Asia/Thimphu',
|
||
|
#'Asia/Tokyo',
|
||
|
#'Asia/Ulaanbaatar',
|
||
|
#'Asia/Urumqi',
|
||
|
#'Asia/Ust-Nera',
|
||
|
#'Asia/Vientiane',
|
||
|
#'Asia/Vladivostok',
|
||
|
#'Asia/Yakutsk',
|
||
|
#'Asia/Yekaterinburg',
|
||
|
#'Asia/Yerevan',
|
||
|
#'Atlantic/Azores',
|
||
|
#'Atlantic/Bermuda',
|
||
|
#'Atlantic/Canary',
|
||
|
#'Atlantic/Cape_Verde',
|
||
|
#'Atlantic/Faroe',
|
||
|
#'Atlantic/Madeira',
|
||
|
#'Atlantic/Reykjavik',
|
||
|
#'Atlantic/South_Georgia',
|
||
|
#'Atlantic/St_Helena',
|
||
|
#'Atlantic/Stanley',
|
||
|
#'Australia/Adelaide',
|
||
|
'Australia/Brisbane',
|
||
|
#'Australia/Broken_Hill',
|
||
|
#'Australia/Currie',
|
||
|
#'Australia/Darwin',
|
||
|
#'Australia/Eucla',
|
||
|
#'Australia/Hobart',
|
||
|
#'Australia/Lindeman',
|
||
|
#'Australia/Lord_Howe',
|
||
|
#'Australia/Melbourne',
|
||
|
'Australia/Perth',
|
||
|
'Australia/Sydney',
|
||
|
#'CET',
|
||
|
#'CST6CDT',
|
||
|
#'EET',
|
||
|
#'EST',
|
||
|
#'EST5EDT',
|
||
|
'Europe/Amsterdam',
|
||
|
#'Europe/Andorra',
|
||
|
#'Europe/Athens',
|
||
|
#'Europe/Belgrade',
|
||
|
#'Europe/Berlin',
|
||
|
#'Europe/Brussels',
|
||
|
#'Europe/Bucharest',
|
||
|
#'Europe/Budapest',
|
||
|
#'Europe/Chisinau',
|
||
|
#'Europe/Copenhagen',
|
||
|
#'Europe/Dublin',
|
||
|
#'Europe/Gibraltar',
|
||
|
#'Europe/Helsinki',
|
||
|
#'Europe/Istanbul',
|
||
|
#'Europe/Kaliningrad',
|
||
|
#'Europe/Kiev',
|
||
|
#'Europe/Lisbon',
|
||
|
'Europe/London',
|
||
|
#'Europe/Luxembourg',
|
||
|
#'Europe/Madrid',
|
||
|
#'Europe/Malta',
|
||
|
#'Europe/Minsk',
|
||
|
#'Europe/Monaco',
|
||
|
#'Europe/Moscow',
|
||
|
#'Europe/Oslo',
|
||
|
#'Europe/Paris',
|
||
|
#'Europe/Prague',
|
||
|
#'Europe/Riga',
|
||
|
#'Europe/Rome',
|
||
|
#'Europe/Samara',
|
||
|
#'Europe/Simferopol',
|
||
|
#'Europe/Sofia',
|
||
|
#'Europe/Stockholm',
|
||
|
#'Europe/Tallinn',
|
||
|
#'Europe/Tirane',
|
||
|
#'Europe/Uzhgorod',
|
||
|
#'Europe/Vienna',
|
||
|
#'Europe/Vilnius',
|
||
|
#'Europe/Volgograd',
|
||
|
#'Europe/Warsaw',
|
||
|
#'Europe/Zaporozhye',
|
||
|
'Europe/Zurich',
|
||
|
#'HST',
|
||
|
#'Indian/Antananarivo',
|
||
|
#'Indian/Chagos',
|
||
|
#'Indian/Christmas',
|
||
|
#'Indian/Cocos',
|
||
|
#'Indian/Comoro',
|
||
|
#'Indian/Kerguelen',
|
||
|
#'Indian/Mahe',
|
||
|
#'Indian/Maldives',
|
||
|
#'Indian/Mauritius',
|
||
|
#'Indian/Mayotte',
|
||
|
#'Indian/Reunion',
|
||
|
#'MET',
|
||
|
#'MST',
|
||
|
#'MST7MDT',
|
||
|
#'PST8PDT',
|
||
|
#'Pacific/Apia',
|
||
|
'Pacific/Auckland',
|
||
|
#'Pacific/Chatham',
|
||
|
#'Pacific/Chuuk',
|
||
|
#'Pacific/Easter',
|
||
|
#'Pacific/Efate',
|
||
|
#'Pacific/Enderbury',
|
||
|
#'Pacific/Fakaofo',
|
||
|
#'Pacific/Fiji',
|
||
|
#'Pacific/Funafuti',
|
||
|
#'Pacific/Galapagos',
|
||
|
#'Pacific/Gambier',
|
||
|
#'Pacific/Guadalcanal',
|
||
|
#'Pacific/Guam',
|
||
|
#'Pacific/Honolulu',
|
||
|
#'Pacific/Kiritimati',
|
||
|
#'Pacific/Kosrae',
|
||
|
#'Pacific/Kwajalein',
|
||
|
#'Pacific/Majuro',
|
||
|
#'Pacific/Marquesas',
|
||
|
#'Pacific/Midway',
|
||
|
#'Pacific/Nauru',
|
||
|
#'Pacific/Niue',
|
||
|
#'Pacific/Norfolk',
|
||
|
#'Pacific/Noumea',
|
||
|
#'Pacific/Pago_Pago',
|
||
|
#'Pacific/Palau',
|
||
|
#'Pacific/Pitcairn',
|
||
|
#'Pacific/Pohnpei',
|
||
|
#'Pacific/Port_Moresby',
|
||
|
#'Pacific/Rarotonga',
|
||
|
#'Pacific/Saipan',
|
||
|
#'Pacific/Tahiti',
|
||
|
#'Pacific/Tarawa',
|
||
|
#'Pacific/Tongatapu',
|
||
|
#'Pacific/Wake',
|
||
|
#'Pacific/Wallis',
|
||
|
'UTC',
|
||
|
#'WET',
|
||
|
#}}}
|
||
|
);
|
||
|
|
||
|
#
|
||
|
# Enable Unicode mode
|
||
|
#
|
||
|
binmode STDOUT, ":encoding(UTF-8)";
|
||
|
binmode STDERR, ":encoding(UTF-8)";
|
||
|
|
||
|
#
|
||
|
# Defaults for options
|
||
|
#
|
||
|
my $DEF_DEBUG = 0;
|
||
|
my $DEF_FROM = 'Dave.Morriss@gmail.com';
|
||
|
my $DEF_TO = 'perloid@autistici.org';
|
||
|
|
||
|
#
|
||
|
# Options and arguments
|
||
|
#
|
||
|
my %options;
|
||
|
Options( \%options );
|
||
|
|
||
|
#
|
||
|
# Default help
|
||
|
#
|
||
|
pod2usage( -msg => "$PROG version $VERSION\n", -verbose => 0, -exitval => 1 )
|
||
|
if ( $options{'help'} );
|
||
|
|
||
|
#
|
||
|
# Full documentation if requested with -doc
|
||
|
#
|
||
|
pod2usage(
|
||
|
-msg => "$PROG version $VERSION\n",
|
||
|
-verbose => 2,
|
||
|
-exitval => 1,
|
||
|
# -noperldoc => 0,
|
||
|
) if ( $options{'documentation'} );
|
||
|
|
||
|
#
|
||
|
# Collect options
|
||
|
#
|
||
|
my $DEBUG = ( defined( $options{debug} ) ? $options{debug} : $DEF_DEBUG );
|
||
|
my $month = $options{month};
|
||
|
my $mail = ( defined( $options{mail} ) ? $options{mail} : 0 );
|
||
|
my $from_address = (
|
||
|
defined( $options{fromaddress} ) ? $options{fromaddress} : $DEF_FROM );
|
||
|
my $to_address
|
||
|
= ( defined( $options{toaddress} ) ? $options{toaddress} : $DEF_TO );
|
||
|
my $date = $options{date};
|
||
|
my $start = $options{starttime};
|
||
|
my $end = $options{endtime};
|
||
|
|
||
|
# This value is in the configuration file and can't be overridden. The planned
|
||
|
# end time can be specified however.
|
||
|
#my $duration = $options{duration};
|
||
|
|
||
|
my $cfgfile
|
||
|
= ( defined( $options{config} ) ? $options{config} : $configfile1 );
|
||
|
my $dbcfgfile
|
||
|
= ( defined( $options{dbconfig} ) ? $options{dbconfig} : $configfile2 );
|
||
|
|
||
|
#
|
||
|
# Use the 'testfile' mailer if option -nomail was chosen. This writes the file
|
||
|
# 'mailer.testfile' and sends no message
|
||
|
#
|
||
|
my $mailertype = ( $mail ? 'sendmail' : 'testfile' );
|
||
|
|
||
|
#
|
||
|
# Sanity checking the options
|
||
|
#
|
||
|
die "Unable to find $cfgfile\n" unless ( -e $cfgfile );
|
||
|
die "Use only one of -month=MONTH or -date=DATE\n"
|
||
|
if (defined($month) && defined($date));
|
||
|
#die "Use only one of -endtime=TIME or -duration=HOURS\n"
|
||
|
# if (defined($end) && defined($duration));
|
||
|
|
||
|
#-------------------------------------------------------------------------------
|
||
|
# Load script and database configuration data
|
||
|
#-------------------------------------------------------------------------------
|
||
|
my $conf = new Config::General(
|
||
|
-ConfigFile => $cfgfile,
|
||
|
-InterPolateVars => 1,
|
||
|
-ExtendedAccess => 1
|
||
|
);
|
||
|
my %config = $conf->getall();
|
||
|
#print Dumper( \%config ), "\n";
|
||
|
|
||
|
#
|
||
|
# Load database configuration data
|
||
|
#
|
||
|
my $dbconf = new Config::General(
|
||
|
-ConfigFile => $dbcfgfile,
|
||
|
-InterPolateVars => 1,
|
||
|
-ExtendedAccess => 1
|
||
|
);
|
||
|
my %dbconfig = $dbconf->getall();
|
||
|
#print Dumper( \%dbconfig ), "\n";
|
||
|
|
||
|
#
|
||
|
# Configuration file values with defaults and/or checks
|
||
|
#
|
||
|
my $server = $config{email}->{server} // 'chatter.skyehaven.net';
|
||
|
my $port = $config{email}->{port} // 64738;
|
||
|
my $room = $config{email}->{room} // 'Hacker Public Radio';
|
||
|
my $duration = $config{email}->{duration} // 2;
|
||
|
my $dayname = $config{email}->{dayname} // 'Sunday';
|
||
|
|
||
|
#
|
||
|
# If we had a start time specified then check it and ensure the end time makes
|
||
|
# sense. Otherwise use the configuration file defaults, but don't check them.
|
||
|
#
|
||
|
if ($start) {
|
||
|
#
|
||
|
# Check start time and add a seconds field if needed
|
||
|
#
|
||
|
$start = validate_time($start);
|
||
|
|
||
|
#
|
||
|
# The end time usually needs to be 2 hours from the start if not
|
||
|
# specified. The actual duration is specified in the configuration file
|
||
|
# (with a default above).
|
||
|
#
|
||
|
unless ($end) {
|
||
|
my @end = split( ':', $start );
|
||
|
$end[0] += $duration;
|
||
|
$end = join(':',@end);
|
||
|
}
|
||
|
|
||
|
#
|
||
|
# Check and add a seconds field if needed
|
||
|
#
|
||
|
$end = validate_time($end);
|
||
|
}
|
||
|
else {
|
||
|
$start = $config{email}->{starttime};
|
||
|
$end = $config{email}->{endtime};
|
||
|
}
|
||
|
|
||
|
#
|
||
|
# Start and end times from options or the configuration file
|
||
|
#
|
||
|
my @starttime = split( ':', $start );
|
||
|
my @endtime = split( ':', $end );
|
||
|
die "Missing start/end time(s)\n" unless ( @starttime && @endtime );
|
||
|
|
||
|
my $template = $config{email}->{template};
|
||
|
die "Missing template file $template\n" unless (-e $template);
|
||
|
|
||
|
_debug($DEBUG >= 2,
|
||
|
'$start: ' . coalesce($start,''),
|
||
|
'$end: ' . coalesce($end,''),
|
||
|
'--'
|
||
|
);
|
||
|
|
||
|
if ($DEBUG >= 1) {
|
||
|
report_settings();
|
||
|
}
|
||
|
|
||
|
#-------------------------------------------------------------------------------
|
||
|
# Connect to the database
|
||
|
# 2021-12-24: moved to MariaDB
|
||
|
# 2022-02-28: the MariaDB driver has gone away apparently. Reverted to MySQL
|
||
|
# again
|
||
|
#-------------------------------------------------------------------------------
|
||
|
my $dbhost = $dbconfig{database}->{host} // '127.0.0.1';
|
||
|
my $dbport = $dbconfig{database}->{port} // 3306;
|
||
|
my $dbname = $dbconfig{database}->{name};
|
||
|
my $dbuser = $dbconfig{database}->{user};
|
||
|
my $dbpwd = $dbconfig{database}->{password};
|
||
|
#$dbh = DBI->connect( "DBI:MariaDB:database=$dbname;host=$dbhost;port=$dbport",
|
||
|
# $dbuser, $dbpwd, { AutoCommit => 1 } )
|
||
|
# or croak $DBI::errstr;
|
||
|
|
||
|
$dbh = DBI->connect( "dbi:mysql:host=$dbhost;port=$dbport;database=$dbname",
|
||
|
$dbuser, $dbpwd, { AutoCommit => 1 } )
|
||
|
or die $DBI::errstr;
|
||
|
|
||
|
#
|
||
|
# Enable client-side UTF8
|
||
|
#
|
||
|
$dbh->{mysql_enable_utf8} = 1;
|
||
|
|
||
|
#
|
||
|
# Date and time values using Date::Calc format
|
||
|
#
|
||
|
my @today = Today();
|
||
|
my @startdate;
|
||
|
my @startmonth;
|
||
|
my @reviewdate;
|
||
|
my $monday = 1; # Day of week number 1-7, Monday-Sunday
|
||
|
my $offset = day_offset($dayname)->{offset};
|
||
|
|
||
|
#-------------------------------------------------------------------------------
|
||
|
# Work out the start date from the -date=DATE option, the -month=DATE option
|
||
|
# or the current date.
|
||
|
#-------------------------------------------------------------------------------
|
||
|
#
|
||
|
# If there's an argument then it'll be an override for the start date
|
||
|
# otherwise we'll compute it.
|
||
|
#
|
||
|
if ( defined($date) ) {
|
||
|
#
|
||
|
# Parse and perform rudimentary validation on the argument
|
||
|
#
|
||
|
my @parsed = strptime($date);
|
||
|
die "Invalid -date=DATE option '$date'\n"
|
||
|
unless ( defined( $parsed[3] )
|
||
|
&& defined( $parsed[4] )
|
||
|
&& defined( $parsed[5] ) );
|
||
|
|
||
|
$parsed[5] += 1900;
|
||
|
$parsed[4] += 1;
|
||
|
@startdate = @parsed[ 5, 4, 3 ];
|
||
|
die "Date is in the past '$date'; aborting\n"
|
||
|
unless ( Date_to_Days(@startdate) > Date_to_Days(@today) );
|
||
|
}
|
||
|
elsif ( defined($month) ) {
|
||
|
#
|
||
|
# Parse the month out of the -month=DATE argument
|
||
|
#
|
||
|
my @parsed = strptime($month);
|
||
|
die "Invalid -month=DATE option '$month'\n"
|
||
|
unless ( defined( $parsed[3] )
|
||
|
&& defined( $parsed[4] )
|
||
|
&& defined( $parsed[5] ) );
|
||
|
|
||
|
$parsed[5] += 1900;
|
||
|
$parsed[4] += 1;
|
||
|
@startmonth = @parsed[ 5, 4, 3 ];
|
||
|
die "Date is in the past '$month'; aborting\n"
|
||
|
unless ( Date_to_Days(@startmonth) > Date_to_Days(@today) );
|
||
|
|
||
|
#
|
||
|
# Compute the next meeting date from now (by finding the next first Monday
|
||
|
# of the month then backing up two days to the Saturday).
|
||
|
#
|
||
|
@startdate = make_date( \@startmonth, $monday, 1, $offset );
|
||
|
}
|
||
|
else {
|
||
|
#
|
||
|
# Compute the next meeting date from now (by finding the next first Monday
|
||
|
# of the month then backing up a number of days to the required date).
|
||
|
#
|
||
|
@startdate = make_date( \@today, $monday, 1, $offset );
|
||
|
}
|
||
|
|
||
|
_debug($DEBUG >= 2, '@startdate: ' . join(',',@startdate));
|
||
|
|
||
|
#
|
||
|
# The month being reviewed is sometimes the same month and sometimes the month
|
||
|
# before.
|
||
|
#
|
||
|
if ( $startdate[1] eq $today[1] ) {
|
||
|
@reviewdate = @startdate;
|
||
|
}
|
||
|
else {
|
||
|
@reviewdate = Add_Delta_YM( @startdate, 0, -1 );
|
||
|
}
|
||
|
|
||
|
_debug($DEBUG >= 2, '@reviewdate: ' . join(',',@reviewdate));
|
||
|
|
||
|
#
|
||
|
# Transfer Date::Calc values into hashes for initialising DateTime objects so
|
||
|
# we can play time zone games
|
||
|
#
|
||
|
my ( %dtargs, $dtstart, $dtend );
|
||
|
@dtargs{ 'year', 'month', 'day', 'hour', 'minute', 'second', 'time_zone' }
|
||
|
= ( @startdate, @starttime, 'UTC' );
|
||
|
$dtstart = DateTime->new(%dtargs);
|
||
|
@dtargs{ 'hour', 'minute', 'second' } = (@endtime);
|
||
|
$dtend = DateTime->new(%dtargs);
|
||
|
|
||
|
#
|
||
|
# Compute the number of days until the recording
|
||
|
#
|
||
|
my $dtnow = DateTime->now( time_zone => 'UTC' );
|
||
|
my $dtoffset = $dtstart->delta_days($dtnow);
|
||
|
my $dtf = DateTime::Format::Duration->new( pattern => '%e' );
|
||
|
my $days = $dtf->format_duration($dtoffset);
|
||
|
|
||
|
#
|
||
|
# Formatted dates for the mail message body
|
||
|
#
|
||
|
my ( $year, $monthname, $nicedate, $starttime, $endtime ) = (
|
||
|
$dtstart->strftime("%Y"), Month_to_Text( $reviewdate[1] ),
|
||
|
$dtstart->strftime("%A, %B %d %Y"), $dtstart->strftime("%R (%Z)"),
|
||
|
$dtend->strftime("%R (%Z)"),
|
||
|
);
|
||
|
|
||
|
_debug($DEBUG >= 2,
|
||
|
"\$year: $year",
|
||
|
"\$monthname: $monthname",
|
||
|
"\$nicedate: $nicedate",
|
||
|
"\$starttime: $starttime",
|
||
|
"\$endtime: $endtime"
|
||
|
);
|
||
|
|
||
|
#
|
||
|
# Build the subject line
|
||
|
#
|
||
|
my $waittime = ( $days > 6 ? "in $days days" : "next %A" );
|
||
|
my $next = ( $days > 6 ? '' : 'next ' );
|
||
|
my $forspec = ( $days > 6 ? "for $monthname " : "" );
|
||
|
my $subject = $dtstart->strftime(
|
||
|
"HPR Community News ${forspec}- $waittime on %FT%TZ");
|
||
|
|
||
|
_debug( $DEBUG >= 2, "\$subject: $subject" );
|
||
|
|
||
|
#
|
||
|
# Prepare to send mail
|
||
|
#
|
||
|
my $mailer = Mail::Mailer->new($mailertype);
|
||
|
|
||
|
#
|
||
|
# Generate the headers we need
|
||
|
#
|
||
|
$mailer->open(
|
||
|
{ To => $to_address,
|
||
|
From => $from_address,
|
||
|
Subject => $subject,
|
||
|
}
|
||
|
);
|
||
|
|
||
|
#
|
||
|
# Build an array of timezone data for the template
|
||
|
#
|
||
|
my @timezones;
|
||
|
for my $tz (@zones) {
|
||
|
push( @timezones, storeTZ( $dtstart, $dtend, $tz ) );
|
||
|
}
|
||
|
|
||
|
#-------------------------------------------------------------------------------
|
||
|
# Find the number of the show with the notes. Take care because the recording
|
||
|
# date might not be on the weekend before the show is released.
|
||
|
#-------------------------------------------------------------------------------
|
||
|
my $isodate = $dtstart->ymd;
|
||
|
$sth1 = $dbh->prepare(q{
|
||
|
SELECT id FROM eps
|
||
|
WHERE date > ?
|
||
|
AND date_format(date,"%W") = 'Monday'
|
||
|
AND title LIKE 'HPR Community News%'
|
||
|
ORDER BY date
|
||
|
LIMIT 1
|
||
|
});
|
||
|
$sth1->execute($isodate);
|
||
|
if ( $dbh->err ) {
|
||
|
warn $dbh->errstr;
|
||
|
}
|
||
|
|
||
|
_debug( $DEBUG >= 2, "\$isodate: $isodate" );
|
||
|
|
||
|
unless ( $h1 = $sth1->fetchrow_hashref ) {
|
||
|
warn "Unable to find a reserved show on the specified date - cannot continue\n";
|
||
|
exit 1;
|
||
|
}
|
||
|
|
||
|
my $shownotes = $h1->{id};
|
||
|
|
||
|
_debug( $DEBUG >= 2, "\$shownotes (slot): $shownotes" );
|
||
|
|
||
|
$sth1->finish;
|
||
|
$dbh->disconnect;
|
||
|
|
||
|
#-------------------------------------------------------------------------------
|
||
|
# Fill the template
|
||
|
#-------------------------------------------------------------------------------
|
||
|
my $tt = Template->new(
|
||
|
{ ABSOLUTE => 1,
|
||
|
ENCODING => 'utf8',
|
||
|
}
|
||
|
);
|
||
|
|
||
|
my $vars = {
|
||
|
# subject => $subject,
|
||
|
# from => $from_address,
|
||
|
# to => $to_address,
|
||
|
server => $server,
|
||
|
port => $port,
|
||
|
room => $room,
|
||
|
timezones => \@timezones,
|
||
|
utc => {
|
||
|
days => $days,
|
||
|
month => $monthname,
|
||
|
year => $year,
|
||
|
date => $nicedate,
|
||
|
start => $starttime,
|
||
|
end => $endtime,
|
||
|
},
|
||
|
shownotes => $shownotes,
|
||
|
};
|
||
|
|
||
|
my $document;
|
||
|
$tt->process( $template,
|
||
|
$vars, \$document, { binmode => ':utf8' } )
|
||
|
|| die $tt->error(), "\n";
|
||
|
|
||
|
#
|
||
|
# Add the template-generated body to the mail message
|
||
|
#
|
||
|
print $mailer $document;
|
||
|
|
||
|
#
|
||
|
# Send the message
|
||
|
#
|
||
|
$mailer->close
|
||
|
or die "Couldn't send message: $!\n";
|
||
|
|
||
|
unless ($mail) {
|
||
|
print "Message was not sent since -nomail was selected (or defaulted).\n";
|
||
|
print "Look in 'mailer.testfile' for the output\n";
|
||
|
}
|
||
|
|
||
|
exit;
|
||
|
|
||
|
|
||
|
#=== FUNCTION ================================================================
|
||
|
# NAME: report_settings
|
||
|
# PURPOSE: Report settings from options (or defaults)
|
||
|
# PARAMETERS: None
|
||
|
# RETURNS: Nothing
|
||
|
# DESCRIPTION:
|
||
|
# THROWS: No exceptions
|
||
|
# COMMENTS: None
|
||
|
# SEE ALSO: N/A
|
||
|
#===============================================================================
|
||
|
sub report_settings {
|
||
|
my $fmt = "D> %-14s = %s\n";
|
||
|
print "D> Settings from options or default values:\n";
|
||
|
printf $fmt, "Month", coalesce($month,'undef');
|
||
|
printf $fmt, "Mail", coalesce($mail,'undef');
|
||
|
printf $fmt, "From", coalesce($from_address,'undef');
|
||
|
printf $fmt, "To", coalesce($to_address,'undef');
|
||
|
printf $fmt, "Meeting date", coalesce($date,'undef');
|
||
|
printf $fmt, "Start time", join(':',@starttime);
|
||
|
printf $fmt, "End time", join(':',@endtime);
|
||
|
printf $fmt, "Config file", coalesce($cfgfile,'undef');
|
||
|
printf $fmt, "DB config file", coalesce($dbcfgfile,'undef');
|
||
|
printf $fmt, "Server", coalesce($server,'undef');
|
||
|
printf $fmt, "Port", coalesce($port,'undef');
|
||
|
printf $fmt, "Room", coalesce($room,'undef');
|
||
|
printf $fmt, "Template", coalesce($template,'undef');
|
||
|
print "D> ----\n";
|
||
|
}
|
||
|
|
||
|
#=== FUNCTION ================================================================
|
||
|
# NAME: compute_endtime
|
||
|
# PURPOSE: Given a start time and a duration computes the end time
|
||
|
# PARAMETERS: $rdate arrayref for the date
|
||
|
# $rstime arrayref for the start time
|
||
|
# $rduration arrayref for the duration [HH,MM,SS]
|
||
|
# RETURNS: The end time as a string (HH:MM:SS)
|
||
|
# DESCRIPTION:
|
||
|
# THROWS: No exceptions
|
||
|
# COMMENTS: Decided not to implement this, may do so in future.
|
||
|
# SEE ALSO: N/A
|
||
|
#===============================================================================
|
||
|
#sub compute_endtime {
|
||
|
#}
|
||
|
|
||
|
#=== FUNCTION ================================================================
|
||
|
# NAME: validate_time
|
||
|
# PURPOSE: Validates a time in HH:MM:SS format
|
||
|
# PARAMETERS: $time Time string
|
||
|
# RETURNS: The input string with any missing fields added in
|
||
|
# DESCRIPTION: The input time needs to be in the format HH:MM[:SS] where
|
||
|
# a missing seconds value is replaced with '00'. A regex check
|
||
|
# on the format is performed, and if that passes a check is made
|
||
|
# on the time values (using 'check_time').
|
||
|
# THROWS: No exceptions
|
||
|
# COMMENTS: None
|
||
|
# SEE ALSO: N/A
|
||
|
#===============================================================================
|
||
|
sub validate_time {
|
||
|
my ($time) = (@_);
|
||
|
|
||
|
if ( defined($time) ) {
|
||
|
if ( ( my @fields )
|
||
|
= ( $time =~ /(\d{1,2}):(\d{1,2})(?::(\d{1,2}))?/ ) )
|
||
|
{
|
||
|
@fields = map { defined($_) ? sprintf('%02d',$_) : "00" } @fields;
|
||
|
$time = join( ':', @fields );
|
||
|
}
|
||
|
else {
|
||
|
die "Invalid time: $time\n";
|
||
|
}
|
||
|
|
||
|
unless ( check_time(split(':',$time)) ) {
|
||
|
die "Invalid time: $time\n";
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return $time;
|
||
|
}
|
||
|
|
||
|
#=== FUNCTION ================================================================
|
||
|
# NAME: make_date
|
||
|
# PURPOSE: Make the event date for recurrence
|
||
|
# PARAMETERS: $refdate
|
||
|
# An arrayref to the reference date array (usually
|
||
|
# today's date)
|
||
|
# $dow Day of week for the event date (1-7, 1=Monday)
|
||
|
# $n The nth day of the week in the given month required
|
||
|
# for the event date
|
||
|
# $offset Number of days to offset the computed date
|
||
|
# RETURNS: The resulting date as a list for Date::Calc
|
||
|
# DESCRIPTION: We want to compute a simple date with an offset, such as
|
||
|
# "the Saturday before the first Monday of the month". We do
|
||
|
# this by computing a pre-offset date (first Monday of month)
|
||
|
# then apply the offset (Saturday before).
|
||
|
# THROWS: No exceptions
|
||
|
# COMMENTS: This function was originally written for my HPR episode on
|
||
|
# iCalendar.
|
||
|
# TODO Needs more testing to be considered truly universal
|
||
|
# SEE ALSO:
|
||
|
#===============================================================================
|
||
|
sub make_date {
|
||
|
my ( $refdate, $dow, $n, $offset ) = @_;
|
||
|
|
||
|
#
|
||
|
# Compute the required date: the "$n"th day of week "$dow" in the year and
|
||
|
# month in @$refdate. This could be a date in the past.
|
||
|
#
|
||
|
my @date = Nth_Weekday_of_Month_Year( @$refdate[ 0, 1 ], $dow, $n );
|
||
|
|
||
|
#
|
||
|
# If the computed date plus the offset is before the base date advance
|
||
|
# a month
|
||
|
#
|
||
|
if ( Day_of_Year(@date) + $offset < Day_of_Year(@$refdate) ) {
|
||
|
#
|
||
|
# Add a month and recompute
|
||
|
#
|
||
|
@date = Add_Delta_YM( @date, 0, 1 );
|
||
|
@date = Nth_Weekday_of_Month_Year( @date[ 0, 1 ], $dow, $n );
|
||
|
}
|
||
|
|
||
|
#
|
||
|
# Apply the day offset
|
||
|
#
|
||
|
@date = Add_Delta_Days( @date, $offset ) if $offset;
|
||
|
|
||
|
#
|
||
|
# Return a list
|
||
|
#
|
||
|
return (@date);
|
||
|
}
|
||
|
|
||
|
#=== FUNCTION ================================================================
|
||
|
# NAME: storeTZ
|
||
|
# PURPOSE: Store start/end times for a timezone
|
||
|
# PARAMETERS: $start DateTime object containing the starting
|
||
|
# datetime as UTC
|
||
|
# $end DateTime object containing the ending datetime
|
||
|
# as UTC
|
||
|
# $tz The textual time zone (need this to be valid)
|
||
|
# RETURNS: A hash containing the timezone name and start and end times
|
||
|
# DESCRIPTION: Relies on DateTime::TimeZone to do the work to turn a UTC time
|
||
|
# into a time in a different time zone. Uses the DateTime
|
||
|
# strftime method to format the dates and times for printing.
|
||
|
# THROWS: No exceptions
|
||
|
# COMMENTS: None
|
||
|
# SEE ALSO: N/A
|
||
|
#===============================================================================
|
||
|
sub storeTZ {
|
||
|
my ( $start, $end, $tz ) = @_;
|
||
|
|
||
|
my %result;
|
||
|
|
||
|
#
|
||
|
# Adjust time zone
|
||
|
#
|
||
|
$start->set_time_zone($tz);
|
||
|
$end->set_time_zone($tz);
|
||
|
|
||
|
#
|
||
|
# Print time zone and start/end times in that zone
|
||
|
#
|
||
|
$result{name} = "$tz";
|
||
|
$result{start} = $start->strftime("%H:%S %a, %b %d %Y");
|
||
|
$result{end} = $end->strftime("%H:%S %a, %b %d %Y");
|
||
|
|
||
|
return \%result;
|
||
|
}
|
||
|
|
||
|
#=== FUNCTION ================================================================
|
||
|
# NAME: printTZ
|
||
|
# PURPOSE: Print start/end times for a timezone
|
||
|
# PARAMETERS: $fh File handle for writing
|
||
|
# $start DateTime object containing the starting
|
||
|
# datetime as UTC
|
||
|
# $end DateTime object containing the ending datetime
|
||
|
# as UTC
|
||
|
# $tz The textual time zone (need this to be valid)
|
||
|
# RETURNS: Nothing
|
||
|
# DESCRIPTION: Relies on DateTime::TimeZone to do the work to turn a UTC time
|
||
|
# into a time in a different time zone. Uses the DateTime
|
||
|
# strftime method to format the dates and times for printing.
|
||
|
# THROWS: No exceptions
|
||
|
# COMMENTS: None
|
||
|
# SEE ALSO:
|
||
|
#===============================================================================
|
||
|
sub printTZ {
|
||
|
my ( $fh, $start, $end, $tz ) = @_;
|
||
|
|
||
|
#
|
||
|
# Adjust time zone
|
||
|
#
|
||
|
$start->set_time_zone($tz);
|
||
|
$end->set_time_zone($tz);
|
||
|
|
||
|
#
|
||
|
# Print time zone and start/end times in that zone
|
||
|
#
|
||
|
print $fh "$tz\n";
|
||
|
print $fh $start->strftime("Start: %H:%S %a, %b %d %Y\n");
|
||
|
print $fh $end->strftime("End: %H:%S %a, %b %d %Y\n\n");
|
||
|
|
||
|
}
|
||
|
|
||
|
#=== FUNCTION ================================================================
|
||
|
# NAME: day_offset
|
||
|
# PURPOSE: Given a day name computes day attributes including the
|
||
|
# (negative) offset in days from the target Monday to the
|
||
|
# recording date.
|
||
|
# PARAMETERS: $dayname Name of a day of the week
|
||
|
# RETURNS: Hashref containing the full day name, the weekday number and
|
||
|
# the integer offset from Monday to the recording day, or undef.
|
||
|
# DESCRIPTION: Uses the hash '%matches' keyed by regular expressions matching
|
||
|
# day names. The argument '$dayname' is matched against each
|
||
|
# regex in turn and if it matches the sub-hash is returned. This
|
||
|
# allows the caller to use 'day_offset($dayname)->{dayname}' to
|
||
|
# get the full name of the day if needed, or the offset as
|
||
|
# 'day_offset($dayname)->{offset}'. If there is no match
|
||
|
# 'undef' is returned.
|
||
|
# THROWS: No exceptions
|
||
|
# COMMENTS: None
|
||
|
# SEE ALSO: N/A
|
||
|
#===============================================================================
|
||
|
sub day_offset {
|
||
|
my ( $dayname ) = @_;
|
||
|
|
||
|
my %matches = (
|
||
|
qr{^(?i)Sun(day)?$} => {
|
||
|
dayname => 'Sunday',
|
||
|
wday => 7,
|
||
|
offset => -1,
|
||
|
},
|
||
|
qr{^(?i)Sat(urday)?$} => {
|
||
|
dayname => 'Saturday',
|
||
|
wday => 6,
|
||
|
offset => -2,
|
||
|
},
|
||
|
qr{^(?i)Fri(day)?$} => {
|
||
|
dayname => 'Friday',
|
||
|
wday => 5,
|
||
|
offset => -3,
|
||
|
},
|
||
|
qr{^(?i)Thu(rsday)?$} => {
|
||
|
dayname => 'Thursday',
|
||
|
wday => 4,
|
||
|
offset => -4,
|
||
|
},
|
||
|
qr{^(?i)Wed(nesday)?$} => {
|
||
|
dayname => 'Wednesday',
|
||
|
wday => 3,
|
||
|
offset => -5,
|
||
|
},
|
||
|
qr{^(?i)Tue(sday)?$} => {
|
||
|
dayname => 'Tuesday',
|
||
|
wday => 2,
|
||
|
offset => -6,
|
||
|
},
|
||
|
qr{^(?i)Mon(day)?$} => {
|
||
|
dayname => 'Monday',
|
||
|
wday => 1,
|
||
|
offset => -7,
|
||
|
},
|
||
|
);
|
||
|
my $match;
|
||
|
|
||
|
foreach my $re (keys(%matches)) {
|
||
|
if ($dayname =~ $re) {
|
||
|
$match = $matches{$re};
|
||
|
last;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return $match;
|
||
|
}
|
||
|
|
||
|
#=== FUNCTION ================================================================
|
||
|
# NAME: _debug
|
||
|
# PURPOSE: Prints debug reports
|
||
|
# PARAMETERS: $active Boolean: 1 for print, 0 for no print
|
||
|
# $messages... Arbitrary list of messages to print
|
||
|
# RETURNS: Nothing
|
||
|
# DESCRIPTION: Outputs messages if $active is true. It removes any trailing
|
||
|
# newline from each one and then adds one in the 'print' to the
|
||
|
# caller doesn't have to bother. Prepends each message with 'D>'
|
||
|
# to show it's a debug message.
|
||
|
# THROWS: No exceptions
|
||
|
# COMMENTS: Differs from other functions of the same name
|
||
|
# SEE ALSO: N/A
|
||
|
#===============================================================================
|
||
|
sub _debug {
|
||
|
my $active = shift;
|
||
|
|
||
|
my $message;
|
||
|
return unless $active;
|
||
|
|
||
|
while ($message = shift) {
|
||
|
chomp($message);
|
||
|
print STDERR "D> $message\n";
|
||
|
}
|
||
|
}
|
||
|
|
||
|
#=== FUNCTION ================================================================
|
||
|
# NAME: coalesce
|
||
|
# PURPOSE: To find the first defined argument and return it
|
||
|
# PARAMETERS: Arbitrary number of arguments
|
||
|
# RETURNS: The first defined argument or undef if there are none
|
||
|
# DESCRIPTION:
|
||
|
# THROWS: No exceptions
|
||
|
# COMMENTS: None
|
||
|
# SEE ALSO: N/A
|
||
|
#===============================================================================
|
||
|
sub coalesce {
|
||
|
foreach (@_) {
|
||
|
return $_ if defined($_);
|
||
|
}
|
||
|
return undef; ## no critic
|
||
|
}
|
||
|
|
||
|
#=== 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", "mail!",
|
||
|
"fromaddress=s", "toaddress=s",
|
||
|
"date=s", "starttime=s",
|
||
|
"endtime=s", "month=s",
|
||
|
"config=s", "dbconfig=s",
|
||
|
);
|
||
|
# "duration=s",
|
||
|
|
||
|
if ( !GetOptions( $optref, @options ) ) {
|
||
|
pod2usage( -msg => "Version $VERSION\n", -verbose => 0, -exitval => 1 );
|
||
|
}
|
||
|
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
__END__
|
||
|
|
||
|
#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||
|
# Application Documentation
|
||
|
#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||
|
#{{{
|
||
|
|
||
|
=head1 NAME
|
||
|
|
||
|
make_email - generates an HPR Community News recording invitation email
|
||
|
|
||
|
=head1 VERSION
|
||
|
|
||
|
This documentation refers to make_email version 0.2.7
|
||
|
|
||
|
|
||
|
=head1 USAGE
|
||
|
|
||
|
make_email [-help] [-documentation] [-debug=N] [-month=DATE] [-[no]mail]
|
||
|
[-from=FROM_ADDRESS] [-to=TO_ADDRESS] [-date=DATE] [-start=START_TIME]
|
||
|
[-end=END_TIME] [-config=FILE] [-dbconfig=FILE]
|
||
|
|
||
|
./make_email -dbconf=$HOME/HPR/.hpr_livedb.cfg -date=2022-12-27
|
||
|
|
||
|
=head1 OPTIONS
|
||
|
|
||
|
=over 8
|
||
|
|
||
|
=item B<-help>
|
||
|
|
||
|
Prints a brief help message describing the usage of the program, and then exits.
|
||
|
|
||
|
=item B<-documentation> B<-man>
|
||
|
|
||
|
Prints the entire embedded documentation for the program, then exits.
|
||
|
|
||
|
Another way to see the full documentation use:
|
||
|
|
||
|
B<perldoc ./make_email>
|
||
|
|
||
|
=item B<-debug=N>
|
||
|
|
||
|
Enables debugging mode when N > 0 (zero is the default, no debugging output).
|
||
|
The levels are:
|
||
|
|
||
|
Values are:
|
||
|
|
||
|
=over 4
|
||
|
|
||
|
=item 1
|
||
|
|
||
|
Reports all of the settings taken from the configuration file, the provided
|
||
|
command line options or their default values. The report is generated early on
|
||
|
in the processing of these values. Use B<-debug=2> for information about the
|
||
|
next stages.
|
||
|
|
||
|
=item 2
|
||
|
|
||
|
Reports the following (as well as the data for level 1):
|
||
|
|
||
|
=over 4
|
||
|
|
||
|
=item .
|
||
|
|
||
|
Details of the start date chosen
|
||
|
|
||
|
=item .
|
||
|
|
||
|
Details of the year, name of month, readable date, and recording start and end
|
||
|
times.
|
||
|
|
||
|
=item .
|
||
|
|
||
|
The subject line chosen for the email.
|
||
|
|
||
|
=item .
|
||
|
|
||
|
The date of the show being searched for in the database.
|
||
|
|
||
|
=item .
|
||
|
|
||
|
The number of the show found in the database.
|
||
|
|
||
|
=back
|
||
|
|
||
|
=back
|
||
|
|
||
|
=item B<-month=DATE>
|
||
|
|
||
|
Defines the month for which the email will be generated using a date in that
|
||
|
month. Normally (without this option) the current month is chosen and the date
|
||
|
of recording computed within it. The month specified here is provided as
|
||
|
a ISO8601 date such as 2014-03-08 (meaning March 2014) or 1-Jan-2017 (meaning
|
||
|
January 2017). Only the year and month parts are used but a valid day must be
|
||
|
present.
|
||
|
|
||
|
=item B<-[no]mail>
|
||
|
|
||
|
** NOTE ** The sending of mail does not work at present, and B<-nomail> should
|
||
|
always be used.
|
||
|
|
||
|
Causes mail to be sent (B<-mail>) or not sent (B<-nomail>). If the mail is
|
||
|
sent then it is sent via the local MTA (in the assumption that there is one).
|
||
|
If this option is omitted, the default is B<-nomail>, in which case the
|
||
|
message is appended to the file B<mailer.testfile> in the current directory.
|
||
|
|
||
|
=item B<-from=FROM_ADDRESS>
|
||
|
|
||
|
** NOTE ** The sending of mail does not work at present.
|
||
|
|
||
|
This option defines the address from which the message is to be sent. This
|
||
|
address is used in the message header; the message envelope will contain the
|
||
|
I<real> sender.
|
||
|
|
||
|
=item B<-to=TO_ADDRESS>
|
||
|
|
||
|
** NOTE ** The sending of mail does not work at present.
|
||
|
|
||
|
This option defines the address to which the message is to be sent.
|
||
|
|
||
|
=item B<-date=DATE>
|
||
|
|
||
|
This is an option provides a non-default date for the recording. Normally the
|
||
|
script computes the next scheduled date based on the algorithm "DAY_OF_WEEK
|
||
|
before the first Monday of the next month" (where DAY_OF_WEEK is the value
|
||
|
defined in the configuration file as B<dayname>) starting from the current
|
||
|
date or the start of the month given in the B<-month=DATE> option. If for any
|
||
|
reason a different date is required then this may be specified via this
|
||
|
option.
|
||
|
|
||
|
The recording date should be given as an ISO8601 date (such as 2014-03-08).
|
||
|
|
||
|
=item B<-start=START_TIME>
|
||
|
|
||
|
The default start time is defined in the configuration file, but if it is
|
||
|
necessary to change it, this option can be used to do it. The B<START_TIME>
|
||
|
value must be a valid B<HH:MM> time specification.
|
||
|
|
||
|
A change to the start time in the configuration file also implies that the end
|
||
|
time should change. If the B<-start=START_TIME> option is present but
|
||
|
B<-end=END_TIME> is not, then the end time is computed by adding a number of
|
||
|
hours to the start time this number is defined in the configuration file as
|
||
|
B<duration>.
|
||
|
|
||
|
=item B<-end=END_TIME>
|
||
|
|
||
|
The default end time is defined in the configuration file, but if it is
|
||
|
necessary to change it temporarily, this option can be used to do it. The
|
||
|
B<END_TIME> value must be a valid B<HH:MM> time specification.
|
||
|
|
||
|
=item B<-config=FILE>
|
||
|
|
||
|
This option defines a configuration file other than the default
|
||
|
B<.make_email.cfg>. The file must be formatted as described below in the
|
||
|
section I<CONFIGURATION AND ENVIRONMENT>.
|
||
|
|
||
|
=item B<-dbconfig=FILE>
|
||
|
|
||
|
This option defines a database configuration file other than the default
|
||
|
B<.hpr_db.cfg>. The file must be formatted as described below in the section
|
||
|
I<CONFIGURATION AND ENVIRONMENT>.
|
||
|
|
||
|
The default file is configured to open a local copy of the HPR database. An
|
||
|
alternative is B<.hpr_livedb.cfg> which assumes an SSH tunnel to the live
|
||
|
database and attempts to connect to it. Use the script I<open_tunnel> to open
|
||
|
the SSH tunnel.
|
||
|
|
||
|
=back
|
||
|
|
||
|
=head1 DESCRIPTION
|
||
|
|
||
|
Makes and sends(*) an invitation email for the next Community News with times per
|
||
|
timezone. The message is structured by a Template Toolkit template, so its
|
||
|
content can be adjusted without changing this script.
|
||
|
|
||
|
In normal operation the script computes the date of the next recording using
|
||
|
the algorithm "Saturday before the first Monday of the next month" starting
|
||
|
from the current date or the start of the month (and year) given in the
|
||
|
B<-month=DATE> option.
|
||
|
|
||
|
It uses the recording date (B<-date=DATE> option) to access the MySQL database
|
||
|
to find the date on which the show will be released. It does that so the notes
|
||
|
on that show can be viewed by the volunteers recording the show. These notes
|
||
|
are expanded to be usable during the recording, with comments relating to
|
||
|
earlier shows being displayed in full, and any comments missed in the last
|
||
|
recording highlighted. Comments made to shows during the past month can be
|
||
|
seen as the shows are visited and discussed.
|
||
|
|
||
|
The email generated by the script is sent to the HPR mailing list, usually on
|
||
|
the Monday prior to the weekend of the recording.
|
||
|
|
||
|
Notes:
|
||
|
* Mail sending does not work at present.
|
||
|
|
||
|
=head1 DIAGNOSTICS
|
||
|
|
||
|
=over 8
|
||
|
|
||
|
=item B<Unable to find ...>
|
||
|
|
||
|
The configuration file specified in B<-config=FILE> (or the default file)
|
||
|
could not be found.
|
||
|
|
||
|
=item B<Use only one of -month=MONTH or -date=DATE>
|
||
|
|
||
|
These options are mutually exclusive. See their specifications earlier in this
|
||
|
document.
|
||
|
|
||
|
=item B<Missing start/end time(s)>
|
||
|
|
||
|
One or both of the start and end times is missing, either from the configuration file or
|
||
|
from the command line options.
|
||
|
|
||
|
=item B<Missing template file ...>
|
||
|
|
||
|
The template file specified in the configuration file could not be found.
|
||
|
|
||
|
=item B<Various database messages>
|
||
|
|
||
|
The program can generate warning messages from the database.
|
||
|
|
||
|
=item B<Invalid -date=DATE option '...'>
|
||
|
|
||
|
An invalid date has been supplied via this option.
|
||
|
|
||
|
=item B<Date is in the past '...'>
|
||
|
|
||
|
The date specified in B<-date=DATE> is in the past.
|
||
|
|
||
|
=item B<Invalid -month=DATE option '...'>
|
||
|
|
||
|
An invalid date has been supplied via this option.
|
||
|
|
||
|
=item B<Date is in the past '...'>
|
||
|
|
||
|
The month specified in B<-month=DATE> is in the past.
|
||
|
|
||
|
=item B<Various Template Toolkit messages>
|
||
|
|
||
|
The program can generate warning messages from the Template.
|
||
|
|
||
|
=item B<Couldn't send message: ...>
|
||
|
|
||
|
The email mesage has been constructed but could not be sent. See the error
|
||
|
returned by the mail subsystem for more information.
|
||
|
|
||
|
=back
|
||
|
|
||
|
=head1 CONFIGURATION AND ENVIRONMENT
|
||
|
|
||
|
=head2 EMAIL CONFIGURATION
|
||
|
|
||
|
The program obtains the settings it requires for preparing the email from
|
||
|
a configuration file, which by default is called B<.make_email.cfg>. This file
|
||
|
needs to contain the following data:
|
||
|
|
||
|
<email>
|
||
|
server = MUMBLE_SERVER_NAME
|
||
|
port = MUMBLE_PORT
|
||
|
room = NAME_OF_ROOM
|
||
|
dayname = DAY_OF_WEEK_OF_RECORDING
|
||
|
starttime = 18:00:00
|
||
|
endtime = 20:00:00
|
||
|
duration = 02 # hours
|
||
|
template = NAME_OF_TEMPLATE
|
||
|
</email>
|
||
|
|
||
|
=head2 DATABASE CONFIGURATION
|
||
|
|
||
|
The program obtains the credentials it requires for connecting to the HPR
|
||
|
database by loading them from a configuration file. The default file is called
|
||
|
B<.hpr_db.cfg> and should contain the following data:
|
||
|
|
||
|
<database>
|
||
|
host = 127.0.0.1
|
||
|
port = PORT
|
||
|
name = DBNAME
|
||
|
user = USER
|
||
|
password = PASSWORD
|
||
|
</database>
|
||
|
|
||
|
The file B<.hpr_livedb.cfg> should be available to allow access to the
|
||
|
database over an SSH tunnel which has been previously opened.
|
||
|
|
||
|
=head1 DEPENDENCIES
|
||
|
|
||
|
DBI
|
||
|
Date::Calc
|
||
|
Date::Parse
|
||
|
DateTime
|
||
|
DateTime::Format::Duration
|
||
|
DateTime::TimeZone
|
||
|
Getopt::Long
|
||
|
Mail::Mailer
|
||
|
Pod::Usage
|
||
|
Template
|
||
|
|
||
|
=head1 BUGS AND LIMITATIONS
|
||
|
|
||
|
There are no known bugs in this script.
|
||
|
Please report problems to Dave Morriss (Dave.Morriss@gmail.com)
|
||
|
Patches are welcome.
|
||
|
|
||
|
=head1 AUTHOR
|
||
|
|
||
|
Dave Morriss (Dave.Morriss@gmail.com) 2013 - 2024
|
||
|
|
||
|
=head1 LICENCE AND COPYRIGHT
|
||
|
|
||
|
Copyright (c) Dave Morriss (Dave.Morriss@gmail.com). All rights reserved.
|
||
|
|
||
|
This program is free software. You can redistribute it and/or modify it under
|
||
|
the same terms as perl itself.
|
||
|
|
||
|
=cut
|
||
|
|
||
|
#}}}
|
||
|
|
||
|
# [zo to open fold, zc to close]
|
||
|
|
||
|
# vim: syntax=perl:ts=8:sw=4:et:ai:tw=78:fo=tcrqn21:fdm=marker
|