├── Dockerfile ├── LICENSE ├── README.md ├── docker-compose.yml ├── entry.sh └── zap2xml.pl /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM alpine:3 2 | 3 | ENV USERNAME=none 4 | ENV PASSWORD=none 5 | ENV OPT_ARGS= 6 | 7 | ENV USERNAME2=none 8 | ENV PASSWORD2=none 9 | ENV OPT_ARGS2= 10 | 11 | ENV XMLTV_FILENAME=xmltv.xml 12 | 13 | # Wait 12 Hours after run 14 | ENV SLEEPTIME=43200 15 | 16 | RUN echo "@edge http://nl.alpinelinux.org/alpine/edge/main" >> /etc/apk/repositories 17 | RUN apk add --no-cache perl@edge perl-html-parser@edge perl-http-cookies@edge \ 18 | perl-lwp-useragent-determined@edge perl-json@edge perl-json-xs@edge \ 19 | perl-lwp-protocol-https@edge perl-uri@edge ca-certificates@edge \ 20 | perl-net-libidn@edge perl-net-ssleay@edge perl-io-socket-ssl@edge \ 21 | perl-libwww@edge perl-mozilla-ca@edge perl-net-http@edge 22 | RUN apk add --no-cache xmltv@edge 23 | 24 | VOLUME /data 25 | ADD zap2xml.pl /zap2xml.pl 26 | ADD entry.sh /entry.sh 27 | RUN chmod 755 /entry.sh /zap2xml.pl 28 | 29 | CMD ["/entry.sh"] 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | The GNU General Public License is a free, copyleft license for 11 | software and other kinds of works. 12 | 13 | The licenses for most software and other practical works are designed 14 | to take away your freedom to share and change the works. By contrast, 15 | the GNU General Public License is intended to guarantee your freedom to 16 | share and change all versions of a program--to make sure it remains free 17 | software for all its users. We, the Free Software Foundation, use the 18 | GNU General Public License for most of our software; it applies also to 19 | any other work released this way by its authors. You can apply it to 20 | your programs, too. 21 | 22 | When we speak of free software, we are referring to freedom, not 23 | price. Our General Public Licenses are designed to make sure that you 24 | have the freedom to distribute copies of free software (and charge for 25 | them if you wish), that you receive source code or can get it if you 26 | want it, that you can change the software or use pieces of it in new 27 | free programs, and that you know you can do these things. 28 | 29 | To protect your rights, we need to prevent others from denying you 30 | these rights or asking you to surrender the rights. Therefore, you have 31 | certain responsibilities if you distribute copies of the software, or if 32 | you modify it: responsibilities to respect the freedom of others. 33 | 34 | For example, if you distribute copies of such a program, whether 35 | gratis or for a fee, you must pass on to the recipients the same 36 | freedoms that you received. You must make sure that they, too, receive 37 | or can get the source code. And you must show them these terms so they 38 | know their rights. 39 | 40 | Developers that use the GNU GPL protect your rights with two steps: 41 | (1) assert copyright on the software, and (2) offer you this License 42 | giving you legal permission to copy, distribute and/or modify it. 43 | 44 | For the developers' and authors' protection, the GPL clearly explains 45 | that there is no warranty for this free software. For both users' and 46 | authors' sake, the GPL requires that modified versions be marked as 47 | changed, so that their problems will not be attributed erroneously to 48 | authors of previous versions. 49 | 50 | Some devices are designed to deny users access to install or run 51 | modified versions of the software inside them, although the manufacturer 52 | can do so. This is fundamentally incompatible with the aim of 53 | protecting users' freedom to change the software. The systematic 54 | pattern of such abuse occurs in the area of products for individuals to 55 | use, which is precisely where it is most unacceptable. Therefore, we 56 | have designed this version of the GPL to prohibit the practice for those 57 | products. If such problems arise substantially in other domains, we 58 | stand ready to extend this provision to those domains in future versions 59 | of the GPL, as needed to protect the freedom of users. 60 | 61 | Finally, every program is threatened constantly by software patents. 62 | States should not allow patents to restrict development and use of 63 | software on general-purpose computers, but in those that do, we wish to 64 | avoid the special danger that patents applied to a free program could 65 | make it effectively proprietary. To prevent this, the GPL assures that 66 | patents cannot be used to render the program non-free. 67 | 68 | The precise terms and conditions for copying, distribution and 69 | modification follow. 70 | 71 | TERMS AND CONDITIONS 72 | 73 | 0. Definitions. 74 | 75 | "This License" refers to version 3 of the GNU General Public License. 76 | 77 | "Copyright" also means copyright-like laws that apply to other kinds of 78 | works, such as semiconductor masks. 79 | 80 | "The Program" refers to any copyrightable work licensed under this 81 | License. Each licensee is addressed as "you". "Licensees" and 82 | "recipients" may be individuals or organizations. 83 | 84 | To "modify" a work means to copy from or adapt all or part of the work 85 | in a fashion requiring copyright permission, other than the making of an 86 | exact copy. The resulting work is called a "modified version" of the 87 | earlier work or a work "based on" the earlier work. 88 | 89 | A "covered work" means either the unmodified Program or a work based 90 | on the Program. 91 | 92 | To "propagate" a work means to do anything with it that, without 93 | permission, would make you directly or secondarily liable for 94 | infringement under applicable copyright law, except executing it on a 95 | computer or modifying a private copy. Propagation includes copying, 96 | distribution (with or without modification), making available to the 97 | public, and in some countries other activities as well. 98 | 99 | To "convey" a work means any kind of propagation that enables other 100 | parties to make or receive copies. Mere interaction with a user through 101 | a computer network, with no transfer of a copy, is not conveying. 102 | 103 | An interactive user interface displays "Appropriate Legal Notices" 104 | to the extent that it includes a convenient and prominently visible 105 | feature that (1) displays an appropriate copyright notice, and (2) 106 | tells the user that there is no warranty for the work (except to the 107 | extent that warranties are provided), that licensees may convey the 108 | work under this License, and how to view a copy of this License. If 109 | the interface presents a list of user commands or options, such as a 110 | menu, a prominent item in the list meets this criterion. 111 | 112 | 1. Source Code. 113 | 114 | The "source code" for a work means the preferred form of the work 115 | for making modifications to it. "Object code" means any non-source 116 | form of a work. 117 | 118 | A "Standard Interface" means an interface that either is an official 119 | standard defined by a recognized standards body, or, in the case of 120 | interfaces specified for a particular programming language, one that 121 | is widely used among developers working in that language. 122 | 123 | The "System Libraries" of an executable work include anything, other 124 | than the work as a whole, that (a) is included in the normal form of 125 | packaging a Major Component, but which is not part of that Major 126 | Component, and (b) serves only to enable use of the work with that 127 | Major Component, or to implement a Standard Interface for which an 128 | implementation is available to the public in source code form. A 129 | "Major Component", in this context, means a major essential component 130 | (kernel, window system, and so on) of the specific operating system 131 | (if any) on which the executable work runs, or a compiler used to 132 | produce the work, or an object code interpreter used to run it. 133 | 134 | The "Corresponding Source" for a work in object code form means all 135 | the source code needed to generate, install, and (for an executable 136 | work) run the object code and to modify the work, including scripts to 137 | control those activities. However, it does not include the work's 138 | System Libraries, or general-purpose tools or generally available free 139 | programs which are used unmodified in performing those activities but 140 | which are not part of the work. For example, Corresponding Source 141 | includes interface definition files associated with source files for 142 | the work, and the source code for shared libraries and dynamically 143 | linked subprograms that the work is specifically designed to require, 144 | such as by intimate data communication or control flow between those 145 | subprograms and other parts of the work. 146 | 147 | The Corresponding Source need not include anything that users 148 | can regenerate automatically from other parts of the Corresponding 149 | Source. 150 | 151 | The Corresponding Source for a work in source code form is that 152 | same work. 153 | 154 | 2. Basic Permissions. 155 | 156 | All rights granted under this License are granted for the term of 157 | copyright on the Program, and are irrevocable provided the stated 158 | conditions are met. This License explicitly affirms your unlimited 159 | permission to run the unmodified Program. The output from running a 160 | covered work is covered by this License only if the output, given its 161 | content, constitutes a covered work. This License acknowledges your 162 | rights of fair use or other equivalent, as provided by copyright law. 163 | 164 | You may make, run and propagate covered works that you do not 165 | convey, without conditions so long as your license otherwise remains 166 | in force. You may convey covered works to others for the sole purpose 167 | of having them make modifications exclusively for you, or provide you 168 | with facilities for running those works, provided that you comply with 169 | the terms of this License in conveying all material for which you do 170 | not control copyright. Those thus making or running the covered works 171 | for you must do so exclusively on your behalf, under your direction 172 | and control, on terms that prohibit them from making any copies of 173 | your copyrighted material outside their relationship with you. 174 | 175 | Conveying under any other circumstances is permitted solely under 176 | the conditions stated below. Sublicensing is not allowed; section 10 177 | makes it unnecessary. 178 | 179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 180 | 181 | No covered work shall be deemed part of an effective technological 182 | measure under any applicable law fulfilling obligations under article 183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 184 | similar laws prohibiting or restricting circumvention of such 185 | measures. 186 | 187 | When you convey a covered work, you waive any legal power to forbid 188 | circumvention of technological measures to the extent such circumvention 189 | is effected by exercising rights under this License with respect to 190 | the covered work, and you disclaim any intention to limit operation or 191 | modification of the work as a means of enforcing, against the work's 192 | users, your or third parties' legal rights to forbid circumvention of 193 | technological measures. 194 | 195 | 4. Conveying Verbatim Copies. 196 | 197 | You may convey verbatim copies of the Program's source code as you 198 | receive it, in any medium, provided that you conspicuously and 199 | appropriately publish on each copy an appropriate copyright notice; 200 | keep intact all notices stating that this License and any 201 | non-permissive terms added in accord with section 7 apply to the code; 202 | keep intact all notices of the absence of any warranty; and give all 203 | recipients a copy of this License along with the Program. 204 | 205 | You may charge any price or no price for each copy that you convey, 206 | and you may offer support or warranty protection for a fee. 207 | 208 | 5. Conveying Modified Source Versions. 209 | 210 | You may convey a work based on the Program, or the modifications to 211 | produce it from the Program, in the form of source code under the 212 | terms of section 4, provided that you also meet all of these conditions: 213 | 214 | a) The work must carry prominent notices stating that you modified 215 | it, and giving a relevant date. 216 | 217 | b) The work must carry prominent notices stating that it is 218 | released under this License and any conditions added under section 219 | 7. This requirement modifies the requirement in section 4 to 220 | "keep intact all notices". 221 | 222 | c) You must license the entire work, as a whole, under this 223 | License to anyone who comes into possession of a copy. This 224 | License will therefore apply, along with any applicable section 7 225 | additional terms, to the whole of the work, and all its parts, 226 | regardless of how they are packaged. This License gives no 227 | permission to license the work in any other way, but it does not 228 | invalidate such permission if you have separately received it. 229 | 230 | d) If the work has interactive user interfaces, each must display 231 | Appropriate Legal Notices; however, if the Program has interactive 232 | interfaces that do not display Appropriate Legal Notices, your 233 | work need not make them do so. 234 | 235 | A compilation of a covered work with other separate and independent 236 | works, which are not by their nature extensions of the covered work, 237 | and which are not combined with it such as to form a larger program, 238 | in or on a volume of a storage or distribution medium, is called an 239 | "aggregate" if the compilation and its resulting copyright are not 240 | used to limit the access or legal rights of the compilation's users 241 | beyond what the individual works permit. Inclusion of a covered work 242 | in an aggregate does not cause this License to apply to the other 243 | parts of the aggregate. 244 | 245 | 6. Conveying Non-Source Forms. 246 | 247 | You may convey a covered work in object code form under the terms 248 | of sections 4 and 5, provided that you also convey the 249 | machine-readable Corresponding Source under the terms of this License, 250 | in one of these ways: 251 | 252 | a) Convey the object code in, or embodied in, a physical product 253 | (including a physical distribution medium), accompanied by the 254 | Corresponding Source fixed on a durable physical medium 255 | customarily used for software interchange. 256 | 257 | b) Convey the object code in, or embodied in, a physical product 258 | (including a physical distribution medium), accompanied by a 259 | written offer, valid for at least three years and valid for as 260 | long as you offer spare parts or customer support for that product 261 | model, to give anyone who possesses the object code either (1) a 262 | copy of the Corresponding Source for all the software in the 263 | product that is covered by this License, on a durable physical 264 | medium customarily used for software interchange, for a price no 265 | more than your reasonable cost of physically performing this 266 | conveying of source, or (2) access to copy the 267 | Corresponding Source from a network server at no charge. 268 | 269 | c) Convey individual copies of the object code with a copy of the 270 | written offer to provide the Corresponding Source. This 271 | alternative is allowed only occasionally and noncommercially, and 272 | only if you received the object code with such an offer, in accord 273 | with subsection 6b. 274 | 275 | d) Convey the object code by offering access from a designated 276 | place (gratis or for a charge), and offer equivalent access to the 277 | Corresponding Source in the same way through the same place at no 278 | further charge. You need not require recipients to copy the 279 | Corresponding Source along with the object code. If the place to 280 | copy the object code is a network server, the Corresponding Source 281 | may be on a different server (operated by you or a third party) 282 | that supports equivalent copying facilities, provided you maintain 283 | clear directions next to the object code saying where to find the 284 | Corresponding Source. Regardless of what server hosts the 285 | Corresponding Source, you remain obligated to ensure that it is 286 | available for as long as needed to satisfy these requirements. 287 | 288 | e) Convey the object code using peer-to-peer transmission, provided 289 | you inform other peers where the object code and Corresponding 290 | Source of the work are being offered to the general public at no 291 | charge under subsection 6d. 292 | 293 | A separable portion of the object code, whose source code is excluded 294 | from the Corresponding Source as a System Library, need not be 295 | included in conveying the object code work. 296 | 297 | A "User Product" is either (1) a "consumer product", which means any 298 | tangible personal property which is normally used for personal, family, 299 | or household purposes, or (2) anything designed or sold for incorporation 300 | into a dwelling. In determining whether a product is a consumer product, 301 | doubtful cases shall be resolved in favor of coverage. For a particular 302 | product received by a particular user, "normally used" refers to a 303 | typical or common use of that class of product, regardless of the status 304 | of the particular user or of the way in which the particular user 305 | actually uses, or expects or is expected to use, the product. A product 306 | is a consumer product regardless of whether the product has substantial 307 | commercial, industrial or non-consumer uses, unless such uses represent 308 | the only significant mode of use of the product. 309 | 310 | "Installation Information" for a User Product means any methods, 311 | procedures, authorization keys, or other information required to install 312 | and execute modified versions of a covered work in that User Product from 313 | a modified version of its Corresponding Source. The information must 314 | suffice to ensure that the continued functioning of the modified object 315 | code is in no case prevented or interfered with solely because 316 | modification has been made. 317 | 318 | If you convey an object code work under this section in, or with, or 319 | specifically for use in, a User Product, and the conveying occurs as 320 | part of a transaction in which the right of possession and use of the 321 | User Product is transferred to the recipient in perpetuity or for a 322 | fixed term (regardless of how the transaction is characterized), the 323 | Corresponding Source conveyed under this section must be accompanied 324 | by the Installation Information. But this requirement does not apply 325 | if neither you nor any third party retains the ability to install 326 | modified object code on the User Product (for example, the work has 327 | been installed in ROM). 328 | 329 | The requirement to provide Installation Information does not include a 330 | requirement to continue to provide support service, warranty, or updates 331 | for a work that has been modified or installed by the recipient, or for 332 | the User Product in which it has been modified or installed. Access to a 333 | network may be denied when the modification itself materially and 334 | adversely affects the operation of the network or violates the rules and 335 | protocols for communication across the network. 336 | 337 | Corresponding Source conveyed, and Installation Information provided, 338 | in accord with this section must be in a format that is publicly 339 | documented (and with an implementation available to the public in 340 | source code form), and must require no special password or key for 341 | unpacking, reading or copying. 342 | 343 | 7. Additional Terms. 344 | 345 | "Additional permissions" are terms that supplement the terms of this 346 | License by making exceptions from one or more of its conditions. 347 | Additional permissions that are applicable to the entire Program shall 348 | be treated as though they were included in this License, to the extent 349 | that they are valid under applicable law. If additional permissions 350 | apply only to part of the Program, that part may be used separately 351 | under those permissions, but the entire Program remains governed by 352 | this License without regard to the additional permissions. 353 | 354 | When you convey a copy of a covered work, you may at your option 355 | remove any additional permissions from that copy, or from any part of 356 | it. (Additional permissions may be written to require their own 357 | removal in certain cases when you modify the work.) You may place 358 | additional permissions on material, added by you to a covered work, 359 | for which you have or can give appropriate copyright permission. 360 | 361 | Notwithstanding any other provision of this License, for material you 362 | add to a covered work, you may (if authorized by the copyright holders of 363 | that material) supplement the terms of this License with terms: 364 | 365 | a) Disclaiming warranty or limiting liability differently from the 366 | terms of sections 15 and 16 of this License; or 367 | 368 | b) Requiring preservation of specified reasonable legal notices or 369 | author attributions in that material or in the Appropriate Legal 370 | Notices displayed by works containing it; or 371 | 372 | c) Prohibiting misrepresentation of the origin of that material, or 373 | requiring that modified versions of such material be marked in 374 | reasonable ways as different from the original version; or 375 | 376 | d) Limiting the use for publicity purposes of names of licensors or 377 | authors of the material; or 378 | 379 | e) Declining to grant rights under trademark law for use of some 380 | trade names, trademarks, or service marks; or 381 | 382 | f) Requiring indemnification of licensors and authors of that 383 | material by anyone who conveys the material (or modified versions of 384 | it) with contractual assumptions of liability to the recipient, for 385 | any liability that these contractual assumptions directly impose on 386 | those licensors and authors. 387 | 388 | All other non-permissive additional terms are considered "further 389 | restrictions" within the meaning of section 10. If the Program as you 390 | received it, or any part of it, contains a notice stating that it is 391 | governed by this License along with a term that is a further 392 | restriction, you may remove that term. If a license document contains 393 | a further restriction but permits relicensing or conveying under this 394 | License, you may add to a covered work material governed by the terms 395 | of that license document, provided that the further restriction does 396 | not survive such relicensing or conveying. 397 | 398 | If you add terms to a covered work in accord with this section, you 399 | must place, in the relevant source files, a statement of the 400 | additional terms that apply to those files, or a notice indicating 401 | where to find the applicable terms. 402 | 403 | Additional terms, permissive or non-permissive, may be stated in the 404 | form of a separately written license, or stated as exceptions; 405 | the above requirements apply either way. 406 | 407 | 8. Termination. 408 | 409 | You may not propagate or modify a covered work except as expressly 410 | provided under this License. Any attempt otherwise to propagate or 411 | modify it is void, and will automatically terminate your rights under 412 | this License (including any patent licenses granted under the third 413 | paragraph of section 11). 414 | 415 | However, if you cease all violation of this License, then your 416 | license from a particular copyright holder is reinstated (a) 417 | provisionally, unless and until the copyright holder explicitly and 418 | finally terminates your license, and (b) permanently, if the copyright 419 | holder fails to notify you of the violation by some reasonable means 420 | prior to 60 days after the cessation. 421 | 422 | Moreover, your license from a particular copyright holder is 423 | reinstated permanently if the copyright holder notifies you of the 424 | violation by some reasonable means, this is the first time you have 425 | received notice of violation of this License (for any work) from that 426 | copyright holder, and you cure the violation prior to 30 days after 427 | your receipt of the notice. 428 | 429 | Termination of your rights under this section does not terminate the 430 | licenses of parties who have received copies or rights from you under 431 | this License. If your rights have been terminated and not permanently 432 | reinstated, you do not qualify to receive new licenses for the same 433 | material under section 10. 434 | 435 | 9. Acceptance Not Required for Having Copies. 436 | 437 | You are not required to accept this License in order to receive or 438 | run a copy of the Program. Ancillary propagation of a covered work 439 | occurring solely as a consequence of using peer-to-peer transmission 440 | to receive a copy likewise does not require acceptance. However, 441 | nothing other than this License grants you permission to propagate or 442 | modify any covered work. These actions infringe copyright if you do 443 | not accept this License. Therefore, by modifying or propagating a 444 | covered work, you indicate your acceptance of this License to do so. 445 | 446 | 10. Automatic Licensing of Downstream Recipients. 447 | 448 | Each time you convey a covered work, the recipient automatically 449 | receives a license from the original licensors, to run, modify and 450 | propagate that work, subject to this License. You are not responsible 451 | for enforcing compliance by third parties with this License. 452 | 453 | An "entity transaction" is a transaction transferring control of an 454 | organization, or substantially all assets of one, or subdividing an 455 | organization, or merging organizations. If propagation of a covered 456 | work results from an entity transaction, each party to that 457 | transaction who receives a copy of the work also receives whatever 458 | licenses to the work the party's predecessor in interest had or could 459 | give under the previous paragraph, plus a right to possession of the 460 | Corresponding Source of the work from the predecessor in interest, if 461 | the predecessor has it or can get it with reasonable efforts. 462 | 463 | You may not impose any further restrictions on the exercise of the 464 | rights granted or affirmed under this License. For example, you may 465 | not impose a license fee, royalty, or other charge for exercise of 466 | rights granted under this License, and you may not initiate litigation 467 | (including a cross-claim or counterclaim in a lawsuit) alleging that 468 | any patent claim is infringed by making, using, selling, offering for 469 | sale, or importing the Program or any portion of it. 470 | 471 | 11. Patents. 472 | 473 | A "contributor" is a copyright holder who authorizes use under this 474 | License of the Program or a work on which the Program is based. The 475 | work thus licensed is called the contributor's "contributor version". 476 | 477 | A contributor's "essential patent claims" are all patent claims 478 | owned or controlled by the contributor, whether already acquired or 479 | hereafter acquired, that would be infringed by some manner, permitted 480 | by this License, of making, using, or selling its contributor version, 481 | but do not include claims that would be infringed only as a 482 | consequence of further modification of the contributor version. For 483 | purposes of this definition, "control" includes the right to grant 484 | patent sublicenses in a manner consistent with the requirements of 485 | this License. 486 | 487 | Each contributor grants you a non-exclusive, worldwide, royalty-free 488 | patent license under the contributor's essential patent claims, to 489 | make, use, sell, offer for sale, import and otherwise run, modify and 490 | propagate the contents of its contributor version. 491 | 492 | In the following three paragraphs, a "patent license" is any express 493 | agreement or commitment, however denominated, not to enforce a patent 494 | (such as an express permission to practice a patent or covenant not to 495 | sue for patent infringement). To "grant" such a patent license to a 496 | party means to make such an agreement or commitment not to enforce a 497 | patent against the party. 498 | 499 | If you convey a covered work, knowingly relying on a patent license, 500 | and the Corresponding Source of the work is not available for anyone 501 | to copy, free of charge and under the terms of this License, through a 502 | publicly available network server or other readily accessible means, 503 | then you must either (1) cause the Corresponding Source to be so 504 | available, or (2) arrange to deprive yourself of the benefit of the 505 | patent license for this particular work, or (3) arrange, in a manner 506 | consistent with the requirements of this License, to extend the patent 507 | license to downstream recipients. "Knowingly relying" means you have 508 | actual knowledge that, but for the patent license, your conveying the 509 | covered work in a country, or your recipient's use of the covered work 510 | in a country, would infringe one or more identifiable patents in that 511 | country that you have reason to believe are valid. 512 | 513 | If, pursuant to or in connection with a single transaction or 514 | arrangement, you convey, or propagate by procuring conveyance of, a 515 | covered work, and grant a patent license to some of the parties 516 | receiving the covered work authorizing them to use, propagate, modify 517 | or convey a specific copy of the covered work, then the patent license 518 | you grant is automatically extended to all recipients of the covered 519 | work and works based on it. 520 | 521 | A patent license is "discriminatory" if it does not include within 522 | the scope of its coverage, prohibits the exercise of, or is 523 | conditioned on the non-exercise of one or more of the rights that are 524 | specifically granted under this License. You may not convey a covered 525 | work if you are a party to an arrangement with a third party that is 526 | in the business of distributing software, under which you make payment 527 | to the third party based on the extent of your activity of conveying 528 | the work, and under which the third party grants, to any of the 529 | parties who would receive the covered work from you, a discriminatory 530 | patent license (a) in connection with copies of the covered work 531 | conveyed by you (or copies made from those copies), or (b) primarily 532 | for and in connection with specific products or compilations that 533 | contain the covered work, unless you entered into that arrangement, 534 | or that patent license was granted, prior to 28 March 2007. 535 | 536 | Nothing in this License shall be construed as excluding or limiting 537 | any implied license or other defenses to infringement that may 538 | otherwise be available to you under applicable patent law. 539 | 540 | 12. No Surrender of Others' Freedom. 541 | 542 | If conditions are imposed on you (whether by court order, agreement or 543 | otherwise) that contradict the conditions of this License, they do not 544 | excuse you from the conditions of this License. If you cannot convey a 545 | covered work so as to satisfy simultaneously your obligations under this 546 | License and any other pertinent obligations, then as a consequence you may 547 | not convey it at all. For example, if you agree to terms that obligate you 548 | to collect a royalty for further conveying from those to whom you convey 549 | the Program, the only way you could satisfy both those terms and this 550 | License would be to refrain entirely from conveying the Program. 551 | 552 | 13. Use with the GNU Affero General Public License. 553 | 554 | Notwithstanding any other provision of this License, you have 555 | permission to link or combine any covered work with a work licensed 556 | under version 3 of the GNU Affero General Public License into a single 557 | combined work, and to convey the resulting work. The terms of this 558 | License will continue to apply to the part which is the covered work, 559 | but the special requirements of the GNU Affero General Public License, 560 | section 13, concerning interaction through a network will apply to the 561 | combination as such. 562 | 563 | 14. Revised Versions of this License. 564 | 565 | The Free Software Foundation may publish revised and/or new versions of 566 | the GNU General Public License from time to time. Such new versions will 567 | be similar in spirit to the present version, but may differ in detail to 568 | address new problems or concerns. 569 | 570 | Each version is given a distinguishing version number. If the 571 | Program specifies that a certain numbered version of the GNU General 572 | Public License "or any later version" applies to it, you have the 573 | option of following the terms and conditions either of that numbered 574 | version or of any later version published by the Free Software 575 | Foundation. If the Program does not specify a version number of the 576 | GNU General Public License, you may choose any version ever published 577 | by the Free Software Foundation. 578 | 579 | If the Program specifies that a proxy can decide which future 580 | versions of the GNU General Public License can be used, that proxy's 581 | public statement of acceptance of a version permanently authorizes you 582 | to choose that version for the Program. 583 | 584 | Later license versions may give you additional or different 585 | permissions. However, no additional obligations are imposed on any 586 | author or copyright holder as a result of your choosing to follow a 587 | later version. 588 | 589 | 15. Disclaimer of Warranty. 590 | 591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 599 | 600 | 16. Limitation of Liability. 601 | 602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 610 | SUCH DAMAGES. 611 | 612 | 17. Interpretation of Sections 15 and 16. 613 | 614 | If the disclaimer of warranty and limitation of liability provided 615 | above cannot be given local legal effect according to their terms, 616 | reviewing courts shall apply local law that most closely approximates 617 | an absolute waiver of all civil liability in connection with the 618 | Program, unless a warranty or assumption of liability accompanies a 619 | copy of the Program in return for a fee. 620 | 621 | END OF TERMS AND CONDITIONS 622 | 623 | How to Apply These Terms to Your New Programs 624 | 625 | If you develop a new program, and you want it to be of the greatest 626 | possible use to the public, the best way to achieve this is to make it 627 | free software which everyone can redistribute and change under these terms. 628 | 629 | To do so, attach the following notices to the program. It is safest 630 | to attach them to the start of each source file to most effectively 631 | state the exclusion of warranty; and each file should have at least 632 | the "copyright" line and a pointer to where the full notice is found. 633 | 634 | {one line to give the program's name and a brief idea of what it does.} 635 | Copyright (C) {year} {name of author} 636 | 637 | This program is free software: you can redistribute it and/or modify 638 | it under the terms of the GNU General Public License as published by 639 | the Free Software Foundation, either version 3 of the License, or 640 | (at your option) any later version. 641 | 642 | This program is distributed in the hope that it will be useful, 643 | but WITHOUT ANY WARRANTY; without even the implied warranty of 644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 645 | GNU General Public License for more details. 646 | 647 | You should have received a copy of the GNU General Public License 648 | along with this program. If not, see . 649 | 650 | Also add information on how to contact you by electronic and paper mail. 651 | 652 | If the program does terminal interaction, make it output a short 653 | notice like this when it starts in an interactive mode: 654 | 655 | {project} Copyright (C) {year} {fullname} 656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 657 | This is free software, and you are welcome to redistribute it 658 | under certain conditions; type `show c' for details. 659 | 660 | The hypothetical commands `show w' and `show c' should show the appropriate 661 | parts of the General Public License. Of course, your program's commands 662 | might be different; for a GUI interface, you would use an "about box". 663 | 664 | You should also get your employer (if you work as a programmer) or school, 665 | if any, to sign a "copyright disclaimer" for the program, if necessary. 666 | For more information on this, and how to apply and follow the GNU GPL, see 667 | . 668 | 669 | The GNU General Public License does not permit incorporating your program 670 | into proprietary programs. If your program is a subroutine library, you 671 | may consider it more useful to permit linking proprietary applications with 672 | the library. If this is what you want to do, use the GNU Lesser General 673 | Public License instead of this License. But first, please read 674 | . 675 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | :no_entry: [DEPRECATED] 2 | 3 | # zap2xml 4 | Docker container for zap2xml 5 | 6 | This is zap2xml with Environment Variables driving the configuration. By default it runs every 12 hours to update your EPG data from zap2it. This container will take a second account for zap2it and will merge the received xml files into one using tv_merge. 7 | 8 | ## Quick Run 9 | `docker run -d --name zap2xml -v /xmltvdata:/data -e USERNAME=youremail@email.com -e PASSWORD=**password** -e OPT_ARGS="-I -D -a" -e USERNAME2=yourseconduser@email.com -e PASSWORD2=**secondpassword** -e OPT_ARGS2="-I -D" -e XMLTV_FILENAME=xmltv.xml shuaiscott/zap2xml` 10 | 11 | ## Environment Settings 12 | You can configure the following environment variables below: 13 | 14 | ### Required 15 | - USERNAME - zap2it.com username 16 | - PASSWORD - zap2it.com password 17 | 18 | ### Optional 19 | - OPT_ARGS - additional command line arguments for zap2xml 20 | - USERNAME2 - Second zap2it.com username 21 | - PASSWORD2 - Second zap2it.com password 22 | - OPT_ARGS2 = additional command line arguments for zap2xml for the second username 23 | - XMLTV_FILENAME - filename for your xmltv file (default: xmltv.xml) 24 | - SLEEPTIME - time in seconds to wait before next run (default: 43200) 25 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | 3 | services: 4 | sports: 5 | image: shuaiscott/zap2xml:latest 6 | restart: always 7 | volumes: 8 | - /xmltvdata:/data 9 | environment: 10 | - USERNAME=myemail@email.com 11 | - PASSWORD=**password** 12 | - OPT_ARGS=-I -D 13 | - XMLTV_FILENAME=sports.xml 14 | -------------------------------------------------------------------------------- /entry.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # start cron 4 | #/usr/sbin/crond -f -l 8 5 | 6 | while : 7 | do 8 | DATE=`date` 9 | mkdir -p /tmp/xmltv/raws 10 | mkdir -p /tmp/xmltv/sorted 11 | 12 | echo "Run zap2xml.pl" 13 | /zap2xml.pl -u $USERNAME -p $PASSWORD -U -o /tmp/xmltv/raws/1.xml -c cache1 $OPT_ARGS 14 | 15 | if [ $USERNAME2 = "none" ] 16 | then 17 | # Just move the raw file to the output 18 | mv /tmp/xmltv/raws/1.xml /data/$XMLTV_FILENAME 19 | else 20 | # Run it again, sort, and merge the files 21 | echo "Run zap2xml.pl for second user" 22 | /zap2xml.pl -u ${USERNAME2} -p ${PASSWORD2} -U -o /tmp/xmltv/raws/2.xml -c cache2 $OPT_ARGS2 23 | echo "Sorting both files" 24 | tv_sort /tmp/xmltv/raws/1.xml --by-channel --output /tmp/xmltv/sorted/1.xml 25 | tv_sort /tmp/xmltv/raws/2.xml --by-channel --output /tmp/xmltv/sorted/2.xml 26 | echo "Merging both files" 27 | tv_merge -i /tmp/xmltv/sorted/1.xml -m /tmp/xmltv/sorted/2.xml -o /data/$XMLTV_FILENAME 28 | fi 29 | echo "Removing intermediate files" 30 | rm -rf /tmp/xmltv 31 | echo "Last run time: $DATE" 32 | echo "Will run in $SLEEPTIME seconds" 33 | sleep $SLEEPTIME 34 | done 35 | -------------------------------------------------------------------------------- /zap2xml.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | # zap2xml (c) - 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 LWP::ConnCache; 38 | use POSIX; 39 | use Time::Local; 40 | use Time::Piece; 41 | use JSON; 42 | 43 | no warnings 'utf8'; 44 | 45 | STDOUT->autoflush(1); 46 | STDERR->autoflush(1); 47 | 48 | $VERSION = "2018-12-01"; 49 | print "zap2xml ($VERSION)\nCommand line: $0 " . join(" ",@ARGV) . "\n"; 50 | 51 | %options=(); 52 | 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); 53 | 54 | $homeDir = $ENV{HOME}; 55 | $homeDir = $ENV{USERPROFILE} if !defined($homeDir); 56 | $homeDir = '.' if !defined($homeDir); 57 | $confFile = $homeDir . '/.zap2xmlrc'; 58 | 59 | # Defaults 60 | $start = 0; 61 | $days = 7; 62 | $ncdays = 0; 63 | $ncsdays = 0; 64 | $ncmday = -1; 65 | $retries = 3; 66 | $outFile = 'xmltv.xml'; 67 | $outFile = 'xtvd.xml' if defined $options{x}; 68 | $cacheDir = 'cache'; 69 | $lang = 'en'; 70 | $userEmail = ''; 71 | $password = ''; 72 | $proxy; 73 | $postalcode; 74 | $country; 75 | $lineupId; 76 | $device; 77 | $sleeptime = 0; 78 | $allChan = 0; 79 | $shiftMinutes = 0; 80 | 81 | $outputXTVD = 0; 82 | $lineuptype; 83 | $lineupname; 84 | $lineuplocation; 85 | 86 | $zapToken; 87 | $zapPref='-'; 88 | %zapFavorites=(); 89 | %sidCache=(); 90 | 91 | $sTBA = "\\bTBA\\b|To Be Announced"; 92 | 93 | %tvgfavs=(); 94 | 95 | &HELP_MESSAGE() if defined $options{'?'}; 96 | 97 | $confFile = $options{C} if defined $options{C}; 98 | # read config file 99 | if (open (CONF, $confFile)) 100 | { 101 | &pout("Reading config file: $confFile\n"); 102 | while () 103 | { 104 | s/#.*//; # comments 105 | if (/^\s*$/i) { } 106 | elsif (/^\s*start\s*=\s*(\d+)/i) { $start = $1; } 107 | elsif (/^\s*days\s*=\s*(\d+)/i) { $days = $1; } 108 | elsif (/^\s*ncdays\s*=\s*(\d+)/i) { $ncdays = $1; } 109 | elsif (/^\s*ncsdays\s*=\s*(\d+)/i) { $ncsdays = $1; } 110 | elsif (/^\s*ncmday\s*=\s*(\d+)/i) { $ncmday = $1; } 111 | elsif (/^\s*retries\s*=\s*(\d+)/i) { $retries = $1; } 112 | elsif (/^\s*user[\w\s]*=\s*(.+)/i) { $userEmail = &rtrim($1); } 113 | elsif (/^\s*pass[\w\s]*=\s*(.+)/i) { $password = &rtrim($1); } 114 | elsif (/^\s*cache\s*=\s*(.+)/i) { $cacheDir = &rtrim($1); } 115 | elsif (/^\s*icon\s*=\s*(.+)/i) { $iconDir = &rtrim($1); } 116 | elsif (/^\s*trailer\s*=\s*(.+)/i) { $trailerDir = &rtrim($1); } 117 | elsif (/^\s*lang\s*=\s*(.+)/i) { $lang = &rtrim($1); } 118 | elsif (/^\s*outfile\s*=\s*(.+)/i) { $outFile = &rtrim($1); } 119 | elsif (/^\s*proxy\s*=\s*(.+)/i) { $proxy = &rtrim($1); } 120 | elsif (/^\s*outformat\s*=\s*(.+)/i) { $outputXTVD = 1 if $1 =~ /xtvd/i; } 121 | elsif (/^\s*lineupid\s*=\s*(.+)/i) { $lineupId = &rtrim($1); } 122 | elsif (/^\s*lineupname\s*=\s*(.+)/i) { $lineupname = &rtrim($1); } 123 | elsif (/^\s*lineuptype\s*=\s*(.+)/i) { $lineuptype = &rtrim($1); } 124 | elsif (/^\s*lineuplocation\s*=\s*(.+)/i) { $lineuplocation = &rtrim($1); } 125 | elsif (/^\s*postalcode\s*=\s*(.+)/i) { $postalcode = &rtrim($1); } 126 | else 127 | { 128 | die "Oddline in config file \"$confFile\".\n\t$_"; 129 | } 130 | } 131 | close (CONF); 132 | } 133 | &HELP_MESSAGE() if !(%options) && $userEmail eq ''; 134 | 135 | $cacheDir = $options{c} if defined $options{c}; 136 | $days = $options{d} if defined $options{d}; 137 | $ncdays = $options{n} if defined $options{n}; 138 | $ncsdays = $options{N} if defined $options{N}; 139 | $ncmday = $options{B} if defined $options{B}; 140 | $start = $options{s} if defined $options{s}; 141 | $retries = $options{r} if defined $options{r}; 142 | $iconDir = $options{i} if defined $options{i}; 143 | $trailerDir = $options{t} if defined $options{t}; 144 | $lang = $options{l} if defined $options{l}; 145 | $outFile = $options{o} if defined $options{o}; 146 | $password = $options{p} if defined $options{p}; 147 | $userEmail = $options{u} if defined $options{u}; 148 | $proxy = $options{P} if defined $options{P}; 149 | $zlineupId = $options{Y} if defined $options{Y}; 150 | $zipcode = $options{Z} if defined $options{Z}; 151 | $includeXMLTV = $options{J} if defined $options{J} && -e $options{J}; 152 | $outputXTVD = 1 if defined $options{x}; 153 | $allChan = 1 if defined($options{a}); 154 | $allChan = 1 if defined($zipcode) && defined($zlineupId); 155 | $sleeptime = $options{S} if defined $options{S}; 156 | $shiftMinutes = $options{m} if defined $options{m}; 157 | $ncdays = $days - $ncdays; # make relative to the end 158 | $urlRoot = 'https://tvlistings.zap2it.com/'; 159 | $urlAssets = 'https://zap2it.tmsimg.com/assets/'; 160 | $tvgurlRoot = 'http://mobilelistings.tvguide.com/'; 161 | $tvgMapiRoot = 'http://mapi.tvguide.com/'; 162 | $tvgurl = 'https://www.tvguide.com/'; 163 | $tvgspritesurl = 'http://static.tvgcdn.net/sprites/'; 164 | $retries = 20 if $retries > 20; # Too many 165 | 166 | my %programs = (); 167 | my $cp; 168 | my %stations = (); 169 | my $cs; 170 | my $rcs; 171 | my %schedule = (); 172 | my $sch; 173 | my %logos = (); 174 | 175 | my $coNum = 0; 176 | my $tb = 0; 177 | my $treq = 0; 178 | my $tsocks = (); 179 | my $expired = 0; 180 | my $ua; 181 | my $tba = 0; 182 | my $exp = 0; 183 | my @fh = (); 184 | 185 | my $XTVD_startTime; 186 | my $XTVD_endTime; 187 | 188 | if (! -d $cacheDir) { 189 | mkdir($cacheDir) or die "Can't mkdir: $!\n"; 190 | } else { 191 | opendir (DIR, "$cacheDir/"); 192 | @cacheFiles = grep(/\.html|\.js/,readdir(DIR)); 193 | closedir (DIR); 194 | foreach $cacheFile (@cacheFiles) { 195 | $fn = "$cacheDir/$cacheFile"; 196 | $atime = (stat($fn))[8]; 197 | if ($atime + ( ($days + 2) * 86400) < time) { 198 | &pout("Deleting old cached file: $fn\n"); 199 | &unf($fn); 200 | } 201 | } 202 | } 203 | 204 | my $s1 = time(); 205 | if (defined($options{z})) { 206 | 207 | &login() if !defined($options{a}); # get favorites 208 | &parseTVGIcons() if defined($iconDir); 209 | $gridHours = 3; 210 | $maxCount = $days * (24 / $gridHours); 211 | $offset = $start * 3600 * 24 * 1000; 212 | $ms = &hourToMillis() + $offset; 213 | 214 | for ($count=0; $count < $maxCount; $count++) { 215 | $curday = int($count / (24/$gridHours)) + 1; 216 | if ($count == 0) { 217 | $XTVD_startTime = $ms; 218 | } elsif ($count == $maxCount - 1) { 219 | $XTVD_endTime = $ms + ($gridHours * 3600000) - 1; 220 | } 221 | 222 | $fn = "$cacheDir/$ms\.js\.gz"; 223 | if (! -e $fn || $curday > $ncdays || $curday <= $ncsdays || $curday == $ncmday) { 224 | &login() if !defined($zlineupId); 225 | my $duration = $gridHours * 60; 226 | my $tvgstart = substr($ms, 0, -3); 227 | $rs = &getURL($tvgurlRoot . "Listingsweb/ws/rest/schedules/$zlineupId/start/$tvgstart/duration/$duration", 1); 228 | last if ($rs eq ''); 229 | $rc = Encode::encode('utf8', $rs); 230 | &wbf($fn, Compress::Zlib::memGzip($rc)); 231 | } 232 | &pout("[" . ($count+1) . "/" . "$maxCount] Parsing: $fn\n"); 233 | &parseTVGGrid($fn); 234 | 235 | if (defined($options{T}) && $tba) { 236 | &pout("Deleting: $fn (contains \"$sTBA\")\n"); 237 | &unf($fn); 238 | } 239 | if ($exp) { 240 | &pout("Deleting: $fn (expired)\n"); 241 | &unf($fn); 242 | } 243 | $exp = 0; 244 | $tba = 0; 245 | $ms += ($gridHours * 3600 * 1000); 246 | } 247 | 248 | } else { 249 | 250 | &login() if !defined($options{a}); # get favorites 251 | $gridHours = 3; 252 | $maxCount = $days * (24 / $gridHours); 253 | $offset = $start * 3600 * 24 * 1000; 254 | $ms = &hourToMillis() + $offset; 255 | for ($count=0; $count < $maxCount; $count++) { 256 | $curday = int($count / (24/$gridHours)) + 1; 257 | if ($count == 0) { 258 | $XTVD_startTime = $ms; 259 | } elsif ($count == $maxCount - 1) { 260 | $XTVD_endTime = $ms + ($gridHours * 3600000) - 1; 261 | } 262 | 263 | $fn = "$cacheDir/$ms\.js\.gz"; 264 | if (! -e $fn || $curday > $ncdays || $curday <= $ncsdays || $curday == $ncmday) { 265 | my $zstart = substr($ms, 0, -3); 266 | $params = "?time=$zstart×pan=$gridHours&pref=$zapPref&"; 267 | $params .= &getZapGParams(); 268 | $params .= '&TMSID=&AffiliateID=gapzap&FromPage=TV%20Grid'; 269 | $params .= '&ActivityID=1&OVDID=&isOverride=true'; 270 | $rs = &getURL($urlRoot . "api/grid$params",1); 271 | last if ($rs eq ''); 272 | $rc = Encode::encode('utf8', $rs); 273 | &wbf($fn, Compress::Zlib::memGzip($rc)); 274 | } 275 | 276 | &pout("[" . ($count+1) . "/" . "$maxCount] Parsing: $fn\n"); 277 | &parseJSON($fn); 278 | 279 | if (defined($options{T}) && $tba) { 280 | &pout("Deleting: $fn (contains \"$sTBA\")\n"); 281 | &unf($fn); 282 | } 283 | if ($exp) { 284 | &pout("Deleting: $fn (expired)\n"); 285 | &unf($fn); 286 | } 287 | $exp = 0; 288 | $tba = 0; 289 | $ms += ($gridHours * 3600 * 1000); 290 | } 291 | 292 | } 293 | my $s2 = time(); 294 | my $tsockt = scalar(keys %tsocks); 295 | &pout("Downloaded " . &pl($tb, "byte") 296 | . " in " . &pl($treq, "http request") 297 | . " using " . &pl($tsockt > 0 ? $tsockt : $treq, "socket") . ".\n") if $tb > 0; 298 | &pout("Expired programs: $expired\n") if $expired > 0; 299 | &pout("Writing XML file: $outFile\n"); 300 | open($FH, ">$outFile"); 301 | my $enc = 'ISO-8859-1'; 302 | if (defined($options{U})) { 303 | $enc = 'UTF-8'; 304 | } 305 | if ($outputXTVD) { 306 | &printHeaderXTVD($FH, $enc); 307 | &printStationsXTVD($FH); 308 | &printLineupsXTVD($FH); 309 | &printSchedulesXTVD($FH); 310 | &printProgramsXTVD($FH); 311 | &printGenresXTVD($FH); 312 | &printFooterXTVD($FH); 313 | } else { 314 | &printHeader($FH, $enc); 315 | &printChannels($FH); 316 | if (defined($includeXMLTV)) { 317 | &pout("Reading XML file: $includeXMLTV\n"); 318 | &incXML("; 337 | } else { 338 | sleep(3) if ($^O eq 'MSWin32'); 339 | } 340 | 341 | exit 0; 342 | 343 | sub incXML { 344 | my ($st, $en, $FH) = @_; 345 | open($XF, "<$includeXMLTV"); 346 | while (<$XF>) { 347 | if (/^\s*$st/../^\s*$en/) { 348 | print $FH $_ unless /^\s*$en/ 349 | } 350 | } 351 | close($XF); 352 | } 353 | 354 | sub pl { 355 | my($i, $s) = @_; 356 | my $r = "$i $s"; 357 | return $i == 1 ? $r : $r . "s"; 358 | } 359 | 360 | sub pout { 361 | print @_ if !defined $options{q}; 362 | } 363 | 364 | sub perr { 365 | warn @_; 366 | } 367 | 368 | sub rtrim { 369 | my $s = shift; 370 | $s =~ s/\s+$//; 371 | return $s; 372 | } 373 | 374 | sub trim { 375 | my $s = shift; 376 | $s =~ s/^\s+//; 377 | $s =~ s/\s+$//; 378 | return $s; 379 | } 380 | 381 | sub trim2 { 382 | my $s = &trim(shift); 383 | $s =~ s/[^\w\s\(\)\,]//gsi; 384 | $s =~ s/\s+/ /gsi; 385 | return $s; 386 | } 387 | 388 | sub _rtrim3 { 389 | my $s = shift; 390 | return substr($s, 0, length($s)-3); 391 | } 392 | 393 | sub convTime { 394 | my $t = shift; 395 | $t += $shiftMinutes * 60 * 1000; 396 | return strftime "%Y%m%d%H%M%S", localtime(&_rtrim3($t)); 397 | } 398 | 399 | sub convTimeXTVD { 400 | my $t = shift; 401 | $t += $shiftMinutes * 60 * 1000; 402 | return strftime "%Y-%m-%dT%H:%M:%SZ", gmtime(&_rtrim3($t)); 403 | } 404 | 405 | sub convOAD { 406 | return strftime "%Y%m%d", gmtime(&_rtrim3(shift)); 407 | } 408 | 409 | sub convOADXTVD { 410 | return strftime "%Y-%m-%d", gmtime(&_rtrim3(shift)); 411 | } 412 | 413 | sub convDurationXTVD { 414 | my $duration = shift; 415 | my $hour = int($duration / 3600000); 416 | my $minutes = int(($duration - ($hour * 3600000)) / 60000); 417 | return sprintf("PT%02dH%02dM", $hour, $minutes); 418 | } 419 | 420 | sub appendAsterisk { 421 | my ($title, $station, $s) = @_; 422 | if (defined($options{A})) { 423 | if (($options{A} =~ "new" && defined($schedule{$station}{$s}{new})) 424 | || ($options{A} =~ "live" && defined($schedule{$station}{$s}{live}))) { 425 | $title .= " *"; 426 | } 427 | } 428 | return $title; 429 | } 430 | 431 | sub stationToChannel { 432 | my $s = shift; 433 | if (defined($options{z})) { 434 | return sprintf("I%s.%s.tvguide.com", $stations{$s}{number},$stations{$s}{stnNum}); 435 | } elsif (defined($options{O})) { 436 | return sprintf("C%s%s.zap2it.com",$stations{$s}{number},lc($stations{$s}{name})); 437 | } elsif (defined($options{9})) { 438 | return sprintf("I%s.labs.zap2it.com",$stations{$s}{stnNum}); 439 | } 440 | return sprintf("I%s.%s.zap2it.com", $stations{$s}{number},$stations{$s}{stnNum}); 441 | } 442 | 443 | sub sortChan { 444 | if (defined($stations{$a}{order}) && defined($stations{$b}{order})) { 445 | my $c = $stations{$a}{order} <=> $stations{$b}{order}; 446 | if ($c == 0) { return $stations{$a}{stnNum} <=> $stations{$b}{stnNum} } 447 | else { return $c }; 448 | } else { 449 | return $stations{$a}{name} cmp $stations{$b}{name}; 450 | } 451 | } 452 | 453 | sub enc { 454 | my $t = shift; 455 | if (!defined($options{U})) {$t = Encode::decode('utf8', $t);} 456 | if (!defined($options{E}) || $options{E} =~ /amp/) {$t =~ s/&/&/gs;} 457 | if (!defined($options{E}) || $options{E} =~ /quot/) {$t =~ s/"/"/gs;} 458 | if (!defined($options{E}) || $options{E} =~ /apos/) {$t =~ s/'/'/gs;} 459 | if (!defined($options{E}) || $options{E} =~ /lt/) {$t =~ s//>/gs;} 461 | if (defined($options{e})) { 462 | $t =~ s/([^\x20-\x7F])/'&#' . ord($1) . ';'/gse; 463 | } 464 | return $t; 465 | } 466 | 467 | sub printHeader { 468 | my ($FH, $enc) = @_; 469 | print $FH "\n"; 470 | print $FH "\n\n"; 471 | if (defined($options{z})) { 472 | print $FH "\n"; 477 | } 478 | 479 | sub printFooter { 480 | my $FH = shift; 481 | print $FH "\n"; 482 | } 483 | 484 | sub printChannels { 485 | my $FH = shift; 486 | for my $key ( sort sortChan keys %stations ) { 487 | $sname = &enc($stations{$key}{name}); 488 | $fname = &enc($stations{$key}{fullname}); 489 | $snum = $stations{$key}{number}; 490 | print $FH "\t\n"; 491 | print $FH "\t\t" . $sname . "\n" if defined($options{F}) && defined($sname); 492 | if (defined($snum)) { 493 | ©Logo($key); 494 | print $FH "\t\t" . $snum . " " . $sname . "\n" if ($snum ne ''); 495 | print $FH "\t\t" . $snum . "\n" if ($snum ne ''); 496 | } 497 | print $FH "\t\t" . $sname . "\n" if !defined($options{F}) && defined($sname); 498 | print $FH "\t\t" . $fname . "\n" if (defined($fname)); 499 | if (defined($stations{$key}{logoURL})) { 500 | print $FH "\t\t\n"; 501 | } 502 | print $FH "\t\n"; 503 | } 504 | } 505 | 506 | sub printProgrammes { 507 | my $FH = shift; 508 | for my $station ( sort sortChan keys %stations ) { 509 | my $i = 0; 510 | my @keyArray = sort { $schedule{$station}{$a}{time} cmp $schedule{$station}{$b}{time} } keys %{$schedule{$station}}; 511 | foreach $s (@keyArray) { 512 | if ($#keyArray <= $i && !defined($schedule{$station}{$s}{endtime})) { 513 | delete $schedule{$station}{$s}; 514 | next; 515 | } 516 | my $p = $schedule{$station}{$s}{program}; 517 | my $startTime = &convTime($schedule{$station}{$s}{time}); 518 | my $startTZ = &timezone($schedule{$station}{$s}{time}); 519 | my $endTime; 520 | if (defined($schedule{$station}{$s}{endtime})) { 521 | $endTime = $schedule{$station}{$s}{endtime}; 522 | } else { 523 | $endTime = $schedule{$station}{$keyArray[$i+1]}{time}; 524 | } 525 | 526 | my $stopTime = &convTime($endTime); 527 | my $stopTZ = &timezone($endTime); 528 | 529 | print $FH "\t\n"; 530 | if (defined($programs{$p}{title})) { 531 | my $title = &enc($programs{$p}{title}); 532 | $title = &appendAsterisk($title, $station, $s); 533 | print $FH "\t\t" . $title . "\n"; 534 | } 535 | 536 | if (defined($programs{$p}{episode}) || (defined($options{M}) && defined($programs{$p}{movie_year}))) { 537 | print $FH "\t\t"; 538 | if (defined($programs{$p}{episode})) { 539 | print $FH &enc($programs{$p}{episode}); 540 | } else { 541 | print $FH "Movie (" . $programs{$p}{movie_year} . ")"; 542 | } 543 | print $FH "\n" 544 | } 545 | 546 | print $FH "\t\t" . &enc($programs{$p}{description}) . "\n" if defined($programs{$p}{description}); 547 | 548 | if (defined($programs{$p}{actor}) 549 | || defined($programs{$p}{director}) 550 | || defined($programs{$p}{writer}) 551 | || defined($programs{$p}{producer}) 552 | || defined($programs{$p}{preseter}) 553 | ) { 554 | print $FH "\t\t\n"; 555 | &printCredits($FH, $p, "director"); 556 | foreach my $g (sort { $programs{$p}{actor}{$a} <=> $programs{$p}{actor}{$b} } keys %{$programs{$p}{actor}} ) { 557 | print $FH "\t\t\t" . &enc($g) . "\n"; 560 | } 561 | &printCredits($FH, $p, "writer"); 562 | &printCredits($FH, $p, "producer"); 563 | &printCredits($FH, $p, "presenter"); 564 | print $FH "\t\t\n"; 565 | } 566 | 567 | my $date; 568 | if (defined($programs{$p}{movie_year})) { 569 | $date = $programs{$p}{movie_year}; 570 | } elsif (defined($programs{$p}{originalAirDate}) && $p =~ /^EP|^\d/) { 571 | $date = &convOAD($programs{$p}{originalAirDate}); 572 | } 573 | print $FH "\t\t$date\n" if defined($date); 574 | 575 | if (defined($programs{$p}{genres})) { 576 | foreach my $g (sort { $programs{$p}{genres}{$a} <=> $programs{$p}{genres}{$b} or $a cmp $b } keys %{$programs{$p}{genres}} ) { 577 | print $FH "\t\t" . &enc(ucfirst($g)) . "\n"; 578 | } 579 | } 580 | 581 | print $FH "\t\t" . $programs{$p}{duration} . "\n" if defined($programs{$p}{duration}); 582 | 583 | if (defined($programs{$p}{imageUrl})) { 584 | print $FH "\t\t\n"; 585 | } 586 | 587 | if (defined($programs{$p}{url})) { 588 | print $FH "\t\t" . &enc($programs{$p}{url}) . "\n"; 589 | } 590 | 591 | my $xs; 592 | my $xe; 593 | 594 | if (defined($programs{$p}{seasonNum}) && defined($programs{$p}{episodeNum})) { 595 | my $s = $programs{$p}{seasonNum}; 596 | my $sf = sprintf("S%0*d", &max(2, length($s)), $s); 597 | my $e = $programs{$p}{episodeNum}; 598 | my $ef = sprintf("E%0*d", &max(2, length($e)), $e); 599 | 600 | $xs = int($s) - 1; 601 | $xe = int($e) - 1; 602 | 603 | if ($s > 0 || $e > 0) { 604 | print $FH "\t\t" . $sf . $ef . "\n"; 605 | } 606 | } 607 | 608 | $dd_prog_id = $p; 609 | if ( $dd_prog_id =~ /^(..\d{8})(\d{4})/ ) { 610 | $dd_prog_id = sprintf("%s.%s",$1,$2); 611 | print $FH "\t\t" . $dd_prog_id . "\n"; 612 | } 613 | 614 | if (defined($xs) && defined($xe) && $xs >= 0 && $xe >= 0) { 615 | print $FH "\t\t" . $xs . "." . $xe . ".\n"; 616 | } 617 | 618 | if (defined($schedule{$station}{$s}{quality})) { 619 | print $FH "\t\t\n"; 623 | } 624 | my $new = defined($schedule{$station}{$s}{new}); 625 | my $live = defined($schedule{$station}{$s}{live}); 626 | my $cc = defined($schedule{$station}{$s}{cc}); 627 | 628 | if (! $new && ! $live && $p =~ /^EP|^SH|^\d/) { 629 | print $FH "\t\t\n"; 635 | } 636 | 637 | if (defined($schedule{$station}{$s}{premiere})) { 638 | print $FH "\t\t" . $schedule{$station}{$s}{premiere} . "\n"; 639 | } 640 | 641 | if (defined($schedule{$station}{$s}{finale})) { 642 | print $FH "\t\t" . $schedule{$station}{$s}{finale} . "\n"; 643 | } 644 | 645 | print $FH "\t\t\n" if $new; 646 | # not part of XMLTV format yet? 647 | print $FH "\t\t\n" if (defined($options{L}) && $live); 648 | print $FH "\t\t\n" if $cc; 649 | 650 | if (defined($programs{$p}{rating})) { 651 | print $FH "\t\t\n\t\t\t" . $programs{$p}{rating} . "\n\t\t\n" 652 | } 653 | 654 | if (defined($programs{$p}{starRating})) { 655 | print $FH "\t\t\n\t\t\t" . $programs{$p}{starRating} . "/4\n\t\t\n"; 656 | } 657 | print $FH "\t\n"; 658 | $i++; 659 | } 660 | } 661 | } 662 | 663 | sub printHeaderXTVD { 664 | my ($FH, $enc) = @_; 665 | print $FH "\n"; 666 | print $FH "\n"; 667 | } 668 | 669 | sub printCredits { 670 | my ($FH, $p, $s) = @_; 671 | foreach my $g (sort { $programs{$p}{$s}{$a} <=> $programs{$p}{$s}{$b} } keys %{$programs{$p}{$s}} ) { 672 | print $FH "\t\t\t<$s>" . &enc($g) . "\n"; 673 | } 674 | } 675 | 676 | sub printFooterXTVD { 677 | my $FH = shift; 678 | print $FH "\n"; 679 | } 680 | 681 | sub printStationsXTVD { 682 | my $FH = shift; 683 | print $FH "\n"; 684 | for my $key ( sort sortChan keys %stations ) { 685 | print $FH "\t\n"; 686 | if (defined($stations{$key}{number})) { 687 | $sname = &enc($stations{$key}{name}); 688 | print $FH "\t\t" . $sname . "\n"; 689 | print $FH "\t\t" . $sname . "\n"; 690 | print $FH "\t\t" . $stations{$key}{number} . "\n"; 691 | ©Logo($key); 692 | } 693 | print $FH "\t\n"; 694 | } 695 | print $FH "\n"; 696 | } 697 | 698 | sub printLineupsXTVD { 699 | my $FH = shift; 700 | print $FH "\n"; 701 | print $FH "\t\n"; 702 | for my $key ( sort sortChan keys %stations ) { 703 | if (defined($stations{$key}{number})) { 704 | print $FH "\t\n"; 705 | } 706 | } 707 | print $FH "\t\n"; 708 | print $FH "\n"; 709 | } 710 | 711 | sub printSchedulesXTVD { 712 | my $FH = shift; 713 | print $FH "\n"; 714 | for my $station ( sort sortChan keys %stations ) { 715 | my $i = 0; 716 | my @keyArray = sort { $schedule{$station}{$a}{time} cmp $schedule{$station}{$b}{time} } keys %{$schedule{$station}}; 717 | foreach $s (@keyArray) { 718 | if ($#keyArray <= $i) { 719 | delete $schedule{$station}{$s}; 720 | next; 721 | } 722 | my $p = $schedule{$station}{$s}{program}; 723 | my $startTime = &convTimeXTVD($schedule{$station}{$s}{time}); 724 | my $stopTime = &convTimeXTVD($schedule{$station}{$keyArray[$i+1]}{time}); 725 | my $duration = &convDurationXTVD($schedule{$station}{$keyArray[$i+1]}{time} - $schedule{$station}{$s}{time}); 726 | 727 | print $FH "\t\n"; 731 | $i++; 732 | } 733 | } 734 | print $FH "\n"; 735 | } 736 | 737 | sub printProgramsXTVD { 738 | my $FH = shift; 739 | print $FH "\n"; 740 | foreach $p (keys %programs) { 741 | print $FH "\t\n"; 742 | print $FH "\t\t" . &enc($programs{$p}{title}) . "\n" if defined($programs{$p}{title}); 743 | print $FH "\t\t" . &enc($programs{$p}{episode}) . "\n" if defined($programs{$p}{episode}); 744 | print $FH "\t\t" . &enc($programs{$p}{description}) . "\n" if defined($programs{$p}{description}); 745 | 746 | if (defined($programs{$p}{movie_year})) { 747 | print $FH "\t\t" . $programs{$p}{movie_year} . "\n"; 748 | } else { #Guess 749 | my $showType = "Series"; 750 | if ($programs{$p}{title} =~ /Paid Programming/i) { 751 | $showType = "Paid Programming"; 752 | } 753 | print $FH "\t\t$showType\n"; 754 | print $FH "\t\tEP" . substr($p,2,8) . "\n"; 755 | print $FH "\t\t" . &convOADXTVD($programs{$p}{originalAirDate}) . "\n" if defined($programs{$p}{originalAirDate}); 756 | } 757 | print $FH "\t\n"; 758 | } 759 | print $FH "\n"; 760 | } 761 | 762 | sub printGenresXTVD { 763 | my $FH = shift; 764 | print $FH "\n"; 765 | foreach $p (keys %programs) { 766 | if (defined($programs{$p}{genres}) && $programs{$p}{genres}{movie} != 1) { 767 | print $FH "\t\n"; 768 | foreach my $g (keys %{$programs{$p}{genres}}) { 769 | print $FH "\t\t\n"; 770 | print $FH "\t\t\t" . &enc(ucfirst($g)) . "\n"; 771 | print $FH "\t\t\t0\n"; 772 | print $FH "\t\t\n"; 773 | } 774 | print $FH "\t\n"; 775 | } 776 | } 777 | print $FH "\n"; 778 | } 779 | 780 | sub loginTVG { 781 | my $r = &ua_get($tvgurl . 'signin/'); 782 | if ($r->is_success) { 783 | my $str = $r->decoded_content; 784 | if ($str =~ / $token, 792 | email => $userEmail, 793 | password => $password, 794 | }, 'X-Requested-With' => 'XMLHttpRequest' 795 | ); 796 | 797 | $dc = Encode::encode('utf8', $r->decoded_content( raise_error => 1 )); 798 | if ($dc =~ /success/) { 799 | $ua->cookie_jar->scan(sub { if ($_[1] eq "ServiceID") { $zlineupId = $_[2]; }; }); 800 | if (!defined($options{a})) { 801 | my $r = &ua_get($tvgurl . "user/favorites/?provider=$zlineupId",'X-Requested-With' => 'XMLHttpRequest'); 802 | $dc = Encode::encode('utf8', $r->decoded_content( raise_error => 1 )); 803 | if ($dc =~ /\{\"code\":200/) { 804 | &parseTVGFavs($dc); 805 | } 806 | } 807 | return $dc; 808 | } else { 809 | &pout("[Attempt $rc] " . $r->status_line . ":" . $dc . "\n"); 810 | sleep ($sleeptime + 1); 811 | } 812 | } 813 | die "Failed to login within $retries retries.\n"; 814 | } 815 | } else { 816 | die "Login token not found\n"; 817 | } 818 | } 819 | } 820 | 821 | sub loginZAP { 822 | my $rc = 0; 823 | while ($rc++ < $retries) { 824 | my $r = &ua_post($urlRoot . 'api/user/login', 825 | { 826 | emailid => $userEmail, password => $password, 827 | usertype => '0', facebookuser =>'false', 828 | } 829 | ); 830 | 831 | $dc = Encode::encode('utf8', $r->decoded_content( raise_error => 1 )); 832 | if ($r->is_success) { 833 | my $t = decode_json($dc); 834 | $zapToken = $t->{'token'}; 835 | $zapPref = ''; 836 | $zapPref .= "m" if ($t->{isMusic}); 837 | $zapPref .= "p" if ($t->{isPPV}); 838 | $zapPref .= "h" if ($t->{isHD}); 839 | if ($zapPref eq '') { $zapPref = '-' } 840 | else { 841 | $zapPref = join(",", split(//, $zapPref)); 842 | } 843 | 844 | my $prs = $t->{'properties'}; 845 | $postalcode = $prs->{2002}; 846 | $country = $prs->{2003}; 847 | ($lineupId, $device) = split(/:/, $prs->{2004}); 848 | if (!defined($options{a})) { 849 | my $r = &ua_post($urlRoot . "api/user/favorites", { token => $zapToken }, 'X-Requested-With' => 'XMLHttpRequest'); 850 | $dc = Encode::encode('utf8', $r->decoded_content( raise_error => 1 )); 851 | if ($r->is_success) { 852 | &parseZFavs($dc); 853 | } else { 854 | &perr("FF" . $r->status_line . ": $dc\n"); 855 | } 856 | } 857 | return $dc; 858 | } else { 859 | &pout("[Attempt $rc] " . $dc . "\n"); 860 | sleep ($sleeptime + 1); 861 | } 862 | } 863 | die "Failed to login within $retries retries.\n"; 864 | } 865 | 866 | sub getZapGParams { 867 | my %hash = &getZapParams(); 868 | $hash{country} = delete $hash{countryCode}; 869 | return join("&", map { "$_=$hash{$_}" } keys %hash); 870 | } 871 | 872 | sub getZapPParams { 873 | my %hash = &getZapParams(); 874 | delete $hash{lineupId}; 875 | return %hash; 876 | } 877 | 878 | sub getZapParams { 879 | my %phash = (); 880 | if (defined($zlineupId) || defined($zipcode)) { 881 | $postalcode = $zipcode; 882 | $country = "USA"; 883 | $country = "CAN" if ($zipcode =~ /[A-z]/); 884 | if ($zlineupId =~ /:/) { 885 | ($lineupId, $device) = split(/:/, $zlineupId); 886 | } else { 887 | $lineupId = $zlineupId; 888 | $device = "-"; 889 | } 890 | $phash{postalCode} = $postalcode; 891 | } else { 892 | $phash{token} = &getZToken(); 893 | } 894 | $phash{lineupId} = "$country-$lineupId-DEFAULT"; 895 | $phash{postalCode} = $postalcode; 896 | $phash{countryCode} = $country; 897 | $phash{headendId} = $lineupId; 898 | $phash{device} = $device; 899 | $phash{aid} = 'gapzap'; 900 | return %phash; 901 | } 902 | 903 | sub login { 904 | if (!defined($userEmail) || $userEmail eq '' || !defined($password) || $password eq '') { 905 | if (!defined($zlineupId)) { 906 | die "Unable to login: Unspecified username or password.\n" 907 | } 908 | } 909 | 910 | if (!defined($ua)) { 911 | $ua = LWP::UserAgent->new(ssl_opts => { verify_hostname => 0 }); # WIN 912 | $ua->conn_cache(LWP::ConnCache->new( total_capacity => undef )); 913 | $ua->cookie_jar(HTTP::Cookies->new); 914 | $ua->proxy(['http', 'https'], $proxy) if defined($proxy); 915 | $ua->agent('Mozilla/4.0'); 916 | $ua->default_headers->push_header('Accept-Encoding' => 'gzip, deflate'); 917 | } 918 | 919 | if ($userEmail ne '' && $password ne '') { 920 | &pout("Logging in as \"$userEmail\" (" . localtime . ")\n"); 921 | if (defined($options{z})) { 922 | &loginTVG(); 923 | } else { 924 | &loginZAP(); 925 | } 926 | } else { 927 | &pout("Connecting with lineupId \"$zlineupId\" (" . localtime . ")\n"); 928 | } 929 | } 930 | 931 | sub ua_stats { 932 | my ($s, @p) = @_; 933 | my $r; 934 | if ($s eq 'POST') { 935 | $r = $ua->post(@p); 936 | } else { 937 | $r = $ua->get(@p); 938 | } 939 | my $cc = $ua->conn_cache; 940 | if (defined($cc)) { 941 | my @cxns = $cc->get_connections(); 942 | foreach (@cxns) { 943 | $tsocks{$_} = 1; 944 | } 945 | } 946 | $treq++; 947 | $tb += length($r->content); 948 | return $r; 949 | } 950 | 951 | sub ua_get { return &ua_stats('GET', @_); } 952 | sub ua_post { return &ua_stats('POST', @_); } 953 | 954 | sub getURL { 955 | my $url = shift; 956 | my $er = shift; 957 | &login() if !defined($ua); 958 | 959 | my $rc = 0; 960 | while ($rc++ < $retries) { 961 | &pout("[$treq] Getting: $url\n"); 962 | sleep $sleeptime; # do these rapid requests flood servers? 963 | my $r = &ua_get($url); 964 | my $cl = length($r->content); 965 | my $dc = $r->decoded_content( raise_error => 1 ); 966 | if ($r->is_success && $cl) { 967 | return $dc; 968 | } elsif ($r->code == 500 && $dc =~ /Could not load details/) { 969 | &pout("$dc\n"); 970 | return ""; 971 | } else { 972 | &perr("[Attempt $rc] $cl:" . $r->status_line . "\n"); 973 | &perr($r->content . "\n"); 974 | sleep ($sleeptime + 2); 975 | } 976 | } 977 | &perr("Failed to download within $retries retries.\n"); 978 | if ($er) { 979 | &perr("Server out of data? Temporary server error? Normal exit anyway.\n"); 980 | return ""; 981 | }; 982 | die; 983 | } 984 | 985 | sub wbf { 986 | my($f, $s) = @_; 987 | open(FO, ">$f") or die "Failed to open '$f': $!"; 988 | binmode(FO); 989 | print FO $s; 990 | close(FO); 991 | } 992 | 993 | sub unf { 994 | my $f = shift; 995 | unlink($f) or &perr("Failed to delete '$f': $!"); 996 | } 997 | 998 | sub copyLogo { 999 | my $key = shift; 1000 | my $cid = $key; 1001 | if (!defined($logos{$cid}{logo})) { 1002 | $cid = substr($key, rindex($key, ".")+1); 1003 | } 1004 | if (defined($iconDir) && defined($logos{$cid}{logo})) { 1005 | my $num = $stations{$key}{number}; 1006 | my $src = "$iconDir/" . $logos{$cid}{logo} . $logos{$cid}{logoExt}; 1007 | my $dest1 = "$iconDir/$num" . $logos{$cid}{logoExt}; 1008 | my $dest2 = "$iconDir/$num " . $stations{$key}{name} . $logos{$cid}{logoExt}; 1009 | my $dest3 = "$iconDir/$num\_" . $stations{$key}{name} . $logos{$cid}{logoExt}; 1010 | copy($src, $dest1); 1011 | copy($src, $dest2); 1012 | copy($src, $dest3); 1013 | } 1014 | } 1015 | 1016 | sub handleLogo { 1017 | my $url = shift; 1018 | if (! -d $iconDir) { 1019 | mkdir($iconDir) or die "Can't mkdir: $!\n"; 1020 | } 1021 | my $n; my $s; ($n,$_,$s) = fileparse($url, qr"\..*"); 1022 | $stations{$cs}{logoURL} = $url; 1023 | $logos{$cs}{logo} = $n; 1024 | $logos{$cs}{logoExt} = $s; 1025 | my $f = $iconDir . "/" . $n . $s; 1026 | if (! -e $f) { &wbf($f, &getURL($url,0)); } 1027 | } 1028 | 1029 | sub setOriginalAirDate { 1030 | if (substr($cp,10,4) ne '0000') { 1031 | if (!defined($programs{$cp}{originalAirDate}) 1032 | || ($schedule{$cs}{$sch}{time} < $programs{$cp}{originalAirDate})) { 1033 | $programs{$cp}{originalAirDate} = $schedule{$cs}{$sch}{time}; 1034 | } 1035 | } 1036 | } 1037 | 1038 | sub getZToken { 1039 | &login() if (!defined($zapToken)); 1040 | return $zapToken; 1041 | } 1042 | 1043 | sub parseZFavs { 1044 | my $buffer = shift; 1045 | my $t = decode_json($buffer); 1046 | if (defined($t->{'channels'})) { 1047 | my $m = $t->{'channels'}; 1048 | foreach my $f (@{$m}) { 1049 | if ($options{R}) { 1050 | my $r = &ua_post($urlRoot . "api/user/ChannelAddtofav", { token => $zapToken, prgsvcid => $f, addToFav => "false" }, 'X-Requested-With' => 'XMLHttpRequest'); 1051 | if ($r->is_success) { 1052 | &pout("Removed favorite $f\n"); 1053 | } else { 1054 | &perr("RF" . $r->status_line . "\n"); 1055 | } 1056 | } else { 1057 | $zapFavorites{$f} = 1; 1058 | } 1059 | } 1060 | if ($options{R}) { 1061 | &pout("Removed favorites, exiting\n"); 1062 | exit; 1063 | }; 1064 | &pout("Lineup favorites: " . (keys %zapFavorites) . "\n"); 1065 | } 1066 | } 1067 | 1068 | sub parseTVGFavs { 1069 | my $buffer = shift; 1070 | my $t = decode_json($buffer); 1071 | if (defined($t->{'message'})) { 1072 | my $m = $t->{'message'}; 1073 | foreach my $f (@{$m}) { 1074 | my $source = $f->{"source"}; 1075 | my $channel = $f->{"channel"}; 1076 | $tvgfavs{"$channel.$source"} = 1; 1077 | } 1078 | &pout("Lineup $zlineupId favorites: " . (keys %tvgfavs) . "\n"); 1079 | } 1080 | } 1081 | 1082 | sub parseTVGIcons { 1083 | require GD; 1084 | $rc = Encode::encode('utf8', &getURL($tvgspritesurl . "$zlineupId\.css",0) ); 1085 | if ($rc =~ /background-image:.+?url\((.+?)\)/) { 1086 | my $url = $tvgspritesurl . $1; 1087 | 1088 | if (! -d $iconDir) { 1089 | mkdir($iconDir) or die "Can't mkdir: $!\n"; 1090 | } 1091 | 1092 | ($n,$_,$s) = fileparse($url, qr"\..*"); 1093 | $f = $iconDir . "/sprites-" . $n . $s; 1094 | &wbf($f, &getURL($url,0)); 1095 | 1096 | GD::Image->trueColor(1); 1097 | $im = new GD::Image->new($f); 1098 | 1099 | my $iconw = 30; 1100 | my $iconh = 20; 1101 | while ($rc =~ /listings-channel-icon-(.+?)\{.+?position:.*?\-(\d+).+?(\d+).*?\}/isg) { 1102 | my $cid = $1; 1103 | my $iconx = $2; 1104 | my $icony = $3; 1105 | 1106 | my $icon = new GD::Image($iconw,$iconh); 1107 | $icon->alphaBlending(0); 1108 | $icon->saveAlpha(1); 1109 | $icon->copy($im, 0, 0, $iconx, $icony, $iconw, $iconh); 1110 | 1111 | $logos{$cid}{logo} = "sprite-" . $cid; 1112 | $logos{$cid}{logoExt} = $s; 1113 | 1114 | my $ifn = $iconDir . "/" . $logos{$cid}{logo} . $logos{$cid}{logoExt}; 1115 | &wbf($ifn, $icon->png); 1116 | } 1117 | } 1118 | } 1119 | 1120 | sub parseTVGD { 1121 | my $gz = gzopen(shift, "rb"); 1122 | my $buffer; 1123 | $buffer .= $b while $gz->gzread($b, 65535) > 0; 1124 | $gz->gzclose(); 1125 | my $t = decode_json($buffer); 1126 | 1127 | if (defined($t->{'program'})) { 1128 | my $prog = $t->{'program'}; 1129 | if (defined($prog->{'release_year'})) { 1130 | $programs{$cp}{movie_year} = $prog->{'release_year'}; 1131 | } 1132 | if (defined($prog->{'rating'}) && !defined($programs{$cp}{rating})) { 1133 | $programs{$cp}{rating} = $prog->{'rating'} if $prog->{'rating'} ne 'NR'; 1134 | } 1135 | } 1136 | 1137 | if (defined($t->{'tvobject'})) { 1138 | my $tvo = $t->{'tvobject'}; 1139 | if (defined($tvo->{'photos'})) { 1140 | my $photos = $tvo->{'photos'}; 1141 | my %phash; 1142 | foreach $ph (@{$photos}) { 1143 | my $w = $ph->{'width'} * $ph->{'height'}; 1144 | my $u = $ph->{'url'}; 1145 | $phash{$w} = $u; 1146 | } 1147 | my $big = (sort {$b <=> $a} keys %phash)[0]; 1148 | $programs{$cp}{imageUrl} = $phash{$big}; 1149 | } 1150 | } 1151 | } 1152 | 1153 | sub parseTVGGrid { 1154 | my $gz = gzopen(shift, "rb"); 1155 | my $buffer; 1156 | $buffer .= $b while $gz->gzread($b, 65535) > 0; 1157 | $gz->gzclose(); 1158 | my $t = decode_json($buffer); 1159 | 1160 | foreach my $e (@{$t}) { 1161 | my $cjs = $e->{'Channel'}; 1162 | my $src = $cjs->{'SourceId'}; 1163 | my $num = $cjs->{'Number'}; 1164 | 1165 | $cs = "$num.$src"; 1166 | 1167 | if (%tvgfavs) { 1168 | next if (!$tvgfavs{$cs}); 1169 | } 1170 | 1171 | if (!defined($stations{$cs}{stnNum})) { 1172 | $stations{$cs}{stnNum} = $src; 1173 | $stations{$cs}{number} = $num; 1174 | $stations{$cs}{name} = $cjs->{'Name'}; 1175 | if (defined($cjs->{'FullName'}) && $cjs->{'FullName'} ne $cjs->{'Name'}) { 1176 | if ($cjs->{'FullName'} ne '') { 1177 | $stations{$cs}{fullname} = $cjs->{'FullName'}; 1178 | } 1179 | } 1180 | 1181 | if (!defined($stations{$cs}{order})) { 1182 | if (defined($options{b})) { 1183 | $stations{$cs}{order} = $coNum++; 1184 | } else { 1185 | $stations{$cs}{order} = $stations{$cs}{number}; 1186 | } 1187 | } 1188 | } 1189 | 1190 | my $cps = $e->{'ProgramSchedules'}; 1191 | foreach my $pe (@{$cps}) { 1192 | next if (!defined($pe->{'ProgramId'})); 1193 | $cp = $pe->{'ProgramId'}; 1194 | my $catid = $pe->{'CatId'}; 1195 | 1196 | if ($catid == 1) { $programs{$cp}{genres}{movie} = 1 } 1197 | elsif ($catid == 2) { $programs{$cp}{genres}{sports} = 1 } 1198 | elsif ($catid == 3) { $programs{$cp}{genres}{family} = 1 } 1199 | elsif ($catid == 4) { $programs{$cp}{genres}{news} = 1 } 1200 | # 5 - 10? 1201 | # my $subcatid = $pe->{'SubCatId'}; 1202 | 1203 | my $ppid = $pe->{'ParentProgramId'}; 1204 | if ((defined($ppid) && $ppid != 0) 1205 | || (defined($options{j}) && $catid != 1)) { 1206 | $programs{$cp}{genres}{series} = 99; 1207 | } 1208 | 1209 | $programs{$cp}{title} = $pe->{'Title'}; 1210 | $tba = 1 if $programs{$cp}{title} =~ /$sTBA/i; 1211 | 1212 | if (defined($pe->{'EpisodeTitle'}) && $pe->{'EpisodeTitle'} ne '') { 1213 | $programs{$cp}{episode} = $pe->{'EpisodeTitle'}; 1214 | $tba = 1 if $programs{$cp}{episode} =~ /$sTBA/i; 1215 | } 1216 | 1217 | $programs{$cp}{description} = $pe->{'CopyText'} if defined($pe->{'CopyText'}) && $pe->{'CopyText'} ne ''; 1218 | $programs{$cp}{rating} = $pe->{'Rating'} if defined($pe->{'Rating'}) && $pe->{'Rating'} ne ''; 1219 | 1220 | my $sch = $pe->{'StartTime'} * 1000; 1221 | $schedule{$cs}{$sch}{time} = $sch; 1222 | $schedule{$cs}{$sch}{endtime} = $pe->{'EndTime'} * 1000; 1223 | $schedule{$cs}{$sch}{program} = $cp; 1224 | $schedule{$cs}{$sch}{station} = $cs; 1225 | 1226 | my $airat = $pe->{'AiringAttrib'}; 1227 | if ($airat & 1) { $schedule{$cs}{$sch}{live} = 1 } 1228 | elsif ($airat & 4) { $schedule{$cs}{$sch}{new} = 1 } 1229 | # other bits? 1230 | 1231 | my $tvo = $pe->{'TVObject'}; 1232 | if (defined($tvo)) { 1233 | if (defined($tvo->{'SeasonNumber'}) && $tvo->{'SeasonNumber'} != 0) { 1234 | $programs{$cp}{seasonNum} = $tvo->{'SeasonNumber'}; 1235 | if (defined($tvo->{'EpisodeNumber'}) && $tvo->{'EpisodeNumber'} != 0) { 1236 | $programs{$cp}{episodeNum} = $tvo->{'EpisodeNumber'}; 1237 | } 1238 | } 1239 | if (defined($tvo->{'EpisodeAirDate'})) { 1240 | my $eaid = $tvo->{'EpisodeAirDate'}; # GMT @ 00:00:00 1241 | $eaid =~ tr/\-0-9//cd; 1242 | $programs{$cp}{originalAirDate} = $eaid if ($eaid ne ''); 1243 | } 1244 | my $url; 1245 | if (defined($tvo->{'EpisodeSEOUrl'}) && $tvo->{'EpisodeSEOUrl'} ne '') { 1246 | $url = $tvo->{'EpisodeSEOUrl'}; 1247 | } elsif(defined($tvo->{'SEOUrl'}) && $tvo->{'SEOUrl'} ne '') { 1248 | $url = $tvo->{'SEOUrl'}; 1249 | $url = "/movies$url" if ($catid == 1 && $url !~ /movies/); 1250 | } 1251 | $programs{$cp}{url} = substr($tvgurl, 0, -1) . $url if defined($url); 1252 | } 1253 | 1254 | if (defined($options{I}) 1255 | || (defined($options{D}) && $programs{$cp}{genres}{movie}) 1256 | || (defined($options{W}) && $programs{$cp}{genres}{movie}) ) { 1257 | &getDetails(\&parseTVGD, $cp, $tvgMapiRoot . "listings/details?program=$cp", ""); 1258 | } 1259 | } 1260 | } 1261 | } 1262 | 1263 | sub getDetails { 1264 | my ($func, $cp, $url, $prefix) = @_; 1265 | my $fn = "$cacheDir/$prefix$cp\.js\.gz"; 1266 | if (! -e $fn) { 1267 | my $rs = &getURL($url,1); 1268 | if (length($rs)) { 1269 | $rc = Encode::encode('utf8', $rs); 1270 | &wbf($fn, Compress::Zlib::memGzip($rc)); 1271 | } 1272 | } 1273 | if (-e $fn) { 1274 | my $l = length($prefix) ? $prefix : "D"; 1275 | &pout("[$l] Parsing: $cp\n"); 1276 | $func->($fn); 1277 | } else { 1278 | &pout("Skipping: $cp\n"); 1279 | } 1280 | } 1281 | 1282 | sub parseJSON { 1283 | my $gz = gzopen(shift, "rb"); 1284 | my $buffer; 1285 | $buffer .= $b while $gz->gzread($b, 65535) > 0; 1286 | $gz->gzclose(); 1287 | my $t = decode_json($buffer); 1288 | 1289 | my $sts = $t->{'channels'}; 1290 | my %zapStarred=(); 1291 | foreach $s (@$sts) { 1292 | 1293 | if (defined($s->{'channelId'})) { 1294 | if (!$allChan && scalar(keys %zapFavorites)) { 1295 | if ($zapFavorites{$s->{channelId}}) { 1296 | if ($options{8}) { 1297 | next if $zapStarred{$s->{channelId}}; 1298 | $zapStarred{$s->{channelId}} = 1; 1299 | } 1300 | } else { 1301 | next; 1302 | } 1303 | } 1304 | # id (uniq) vs channelId, but id not nec consistent in cache 1305 | $cs = $s->{channelNo} . "." . $s->{channelId}; 1306 | $stations{$cs}{stnNum} = $s->{channelId}; 1307 | $stations{$cs}{name} = $s->{'callSign'}; 1308 | $stations{$cs}{number} = $s->{'channelNo'}; 1309 | $stations{$cs}{number} =~ s/^0+//g; 1310 | 1311 | if (!defined($stations{$cs}{order})) { 1312 | if (defined($options{b})) { 1313 | $stations{$cs}{order} = $coNum++; 1314 | } else { 1315 | $stations{$cs}{order} = $stations{$cs}{number}; 1316 | } 1317 | } 1318 | 1319 | if ($s->{'thumbnail'} ne '') { 1320 | my $url = $s->{'thumbnail'}; 1321 | $url =~ s/\?.*//; # remove size 1322 | if ($url !~ /^http/) { 1323 | $url = "https:" . $url; 1324 | } 1325 | $stations{$cs}{logoURL} = $url; 1326 | &handleLogo($url) if defined($iconDir); 1327 | } 1328 | 1329 | my $events = $s->{'events'}; 1330 | foreach $e (@$events) { 1331 | my $program = $e->{'program'}; 1332 | $cp = $program->{'id'}; 1333 | $programs{$cp}{title} = $program->{'title'}; 1334 | $tba = 1 if $programs{$cp}{title} =~ /$sTBA/i; 1335 | $programs{$cp}{episode} = $program->{'episodeTitle'} if ($program->{'episodeTitle'} ne ''); 1336 | $programs{$cp}{description} = $program->{'shortDesc'} if ($program->{'shortDesc'} ne ''); 1337 | $programs{$cp}{duration} = $e->{duration} if ($e->{duration} > 0); 1338 | $programs{$cp}{movie_year} = $program->{releaseYear} if ($program->{releaseYear} ne ''); 1339 | $programs{$cp}{seasonNum} = $program->{season} if ($program->{'season'} ne ''); 1340 | if ($program->{'episode'} ne '') { 1341 | $programs{$cp}{episodeNum} = $program->{episode}; 1342 | } 1343 | if ($e->{'thumbnail'} ne '') { 1344 | my $turl = $urlAssets; 1345 | $turl .= $e->{'thumbnail'} . ".jpg"; 1346 | $programs{$cp}{imageUrl} = $turl; 1347 | } 1348 | if ($program->{'seriesId'} ne '' && $program->{'tmsId'} ne '') { 1349 | $programs{$cp}{url} = $urlRoot . "/overview.html?programSeriesId=" 1350 | . $program->{seriesId} . "&tmsId=" . $program->{tmsId}; 1351 | } 1352 | 1353 | $sch = str2time1($e->{'startTime'}) * 1000; 1354 | $schedule{$cs}{$sch}{time} = $sch; 1355 | $schedule{$cs}{$sch}{endTime} = str2time1($e->{'endTime'}) * 1000; 1356 | $schedule{$cs}{$sch}{program} = $cp; 1357 | $schedule{$cs}{$sch}{station} = $cs; 1358 | 1359 | if ($e->{'filter'}) { 1360 | my $genres = $e->{'filter'}; 1361 | my $i = 1; 1362 | foreach $g (@{$genres}) { 1363 | $g =~ s/filter-//i; 1364 | ${$programs{$cp}{genres}}{lc($g)} = $i++; 1365 | } 1366 | } 1367 | 1368 | $programs{$cp}{rating} = $e->{rating} if ($e->{rating} ne ''); 1369 | 1370 | if ($e->{'tags'}) { 1371 | my $tags = $e->{'tags'}; 1372 | if (grep $_ eq 'CC', @{$tags}) { 1373 | $schedule{$cs}{$sch}{cc} = 1 1374 | } 1375 | } 1376 | 1377 | if ($e->{'flag'}) { 1378 | my $flags = $e->{'flag'}; 1379 | if (grep $_ eq 'New', @{$flags}) { 1380 | $schedule{$cs}{$sch}{new} = 'New' 1381 | &setOriginalAirDate(); 1382 | } 1383 | if (grep $_ eq 'Live', @{$flags}) { 1384 | $schedule{$cs}{$sch}{live} = 'Live' 1385 | &setOriginalAirDate(); # live to tape? 1386 | } 1387 | if (grep $_ eq 'Premiere', @{$flags}) { 1388 | $schedule{$cs}{$sch}{premiere} = 'Premiere'; 1389 | } 1390 | if (grep $_ eq 'Finale', @{$flags}) { 1391 | $schedule{$cs}{$sch}{finale} = 'Finale'; 1392 | } 1393 | } 1394 | 1395 | if ($options{D} && !$program->{isGeneric}) { 1396 | &postJSONO($cp, $program->{seriesId}); 1397 | } 1398 | if (defined($options{j}) && $cp !~ /^MV/) { 1399 | $programs{$cp}{genres}{series} = 99; 1400 | } 1401 | } 1402 | } 1403 | } 1404 | return 0; 1405 | } 1406 | 1407 | sub postJSONO { 1408 | my ($cp, $sid) = @_; 1409 | my $fn = "$cacheDir/O$cp\.js\.gz"; 1410 | 1411 | if (! -e $fn && defined($sidCache{$sid}) && -e $sidCache{$sid}) { 1412 | copy($sidCache{$sid}, $fn); 1413 | } 1414 | if (! -e $fn) { 1415 | my $url = $urlRoot . 'api/program/overviewDetails'; 1416 | &pout("[$treq] Post $sid: $url\n"); 1417 | sleep $sleeptime; # do these rapid requests flood servers? 1418 | my %phash = &getZapPParams(); 1419 | $phash{programSeriesID} = $sid; 1420 | $phash{'clickstream[FromPage]'} = 'TV%20Grid'; 1421 | my $r = &ua_post($url, \%phash, 'X-Requested-With' => 'XMLHttpRequest'); 1422 | if ($r->is_success) { 1423 | $dc = Encode::encode('utf8', $r->decoded_content( raise_error => 1 )); 1424 | &wbf($fn, Compress::Zlib::memGzip($dc)); 1425 | $sidCache{$sid} = $fn; 1426 | } else { 1427 | &perr($id . " :" . $r->status_line); 1428 | } 1429 | } 1430 | if (-e $fn) { 1431 | &pout("[D] Parsing: $cp\n"); 1432 | my $gz = gzopen($fn, "rb"); 1433 | my $buffer; 1434 | $buffer .= $b while $gz->gzread($b, 65535) > 0; 1435 | $gz->gzclose(); 1436 | my $t = decode_json($buffer); 1437 | 1438 | if ($t->{seriesGenres} ne '') { 1439 | my $i = 2; 1440 | my %gh = %{$programs{$cp}{genres}}; 1441 | if (keys %gh) { 1442 | my @genArr = sort { $gh{$a} <=> $gh{$b} } keys %gh; 1443 | my $max = $genArr[-1]; 1444 | $i = $gh{$max} + 1; 1445 | } 1446 | foreach my $sg (split(/\|/, lc($t->{seriesGenres}))) { 1447 | if (!${$programs{$cp}{genres}}{$sg}) { 1448 | ${$programs{$cp}{genres}}{$sg} = $i++; 1449 | } 1450 | } 1451 | } 1452 | 1453 | my $i = 1; 1454 | foreach my $c (@{$t->{'overviewTab'}->{cast}}) { 1455 | my $n = $c->{name}; 1456 | my $cn = $c->{characterName}; 1457 | my $cr = lc($c->{role}); 1458 | 1459 | if ($cr eq 'host') { 1460 | ${$programs{$cp}{presenter}}{$n} = $i++; 1461 | } else { 1462 | ${$programs{$cp}{actor}}{$n} = $i++; 1463 | ${$programs{$cp}{role}}{$n} = $cn if length($cn); 1464 | } 1465 | } 1466 | $i = 1; 1467 | foreach my $c (@{$t->{'overviewTab'}->{crew}}) { 1468 | my $n = $c->{name}; 1469 | my $cr = lc($c->{role}); 1470 | if ($cr =~ /producer/) { 1471 | ${$programs{$cp}{producer}}{$n} = $i++; 1472 | } elsif ($cr =~ /director/) { 1473 | ${$programs{$cp}{director}}{$n} = $i++; 1474 | } elsif ($cr =~ /writer/) { 1475 | ${$programs{$cp}{writer}}{$n} = $i++; 1476 | } 1477 | } 1478 | if (!defined($programs{$cp}{imageUrl}) && $t->{seriesImage} ne '') { 1479 | my $turl = $urlAssets; 1480 | $turl .= $t->{seriesImage} . ".jpg"; 1481 | $programs{$cp}{imageUrl} = $turl; 1482 | } 1483 | if ($cp =~ /^MV|^SH/ && length($t->{seriesDescription}) > length($programs{$cp}{description})) { 1484 | $programs{$cp}{description} = $t->{seriesDescription}; 1485 | } 1486 | if ($cp =~ /^EP/) { # GMT @ 00:00:00 1487 | my $ue = $t->{overviewTab}->{upcomingEpisode}; 1488 | if (defined($ue) && lc($ue->{tmsID}) eq lc($cp) 1489 | && $ue->{originalAirDate} ne '' 1490 | && $ue->{originalAirDate} ne '1000-01-01T00:00Z') { 1491 | $oad = str2time2($ue->{originalAirDate}) ; 1492 | $oad *= 1000; 1493 | $programs{$cp}{originalAirDate} = $oad; 1494 | } else { 1495 | foreach my $ue (@{$t->{upcomingEpisodeTab}}) { 1496 | if (lc($ue->{tmsID}) eq lc($cp) 1497 | && $ue->{originalAirDate} ne '' 1498 | && $ue->{originalAirDate} ne '1000-01-01T00:00Z' 1499 | ) { 1500 | $oad = str2time2($ue->{originalAirDate}) ; 1501 | $oad *= 1000; 1502 | $programs{$cp}{originalAirDate} = $oad; 1503 | last; 1504 | } 1505 | } 1506 | } 1507 | } 1508 | } else { 1509 | &pout("Skipping: $sid\n"); 1510 | } 1511 | } 1512 | 1513 | sub str2time1 { 1514 | my $t = Time::Piece->strptime(shift, '%Y-%m-%dT%H:%M:%SZ'); 1515 | return $t->epoch(); 1516 | } 1517 | 1518 | sub str2time2 { 1519 | my $t = Time::Piece->strptime(shift, '%Y-%m-%dT%H:%MZ'); 1520 | return $t->epoch(); 1521 | } 1522 | 1523 | sub hourToMillis { 1524 | ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(time); 1525 | if ($start == 0) { 1526 | $hour = int($hour/$gridHours) * $gridHours; 1527 | } else { 1528 | $hour = 0; 1529 | } 1530 | $t = timegm(0,0,$hour,$mday,$mon,$year); 1531 | $t = $t - (&tz_offset * 3600) if !defined($options{g}); 1532 | ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = gmtime($t); 1533 | $t = timegm($sec, $min, $hour,$mday,$mon,$year); 1534 | return $t . "000"; 1535 | } 1536 | 1537 | sub tz_offset { 1538 | my $n = defined $_[0] ? $_[0] : time; 1539 | my ($lm, $lh, $ly, $lyd) = (localtime $n)[1, 2, 5, 7]; 1540 | my ($gm, $gh, $gy, $gyd) = (gmtime $n)[1, 2, 5, 7]; 1541 | ($lm - $gm)/60 + $lh - $gh + 24 * ($ly - $gy || $lyd - $gyd) 1542 | } 1543 | 1544 | sub timezone { 1545 | my $tztime = defined $_[0] ? &_rtrim3(shift) : time; 1546 | my $os = sprintf "%.1f", (timegm(localtime($tztime)) - $tztime) / 3600; 1547 | my $mins = sprintf "%02d", abs( $os - int($os) ) * 60; 1548 | return sprintf("%+03d", int($os)) . $mins; 1549 | } 1550 | 1551 | sub max ($$) { $_[$_[0] < $_[1]] } 1552 | sub min ($$) { $_[$_[0] > $_[1]] } 1553 | 1554 | sub HELP_MESSAGE { 1555 | print < ($VERSION) 1557 | -u 1558 | -p 1559 | -d <# of days> (default = $days) 1560 | -n <# of no-cache days> (from end) (default = $ncdays) 1561 | -N <# of no-cache days> (from start) (default = $ncsdays) 1562 | -B 1563 | -s (default = $start) 1564 | -o (default = "$outFile") 1565 | -c (default = "$cacheDir") 1566 | -l (default = "$lang") 1567 | -i (default = don't download channel icons) 1568 | -m <#> = offset program times by # minutes (better to use TZ env var) 1569 | -b = retain website channel order 1570 | -x = output XTVD xml file format (default = XMLTV) 1571 | -w = wait on exit (require keypress before exiting) 1572 | -q = quiet (no status output) 1573 | -r <# of connection retries before failure> (default = $retries, max 20) 1574 | -e = hex encode entities (html special characters like accents) 1575 | -E "amp apos quot lt gt" = selectively encode standard XML entities 1576 | -F = output channel names first (rather than "number name") 1577 | -O = use old tv_grab_na style channel ids (C###nnnn.zap2it.com) 1578 | -A "new live" = append " *" to program titles that are "new" and/or "live" 1579 | -M = copy movie_year to empty movie sub-title tags 1580 | -U = UTF-8 encoding (default = "ISO-8859-1") 1581 | -L = output "" tag (not part of xmltv.dtd) 1582 | -T = don't cache files containing programs with "$sTBA" titles 1583 | -P = to use an http proxy 1584 | -C (default = "$confFile") 1585 | -S <#seconds> = sleep between requests to prevent flooding of server 1586 | -D = include details = 1 extra http request per program! 1587 | -I = include icons (image URLs) - 1 extra http request per program! 1588 | -J = include xmltv file in output 1589 | -Y (if not using username/password) 1590 | -Z (if not using username/password) 1591 | -z = use tvguide.com instead of zap2it.com 1592 | -a = output all channels (not just favorites) 1593 | -j = add "series" category to all non-movie programs 1594 | END 1595 | sleep(5) if ($^O eq 'MSWin32'); 1596 | exit 0; 1597 | } 1598 | --------------------------------------------------------------------------------