Advertisement
Guest User

Untitled

a guest
Jan 9th, 2018
556
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 48.85 KB | None | 0 0
  1. #!/usr/bin/env perl
  2. # zap2xml (c) <zap2xml@gmail.com> - for personal use only!
  3. # not for redistribution of any kind, or conversion to other languages,
  4. # not GPL. not for github, thank you.
  5.  
  6. BEGIN { $SIG{__DIE__} = sub {
  7. return if $^S;
  8. my $msg = join(" ", @_);
  9. print STDERR "$msg";
  10. if ($msg =~ /can't locate/i) {
  11. print "\nSee homepage for tips on installing missing modules (example: \"perl -MCPAN -e shell\")\n";
  12. if ($^O eq 'MSWin32') {
  13. print "Use \"ppm install\" on windows\n";
  14. }
  15. }
  16. if ($^O eq 'MSWin32') {
  17. if ($msg =~ /uri.pm/i && $msg =~ /temp/i) {
  18. print "\nIf your scanner deleted the perl URI.pm file see the homepage for tips\n";
  19. if ($msg =~ /(\ .\:.+?par-.+?\\)/) {
  20. print "(Delete the $1 folder and retry)\n";
  21. }
  22. }
  23. sleep(5);
  24. }
  25. exit 1;
  26. }}
  27.  
  28. use Compress::Zlib;
  29. use Encode;
  30. use File::Basename;
  31. use File::Copy;
  32. use Getopt::Std;
  33. use HTTP::Cookies;
  34. use URI;
  35. use URI::Escape;
  36. use LWP::UserAgent;
  37. use POSIX;
  38. use Time::Local;
  39. use Date::Parse;
  40. use JSON;
  41.  
  42. no warnings 'utf8';
  43.  
  44. STDOUT->autoflush(1);
  45. STDERR->autoflush(1);
  46.  
  47. %options=();
  48. getopts("?aA:bB:c:C:d:DeE:Fgi:IjJ:l:Lm:Mn:N:o:Op:P:qRr:s:S:t:Tu:UwWxY:zZ:",\%options);
  49.  
  50. $homeDir = $ENV{HOME};
  51. $homeDir = $ENV{USERPROFILE} if !defined($homeDir);
  52. $homeDir = '.' if !defined($homeDir);
  53. $confFile = $homeDir . '/.zap2xmlrc';
  54.  
  55. # Defaults
  56. $start = 0;
  57. $days = 7;
  58. $ncdays = 0;
  59. $ncsdays = 0;
  60. $ncmday = -1;
  61. $retries = 3;
  62. $outFile = 'xmltv.xml';
  63. $outFile = 'xtvd.xml' if defined $options{x};
  64. $cacheDir = 'cache';
  65. $lang = 'en';
  66. $userEmail = '';
  67. $password = '';
  68. $proxy;
  69. $postalcode;
  70. $country;
  71. $lineupId;
  72. $device;
  73. $sleeptime = 0;
  74. $allChan = 0;
  75. $shiftMinutes = 0;
  76.  
  77. $outputXTVD = 0;
  78. $lineuptype;
  79. $lineupname;
  80. $lineuplocation;
  81.  
  82. $zapToken;
  83. %zapFavorites=();
  84. %sidCache=();
  85.  
  86. $sTBA = "\\bTBA\\b|To Be Announced";
  87.  
  88. %tvgfavs=();
  89.  
  90. &HELP_MESSAGE() if defined $options{'?'};
  91.  
  92. $confFile = $options{C} if defined $options{C};
  93. # read config file
  94. if (open (CONF, $confFile))
  95. {
  96. &pout("Reading config file: $confFile\n");
  97. while (<CONF>)
  98. {
  99. s/#.*//; # comments
  100. if (/^\s*$/i) { }
  101. elsif (/^\s*start\s*=\s*(\d+)/i) { $start = $1; }
  102. elsif (/^\s*days\s*=\s*(\d+)/i) { $days = $1; }
  103. elsif (/^\s*ncdays\s*=\s*(\d+)/i) { $ncdays = $1; }
  104. elsif (/^\s*ncsdays\s*=\s*(\d+)/i) { $ncsdays = $1; }
  105. elsif (/^\s*ncmday\s*=\s*(\d+)/i) { $ncmday = $1; }
  106. elsif (/^\s*retries\s*=\s*(\d+)/i) { $retries = $1; }
  107. elsif (/^\s*user[\w\s]*=\s*(.+)/i) { $userEmail = &rtrim($1); }
  108. elsif (/^\s*pass[\w\s]*=\s*(.+)/i) { $password = &rtrim($1); }
  109. elsif (/^\s*cache\s*=\s*(.+)/i) { $cacheDir = &rtrim($1); }
  110. elsif (/^\s*icon\s*=\s*(.+)/i) { $iconDir = &rtrim($1); }
  111. elsif (/^\s*trailer\s*=\s*(.+)/i) { $trailerDir = &rtrim($1); }
  112. elsif (/^\s*lang\s*=\s*(.+)/i) { $lang = &rtrim($1); }
  113. elsif (/^\s*outfile\s*=\s*(.+)/i) { $outFile = &rtrim($1); }
  114. elsif (/^\s*proxy\s*=\s*(.+)/i) { $proxy = &rtrim($1); }
  115. elsif (/^\s*outformat\s*=\s*(.+)/i) { $outputXTVD = 1 if $1 =~ /xtvd/i; }
  116. elsif (/^\s*lineupid\s*=\s*(.+)/i) { $lineupId = &rtrim($1); }
  117. elsif (/^\s*lineupname\s*=\s*(.+)/i) { $lineupname = &rtrim($1); }
  118. elsif (/^\s*lineuptype\s*=\s*(.+)/i) { $lineuptype = &rtrim($1); }
  119. elsif (/^\s*lineuplocation\s*=\s*(.+)/i) { $lineuplocation = &rtrim($1); }
  120. elsif (/^\s*postalcode\s*=\s*(.+)/i) { $postalcode = &rtrim($1); }
  121. else
  122. {
  123. die "Oddline in config file \"$confFile\".\n\t$_";
  124. }
  125. }
  126. close (CONF);
  127. }
  128. &HELP_MESSAGE() if !(%options) && $userEmail eq '';
  129.  
  130. $cacheDir = $options{c} if defined $options{c};
  131. $days = $options{d} if defined $options{d};
  132. $ncdays = $options{n} if defined $options{n};
  133. $ncsdays = $options{N} if defined $options{N};
  134. $ncmday = $options{B} if defined $options{B};
  135. $start = $options{s} if defined $options{s};
  136. $retries = $options{r} if defined $options{r};
  137. $iconDir = $options{i} if defined $options{i};
  138. $trailerDir = $options{t} if defined $options{t};
  139. $lang = $options{l} if defined $options{l};
  140. $outFile = $options{o} if defined $options{o};
  141. $password = $options{p} if defined $options{p};
  142. $userEmail = $options{u} if defined $options{u};
  143. $proxy = $options{P} if defined $options{P};
  144. $zlineupId = $options{Y} if defined $options{Y};
  145. $zipcode = $options{Z} if defined $options{Z};
  146. $includeXMLTV = $options{J} if defined $options{J} && -e $options{J};
  147. $outputXTVD = 1 if defined $options{x};
  148. $allChan = 1 if defined($options{a});
  149. $allChan = 1 if defined($zipcode) && defined($zlineupId);
  150. $sleeptime = $options{S} if defined $options{S};
  151. $shiftMinutes = $options{m} if defined $options{m};
  152. $ncdays = $days - $ncdays; # make relative to the end
  153. $urlRoot = 'https://tvlistings.zap2it.com/';
  154. $urlAssets = 'https://zap2it.tmsimg.com/assets/';
  155. $tvgurlRoot = 'http://mobilelistings.tvguide.com/';
  156. $tvgMapiRoot = 'http://mapi.tvguide.com/';
  157. $tvgurl = 'http://www.tvguide.com/';
  158. $tvgspritesurl = 'http://static.tvgcdn.net/sprites/';
  159. $retries = 20 if $retries > 20; # Too many
  160.  
  161. my %programs = ();
  162. my $cp;
  163. my %stations = ();
  164. my $cs;
  165. my $rcs;
  166. my %schedule = ();
  167. my $sch;
  168.  
  169. my $coNum = 0;
  170. my $tb = 0;
  171. my $treq = 0;
  172. my $expired = 0;
  173. my $ua;
  174. my $tba = 0;
  175. my $exp = 0;
  176. my @fh = ();
  177.  
  178. my $XTVD_startTime;
  179. my $XTVD_endTime;
  180.  
  181. if (! -d $cacheDir) {
  182. mkdir($cacheDir) or die "Can't mkdir: $!\n";
  183. } else {
  184. opendir (DIR, "$cacheDir/");
  185. @cacheFiles = grep(/\.html|\.js/,readdir(DIR));
  186. closedir (DIR);
  187. foreach $cacheFile (@cacheFiles) {
  188. $fn = "$cacheDir/$cacheFile";
  189. $atime = (stat($fn))[8];
  190. if ($atime + ( ($days + 2) * 86400) < time) {
  191. &pout("Deleting old cached file: $fn\n");
  192. &unf($fn);
  193. }
  194. }
  195. }
  196.  
  197. my $s1 = time();
  198. if (defined($options{z})) {
  199.  
  200. &login() if !defined($options{a}); # get favorites
  201. &parseTVGIcons() if defined($iconDir);
  202. $gridHours = 3;
  203. $maxCount = $days * (24 / $gridHours);
  204. $offset = $start * 3600 * 24 * 1000;
  205. $ms = &hourToMillis() + $offset;
  206.  
  207. for ($count=0; $count < $maxCount; $count++) {
  208. $curday = int($count / (24/$gridHours)) + 1;
  209. if ($count == 0) {
  210. $XTVD_startTime = $ms;
  211. } elsif ($count == $maxCount - 1) {
  212. $XTVD_endTime = $ms + ($gridHours * 3600000) - 1;
  213. }
  214.  
  215. $fn = "$cacheDir/$ms\.js\.gz";
  216. if (! -e $fn || $curday > $ncdays || $curday <= $ncsdays || $curday == $ncmday) {
  217. &login() if !defined($zlineupId);
  218. my $duration = $gridHours * 60;
  219. my $tvgstart = substr($ms, 0, -3);
  220. $rc = Encode::encode('utf8', &getURL($tvgurlRoot . "Listingsweb/ws/rest/schedules/$zlineupId/start/$tvgstart/duration/$duration"));
  221. &wbf($fn, Compress::Zlib::memGzip($rc));
  222. }
  223. &pout("[" . ($count+1) . "/" . "$maxCount] Parsing: $fn\n");
  224. &parseTVGGrid($fn);
  225.  
  226. if (defined($options{T}) && $tba) {
  227. &pout("Deleting: $fn (contains \"$sTBA\")\n");
  228. &unf($fn);
  229. }
  230. if ($exp) {
  231. &pout("Deleting: $fn (expired)\n");
  232. &unf($fn);
  233. }
  234. $exp = 0;
  235. $tba = 0;
  236. $ms += ($gridHours * 3600 * 1000);
  237. }
  238.  
  239. } else {
  240.  
  241. &login() if !defined($options{a}); # get favorites
  242. $gridHours = 3;
  243. $maxCount = $days * (24 / $gridHours);
  244. $offset = $start * 3600 * 24 * 1000;
  245. $ms = &hourToMillis() + $offset;
  246. for ($count=0; $count < $maxCount; $count++) {
  247. $curday = int($count / (24/$gridHours)) + 1;
  248. if ($count == 0) {
  249. $XTVD_startTime = $ms;
  250. } elsif ($count == $maxCount - 1) {
  251. $XTVD_endTime = $ms + ($gridHours * 3600000) - 1;
  252. }
  253.  
  254. $fn = "$cacheDir/$ms\.js\.gz";
  255. if (! -e $fn || $curday > $ncdays || $curday <= $ncsdays || $curday == $ncmday) {
  256. my $zstart = substr($ms, 0, -3);
  257. $params = "?time=$zstart&timespan=$gridHours&";
  258. $params .= &getZapGParams();
  259. $params .= '&TMSID=&AffiliateID=gapzap&FromPage=TV%20Grid';
  260. $params .= '&ActivityID=1&OVDID=&isOverride=true&pref=-';
  261. $rs = &getURL($urlRoot . "api/grid$params",'X-Requested-With' => 'XMLHttpRequest');
  262. last if ($rs eq '');
  263. $rc = Encode::encode('utf8', $rs);
  264. &wbf($fn, Compress::Zlib::memGzip($rc));
  265. }
  266.  
  267. &pout("[" . ($count+1) . "/" . "$maxCount] Parsing: $fn\n");
  268. &parseJSON($fn);
  269.  
  270. if (defined($options{T}) && $tba) {
  271. &pout("Deleting: $fn (contains \"$sTBA\")\n");
  272. &unf($fn);
  273. }
  274. if ($exp) {
  275. &pout("Deleting: $fn (expired)\n");
  276. &unf($fn);
  277. }
  278. $exp = 0;
  279. $tba = 0;
  280. $ms += ($gridHours * 3600 * 1000);
  281. }
  282.  
  283. }
  284. my $s2 = time();
  285.  
  286. &pout("Downloaded $tb bytes in $treq http requests.\n") if $tb > 0;
  287. &pout("Expired programs: $expired\n") if $expired > 0;
  288. &pout("Writing XML file: $outFile\n");
  289. open($FH, ">$outFile");
  290. my $enc = 'ISO-8859-1';
  291. if (defined($options{U})) {
  292. $enc = 'UTF-8';
  293. }
  294. if ($outputXTVD) {
  295. &printHeaderXTVD($FH, $enc);
  296. &printStationsXTVD($FH);
  297. &printLineupsXTVD($FH);
  298. &printSchedulesXTVD($FH);
  299. &printProgramsXTVD($FH);
  300. &printGenresXTVD($FH);
  301. &printFooterXTVD($FH);
  302. } else {
  303. &printHeader($FH, $enc);
  304. &printChannels($FH);
  305. if (defined($includeXMLTV)) {
  306. &pout("Reading XML file: $includeXMLTV\n");
  307. &incXML("<channel","<programme", $FH);
  308. }
  309. &printProgrammes($FH);
  310. &incXML("<programme","</tv", $FH) if defined($includeXMLTV);
  311. &printFooter($FH);
  312. }
  313.  
  314. close($FH);
  315.  
  316. my $ts = 0;
  317. for my $station (keys %stations ) {
  318. $ts += scalar (keys %{$schedule{$station}})
  319. }
  320. my $s3 = time();
  321. &pout("Completed in " . ( $s3 - $s1 ) . "s (Parse: " . ( $s2 - $s1 ) . "s) " . keys(%stations) . " stations, " . keys(%programs) . " programs, $ts scheduled.\n");
  322.  
  323. if (defined($options{w})) {
  324. print "Press ENTER to exit:";
  325. <STDIN>;
  326. } else {
  327. sleep(3) if ($^O eq 'MSWin32');
  328. }
  329.  
  330. exit 0;
  331.  
  332. sub incXML {
  333. my ($st, $en, $FH) = @_;
  334. open($XF, "<$includeXMLTV");
  335. while (<$XF>) {
  336. if (/^\s*$st/../^\s*$en/) {
  337. print $FH $_ unless /^\s*$en/
  338. }
  339. }
  340. close($XF);
  341. }
  342.  
  343. sub pout {
  344. print @_ if !defined $options{q};
  345. }
  346.  
  347. sub perr {
  348. warn @_;
  349. }
  350.  
  351. sub rtrim {
  352. my $s = shift;
  353. $s =~ s/\s+$//;
  354. return $s;
  355. }
  356.  
  357. sub trim {
  358. my $s = shift;
  359. $s =~ s/^\s+//;
  360. $s =~ s/\s+$//;
  361. return $s;
  362. }
  363.  
  364. sub trim2 {
  365. my $s = &trim(shift);
  366. $s =~ s/[^\w\s\(\)\,]//gsi;
  367. $s =~ s/\s+/ /gsi;
  368. return $s;
  369. }
  370.  
  371. sub _rtrim3 {
  372. my $s = shift;
  373. return substr($s, 0, length($s)-3);
  374. }
  375.  
  376. sub convTime {
  377. my $t = shift;
  378. $t += $shiftMinutes * 60 * 1000;
  379. return strftime "%Y%m%d%H%M%S", localtime(&_rtrim3($t));
  380. }
  381.  
  382. sub convTimeXTVD {
  383. my $t = shift;
  384. $t += $shiftMinutes * 60 * 1000;
  385. return strftime "%Y-%m-%dT%H:%M:%SZ", gmtime(&_rtrim3($t));
  386. }
  387.  
  388. sub convOAD {
  389. return strftime "%Y%m%d", gmtime(&_rtrim3(shift));
  390. }
  391.  
  392. sub convOADXTVD {
  393. return strftime "%Y-%m-%d", gmtime(&_rtrim3(shift));
  394. }
  395.  
  396. sub convDurationXTVD {
  397. my $duration = shift;
  398. my $hour = int($duration / 3600000);
  399. my $minutes = int(($duration - ($hour * 3600000)) / 60000);
  400. return sprintf("PT%02dH%02dM", $hour, $minutes);
  401. }
  402.  
  403. sub appendAsterisk {
  404. my ($title, $station, $s) = @_;
  405. if (defined($options{A})) {
  406. if (($options{A} =~ "new" && defined($schedule{$station}{$s}{new}))
  407. || ($options{A} =~ "live" && defined($schedule{$station}{$s}{live}))) {
  408. $title .= " *";
  409. }
  410. }
  411. return $title;
  412. }
  413.  
  414. sub stationToChannel {
  415. my $s = shift;
  416. if (defined($options{z})) {
  417. return sprintf("I%s.%s.tvguide.com", $stations{$s}{number},$stations{$s}{stnNum});
  418. } elsif (defined($options{O})) {
  419. return sprintf("C%s%s.zap2it.com",$stations{$s}{number},lc($stations{$s}{name}));
  420. }
  421. return sprintf("I%s.labs.zap2it.com", $stations{$s}{stnNum});
  422. }
  423.  
  424. sub sortChan {
  425. if (defined($stations{$a}{order}) && defined($stations{$b}{order})) {
  426. return $stations{$a}{order} <=> $stations{$b}{order};
  427. } else {
  428. return $stations{$a}{name} cmp $stations{$b}{name};
  429. }
  430. }
  431.  
  432. sub enc {
  433. my $t = shift;
  434. if (!defined($options{U})) {$t = Encode::decode('utf8', $t);}
  435. if (!defined($options{E}) || $options{E} =~ /amp/) {$t =~ s/&/&amp;/gs;}
  436. if (!defined($options{E}) || $options{E} =~ /quot/) {$t =~ s/"/&quot;/gs;}
  437. if (!defined($options{E}) || $options{E} =~ /apos/) {$t =~ s/'/&apos;/gs;}
  438. if (!defined($options{E}) || $options{E} =~ /lt/) {$t =~ s/</&lt;/gs;}
  439. if (!defined($options{E}) || $options{E} =~ /gt/) {$t =~ s/>/&gt;/gs;}
  440. if (defined($options{e})) {
  441. $t =~ s/([^\x20-\x7F])/'&#' . ord($1) . ';'/gse;
  442. }
  443. return $t;
  444. }
  445.  
  446. sub printHeader {
  447. my ($FH, $enc) = @_;
  448. print $FH "<?xml version=\"1.0\" encoding=\"$enc\"?>\n";
  449. print $FH "<!DOCTYPE tv SYSTEM \"xmltv.dtd\">\n\n";
  450. if (defined($options{z})) {
  451. print $FH "<tv source-info-url=\"http://tvguide.com/\" source-info-name=\"tvguide.com\"";
  452. } else {
  453. print $FH "<tv source-info-url=\"http://tvschedule.zap2it.com/\" source-info-name=\"zap2it.com\"";
  454. }
  455. print $FH " generator-info-name=\"zap2xml\" generator-info-url=\"zap2xml\@gmail.com\">\n";
  456. }
  457.  
  458. sub printFooter {
  459. my $FH = shift;
  460. print $FH "</tv>\n";
  461. }
  462.  
  463. sub printChannels {
  464. my $FH = shift;
  465. for my $key ( sort sortChan keys %stations ) {
  466. $sname = &enc($stations{$key}{name});
  467. $fname = &enc($stations{$key}{fullname});
  468. $snum = $stations{$key}{number};
  469. print $FH "\t<channel id=\"" . &stationToChannel($key) . "\">\n";
  470. print $FH "\t\t<display-name>" . $sname . "</display-name>\n" if defined($options{F}) && defined($sname);
  471. if (defined($snum)) {
  472. &copyLogo($key);
  473. print $FH "\t\t<display-name>" . $snum . " " . $sname . "</display-name>\n";
  474. print $FH "\t\t<display-name>" . $snum . "</display-name>\n";
  475. }
  476. print $FH "\t\t<display-name>" . $sname . "</display-name>\n" if !defined($options{F}) && defined($sname);
  477. print $FH "\t\t<display-name>" . $fname . "</display-name>\n" if (defined($fname));
  478. if (defined($stations{$key}{logoURL})) {
  479. print $FH "\t\t<icon src=\"" . $stations{$key}{logoURL} . "\" />\n";
  480. }
  481. print $FH "\t</channel>\n";
  482. }
  483. }
  484.  
  485. sub printProgrammes {
  486. my $FH = shift;
  487. for my $station ( sort sortChan keys %stations ) {
  488. my $i = 0;
  489. my @keyArray = sort { $schedule{$station}{$a}{time} cmp $schedule{$station}{$b}{time} } keys %{$schedule{$station}};
  490. foreach $s (@keyArray) {
  491. if ($#keyArray <= $i && !defined($schedule{$station}{$s}{endtime})) {
  492. delete $schedule{$station}{$s};
  493. next;
  494. }
  495. my $p = $schedule{$station}{$s}{program};
  496. my $startTime = &convTime($schedule{$station}{$s}{time});
  497. my $startTZ = &timezone($schedule{$station}{$s}{time});
  498. my $endTime;
  499. if (defined($schedule{$station}{$s}{endtime})) {
  500. $endTime = $schedule{$station}{$s}{endtime};
  501. } else {
  502. $endTime = $schedule{$station}{$keyArray[$i+1]}{time};
  503. }
  504.  
  505. my $stopTime = &convTime($endTime);
  506. my $stopTZ = &timezone($endTime);
  507.  
  508. print $FH "\t<programme start=\"$startTime $startTZ\" stop=\"$stopTime $stopTZ\" channel=\"" . &stationToChannel($schedule{$station}{$s}{station}) . "\">\n";
  509. if (defined($programs{$p}{title})) {
  510. my $title = &enc($programs{$p}{title});
  511. $title = &appendAsterisk($title, $station, $s);
  512. print $FH "\t\t<title lang=\"$lang\">" . $title . "</title>\n";
  513. }
  514.  
  515. if (defined($programs{$p}{episode}) || (defined($options{M}) && defined($programs{$p}{movie_year}))) {
  516. print $FH "\t\t<sub-title lang=\"$lang\">";
  517. if (defined($programs{$p}{episode})) {
  518. print $FH &enc($programs{$p}{episode});
  519. } else {
  520. print $FH "Movie (" . $programs{$p}{movie_year} . ")";
  521. }
  522. print $FH "</sub-title>\n"
  523. }
  524.  
  525. print $FH "\t\t<desc lang=\"$lang\">" . &enc($programs{$p}{description}) . "</desc>\n" if defined($programs{$p}{description});
  526.  
  527. if (defined($programs{$p}{actor})
  528. || defined($programs{$p}{director})
  529. || defined($programs{$p}{writer})
  530. || defined($programs{$p}{producer})
  531. || defined($programs{$p}{preseter})
  532. ) {
  533. print $FH "\t\t<credits>\n";
  534. &printCredits($FH, $p, "director");
  535. foreach my $g (sort { $programs{$p}{actor}{$a} <=> $programs{$p}{actor}{$b} } keys %{$programs{$p}{actor}} ) {
  536. print $FH "\t\t\t<actor";
  537. print $FH " role=\"" . &enc($programs{$p}{role}{$g}) . "\"" if (defined($programs{$p}{role}{$g}));
  538. print $FH ">" . &enc($g) . "</actor>\n";
  539. }
  540. &printCredits($FH, $p, "writer");
  541. &printCredits($FH, $p, "producer");
  542. &printCredits($FH, $p, "presenter");
  543. print $FH "\t\t</credits>\n";
  544. }
  545.  
  546. my $date;
  547. if (defined($programs{$p}{movie_year})) {
  548. $date = $programs{$p}{movie_year};
  549. } elsif (defined($programs{$p}{originalAirDate}) && $p =~ /^EP|^\d/) {
  550. $date = &convOAD($programs{$p}{originalAirDate});
  551. }
  552. print $FH "\t\t<date>$date</date>\n" if defined($date);
  553.  
  554. if (defined($programs{$p}{genres})) {
  555. foreach my $g (sort { $programs{$p}{genres}{$a} <=> $programs{$p}{genres}{$b} } keys %{$programs{$p}{genres}} ) {
  556. print $FH "\t\t<category lang=\"$lang\">" . &enc(ucfirst($g)) . "</category>\n";
  557. }
  558. }
  559.  
  560. print $FH "\t\t<length units=\"minutes\">" . $programs{$p}{duration} . "</length>\n" if defined($programs{$p}{duration});
  561.  
  562. if (defined($programs{$p}{imageUrl})) {
  563. print $FH "\t\t<icon src=\"" . &enc($programs{$p}{imageUrl}) . "\" />\n";
  564. }
  565.  
  566. if (defined($programs{$p}{url})) {
  567. print $FH "\t\t<url>" . &enc($programs{$p}{url}) . "</url>\n";
  568. }
  569.  
  570. my $xs;
  571. my $xe;
  572.  
  573. if (defined($programs{$p}{seasonNum}) && defined($programs{$p}{episodeNum})) {
  574. my $s = $programs{$p}{seasonNum};
  575. my $sf = sprintf("S%0*d", &max(2, length($s)), $s);
  576. my $e = $programs{$p}{episodeNum};
  577. my $ef = sprintf("E%0*d", &max(2, length($e)), $e);
  578.  
  579. $xs = int($s) - 1;
  580. $xe = int($e) - 1;
  581.  
  582. if ($s > 0 || $e > 0) {
  583. print $FH "\t\t<episode-num system=\"common\">" . $sf . $ef . "</episode-num>\n";
  584. }
  585. }
  586.  
  587. $dd_prog_id = $p;
  588. if ( $dd_prog_id =~ /^(..\d{8})(\d{4})/ ) {
  589. $dd_prog_id = sprintf("%s.%s",$1,$2);
  590. print $FH "\t\t<episode-num system=\"dd_progid\">" . $dd_prog_id . "</episode-num>\n";
  591. }
  592.  
  593. if (defined($xs) && defined($xe) && $xs >= 0 && $xe >= 0) {
  594. print $FH "\t\t<episode-num system=\"xmltv_ns\">" . $xs . "." . $xe . ".</episode-num>\n";
  595. }
  596.  
  597. if (defined($schedule{$station}{$s}{quality})) {
  598. print $FH "\t\t<video>\n";
  599. print $FH "\t\t\t<aspect>16:9</aspect>\n";
  600. print $FH "\t\t\t<quality>HDTV</quality>\n";
  601. print $FH "\t\t</video>\n";
  602. }
  603. my $new = defined($schedule{$station}{$s}{new});
  604. my $live = defined($schedule{$station}{$s}{live});
  605. my $cc = defined($schedule{$station}{$s}{cc});
  606.  
  607. if (! $new && ! $live && $p =~ /^EP|^SH|^\d/) {
  608. print $FH "\t\t<previously-shown ";
  609. if (defined($programs{$p}{originalAirDate})) {
  610. $date = &convOAD($programs{$p}{originalAirDate});
  611. print $FH "start=\"" . $date . "000000\" ";
  612. }
  613. print $FH "/>\n";
  614. }
  615.  
  616. if (defined($schedule{$station}{$s}{premiere})) {
  617. print $FH "\t\t<premiere>" . $schedule{$station}{$s}{premiere} . "</premiere>\n";
  618. }
  619.  
  620. if (defined($schedule{$station}{$s}{finale})) {
  621. print $FH "\t\t<last-chance>" . $schedule{$station}{$s}{finale} . "</last-chance>\n";
  622. }
  623.  
  624. print $FH "\t\t<new />\n" if $new;
  625. # not part of XMLTV format yet?
  626. print $FH "\t\t<live />\n" if (defined($options{L}) && $live);
  627. print $FH "\t\t<subtitles type=\"teletext\" />\n" if $cc;
  628.  
  629. if (defined($programs{$p}{rating})) {
  630. print $FH "\t\t<rating>\n\t\t\t<value>" . $programs{$p}{rating} . "</value>\n\t\t</rating>\n"
  631. }
  632.  
  633. if (defined($programs{$p}{starRating})) {
  634. print $FH "\t\t<star-rating>\n\t\t\t<value>" . $programs{$p}{starRating} . "/4</value>\n\t\t</star-rating>\n";
  635. }
  636. print $FH "\t</programme>\n";
  637. $i++;
  638. }
  639. }
  640. }
  641.  
  642. sub printHeaderXTVD {
  643. my ($FH, $enc) = @_;
  644. print $FH "<?xml version='1.0' encoding='$enc'?>\n";
  645. print $FH "<xtvd from='" . &convTimeXTVD($XTVD_startTime) . "' to='" . &convTimeXTVD($XTVD_endTime) . "' schemaVersion='1.3' xmlns='urn:TMSWebServices' xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance' xsi:schemaLocation='urn:TMSWebServices http://docs.tms.tribune.com/tech/xml/schemas/tmsxtvd.xsd'>\n";
  646. }
  647.  
  648. sub printCredits {
  649. my ($FH, $p, $s) = @_;
  650. foreach my $g (sort { $programs{$p}{$s}{$a} <=> $programs{$p}{$s}{$b} } keys %{$programs{$p}{$s}} ) {
  651. print $FH "\t\t\t<$s>" . &enc($g) . "</$s>\n";
  652. }
  653. }
  654.  
  655. sub printFooterXTVD {
  656. my $FH = shift;
  657. print $FH "</xtvd>\n";
  658. }
  659.  
  660. sub printStationsXTVD {
  661. my $FH = shift;
  662. print $FH "<stations>\n";
  663. for my $key ( sort sortChan keys %stations ) {
  664. print $FH "\t<station id='" . $stations{$key}{stnNum} . "'>\n";
  665. if (defined($stations{$key}{number})) {
  666. $sname = &enc($stations{$key}{name});
  667. print $FH "\t\t<callSign>" . $sname . "</callSign>\n";
  668. print $FH "\t\t<name>" . $sname . "</name>\n";
  669. print $FH "\t\t<fccChannelNumber>" . $stations{$key}{number} . "</fccChannelNumber>\n";
  670. if (defined($stations{$key}{logo}) && $stations{$key}{logo} =~ /_affiliate/i) {
  671. $affiliate = $stations{$key}{logo};
  672. $affiliate =~ s/(.*)\_.*/uc($1)/e;
  673. print $FH "\t\t<affiliate>" . $affiliate . " Affiliate</affiliate>\n";
  674. }
  675. &copyLogo($key);
  676. }
  677. print $FH "\t</station>\n";
  678. }
  679. print $FH "</stations>\n";
  680. }
  681.  
  682. sub printLineupsXTVD {
  683. my $FH = shift;
  684. print $FH "<lineups>\n";
  685. print $FH "\t<lineup id='$lineupId' name='$lineupname' location='$lineuplocation' type='$lineuptype' postalCode='$postalcode'>\n";
  686. for my $key ( sort sortChan keys %stations ) {
  687. if (defined($stations{$key}{number})) {
  688. print $FH "\t<map station='" . $stations{$key}{stnNum} . "' channel='" . $stations{$key}{number} . "'></map>\n";
  689. }
  690. }
  691. print $FH "\t</lineup>\n";
  692. print $FH "</lineups>\n";
  693. }
  694.  
  695. sub printSchedulesXTVD {
  696. my $FH = shift;
  697. print $FH "<schedules>\n";
  698. for my $station ( sort sortChan keys %stations ) {
  699. my $i = 0;
  700. my @keyArray = sort { $schedule{$station}{$a}{time} cmp $schedule{$station}{$b}{time} } keys %{$schedule{$station}};
  701. foreach $s (@keyArray) {
  702. if ($#keyArray <= $i) {
  703. delete $schedule{$station}{$s};
  704. next;
  705. }
  706. my $p = $schedule{$station}{$s}{program};
  707. my $startTime = &convTimeXTVD($schedule{$station}{$s}{time});
  708. my $stopTime = &convTimeXTVD($schedule{$station}{$keyArray[$i+1]}{time});
  709. my $duration = &convDurationXTVD($schedule{$station}{$keyArray[$i+1]}{time} - $schedule{$station}{$s}{time});
  710.  
  711. print $FH "\t<schedule program='$p' station='" . $stations{$station}{stnNum} . "' time='$startTime' duration='$duration'";
  712. print $FH " hdtv='true' " if (defined($schedule{$station}{$s}{quality}));
  713. print $FH " new='true' " if (defined($schedule{$station}{$s}{new}) || defined($schedule{$station}{$s}{live}));
  714. print $FH "/>\n";
  715. $i++;
  716. }
  717. }
  718. print $FH "</schedules>\n";
  719. }
  720.  
  721. sub printProgramsXTVD {
  722. my $FH = shift;
  723. print $FH "<programs>\n";
  724. foreach $p (keys %programs) {
  725. print $FH "\t<program id='" . $p . "'>\n";
  726. print $FH "\t\t<title>" . &enc($programs{$p}{title}) . "</title>\n" if defined($programs{$p}{title});
  727. print $FH "\t\t<subtitle>" . &enc($programs{$p}{episode}) . "</subtitle>\n" if defined($programs{$p}{episode});
  728. print $FH "\t\t<description>" . &enc($programs{$p}{description}) . "</description>\n" if defined($programs{$p}{description});
  729.  
  730. if (defined($programs{$p}{movie_year})) {
  731. print $FH "\t\t<year>" . $programs{$p}{movie_year} . "</year>\n";
  732. } else { #Guess
  733. my $showType = "Series";
  734. if ($programs{$p}{title} =~ /Paid Programming/i) {
  735. $showType = "Paid Programming";
  736. }
  737. print $FH "\t\t<showType>$showType</showType>\n";
  738. print $FH "\t\t<series>EP" . substr($p,2,8) . "</series>\n";
  739. print $FH "\t\t<originalAirDate>" . &convOADXTVD($programs{$p}{originalAirDate}) . "</originalAirDate>\n" if defined($programs{$p}{originalAirDate});
  740. }
  741. print $FH "\t</program>\n";
  742. }
  743. print $FH "</programs>\n";
  744. }
  745.  
  746. sub printGenresXTVD {
  747. my $FH = shift;
  748. print $FH "<genres>\n";
  749. foreach $p (keys %programs) {
  750. if (defined($programs{$p}{genres}) && $programs{$p}{genres}{movie} != 1) {
  751. print $FH "\t<programGenre program='" . $p . "'>\n";
  752. foreach my $g (keys %{$programs{$p}{genres}}) {
  753. print $FH "\t\t<genre>\n";
  754. print $FH "\t\t\t<class>" . &enc(ucfirst($g)) . "</class>\n";
  755. print $FH "\t\t\t<relevance>0</relevance>\n";
  756. print $FH "\t\t</genre>\n";
  757. }
  758. print $FH "\t</programGenre>\n";
  759. }
  760. }
  761. print $FH "</genres>\n";
  762. }
  763.  
  764. sub loginTVG {
  765. $treq++;
  766. my $r = $ua->get($tvgurl . 'user/_modal/');
  767. if ($r->is_success) {
  768. my $str = $r->decoded_content;
  769. if ($str =~ /<input.+name=\"_token\".+?value=\"(.*?)\"/is) {
  770. $token = $1;
  771. if ($userEmail ne '' && $password ne '') {
  772. my $rc = 0;
  773. while ($rc++ < $retries) {
  774. my $r = $ua->post($tvgurl . 'user/attempt/',
  775. {
  776. _token => $token,
  777. email => $userEmail,
  778. password => $password,
  779. }, 'X-Requested-With' => 'XMLHttpRequest'
  780. );
  781.  
  782. $dc = Encode::encode('utf8', $r->decoded_content( raise_error => 1 ));
  783. if ($dc =~ /success/) {
  784. $ua->cookie_jar->scan(sub { if ($_[1] eq "ServiceID") { $zlineupId = $_[2]; }; });
  785. if (!defined($options{a})) {
  786. my $r = $ua->get($tvgurl . "user/favorites/?provider=$zlineupId",'X-Requested-With' => 'XMLHttpRequest');
  787. $dc = Encode::encode('utf8', $r->decoded_content( raise_error => 1 ));
  788. if ($dc =~ /\{\"code\":200/) {
  789. &parseTVGFavs($dc);
  790. }
  791. }
  792. return $dc;
  793. } else {
  794. &pout("[Attempt $rc] " . $dc . "\n");
  795. sleep ($sleeptime + 1);
  796. }
  797. }
  798. die "Failed to login within $retries retries.\n";
  799. }
  800. } else {
  801. die "Login token not found\n";
  802. }
  803. }
  804. }
  805.  
  806. sub loginZAP {
  807. my $rc = 0;
  808. while ($rc++ < $retries) {
  809. $treq++;
  810. my $r = $ua->post($urlRoot . 'api/user/login',
  811. {
  812. emailid => $userEmail, password => $password,
  813. usertype => '0', facebookuser =>'false',
  814. }
  815. );
  816.  
  817. $dc = Encode::encode('utf8', $r->decoded_content( raise_error => 1 ));
  818. if ($r->is_success) {
  819. my $t = decode_json($dc);
  820. $zapToken = $t->{'token'};
  821. my $prs = $t->{'properties'};
  822. $postalcode = $prs->{2002};
  823. $country = $prs->{2003};
  824. ($lineupId, $device) = split(/:/, $prs->{2004});
  825. if (!defined($options{a})) {
  826. my $r = $ua->post($urlRoot . "api/user/favorites", { token => $zapToken }, 'X-Requested-With' => 'XMLHttpRequest');
  827. $dc = Encode::encode('utf8', $r->decoded_content( raise_error => 1 ));
  828. if ($r->is_success) {
  829. &parseZFavs($dc);
  830. } else {
  831. &perr("FF" . $r->status_line . ": $dc\n");
  832. }
  833. }
  834. return $dc;
  835. } else {
  836. &pout("[Attempt $rc] " . $dc . "\n");
  837. sleep ($sleeptime + 1);
  838. }
  839. }
  840. die "Failed to login within $retries retries.\n";
  841. }
  842.  
  843. sub getZapGParams {
  844. my %hash = &getZapParams();
  845. $hash{country} = delete $hash{countryCode};
  846. return join("&", map { "$_=$hash{$_}" } keys %hash);
  847. }
  848.  
  849. sub getZapPParams {
  850. my %hash = &getZapParams();
  851. delete $hash{lineupId};
  852. return %hash;
  853. }
  854.  
  855. sub getZapParams {
  856. my %phash = ();
  857. if (defined($zlineupId) || defined($zipcode)) {
  858. $postalcode = $zipcode;
  859. $country = "USA";
  860. $country = "CAN" if ($zipcode =~ /[A-z]/);
  861. if ($zlineupId =~ /:/) {
  862. ($lineupId, $device) = split(/:/, $zlineupId);
  863. } else {
  864. $lineupId = $zlineupId;
  865. $device = "-";
  866. }
  867. $phash{postalCode} = $postalcode;
  868. } else {
  869. $phash{token} = &getZToken();
  870. }
  871. $phash{lineupId} = "$country-$lineupId-DEFAULT";
  872. $phash{postalCode} = $postalcode;
  873. $phash{countryCode} = $country;
  874. $phash{headendId} = $lineupId;
  875. $phash{device} = $device;
  876. $phash{aid} = 'gapzap';
  877. return %phash;
  878. }
  879.  
  880. sub login {
  881. if (!defined($userEmail) || $userEmail eq '' || !defined($password) || $password eq '') {
  882. if (!defined($zlineupId)) {
  883. die "Unable to login: Unspecified username or password.\n"
  884. }
  885. }
  886.  
  887. if (!defined($ua)) {
  888. $ua = LWP::UserAgent->new(ssl_opts => { verify_hostname => 0 }); # WIN
  889. $ua->cookie_jar(HTTP::Cookies->new);
  890. $ua->proxy(['http', 'https'], $proxy) if defined($proxy);
  891. $ua->agent('Mozilla/4.0');
  892. $ua->default_headers->push_header('Accept-Encoding' => 'gzip, deflate');
  893. }
  894.  
  895. if ($userEmail ne '' && $password ne '') {
  896. &pout("Logging in as \"$userEmail\" (" . localtime . ")\n");
  897. if (defined($options{z})) {
  898. &loginTVG();
  899. } else {
  900. &loginZAP();
  901. }
  902. } else {
  903. &pout("Connecting with lineupId \"$zlineupId\" (" . localtime . ")\n");
  904. }
  905. }
  906.  
  907. sub getURL {
  908. my $url = shift;
  909. &login() if !defined($ua);
  910.  
  911. my $rc = 0;
  912. while ($rc++ < $retries) {
  913. &pout("[$treq] Getting: $url\n");
  914. sleep $sleeptime; # do these rapid requests flood servers?
  915. $treq++;
  916. my $r = $ua->get($url);
  917. my $cl = length($r->content);
  918. $tb += $cl;
  919. my $dc = $r->decoded_content( raise_error => 1 );
  920. if ($r->is_success && $cl) {
  921. return $dc;
  922. } elsif ($r->code == 400 && $dc =~ /Invalid time stamp passed/) {
  923. &pout("$dc\n");
  924. &pout("Date not in range (reached zap2it limit), normal exit.\n");
  925. return "";
  926. } elsif ($r->code == 500 && $dc =~ /Could not load details/) {
  927. &pout("$dc\n");
  928. return "";
  929. } else {
  930. &perr("[Attempt $rc] $cl:" . $r->status_line . "\n");
  931. sleep ($sleeptime + 2);
  932. }
  933. }
  934. die "Failed to download within $retries retries.\n";
  935. }
  936.  
  937. sub wbf {
  938. my($f, $s) = @_;
  939. open(FO, ">$f") or die "Failed to open '$f': $!";
  940. binmode(FO);
  941. print FO $s;
  942. close(FO);
  943. }
  944.  
  945. sub unf {
  946. my $f = shift;
  947. unlink($f) or &perr("Failed to delete '$f': $!");
  948. }
  949.  
  950. sub copyLogo {
  951. my $key = shift;
  952. if (defined($iconDir) && defined($stations{$key}{logo})) {
  953. my $num = $stations{$key}{number};
  954. my $src = "$iconDir/" . $stations{$key}{logo} . $stations{$key}{logoExt};
  955. my $dest1 = "$iconDir/$num" . $stations{$key}{logoExt};
  956. my $dest2 = "$iconDir/$num " . $stations{$key}{name} . $stations{$key}{logoExt};
  957. copy($src, $dest1);
  958. copy($src, $dest2);
  959. }
  960. }
  961.  
  962. sub handleLogo {
  963. my $url = shift;
  964. if (! -d $iconDir) {
  965. mkdir($iconDir) or die "Can't mkdir: $!\n";
  966. }
  967. my $n; my $s; ($n,$_,$s) = fileparse($url, qr"\..*");
  968. $stations{$cs}{logo} = $n;
  969. $stations{$cs}{logoExt} = $s;
  970. $stations{$cs}{logoURL} = $url;
  971. my $f = $iconDir . "/" . $n . $s;
  972. if (! -e $f) { &wbf($f, &getURL($url)); }
  973. }
  974.  
  975. sub setOriginalAirDate {
  976. if (substr($cp,10,4) ne '0000') {
  977. if (!defined($programs{$cp}{originalAirDate})
  978. || ($schedule{$cs}{$sch}{time} < $programs{$cp}{originalAirDate})) {
  979. $programs{$cp}{originalAirDate} = $schedule{$cs}{$sch}{time};
  980. }
  981. }
  982. }
  983.  
  984. sub getZToken {
  985. &login() if (!defined($zapToken));
  986. return $zapToken;
  987. }
  988.  
  989. sub parseZFavs {
  990. my $buffer = shift;
  991. my $t = decode_json($buffer);
  992. if (defined($t->{'channels'})) {
  993. my $m = $t->{'channels'};
  994. foreach my $f (@{$m}) {
  995. if ($options{R}) {
  996. my $r = $ua->post($urlRoot . "api/user/ChannelAddtofav", { token => $zapToken, prgsvcid => $f, addToFav => "false" }, 'X-Requested-With' => 'XMLHttpRequest');
  997. if ($r->is_success) {
  998. &pout("Removed favorite $f\n");
  999. } else {
  1000. &perr("RF" . $r->status_line . "\n");
  1001. }
  1002. } else {
  1003. $zapFavorites{$f} = 1;
  1004. }
  1005. }
  1006. if ($options{R}) {
  1007. &pout("Removed favorites, exiting\n");
  1008. exit;
  1009. };
  1010. &pout("Lineup favorites: " . (keys %zapFavorites) . "\n");
  1011. }
  1012. }
  1013.  
  1014. sub parseTVGFavs {
  1015. my $buffer = shift;
  1016. my $t = decode_json($buffer);
  1017.  
  1018. if (defined($t->{'message'})) {
  1019. my $m = $t->{'message'};
  1020. foreach my $f (@{$m}) {
  1021. my $source = $f->{"source"};
  1022. my $channel = $f->{"channel"};
  1023. $tvgfavs{$channel} = $source;
  1024. }
  1025. &pout("Lineup $zlineupId favorites: " . (keys %tvgfavs) . "\n");
  1026. }
  1027. }
  1028.  
  1029. sub parseTVGIcons {
  1030. require GD;
  1031. $rc = Encode::encode('utf8', &getURL($tvgspritesurl . "$zlineupId\.css") );
  1032. if ($rc =~ /background-image:.+?url\((.+?)\)/) {
  1033. my $url = $tvgspritesurl . $1;
  1034.  
  1035. if (! -d $iconDir) {
  1036. mkdir($iconDir) or die "Can't mkdir: $!\n";
  1037. }
  1038.  
  1039. ($n,$_,$s) = fileparse($url, qr"\..*");
  1040. $f = $iconDir . "/sprites-" . $n . $s;
  1041. &wbf($f, &getURL($url));
  1042.  
  1043. GD::Image->trueColor(1);
  1044. $im = new GD::Image->new($f);
  1045.  
  1046. my $iconw = 30;
  1047. my $iconh = 20;
  1048. while ($rc =~ /listings-channel-icon-(.+?)\{.+?position:.*?\-(\d+).+?(\d+).*?\}/isg) {
  1049. my $cid = $1;
  1050. my $iconx = $2;
  1051. my $icony = $3;
  1052.  
  1053. my $icon = new GD::Image($iconw,$iconh);
  1054. $icon->alphaBlending(0);
  1055. $icon->saveAlpha(1);
  1056. $icon->copy($im, 0, 0, $iconx, $icony, $iconw, $iconh);
  1057.  
  1058. $stations{$cid}{logo} = "sprite-" . $cid;
  1059. $stations{$cid}{logoExt} = $s;
  1060.  
  1061. my $ifn = $iconDir . "/" . $stations{$cid}{logo} . $stations{$cid}{logoExt};
  1062. &wbf($ifn, $icon->png);
  1063. }
  1064. }
  1065. }
  1066.  
  1067. sub parseTVGD {
  1068. my $gz = gzopen(shift, "rb");
  1069. my $buffer;
  1070. $buffer .= $b while $gz->gzread($b, 65535) > 0;
  1071. $gz->gzclose();
  1072. my $t = decode_json($buffer);
  1073.  
  1074. if (defined($t->{'program'})) {
  1075. my $prog = $t->{'program'};
  1076. if (defined($prog->{'release_year'})) {
  1077. $programs{$cp}{movie_year} = $prog->{'release_year'};
  1078. }
  1079. if (defined($prog->{'rating'}) && !defined($programs{$cp}{rating})) {
  1080. $programs{$cp}{rating} = $prog->{'rating'} if $prog->{'rating'} ne 'NR';
  1081. }
  1082. }
  1083.  
  1084. if (defined($t->{'tvobject'})) {
  1085. my $tvo = $t->{'tvobject'};
  1086. if (defined($tvo->{'photos'})) {
  1087. my $photos = $tvo->{'photos'};
  1088. my %phash;
  1089. foreach $ph (@{$photos}) {
  1090. my $w = $ph->{'width'} * $ph->{'height'};
  1091. my $u = $ph->{'url'};
  1092. $phash{$w} = $u;
  1093. }
  1094. my $big = (sort {$b <=> $a} keys %phash)[0];
  1095. $programs{$cp}{imageUrl} = $phash{$big};
  1096. }
  1097. }
  1098. }
  1099.  
  1100. sub parseTVGGrid {
  1101. my $gz = gzopen(shift, "rb");
  1102. my $buffer;
  1103. $buffer .= $b while $gz->gzread($b, 65535) > 0;
  1104. $gz->gzclose();
  1105. my $t = decode_json($buffer);
  1106.  
  1107. foreach my $e (@{$t}) {
  1108. my $cjs = $e->{'Channel'};
  1109. $cs = $cjs->{'SourceId'};
  1110.  
  1111. if (%tvgfavs) {
  1112. if (defined($cjs->{'Number'}) && $cjs->{'Number'} ne '') {
  1113. my $n = $cjs->{'Number'};
  1114. if ($cs != $tvgfavs{$n}) {
  1115. next;
  1116. }
  1117. }
  1118. }
  1119.  
  1120. if (!defined($stations{$cs}{stnNum})) {
  1121. $stations{$cs}{stnNum} = $cs;
  1122. $stations{$cs}{number} = $cjs->{'Number'} if defined($cjs->{'Number'}) && $cjs->{'Number'} ne '';
  1123. $stations{$cs}{name} = $cjs->{'Name'};
  1124. if (defined($cjs->{'FullName'}) && $cjs->{'FullName'} ne $cjs->{'Name'}) {
  1125. if ($cjs->{'FullName'} ne '') {
  1126. $stations{$cs}{fullname} = $cjs->{'FullName'};
  1127. }
  1128. }
  1129.  
  1130. if (!defined($stations{$cs}{order})) {
  1131. if (defined($options{b})) {
  1132. $stations{$cs}{order} = $coNum++;
  1133. } else {
  1134. $stations{$cs}{order} = $stations{$cs}{number};
  1135. }
  1136. }
  1137. }
  1138.  
  1139. my $cps = $e->{'ProgramSchedules'};
  1140. foreach my $pe (@{$cps}) {
  1141. next if (!defined($pe->{'ProgramId'}));
  1142. $cp = $pe->{'ProgramId'};
  1143. my $catid = $pe->{'CatId'};
  1144.  
  1145. if ($catid == 1) { $programs{$cp}{genres}{movie} = 1 }
  1146. elsif ($catid == 2) { $programs{$cp}{genres}{sports} = 1 }
  1147. elsif ($catid == 3) { $programs{$cp}{genres}{family} = 1 }
  1148. elsif ($catid == 4) { $programs{$cp}{genres}{news} = 1 }
  1149. # 5 - 10?
  1150. # my $subcatid = $pe->{'SubCatId'};
  1151.  
  1152. my $ppid = $pe->{'ParentProgramId'};
  1153. if ((defined($ppid) && $ppid != 0)
  1154. || (defined($options{j}) && $catid != 1)) {
  1155. $programs{$cp}{genres}{series} = 9;
  1156. }
  1157.  
  1158. $programs{$cp}{title} = $pe->{'Title'};
  1159. $tba = 1 if $programs{$cp}{title} =~ /$sTBA/i;
  1160.  
  1161. if (defined($pe->{'EpisodeTitle'}) && $pe->{'EpisodeTitle'} ne '') {
  1162. $programs{$cp}{episode} = $pe->{'EpisodeTitle'};
  1163. $tba = 1 if $programs{$cp}{episode} =~ /$sTBA/i;
  1164. }
  1165.  
  1166. $programs{$cp}{description} = $pe->{'CopyText'} if defined($pe->{'CopyText'}) && $pe->{'CopyText'} ne '';
  1167. $programs{$cp}{rating} = $pe->{'Rating'} if defined($pe->{'Rating'}) && $pe->{'Rating'} ne '';
  1168.  
  1169. my $sch = $pe->{'StartTime'} * 1000;
  1170. $schedule{$cs}{$sch}{time} = $sch;
  1171. $schedule{$cs}{$sch}{endtime} = $pe->{'EndTime'} * 1000;
  1172. $schedule{$cs}{$sch}{program} = $cp;
  1173. $schedule{$cs}{$sch}{station} = $cs;
  1174.  
  1175. my $airat = $pe->{'AiringAttrib'};
  1176. if ($airat & 1) { $schedule{$cs}{$sch}{live} = 1 }
  1177. elsif ($airat & 4) { $schedule{$cs}{$sch}{new} = 1 }
  1178. # other bits?
  1179.  
  1180. my $tvo = $pe->{'TVObject'};
  1181. if (defined($tvo)) {
  1182. if (defined($tvo->{'SeasonNumber'}) && $tvo->{'SeasonNumber'} != 0) {
  1183. $programs{$cp}{seasonNum} = $tvo->{'SeasonNumber'};
  1184. if (defined($tvo->{'EpisodeNumber'}) && $tvo->{'EpisodeNumber'} != 0) {
  1185. $programs{$cp}{episodeNum} = $tvo->{'EpisodeNumber'};
  1186. }
  1187. }
  1188. if (defined($tvo->{'EpisodeAirDate'})) {
  1189. my $eaid = $tvo->{'EpisodeAirDate'}; # GMT @ 00:00:00
  1190. $eaid =~ tr/0-9//cd;
  1191. $programs{$cp}{originalAirDate} = $eaid if ($eaid ne '');
  1192. }
  1193. my $url;
  1194. if (defined($tvo->{'EpisodeSEOUrl'}) && $tvo->{'EpisodeSEOUrl'} ne '') {
  1195. $url = $tvo->{'EpisodeSEOUrl'};
  1196. } elsif(defined($tvo->{'SEOUrl'}) && $tvo->{'SEOUrl'} ne '') {
  1197. $url = $tvo->{'SEOUrl'};
  1198. $url = "/movies$url" if ($catid == 1 && $url !~ /movies/);
  1199. }
  1200. $programs{$cp}{url} = substr($tvgurl, 0, -1) . $url if defined($url);
  1201. }
  1202.  
  1203. if (defined($options{I})
  1204. || (defined($options{D}) && $programs{$cp}{genres}{movie})
  1205. || (defined($options{W}) && $programs{$cp}{genres}{movie}) ) {
  1206. &getDetails(\&parseTVGD, $cp, $tvgMapiRoot . "listings/details?program=$cp", "");
  1207. }
  1208. }
  1209. }
  1210. }
  1211.  
  1212. sub getDetails {
  1213. my ($func, $cp, $url, $prefix) = @_;
  1214. my $fn = "$cacheDir/$prefix$cp\.js\.gz";
  1215. if (! -e $fn) {
  1216. my $rs = &getURL($url);
  1217. if (length($rs)) {
  1218. $rc = Encode::encode('utf8', $rs);
  1219. &wbf($fn, Compress::Zlib::memGzip($rc));
  1220. }
  1221. }
  1222. if (-e $fn) {
  1223. my $l = length($prefix) ? $prefix : "D";
  1224. &pout("[$l] Parsing: $cp\n");
  1225. $func->($fn);
  1226. } else {
  1227. &pout("Skipping: $cp\n");
  1228. }
  1229. }
  1230.  
  1231. sub parseJSON {
  1232. my $gz = gzopen(shift, "rb");
  1233. my $buffer;
  1234. $buffer .= $b while $gz->gzread($b, 65535) > 0;
  1235. $gz->gzclose();
  1236. my $t = decode_json($buffer);
  1237.  
  1238. my $sts = $t->{'channels'};
  1239. foreach $s (@$sts) {
  1240.  
  1241. if (defined($s->{'id'})) {
  1242. my $xyz = scalar(keys %zapFavorites);
  1243. if (!$allChan && scalar(keys %zapFavorites)) {
  1244. next unless $zapFavorites{$s->{channelId}};
  1245. }
  1246.  
  1247. $cs = $s->{'id'};
  1248. $stations{$cs}{stnNum} = $cs;
  1249. $stations{$cs}{name} = $s->{'callSign'};
  1250. $stations{$cs}{number} = $s->{'channelNo'};
  1251. $stations{$cs}{number} =~ s/^0+//g;
  1252.  
  1253. if (!defined($stations{$cs}{order})) {
  1254. if (defined($options{b})) {
  1255. $stations{$cs}{order} = $coNum++;
  1256. } else {
  1257. $stations{$cs}{order} = $stations{$cs}{number};
  1258. }
  1259. }
  1260.  
  1261. if ($s->{'thumbnail'} ne '') {
  1262. my $url = $s->{'thumbnail'};
  1263. $url =~ s/\?.*//; # remove size
  1264. if ($url !~ /^http/) {
  1265. $url = "https:" . $url;
  1266. }
  1267. $stations{$cs}{logoURL} = $url;
  1268. &handleLogo($url) if defined($iconDir);
  1269. }
  1270.  
  1271. my $events = $s->{'events'};
  1272. foreach $e (@$events) {
  1273. my $program = $e->{'program'};
  1274. $cp = $program->{'id'};
  1275. $programs{$cp}{title} = $program->{'title'};
  1276. $tba = 1 if $programs{$cp}{title} =~ /$sTBA/i;
  1277. $programs{$cp}{episode} = $program->{'episodeTitle'} if ($program->{'episodeTitle'} ne '');
  1278. $programs{$cp}{description} = $program->{'shortDesc'} if ($program->{'shortDesc'} ne '');
  1279. $programs{$cp}{duration} = $e->{duration} if ($e->{duration} > 0);
  1280. $programs{$cp}{movie_year} = $program->{releaseYear} if ($program->{releaseYear} ne '');
  1281. $programs{$cp}{seasonNum} = $program->{season} if ($program->{'season'} ne '');
  1282. if ($program->{'episode'} ne '') {
  1283. $programs{$cp}{episodeNum} = $program->{episode};
  1284. }
  1285. if ($e->{'thumbnail'} ne '') {
  1286. my $turl = $urlAssets;
  1287. $turl .= $e->{'thumbnail'} . ".jpg";
  1288. $programs{$cp}{imageUrl} = $turl;
  1289. }
  1290. if ($program->{'seriesId'} ne '' && $program->{'tmsId'} ne '') {
  1291. $programs{$cp}{url} = $urlRoot . "/overview.html?programSeriesId="
  1292. . $program->{seriesId} . "&tmsId=" . $program->{tmsId};
  1293. }
  1294.  
  1295. $sch = str2time($e->{'startTime'}) * 1000;
  1296. $schedule{$cs}{$sch}{time} = $sch;
  1297. $schedule{$cs}{$sch}{endTime} = str2time($e->{'endTime'}) * 1000;
  1298. $schedule{$cs}{$sch}{program} = $cp;
  1299. $schedule{$cs}{$sch}{station} = $cs;
  1300.  
  1301. if ($e->{'filter'}) {
  1302. my $genres = $e->{'filter'};
  1303. foreach $g (@{$genres}) {
  1304. $g =~ s/filter-//;
  1305. ${$programs{$cp}{genres}}{$g} = 1;
  1306. }
  1307. }
  1308.  
  1309. $programs{$cp}{rating} = $e->{rating} if ($e->{rating} ne '');
  1310.  
  1311. if ($e->{'tags'}) {
  1312. my $tags = $e->{'tags'};
  1313. if (grep $_ eq 'CC', @{$tags}) {
  1314. $schedule{$cs}{$sch}{cc} = 1
  1315. }
  1316. }
  1317.  
  1318. if ($e->{'flag'}) {
  1319. my $flags = $e->{'flag'};
  1320. if (grep $_ eq 'New', @{$flags}) {
  1321. $schedule{$cs}{$sch}{new} = 'New'
  1322. &setOriginalAirDate();
  1323. }
  1324. if (grep $_ eq 'Live', @{$flags}) {
  1325. $schedule{$cs}{$sch}{live} = 'Live'
  1326. &setOriginalAirDate(); # live to tape?
  1327. }
  1328. if (grep $_ eq 'Premiere', @{$flags}) {
  1329. $schedule{$cs}{$sch}{premiere} = 'Premiere';
  1330. }
  1331. if (grep $_ eq 'Finale', @{$flags}) {
  1332. $schedule{$cs}{$sch}{finale} = 'Finale';
  1333. }
  1334. }
  1335.  
  1336. if ($options{D} && !$program->{isGeneric}) {
  1337. &postJSONO($cp, $program->{seriesId});
  1338. }
  1339.  
  1340. }
  1341. }
  1342. }
  1343. return 0;
  1344. }
  1345.  
  1346. sub postJSONO {
  1347. my ($cp, $sid) = @_;
  1348. my $fn = "$cacheDir/O$cp\.js\.gz";
  1349.  
  1350. if (! -e $fn && defined($sidCache{$sid}) && -e $sidCache{$sid}) {
  1351. copy($sidCache{$sid}, $fn);
  1352. }
  1353. if (! -e $fn) {
  1354. my $url = $urlRoot . 'api/program/overviewDetails';
  1355. &pout("[$treq] Post $sid: $url\n");
  1356. $treq++;
  1357. my %phash = &getZapPParams();
  1358. $phash{programSeriesID} = $sid;
  1359. $phash{'clickstream[FromPage]'} = 'TV%20Grid';
  1360. my $r = $ua->post($url, \%phash, 'X-Requested-With' => 'XMLHttpRequest');
  1361. my $cl = length($r->content);
  1362. $tb += $cl;
  1363. if ($r->is_success) {
  1364. $dc = Encode::encode('utf8', $r->decoded_content( raise_error => 1 ));
  1365. &wbf($fn, Compress::Zlib::memGzip($dc));
  1366. $sidCache{$sid} = $fn;
  1367. } else {
  1368. &perr($id . " :" . $r->status_line);
  1369. }
  1370. }
  1371. if (-e $fn) {
  1372. &pout("[D] Parsing: $cp\n");
  1373. my $gz = gzopen($fn, "rb");
  1374. my $buffer;
  1375. $buffer .= $b while $gz->gzread($b, 65535) > 0;
  1376. $gz->gzclose();
  1377. my $t = decode_json($buffer);
  1378.  
  1379. foreach my $sg (split(/\|/, $t->{seriesGenres})) {
  1380. ${$programs{$cp}{genres}}{lc($sg)} = 1;
  1381. }
  1382.  
  1383. my $i = 1;
  1384. foreach my $c (@{$t->{'overviewTab'}->{cast}}) {
  1385. my $n = $c->{name};
  1386. my $cn = $c->{characterName};
  1387. my $cr = lc($c->{role});
  1388.  
  1389. if ($cr eq 'host') {
  1390. ${$programs{$cp}{presenter}}{$n} = $i++;
  1391. } else {
  1392. ${$programs{$cp}{actor}}{$n} = $i++;
  1393. ${$programs{$cp}{role}}{$n} = $cn if length($cn);
  1394. }
  1395. }
  1396. $i = 1;
  1397. foreach my $c (@{$t->{'overviewTab'}->{crew}}) {
  1398. my $n = $c->{name};
  1399. my $cr = lc($c->{role});
  1400. if ($cr =~ /producer/) {
  1401. ${$programs{$cp}{producer}}{$n} = $i++;
  1402. } elsif ($cr =~ /director/) {
  1403. ${$programs{$cp}{director}}{$n} = $i++;
  1404. } elsif ($cr =~ /writer/) {
  1405. ${$programs{$cp}{writer}}{$n} = $i++;
  1406. }
  1407. }
  1408. if (!defined($programs{$cp}{imageUrl}) && $t->{seriesImage} ne '') {
  1409. my $turl = $urlAssets;
  1410. $turl .= $t->{seriesImage} . ".jpg";
  1411. $programs{$cp}{imageUrl} = $turl;
  1412. }
  1413. if ($cp =~ /^MV|^SH/ && length($t->{seriesDescription}) > length($programs{$cp}{description})) {
  1414. $programs{$cp}{description} = $t->{seriesDescription};
  1415. }
  1416. if ($cp =~ /^EP/) { # GMT @ 00:00:00
  1417. my $ue = $t->{overviewTab}->{upcomingEpisode};
  1418. if (defined($ue) && lc($ue->{tmsID}) eq lc($cp)) {
  1419. $oad = str2time($ue->{originalAirDate}) ;
  1420. if ($oad > 0) { #'1000-01-01T00:00Z'
  1421. $oad *= 1000;
  1422. $programs{$cp}{originalAirDate} = $oad;
  1423. }
  1424. } else {
  1425. foreach my $ue (@{$t->{upcomingEpisodeTab}}) {
  1426. if (lc($ue->{tmsID}) eq lc($cp)) {
  1427. $oad = str2time($ue->{originalAirDate}) ;
  1428. if ($oad > 0) {
  1429. $oad *= 1000;
  1430. $programs{$cp}{originalAirDate} = $oad;
  1431. last;
  1432. }
  1433. }
  1434. }
  1435. }
  1436. }
  1437. } else {
  1438. &pout("Skipping: $sid\n");
  1439. }
  1440. }
  1441.  
  1442. sub hourToMillis {
  1443. ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(time);
  1444. if ($start == 0) {
  1445. $hour = int($hour/$gridHours) * $gridHours;
  1446. } else {
  1447. $hour = 0;
  1448. }
  1449. $t = timegm(0,0,$hour,$mday,$mon,$year);
  1450. $t = $t - (&tz_offset * 3600) if !defined($options{g});
  1451. ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = gmtime($t);
  1452. $t = timegm($sec, $min, $hour,$mday,$mon,$year);
  1453. return $t . "000";
  1454. }
  1455.  
  1456. sub tz_offset {
  1457. my $n = defined $_[0] ? $_[0] : time;
  1458. my ($lm, $lh, $ly, $lyd) = (localtime $n)[1, 2, 5, 7];
  1459. my ($gm, $gh, $gy, $gyd) = (gmtime $n)[1, 2, 5, 7];
  1460. ($lm - $gm)/60 + $lh - $gh + 24 * ($ly - $gy || $lyd - $gyd)
  1461. }
  1462.  
  1463. sub timezone {
  1464. my $tztime = defined $_[0] ? &_rtrim3(shift) : time;
  1465. my $os = sprintf "%.1f", (timegm(localtime($tztime)) - $tztime) / 3600;
  1466. my $mins = sprintf "%02d", abs( $os - int($os) ) * 60;
  1467. return sprintf("%+03d", int($os)) . $mins;
  1468. }
  1469.  
  1470. sub max ($$) { $_[$_[0] < $_[1]] }
  1471. sub min ($$) { $_[$_[0] > $_[1]] }
  1472.  
  1473. sub HELP_MESSAGE {
  1474. print <<END;
  1475. zap2xml <zap2xml\@gmail.com> (2018-01-08)
  1476. -u <username>
  1477. -p <password>
  1478. -d <# of days> (default = $days)
  1479. -n <# of no-cache days> (from end) (default = $ncdays)
  1480. -N <# of no-cache days> (from start) (default = $ncsdays)
  1481. -B <no-cache day>
  1482. -s <start day offset> (default = $start)
  1483. -o <output xml filename> (default = "$outFile")
  1484. -c <cacheDirectory> (default = "$cacheDir")
  1485. -l <lang> (default = "$lang")
  1486. -i <iconDirectory> (default = don't download channel icons)
  1487. -m <#> = offset program times by # minutes (better to use TZ env var)
  1488. -b = retain website channel order
  1489. -x = output XTVD xml file format (default = XMLTV)
  1490. -w = wait on exit (require keypress before exiting)
  1491. -q = quiet (no status output)
  1492. -r <# of connection retries before failure> (default = $retries, max 20)
  1493. -e = hex encode entities (html special characters like accents)
  1494. -E "amp apos quot lt gt" = selectively encode standard XML entities
  1495. -F = output channel names first (rather than "number name")
  1496. -O = use old tv_grab_na style channel ids (C###nnnn.zap2it.com)
  1497. -A "new live" = append " *" to program titles that are "new" and/or "live"
  1498. -M = copy movie_year to empty movie sub-title tags
  1499. -U = UTF-8 encoding (default = "ISO-8859-1")
  1500. -L = output "<live />" tag (not part of xmltv.dtd)
  1501. -T = don't cache files containing programs with "$sTBA" titles
  1502. -P <http://proxyhost:port> = to use an http proxy
  1503. -C <configuration file> (default = "$confFile")
  1504. -S <#seconds> = sleep between requests to prevent flooding of server
  1505. -D = include details = 1 extra http request per program!
  1506. -I = include icons (image URLs) - 1 extra http request per program!
  1507. -J <xmltv> = include xmltv file in output
  1508. -Y <lineupId> (if not using username/password)
  1509. -Z <zipcode> (if not using username/password)
  1510. -z = use tvguide.com instead of zap2it.com
  1511. -a = output all channels (not just favorites)
  1512. -j = add "series" category to all non-movie programs
  1513. END
  1514. sleep(5) if ($^O eq 'MSWin32');
  1515. exit 0;
  1516. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement