root/grabbers/rex @ 63

Revision 63, 23.9 kB (checked in by max, 7 years ago)

Rex bugfix.

Line 
1#!/usr/bin/perl -w
2#
3# "Rex"
4
5my $version  = '3.2.2';
6
7# An Australian TV Guide Grabber (a.k.a. tv_grab_au)
8# by Max Barry
9# http://www.maxbarry.com
10#
11# Based on the long-serving but currently defunct NMSN Australian TV grabber
12# by Michael 'Immir' Smith
13#
14# Use --help for command-line options.
15#
16# A current version of this script, plus a README file, might be here:
17# http://www.whuffy.com/tv_grab_au/
18#
19# To install Perl dependencies (like XMLTV.pm), you generally need to
20# do this (as root): perl -MCPAN -e 'install <whatever>'
21# E.g. perl -MCPAN -e 'install XMLTV::Ask'
22#
23# Changelog:
24# 0.1.0   : Let there be code
25# 0.2.0   : Better caching (fewer HTTP connections)
26# 0.3.0   : Aborted attempt to use proxy caches
27# 0.4.0   : Reverted to non-proxy version; switched datasources
28# 1.0.0   : --configure works; code released
29# 1.0.1   : Bugfix : --config-file now works
30# 1.1.0   : Feature: --stats option
31# 1.1.1   : Bugfix : Download Pay TV show details properly
32# 1.1.2   : Bugfix : Replaced non-working --static with --test;
33#                    put sport and free-to-air movies in correct category
34#                    for MythTV
35# 1.1.3   : Bugfix : Don't die on a failed download, just report
36# 1.2.0   : Feature: --cache-file option
37# 1.2.1   : Bugfix : Better explanation for config-file failure
38# 1.2.2   : Bugfix : Get more than 1 day's data for Pay TV channels
39# 1.2.3   : Bugfix : Translate some category names for MythTV
40# 1.2.4   : Bugfix : Better handling of config file locations
41# 1.2.5   : Bugfix : Corrected typo in a category name
42# 2.0.0   : Datasource change: August 29th, 2006
43# 2.1.0   : Feature: Interpret javascript
44# 2.1.1   : Bugfix : Try to avoid replacing good data with poisoned data
45# 2.1.2   : Bugfix : Minor code tidy
46# 2.2.0   : Feature: --help option
47# 2.2.1   : Bugfix : Don't overwrite cache if no shows found (e.g. net failure)
48# 2.3.0   : Feature: Better detection of changes to pre-fetched guide data
49# 2.3.1   : Bugfix : adapted precache to non-fatal datasource change
50# 2.4.0   : Feature: --quick option
51# 2.4.1   : Bugfix : removed --quick (too hard to accurately parse stop time);
52#                    code tidy
53# 2.4.2   : Bugfix : progress stats on by default
54# 2.5.0   : Feature: altered/added --ignore-cache, --nowrite-cache, and
55#                    --rebuild-cache options; exit on unknown option
56# 3.0.0   : Shepherd compatibility
57# 3.1.0   : Feature: --ready option
58# 3.2.0   : Feature: gzip compression, report KB downloaded
59# 3.2.1   : Bugfix : handle failed downloads better
60# 3.2.2   : Bugfix : no empty sub-titles in cache; no output buffering
61
62use strict;
63use Getopt::Long;
64use HTTP::Request::Common;
65use LWP::UserAgent;
66use Date::Manip;
67use File::Path;
68use File::Basename;
69use Data::Dumper;
70use HTML::TreeBuilder;
71use JavaScript;
72use Storable;
73
74use XMLTV;
75use XMLTV::Ask;
76
77# ---------------------------------------------------------------------------
78# --- Global Variables
79
80my $progname = "Rex";
81my $lang = "en";
82
83my $output_dir = ($ENV{HOME} ? $ENV{HOME} . "/." : "/var/local/") . 
84                 lc($progname);         # Default location is ~/.rex/
85
86my $cache_file = "$output_dir/cache.dat";
87
88my ($count_dl, $count_detail, $count_bad, $count_cache, $count_changes, $count_kb) = (0) x 6;
89
90my $DATASOURCE             = "http://www.yourtv.com.au";
91my $DATASOURCE_SETUP       = "$DATASOURCE/profile/index.cfm?action=saveRegions";
92my $DATASOURCE_GUIDE       = "$DATASOURCE/guide/index.cfm";
93my $DATASOURCE_GUIDE_TODAY = "$DATASOURCE/guide/index.cfm?action=restofday";
94my $DATASOURCE_DETAIL      = "$DATASOURCE/guide/index.cfm";
95my $WW                     = "http://webwarper.net/ww/";
96
97my $runtime = time();
98my $laststats = $runtime;
99my $firstfetch;
100my $debug = 0;
101my $opt;
102my $channels = {};
103my %chanid;
104my $cached;
105my $precache;
106my $ua;
107my $sid;
108my $jsc;
109my %shows;
110my $numshows;
111my $dcount;
112
113# ---------------------------------------------------------------------------
114# --- Setup
115
116print "$progname $version\n";
117
118$| = 1;
119
120Getopt::Long::Configure(qw/pass_through/);
121
122get_command_line_options();
123
124exit 0 if ($opt->{ver}) or ($opt->{ready});
125
126help() if ($opt->{help});
127
128read_channels_file() if ($opt->{channels_file});
129
130setup_javascript();
131
132set_defaults();
133
134if ($opt->{test})
135{
136  print "Test mode: won't write XML or cache.\n";
137  $opt->{output} = undef;
138}
139       
140build_channel_map();
141
142if ($opt->{show_channels})
143{
144  show_channels();
145  exit 0;
146}
147
148#Date_Init("TZ=$opt->{TZ}"); # explicitly Set Timezone
149
150if ($debug or $opt->{show_config})
151{
152  show_config();
153  exit 0 if ($opt->{show_config});
154}
155
156restore_cache();
157
158if ($opt->{dump_cache})
159{
160  dump_cache();
161  exit 0;
162}
163
164# ---------------------------------------------------------------------------
165# --- Go!
166
167get_guide_data();
168
169get_details_data();
170
171save_cache() if ($numshows);
172
173write_xml();
174
175print stats(1);
176
177exit 0;
178
179# ---------------------------------------------------------------------------
180# --- Stop!
181
182
183#
184# Subs!
185#
186
187sub get_guide_data
188{
189  print "Grabbing data for days " . ($opt->{offset} + 1) .
190        " - $opt->{days}" . 
191        ($opt->{output} ? " into " . $opt->{output} : '') .
192        ".\n";
193
194  refresh_ua() unless ($ua);
195
196  my ($guidedata, $date);
197
198  for my $day ($opt->{offset} .. $opt->{days} - 1)
199  {
200    $date = Ymd(DateCalc("today", "+ $day days"));
201    print "Day $day.\n" if ($debug);
202
203    if (!$day)
204    {
205      # Special bandwidth-saving URL for day 0
206
207      $guidedata = get_page($DATASOURCE_GUIDE_TODAY);
208      parse_guide($guidedata, $date);
209    }
210    else
211    {
212      # Need to grab day in 6-hour chunks.
213      my $rid = $opt->{region};
214      for (1 .. 4)
215      {
216        $guidedata = post_page($DATASOURCE_GUIDE,
217                        [ 'action' => "sessionTimes",
218                          'region_id' => $rid,
219                          'date' => $date,
220                          'period' => $_,
221                          'submit' => 'submit'
222                        ]);
223        parse_guide($guidedata, $date, $_);
224      }
225    }
226  }
227  $numshows = scalar(keys %$precache);
228  print "Shows found: $numshows.\n";
229}
230
231#
232# This sub fills up %shows with details, either from the cache or
233# from the web.
234#
235sub get_details_data
236{
237  $firstfetch = time();
238  my $show;
239  foreach my $pid (keys %$precache)
240  {
241    if ($cached->{$pid})
242    {
243      print "Cached: " . $cached->{$pid}->{title}[0][0] . ".\n" if ($debug);
244      if (verify_cache($pid))
245      {
246        $shows{$pid} = $cached->{$pid};
247        $count_cache++;
248      }
249      else
250      {
251        print "Cache has stale data for \"" . $cached->{$pid}->{title}[0][0] .
252              "\": fetching fresh data.\n" if ($debug);
253        $count_changes++;
254      }
255    }
256    unless ($shows{$pid})
257    {
258      $show = download_show($pid);
259      if ($show)
260      {
261        $shows{$pid} = $show;
262        $count_detail++;
263      }
264      else
265      {
266        print "Failed to parse show $pid.\n";
267      }
268      sleep int(2 + rand(5));
269    }
270    if ($opt->{stats} and time() - $laststats >= $opt->{stats})
271    {
272      print stats();
273    }
274  }
275}
276
277sub verify_cache
278{
279  my $pid = shift;
280
281  my @c = ( 
282            [ 
283              'Title', 
284              $cached->{$pid}->{title}[0][0], 
285              $precache->{$pid}->{title} 
286            ],
287            [ 
288              'Start time',
289              $cached->{$pid}->{start},
290              $precache->{$pid}->{start}
291            ],
292            [
293              'Subtitle',
294              ($cached->{$pid}->{'sub-title'} ? $cached->{$pid}->{'sub-title'}[0][0] : undef),
295              $precache->{$pid}->{'sub-title'}
296            ]
297          ); 
298  foreach my $cmp (@c)
299  {
300    return 0 unless compare_caches($cmp->[0], $cmp->[1], $cmp->[2]);
301  }
302  return 1;
303}
304
305sub compare_caches
306{
307  my ($name, $arg1, $arg2) = @_;
308
309  # Special case for subtitles.
310  return 1 if ($name eq 'Subtitle' and !$arg2);
311 
312  $arg1 = '--missing--' unless $arg1;
313  $arg2 = '--missing--' unless $arg2;
314
315  return 1 if $arg1 eq $arg2;
316
317  print "$name mismatch: \"$arg1\" vs \"$arg2\".\n" if ($debug);
318  return 0;
319}
320
321sub download_show
322{
323  my ($pid, $recurse_count) = @_;
324
325  $recurse_count ||= 0;
326  return undef if ($recurse_count > 2);
327 
328  $dcount++;
329  refresh_ua() if ($dcount % 20 == 0); # don't wait for error page
330
331  my $result;
332
333  print "Downloading # $pid.\n" if ($debug);
334  my $detailsdata = get_page($DATASOURCE_DETAIL .
335                      '?action=session_info&event_id=' . $pid .
336                      '&sid=' . $sid . '&loc=grid');
337  $result = parse_details($detailsdata) if ($detailsdata);
338  unless ($detailsdata and $result)
339  {
340    print "Download failed.\n" if ($debug);
341    sleep(5);
342    $count_bad++;
343    refresh_ua();
344    return download_show($pid, $recurse_count+1);
345  }
346
347  return $result;
348}
349
350sub save_cache
351{
352  return if ($opt->{test} or $opt->{nowrite_cache});
353  print "Saving cache.\n";
354  Storable::store(\%shows, $cache_file);
355}
356
357sub write_xml
358{
359  return if ($opt->{test});
360 
361  my %writer_args = ( encoding => 'ISO-8859-1' );
362
363  print "Writing XML.\n";
364
365  if ($opt->{output}) 
366  {
367    my $fh = new IO::File(">" . $opt->{output}) 
368             or die "can't open " . $opt->{output} . ": $!";
369    $writer_args{OUTPUT} = $fh;
370  }
371
372  my $writer = new XMLTV::Writer(%writer_args);
373
374  $writer->start
375    ( { 'source-info-url'    => $DATASOURCE,
376        'source-info-name'   => "Datasource Name",
377        'generator-info-name' => "XMLTV - $progname v$version"} );
378
379  for my $channel (sort keys %$channels) 
380  {
381    my $chanid = $chanid{lc $channel};
382    $writer->write_channel( { 'display-name' => [[$channel, $lang]],
383                              'id' => $chanid } );
384  } 
385
386  foreach my $pid (keys %shows)
387  {
388    print "- " . $shows{$pid}->{'title'}[0][0] . "\n" if ($debug);
389    $writer->write_programme($shows{$pid});
390  }
391
392  $writer->end();
393}
394
395sub refresh_ua
396{
397  print "Refreshing UA.\n" if ($debug);
398 
399  if ($ua)
400  {
401     print "Sleeping...\n" if ($debug);
402     sleep 5 + int(rand(20));
403  }
404
405  my $agent = 
406    ( 
407    'Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1)',
408    'Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.0.4) Gecko/20060508 Firefox/1.5.0.4',
409    'Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.7.6) Gecko/20050512 Firefox',
410    'Opera/9.00 (Windows NT 5.1; U; en)',
411    'Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en-us) AppleWebKit/412 (KHTML, like Gecko) Safari/412'
412    )[int(rand(5))];
413               
414  $ua = LWP::UserAgent->new
415    ('timeout' => 30,
416     'keep_alive' => 1,
417     'agent' => $agent);
418  $ua->env_proxy;
419  $ua->cookie_jar({});
420
421  # Set initial cookie
422  get_page($DATASOURCE, 1);
423
424  # Set region/service cookie
425  post_page($DATASOURCE_SETUP,
426        [ 'fta_region_id' => $opt->{'region'} ],
427        1 );
428  $ua->cookie_jar()->scan(\&refresh_sid);
429
430  $dcount = 0;
431}
432
433sub refresh_sid
434{
435  my ($version, $key, $val) = @_;
436
437  $sid = $val if ($key eq 'CFID');
438}
439
440sub setup_javascript 
441{
442  print "Initializing JavaScript interpreter.\n" if ($debug);
443  $jsc = new JavaScript::Runtime->create_context();
444  $jsc->set_error_handler( sub { } );
445  $jsc->eval(qq{
446    var doc = '';
447    function Location() { this.href  = '$DATASOURCE'; }
448    function Document() { this.write = function(x) { doc += x; } }
449    function Window()   { this.___ww = 0 }
450    location = new Location;
451    document = new Document;
452    window   = new Window;
453  });
454}
455
456sub restore_cache
457{
458  unless ($opt->{ignore_cache} or $opt->{rebuild_cache})
459  {
460    if (-r $cache_file)
461    {
462      $cached = Storable::retrieve($cache_file);
463    }
464    else
465    {
466      print "Unable to read cache file: $cache_file.\n";
467    }
468  }
469  if ($cached)
470  {
471    print "Retrieved " . keys(%$cached) . " cached items from file.\n";
472  }
473  else
474  {
475    $cached = { };
476    print "Not using cache.\n";
477  }
478}
479
480sub dump_cache
481{
482  print "Cache: " . Dumper($cached) . "\n";
483}
484
485sub stats
486{
487  my $finished = shift;
488
489  my $t = time() - $runtime;
490 
491  my $ret = "$progname $version " . 
492            ($finished ? "finished" : "in progress") .
493            ":\n";
494  $ret .= sprintf(
495        " %d shows grabbed\n" .
496        " %d downloads, including %d detail pages (%d KB)\n" .
497        " %d cache hits, %d changes from cache, %d failed downloads\n",
498        scalar(keys %shows),
499        $count_dl, $count_detail, $count_kb, 
500        $count_cache, $count_changes, $count_bad);
501  $ret .= " Time elapsed: " . timestats($t) . "\n";
502  unless ($finished or !$count_detail)
503  {
504    $t = ((((time() - $firstfetch) * ($numshows - $count_cache)) / $count_detail)) - $t;
505   
506    $ret .= " Estimated time remaining: " . timestats($t) . "\n";
507  }
508  $laststats = time();
509  return $ret;
510}
511
512sub timestats
513{
514  my $t = shift;
515
516  my $ret = '';
517  if ($t >= 3600)
518  {
519    $ret .= sprintf("%d hr ", $t / 3600);
520    $t = $t % 3600;
521  }
522  $ret .= sprintf("%d min %d sec", $t / 60, $t % 60);
523  return $ret;
524}
525
526
527sub get_page
528{
529  my ($url, $ignore_failure) = @_;
530  my $request = GET $url;
531  return fetch_page($request, $ignore_failure);
532}
533
534sub post_page
535{
536  my ($url, $headers, $ignore_failure) = @_;
537  my $request = POST $url, $headers;
538  return fetch_page($request, $ignore_failure);
539}
540
541sub fetch_page
542{
543  my ($request, $ignore_failure) = @_;
544 
545  $request->uri() =~ s/^http:\/\//$WW/ if $opt->{warper};
546
547  $request->header('Accept-Encoding' => 'gzip');
548
549  print "Fetching: " . $request->as_string() . "\n" if ($debug);
550  my $response;
551  for my $c (1..2) {
552    print "Attempt #$c.\n" if ($debug);
553    $response = $ua->request($request);
554    last if ($response->is_success() or $ignore_failure);
555    sleep 5;
556  }
557  unless ($response->is_success() or $ignore_failure)
558  {
559    print "ERROR! Failed to retrieve page: " . $request->uri() . ".\n";
560  }
561  if ($debug and (my $r = $response)->previous) 
562  {
563    print "GET_CONTENT_BASE redirection backtrace:\n";
564    while ($r) { print "    ", $r->base, "\n"; $r = $r->previous }
565  }
566  $count_dl++;
567  my $page = $response->content();
568  $count_kb += (do {use bytes; length($page)}) / 1024;
569
570  if ($response->header('Content-Encoding')
571      and
572      $response->header('Content-Encoding') eq 'gzip') 
573  {
574      $page = Compress::Zlib::memGunzip($response->content());
575  }
576
577  $page =~ s/&nbsp;/ /g;
578  return $page;
579}
580
581sub parse_guide
582{
583  my ($guidedata, $date, $phase) = @_;
584
585  print "Parsing guide page.\n" if ($debug);
586
587  $guidedata = readjs(Encode::decode_utf8($guidedata));
588
589  my $tree = HTML::TreeBuilder->new_from_content($guidedata);
590  my $curchan = '';
591  my ($pid, $block, $line, $link, $title);
592  foreach my $tag ($tree->look_down('_tag' => 'td', 'class' => 'venue'))
593  {
594    next if ($curchan eq $tag->as_text()); # Ignore repeated Station name
595    $curchan = $tag->as_text();
596    $curchan =~ s/\(.*\)//;
597    if (!$channels->{$curchan})
598    {
599      print "Ignoring unsubscribed channel $curchan.\n" if ($debug);
600      next;
601    }
602    print "Channel: $curchan.\n" if ($debug);
603    $block = $tag->parent();
604    foreach $line ($block->look_down('_tag' => 'td', 'class' => undef))
605    {
606      my ($pid, $title, $start, $pextra);
607      foreach my $bit ($line->look_down('_tag' => 'div'))
608      {
609        #print "Looking at: " . $bit->as_text() . "\n" if ($debug);
610        if ($bit->attr('class') eq 'ptime')
611        {
612          if ($start)
613          {
614            addme($pid, $title, $start, $pextra);
615            $pextra = undef;
616            $title = undef;
617            $pid = undef;
618          }
619          $start = $bit->as_text();
620          if ($phase and $phase == 1 and $start =~ /pm/)
621          {
622            $start = expand_date($start ." ". Ymd(DateCalc($date, "- 1 day")));
623          }
624          else
625          {
626            $start = expand_date("$start $date");
627          }
628        }
629        elsif ($bit->attr('class') eq 'pname')
630        {
631          $title = strip_whitespace($bit->as_text());
632          if ($bit->as_HTML() =~ /session_info\(\&#39;(\d+)/) 
633          {
634            $pid = $1; 
635          }
636        }
637        elsif ($bit->attr('class') eq 'pextra')
638        {
639          $pextra = strip_whitespace($bit->as_text());
640        }
641      }
642      if ($pid)
643      {
644        addme($pid, $title, $start, $pextra);
645      }
646      else
647      {
648        print "Parsing error: No pid found in block.\n";
649      }
650    }
651  }
652}
653
654sub addme
655{
656  my ($pid, $title, $start, $pextra) = @_;
657 
658  if ($precache->{$pid})
659  {
660    print "Duplicate: $title ($pid).\n" if ($debug);
661  }
662  else
663  {
664    print "New: $title " . ($pextra ? "($pextra) " : '') . 
665          "($pid) $start\n" if ($debug);
666    if ($pextra and $pextra =~ /\(\d\d\d\d\)/)
667    {
668      print "Ignoring detail.\n" if ($debug);
669      $pextra = undef;
670    }
671    $precache->{$pid} = { 'title' => $title, 
672                          'start' => $start,
673                          'sub-title' => $pextra
674                        };
675  }
676}
677
678sub parse_details 
679{
680  my $detailsdata = shift;
681
682  my ($show, $str, @rows, $block, $start, $stop, $date, @extra, @items);
683
684  $detailsdata = readjs(Encode::decode_utf8($detailsdata));
685
686  my $tree = HTML::TreeBuilder->new_from_content($detailsdata);
687
688  $block = $tree->find('h1');
689  return undef unless ($block);  # site is probably sending that block page
690
691  $show->{'title'} = [[ strip_whitespace($block->as_text()), $lang ]];
692  $str = $tree->find('h2');
693  if ($str)
694  {
695    $show->{'sub-title'} = [[ strip_whitespace($str->as_text()), $lang ]];
696  }
697
698  $block = $tree->find('h3') or return undef;
699  @rows = $block->look_down('_tag' => 'div');
700  $date = $rows[1]->as_text();
701  if($rows[0]->as_HTML() =~ /([\d\.]+[ap]m)\s+&ndash;\s+([\d\.]+[ap]m)/)
702  {
703    ($start, $stop) = ($1, $2);
704    $show->{'start'} = expand_date("$start $date");
705    $show->{'stop'} = expand_date("$stop $date");
706  }
707  else
708  {
709    return undef;  # Failed to parse
710  }
711  if (Date_Cmp($show->{'start'}, $show->{'stop'}) == 1)
712  {
713     $show->{'stop'} = expand_date(DateCalc($show->{'stop'}, "+ 1 day"));
714     print "Adjusted STOP time.\n" if ($debug);
715  }
716  $show->{'channel'} = $chanid{lc($rows[0]->find('span')->as_text())};
717
718  $block = $tree->find('_tag' => 'hr', 'noshade') or return undef;
719  $block = $block->right();
720  if ($block->as_text())
721  {
722    $show->{'desc'} = [[ strip_whitespace($block->as_text()), $lang ]];
723    $block = $block->right()->right();
724  }
725  else
726  {
727    $block = $block->right();
728  }
729  foreach my $tag ($block->look_down('_tag' => 'tr'))
730  {
731    $str = $tag->as_text();
732    if ($str =~ /Genre:(.+)/)
733    {
734      $str = strip_whitespace($1);
735      if ($str eq 'N/A')
736      {
737        @extra = ( $str );
738      }
739      else
740      {
741        @extra = split(/\//, $str);
742      }
743      $str = $tree->find('h5');
744      if ($str and $str->as_text() =~ /movie/i)
745      {
746        unshift (@extra, 'movie');
747      }
748
749      foreach (@extra)
750      {
751        $_ = [ translate_category($_), $lang ];
752      }
753      $show->{'category'} = [ @extra ];
754    }
755    elsif ($str =~ /Rating:(.+)/)
756    {
757      $str = strip_whitespace($1);
758      if ($str =~ /(.*?)\s*\[(.+)\]/)
759      {
760        @extra = split(/, /, $2);
761        foreach (@extra)
762        {
763          $_ = [ $_, 'advisory', undef ];
764        }
765        $show->{'rating'} = [[ $1, 'CTVA', undef],  @extra ];
766      }
767      else
768      {
769        $show->{'rating'} = [[ $1, "CTVA", undef]];
770      }
771    }
772    elsif ($str =~ /Cast: (.+)/)
773    {
774      $show->{'credits'}{'actor'} = [ split(/, /, strip_whitespace($1)) ];
775    }
776    elsif ($str =~ /Year:(\d+)/)
777    {
778#      $show->{'date'} = strip_whitespace($1);
779    }
780    elsif ($str =~ /Other:(.+)/)
781    {
782      next unless (strip_whitespace($1));
783      $str = $tag->as_HTML();
784      @extra = split(/<br>/, $tag->as_HTML());
785      my @to_add;
786      foreach my $bit (@extra)
787      {
788        $bit =~ s/<.*>//;
789        $bit = strip_whitespace($bit);
790        next unless ($bit);
791        push (@to_add, $bit);
792      }
793      foreach (@to_add)
794      {
795        if ($show->{'desc'})
796        {
797          $show->{'desc'}[0][0] .= " $_.";
798        }
799        else
800        {
801          $show->{'desc'} = [[ "$_.", $lang ]];
802        }
803      }
804    }
805  }
806
807  print Dumper($show) if ($debug);
808  return $show;
809}
810
811sub translate_category
812{
813  my %translation = (   'Sport' => 'sports',
814                        'Soap Opera' => 'Soap',
815                        'Science and Technology' => 'Science/Nature',
816                        'Real Life' => 'Reality',
817                        'Cartoon' => 'Animation',
818                        'Family' => 'Children',
819                        'Murder' => 'Crime' );
820                       
821  return $translation{$_} if $translation{$_};
822  return $_;
823}
824
825sub readjs {
826  my $data = shift;
827  $data =~ s{<script type="?text/javascript"?[^>]*>(.*?)</script>}{
828    my $x = $1;
829    $jsc->eval(qq{ doc = '' });
830    $jsc->eval($x);
831    $jsc->eval(qq{ doc }) || '';
832  }isge;
833  $data;
834}
835 
836sub read_channels_file 
837{   
838    print "Reading channels file: $opt->{channels_file}\n";
839    if (-r $opt->{channels_file})
840    {
841        local (@ARGV, $/) = ($opt->{channels_file});
842        no warnings 'all';
843        eval <>;
844        die "$@" if ($@);
845    }
846    else
847    {
848        print "Unable to read channels file.\n";
849    }
850}
851
852sub get_command_line_options
853{
854  GetOptions( 'config-file=s'   => \$opt->{configfile},
855              'help'            => \$opt->{help},
856              'debug'           => \$debug,
857              'days=i'          => \$opt->{days},
858              'offset=i'        => \$opt->{offset},
859              'show-channels'   => \$opt->{show_channels},
860              'output=s'        => \$opt->{output},
861              'rebuild-cache'   => \$opt->{rebuild_cache},
862              'ignore-cache'    => \$opt->{ignore_cache},
863              'nowrite-cache'   => \$opt->{nowrite_cache},
864              'dump-cache'      => \$opt->{dump_cache},
865              'test'            => \$opt->{test},
866              'show-config'     => \$opt->{show_config},
867              'warper'          => \$opt->{warper},
868              'cache-file=s'    => \$cache_file,
869              'stats=i'         => \$opt->{stats},
870              'region=i'        => \$opt->{region},
871              'channels_file=s' => \$opt->{channels_file},
872              'version'         => \$opt->{ver},
873              'desc'            => \$opt->{desc},
874              'ready'           => \$opt->{ready}
875             );
876
877  if (@ARGV)
878  {
879    print "\nERROR: Unknown option(s): @ARGV\n";
880    help();
881  }
882}
883
884sub show_config
885{
886  my $short = shift;
887 
888  print "\nConfiguration\n".
889          "-------------\n";
890  unless ($short)
891  {
892    print "Debug mode : " . is_set($debug) . "\n" .
893          "Test mode  : " . is_set($opt->{test}) . "\n" .
894          "Webwarping : " . is_set($opt->{warper}) . "\n" .
895          "Cache file : $cache_file";
896    print ' (not reading)' if ($opt->{ignore_cache} or $opt->{rebuild_cache});
897    print ' (not updating)' if ($opt->{test} or $opt->{ignore_cache} or $opt->{nowrite_cache});
898    print "\n" .
899          "Output file: " . ($opt->{output} ? $opt->{output} : "None") . "\n" .
900          "Chann file : $opt->{channels_file}\n" .
901          "Statistics : " . ($opt->{stats} ? "every " . $opt->{stats} . " seconds" : "off") . "\n";
902  }
903  print   "Region ID  : $opt->{region}\n" .
904          "Days wanted: $opt->{days} (offset: $opt->{offset})\n";
905  show_channels();
906  print "\n";
907}
908
909sub is_set
910{
911  my $arg = shift;
912  return $arg ? "Yes" : "No";
913}
914
915sub show_channels
916{
917  print "Subscribed channels:\n";
918  print "    $_ -> $channels->{$_}\n" for sort keys %$channels;
919}
920
921sub build_channel_map
922{
923  # --- extract sorted subscribed channel list from config-file hash;
924  # also compute canonicalised lowercased channel to xmltvid hash
925  %chanid = map { lc $_, $channels->{$_} } keys %$channels;
926}
927
928sub set_defaults
929{
930  my $defaults = {
931          'days' => 7,
932          'offset' => 0,
933          'region' => 94,
934          'stats' => 300,
935          'output' => "$output_dir/guide.xml",
936          'channels_file' => "$output_dir/channels.conf"
937  };
938                                             
939  foreach (keys %$defaults)
940  {
941    unless (defined $opt->{$_})
942    {
943      $opt->{$_} = $defaults->{$_};
944    }
945  }
946
947  $opt->{'days'} = 7 if ($opt->{'days'} > 7);
948}
949
950sub expand_date
951{
952  my $t = shift;
953
954  $t =~ s/\./:/;
955  my $ret = ParseDate($t);
956
957  # --- append timezone and strip colons
958#  ($ret .= " $opt->{TZ}") =~ tr/://d;
959  $ret =~ tr/://d;
960
961  return $ret;
962}
963
964sub Ymd { UnixDate($_[0], "%Y-%m-%d") or die "problem in Ymd($_[0])" }
965
966sub strip_whitespace 
967{ 
968  my $str = shift; 
969  $str =~ s/^\s*(.*?)\s*$/$1/; 
970  return $str;
971}
972
973sub help
974{
975  print q{
976Command-line options:
977    --help                 Print this message
978
979    --version              Show current version
980    --show-channels        Show subscribed channels & exit
981    --show-config          Show configuration details & exit
982    --dump-cache           Show cache & exit
983               
984    --cache-file <file>    Use the specified cache file
985    --output <file>        Write XML into the specified file
986    --channels_file <file> Read channel subscriptions from file
987
988    --region <n>           Grab data for region code <n>
989    --days <n>             Grab <n> days of data (today being day 1)
990    --offset <n>           Skip the first <n> days
991
992    --stats <n>            Print stats every n secs (0=off)
993    --debug                Print lots of debugging output
994
995    --warper               Use webwarper.net anonymizer
996
997    --test                 Don't write any output or update cache
998    --ignore-cache         Neither read nor update cache
999    --nowrite-cache        Read but don't update cache
1000    --rebuild-cache        Destroy and rebuild cache
1001};
1002  exit 0;
1003}
Note: See TracBrowser for help on using the browser.