commit e4c78bc864ac8dc1c62ebbb9dd4c0ae9fe1dc1d7 Author: Radar231 Date: Sun Aug 1 18:35:42 2021 -0400 Initial checkin diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..03bd412 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +*.env diff --git a/data/.placeholder b/data/.placeholder new file mode 100644 index 0000000..e69de29 diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..cb40325 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,21 @@ +--- +version: '3' + +services: + zap2xml: + container_name: zap2xml + image: shuaiscott/zap2xml:latest + restart: unless-stopped + env_file: + - zap2xml.env + volumes: + - ./data:/data + environment: + # values retrieved from zap2xml.env file + - USERNAME=xxxxxxx + - PASSWORD=xxxxxxx + - OPT_ARGS=-I -D + - XMLTV_FILENAME=xmltv.xml + +# EOF + diff --git a/zap2xml.pl b/zap2xml.pl new file mode 100755 index 0000000..15f1c96 --- /dev/null +++ b/zap2xml.pl @@ -0,0 +1,1598 @@ +#!/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; +} +