Fix: Consistency check for MID-Format fixed (regexp).
[usenet/yapfaq.git] / yapfaq.pl
index 14efde4..df69b78 100644 (file)
--- a/yapfaq.pl
+++ b/yapfaq.pl
@@ -1,6 +1,6 @@
 #! /usr/bin/perl -W
 #
-# yapfaq Version 0.6 by Thomas Hochstein
+# yapfaq Version 0.7 by Thomas Hochstein
 # (Original author: Marc Brockschmidt)
 #
 # This script posts any project described in its config-file. Most people
 # It can be redistributed and/or modified under the same terms under 
 # which Perl itself is published.
 
-my $Version = "0.6.2";
+my $Version = "0.8-prelease";
 
+# Please do not change this setting!
+# You may override the default .rc file (.yapfaqrc) by using "-c .rc file"
 my $RCFile = '.yapfaqrc';
+# Valid configuration variables for use in a .rc file
 my @ValidConfVars = ('NNTPServer','NNTPUser','NNTPPass','Sender','ConfigFile',
                      'UsePGP','pgp','PGPVersion','PGPSigner','PGPPass',
                      'PathtoPGPPass','pgpbegin','pgpend','pgptmpf','pgpheader');
 
-################################### Defaults ##################################
-my %Config = (NNTPServer => "localhost",
+################################### Defaults ###################################
+# Please do not change anything in here!
+# Use a runtime configuration file (.yapfaqrc by default) to override defaults.
+my %Config = (NNTPServer => "",
               NNTPUser   => "",
               NNTPPass   => "",
               Sender     => "",
@@ -50,7 +55,7 @@ my @PGPorderheaders = ('from', 'newsgroups', 'subject', 'control',
        'content-transfer-encoding', 'summary', 'keywords', 'cancel-lock',
        'cancel-key', 'also-control', 'x-pgp', 'user-agent');
 
-############################# End of Configuration #############################
+################################# Main program #################################
 
 use strict;
 use Net::NNTP;
@@ -166,7 +171,7 @@ sub readconfig{
   my ($File, $Config, $Faq) = @_;
   my ($LastEntry, $Error, $i) = ('','',0);
 
-  print "Reading configuration.\n" if($Options{'v'});
+  print "Reading configuration from $$File.\n" if($Options{'v'});
 
   open FH, "<$$File" or die "$0: E: Can't open $$File: $!";
   while (<FH>) {
@@ -207,10 +212,12 @@ sub readconfig{
       $Error .= "E: The Posting-frequency for your project \"$$Config[$i]{'name'}\" is invalid.\n"
     }
     unless(!$$Config[$i]{'expires'} || $$Config[$i]{'expires'} =~ /^\s*\d+\s*[dwmy]\s*$/) {
-         warn "$0: W: The Expires for your project \"$$Config[$i]{'name'}\" is invalid - set to 3 month.\n";
+      warn "$0: W: The Expires for your project \"$$Config[$i]{'name'}\" is invalid - set to 3 month.\n";
+      $$Config[$i]{'expires'} = '3m'; # set default (3 month) if expires is unset or invalid
     }
-    unless(defined($$Config[$i]{'mid-format'}) && $$Config[$i]{'mid-format'} =~ /^<\S+\@\S{2,}\.\S{2,}>$/) {
-         warn "$0: W: The Expires for your project \"$$Config[$i]{'name'}\" seems to be invalid - set to default.\n";
+    unless(!$$Config[$i]{'mid-format'} || $$Config[$i]{'mid-format'} =~ /^<\S+\@(\S+\.)?\S{2,}\.\S{2,}>/) {
+      warn "$0: W: The Message-ID format for your project \"$$Config[$i]{'name'}\" seems to be invalid - set to default.\n";
+      $$Config[$i]{'mid-format'} = '<%n-%d.%m.%y@'.hostfqdn.'>'; # set default if mid-format is invalid
     }
   }
   $Error .= "-" x 25 . 'program terminated' . "-" x 25 . "\n" if $Error;
@@ -232,6 +239,21 @@ sub calcdelta {
   }
   return ($NYear, $NMonth, $NDay);
 }
+
+################################ updatestatus ###############################
+# Takes a MID and a status file name
+# and writes status information to disk
+
+sub updatestatus {
+  my ($ActName, $File, $date, $MID) = @_;
+  
+  print "$$ActName: Save status information.\n" if($Options{'v'});
+
+  open (FH, ">$$File.cfg") or die "$0: E: Can't open $$File.cfg: $!";
+  print FH "##;; Lastpost: $date\n";
+  print FH "##;; LastMID: $MID\n";
+  close FH;
+}
   
 ################################## postfaq ##################################
 # Takes a filename and many other vars.
@@ -247,13 +269,15 @@ sub postfaq {
   #Prepare MID:
   $$TDM = ($$TDM < 10 && $$TDM !~ /^0/) ? "0" . $$TDM : $$TDM;
   $$TDD = ($$TDD < 10 && $$TDD !~ /^0/) ? "0" . $$TDD : $$TDD;
+  my $Timestamp = time;
 
   $MID = $$MIDF;
-  $MID = '<%n-%d.%m.%y@'.hostfqdn.'>' if !defined($MID);
+  $MID = '<%n-%d.%m.%y@'.hostfqdn.'>' if !defined($MID); # set to default if unset
   $MID =~ s/\%n/$$ActName/g;
   $MID =~ s/\%d/$$TDD/g;
   $MID =~ s/\%m/$$TDM/g;
   $MID =~ s/\%y/$$TDY/g;
+  $MID =~ s/\%t/$Timestamp/g;
 
   #Now get the body:
   open (FH, "<$$File");
@@ -321,18 +345,13 @@ sub postfaq {
   
   # post article
   print "$$ActName: Posting article ...\n" if($Options{'v'});
-  post(\@Article);
-
-  # Test mode?
-  return if($Options{'t'});
-
-  # otherwise: update status data
-  print "$$ActName: Save status information.\n" if($Options{'v'});
-
-  open (FH, ">$$File.cfg") or die "$0: E: Can't open $$File.cfg: $!";
-  print FH "##;; Lastpost: $day.$month.$year\n";
-  print FH "##;; LastMID: $MID\n";
-  close FH;
+  my $failure = post(\@Article);
+  
+  if ($failure) {
+    print "$$ActName: Posting failed, ERROR.dat may have more information.\n" if($Options{'v'} && (!defined($Options{'t'}) || $Options{'t'} !~ /console/i));
+  } else {
+    updatestatus($ActName, $File, "$day.$month.$year", $MID) if !defined($Options{'t'});
+  }
 }
 
 ################################## post ##################################
@@ -342,42 +361,49 @@ sub postfaq {
 
 sub post {
   my ($ArticleR) = @_;
+  my ($failure) = -1;
 
-  # Test mode?
+  # test mode - print article to console
   if(defined($Options{'t'}) and $Options{'t'} =~ /console/i) {
     print "-----BEGIN--------------------------------------------------\n";
-       print @$ArticleR;
+    print @$ArticleR;
     print "------END---------------------------------------------------\n";
-       return;
-  }
-
-  # pipe to script?
-  if(defined($Options{'s'})) {
+  # pipe article to script
+  } elsif(defined($Options{'s'})) {
     open (POST, "| $Options{'s'}") or die "$0: E: Cannot fork $Options{'s'}: $!\n";
     print POST @$ArticleR;
     close POST;
-    return;
-  }
-
-  my $NewsConnection = Net::NNTP->new($Config{'NNTPServer'}, Reader => 1)    or die "$0: E: Can't connect to news server '$Config{'NNTPServer'}'!\n";
-  $NewsConnection->authinfo ($Config{'NNTPUser'}, $Config{'NNTPPass'}) if (defined($Config{'NNTPUser'}));
-  $NewsConnection->post();
-  $NewsConnection->datasend (@$ArticleR);
-  $NewsConnection->dataend();
-
-  # Posting failed? Save to ERROR.dat
-  if (!$NewsConnection->ok()) {
-    open FH, ">>ERROR.dat";
-    print FH "\nPosting failed! Saving to ERROR.dat. Response from news server:\n";
-    print FH $NewsConnection->code();
-    print FH $NewsConnection->message();
-    print FH "\n";
-    print FH @$ArticleR;
-    print FH "-" x 80, "\n";
-    close FH;
+    if ($? == 0) {
+      $failure = 0;
+    } else {
+      warn "$0: W: $Options{'s'} exited with status ", ($? >> 8), "\n";
+      $failure = $?;
+    }
+  # post article
+  } else {
+    my $NewsConnection = Net::NNTP->new($Config{'NNTPServer'}, Reader => 1) or die "$0: E: Can't connect to news server '$Config{'NNTPServer'}'!\n";
+    $NewsConnection->authinfo ($Config{'NNTPUser'}, $Config{'NNTPPass'}) if (defined($Config{'NNTPUser'}));
+    $NewsConnection->post();
+    $NewsConnection->datasend (@$ArticleR);
+    $NewsConnection->dataend();
+
+    if ($NewsConnection->ok()) {
+      $failure = 0;
+    # Posting failed? Save to ERROR.dat
+    } else {
+         warn "$0: W: Posting failed!\n";
+      open FH, ">>ERROR.dat";
+      print FH "\nPosting failed! Saving to ERROR.dat. Response from news server:\n";
+      print FH $NewsConnection->code();
+      print FH $NewsConnection->message();
+      print FH "\n";
+      print FH @$ArticleR;
+      print FH "-" x 80, "\n";
+      close FH;
+    }
+    $NewsConnection->quit();
   }
-
-  $NewsConnection->quit();
+  return $failure;
 }
 
 #-------- sub getpgpcommand
@@ -553,7 +579,7 @@ yapfaq - Post Usenet FAQs I<(yet another postfaq)>
 
 =head1 SYNOPSIS
 
-B<yapfaq> [B<-hvpd>] [B<-t> I<newsgroups> | CONSOLE] [B<-f> I<project name>] [B<-s> I<program>] [B<-c> I<.rc file>]
+B<yapfaq> [B<-Vhvpd>] [B<-t> I<newsgroups> | CONSOLE] [B<-f> I<project name>] [B<-s> I<program>] [B<-c> I<.rc file>]
 
 =head1 REQUIREMENTS
 
@@ -618,7 +644,7 @@ or I<B<m>onths> or I<B<y>ears>.
 
 This value must be set.
 
-=item B<Expires> = I<time period>
+=item B<Expires> = I<time period> (optional)
 
 The period of time after which your message will expire. An Expires
 header will be calculated adding this time period to today's date.
@@ -626,7 +652,7 @@ header will be calculated adding this time period to today's date.
 You can declare this  time period either in I<B<d>ays> or I<B<w>weeks>
 or I<B<m>onths> or I<B<y>ears>.
 
-This setting is optional; the default  is 3 months.
+This setting is optional; the default is 3 months.
 
 =item B<From> = I<author>
 
@@ -652,32 +678,37 @@ appear in the Newsgroups header of the message.
 
 This value must be set.
 
-=item B<Fup2> = I<newsgroup | poster>
+=item B<Fup2> = I<newsgroup | poster>  (optional)
 
 A comma-separated list of newsgroup(s) or the special string I<poster>
 as it will appear in the Followup-To header of the message.
 
 This setting is optional.
 
-=item B<MID-Format> = I<pattern>
+=item B<MID-Format> = I<pattern>  (optional)
 
 A pattern from which the message ID is generated as it will appear in
 the Message-ID header of the message.
 
 You may use the special strings C<%n> for the I<Name> of your project,
-C<%d> for the date the message is posted, C<%m> for the month and
-C<%y> for the year, respectively.
+C<%d> for the date the message is posted, C<%m> for the month, C<%y>
+for the year and C<%t> for a time stamp (number of seconds since the
+epoch), respectively.
 
-This value must be set.
+This setting is optional; the default is '<%n-%d.%m.%y@I<YOURHOST>>'
+where I<YOURHOST> is the fully qualified domain name (FQDN) of the
+host B<yapfaq> is running on. Obviously that will only work if you
+have defined a reasonable hostname that the hostfqdn() function of
+Net::Domain can return.
 
-=item B<Supersede> = I<yes>
+=item B<Supersede> = I<yes>  (optional)
 
 Add Supersedes header to the message containing the Message-ID header
 of the last posting.
 
 This setting is optional; you should set it to yes or leave it out.
 
-=item B<ExtraHeader> = I<additional headers>
+=item B<ExtraHeader> = I<additional headers>  (optional)
 
 The contents of I<ExtraHeader> is added verbatim to the headers of
 your message so you can add custom headers like Approved.
@@ -701,7 +732,7 @@ This setting is optional.
     
     # time period after which the posting should expire
     # use (d)ay OR (w)eek OR (m)onth OR (y)ear
-    Expires = '3m'
+    Expires = '3m'
     
     # header "From:"
     From = 'test@domain.invalid'
@@ -716,10 +747,10 @@ This setting is optional.
     NGs = 'de.test'
     
     # header "Followup-To:"
-    Fup2 = 'poster'
+    Fup2 = 'poster'
     
     # Message-ID ("%n" is $Name)
-    MID-Format = '<%n-%d.%m.%y@domain.invalid>'
+    MID-Format = '<%n-%d.%m.%y@domain.invalid>'
     
     # Supersede last posting?
     Supersede = yes
@@ -921,9 +952,26 @@ if it is due:
 
     yapfaq -t de.test -f myfaq
 
+Post all FAQs (that are due for posting) using inews from INN:
+
+    yapfaq -s inews
+
+Do a dry run using a runtime configuration from .alternaterc, showing
+which FAQs would be posted:
+
+    yapfaq -dvc .alternaterc
+
 =head1 ENVIRONMENT
 
-There are no special environment variables used by B<yapfaq>.
+=over 4
+
+=item NNTPSERVER
+
+The default NNTP server to post to, used by the Net::NNTP module. You
+can also  specify the server using the runtime configuration file (by
+default F<.yapfaqrc>).
+
+=back
 
 =head1 FILES
 
This page took 0.014561 seconds and 4 git commands to generate.