2817dcad64fa49e50cb92b823c525897c79815f1
[usenet/yapfaq.git] / yapfaq.pl
1 #! /usr/bin/perl -W
2 #
3 # yapfaq Version 0.7 by Thomas Hochstein
4 # (Original author: Marc Brockschmidt)
5 #
6 # This script posts any project described in its config-file. Most people
7 # will use it in combination with cron(8).
8
9 # Copyright (C) 2003 Marc Brockschmidt <marc@marcbrockschmidt.de>
10 # Copyright (c) 2010 Thomas Hochstein <thh@inter.net>
11 #
12 # It can be redistributed and/or modified under the same terms under 
13 # which Perl itself is published.
14
15 my $Version = "0.8-prelease";
16
17 # Please do not change this setting!
18 # You may override the default .rc file (.yapfaqrc) by using "-c .rc file"
19 my $RCFile = '.yapfaqrc';
20 # Valid configuration variables for use in a .rc file
21 my @ValidConfVars = ('NNTPServer','NNTPUser','NNTPPass','Sender','ConfigFile');
22
23 ################################### Defaults ###################################
24 # Please do not change anything in here!
25 # Use a runtime configuration file (.yapfaqrc by default) to override defaults.
26 my %Config = (NNTPServer => "",
27               NNTPUser   => "",
28               NNTPPass   => "",
29               Sender     => "",
30               ConfigFile => "yapfaq.cfg");
31
32 ################################# Main program #################################
33
34 use strict;
35 use Net::NNTP;
36 use Net::Domain qw(hostfqdn);
37 use Date::Calc qw(Add_Delta_YM Add_Delta_Days Delta_Days Today);
38 use Fcntl ':flock'; # import LOCK_* constants
39 use Getopt::Std;
40 my ($TDY, $TDM, $TDD) = Today(); #TD: Today's date
41
42 # read commandline options
43 my %Options;
44 getopts('Vhvpdt:f:c:s:', \%Options);
45 # -V: print version / copyright information
46 if ($Options{'V'}) {
47   print "$0 v $Version\nCopyright (c) 2003 Marc Brockschmidt <marc\@marcbrockschmidt.de>\nCopyright (c) 2010 Thomas Hochstein <thh\@inter.net>\n";
48   print "This program is free software; you may redistribute it and/or modify it under the same terms as Perl itself.\n";
49   exit(0);
50 }
51 # -h: feed myself to perldoc
52 if ($Options{'h'}) {
53   exec ('perldoc', $0);
54   exit(0);
55 };
56 # -f: set $Faq
57 my ($Faq) = $Options{'f'} if ($Options{'f'});
58
59 # read runtime configuration (configuration variables)
60 $RCFile = $Options{'c'} if ($Options{'c'});
61 if (-f $RCFile) {
62   readrc (\$RCFile,\%Config);
63 } else {
64   warn "$0: W: .rc file $RCFile does not exist!\n";
65 }
66
67 # read configuration (configured FAQs)
68 my @Config;
69 readconfig (\$Config{'ConfigFile'}, \@Config, \$Faq);
70
71 # for each FAQ:
72 # - parse configuration
73 # - read status data
74 # - if FAQ is due: call postfaq()
75 foreach (@Config) { 
76   my ($LPD,$LPM,$LPY) = (01, 01, 0001);  #LP: Last posting-date
77   my ($NPY,$NPM,$NPD);                   #NP: Next posting-date
78   my $SupersedeMID;
79   
80   my ($ActName,$File,$PFreq,$Expire) =($$_{'name'},$$_{'file'},$$_{'posting-frequency'},$$_{'expires'});
81   my ($From,$Subject,$NG,$Fup2)=($$_{'from'},$$_{'subject'},$$_{'ngs'},$$_{'fup2'});
82   my ($MIDF,$ReplyTo,$ExtHea)=($$_{'mid-format'},$$_{'reply-to'},$$_{'extraheader'});
83   my ($Supersede)            =($$_{'supersede'});
84
85   # -f: loop if not FAQ to post
86   next if (defined($Faq) && $ActName ne $Faq);
87         
88   # read status data
89   if (open (FH, "<$File.cfg")) {
90     while(<FH>){
91       if (/##;; Lastpost:\s*(\d{1,2})\.(\d{1,2})\.(\d{2}(\d{2})?)/){
92         ($LPD, $LPM, $LPY) = ($1, $2, $3);
93       } elsif (/^##;;\s*LastMID:\s*(<\S+@\S+>)\s*$/) {
94         $SupersedeMID = $1;
95       }
96     }
97     close FH;
98   } else { 
99     warn "$0: W: Couldn't open $File.cfg: $!\n";
100   }
101
102   $SupersedeMID = "" unless $Supersede;
103
104   ($NPY,$NPM,$NPD) = calcdelta ($LPY,$LPM,$LPD,$PFreq);
105
106   # if FAQ is due: get it out
107   if (Delta_Days($NPY,$NPM,$NPD,$TDY,$TDM,$TDD) >= 0 or ($Options{'p'})) {
108     if($Options{'d'}) {
109           print "$ActName: Would be posted now (but running in simulation mode [$0 -d]).\n" if $Options{'v'};
110         } else {
111       postfaq(\$ActName,\$File,\$From,\$Subject,\$NG,\$Fup2,\$MIDF,\$ExtHea,\$Config{'Sender'},\$TDY,\$TDM,\$TDD,\$ReplyTo,\$SupersedeMID,\$Expire);
112         }
113   } elsif($Options{'v'}) {
114     print "$ActName: Nothing to do.\n";
115   }
116 }
117
118 exit;
119
120 #################################### readrc ####################################
121 # Takes a filename and the reference to an array which contains the valid options
122
123 sub readrc{
124   my ($File, $Config) = @_;
125
126   print "Reading $$File.\n" if($Options{'v'});
127
128   open FH, "<$$File" or die "$0: Can't open $$File: $!";
129   while (<FH>) {
130     if (/^\s*(\S+)\s*=\s*'?(.*?)'?\s*(#.*$|$)/) {
131       if (grep(/$1/,@ValidConfVars)) {
132         $$Config{$1} = $2 if $2 ne '';
133       } else {
134         warn "$0: W: $1 is not a valid configuration variable (reading from $$File)\n";
135       }
136     }
137   }
138 }
139
140 ################################## readconfig ##################################
141 # Takes a filename, a reference to an array, which will hold hashes with
142 # the data from $File, and - optionally - the name of the (single) FAQ to post
143
144 sub readconfig{
145   my ($File, $Config, $Faq) = @_;
146   my ($LastEntry, $Error, $i) = ('','',0);
147
148   print "Reading configuration from $$File.\n" if($Options{'v'});
149
150   open FH, "<$$File" or die "$0: E: Can't open $$File: $!";
151   while (<FH>) {
152     next if (defined($$Faq) && !/^\s*=====\s*$/ && defined($$Config[$i]{'name'}) && $$Config[$i]{'name'} ne $$Faq );
153     if (/^(\s*(\S+)\s*=\s*'?(.*?)'?\s*(#.*$|$)|^(.*?)'?\s*(#.*$|$))/ && not /^\s*$/) {
154       $LastEntry = lc($2) if $2;
155       $$Config[$i]{$LastEntry} .= $3 if $3;  
156       $$Config[$i]{$LastEntry} .= "\n$5" if $5 && $5;
157     } 
158     if (/^\s*=====\s*$/) {
159       $i++;
160     }
161   }
162   close FH;
163
164   #Check saved values:
165   for $i (0..$i){
166     next if (defined($$Faq) && defined($$Config[$i]{'name'}) && $$Config[$i]{'name'} ne $$Faq );
167     unless(defined($$Config[$i]{'name'}) && $$Config[$i]{'name'} =~ /^\S+$/) {
168       $Error .= "E: The name of your project \"$$Config[$i]{'name'}\" is not defined or contains whitespaces.\n"
169     }
170     unless(defined($$Config[$i]{'file'}) && -f $$Config[$i]{'file'}) {
171       $Error .= "E: The file to post for your project \"$$Config[$i]{'name'}\" is not defined or does not exist.\n"
172     }
173     unless(defined($$Config[$i]{'from'}) && $$Config[$i]{'from'} =~ /\S+\@(\S+\.)?\S{2,}\.\S{2,}/) {
174       $Error .= "E: The From header for your project \"$$Config[$i]{'name'}\" seems to be incorrect.\n"
175     }
176     unless(defined($$Config[$i]{'ngs'}) && $$Config[$i]{'ngs'} =~ /^\S+$/) {
177       $Error .= "E: The Newsgroups header for your project \"$$Config[$i]{'name'}\" is not defined or contains whitespaces.\n"
178     }
179     unless(defined($$Config[$i]{'subject'})) {
180       $Error .= "E: The Subject header for your project \"$$Config[$i]{'name'}\" is not defined.\n"
181     }
182     unless(!$$Config[$i]{'fup2'} || $$Config[$i]{'fup2'} =~ /^\S+$/) {
183       $Error .= "E: The Followup-To header for your project \"$$Config[$i]{'name'}\" contains whitespaces.\n"
184     }
185     unless(defined($$Config[$i]{'posting-frequency'}) && $$Config[$i]{'posting-frequency'} =~ /^\s*\d+\s*[dwmy]\s*$/) {
186       $Error .= "E: The Posting-frequency for your project \"$$Config[$i]{'name'}\" is invalid.\n"
187     }
188     unless(!$$Config[$i]{'expires'} || $$Config[$i]{'expires'} =~ /^\s*\d+\s*[dwmy]\s*$/) {
189       warn "$0: W: The Expires for your project \"$$Config[$i]{'name'}\" is invalid - set to 3 month.\n";
190       $$Config[$i]{'expires'} = '3m'; # set default (3 month) if expires is unset or invalid
191     }
192     unless(!$$Config[$i]{'mid-format'} || $$Config[$i]{'mid-format'} =~ /^<\S+\@(\S+\.)?\S{2,}\.\S{2,}>/) {
193       warn "$0: W: The Message-ID format for your project \"$$Config[$i]{'name'}\" seems to be invalid - set to default.\n";
194       $$Config[$i]{'mid-format'} = '<%n-%d.%m.%y@'.hostfqdn.'>'; # set default if mid-format is invalid
195     }
196   }
197   $Error .= "-" x 25 . 'program terminated' . "-" x 25 . "\n" if $Error;
198   die $Error if $Error;
199 }
200
201 ################################# calcdelta #################################
202 # Takes a date (year,  month and day) and a time period (1d, 1w, 1m, 1y, ...)
203 # and adds the latter to the former
204
205 sub calcdelta {
206   my ($Year, $Month, $Day, $Period) = @_;
207   my ($NYear, $NMonth, $NDay);
208
209   if ($Period =~ /(\d+)\s*([dw])/) { # Is counted in days or weeks: Use Add_Delta_Days.
210     ($NYear, $NMonth, $NDay) = Add_Delta_Days($Year, $Month, $Day, (($2 eq "w")?$1 * 7: $1 * 1));
211   } elsif ($Period =~ /(\d+)\s*([my])/) { #Is counted in months or years: Use Add_Delta_YM
212     ($NYear, $NMonth, $NDay) = Add_Delta_YM($Year, $Month, $Day, (($2 eq "m")?(0,$1):($1,0)));
213   }
214   return ($NYear, $NMonth, $NDay);
215 }
216
217 ################################ updatestatus ###############################
218 # Takes a MID and a status file name
219 # and writes status information to disk
220
221 sub updatestatus {
222   my ($ActName, $File, $date, $MID) = @_;
223   
224   print "$$ActName: Save status information.\n" if($Options{'v'});
225
226   open (FH, ">$$File.cfg") or die "$0: E: Can't open $$File.cfg: $!";
227   print FH "##;; Lastpost: $date\n";
228   print FH "##;; LastMID: $MID\n";
229   close FH;
230 }
231   
232 ################################## postfaq ##################################
233 # Takes a filename and many other vars.
234 #
235 # It reads the data-file $File and then posts the article.
236
237 sub postfaq {
238   my ($ActName,$File,$From,$Subject,$NG,$Fup2,$MIDF,$ExtraHeaders,$Sender,$TDY,$TDM,$TDD,$ReplyTo,$Supersedes,$Expire) = @_;
239   my (@Header,@Body,$MID,$InRealBody,$LastModified);
240
241   print "$$ActName: Preparing to post.\n" if($Options{'v'});
242   
243   #Prepare MID:
244   $$TDM = ($$TDM < 10 && $$TDM !~ /^0/) ? "0" . $$TDM : $$TDM;
245   $$TDD = ($$TDD < 10 && $$TDD !~ /^0/) ? "0" . $$TDD : $$TDD;
246   my $Timestamp = time;
247
248   $MID = $$MIDF;
249   $MID = '<%n-%d.%m.%y@'.hostfqdn.'>' if !defined($MID); # set to default if unset
250   $MID =~ s/\%n/$$ActName/g;
251   $MID =~ s/\%d/$$TDD/g;
252   $MID =~ s/\%m/$$TDM/g;
253   $MID =~ s/\%y/$$TDY/g;
254   $MID =~ s/\%t/$Timestamp/g;
255
256   #Now get the body:
257   open (FH, "<$$File");
258   while (<FH>){  
259     s/\r//;
260     push (@Body, $_), next if $InRealBody;
261     $InRealBody++ if /^$/;
262     $LastModified = $1 if /^Last-modified: (\S+)$/i;
263     push @Body, $_;
264   }
265   close FH;
266   push @Body, "\n" if ($Body[-1] ne "\n");
267
268   #Create Date- and Expires-Header:
269   my @time = localtime;
270   my $ss =  ($time[0]<10) ? "0" . $time[0] : $time[0];
271   my $mm =  ($time[1]<10) ? "0" . $time[1] : $time[1];
272   my $hh =  ($time[2]<10) ? "0" . $time[2] : $time[2];
273   my $day = $time[3];
274   my $month = ($time[4]+1<10) ? "0" . ($time[4]+1) : $time[4]+1;
275   my $monthN = ("Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec")[$time[4]];
276   my $wday = ("Sun","Mon","Tue","Wed","Thu","Fri","Sat")[$time[6]];
277   my $year = (1900 + $time[5]);
278   my $tz = $time[8] ? " +0200" : " +0100";
279
280   $$Expire = '3m' if !$$Expire; # set default if unset: 3 month
281
282   my ($expY,$expM,$expD) = calcdelta ($year,$month,$day,$$Expire);
283   my $expmonthN = ("Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec")[$expM-1];
284
285   my $date = "$day $monthN $year " . $hh . ":" . $mm . ":" . $ss . $tz;
286   my $expdate = "$expD $expmonthN $expY $hh:$mm:$ss$tz";
287  
288   #Replace %LM by the content of the news.answer-pseudo-header Last-modified:
289   if ($LastModified) {
290     $$Subject =~ s/\%LM/$LastModified/;
291   }
292
293   # Test mode?
294   if($Options{'t'} and $Options{'t'} !~ /console/i) {
295     $$NG = $Options{'t'};
296   }
297
298   #Now create the complete Header:
299   push @Header, "From: $$From\n";
300   push @Header, "Newsgroups: $$NG\n";
301   push @Header, "Followup-To: $$Fup2\n" if $$Fup2;
302   push @Header, "Subject: $$Subject\n";
303   push @Header, "Message-ID: $MID\n";
304   push @Header, "Supersedes: $$Supersedes\n" if $$Supersedes;
305   push @Header, "Date: $date\n";
306   push @Header, "Expires: $expdate\n";
307   push @Header, "Sender: $$Sender\n" if $$Sender;
308   push @Header, "Mime-Version: 1.0\n";
309   push @Header, "Reply-To: $$ReplyTo\n" if $$ReplyTo;
310   push @Header, "Content-Type: text/plain; charset=ISO-8859-15\n";
311   push @Header, "Content-Transfer-Encoding: 8bit\n";
312   push @Header, "User-Agent: yapfaq/$Version\n";
313   if ($$ExtraHeaders) {
314     push @Header, "$_\n" for (split /\n/, $$ExtraHeaders);
315   }
316
317   my @Article = (@Header, "\n", @Body);
318
319   # post article
320   print "$$ActName: Posting article ...\n" if($Options{'v'});
321   my $failure = post(\@Article);
322   
323   if ($failure) {
324     print "$$ActName: Posting failed, ERROR.dat may have more information.\n" if($Options{'v'} && (!defined($Options{'t'}) || $Options{'t'} !~ /console/i));
325   } else {
326     updatestatus($ActName, $File, "$day.$month.$year", $MID) if !defined($Options{'t'});
327   }
328 }
329
330 ################################## post ##################################
331 # Takes a complete article (Header and Body).
332 #
333 # It opens a connection to $NNTPServer and posts the message.
334
335 sub post {
336   my ($ArticleR) = @_;
337   my ($failure) = -1;
338
339   # test mode - print article to console
340   if(defined($Options{'t'}) and $Options{'t'} =~ /console/i) {
341     print "-----BEGIN--------------------------------------------------\n";
342     print @$ArticleR;
343     print "------END---------------------------------------------------\n";
344   # pipe article to script
345   } elsif(defined($Options{'s'})) {
346     open (POST, "| $Options{'s'}") or die "$0: E: Cannot fork $Options{'s'}: $!\n";
347     print POST @$ArticleR;
348     close POST;
349     if ($? == 0) {
350       $failure = 0;
351     } else {
352       warn "$0: W: $Options{'s'} exited with status ", ($? >> 8), "\n";
353       $failure = $?;
354     }
355   # post article
356   } else {
357     my $NewsConnection = Net::NNTP->new($Config{'NNTPServer'}, Reader => 1) or die "$0: E: Can't connect to news server '$Config{'NNTPServer'}'!\n";
358     $NewsConnection->authinfo ($Config{'NNTPUser'}, $Config{'NNTPPass'}) if (defined($Config{'NNTPUser'}));
359     $NewsConnection->post();
360     $NewsConnection->datasend (@$ArticleR);
361     $NewsConnection->dataend();
362
363     if ($NewsConnection->ok()) {
364       $failure = 0;
365     # Posting failed? Save to ERROR.dat
366     } else {
367           warn "$0: W: Posting failed!\n";
368       open FH, ">>ERROR.dat";
369       print FH "\nPosting failed! Saving to ERROR.dat. Response from news server:\n";
370       print FH $NewsConnection->code();
371       print FH $NewsConnection->message();
372       print FH "\n";
373       print FH @$ArticleR;
374       print FH "-" x 80, "\n";
375       close FH;
376     }
377     $NewsConnection->quit();
378   }
379   return $failure;
380 }
381
382 __END__
383
384 ################################ Documentation #################################
385
386 =head1 NAME
387
388 yapfaq - Post Usenet FAQs I<(yet another postfaq)>
389
390 =head1 SYNOPSIS
391
392 B<yapfaq> [B<-Vhvpd>] [B<-t> I<newsgroups> | CONSOLE] [B<-f> I<project name>] [B<-s> I<program>] [B<-c> I<.rc file>]
393
394 =head1 REQUIREMENTS
395
396 =over 2
397
398 =item -
399
400 Perl 5.8 or later
401
402 =item -
403
404 Net::NNTP
405
406 =item -
407
408 Date::Calc
409
410 =item -
411
412 Getopt::Std
413
414 =back
415
416 Furthermore you need access to a news server to actually post FAQs.
417
418 =head1 DESCRIPTION
419
420 B<yapfaq> posts (one or more) FAQs to Usenet with a certain posting
421 frequency (every n days, weeks, months or years), adding all necessary
422 headers as defined in its config file (by default F<yapfaq.cfg>).
423
424 =head2 Configuration
425
426 F<yapfaq.cfg> consists of one or more blocks, separated by C<=====> on
427 a single line, each containing the configuration for one FAQ as a set
428 of definitions in the form of I<param = value>. Everything after a "#"
429 sign is ignored so you may comment your configuration file.
430
431 =over 4
432
433 =item B<Name> = I<project name>
434
435 A name referring to your FAQ, also used for generation of a Message-ID.
436
437 This value must be set.
438
439 =item B<File> = I<file name>
440
441 A file containing the message body of your FAQ and all pseudo headers
442 (subheaders in the news.answers style).
443
444 This value must be set.
445
446 =item B<Posting-frequency> = I<time period>
447
448 The posting frequency defines how often your FAQ will be posted.
449 B<yapfaq> will only post your FAQ if this period of time has passed
450 since the last posting.
451
452 You can declare that time period either in I<B<d>ays> or I<B<w>weeks>
453 or I<B<m>onths> or I<B<y>ears>.
454
455 This value must be set.
456
457 =item B<Expires> = I<time period> (optional)
458
459 The period of time after which your message will expire. An Expires
460 header will be calculated adding this time period to today's date.
461
462 You can declare this  time period either in I<B<d>ays> or I<B<w>weeks>
463 or I<B<m>onths> or I<B<y>ears>.
464
465 This setting is optional; the default is 3 months.
466
467 =item B<From> = I<author>
468
469 The author of your FAQ as it will appear in the From header of the
470 message.
471
472 This value must be set.
473
474 =item B<Subject> = I<subject>
475
476 The title of your FAQ as it will appear in the Subject header of the
477 message.
478
479 You may use the special string C<%LM> which will be replaced with
480 the contents of the Last-Modified subheader in your I<File>.
481
482 This value must be set.
483
484 =item B<NGs> = I<newsgroups>
485
486 A comma-separated list of newsgroup(s) to post your FAQ to as it will
487 appear in the Newsgroups header of the message.
488
489 This value must be set.
490
491 =item B<Fup2> = I<newsgroup | poster>  (optional)
492
493 A comma-separated list of newsgroup(s) or the special string I<poster>
494 as it will appear in the Followup-To header of the message.
495
496 This setting is optional.
497
498 =item B<MID-Format> = I<pattern>  (optional)
499
500 A pattern from which the message ID is generated as it will appear in
501 the Message-ID header of the message.
502
503 You may use the special strings C<%n> for the I<Name> of your project,
504 C<%d> for the date the message is posted, C<%m> for the month, C<%y>
505 for the year and C<%t> for a time stamp (number of seconds since the
506 epoch), respectively.
507
508 This setting is optional; the default is '<%n-%d.%m.%y@I<YOURHOST>>'
509 where I<YOURHOST> is the fully qualified domain name (FQDN) of the
510 host B<yapfaq> is running on. Obviously that will only work if you
511 have defined a reasonable hostname that the hostfqdn() function of
512 Net::Domain can return.
513
514 =item B<Supersede> = I<yes>  (optional)
515
516 Add Supersedes header to the message containing the Message-ID header
517 of the last posting.
518
519 This setting is optional; you should set it to yes or leave it out.
520
521 =item B<ExtraHeader> = I<additional headers>  (optional)
522
523 The contents of I<ExtraHeader> is added verbatim to the headers of
524 your message so you can add custom headers like Approved.
525
526 This setting is optional.
527
528 =back
529
530 =head3 Example configuration file
531
532     # name of your project
533     Name = 'testpost'
534     
535     # file to post (complete body and pseudo-headers)
536     # ($File.cfg contains data on last posting and last MID)
537     File = 'test.txt'
538     
539     # how often your project should be posted
540     # use (d)ay OR (w)eek OR (m)onth OR (y)ear
541     Posting-frequency = '1d'
542     
543     # time period after which the posting should expire
544     # use (d)ay OR (w)eek OR (m)onth OR (y)ear
545     # Expires = '3m'
546     
547     # header "From:"
548     From = 'test@domain.invalid'
549     
550     # header "Subject:"
551     # (may contain "%LM" which will be replaced by the contents of the
552     #  Last-Modified pseudo header).
553     Subject = 'test noreply ignore'
554     
555     # comma-separated list of newsgroup(s) to post to
556     # (header "Newsgroups:")
557     NGs = 'de.test'
558     
559     # header "Followup-To:"
560     # Fup2 = 'poster'
561     
562     # Message-ID ("%n" is $Name)
563     # MID-Format = '<%n-%d.%m.%y@domain.invalid>'
564     
565     # Supersede last posting?
566     Supersede = yes
567     
568     # extra headers (appended verbatim)
569     # use this for custom headers like "Approved:"
570     ExtraHeader = 'Approved: moderator@domain.invalid
571     X-Header: Some text'
572     
573     # other projects may follow separated with "====="
574     =====
575     
576     Name = 'othertest'
577     File = 'test.txt'
578     Posting-frequency = '2m'
579     From = 'My Name <my.name@domain.invalid>'
580     Subject = 'Test of yapfag <%LM>'
581     NGs = 'de.test,de.alt.test'
582     Fup2 = 'de.test'
583     MID-Format = '<%n-%m.%y@domain.invalid>'
584     Supersede = yes
585
586 =head3 Status Information
587
588 Information about the last post and about how to form message IDs for
589 posts is stored in a file named F<I<project name>.cfg> which will be
590 generated if it does not exist. Each of those status files will
591 contain two lines, the first being the date of the last time the FAQ
592 was posted and the second being the message ID of that incarnation.
593
594 =head2 Runtime Configuration
595
596 Apart from configuring which FAQ(s) to post you may (re)set some
597 runtime configuration variables via the .rcfile (by default
598 F<.yapfaqrc>). F<.yapfaqrc> must contain one definition in the form of
599 I<param = value> on each line; everything after a "#" sign is ignored.
600
601 If you omit some settings they will be set to default values hardcoded
602 in F<yapfaq.pl>.
603
604 B<Please note that all parameter names are case-sensitive!>
605
606 =over 4
607
608 =item B<NNTPServer> = I<NNTP server> (mandatory)
609
610 Host name of the NNTP server to post to. Must be set (or omitted; the
611 default is "localhost"); if set to en empty string, B<yapfaq> falls
612 back to Perl's build-in defaults (contents of environment variables
613 NNTPSERVER and NEWSHOST; if not set, default from Net::Config; if not
614 set, "news" is used).
615
616 =item B<NNTPUser> = I<user name> (optional)
617
618 User name used for authentication with the NNTP server (I<AUTHINFO
619 USER>).
620
621 This setting is optional; if it is not set, I<NNTPPass> is ignored and
622 no authentication is tried.
623
624 =item B<NNTPPass> = I<password> (optional)
625
626 Password used for authentication with the NNTP server (I<AUTHINFO
627 PASS>).
628
629 This setting is optional; it must be set if I<NNTPUser> is present.
630
631 =item B<Sender> = I<Sender header> (optional)
632
633 The Sender header that will be added to every posted message.
634
635 This setting is optional.
636
637 =item B<ConfigFile> = I<configuration file> (mandatory)
638
639 The configuration file defining the FAQ(s) to post. Must be set (or
640 omitted; the default is "yapfaq.cfg").
641
642 =back
643
644 =head3 Example runtime configuration file
645
646     NNTPServer = 'localhost'
647     NNTPUser   = ''
648     NNTPPass   = ''
649     Sender     = ''
650     ConfigFile = 'yapfaq.cfg'
651
652 =head3 Using more than one runtime configuration
653
654 You may use more than one runtime configuration file with the B<-c>
655 option (see below).
656
657 =head1 OPTIONS
658
659 =over 3
660
661 =item B<-V> (version)
662
663 Print out version and copyright information on B<yapfaq> and exit.
664
665 =item B<-h> (help)
666
667 Print this man page and exit.
668
669 =item B<-v> (verbose)
670
671 Print out status information while running to STDOUT.
672
673 =item B<-p> (post unconditionally)
674
675 Post (all) FAQs unconditionally ignoring the posting frequency setting.
676
677 You may want to use this with the B<-f> option (see below).
678
679 =item B<-d> (dry run)
680
681 Start B<yapfaq> in simulation mode, i.e. don't post anything and don't
682 update any status information.
683
684 =item B<-t> I<newsgroup(s) | CONSOLE> (test)
685
686 Don't post to the newsgroups defined in F<yqpfaq.cfg>, but to the
687 newsgroups given after B<-t> as a comma-separated list or print the
688 FAQs to STDOUT separated by lines of dashes if the special string
689 C<CONSOLE> is given.  This can be used to preview what B<yapfaq> would
690 do without embarassing yourself on Usenet.  The status files are not
691 updated when this option is given.
692
693 You may want to use this with the B<-f> option (see below).
694
695 =item B<-f> I<project name>
696
697 Just deal with one FAQ only.
698
699 By default B<yapfaq> will work on all FAQs that are defined in
700 F<yapfaq.cfg>, check whether they are due for posting and - if they
701 are - post them. Consequently when the B<-p> option is set all FAQs
702 will be posted unconditionally. That may not be what you want to
703 achieve, so you can limit the operation of B<yapfaq> to the named FAQ
704 only.
705
706 =item B<-s> I<program> (pipe to script)
707
708 Instead of posting the article(s) to Usenet pipe them to the external
709 I<program> on STDIN (which may post the article(s) then). A return
710 value of 0 will be considered success.
711
712 For example, you may want to use the I<inews> utility from the INN package
713 or the much more powerful replacement I<tinews.pl> from
714 I<ftp://ftp.tin.org/tin/tools/tinews.pl> which is able to sign postings.
715
716 =item B<-c> I<.rc file>
717
718 Load another runtime configuration file (.rc file) than F<.yaofaq.rc>.
719
720 You may for example define another usenet server to post your FAQ(s)
721 to or load another configuration file defining (an)other FAQ(s).
722
723 =back
724
725 =head1 EXAMPLES
726
727 Post all FAQs that are due for posting:
728
729     yapfaq
730
731 Do a dry run, showing which FAQs would be posted:
732
733     yapfaq -dv
734
735 Do a test run and print on STDOUT what the FAQ I<myfaq> would look
736 like when posted, regardless whether it is due for posting or not:
737
738     yapfaq -pt CONSOLE -f myfaq
739
740 Do a "real" test run and post the FAQ I<myfaq> to I<de.test>, but only
741 if it is due:
742
743     yapfaq -t de.test -f myfaq
744
745 Post all FAQs (that are due for posting) using inews from INN:
746
747     yapfaq -s inews
748
749 Do a dry run using a runtime configuration from .alternaterc, showing
750 which FAQs would be posted:
751
752     yapfaq -dvc .alternaterc
753
754 =head1 ENVIRONMENT
755
756 =over 4
757
758 =item NNTPSERVER
759
760 The default NNTP server to post to, used by the Net::NNTP module. You
761 can also  specify the server using the runtime configuration file (by
762 default F<.yapfaqrc>).
763
764 =back
765
766 =head1 FILES
767
768 =over 4
769
770 =item F<yapfaq.pl>
771
772 The script itself.
773
774 =item F<.yapfaqrc>
775
776 Runtime configuration file for B<yapfaq>.
777
778 =item F<yapfaq.cfg>
779
780 Configuration file for B<yapfaq>.
781
782 =item F<*.cfg>
783
784 Status data on FAQs.
785
786 The status files will be created on successful posting if they don't
787 already exist. The first line of the file will be the date of the last
788 time the FAQ was posted and the second line will be the message ID of
789 the last post of that FAQ.
790
791 =back
792
793 =head1 BUGS
794
795 Many, I'm sure.
796
797 =head1 SEE ALSO
798
799 L<http://th-h.de/download/scripts.php> will have the current
800 version of this program.
801
802 =head1 AUTHOR
803
804 Thomas Hochstein <thh@inter.net>
805
806 Original author (up to version 0.5b, dating from 2003):
807 Marc Brockschmidt <marc@marcbrockschmidt.de>
808
809 =head1 COPYRIGHT AND LICENSE
810
811 Copyright (c) 2003 Marc Brockschmidt <marc@marcbrockschmidt.de>
812
813 Copyright (c) 2010 Thomas Hochstein <thh@inter.net>
814
815 This program is free software; you may redistribute it and/or modify it
816 under the same terms as Perl itself.
817
818 =cut
This page took 0.029396 seconds and 2 git commands to generate.