#!/usr/bin/env perl # zap2xml (c) - for personal use only! # not for redistribution of any kind, or conversion to other languages, # not GPL. not for github, thank you. BEGIN { $SIG{__DIE__} = sub { return if $^S; my $msg = join(" ", @_); print STDERR "$msg"; if ($msg =~ /can't locate/i) { print "\nSee homepage for tips on installing missing modules (example: \"perl -MCPAN -e shell\")\n"; if ($^O eq 'MSWin32') { print "Use \"ppm install\" on windows\n"; } } if ($^O eq 'MSWin32') { if ($msg =~ /uri.pm/i && $msg =~ /temp/i) { print "\nIf your scanner deleted the perl URI.pm file see the homepage for tips\n"; if ($msg =~ /(\ .\:.+?par-.+?\\)/) { print "(Delete the $1 folder and retry)\n"; } } sleep(5); } exit 1; }} use Compress::Zlib; use Encode; use File::Basename; use File::Copy; use Getopt::Std; use HTTP::Cookies; use URI; use URI::Escape; use LWP::UserAgent; use LWP::ConnCache; use POSIX; use Time::Local; use Time::Piece; use JSON; no warnings 'utf8'; STDOUT->autoflush(1); STDERR->autoflush(1); $VERSION = "2018-12-01"; print "zap2xml ($VERSION)\nCommand line: $0 " . join(" ",@ARGV) . "\n"; %options=(); getopts("?aA:bB:c:C:d:DeE:Fgi:IjJ:l:Lm:Mn:N:o:Op:P:qRr:s:S:t:Tu:UwWxY:zZ:89",\%options); $homeDir = $ENV{HOME}; $homeDir = $ENV{USERPROFILE} if !defined($homeDir); $homeDir = '.' if !defined($homeDir); $confFile = $homeDir . '/.zap2xmlrc'; # Defaults $start = 0; $days = 7; $ncdays = 0; $ncsdays = 0; $ncmday = -1; $retries = 3; $outFile = 'xmltv.xml'; $outFile = 'xtvd.xml' if defined $options{x}; $cacheDir = 'cache'; $lang = 'en'; $userEmail = ''; $password = ''; $proxy; $postalcode; $country; $lineupId; $device; $sleeptime = 0; $allChan = 0; $shiftMinutes = 0; $outputXTVD = 0; $lineuptype; $lineupname; $lineuplocation; $zapToken; $zapPref='-'; %zapFavorites=(); %sidCache=(); $sTBA = "\\bTBA\\b|To Be Announced"; %tvgfavs=(); &HELP_MESSAGE() if defined $options{'?'}; $confFile = $options{C} if defined $options{C}; # read config file if (open (CONF, $confFile)) { &pout("Reading config file: $confFile\n"); while () { s/#.*//; # comments if (/^\s*$/i) { } elsif (/^\s*start\s*=\s*(\d+)/i) { $start = $1; } elsif (/^\s*days\s*=\s*(\d+)/i) { $days = $1; } elsif (/^\s*ncdays\s*=\s*(\d+)/i) { $ncdays = $1; } elsif (/^\s*ncsdays\s*=\s*(\d+)/i) { $ncsdays = $1; } elsif (/^\s*ncmday\s*=\s*(\d+)/i) { $ncmday = $1; } elsif (/^\s*retries\s*=\s*(\d+)/i) { $retries = $1; } elsif (/^\s*user[\w\s]*=\s*(.+)/i) { $userEmail = &rtrim($1); } elsif (/^\s*pass[\w\s]*=\s*(.+)/i) { $password = &rtrim($1); } elsif (/^\s*cache\s*=\s*(.+)/i) { $cacheDir = &rtrim($1); } elsif (/^\s*icon\s*=\s*(.+)/i) { $iconDir = &rtrim($1); } elsif (/^\s*trailer\s*=\s*(.+)/i) { $trailerDir = &rtrim($1); } elsif (/^\s*lang\s*=\s*(.+)/i) { $lang = &rtrim($1); } elsif (/^\s*outfile\s*=\s*(.+)/i) { $outFile = &rtrim($1); } elsif (/^\s*proxy\s*=\s*(.+)/i) { $proxy = &rtrim($1); } elsif (/^\s*outformat\s*=\s*(.+)/i) { $outputXTVD = 1 if $1 =~ /xtvd/i; } elsif (/^\s*lineupid\s*=\s*(.+)/i) { $lineupId = &rtrim($1); } elsif (/^\s*lineupname\s*=\s*(.+)/i) { $lineupname = &rtrim($1); } elsif (/^\s*lineuptype\s*=\s*(.+)/i) { $lineuptype = &rtrim($1); } elsif (/^\s*lineuplocation\s*=\s*(.+)/i) { $lineuplocation = &rtrim($1); } elsif (/^\s*postalcode\s*=\s*(.+)/i) { $postalcode = &rtrim($1); } else { die "Oddline in config file \"$confFile\".\n\t$_"; } } close (CONF); } &HELP_MESSAGE() if !(%options) && $userEmail eq ''; $cacheDir = $options{c} if defined $options{c}; $days = $options{d} if defined $options{d}; $ncdays = $options{n} if defined $options{n}; $ncsdays = $options{N} if defined $options{N}; $ncmday = $options{B} if defined $options{B}; $start = $options{s} if defined $options{s}; $retries = $options{r} if defined $options{r}; $iconDir = $options{i} if defined $options{i}; $trailerDir = $options{t} if defined $options{t}; $lang = $options{l} if defined $options{l}; $outFile = $options{o} if defined $options{o}; $password = $options{p} if defined $options{p}; $userEmail = $options{u} if defined $options{u}; $proxy = $options{P} if defined $options{P}; $zlineupId = $options{Y} if defined $options{Y}; $zipcode = $options{Z} if defined $options{Z}; $includeXMLTV = $options{J} if defined $options{J} && -e $options{J}; $outputXTVD = 1 if defined $options{x}; $allChan = 1 if defined($options{a}); $allChan = 1 if defined($zipcode) && defined($zlineupId); $sleeptime = $options{S} if defined $options{S}; $shiftMinutes = $options{m} if defined $options{m}; $ncdays = $days - $ncdays; # make relative to the end $urlRoot = 'https://tvlistings.zap2it.com/'; $urlAssets = 'https://zap2it.tmsimg.com/assets/'; $tvgurlRoot = 'http://mobilelistings.tvguide.com/'; $tvgMapiRoot = 'http://mapi.tvguide.com/'; $tvgurl = 'https://www.tvguide.com/'; $tvgspritesurl = 'http://static.tvgcdn.net/sprites/'; $retries = 20 if $retries > 20; # Too many my %programs = (); my $cp; my %stations = (); my $cs; my $rcs; my %schedule = (); my $sch; my %logos = (); my $coNum = 0; my $tb = 0; my $treq = 0; my $tsocks = (); my $expired = 0; my $ua; my $tba = 0; my $exp = 0; my @fh = (); my $XTVD_startTime; my $XTVD_endTime; if (! -d $cacheDir) { mkdir($cacheDir) or die "Can't mkdir: $!\n"; } else { opendir (DIR, "$cacheDir/"); @cacheFiles = grep(/\.html|\.js/,readdir(DIR)); closedir (DIR); foreach $cacheFile (@cacheFiles) { $fn = "$cacheDir/$cacheFile"; $atime = (stat($fn))[8]; if ($atime + ( ($days + 2) * 86400) < time) { &pout("Deleting old cached file: $fn\n"); &unf($fn); } } } my $s1 = time(); if (defined($options{z})) { &login() if !defined($options{a}); # get favorites &parseTVGIcons() if defined($iconDir); $gridHours = 3; $maxCount = $days * (24 / $gridHours); $offset = $start * 3600 * 24 * 1000; $ms = &hourToMillis() + $offset; for ($count=0; $count < $maxCount; $count++) { $curday = int($count / (24/$gridHours)) + 1; if ($count == 0) { $XTVD_startTime = $ms; } elsif ($count == $maxCount - 1) { $XTVD_endTime = $ms + ($gridHours * 3600000) - 1; } $fn = "$cacheDir/$ms\.js\.gz"; if (! -e $fn || $curday > $ncdays || $curday <= $ncsdays || $curday == $ncmday) { &login() if !defined($zlineupId); my $duration = $gridHours * 60; my $tvgstart = substr($ms, 0, -3); $rs = &getURL($tvgurlRoot . "Listingsweb/ws/rest/schedules/$zlineupId/start/$tvgstart/duration/$duration", 1); last if ($rs eq ''); $rc = Encode::encode('utf8', $rs); &wbf($fn, Compress::Zlib::memGzip($rc)); } &pout("[" . ($count+1) . "/" . "$maxCount] Parsing: $fn\n"); &parseTVGGrid($fn); if (defined($options{T}) && $tba) { &pout("Deleting: $fn (contains \"$sTBA\")\n"); &unf($fn); } if ($exp) { &pout("Deleting: $fn (expired)\n"); &unf($fn); } $exp = 0; $tba = 0; $ms += ($gridHours * 3600 * 1000); } } else { &login() if !defined($options{a}); # get favorites $gridHours = 3; $maxCount = $days * (24 / $gridHours); $offset = $start * 3600 * 24 * 1000; $ms = &hourToMillis() + $offset; for ($count=0; $count < $maxCount; $count++) { $curday = int($count / (24/$gridHours)) + 1; if ($count == 0) { $XTVD_startTime = $ms; } elsif ($count == $maxCount - 1) { $XTVD_endTime = $ms + ($gridHours * 3600000) - 1; } $fn = "$cacheDir/$ms\.js\.gz"; if (! -e $fn || $curday > $ncdays || $curday <= $ncsdays || $curday == $ncmday) { my $zstart = substr($ms, 0, -3); $params = "?time=$zstart×pan=$gridHours&pref=$zapPref&"; $params .= &getZapGParams(); $params .= '&TMSID=&AffiliateID=gapzap&FromPage=TV%20Grid'; $params .= '&ActivityID=1&OVDID=&isOverride=true'; $rs = &getURL($urlRoot . "api/grid$params",1); last if ($rs eq ''); $rc = Encode::encode('utf8', $rs); &wbf($fn, Compress::Zlib::memGzip($rc)); } &pout("[" . ($count+1) . "/" . "$maxCount] Parsing: $fn\n"); &parseJSON($fn); if (defined($options{T}) && $tba) { &pout("Deleting: $fn (contains \"$sTBA\")\n"); &unf($fn); } if ($exp) { &pout("Deleting: $fn (expired)\n"); &unf($fn); } $exp = 0; $tba = 0; $ms += ($gridHours * 3600 * 1000); } } my $s2 = time(); my $tsockt = scalar(keys %tsocks); &pout("Downloaded " . &pl($tb, "byte") . " in " . &pl($treq, "http request") . " using " . &pl($tsockt > 0 ? $tsockt : $treq, "socket") . ".\n") if $tb > 0; &pout("Expired programs: $expired\n") if $expired > 0; &pout("Writing XML file: $outFile\n"); open($FH, ">$outFile"); my $enc = 'ISO-8859-1'; if (defined($options{U})) { $enc = 'UTF-8'; } if ($outputXTVD) { &printHeaderXTVD($FH, $enc); &printStationsXTVD($FH); &printLineupsXTVD($FH); &printSchedulesXTVD($FH); &printProgramsXTVD($FH); &printGenresXTVD($FH); &printFooterXTVD($FH); } else { &printHeader($FH, $enc); &printChannels($FH); if (defined($includeXMLTV)) { &pout("Reading XML file: $includeXMLTV\n"); &incXML("; } else { sleep(3) if ($^O eq 'MSWin32'); } exit 0; sub incXML { my ($st, $en, $FH) = @_; open($XF, "<$includeXMLTV"); while (<$XF>) { if (/^\s*$st/../^\s*$en/) { print $FH $_ unless /^\s*$en/ } } close($XF); } sub pl { my($i, $s) = @_; my $r = "$i $s"; return $i == 1 ? $r : $r . "s"; } sub pout { print @_ if !defined $options{q}; } sub perr { warn @_; } sub rtrim { my $s = shift; $s =~ s/\s+$//; return $s; } sub trim { my $s = shift; $s =~ s/^\s+//; $s =~ s/\s+$//; return $s; } sub trim2 { my $s = &trim(shift); $s =~ s/[^\w\s\(\)\,]//gsi; $s =~ s/\s+/ /gsi; return $s; } sub _rtrim3 { my $s = shift; return substr($s, 0, length($s)-3); } sub convTime { my $t = shift; $t += $shiftMinutes * 60 * 1000; return strftime "%Y%m%d%H%M%S", localtime(&_rtrim3($t)); } sub convTimeXTVD { my $t = shift; $t += $shiftMinutes * 60 * 1000; return strftime "%Y-%m-%dT%H:%M:%SZ", gmtime(&_rtrim3($t)); } sub convOAD { return strftime "%Y%m%d", gmtime(&_rtrim3(shift)); } sub convOADXTVD { return strftime "%Y-%m-%d", gmtime(&_rtrim3(shift)); } sub convDurationXTVD { my $duration = shift; my $hour = int($duration / 3600000); my $minutes = int(($duration - ($hour * 3600000)) / 60000); return sprintf("PT%02dH%02dM", $hour, $minutes); } sub appendAsterisk { my ($title, $station, $s) = @_; if (defined($options{A})) { if (($options{A} =~ "new" && defined($schedule{$station}{$s}{new})) || ($options{A} =~ "live" && defined($schedule{$station}{$s}{live}))) { $title .= " *"; } } return $title; } sub stationToChannel { my $s = shift; if (defined($options{z})) { return sprintf("I%s.%s.tvguide.com", $stations{$s}{number},$stations{$s}{stnNum}); } elsif (defined($options{O})) { return sprintf("C%s%s.zap2it.com",$stations{$s}{number},lc($stations{$s}{name})); } elsif (defined($options{9})) { return sprintf("I%s.labs.zap2it.com",$stations{$s}{stnNum}); } return sprintf("I%s.%s.zap2it.com", $stations{$s}{number},$stations{$s}{stnNum}); } sub sortChan { if (defined($stations{$a}{order}) && defined($stations{$b}{order})) { my $c = $stations{$a}{order} <=> $stations{$b}{order}; if ($c == 0) { return $stations{$a}{stnNum} <=> $stations{$b}{stnNum} } else { return $c }; } else { return $stations{$a}{name} cmp $stations{$b}{name}; } } sub enc { my $t = shift; if (!defined($options{U})) {$t = Encode::decode('utf8', $t);} if (!defined($options{E}) || $options{E} =~ /amp/) {$t =~ s/&/&/gs;} if (!defined($options{E}) || $options{E} =~ /quot/) {$t =~ s/"/"/gs;} if (!defined($options{E}) || $options{E} =~ /apos/) {$t =~ s/'/'/gs;} if (!defined($options{E}) || $options{E} =~ /lt/) {$t =~ s//>/gs;} if (defined($options{e})) { $t =~ s/([^\x20-\x7F])/'&#' . ord($1) . ';'/gse; } return $t; } sub printHeader { my ($FH, $enc) = @_; print $FH "\n"; print $FH "\n\n"; if (defined($options{z})) { print $FH "\n"; } sub printFooter { my $FH = shift; print $FH "\n"; } sub printChannels { my $FH = shift; for my $key ( sort sortChan keys %stations ) { $sname = &enc($stations{$key}{name}); $fname = &enc($stations{$key}{fullname}); $snum = $stations{$key}{number}; print $FH "\t\n"; print $FH "\t\t" . $sname . "\n" if defined($options{F}) && defined($sname); if (defined($snum)) { ©Logo($key); print $FH "\t\t" . $snum . " " . $sname . "\n" if ($snum ne ''); print $FH "\t\t" . $snum . "\n" if ($snum ne ''); } print $FH "\t\t" . $sname . "\n" if !defined($options{F}) && defined($sname); print $FH "\t\t" . $fname . "\n" if (defined($fname)); if (defined($stations{$key}{logoURL})) { print $FH "\t\t\n"; } print $FH "\t\n"; } } sub printProgrammes { my $FH = shift; for my $station ( sort sortChan keys %stations ) { my $i = 0; my @keyArray = sort { $schedule{$station}{$a}{time} cmp $schedule{$station}{$b}{time} } keys %{$schedule{$station}}; foreach $s (@keyArray) { if ($#keyArray <= $i && !defined($schedule{$station}{$s}{endtime})) { delete $schedule{$station}{$s}; next; } my $p = $schedule{$station}{$s}{program}; my $startTime = &convTime($schedule{$station}{$s}{time}); my $startTZ = &timezone($schedule{$station}{$s}{time}); my $endTime; if (defined($schedule{$station}{$s}{endtime})) { $endTime = $schedule{$station}{$s}{endtime}; } else { $endTime = $schedule{$station}{$keyArray[$i+1]}{time}; } my $stopTime = &convTime($endTime); my $stopTZ = &timezone($endTime); print $FH "\t\n"; if (defined($programs{$p}{title})) { my $title = &enc($programs{$p}{title}); $title = &appendAsterisk($title, $station, $s); print $FH "\t\t" . $title . "\n"; } if (defined($programs{$p}{episode}) || (defined($options{M}) && defined($programs{$p}{movie_year}))) { print $FH "\t\t"; if (defined($programs{$p}{episode})) { print $FH &enc($programs{$p}{episode}); } else { print $FH "Movie (" . $programs{$p}{movie_year} . ")"; } print $FH "\n" } print $FH "\t\t" . &enc($programs{$p}{description}) . "\n" if defined($programs{$p}{description}); if (defined($programs{$p}{actor}) || defined($programs{$p}{director}) || defined($programs{$p}{writer}) || defined($programs{$p}{producer}) || defined($programs{$p}{preseter}) ) { print $FH "\t\t\n"; &printCredits($FH, $p, "director"); foreach my $g (sort { $programs{$p}{actor}{$a} <=> $programs{$p}{actor}{$b} } keys %{$programs{$p}{actor}} ) { print $FH "\t\t\t" . &enc($g) . "\n"; } &printCredits($FH, $p, "writer"); &printCredits($FH, $p, "producer"); &printCredits($FH, $p, "presenter"); print $FH "\t\t\n"; } my $date; if (defined($programs{$p}{movie_year})) { $date = $programs{$p}{movie_year}; } elsif (defined($programs{$p}{originalAirDate}) && $p =~ /^EP|^\d/) { $date = &convOAD($programs{$p}{originalAirDate}); } print $FH "\t\t$date\n" if defined($date); if (defined($programs{$p}{genres})) { foreach my $g (sort { $programs{$p}{genres}{$a} <=> $programs{$p}{genres}{$b} or $a cmp $b } keys %{$programs{$p}{genres}} ) { print $FH "\t\t" . &enc(ucfirst($g)) . "\n"; } } print $FH "\t\t" . $programs{$p}{duration} . "\n" if defined($programs{$p}{duration}); if (defined($programs{$p}{imageUrl})) { print $FH "\t\t\n"; } if (defined($programs{$p}{url})) { print $FH "\t\t" . &enc($programs{$p}{url}) . "\n"; } my $xs; my $xe; if (defined($programs{$p}{seasonNum}) && defined($programs{$p}{episodeNum})) { my $s = $programs{$p}{seasonNum}; my $sf = sprintf("S%0*d", &max(2, length($s)), $s); my $e = $programs{$p}{episodeNum}; my $ef = sprintf("E%0*d", &max(2, length($e)), $e); $xs = int($s) - 1; $xe = int($e) - 1; if ($s > 0 || $e > 0) { print $FH "\t\t" . $sf . $ef . "\n"; } } $dd_prog_id = $p; if ( $dd_prog_id =~ /^(..\d{8})(\d{4})/ ) { $dd_prog_id = sprintf("%s.%s",$1,$2); print $FH "\t\t" . $dd_prog_id . "\n"; } if (defined($xs) && defined($xe) && $xs >= 0 && $xe >= 0) { print $FH "\t\t" . $xs . "." . $xe . ".\n"; } if (defined($schedule{$station}{$s}{quality})) { print $FH "\t\t\n"; } my $new = defined($schedule{$station}{$s}{new}); my $live = defined($schedule{$station}{$s}{live}); my $cc = defined($schedule{$station}{$s}{cc}); if (! $new && ! $live && $p =~ /^EP|^SH|^\d/) { print $FH "\t\t\n"; } if (defined($schedule{$station}{$s}{premiere})) { print $FH "\t\t" . $schedule{$station}{$s}{premiere} . "\n"; } if (defined($schedule{$station}{$s}{finale})) { print $FH "\t\t" . $schedule{$station}{$s}{finale} . "\n"; } print $FH "\t\t\n" if $new; # not part of XMLTV format yet? print $FH "\t\t\n" if (defined($options{L}) && $live); print $FH "\t\t\n" if $cc; if (defined($programs{$p}{rating})) { print $FH "\t\t\n\t\t\t" . $programs{$p}{rating} . "\n\t\t\n" } if (defined($programs{$p}{starRating})) { print $FH "\t\t\n\t\t\t" . $programs{$p}{starRating} . "/4\n\t\t\n"; } print $FH "\t\n"; $i++; } } } sub printHeaderXTVD { my ($FH, $enc) = @_; print $FH "\n"; print $FH "\n"; } sub printCredits { my ($FH, $p, $s) = @_; foreach my $g (sort { $programs{$p}{$s}{$a} <=> $programs{$p}{$s}{$b} } keys %{$programs{$p}{$s}} ) { print $FH "\t\t\t<$s>" . &enc($g) . "\n"; } } sub printFooterXTVD { my $FH = shift; print $FH "\n"; } sub printStationsXTVD { my $FH = shift; print $FH "\n"; for my $key ( sort sortChan keys %stations ) { print $FH "\t\n"; if (defined($stations{$key}{number})) { $sname = &enc($stations{$key}{name}); print $FH "\t\t" . $sname . "\n"; print $FH "\t\t" . $sname . "\n"; print $FH "\t\t" . $stations{$key}{number} . "\n"; ©Logo($key); } print $FH "\t\n"; } print $FH "\n"; } sub printLineupsXTVD { my $FH = shift; print $FH "\n"; print $FH "\t\n"; for my $key ( sort sortChan keys %stations ) { if (defined($stations{$key}{number})) { print $FH "\t\n"; } } print $FH "\t\n"; print $FH "\n"; } sub printSchedulesXTVD { my $FH = shift; print $FH "\n"; for my $station ( sort sortChan keys %stations ) { my $i = 0; my @keyArray = sort { $schedule{$station}{$a}{time} cmp $schedule{$station}{$b}{time} } keys %{$schedule{$station}}; foreach $s (@keyArray) { if ($#keyArray <= $i) { delete $schedule{$station}{$s}; next; } my $p = $schedule{$station}{$s}{program}; my $startTime = &convTimeXTVD($schedule{$station}{$s}{time}); my $stopTime = &convTimeXTVD($schedule{$station}{$keyArray[$i+1]}{time}); my $duration = &convDurationXTVD($schedule{$station}{$keyArray[$i+1]}{time} - $schedule{$station}{$s}{time}); print $FH "\t\n"; $i++; } } print $FH "\n"; } sub printProgramsXTVD { my $FH = shift; print $FH "\n"; foreach $p (keys %programs) { print $FH "\t\n"; print $FH "\t\t" . &enc($programs{$p}{title}) . "\n" if defined($programs{$p}{title}); print $FH "\t\t" . &enc($programs{$p}{episode}) . "\n" if defined($programs{$p}{episode}); print $FH "\t\t" . &enc($programs{$p}{description}) . "\n" if defined($programs{$p}{description}); if (defined($programs{$p}{movie_year})) { print $FH "\t\t" . $programs{$p}{movie_year} . "\n"; } else { #Guess my $showType = "Series"; if ($programs{$p}{title} =~ /Paid Programming/i) { $showType = "Paid Programming"; } print $FH "\t\t$showType\n"; print $FH "\t\tEP" . substr($p,2,8) . "\n"; print $FH "\t\t" . &convOADXTVD($programs{$p}{originalAirDate}) . "\n" if defined($programs{$p}{originalAirDate}); } print $FH "\t\n"; } print $FH "\n"; } sub printGenresXTVD { my $FH = shift; print $FH "\n"; foreach $p (keys %programs) { if (defined($programs{$p}{genres}) && $programs{$p}{genres}{movie} != 1) { print $FH "\t\n"; foreach my $g (keys %{$programs{$p}{genres}}) { print $FH "\t\t\n"; print $FH "\t\t\t" . &enc(ucfirst($g)) . "\n"; print $FH "\t\t\t0\n"; print $FH "\t\t\n"; } print $FH "\t\n"; } } print $FH "\n"; } sub loginTVG { my $r = &ua_get($tvgurl . 'signin/'); if ($r->is_success) { my $str = $r->decoded_content; if ($str =~ / $token, email => $userEmail, password => $password, }, 'X-Requested-With' => 'XMLHttpRequest' ); $dc = Encode::encode('utf8', $r->decoded_content( raise_error => 1 )); if ($dc =~ /success/) { $ua->cookie_jar->scan(sub { if ($_[1] eq "ServiceID") { $zlineupId = $_[2]; }; }); if (!defined($options{a})) { my $r = &ua_get($tvgurl . "user/favorites/?provider=$zlineupId",'X-Requested-With' => 'XMLHttpRequest'); $dc = Encode::encode('utf8', $r->decoded_content( raise_error => 1 )); if ($dc =~ /\{\"code\":200/) { &parseTVGFavs($dc); } } return $dc; } else { &pout("[Attempt $rc] " . $r->status_line . ":" . $dc . "\n"); sleep ($sleeptime + 1); } } die "Failed to login within $retries retries.\n"; } } else { die "Login token not found\n"; } } } sub loginZAP { my $rc = 0; while ($rc++ < $retries) { my $r = &ua_post($urlRoot . 'api/user/login', { emailid => $userEmail, password => $password, usertype => '0', facebookuser =>'false', } ); $dc = Encode::encode('utf8', $r->decoded_content( raise_error => 1 )); if ($r->is_success) { my $t = decode_json($dc); $zapToken = $t->{'token'}; $zapPref = ''; $zapPref .= "m" if ($t->{isMusic}); $zapPref .= "p" if ($t->{isPPV}); $zapPref .= "h" if ($t->{isHD}); if ($zapPref eq '') { $zapPref = '-' } else { $zapPref = join(",", split(//, $zapPref)); } my $prs = $t->{'properties'}; $postalcode = $prs->{2002}; $country = $prs->{2003}; ($lineupId, $device) = split(/:/, $prs->{2004}); if (!defined($options{a})) { my $r = &ua_post($urlRoot . "api/user/favorites", { token => $zapToken }, 'X-Requested-With' => 'XMLHttpRequest'); $dc = Encode::encode('utf8', $r->decoded_content( raise_error => 1 )); if ($r->is_success) { &parseZFavs($dc); } else { &perr("FF" . $r->status_line . ": $dc\n"); } } return $dc; } else { &pout("[Attempt $rc] " . $dc . "\n"); sleep ($sleeptime + 1); } } die "Failed to login within $retries retries.\n"; } sub getZapGParams { my %hash = &getZapParams(); $hash{country} = delete $hash{countryCode}; return join("&", map { "$_=$hash{$_}" } keys %hash); } sub getZapPParams { my %hash = &getZapParams(); delete $hash{lineupId}; return %hash; } sub getZapParams { my %phash = (); if (defined($zlineupId) || defined($zipcode)) { $postalcode = $zipcode; $country = "USA"; $country = "CAN" if ($zipcode =~ /[A-z]/); if ($zlineupId =~ /:/) { ($lineupId, $device) = split(/:/, $zlineupId); } else { $lineupId = $zlineupId; $device = "-"; } $phash{postalCode} = $postalcode; } else { $phash{token} = &getZToken(); } $phash{lineupId} = "$country-$lineupId-DEFAULT"; $phash{postalCode} = $postalcode; $phash{countryCode} = $country; $phash{headendId} = $lineupId; $phash{device} = $device; $phash{aid} = 'gapzap'; return %phash; } sub login { if (!defined($userEmail) || $userEmail eq '' || !defined($password) || $password eq '') { if (!defined($zlineupId)) { die "Unable to login: Unspecified username or password.\n" } } if (!defined($ua)) { $ua = LWP::UserAgent->new(ssl_opts => { verify_hostname => 0 }); # WIN $ua->conn_cache(LWP::ConnCache->new( total_capacity => undef )); $ua->cookie_jar(HTTP::Cookies->new); $ua->proxy(['http', 'https'], $proxy) if defined($proxy); $ua->agent('Mozilla/4.0'); $ua->default_headers->push_header('Accept-Encoding' => 'gzip, deflate'); } if ($userEmail ne '' && $password ne '') { &pout("Logging in as \"$userEmail\" (" . localtime . ")\n"); if (defined($options{z})) { &loginTVG(); } else { &loginZAP(); } } else { &pout("Connecting with lineupId \"$zlineupId\" (" . localtime . ")\n"); } } sub ua_stats { my ($s, @p) = @_; my $r; if ($s eq 'POST') { $r = $ua->post(@p); } else { $r = $ua->get(@p); } my $cc = $ua->conn_cache; if (defined($cc)) { my @cxns = $cc->get_connections(); foreach (@cxns) { $tsocks{$_} = 1; } } $treq++; $tb += length($r->content); return $r; } sub ua_get { return &ua_stats('GET', @_); } sub ua_post { return &ua_stats('POST', @_); } sub getURL { my $url = shift; my $er = shift; &login() if !defined($ua); my $rc = 0; while ($rc++ < $retries) { &pout("[$treq] Getting: $url\n"); sleep $sleeptime; # do these rapid requests flood servers? my $r = &ua_get($url); my $cl = length($r->content); my $dc = $r->decoded_content( raise_error => 1 ); if ($r->is_success && $cl) { return $dc; } elsif ($r->code == 500 && $dc =~ /Could not load details/) { &pout("$dc\n"); return ""; } else { &perr("[Attempt $rc] $cl:" . $r->status_line . "\n"); &perr($r->content . "\n"); sleep ($sleeptime + 2); } } &perr("Failed to download within $retries retries.\n"); if ($er) { &perr("Server out of data? Temporary server error? Normal exit anyway.\n"); return ""; }; die; } sub wbf { my($f, $s) = @_; open(FO, ">$f") or die "Failed to open '$f': $!"; binmode(FO); print FO $s; close(FO); } sub unf { my $f = shift; unlink($f) or &perr("Failed to delete '$f': $!"); } sub copyLogo { my $key = shift; my $cid = $key; if (!defined($logos{$cid}{logo})) { $cid = substr($key, rindex($key, ".")+1); } if (defined($iconDir) && defined($logos{$cid}{logo})) { my $num = $stations{$key}{number}; my $src = "$iconDir/" . $logos{$cid}{logo} . $logos{$cid}{logoExt}; my $dest1 = "$iconDir/$num" . $logos{$cid}{logoExt}; my $dest2 = "$iconDir/$num " . $stations{$key}{name} . $logos{$cid}{logoExt}; my $dest3 = "$iconDir/$num\_" . $stations{$key}{name} . $logos{$cid}{logoExt}; copy($src, $dest1); copy($src, $dest2); copy($src, $dest3); } } sub handleLogo { my $url = shift; if (! -d $iconDir) { mkdir($iconDir) or die "Can't mkdir: $!\n"; } my $n; my $s; ($n,$_,$s) = fileparse($url, qr"\..*"); $stations{$cs}{logoURL} = $url; $logos{$cs}{logo} = $n; $logos{$cs}{logoExt} = $s; my $f = $iconDir . "/" . $n . $s; if (! -e $f) { &wbf($f, &getURL($url,0)); } } sub setOriginalAirDate { if (substr($cp,10,4) ne '0000') { if (!defined($programs{$cp}{originalAirDate}) || ($schedule{$cs}{$sch}{time} < $programs{$cp}{originalAirDate})) { $programs{$cp}{originalAirDate} = $schedule{$cs}{$sch}{time}; } } } sub getZToken { &login() if (!defined($zapToken)); return $zapToken; } sub parseZFavs { my $buffer = shift; my $t = decode_json($buffer); if (defined($t->{'channels'})) { my $m = $t->{'channels'}; foreach my $f (@{$m}) { if ($options{R}) { my $r = &ua_post($urlRoot . "api/user/ChannelAddtofav", { token => $zapToken, prgsvcid => $f, addToFav => "false" }, 'X-Requested-With' => 'XMLHttpRequest'); if ($r->is_success) { &pout("Removed favorite $f\n"); } else { &perr("RF" . $r->status_line . "\n"); } } else { $zapFavorites{$f} = 1; } } if ($options{R}) { &pout("Removed favorites, exiting\n"); exit; }; &pout("Lineup favorites: " . (keys %zapFavorites) . "\n"); } } sub parseTVGFavs { my $buffer = shift; my $t = decode_json($buffer); if (defined($t->{'message'})) { my $m = $t->{'message'}; foreach my $f (@{$m}) { my $source = $f->{"source"}; my $channel = $f->{"channel"}; $tvgfavs{"$channel.$source"} = 1; } &pout("Lineup $zlineupId favorites: " . (keys %tvgfavs) . "\n"); } } sub parseTVGIcons { require GD; $rc = Encode::encode('utf8', &getURL($tvgspritesurl . "$zlineupId\.css",0) ); if ($rc =~ /background-image:.+?url\((.+?)\)/) { my $url = $tvgspritesurl . $1; if (! -d $iconDir) { mkdir($iconDir) or die "Can't mkdir: $!\n"; } ($n,$_,$s) = fileparse($url, qr"\..*"); $f = $iconDir . "/sprites-" . $n . $s; &wbf($f, &getURL($url,0)); GD::Image->trueColor(1); $im = new GD::Image->new($f); my $iconw = 30; my $iconh = 20; while ($rc =~ /listings-channel-icon-(.+?)\{.+?position:.*?\-(\d+).+?(\d+).*?\}/isg) { my $cid = $1; my $iconx = $2; my $icony = $3; my $icon = new GD::Image($iconw,$iconh); $icon->alphaBlending(0); $icon->saveAlpha(1); $icon->copy($im, 0, 0, $iconx, $icony, $iconw, $iconh); $logos{$cid}{logo} = "sprite-" . $cid; $logos{$cid}{logoExt} = $s; my $ifn = $iconDir . "/" . $logos{$cid}{logo} . $logos{$cid}{logoExt}; &wbf($ifn, $icon->png); } } } sub parseTVGD { my $gz = gzopen(shift, "rb"); my $buffer; $buffer .= $b while $gz->gzread($b, 65535) > 0; $gz->gzclose(); my $t = decode_json($buffer); if (defined($t->{'program'})) { my $prog = $t->{'program'}; if (defined($prog->{'release_year'})) { $programs{$cp}{movie_year} = $prog->{'release_year'}; } if (defined($prog->{'rating'}) && !defined($programs{$cp}{rating})) { $programs{$cp}{rating} = $prog->{'rating'} if $prog->{'rating'} ne 'NR'; } } if (defined($t->{'tvobject'})) { my $tvo = $t->{'tvobject'}; if (defined($tvo->{'photos'})) { my $photos = $tvo->{'photos'}; my %phash; foreach $ph (@{$photos}) { my $w = $ph->{'width'} * $ph->{'height'}; my $u = $ph->{'url'}; $phash{$w} = $u; } my $big = (sort {$b <=> $a} keys %phash)[0]; $programs{$cp}{imageUrl} = $phash{$big}; } } } sub parseTVGGrid { my $gz = gzopen(shift, "rb"); my $buffer; $buffer .= $b while $gz->gzread($b, 65535) > 0; $gz->gzclose(); my $t = decode_json($buffer); foreach my $e (@{$t}) { my $cjs = $e->{'Channel'}; my $src = $cjs->{'SourceId'}; my $num = $cjs->{'Number'}; $cs = "$num.$src"; if (%tvgfavs) { next if (!$tvgfavs{$cs}); } if (!defined($stations{$cs}{stnNum})) { $stations{$cs}{stnNum} = $src; $stations{$cs}{number} = $num; $stations{$cs}{name} = $cjs->{'Name'}; if (defined($cjs->{'FullName'}) && $cjs->{'FullName'} ne $cjs->{'Name'}) { if ($cjs->{'FullName'} ne '') { $stations{$cs}{fullname} = $cjs->{'FullName'}; } } if (!defined($stations{$cs}{order})) { if (defined($options{b})) { $stations{$cs}{order} = $coNum++; } else { $stations{$cs}{order} = $stations{$cs}{number}; } } } my $cps = $e->{'ProgramSchedules'}; foreach my $pe (@{$cps}) { next if (!defined($pe->{'ProgramId'})); $cp = $pe->{'ProgramId'}; my $catid = $pe->{'CatId'}; if ($catid == 1) { $programs{$cp}{genres}{movie} = 1 } elsif ($catid == 2) { $programs{$cp}{genres}{sports} = 1 } elsif ($catid == 3) { $programs{$cp}{genres}{family} = 1 } elsif ($catid == 4) { $programs{$cp}{genres}{news} = 1 } # 5 - 10? # my $subcatid = $pe->{'SubCatId'}; my $ppid = $pe->{'ParentProgramId'}; if ((defined($ppid) && $ppid != 0) || (defined($options{j}) && $catid != 1)) { $programs{$cp}{genres}{series} = 99; } $programs{$cp}{title} = $pe->{'Title'}; $tba = 1 if $programs{$cp}{title} =~ /$sTBA/i; if (defined($pe->{'EpisodeTitle'}) && $pe->{'EpisodeTitle'} ne '') { $programs{$cp}{episode} = $pe->{'EpisodeTitle'}; $tba = 1 if $programs{$cp}{episode} =~ /$sTBA/i; } $programs{$cp}{description} = $pe->{'CopyText'} if defined($pe->{'CopyText'}) && $pe->{'CopyText'} ne ''; $programs{$cp}{rating} = $pe->{'Rating'} if defined($pe->{'Rating'}) && $pe->{'Rating'} ne ''; my $sch = $pe->{'StartTime'} * 1000; $schedule{$cs}{$sch}{time} = $sch; $schedule{$cs}{$sch}{endtime} = $pe->{'EndTime'} * 1000; $schedule{$cs}{$sch}{program} = $cp; $schedule{$cs}{$sch}{station} = $cs; my $airat = $pe->{'AiringAttrib'}; if ($airat & 1) { $schedule{$cs}{$sch}{live} = 1 } elsif ($airat & 4) { $schedule{$cs}{$sch}{new} = 1 } # other bits? my $tvo = $pe->{'TVObject'}; if (defined($tvo)) { if (defined($tvo->{'SeasonNumber'}) && $tvo->{'SeasonNumber'} != 0) { $programs{$cp}{seasonNum} = $tvo->{'SeasonNumber'}; if (defined($tvo->{'EpisodeNumber'}) && $tvo->{'EpisodeNumber'} != 0) { $programs{$cp}{episodeNum} = $tvo->{'EpisodeNumber'}; } } if (defined($tvo->{'EpisodeAirDate'})) { my $eaid = $tvo->{'EpisodeAirDate'}; # GMT @ 00:00:00 $eaid =~ tr/\-0-9//cd; $programs{$cp}{originalAirDate} = $eaid if ($eaid ne ''); } my $url; if (defined($tvo->{'EpisodeSEOUrl'}) && $tvo->{'EpisodeSEOUrl'} ne '') { $url = $tvo->{'EpisodeSEOUrl'}; } elsif(defined($tvo->{'SEOUrl'}) && $tvo->{'SEOUrl'} ne '') { $url = $tvo->{'SEOUrl'}; $url = "/movies$url" if ($catid == 1 && $url !~ /movies/); } $programs{$cp}{url} = substr($tvgurl, 0, -1) . $url if defined($url); } if (defined($options{I}) || (defined($options{D}) && $programs{$cp}{genres}{movie}) || (defined($options{W}) && $programs{$cp}{genres}{movie}) ) { &getDetails(\&parseTVGD, $cp, $tvgMapiRoot . "listings/details?program=$cp", ""); } } } } sub getDetails { my ($func, $cp, $url, $prefix) = @_; my $fn = "$cacheDir/$prefix$cp\.js\.gz"; if (! -e $fn) { my $rs = &getURL($url,1); if (length($rs)) { $rc = Encode::encode('utf8', $rs); &wbf($fn, Compress::Zlib::memGzip($rc)); } } if (-e $fn) { my $l = length($prefix) ? $prefix : "D"; &pout("[$l] Parsing: $cp\n"); $func->($fn); } else { &pout("Skipping: $cp\n"); } } sub parseJSON { my $gz = gzopen(shift, "rb"); my $buffer; $buffer .= $b while $gz->gzread($b, 65535) > 0; $gz->gzclose(); my $t = decode_json($buffer); my $sts = $t->{'channels'}; my %zapStarred=(); foreach $s (@$sts) { if (defined($s->{'channelId'})) { if (!$allChan && scalar(keys %zapFavorites)) { if ($zapFavorites{$s->{channelId}}) { if ($options{8}) { next if $zapStarred{$s->{channelId}}; $zapStarred{$s->{channelId}} = 1; } } else { next; } } # id (uniq) vs channelId, but id not nec consistent in cache $cs = $s->{channelNo} . "." . $s->{channelId}; $stations{$cs}{stnNum} = $s->{channelId}; $stations{$cs}{name} = $s->{'callSign'}; $stations{$cs}{number} = $s->{'channelNo'}; $stations{$cs}{number} =~ s/^0+//g; if (!defined($stations{$cs}{order})) { if (defined($options{b})) { $stations{$cs}{order} = $coNum++; } else { $stations{$cs}{order} = $stations{$cs}{number}; } } if ($s->{'thumbnail'} ne '') { my $url = $s->{'thumbnail'}; $url =~ s/\?.*//; # remove size if ($url !~ /^http/) { $url = "https:" . $url; } $stations{$cs}{logoURL} = $url; &handleLogo($url) if defined($iconDir); } my $events = $s->{'events'}; foreach $e (@$events) { my $program = $e->{'program'}; $cp = $program->{'id'}; $programs{$cp}{title} = $program->{'title'}; $tba = 1 if $programs{$cp}{title} =~ /$sTBA/i; $programs{$cp}{episode} = $program->{'episodeTitle'} if ($program->{'episodeTitle'} ne ''); $programs{$cp}{description} = $program->{'shortDesc'} if ($program->{'shortDesc'} ne ''); $programs{$cp}{duration} = $e->{duration} if ($e->{duration} > 0); $programs{$cp}{movie_year} = $program->{releaseYear} if ($program->{releaseYear} ne ''); $programs{$cp}{seasonNum} = $program->{season} if ($program->{'season'} ne ''); if ($program->{'episode'} ne '') { $programs{$cp}{episodeNum} = $program->{episode}; } if ($e->{'thumbnail'} ne '') { my $turl = $urlAssets; $turl .= $e->{'thumbnail'} . ".jpg"; $programs{$cp}{imageUrl} = $turl; } if ($program->{'seriesId'} ne '' && $program->{'tmsId'} ne '') { $programs{$cp}{url} = $urlRoot . "/overview.html?programSeriesId=" . $program->{seriesId} . "&tmsId=" . $program->{tmsId}; } $sch = str2time1($e->{'startTime'}) * 1000; $schedule{$cs}{$sch}{time} = $sch; $schedule{$cs}{$sch}{endTime} = str2time1($e->{'endTime'}) * 1000; $schedule{$cs}{$sch}{program} = $cp; $schedule{$cs}{$sch}{station} = $cs; if ($e->{'filter'}) { my $genres = $e->{'filter'}; my $i = 1; foreach $g (@{$genres}) { $g =~ s/filter-//i; ${$programs{$cp}{genres}}{lc($g)} = $i++; } } $programs{$cp}{rating} = $e->{rating} if ($e->{rating} ne ''); if ($e->{'tags'}) { my $tags = $e->{'tags'}; if (grep $_ eq 'CC', @{$tags}) { $schedule{$cs}{$sch}{cc} = 1 } } if ($e->{'flag'}) { my $flags = $e->{'flag'}; if (grep $_ eq 'New', @{$flags}) { $schedule{$cs}{$sch}{new} = 'New' &setOriginalAirDate(); } if (grep $_ eq 'Live', @{$flags}) { $schedule{$cs}{$sch}{live} = 'Live' &setOriginalAirDate(); # live to tape? } if (grep $_ eq 'Premiere', @{$flags}) { $schedule{$cs}{$sch}{premiere} = 'Premiere'; } if (grep $_ eq 'Finale', @{$flags}) { $schedule{$cs}{$sch}{finale} = 'Finale'; } } if ($options{D} && !$program->{isGeneric}) { &postJSONO($cp, $program->{seriesId}); } if (defined($options{j}) && $cp !~ /^MV/) { $programs{$cp}{genres}{series} = 99; } } } } return 0; } sub postJSONO { my ($cp, $sid) = @_; my $fn = "$cacheDir/O$cp\.js\.gz"; if (! -e $fn && defined($sidCache{$sid}) && -e $sidCache{$sid}) { copy($sidCache{$sid}, $fn); } if (! -e $fn) { my $url = $urlRoot . 'api/program/overviewDetails'; &pout("[$treq] Post $sid: $url\n"); sleep $sleeptime; # do these rapid requests flood servers? my %phash = &getZapPParams(); $phash{programSeriesID} = $sid; $phash{'clickstream[FromPage]'} = 'TV%20Grid'; my $r = &ua_post($url, \%phash, 'X-Requested-With' => 'XMLHttpRequest'); if ($r->is_success) { $dc = Encode::encode('utf8', $r->decoded_content( raise_error => 1 )); &wbf($fn, Compress::Zlib::memGzip($dc)); $sidCache{$sid} = $fn; } else { &perr($id . " :" . $r->status_line); } } if (-e $fn) { &pout("[D] Parsing: $cp\n"); my $gz = gzopen($fn, "rb"); my $buffer; $buffer .= $b while $gz->gzread($b, 65535) > 0; $gz->gzclose(); my $t = decode_json($buffer); if ($t->{seriesGenres} ne '') { my $i = 2; my %gh = %{$programs{$cp}{genres}}; if (keys %gh) { my @genArr = sort { $gh{$a} <=> $gh{$b} } keys %gh; my $max = $genArr[-1]; $i = $gh{$max} + 1; } foreach my $sg (split(/\|/, lc($t->{seriesGenres}))) { if (!${$programs{$cp}{genres}}{$sg}) { ${$programs{$cp}{genres}}{$sg} = $i++; } } } my $i = 1; foreach my $c (@{$t->{'overviewTab'}->{cast}}) { my $n = $c->{name}; my $cn = $c->{characterName}; my $cr = lc($c->{role}); if ($cr eq 'host') { ${$programs{$cp}{presenter}}{$n} = $i++; } else { ${$programs{$cp}{actor}}{$n} = $i++; ${$programs{$cp}{role}}{$n} = $cn if length($cn); } } $i = 1; foreach my $c (@{$t->{'overviewTab'}->{crew}}) { my $n = $c->{name}; my $cr = lc($c->{role}); if ($cr =~ /producer/) { ${$programs{$cp}{producer}}{$n} = $i++; } elsif ($cr =~ /director/) { ${$programs{$cp}{director}}{$n} = $i++; } elsif ($cr =~ /writer/) { ${$programs{$cp}{writer}}{$n} = $i++; } } if (!defined($programs{$cp}{imageUrl}) && $t->{seriesImage} ne '') { my $turl = $urlAssets; $turl .= $t->{seriesImage} . ".jpg"; $programs{$cp}{imageUrl} = $turl; } if ($cp =~ /^MV|^SH/ && length($t->{seriesDescription}) > length($programs{$cp}{description})) { $programs{$cp}{description} = $t->{seriesDescription}; } if ($cp =~ /^EP/) { # GMT @ 00:00:00 my $ue = $t->{overviewTab}->{upcomingEpisode}; if (defined($ue) && lc($ue->{tmsID}) eq lc($cp) && $ue->{originalAirDate} ne '' && $ue->{originalAirDate} ne '1000-01-01T00:00Z') { $oad = str2time2($ue->{originalAirDate}) ; $oad *= 1000; $programs{$cp}{originalAirDate} = $oad; } else { foreach my $ue (@{$t->{upcomingEpisodeTab}}) { if (lc($ue->{tmsID}) eq lc($cp) && $ue->{originalAirDate} ne '' && $ue->{originalAirDate} ne '1000-01-01T00:00Z' ) { $oad = str2time2($ue->{originalAirDate}) ; $oad *= 1000; $programs{$cp}{originalAirDate} = $oad; last; } } } } } else { &pout("Skipping: $sid\n"); } } sub str2time1 { my $t = Time::Piece->strptime(shift, '%Y-%m-%dT%H:%M:%SZ'); return $t->epoch(); } sub str2time2 { my $t = Time::Piece->strptime(shift, '%Y-%m-%dT%H:%MZ'); return $t->epoch(); } sub hourToMillis { ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(time); if ($start == 0) { $hour = int($hour/$gridHours) * $gridHours; } else { $hour = 0; } $t = timegm(0,0,$hour,$mday,$mon,$year); $t = $t - (&tz_offset * 3600) if !defined($options{g}); ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = gmtime($t); $t = timegm($sec, $min, $hour,$mday,$mon,$year); return $t . "000"; } sub tz_offset { my $n = defined $_[0] ? $_[0] : time; my ($lm, $lh, $ly, $lyd) = (localtime $n)[1, 2, 5, 7]; my ($gm, $gh, $gy, $gyd) = (gmtime $n)[1, 2, 5, 7]; ($lm - $gm)/60 + $lh - $gh + 24 * ($ly - $gy || $lyd - $gyd) } sub timezone { my $tztime = defined $_[0] ? &_rtrim3(shift) : time; my $os = sprintf "%.1f", (timegm(localtime($tztime)) - $tztime) / 3600; my $mins = sprintf "%02d", abs( $os - int($os) ) * 60; return sprintf("%+03d", int($os)) . $mins; } sub max ($$) { $_[$_[0] < $_[1]] } sub min ($$) { $_[$_[0] > $_[1]] } sub HELP_MESSAGE { print < ($VERSION) -u -p -d <# of days> (default = $days) -n <# of no-cache days> (from end) (default = $ncdays) -N <# of no-cache days> (from start) (default = $ncsdays) -B -s (default = $start) -o (default = "$outFile") -c (default = "$cacheDir") -l (default = "$lang") -i (default = don't download channel icons) -m <#> = offset program times by # minutes (better to use TZ env var) -b = retain website channel order -x = output XTVD xml file format (default = XMLTV) -w = wait on exit (require keypress before exiting) -q = quiet (no status output) -r <# of connection retries before failure> (default = $retries, max 20) -e = hex encode entities (html special characters like accents) -E "amp apos quot lt gt" = selectively encode standard XML entities -F = output channel names first (rather than "number name") -O = use old tv_grab_na style channel ids (C###nnnn.zap2it.com) -A "new live" = append " *" to program titles that are "new" and/or "live" -M = copy movie_year to empty movie sub-title tags -U = UTF-8 encoding (default = "ISO-8859-1") -L = output "" tag (not part of xmltv.dtd) -T = don't cache files containing programs with "$sTBA" titles -P = to use an http proxy -C (default = "$confFile") -S <#seconds> = sleep between requests to prevent flooding of server -D = include details = 1 extra http request per program! -I = include icons (image URLs) - 1 extra http request per program! -J = include xmltv file in output -Y (if not using username/password) -Z (if not using username/password) -z = use tvguide.com instead of zap2it.com -a = output all channels (not just favorites) -j = add "series" category to all non-movie programs END sleep(5) if ($^O eq 'MSWin32'); exit 0; }