├── LICENSE ├── README.md ├── Show_Letterboxd_rating.user.js └── screenshot_amazon.png /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 | 676 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Letterboxd-userscript 2 | ===================== 3 | A userscript or greasemonkey script that shows the rating from letterboxd.com on several other websites like amazon, google play and others. 4 | 5 | 6 | This is a userscript for your browser. It's primarily designed for Firefox and Chrome with 7 | [Tampermonkey](https://www.tampermonkey.net/) [![Chrome logo](https://raw.githubusercontent.com/OpenUserJS/OpenUserJS.org/master/public/images/ua/chrome16.png)](https://chrome.google.com/webstore/detail/tampermonkey/dhdgffkkebhmkfjojejmpbldmpobfkfo) [![Firefox logo](https://raw.githubusercontent.com/OpenUserJS/OpenUserJS.org/master/public/images/ua/firefox16.png)](https://addons.mozilla.org/en-US/firefox/addon/tampermonkey/) 8 | or 9 | [FireMonkey ![Firefox logo](https://raw.githubusercontent.com/OpenUserJS/OpenUserJS.org/master/public/images/ua/firefox16.png)](https://addons.mozilla.org/en-US/firefox/addon/firemonkey/). 10 | 11 | General information about userscripts and how to use them can be found at [openuserjs.org/about/Userscript-Beginners-HOWTO](https://openuserjs.org/about/Userscript-Beginners-HOWTO). 12 | 13 | If you already have a userscript extension installed, you can click [here](https://greasyfork.org/scripts/386219-show-letterboxd-rating/code/Show%20Letterboxd%20rating.user.js) to install this script. 14 | 15 | 16 | I develop similar scripts for the [Metacritic](https://www.metacritic.com/) score, see [cvzi/Metacritic-userscript](https://github.com/cvzi/Metacritic-userscript/) 17 | and for the [Rotten Tomatoes](https://www.rottentomatoes.com/) score, see [cvzi/Rottentomatoes-userscript](https://github.com/cvzi/Rottentomatoes-userscript) 18 | 19 | 20 | Supported sites and products: 21 | * imdb.com 22 | * metacritic.com 23 | * letterboxd.com 24 | * BoxOfficeMojo 25 | * Amazon 26 | * Google Play 27 | * allmovie.com 28 | * Wikipedia 29 | * fandango.com 30 | * thetvdb.com 31 | * nme.com - New Musical Express 32 | * itunes.apple.com 33 | * save.tv 34 | * argenteam.net 35 | * [Radarr](https://github.com/Radarr/Radarr) - you need to set your server address in the script header, default is `@match http://localhost:7878/*` 36 | * trakt.tv 37 | 38 | Screenshot: 39 | ![Screenshot of amazon product page](https://raw.githubusercontent.com/cvzi/Letterboxd-userscript/master/screenshot_amazon.png) 40 | -------------------------------------------------------------------------------- /Show_Letterboxd_rating.user.js: -------------------------------------------------------------------------------- 1 | // ==UserScript== 2 | // @name Show Letterboxd rating 3 | // @description Show Letterboxd rating on imdb.com, metacritic.com, rottentomatoes.com, BoxOfficeMojo, Amazon, Google Play, allmovie.com, Wikipedia, themoviedb.org, fandango.com, thetvdb.com, save.tv 4 | // @namespace cuzi 5 | // @icon https://a.ltrbxd.com/logos/letterboxd-mac-icon.png 6 | // @updateURL https://openuserjs.org/meta/cuzi/Show_Letterboxd_rating.meta.js 7 | // @grant GM_xmlhttpRequest 8 | // @grant GM_setValue 9 | // @grant GM_getValue 10 | // @grant GM.xmlHttpRequest 11 | // @grant GM.setValue 12 | // @grant GM.getValue 13 | // @require https://ajax.googleapis.com/ajax/libs/jquery/3.7.1/jquery.min.js 14 | // @license GPL-3.0-or-later; https://www.gnu.org/licenses/gpl-3.0.txt 15 | // @version 27 16 | // @connect letterboxd.com 17 | // @match https://play.google.com/store/movies/details/* 18 | // @match https://www.amazon.ca/* 19 | // @match https://www.amazon.co.jp/* 20 | // @match https://www.amazon.co.uk/* 21 | // @match https://smile.amazon.co.uk/* 22 | // @match https://www.amazon.com.au/* 23 | // @match https://www.amazon.com.mx/* 24 | // @match https://www.amazon.com/* 25 | // @match https://smile.amazon.com/* 26 | // @match https://www.amazon.de/* 27 | // @match https://smile.amazon.de/* 28 | // @match https://www.amazon.es/* 29 | // @match https://www.amazon.fr/* 30 | // @match https://www.amazon.in/* 31 | // @match https://www.amazon.it/* 32 | // @match https://www.imdb.com/title/* 33 | // @match https://www.boxofficemojo.com/movies/* 34 | // @match https://www.boxofficemojo.com/release/* 35 | // @match https://www.allmovie.com/movie/* 36 | // @match https://en.wikipedia.org/* 37 | // @match https://www.fandango.com/* 38 | // @match https://flixster.com/movie/* 39 | // @match https://www.themoviedb.org/movie/* 40 | // @match https://www.rottentomatoes.com/m/* 41 | // @match https://rottentomatoes.com/m/* 42 | // @match https://www.metacritic.com/movie/* 43 | // @match https://www.nme.com/reviews/movie/* 44 | // @match https://www.nme.com/reviews/film-reviews/* 45 | // @match https://itunes.apple.com/* 46 | // @match https://thetvdb.com/movies/* 47 | // @match https://rlsbb.ru/*/ 48 | // @match https://www.sho.com/* 49 | // @match https://www.gog.com/* 50 | // @match https://psa.wf/* 51 | // @match https://www.save.tv/* 52 | // @match https://www.wikiwand.com/* 53 | // @match https://trakt.tv/* 54 | // @match http://localhost:7878/* 55 | // ==/UserScript== 56 | 57 | /* global GM, $, Image, DOMParser */ 58 | /* jshint asi: true, esversion: 8 */ 59 | 60 | const baseURL = 'https://letterboxd.com' 61 | const baseURLsearch = baseURL + '/s/autocompletefilm?q={query}&limit=20×tamp={timestamp}' 62 | const baseURLopenTab = baseURL + '/search/{query}/' 63 | const baseURLratingHistogram = baseURL + '/csi{url}rating-histogram/' 64 | const baseURLposter = baseURL + '/ajax/poster{film_url}std/70x105/' 65 | 66 | const cacheExpireAfterHours = 4 67 | 68 | function minutesSince (time) { 69 | const seconds = ((new Date()).getTime() - time.getTime()) / 1000 70 | return seconds > 60 ? parseInt(seconds / 60) + ' min ago' : 'now' 71 | } 72 | 73 | function fixLetterboxdURLs (html) { 74 | return html.replace(/ { 112 | xmlEntitiesElement.innerHTML = m 113 | return xmlEntitiesElement.textContent 114 | }) 115 | return s 116 | } 117 | const decodeXmlEntities = function (jsonObj) { 118 | // Traverse through object, decoding all strings 119 | if (jsonObj !== null && typeof jsonObj === 'object') { 120 | Object.entries(jsonObj).forEach(([key, value]) => { 121 | // key is either an array index or object key 122 | jsonObj[key] = decodeXmlEntities(value) 123 | }) 124 | } else if (typeof jsonObj === 'string') { 125 | return xmlEntities(jsonObj) 126 | } 127 | return jsonObj 128 | } 129 | 130 | const data = [] 131 | const scripts = document.querySelectorAll('script[type="application/ld+json"]') 132 | for (let i = 0; i < scripts.length; i++) { 133 | let jsonld 134 | if (scripts[i].innerText in parseLDJSONCache) { 135 | jsonld = parseLDJSONCache[scripts[i].innerText] 136 | } else { 137 | try { 138 | jsonld = JSON.parse(scripts[i].innerText) 139 | parseLDJSONCache[scripts[i].innerText] = jsonld 140 | } catch (e) { 141 | parseLDJSONCache[scripts[i].innerText] = null 142 | continue 143 | } 144 | } 145 | if (jsonld) { 146 | if (Array.isArray(jsonld)) { 147 | data.push(...jsonld) 148 | } else { 149 | data.push(jsonld) 150 | } 151 | } 152 | } 153 | for (let i = 0; i < data.length; i++) { 154 | try { 155 | if (data[i] && data[i] && (typeof condition !== 'function' || condition(data[i]))) { 156 | if (Array.isArray(keys)) { 157 | const r = [] 158 | for (let j = 0; j < keys.length; j++) { 159 | r.push(data[i][keys[j]]) 160 | } 161 | return decodeXmlEntities(r) 162 | } else if (keys) { 163 | return decodeXmlEntities(data[i][keys]) 164 | } else if (typeof condition === 'function') { 165 | return decodeXmlEntities(data[i]) // Return whole object 166 | } 167 | } 168 | } catch (e) { 169 | continue 170 | } 171 | } 172 | return decodeXmlEntities(data) 173 | } 174 | return null 175 | } 176 | 177 | async function addToWhiteList (letterboxdUrl) { 178 | const whitelist = JSON.parse(await GM.getValue('whitelist', '{}')) 179 | const docUrl = filterUniversalUrl(document.location.href) 180 | whitelist[docUrl] = letterboxdUrl 181 | await GM.setValue('whitelist', JSON.stringify(whitelist)) 182 | } 183 | 184 | async function removeFromWhiteList () { 185 | const whitelist = JSON.parse(await GM.getValue('whitelist', '{}')) 186 | const docUrl = filterUniversalUrl(document.location.href) 187 | if (docUrl in whitelist) { 188 | delete whitelist[docUrl] 189 | await GM.setValue('whitelist', JSON.stringify(whitelist)) 190 | } 191 | } 192 | 193 | const current = { 194 | type: null, 195 | query: null, 196 | year: null 197 | } 198 | 199 | async function searchMovie (query, type, year, forceList) { 200 | // Load data from letterboxd search API or from cache 201 | 202 | current.type = type 203 | current.query = query 204 | current.year = year 205 | 206 | let whitelist = JSON.parse(await GM.getValue('whitelist', '{}')) 207 | 208 | if (forceList) { 209 | whitelist = {} 210 | } 211 | 212 | const docUrl = filterUniversalUrl(document.location.href) 213 | if (docUrl in whitelist) { 214 | return loadMovieRating({ url: whitelist[docUrl] }) 215 | } 216 | 217 | const url = baseURLsearch.replace('{query}', encodeURIComponent(query)).replace('{timestamp}', encodeURIComponent(Date.now())) 218 | 219 | const cache = JSON.parse(await GM.getValue('cache', '{}')) 220 | 221 | // Delete cached values, that are expired 222 | for (const prop in cache) { 223 | if ((new Date()).getTime() - (new Date(cache[prop].time)).getTime() > cacheExpireAfterHours * 60 * 60 * 1000) { 224 | delete cache[prop] 225 | } 226 | } 227 | 228 | // Check cache or request new content 229 | if (url in cache) { 230 | // Use cached response 231 | handleSearchResponse(cache[url], forceList) 232 | } else { 233 | GM.xmlHttpRequest({ 234 | method: 'GET', 235 | url, 236 | onload: function (response) { 237 | // Save to chache 238 | 239 | response.time = (new Date()).toJSON() 240 | 241 | // Chrome fix: Otherwise JSON.stringify(cache) omits responseText 242 | const newobj = {} 243 | for (const key in response) { 244 | newobj[key] = response[key] 245 | } 246 | newobj.responseText = response.responseText 247 | 248 | cache[url] = newobj 249 | 250 | GM.setValue('cache', JSON.stringify(cache)) 251 | 252 | handleSearchResponse(response, forceList) 253 | }, 254 | onerror: function (response) { 255 | console.log('ShowLetterboxd: GM.xmlHttpRequest Error: ' + response.status + '\nURL: ' + url + '\nResponse:\n' + response.responseText) 256 | } 257 | }) 258 | } 259 | } 260 | 261 | function handleSearchResponse (response, forceList) { 262 | // Handle GM.xmlHttpRequest response 263 | 264 | const result = JSON.parse(response.responseText) 265 | 266 | if (forceList && (result.result === false || !result.data || !result.data.length)) { 267 | window.alert('Letterboxd userscript\n\nNo results for ' + current.query) 268 | } else if (result.result === false || !result.data || !result.data.length) { 269 | console.log('ShowLetterboxd: No results for ' + current.query) 270 | } else if (!forceList && result.data.length === 1) { 271 | loadMovieRating(result.data[0]) 272 | } else { 273 | // Sort results by closest match 274 | function matchQuality (title, year, originalTitle) { 275 | if (title === current.query && year === current.year) { 276 | return 105 + year 277 | } 278 | if (originalTitle && originalTitle === current.query && year === current.year) { 279 | return 104 + year 280 | } 281 | if (title === current.query && current.year) { 282 | return 103 - Math.abs(year - current.year) 283 | } 284 | if (originalTitle && originalTitle === current.query && current.year) { 285 | return 102 - Math.abs(year - current.year) 286 | } 287 | if (title.replace(/\(.+\)/, '').trim() === current.query && current.year) { 288 | return 101 - Math.abs(year - current.year) 289 | } 290 | if (originalTitle && originalTitle.replace(/\(.+\)/, '').trim() === current.query && current.year) { 291 | return 100 - Math.abs(year - current.year) 292 | } 293 | if (title === current.query) { 294 | return 12 295 | } 296 | if (originalTitle && originalTitle === current.query) { 297 | return 11 298 | } 299 | if (title.replace(/\(.+\)/, '').trim() === current.query) { 300 | return 10 301 | } 302 | if (originalTitle && originalTitle.replace(/\(.+\)/, '').trim() === current.query) { 303 | return 9 304 | } 305 | if (title.startsWith(current.query)) { 306 | return 8 307 | } 308 | if (originalTitle && originalTitle.startsWith(current.query)) { 309 | return 7 310 | } 311 | if (current.query.indexOf(title) !== -1) { 312 | return 6 313 | } 314 | if (originalTitle && current.query.indexOf(originalTitle) !== -1) { 315 | return 5 316 | } 317 | if (title.indexOf(current.query) !== -1) { 318 | return 4 319 | } 320 | if (originalTitle && originalTitle.indexOf(current.query) !== -1) { 321 | return 3 322 | } 323 | if (current.query.toLowerCase().indexOf(title.toLowerCase()) !== -1) { 324 | return 2 325 | } 326 | if (title.toLowerCase().indexOf(current.query.toLowerCase()) !== -1) { 327 | return 1 328 | } 329 | return 0 330 | } 331 | 332 | result.data.sort(function (a, b) { 333 | if (!Object.prototype.hasOwnProperty.call(a, 'matchQuality')) { 334 | a.matchQuality = matchQuality(a.name, a.releaseYear, a.originalName) 335 | } 336 | if (!Object.prototype.hasOwnProperty.call(b, 'matchQuality')) { 337 | b.matchQuality = matchQuality(b.name, b.releaseYear, b.originalName) 338 | } 339 | 340 | return b.matchQuality - a.matchQuality 341 | }) 342 | 343 | if (!forceList && result.data.length > 1 && result.data[0].matchQuality > 100 && result.data[1].matchQuality < result.data[0].matchQuality) { 344 | loadMovieRating(result.data[0]) 345 | } else { 346 | showMovieList(result.data, new Date(response.time)) 347 | } 348 | } 349 | } 350 | 351 | function showMovieList (arr, time) { 352 | // Show a small box in the right lower corner 353 | const parser = new DOMParser() 354 | $('#mcdiv321letterboxd').remove() 355 | const div = $('
').appendTo(document.body) 356 | div.css({ 357 | position: 'fixed', 358 | bottom: 0, 359 | right: 0, 360 | minWidth: 100, 361 | maxHeight: '80%', 362 | overflow: 'auto', 363 | backgroundColor: '#fff', 364 | border: '2px solid #bbb', 365 | borderRadius: ' 6px', 366 | boxShadow: '0 0 3px 3px rgba(100, 100, 100, 0.2)', 367 | color: '#000', 368 | padding: ' 3px', 369 | zIndex: '5010001', 370 | fontFamily: 'Helvetica,Arial,sans-serif' 371 | }) 372 | 373 | const imgFrame = function imgFrameFct (filmUrl, scale) { 374 | if (!filmUrl) { 375 | return 376 | } 377 | 378 | console.log(filmUrl) 379 | const url = baseURLposter.replace('{film_url}', filmUrl) 380 | console.log(url) 381 | 382 | const id = 'iframeimg' + Math.random() 383 | const mWidth = 180.0 * scale - 45.0 384 | const mHeight = 180.0 * scale - 25 385 | let html = ' ' 386 | html += '
' 387 | GM.xmlHttpRequest({ 388 | method: 'GET', 389 | url, 390 | onload: function (response) { 391 | const html = '' + response.responseText 392 | if (html.indexOf('srcset=')) { 393 | const doc = parser.parseFromString(html, 'text/html') 394 | const image = doc.querySelector('img.image') 395 | const img = new Image() 396 | img.src = image.src 397 | img.srcset = image.srcset 398 | img.style.maxWidth = mWidth + 'px' 399 | img.style.maxHeight = mHeight + 'px' 400 | document.getElementById(id).parentNode.replaceChild(img, document.getElementById(id)) 401 | } else { 402 | document.getElementById(id).src = 'data:text/html;charset=utf-8,' + encodeURIComponent(html) 403 | } 404 | } 405 | }) 406 | return html 407 | } 408 | 409 | // First result 410 | 411 | console.log(arr[0]) 412 | const first = $('
' + imgFrame(arr[0].url, 0.75) + '
' + arr[0].name + (arr[0].originalTitle ? ' [' + arr[0].originalTitle + ']' : '') + (arr[0].releaseYear ? ' (' + arr[0].releaseYear + ')' : '') + '
').click(selectMovie).appendTo(div) 413 | first[0].dataset.movie = JSON.stringify(arr[0]) 414 | 415 | // Shall the following results be collapsed by default? 416 | let more = null 417 | if ((arr.length > 1 && arr[0].matchQuality > 10) || arr.length > 10) { 418 | $('More results...').appendTo(div).click(function () { more.css('display', 'block'); this.parentNode.removeChild(this) }) 419 | more = $('
').appendTo(div) 420 | } else { 421 | more = $('
').appendTo(div) 422 | } 423 | 424 | // More results 425 | for (let i = 1; i < arr.length; i++) { 426 | const entry = $('
' + imgFrame(arr[i].url, 0.5) + '
' + arr[i].name + (arr[i].originalTitle ? ' [' + arr[i].originalTitle + ']' : '') + (arr[0].releaseYear ? ' (' + arr[0].releaseYear + ')' : '') + '
').click(selectMovie).appendTo(more) 427 | entry[0].dataset.movie = JSON.stringify(arr[i]) 428 | } 429 | 430 | // Footer 431 | const sub = $('
').appendTo(div) 432 | $('').appendTo(sub) 433 | $('@letterboxd.com').appendTo(sub) 434 | $('').appendTo(sub).click(function () { 435 | document.body.removeChild(this.parentNode.parentNode) 436 | }) 437 | } 438 | 439 | function selectMovie (ev) { 440 | ev.preventDefault() 441 | $('#mcdiv321letterboxd').html('Loading...') 442 | 443 | const data = JSON.parse(this.dataset.movie) 444 | 445 | loadMovieRating(data) 446 | 447 | addToWhiteList(data.url) 448 | } 449 | 450 | async function loadMovieRating (data) { 451 | // Load page from letterboxd 452 | 453 | if ('name' in data) { 454 | current.query = data.name 455 | } 456 | if ('releaseYear' in data) { 457 | current.year = data.releaseYear 458 | } 459 | 460 | const url = baseURLratingHistogram.replace('{url}', data.url) 461 | 462 | const cache = JSON.parse(await GM.getValue('cache', '{}')) 463 | 464 | // Delete cached values, that are expired 465 | for (const prop in cache) { 466 | if ((new Date()).getTime() - (new Date(cache[prop].time)).getTime() > cacheExpireAfterHours * 60 * 60 * 1000) { 467 | delete cache[prop] 468 | } 469 | } 470 | 471 | // Check cache or request new content 472 | if (url in cache) { 473 | // Use cached response 474 | showMovieRating(cache[url], data.url, data) 475 | } else { 476 | GM.xmlHttpRequest({ 477 | method: 'GET', 478 | url, 479 | onload: function (response) { 480 | // Save to chache 481 | response.time = (new Date()).toJSON() 482 | 483 | // Chrome fix: Otherwise JSON.stringify(cache) omits responseText 484 | const newobj = {} 485 | for (const key in response) { 486 | newobj[key] = response[key] 487 | } 488 | newobj.responseText = response.responseText 489 | 490 | cache[url] = newobj 491 | 492 | GM.setValue('cache', JSON.stringify(cache)) 493 | 494 | showMovieRating(newobj, data.url, data) 495 | }, 496 | onerror: function (response) { 497 | console.log('ShowLetterboxd: GM.xmlHttpRequest Error: ' + response.status + '\nURL: ' + url + '\nResponse:\n' + response.responseText) 498 | } 499 | }) 500 | } 501 | } 502 | 503 | function showMovieRating (response, letterboxdUrl, otherData) { 504 | // Show a small box in the right lower corner 505 | const time = new Date(response.time) 506 | 507 | $('#mcdiv321letterboxd').remove() 508 | 509 | const div = $('
').appendTo(document.body) 510 | div.css({ 511 | position: 'fixed', 512 | bottom: 0, 513 | right: 0, 514 | width: 230, 515 | minHeight: 44, 516 | color: '#789', 517 | padding: ' 3px', 518 | zIndex: '5010001', 519 | fontFamily: 'Helvetica,Arial,sans-serif' 520 | }) 521 | 522 | const CSS = `` 726 | 727 | $(CSS).appendTo(div) 728 | const section = $(fixLetterboxdURLs(response.responseText)).appendTo(div) 729 | 730 | section.find('h2').remove() 731 | 732 | let identName = current.query 733 | let identYear = current.year ? ' (' + current.year + ')' : '' 734 | let identOriginalName = '' 735 | let identDirector = '' 736 | if (otherData) { 737 | if ('name' in otherData && otherData.name) { 738 | identName = otherData.name 739 | } 740 | if ('year' in otherData && otherData.year) { 741 | identYear = ' (' + otherData.year + ')' 742 | } 743 | if ('originalName' in otherData && otherData.originalName) { 744 | identOriginalName = ' "' + otherData.originalName + '"' 745 | } 746 | if ('directors' in otherData) { 747 | identDirector = [] 748 | for (let i = 0; i < otherData.directors.length; i++) { 749 | if ('name' in otherData.directors[i]) { 750 | identDirector.push(otherData.directors[i].name) 751 | } 752 | } 753 | if (identDirector) { 754 | identDirector = '
Dir. ' + identDirector.join(', ') + '' 755 | } else { 756 | identDirector = '' 757 | } 758 | } 759 | } 760 | 761 | // Footer 762 | const sub = $('').appendTo(div) 763 | $('' + identName + identOriginalName + identYear + identDirector + '').appendTo(sub) 764 | $('
').appendTo(sub) 765 | $('').appendTo(sub) 766 | $('@letterboxd.com').appendTo(sub) 767 | $('').appendTo(sub).click(function () { 768 | document.getElementById('mcdiv321letterboxd').remove() 769 | }) 770 | $('🙅').appendTo(sub).click(function () { 771 | removeFromWhiteList() 772 | searchMovie(current.query, current.type, current.year, true) 773 | }) 774 | $('').appendTo(sub) 775 | } 776 | 777 | const Always = () => true 778 | const sites = { 779 | googleplay: { 780 | host: ['play.google.com'], 781 | condition: Always, 782 | products: [ 783 | { 784 | condition: () => ~document.location.href.indexOf('/movies/details/'), 785 | type: 'movie', 786 | data: () => document.querySelector('*[itemprop=name]').textContent 787 | } 788 | ] 789 | }, 790 | imdb: { 791 | host: ['imdb.com'], 792 | condition: () => !~document.location.pathname.indexOf('/mediaviewer') && !~document.location.pathname.indexOf('/mediaindex') && !~document.location.pathname.indexOf('/videoplayer'), 793 | products: [ 794 | { 795 | condition: function () { 796 | const e = document.querySelector("meta[property='og:type']") 797 | if (e && e.content === 'video.movie') { 798 | return true 799 | } else if (document.querySelector('[data-testid="hero__pageTitle"]') && !document.querySelector('[data-testid="hero-subnav-bar-left-block"] a[href*="episodes/"]')) { 800 | return true 801 | } 802 | return false 803 | }, 804 | type: 'movie', 805 | data: function () { 806 | let year = null 807 | if (document.querySelector('script[type="application/ld+json"]')) { 808 | const ld = parseLDJSON(['name', 'alternateName', 'datePublished']) 809 | if (ld.length > 2) { 810 | year = parseInt(ld[2].match(/\d{4}/)[0]) 811 | } 812 | if (ld.length > 1 && ld[1]) { 813 | console.debug('ShowLetterboxd: Movie ld+json alternateName', ld[1], year) 814 | return [ld[1], year] 815 | } 816 | console.debug('ShowLetterboxd: Movie ld+json name', ld[0], year) 817 | return [ld[0], year] 818 | } else { 819 | const m = document.title.match(/(.+?)\s+(\((\d+)\))? - /) 820 | console.debug('ShowLetterboxd: Movie ', [m[1], m[3]]) 821 | return [m[1], parseInt(m[3])] 822 | } 823 | } 824 | } 825 | ] 826 | }, 827 | metacritic: { 828 | host: ['www.metacritic.com'], 829 | condition: () => document.querySelector("meta[property='og:type']"), 830 | products: [{ 831 | condition: () => document.querySelector("meta[property='og:type']").content === 'video.movie', 832 | type: 'movie', 833 | data: function () { 834 | let year = null 835 | if (document.querySelector('.release_year')) { 836 | year = parseInt(document.querySelector('.release_year').firstChild.textContent) 837 | } else if (document.querySelector('.release_data .data')) { 838 | year = document.querySelector('.release_data .data').textContent.match(/(\d{4})/)[1] 839 | } 840 | 841 | return [document.querySelector("meta[property='og:title']").content, year] 842 | } 843 | }] 844 | }, 845 | amazon: { 846 | host: ['amazon.'], 847 | condition: Always, 848 | products: [{ 849 | condition: () => document.querySelector('[data-automation-id=title]'), 850 | type: 'movie', 851 | data: () => document.querySelector('[data-automation-id=title]').textContent.trim().replace(/\[.{1,8}\]/, '') 852 | }, 853 | { 854 | condition: () => document.querySelector('#watchNowContainer a[href*="/gp/video/"]'), 855 | type: 'movie', 856 | data: () => document.getElementById('productTitle').textContent.trim() 857 | }] 858 | }, 859 | BoxOfficeMojo: { 860 | host: ['boxofficemojo.com'], 861 | condition: () => Always, 862 | products: [ 863 | { 864 | condition: () => document.location.pathname.startsWith('/release/'), 865 | type: 'movie', 866 | data: function () { 867 | let year = null 868 | const cells = document.querySelectorAll('#body .mojo-summary-values .a-section span') 869 | for (let i = 0; i < cells.length; i++) { 870 | if (~cells[i].innerText.indexOf('Release Date')) { 871 | year = parseInt(cells[i].nextElementSibling.textContent.match(/\d{4}/)[0]) 872 | break 873 | } 874 | } 875 | return [document.querySelector('meta[name=title]').content, year] 876 | } 877 | }, 878 | { 879 | condition: () => ~document.location.search.indexOf('id=') && document.querySelector('#body table:nth-child(2) tr:first-child b'), 880 | type: 'movie', 881 | data: function () { 882 | let year = null 883 | try { 884 | const tds = document.querySelectorAll('#body table:nth-child(2) tr:first-child table table table td') 885 | for (let i = 0; i < tds.length; i++) { 886 | if (~tds[i].innerText.indexOf('Release Date')) { 887 | year = parseInt(tds[i].innerText.match(/\d{4}/)[0]) 888 | break 889 | } 890 | } 891 | } catch (e) { } 892 | return [document.querySelector('#body table:nth-child(2) tr:first-child b').firstChild.textContent, year] 893 | } 894 | }] 895 | }, 896 | AllMovie: { 897 | host: ['allmovie.com'], 898 | condition: () => document.querySelector('h2.movie-title'), 899 | products: [{ 900 | condition: () => document.querySelector('h2.movie-title'), 901 | type: 'movie', 902 | data: () => document.querySelector('h2.movie-title').firstChild.textContent.trim() 903 | }] 904 | }, 905 | 'en.wikipedia': { 906 | host: ['en.wikipedia.org'], 907 | condition: Always, 908 | products: [{ 909 | condition: function () { 910 | if (!document.querySelector('.infobox .summary')) { 911 | return false 912 | } 913 | const r = /\d\d\d\d films/ 914 | return $('#catlinks a').filter((i, e) => e.firstChild.textContent.match(r)).length 915 | }, 916 | type: 'movie', 917 | data: () => document.querySelector('.infobox .summary').firstChild.textContent 918 | }] 919 | }, 920 | fandango: { 921 | host: ['fandango.com'], 922 | condition: () => document.querySelector("meta[property='og:title']"), 923 | products: [{ 924 | condition: Always, 925 | type: 'movie', 926 | data: () => document.querySelector("meta[property='og:title']").content.match(/(.+?)\s+\(\d{4}\)/)[1].trim() 927 | }] 928 | }, 929 | flixster: { 930 | host: ['flixster.com'], 931 | condition: () => Always, 932 | products: [{ 933 | condition: () => parseLDJSON('@type') === 'Movie', 934 | type: 'movie', 935 | data: () => parseLDJSON('name', (j) => (j['@type'] === 'Movie')) 936 | }] 937 | }, 938 | themoviedb: { 939 | host: ['themoviedb.org'], 940 | condition: () => document.querySelector("meta[property='og:type']"), 941 | products: [{ 942 | condition: () => document.querySelector("meta[property='og:type']").content === 'movie' || 943 | document.querySelector("meta[property='og:type']").content === 'video.movie', 944 | type: 'movie', 945 | data: function () { 946 | let year = null 947 | try { 948 | year = parseInt(document.querySelector('.release_date').innerText.match(/\d{4}/)[0]) 949 | } catch (e) {} 950 | 951 | return [document.querySelector("meta[property='og:title']").content, year] 952 | } 953 | }] 954 | }, 955 | rottentomatoes: { 956 | host: ['rottentomatoes.com'], 957 | condition: Always, 958 | products: [{ 959 | condition: () => document.location.pathname.startsWith('/m/'), 960 | type: 'movie', 961 | data: () => document.querySelector('h1').firstChild.textContent 962 | } 963 | ] 964 | }, 965 | nme: { 966 | host: ['nme.com'], 967 | condition: () => document.location.pathname.startsWith('/reviews/'), 968 | products: [{ 969 | condition: () => document.querySelector('.tdb-breadcrumbs a[href*="/reviews/film-reviews"]'), 970 | type: 'movie', 971 | data: function () { 972 | let year = null 973 | try { 974 | year = parseInt(document.querySelector('*[itemprop=datePublished]').content.match(/\d{4}/)[0]) 975 | } catch (e) {} 976 | 977 | try { 978 | return [document.title.match(/[‘'](.+?)[’']/)[1], year] 979 | } catch (e) { 980 | try { 981 | return [document.querySelector('h1.tdb-title-text').textContent.match(/[‘'](.+?)[’']/)[1], year] 982 | } catch (e) { 983 | return [document.querySelector('h1').textContent.match(/:\s*(.+)/)[1].trim(), year] 984 | } 985 | } 986 | } 987 | }] 988 | }, 989 | TheTVDB: { 990 | host: ['thetvdb.com'], 991 | condition: Always, 992 | products: [{ 993 | condition: () => document.location.pathname.startsWith('/movies/'), 994 | type: 'movie', 995 | data: () => document.getElementById('series_title').firstChild.textContent.trim() 996 | }] 997 | }, 998 | itunes: { 999 | host: ['itunes.apple.com'], 1000 | condition: Always, 1001 | products: [{ 1002 | condition: () => ~document.location.href.indexOf('/movie/'), 1003 | type: 'movie', 1004 | data: () => parseLDJSON('name', (j) => (j['@type'] === 'Movie')) 1005 | }] 1006 | }, 1007 | RlsBB: { 1008 | host: ['rlsbb.ru'], 1009 | condition: () => document.querySelectorAll('.post').length === 1, 1010 | products: [ 1011 | { 1012 | condition: () => document.querySelector('#post-wrapper .entry-meta a[href*="/category/movies/"]'), 1013 | type: 'movie', 1014 | data: () => document.querySelector('h1.entry-title').textContent.match(/(.+?)\s+\d{4}/)[1].trim() 1015 | }] 1016 | }, 1017 | showtime: { 1018 | host: ['sho.com'], 1019 | condition: Always, 1020 | products: [ 1021 | { 1022 | condition: () => parseLDJSON('@type') === 'Movie', 1023 | type: 'movie', 1024 | data: () => parseLDJSON('name', (j) => (j['@type'] === 'Movie')) 1025 | }] 1026 | }, 1027 | gog: { 1028 | host: ['www.gog.com'], 1029 | condition: () => document.querySelector('.productcard-basics__title'), 1030 | products: [{ 1031 | condition: () => document.location.pathname.split('/').length > 2 && ( 1032 | document.location.pathname.split('/')[1] === 'movie' || 1033 | document.location.pathname.split('/')[2] === 'movie'), 1034 | type: 'movie', 1035 | data: () => document.querySelector('.productcard-basics__title').textContent 1036 | }] 1037 | }, 1038 | psapm: { 1039 | host: ['psa.wf'], 1040 | condition: Always, 1041 | products: [ 1042 | { 1043 | condition: () => document.location.pathname.startsWith('/movie/'), 1044 | type: 'movie', 1045 | data: function () { 1046 | const title = document.querySelector('h1').textContent.trim() 1047 | const m = title.match(/(.+)\((\d+)\)$/) 1048 | if (m) { 1049 | return [m[1].trim(), parseInt(m[2])] 1050 | } else { 1051 | return title 1052 | } 1053 | } 1054 | }] 1055 | }, 1056 | 'save.tv': { 1057 | host: ['save.tv'], 1058 | condition: () => document.location.pathname.startsWith('/STV/M/obj/archive/'), 1059 | products: [ 1060 | { 1061 | condition: () => document.location.pathname.startsWith('/STV/M/obj/archive/'), 1062 | type: 'movie', 1063 | data: function () { 1064 | let title = null 1065 | if (document.querySelector("span[data-bind='text:OrigTitle']")) { 1066 | title = document.querySelector("span[data-bind='text:OrigTitle']").textContent 1067 | } else { 1068 | title = document.querySelector("h2[data-bind='text:Title']").textContent 1069 | } 1070 | let year = null 1071 | if (document.querySelector("span[data-bind='text:ProductionYear']")) { 1072 | year = parseInt(document.querySelector("span[data-bind='text:ProductionYear']").textContent) 1073 | } 1074 | return [title, year] 1075 | } 1076 | } 1077 | ] 1078 | }, 1079 | wikiwand: { 1080 | host: ['www.wikiwand.com'], 1081 | condition: Always, 1082 | products: [{ 1083 | condition: function () { 1084 | const title = document.querySelector('h1').textContent.toLowerCase() 1085 | const subtitle = document.querySelector('h2[class*="subtitle"]') ? document.querySelector('h2[class*="subtitle"]').textContent.toLowerCase() : '' 1086 | if (title.indexOf('film') === -1 && !subtitle) { 1087 | return false 1088 | } 1089 | return title.indexOf('film') !== -1 || 1090 | subtitle.indexOf('film') !== -1 || 1091 | subtitle.indexOf('movie') !== -1 1092 | }, 1093 | type: 'movie', 1094 | data: () => document.querySelector('h1').textContent.replace(/\((\d{4} )?film\)/i, '').trim() 1095 | }] 1096 | }, 1097 | trakt: { 1098 | host: ['trakt.tv'], 1099 | condition: Always, 1100 | products: [ 1101 | { 1102 | condition: () => document.location.pathname.startsWith('/movies/'), 1103 | type: 'movie', 1104 | data: function () { 1105 | const title = Array.from(document.querySelector('.summary h1').childNodes).filter(node => node.nodeType === node.TEXT_NODE).map(node => node.textContent).join(' ').trim() 1106 | const year = document.querySelector('.summary h1 .year').textContent 1107 | return [title, year] 1108 | } 1109 | } 1110 | ] 1111 | }, 1112 | radarr: { 1113 | host: ['*'], 1114 | condition: () => document.location.pathname.startsWith('/movie/'), 1115 | products: [{ 1116 | condition: () => document.querySelector('[class*="MovieDetails-title"] span'), 1117 | type: 'movie', 1118 | data: () => { 1119 | let year = null 1120 | if (document.querySelector('[class*="MovieDetails-yea"] span')) { 1121 | year = document.querySelector('[class*="MovieDetails-yea"] span').textContent.trim() 1122 | } 1123 | return [document.querySelector('[class*="MovieDetails-title"] span').textContent.trim(), year] 1124 | } 1125 | }] 1126 | } 1127 | 1128 | } 1129 | 1130 | function main () { 1131 | let dataFound = false 1132 | for (const name in sites) { 1133 | const site = sites[name] 1134 | if (site.host.some(function (e) { return ~this.indexOf(e) || e === '*' }, document.location.hostname) && site.condition()) { 1135 | for (let i = 0; i < site.products.length; i++) { 1136 | if (site.products[i].condition()) { 1137 | // Try to retrieve item name from page 1138 | let data 1139 | try { 1140 | data = site.products[i].data() 1141 | } catch (e) { 1142 | data = false 1143 | console.error(`ShowLetterboxd: Error in data() of site='${name}', type='${site.products[i].type}'`) 1144 | console.error(e) 1145 | } 1146 | if (data) { 1147 | if (Array.isArray(data)) { 1148 | if (data[1]) { 1149 | searchMovie(data[0].trim(), site.products[i].type, parseInt(data[1])) 1150 | } else { 1151 | searchMovie(data.trim(), site.products[i].type) 1152 | } 1153 | } else { 1154 | searchMovie(data.trim(), site.products[i].type) 1155 | } 1156 | dataFound = true 1157 | } 1158 | break 1159 | } 1160 | } 1161 | break 1162 | } 1163 | } 1164 | return dataFound 1165 | } 1166 | 1167 | async function adaptForRottentomatoesScript () { 1168 | // Move this container above the rottentomatoes container and if the meta container is on the right side above both 1169 | const letterC = document.getElementById('mcdiv321letterboxd') 1170 | const metaC = document.getElementById('mcdiv123') 1171 | const rottenC = document.getElementById('mcdiv321rotten') 1172 | 1173 | if (!letterC || (!metaC && !rottenC)) { 1174 | return 1175 | } 1176 | const letterBounds = letterC.getBoundingClientRect() 1177 | 1178 | let bottom = 0 1179 | if (metaC) { 1180 | const metaBounds = metaC.getBoundingClientRect() 1181 | if (Math.abs(metaBounds.right - letterBounds.right) < 20 && metaBounds.top > 20) { 1182 | bottom += metaBounds.height 1183 | } 1184 | } 1185 | if (rottenC) { 1186 | const rottenBounds = rottenC.getBoundingClientRect() 1187 | if (Math.abs(rottenBounds.right - letterBounds.right) < 20 && rottenBounds.top > 20) { 1188 | bottom += rottenBounds.height 1189 | } 1190 | } 1191 | 1192 | if (bottom > 0) { 1193 | letterC.style.bottom = bottom + 'px' 1194 | } 1195 | } 1196 | 1197 | (function () { 1198 | const firstRunResult = main() 1199 | let lastLoc = document.location.href 1200 | let lastContent = document.body.innerText 1201 | let lastCounter = 0 1202 | function newpage () { 1203 | if (lastContent === document.body.innerText && lastCounter < 15) { 1204 | window.setTimeout(newpage, 500) 1205 | lastCounter++ 1206 | } else { 1207 | lastContent = document.body.innerText 1208 | lastCounter = 0 1209 | const re = main() 1210 | if (!re) { // No page matched or no data found 1211 | window.setTimeout(newpage, 1000) 1212 | } 1213 | } 1214 | } 1215 | window.setInterval(function () { 1216 | adaptForRottentomatoesScript() 1217 | if (document.location.href !== lastLoc) { 1218 | lastLoc = document.location.href 1219 | $('#mcdiv321letterboxd').remove() 1220 | 1221 | window.setTimeout(newpage, 1000) 1222 | } 1223 | }, 500) 1224 | 1225 | if (!firstRunResult) { 1226 | // Initial run had no match, let's try again there may be new content 1227 | window.setTimeout(main, 2000) 1228 | } 1229 | })() 1230 | -------------------------------------------------------------------------------- /screenshot_amazon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cvzi/Letterboxd-userscript/2632b9e59c89b485ebc047e47ca91fa1e930b7bf/screenshot_amazon.png --------------------------------------------------------------------------------