IMDb: Link 'em all!

Adds all kinds of links to IMDb, customizable!

当前为 2021-07-12 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name IMDb: Link 'em all!
  3. // @description Adds all kinds of links to IMDb, customizable!
  4. // @namespace https://greasyfork.org/en/users/8981-buzz
  5. // @match *://*.imdb.com/title/tt*/*
  6. // @connect *
  7. // @require https://greasemonkey.github.io/gm4-polyfill/gm4-polyfill.js
  8. // @require https://unpkg.com/preact@10.5.7/dist/preact.umd.js
  9. // @require https://unpkg.com/preact@10.5.7/hooks/dist/hooks.umd.js
  10. // @license GPLv2
  11. // @noframes
  12. // @author buzz
  13. // @version 2.0.8
  14. // @grant GM_getValue
  15. // @grant GM_setValue
  16. // @grant GM_xmlhttpRequest
  17. // @grant GM.getValue
  18. // @grant GM.setValue
  19. // @grant GM.xmlHttpRequest
  20. // ==/UserScript==
  21. (function (preact, hooks) {
  22. 'use strict';
  23.  
  24. var version = "2.0.8";
  25. var description = "Adds all kinds of links to IMDb, customizable!";
  26. var homepage = "https://github.com/buzz/imdb-link-em-all#readme";
  27.  
  28. const DESCRIPTION = description;
  29. const HOMEPAGE = homepage;
  30. const NAME_VERSION = `Link 'em all! v${version}`;
  31. const SITES_URL = 'https://raw.githubusercontent.com/buzz/imdb-link-em-all/master/sites.json'; // gets replaced by rollup!
  32.  
  33. const GM_CONFIG_KEY = 'config';
  34. const GREASYFORK_URL = 'https://greasyfork.org/scripts/17154-imdb-link-em-all';
  35. const DEFAULT_CONFIG = {
  36. enabled_sites: [],
  37. fetch_results: true,
  38. first_run: true,
  39. open_blank: true,
  40. show_category_captions: true
  41. };
  42. const CATEGORIES = {
  43. search: 'Search',
  44. movie_site: 'Movie sites',
  45. pub_tracker: 'Public trackers',
  46. priv_tracker: 'Private trackers',
  47. streaming: 'Streaming',
  48. filehoster: 'Filehosters',
  49. subtitles: 'Subtitles',
  50. tv: 'TV'
  51. };
  52. const FETCH_STATE = {
  53. LOADING: 0,
  54. NO_RESULTS: 1,
  55. RESULTS_FOUND: 2,
  56. NO_ACCESS: 3,
  57. TIMEOUT: 4,
  58. ERROR: 5
  59. };
  60.  
  61. var img$8 = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAQAAAC1+jfqAAABkklEQVQoz1VRTUsCYRBe/AHe/Qn+j710EbKLVJcyiAqLLkWJkdkSUdChOpQRRWVRG6RQUqirtq7pupZRUvRxyOIlIU9ed5+mbSFjDjPvzDPPM+8MB+7PVG9ekiXJ25qzXMVZtqu2fP0D7xDrZ7aY/djZAqiEy3qRKY4se8ULYizqENm+vhO2ADf+Z3zhCdlmyqjiDieG2FTBEMeC3wQUA7LxTIVHAlVNfwsVV5gwRgOWRE64QwkFXGAD28hCQYb65wVT4kqTa+nGAzQkMKOM81P8knJJIA2LjblaSONk/ZOICyhjD7P8T886L0ImNoUGHtI5SX8jTYU6olg2Aav8ATHEkaZ8j87taEu1rcY1QUrYVNb4FZLIkNw5+hqeWodmDikKORorhzwOsU9RCqcUDQjWHo4CEeOeyioqNEuemHJI0mvY6P/95q4/gVdEEGoKhkzqPmO4GSH9abj91h6C4RG9j405Qkwlhl7W6fAwl94WbjnWiHPQPmkL1pOIoaveaeu2u5z/rvlrPq9Hapfc/879DQmIXQjyme6GAAAAAElFTkSuQmCC";
  62.  
  63. var img$7 = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAIsSURBVDjLpVNLSJQBEP7+h6uu62vLVAJDW1KQTMrINQ1vPQzq1GOpa9EppGOHLh0kCEKL7JBEhVCHihAsESyJiE4FWShGRmauu7KYiv6Pma+DGoFrBQ7MzGFmPr5vmDFIYj1mr1WYfrHPovA9VVOqbC7e/1rS9ZlrAVDYHig5WB0oPtBI0TNrUiC5yhP9jeF4X8NPcWfopoY48XT39PjjXeF0vWkZqOjd7LJYrmGasHPCCJbHwhS9/F8M4s8baid764Xi0Ilfp5voorpJfn2wwx/r3l77TwZUvR+qajXVn8PnvocYfXYH6k2ioOaCpaIdf11ivDcayyiMVudsOYqFb60gARJYHG9DbqQFmSVNjaO3K2NpAeK90ZCqtgcrjkP9aUCXp0moetDFEeRXnYCKXhm+uTW0CkBFu4JlxzZkFlbASz4CQGQVBFeEwZm8geyiMuRVntzsL3oXV+YMkvjRsydC1U+lhwZsWXgHb+oWVAEzIwvzyVlk5igsi7DymmHlHsFQR50rjl+981Jy1Fw6Gu0ObTtnU+cgs28AKgDiy+Awpj5OACBAhZ/qh2HOo6i+NeA73jUAML4/qWux8mt6NjW1w599CS9xb0mSEqQBEDAtwqALUmBaG5FV3oYPnTHMjAwetlWksyByaukxQg2wQ9FlccaK/OXA3/uAEUDp3rNIDQ1ctSk6kHh1/jRFoaL4M4snEMeD73gQx4M4PsT1IZ5AfYH68tZY7zv/ApRMY9mnuVMvAAAAAElFTkSuQmCC";
  64.  
  65. var img$6 = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAKcSURBVDjLpZPLa9RXHMU/d0ysZEwmMQqZiTaP0agoaKGJUiwIxU0hUjtUQaIuXHSVbRVc+R8ICj5WvrCldJquhVqalIbOohuZxjDVxDSP0RgzyST9zdzvvffrQkh8tBs9yy9fPhw45xhV5X1U8+Yhc3U0LcEdVxdOVq20OA0ooQjhpnfhzuDZTx6++m9edfDFlZGMtXKxI6HJnrZGGtauAWAhcgwVnnB/enkGo/25859l3wIcvpzP2EhuHNpWF9/dWs/UnKW4EOGDkqhbQyqxjsKzMgM/P1ymhlO5C4ezK4DeS/c7RdzQoa3x1PaWenJjJZwT9rQ1gSp/js1jYoZdyfX8M1/mp7uFaTR8mrt29FEMQILr62jQ1I5kA8OF59jIItVA78dJertTiBNs1ZKfLNG+MUHX1oaURtIHEAOw3p/Y197MWHEJEUGCxwfHj8MTZIcnsGKxzrIURYzPLnJgbxvG2hMrKdjItjbV11CYKeG8R7ygIdB3sBMFhkem0RAAQ3Fuka7UZtRHrasOqhYNilOwrkrwnhCU/ON5/q04vHV48ThxOCuoAbxnBQB+am65QnO8FqMxNCjBe14mpHhxBBGCWBLxD3iyWMaYMLUKsO7WYH6Stk1xCAGccmR/Ozs/bKJuXS39R/YgIjgROloSDA39Deit1SZWotsjD8pfp5ONqZ6uTfyWn+T7X0f59t5fqDhUA4ry0fYtjJcWeZQvTBu4/VqRuk9/l9Fy5cbnX+6Od26s58HjWWaflwkusKGxjm1bmhkvLXHvh1+WMbWncgPfZN+qcvex6xnUXkzvSiYP7EvTvH4toDxdqDD4+ygT+cKMMbH+3MCZ7H9uAaDnqytpVX8cDScJlRY0YIwpAjcNcuePgXP/P6Z30QuoP4J7WbYhuQAAAABJRU5ErkJggg==";
  66.  
  67. var img$5 = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAJ/SURBVDjLbVJBaxNBGH2bpEkTmxi1NTRKTZtoQUHEWz0Igj2I4kG9eVNQhEBO7bEHc+yv8JAiHnr2B4gFqVrQRhObljQolBSTJqZJdnZmfbNr2rU68DEz33zfm/fejGHbNrxjaWlpRCk1J6WcYZxkgPGTsWJZ1mIul/vlrTe8AIVC4Qqbl5PJ5GQsFoPP5wP36PV6qNfr2OIg0L35+fm1fwDYPMLDj+l0OmOaJmq1Gjqdjr4dgUAAiUTCqSsWixvMXV5YWOjqvW+AxOSz8fHxjBAC5XJ5s91up7gO6tDrUqn0QwOTXYZSsoO+wGDB5EwkEkGlUgGb7mSz2apHajWfz9+sVqvFVCrl1P4PYExr5m16vYUjQ+c0O11DtmN/ebD95pG9UpnGzl7Y0Xz30ir8toAtLdiWG0JIvFi76piaGG7g9plVTD/5YLgMCPLg/g0YtMTwhznfApRBfsP6kAYJSKuN57Md5oXTsvHy7aEEfZMutHZfIRAahWGMsHAICMeZVsD+HmTrG8zudyhrH+HJLGyz7wEgRSh9k4nm+nvqPIb4xWuovV5k/2lMXJ9F8+s6ARqIpk6QsIQtTC+AcGTYpBqfvgBfcJTuKMi+xKfdMCZgIp6eRK8TYu2+w2oA4PwDm+5qVK218XmNLN7xxILqKfS7pGqTWekLmuVtV65STs8hA73RqJQQP5+CP3KKACamHj7FlGBDawfH00kEW0MuA8o9AmA6qMrSHqwTIAoM08hAkHkN0ES3UYfotBGdiNFu5cr2AmgJobOPET7nhxEMuU/o40soSjO7iHbbVNgnUen6pY0/AOCTbC7PuV44H0f8Cetg5g9zP5aU7loDcfwGcrKyzYdvwUUAAAAASUVORK5CYII=";
  68.  
  69. var img$4 = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAGrSURBVDjLvZPZLkNhFIV75zjvYm7VGFNCqoZUJ+roKUUpjRuqp61Wq0NKDMelGGqOxBSUIBKXWtWGZxAvobr8lWjChRgSF//dv9be+9trCwAI/vIE/26gXmviW5bqnb8yUK028qZjPfoPWEj4Ku5HBspgAz941IXZeze8N1bottSo8BTZviVWrEh546EO03EXpuJOdG63otJbjBKHkEp/Ml6yNYYzpuezWL4s5VMtT8acCMQcb5XL3eJE8VgBlR7BeMGW9Z4yT9y1CeyucuhdTGDxfftaBO7G4L+zg91UocxVmCiy51NpiP3n2treUPujL8xhOjYOzZYsQWANyRYlU4Y9Br6oHd5bDh0bCpSOixJiWx71YY09J5pM/WEbzFcDmHvwwBu2wnikg+lEj4mwBe5bC5h1OUqcwpdC60dxegRmR06TyjCF9G9z+qM2uCJmuMJmaNZaUrCSIi6X+jJIBBYtW5Cge7cd7sgoHDfDaAvKQGAlRZYc6ltJlMxX03UzlaRlBdQrzSCwksLRbOpHUSb7pcsnxCCwngvM2Rm/ugUCi84fycr4l2t8Bb6iqTxSCgNIAAAAAElFTkSuQmCC";
  70.  
  71. var img$3 = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAALnSURBVDjLpZNbSNNxFMcN9b3opZce7CHqIagegqAgLB+NyMIMFCRBSX3wPm+Z1ZZZlhpT03ReZs7ZvKDpps7LmKa2uTbTnO7inM3L5vxvc3P+1X37zYeVGBF04Mvvxzmcz+/8Duf4AfD7Hx1yDPIKg0dbHonlnYz1r8JsWt6VRUubk1ZE1Unt7e+yLv8VIOGzylS9jG2jegxry1rYbFZQlAVLCyqopDwI38duNr9JyP0jYIjHLNHIymE1G6A2WPFxQI8ywTRK+d/Q0KPB5NwK9OpRdFfFgcOMSTsAEDUUXJF1ptKWVT0kChNaB/XQGG2w2Bww2zahXrShoVeDtmEdZhU94D6956xiPbzgA/TXZXTPk8D3hXXwxTp4zUpRmPuxjC1y98rhASq6NJAqDejjpqAiJ6LBBxioT1w2GabAFc1jhrzs3PHA4XSCXcPBKgHZ3W4IRAOQzFF42aTE1EQzqrLD9D6AuC5hy2pZQmGjEqsuGlb3HrZJIPNxPritHcjIy0fv6DimKBrJ7HEs6sdRk33H5QMIK2O3LGta5HMUWHTQBLIHI2XHs1dF+8kShQrmHUBmoZFY/BkG7TCqM8N+AQSvo3TaqR4U85UYnqdgIp822ml0D41At27Dyi6gamFjIPIM+oMD0R92HE2RZykfoJYZxRlqZmBUOYcCvhpm0jArDdj3iMh9vq0MqvSr2PpUBM+MEE5eMr7En/P0Xg9I3AdUP48/X/8k3DUrq0djjxwv+LNQ6DfgIP1wOGn0R5yCiySjNBTIOAawgmAuvAbRDX+db5Aq86MZHwruYmasDIMTMrA4Y0gvHUFKsXS/bI+8Fb/bRt4J4g/wHBhldk5kbiXjlqOj/D4mxUzoZrjQTdei7/ZRbFY/AEiSO90PViJDnD9EIf5Lh5aJnRt9qSQtrOlt8k1DeWqoyytBzEVqLOa0x5QRhLXMQGhjjkAcGrArDPHP+ue1lYafZJAX9d6yyWn0Jnv9PwH2GPv45gRecwAAAABJRU5ErkJggg==";
  72.  
  73. var img$2 = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAIhSURBVDjLlZPrThNRFIWJicmJz6BWiYbIkYDEG0JbBiitDQgm0PuFXqSAtKXtpE2hNuoPTXwSnwtExd6w0pl2OtPlrphKLSXhx07OZM769qy19wwAGLhM1ddC184+d18QMzoq3lfsD3LZ7Y3XbE5DL6Atzuyilc5Ciyd7IHVfgNcDYTQ2tvDr5crn6uLSvX+Av2Lk36FFpSVENDe3OxDZu8apO5rROJDLo30+Nlvj5RnTlVNAKs1aCVFr7b4BPn6Cls21AWgEQlz2+Dl1h7IdA+i97A/geP65WhbmrnZZ0GIJpr6OqZqYAd5/gJpKox4Mg7pD2YoC2b0/54rJQuJZdm6Izcgma4TW1WZ0h+y8BfbyJMwBmSxkjw+VObNanp5h/adwGhaTXF4NWbLj9gEONyCmUZmd10pGgf1/vwcgOT3tUQE0DdicwIod2EmSbwsKE1P8QoDkcHPJ5YESjgBJkYQpIEZ2KEB51Y6y3ojvY+P8XEDN7uKS0w0ltA7QGCWHCxSWWpwyaCeLy0BkA7UXyyg8fIzDoWHeBaDN4tQdSvAVdU1Aok+nsNTipIEVnkywo/FHatVkBoIhnFisOBoZxcGtQd4B0GYJNZsDSiAEadUBCkstPtN3Avs2Msa+Dt9XfxoFSNYF/Bh9gP0bOqHLAm2WUF1YQskwrVFYPWkf3h1iXwbvqGfFPSGW9Eah8HSS9fuZDnS32f71m8KFY7xs/QZyu6TH2+2+FAAAAABJRU5ErkJggg==";
  74.  
  75. var img$1 = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAMtSURBVDjLVZNLa1xlAIafc5szk8xkMkkm5MKY2EpT2qa2MTVCmoLS2gq6EKooimAW7iQb/0I2bgTRIog0oFW7KQpCS7VqrSmmJGlSQtswqWlLLmbGmcmcZM6cy/edz00r6bt8eXh4N6+mlGJnxiZHR4APgSNAFjCBKjClInXm05Gzl3by2mPB2OSoCUwAp1/LHbcziSyO24gbgJAegg2urF8UUsifhZBvfvXK99v/C8YmRy3gt8G2/cMv517E8Wx8ApYcjZiyKbkRSgQkcFn3rzG9Nn1LhOLYt2/8UNUfLZkYaN0zfLRrkLIMCHUNIXTqIoZLjLJvU/ASrFQtnko+z2BH38HAD78DMConHh4FPn5nz6vGgqyxTp16JNj2kpR9C8eD/OoW1VoNO1NCS+d5oW0vV27f2PX11MS8MTR6+JOTXUMHNCPBui5AtdMpk8xsGNQ9ndur20TxCnbPIn5TnmJUwaxIDrTm9Jn7d1tM4EiuqZs5d41iXGefsZsIwYNCgOfVSXconJbLLEWb4CuahU2+6HO8d4DQF/0m0NpgNvLAXaPgu6QadrEZpKhUItJZj/aMS1EewvHnsdUWW/+WKG82kEykCAPRbCqlNE1B4DsocpiW5OJfIVoiyfqSQFdNdGXrpLZGcFZDPKYJg2VQCiGEZkoRlZ3A6W41mknFn2WlaOKFFrG4Tbw9wb2/S3g3miHySLdbNDd2kzYKVGpVpIiqugjF7P3yQ55pyLFWmCSyVokZPqHnEoYmsWQGuyWOGdexNIkRFOnqbGN5bRngjh4G4rMLd6+KnmQW012lWrpOJuNjCh9LU9i6gRkEZHIrpNv/QK8vcijXz5lfLijgS+PmuYV75+fPDXr1Wt9znfsouy5x+2miuoltW1iawBJV0o0/wT8lBvbv5WZ+gaWNlasz43MfmQChH777e37uT78eHDx5+BiLBROjqhDaFmGkQ1KS6+mlr7+XX2evc+nWVB54+4kznfr8pZQIxXkRyhPvDb9vIjtQqgFN12hLO2yUZ/ni8o8SuAa8NTM+t/GE4HGGx4del0J+IGXUH8ko86iuAneAszPjc9/s5P8DuO6ZcsXuRqAAAAAASUVORK5CYII=";
  76.  
  77. var img = "data:image/gif;base64,R0lGODlhEAAQAPUCAHR2dOTi5Pz6/PTy9MzKzNTW1Ly6vHx+fOTm5LSytHx6fLy+vPT29Nze3ISChIyOjOzq7JSWlJyenKSmpNTS1JSSlJyanNza3Pz+/MTGxOzu7KSipKyqrIyKjISGhLS2tMTCxMzOzKyurP///wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH/C05FVFNDQVBFMi4wAwEAAAAh+QQJBQACACwAAAAAEAAQAAAFUaAgjsJ1kWgpXoBypsIGbGV7BlMwMi3AlK/JjCSjoXA60g+m3Eg0TGJLEo1EYtPqFSLZQKLHRRIcUNiODcxo0VscXTszXPMVYC7qnXiVAftFIQAh+QQJBQABACwAAAAADwAQAAAFUWAgjoEGkeioKYqWBouzlKzLZMLoAI5oihlFZhSboQS4l7JkMOSWMJ5h2TRIqc4mA6q6bKEMD+9LIl94QohEcgp/w+MKq+IrewMdNJdyOFBQIQAh+QQJBQAKACwAAAEADwAPAAAFSaAijkpVkWRhFaI5ImQEWKgSAMG71jbvv5TfC3AI+igFCtHIS9qYQtGgSYCMJDMU5LC0EXECgyH8JbSyCWICwS2+zllIlbojhQAAIfkECQUAEAAsAAABABAADwAABVMgJI6QYZDosAyiOTLoAiwoxBzwa7C1UP8/BAJoezwaCIVi+DMCjkpmzfmwSXvGAFEkCPhGMhpJ4PxCHM9WwfYEaFsOGuFJICvNJNnMih8nEjkkIQAh+QQJBQAGACwAAAEAEAAPAAAFV6AhjgZFiUMxkNiFiaYxOICzwsBFlnU+ti8WzbYrylS/oHHA4Qwwj4dyx6k5owKjoargyKY7plPL2hUihWLVO4p0URBZLwtDyxWBLYBdLNTSMmQtBGAGIQAh+QQJBQABACwBAAAADwAQAAAGVsCAUNh4PBrDZNIIeAQwGkxyIOw0OxhLU/oEUIudBqTpHVKV2a1yLYiu3xhQRvD5vIlNuf0Oyt/Nclx/aAZnSRlhQwYABkJtVU2AGl0KX4lsfoMBYm9BACH5BAkFAAQALAEAAAAPABAAAAZVQIJQOOBwBsMkYoEgbAAATpKogAY4UOnn8BEuoNHiBnmAUq3TbZfInLrf08uFEArB6db6/VKd39l/bxgXAm4XEn54iUMSUUMChF4bVItJHR2BRIVuQQAh+QQFBQABACwAAAAAEAAPAAAGWsCAcCgAgQTDZIBxYQRAAABIKWR4FB5mdPGshIYXrKJpRFYAFeI1qwx5k0UkNUlxjOdVTcAexWuwf1EOflEadQ4UeEt6io0DcwwLTmqPbQpccGAZVZKKIiJzQQA7";
  78.  
  79. const iconSrcs = {
  80. cog: img$8,
  81. error: img$7,
  82. info: img$6,
  83. lock: img$5,
  84. tick: img$4,
  85. timeout: img$3,
  86. world: img$1,
  87. x: img$2,
  88. spinner: img
  89. };
  90.  
  91. const Icon = ({
  92. className,
  93. title,
  94. type
  95. }) => preact.h("img", {
  96. alt: `${type} icon`,
  97. className: className,
  98. src: iconSrcs[type],
  99. title: title
  100. });
  101.  
  102. function styleInject(css, ref) {
  103. if ( ref === void 0 ) ref = {};
  104. var insertAt = ref.insertAt;
  105.  
  106. if (!css || typeof document === 'undefined') { return; }
  107.  
  108. var head = document.head || document.getElementsByTagName('head')[0];
  109. var style = document.createElement('style');
  110. style.type = 'text/css';
  111.  
  112. if (insertAt === 'top') {
  113. if (head.firstChild) {
  114. head.insertBefore(style, head.firstChild);
  115. } else {
  116. head.appendChild(style);
  117. }
  118. } else {
  119. head.appendChild(style);
  120. }
  121.  
  122. if (style.styleSheet) {
  123. style.styleSheet.cssText = css;
  124. } else {
  125. style.appendChild(document.createTextNode(css));
  126. }
  127. }
  128.  
  129. var css_248z$6 = ".Options_options__8dIDU {\n margin-top: 10px;\n}\n\n .Options_options__8dIDU > label > span {\n margin-left: 10px;\n}\n";
  130. var css$6 = {"options":"Options_options__8dIDU"};
  131. styleInject(css_248z$6);
  132.  
  133. const Options = ({
  134. options
  135. }) => {
  136. const optionLabels = options.map(([key, title, val, setter]) => preact.h("label", {
  137. key: key
  138. }, preact.h("input", {
  139. checked: val,
  140. onInput: ev => setter(ev.target.checked),
  141. type: "checkbox"
  142. }), preact.h("span", null, title), preact.h("br", null)));
  143. return preact.h("div", {
  144. className: css$6.options
  145. }, optionLabels);
  146. };
  147.  
  148. const SiteIcon = ({
  149. className,
  150. site,
  151. title
  152. }) => site.icon ? preact.h("img", {
  153. alt: site.title,
  154. className: className,
  155. src: site.icon,
  156. title: title
  157. }) : null;
  158.  
  159. var css_248z$5 = ".Sites_searchBar__1cpJl {\n display: flex;\n flex-direction: row;\n margin-bottom: 1em;\n}\n\n .Sites_searchBar__1cpJl .Sites_searchInput__1iJDL {\n background-color: rgba(255, 255, 255, 0.9);\n border-radius: 3px;\n border-top-color: #949494;\n border: 1px solid #a6a6a6;\n box-shadow: 0 1px 0 rgba(0, 0, 0, .07) inset;\n display: flex;\n flex-direction: row;\n height: 24px;\n line-height: normal;\n outline: 0;\n padding: 3px 7px;\n transition: all 100ms linear;\n width: 100%;\n}\n\n .Sites_searchBar__1cpJl .Sites_searchInput__1iJDL:focus-within {\n background-color: #fff;\n border-color: #e77600;\n box-shadow: 0 0 2px 2px rgba(228, 121, 17, 0.25);\n}\n\n .Sites_searchBar__1cpJl .Sites_searchInput__1iJDL > * {\n background-color: transparent;\n border: none;\n height: 16px;\n}\n\n .Sites_searchBar__1cpJl .Sites_searchInput__1iJDL > button {\n margin: 0 0 0 0.7em;\n padding: 0;\n}\n\n .Sites_searchBar__1cpJl .Sites_searchInput__1iJDL > input {\n flex-grow: 1;\n outline: none;\n padding: 0 0 0 0.5em;\n}\n\n .Sites_searchBar__1cpJl .Sites_resultCount__2p4vG {\n font-weight: bold;\n margin-left: 2em;\n min-width: 140px;\n text-align: right;\n}\n\n .Sites_searchBar__1cpJl .Sites_resultCount__2p4vG > span {\n color: black;\n}\n\n.Sites_siteList__1Y3wR .Sites_catList__6txMX {\n display: flex;\n flex-wrap: wrap;\n}\n\n.Sites_siteList__1Y3wR .Sites_catList__6txMX h4 {\n width: 100%;\n}\n\n.Sites_siteList__1Y3wR .Sites_catList__6txMX label {\n align-items: center;\n color: #444;\n display: flex;\n flex-flow: row;\n padding: 0 6px;\n transition: color 100ms;\n width: 25%;\n}\n\n.Sites_siteList__1Y3wR .Sites_catList__6txMX label:hover {\n color: #222;\n}\n\n.Sites_siteList__1Y3wR .Sites_catList__6txMX label.Sites_checked__3D9QY span {\n color: black;\n}\n\n.Sites_siteList__1Y3wR .Sites_catList__6txMX label .Sites_title__1Gu_F {\n flex-grow: 1;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n.Sites_siteList__1Y3wR .Sites_catList__6txMX label input {\n margin-right: 4px;\n}\n\n.Sites_siteList__1Y3wR .Sites_catList__6txMX label .Sites_extraIcon__jwLPa {\n height: 12px;\n margin-left: 4px;\n width: 12px;\n}\n\n.Sites_siteList__1Y3wR .Sites_catList__6txMX label .Sites_siteIcon__3uzGl {\n flex-shrink: 0;\n margin-right: 6px;\n}\n";
  160. var css$5 = {"searchBar":"Sites_searchBar__1cpJl","searchInput":"Sites_searchInput__1iJDL","resultCount":"Sites_resultCount__2p4vG","siteList":"Sites_siteList__1Y3wR","catList":"Sites_catList__6txMX","checked":"Sites_checked__3D9QY","title":"Sites_title__1Gu_F","extraIcon":"Sites_extraIcon__jwLPa","siteIcon":"Sites_siteIcon__3uzGl"};
  161. styleInject(css_248z$5);
  162.  
  163. const SearchInput = ({
  164. q,
  165. setQ
  166. }) => preact.h("div", {
  167. className: css$5.searchInput
  168. }, preact.h("span", null, "\uD83D\uDD0D"), preact.h("input", {
  169. onInput: e => {
  170. setQ(e.target.value.toLowerCase().trim());
  171. },
  172. placeholder: "Search",
  173. value: q
  174. }), preact.h("button", {
  175. style: {
  176. display: q.length ? 'unset' : 'none'
  177. },
  178. title: "Clear",
  179. type: "button",
  180. onClick: () => setQ('')
  181. }, preact.h(Icon, {
  182. type: "x"
  183. })));
  184.  
  185. const DummyIcon = ({
  186. size
  187. }) => {
  188. const sizePx = `${size}px`;
  189. const style = {
  190. display: 'inline-block',
  191. height: sizePx,
  192. width: sizePx
  193. };
  194. return preact.h("div", {
  195. className: css$5.siteIcon,
  196. style: style
  197. });
  198. };
  199.  
  200. const SiteLabel = ({
  201. checked,
  202. setEnabled,
  203. site
  204. }) => {
  205. const input = preact.h("input", {
  206. checked: checked,
  207. onInput: e => setEnabled(prev => e.target.checked ? [...prev, site.id] : prev.filter(id => id !== site.id)),
  208. type: "checkbox"
  209. });
  210. const icon = site.icon ? preact.h(SiteIcon, {
  211. className: css$5.siteIcon,
  212. site: site,
  213. title: site.title
  214. }) : preact.h(DummyIcon, {
  215. size: 16
  216. });
  217. const title = preact.h("span", {
  218. className: css$5.title,
  219. title: site.title
  220. }, site.title);
  221. const extraIcons = [site.noAccessMatcher ? preact.h(Icon, {
  222. className: css$5.extraIcon,
  223. title: "Access restricted",
  224. type: "lock"
  225. }) : null, site.noResultsMatcher ? preact.h(Icon, {
  226. className: css$5.extraIcon,
  227. title: "Site supports fetching of results",
  228. type: "tick"
  229. }) : null];
  230. return preact.h("label", {
  231. className: checked ? css$5.checked : null
  232. }, input, icon, " ", title, " ", extraIcons);
  233. };
  234.  
  235. const CategoryList = ({
  236. enabled,
  237. name,
  238. setEnabled,
  239. sites
  240. }) => {
  241. const siteLabels = sites.map(site => preact.h(SiteLabel, {
  242. checked: enabled.includes(site.id),
  243. setEnabled: setEnabled,
  244. site: site
  245. }));
  246. return preact.h("div", {
  247. className: css$5.catList
  248. }, preact.h("h4", null, name, " ", preact.h("span", null, "(", sites.length, ")")), siteLabels);
  249. };
  250.  
  251. const Sites = ({
  252. enabledSites,
  253. setEnabledSites,
  254. sites
  255. }) => {
  256. const [q, setQ] = hooks.useState('');
  257. const catSites = Object.keys(CATEGORIES).map(cat => {
  258. const s = sites.filter(site => site.category === cat);
  259.  
  260. if (q.length) {
  261. return s.filter(site => site.title.toLowerCase().includes(q));
  262. }
  263.  
  264. return s;
  265. });
  266. const cats = Object.entries(CATEGORIES).map(([cat, catName], i) => catSites[i].length ? preact.h(CategoryList, {
  267. enabled: enabledSites,
  268. key: cat,
  269. name: catName,
  270. setEnabled: setEnabledSites,
  271. sites: catSites[i]
  272. }) : null);
  273. const total = catSites.reduce((acc, s) => acc + s.length, 0);
  274. return preact.h(preact.Fragment, null, preact.h("div", {
  275. className: css$5.searchBar
  276. }, preact.h(SearchInput, {
  277. q: q,
  278. setQ: setQ
  279. }), preact.h("div", {
  280. className: css$5.resultCount
  281. }, "Showing ", preact.h("span", null, total), " sites.")), preact.h("div", {
  282. className: css$5.siteList
  283. }, cats));
  284. };
  285.  
  286. var css_248z$4 = ".About_about__3lHx7 {\n padding: 1em 0;\n position: relative;\n}\n\n .About_about__3lHx7 ul > li {\n margin-bottom: 0;\n}\n\n .About_about__3lHx7 h2 {\n font-size: 20px;\n margin: 0.5em 0;\n}\n\n .About_about__3lHx7 > *:last-child {\n margin-bottom: 0;\n}\n\n .About_about__3lHx7 .About_top__3XyCB {\n text-align: center;\n}\n\n .About_about__3lHx7 .About_content__1xMTu {\n width: 61.8%;\n margin: 0 auto;\n}\n";
  287. var css$4 = {"about":"About_about__3lHx7","top":"About_top__3XyCB","content":"About_content__1xMTu"};
  288. styleInject(css_248z$4);
  289.  
  290. const About = () => preact.h("div", {
  291. className: css$4.about
  292. }, preact.h("div", {
  293. className: css$4.top
  294. }, preact.h("h3", null, "\uD83C\uDFA5 ", NAME_VERSION), preact.h("p", null, DESCRIPTION)), preact.h("div", {
  295. className: css$4.content
  296. }, preact.h("h2", null, "\uD83D\uDD17 Links"), preact.h("ul", null, preact.h("li", null, preact.h("a", {
  297. target: "_blank",
  298. rel: "noreferrer",
  299. href: HOMEPAGE
  300. }, "GitHub")), preact.h("li", null, preact.h("a", {
  301. target: "_blank",
  302. rel: "noreferrer",
  303. href: GREASYFORK_URL
  304. }, "Greasy Fork"))), preact.h("h2", null, "\u2728 Contributions"), preact.h("p", null, "Add new sites or update existing entries."), preact.h("ul", null, preact.h("li", null, preact.h("a", {
  305. target: "_blank",
  306. rel: "noreferrer",
  307. href: "https://github.com/buzz/imdb-link-em-all/issues/new"
  308. }, "Open a GitHub issue"), ' ', "or"), preact.h("li", null, preact.h("a", {
  309. target: "_blank",
  310. rel: "noreferrer",
  311. href: "https://greasyfork.org/en/scripts/17154-imdb-link-em-all/feedback"
  312. }, "Give feedback"), ' ', "on Greasy Fork.")), preact.h("p", null, preact.h("em", null, "Thanks to all the contributors!"), " \uD83D\uDC4D"), preact.h("h2", null, "\u2696 License"), preact.h("p", null, "This script is licensed under the terms of the", ' ', preact.h("a", {
  313. target: "_blank",
  314. rel: "noreferrer",
  315. href: "https://github.com/buzz/imdb-link-em-all/blob/master/LICENSE"
  316. }, "GPL-2.0 License"), ".")));
  317.  
  318. var css_248z$3 = ".Config_popover__3RK3L {\n background-color: #a5a5a5;\n border-radius: 4px;\n box-shadow: 0 0 2em rgba(0, 0, 0, 0.1);\n color: #333;\n display: block;\n font-family: Verdana, Arial, sans-serif;\n font-size: 11px;\n left: calc(-800px + 250px);\n line-height: 1.5rem;\n padding: 10px;\n position: absolute;\n top: calc(20px + 8px);\n white-space: nowrap;\n width: 800px;\n z-index: 100;\n}\n.Config_popover__3RK3L:before {\n border-bottom: 8px solid #a5a5a5;\n border-left: 8px solid transparent;\n border-right: 8px solid transparent;\n border-top: 8px solid transparent;\n content: \"\";\n display: block;\n height: 8px;\n right: calc(250px - 2 * 8px);\n position: absolute;\n top: calc(-2 * 8px);\n width: 0;\n}\n.Config_popover__3RK3L .Config_inner__2Sbjz {\n display: flex;\n flex-direction: column;\n text-shadow: 0 1px 0 rgba(255, 255, 255, 0.2);\n}\n.Config_popover__3RK3L .Config_inner__2Sbjz .Config_top__2kgQ3 {\n display: flex;\n flex-direction: row;\n}\n.Config_popover__3RK3L .Config_inner__2Sbjz .Config_top__2kgQ3 .Config_link__3aqRB {\n flex-grow: 1;\n text-align: right;\n}\n.Config_popover__3RK3L .Config_inner__2Sbjz .Config_top__2kgQ3 .Config_link__3aqRB > a {\n color: #333;\n margin-left: 12px;\n margin-right: 4px;\n}\n.Config_popover__3RK3L .Config_inner__2Sbjz .Config_top__2kgQ3 .Config_link__3aqRB > a:visited {\n color: #333;\n}\n.Config_popover__3RK3L .Config_inner__2Sbjz .Config_top__2kgQ3 > button {\n background-color: rgba(0, 0, 0, 0.05);\n border-bottom-left-radius: 0;\n border-bottom-right-radius: 0;\n border-bottom: transparent;\n border-left: 1px solid rgba(0, 0, 0, 0.25);\n border-right: 1px solid rgba(0, 0, 0, 0.25);\n border-top-left-radius: 2px;\n border-top-right-radius: 2px;\n border-top: 1px solid rgba(0, 0, 0, 0.25);\n color: #424242;\n font-size: 12px;\n margin: 0 6px 0 0;\n outline: none;\n padding: 0 6px;\n transform: translateY(1px);\n text-shadow: 0 1px 0 rgba(255, 255, 255, 0.2);\n}\n.Config_popover__3RK3L .Config_inner__2Sbjz .Config_top__2kgQ3 > button:hover {\n background-color: rgba(0, 0, 0, 0.1);\n color: #222;\n}\n.Config_popover__3RK3L .Config_inner__2Sbjz .Config_top__2kgQ3 > button.Config_active__iBK3y {\n background-color: #c2c2c2;\n color: #222;\n}\n.Config_popover__3RK3L .Config_inner__2Sbjz .Config_top__2kgQ3 > button:last-child {\n margin-right: 0;\n}\n.Config_popover__3RK3L .Config_inner__2Sbjz .Config_top__2kgQ3 > button > img {\n vertical-align: text-bottom;\n}\n.Config_popover__3RK3L .Config_inner__2Sbjz .Config_body__2JuhF {\n background-color: #c2c2c2;\n border-bottom-left-radius: 2px;\n border-bottom-right-radius: 2px;\n border-top-right-radius: 2px;\n border: 1px solid rgba(0, 0, 0, 0.25);\n padding: 12px 10px 12px;\n}\n.Config_popover__3RK3L .Config_inner__2Sbjz .Config_body__2JuhF > div {\n overflow: hidden;\n}\n.Config_popover__3RK3L .Config_inner__2Sbjz .Config_body__2JuhF > div > *:first-child {\n margin-top: 0;\n}\n.Config_popover__3RK3L .Config_inner__2Sbjz .Config_body__2JuhF > div > *:last-child {\n margin-bottom: 0;\n}\n.Config_popover__3RK3L .Config_inner__2Sbjz .Config_controls__3hBBQ {\n display: flex;\n flex-direction: row;\n margin-top: 10px;\n}\n.Config_popover__3RK3L .Config_inner__2Sbjz .Config_controls__3hBBQ > div:first-child {\n flex-grow: 1;\n}\n.Config_popover__3RK3L .Config_inner__2Sbjz .Config_controls__3hBBQ button {\n padding-bottom: 0;\n padding-top: 0;\n margin-right: 12px;\n}\n";
  319. var css$3 = {"popover":"Config_popover__3RK3L","inner":"Config_inner__2Sbjz","top":"Config_top__2kgQ3","link":"Config_link__3aqRB","active":"Config_active__iBK3y","body":"Config_body__2JuhF","controls":"Config_controls__3hBBQ"};
  320. styleInject(css_248z$3);
  321.  
  322. const OPTIONS = [['show_category_captions', 'Show category captions'], ['open_blank', 'Open links in new tab'], ['fetch_results', 'Automatically fetch results']];
  323.  
  324. const Config = ({
  325. config,
  326. setConfig,
  327. setShow,
  328. show,
  329. sites
  330. }) => {
  331. const [enabledSites, setEnabledSites] = hooks.useState(config.enabled_sites);
  332. const showCategoryCaptionsArr = hooks.useState(config.show_category_captions);
  333. const openBlankArr = hooks.useState(config.open_blank);
  334. const fetchResultsArr = hooks.useState(config.fetch_results);
  335. const [showCategoryCaptions, setShowCategoryCaptions] = showCategoryCaptionsArr;
  336. const [openBlank, setOpenBlank] = openBlankArr;
  337. const [fetchResults, setFetchResults] = fetchResultsArr;
  338. const optStates = [showCategoryCaptionsArr, openBlankArr, fetchResultsArr];
  339. const options = OPTIONS.map((opt, i) => [...opt, ...optStates[i]]);
  340. const [tab, setTab] = hooks.useState(0);
  341. const tabs = [{
  342. title: 'Sites',
  343. icon: 'world',
  344. comp: preact.h(Sites, {
  345. enabledSites: enabledSites,
  346. setEnabledSites: setEnabledSites,
  347. sites: sites
  348. })
  349. }, {
  350. title: 'Options',
  351. icon: 'cog',
  352. comp: preact.h(Options, {
  353. options: options
  354. })
  355. }, {
  356. title: 'About',
  357. icon: 'info',
  358. comp: preact.h(About, null)
  359. }];
  360.  
  361. const onClickCancel = () => {
  362. setShow(false); // Restore state
  363.  
  364. setEnabledSites(config.enabled_sites);
  365. setFetchResults(config.fetch_results);
  366. setOpenBlank(config.open_blank);
  367. setShowCategoryCaptions(config.show_category_captions);
  368. };
  369.  
  370. const onClickSave = () => {
  371. setConfig({
  372. enabled_sites: enabledSites,
  373. fetch_results: fetchResults,
  374. open_blank: openBlank,
  375. show_category_captions: showCategoryCaptions
  376. });
  377. setShow(false);
  378. };
  379.  
  380. return preact.h("div", {
  381. className: css$3.popover,
  382. style: {
  383. display: show ? 'block' : 'none'
  384. }
  385. }, preact.h("div", {
  386. className: css$3.inner
  387. }, preact.h("div", {
  388. className: css$3.top
  389. }, tabs.map(({
  390. title,
  391. icon
  392. }, i) => preact.h("button", {
  393. className: tab === i ? css$3.active : null,
  394. type: "button",
  395. onClick: () => setTab(i)
  396. }, preact.h(Icon, {
  397. title: title,
  398. type: icon
  399. }), " ", title)), preact.h("div", {
  400. className: css$3.link
  401. }, preact.h("a", {
  402. target: "_blank",
  403. rel: "noreferrer",
  404. href: HOMEPAGE
  405. }, "\uD83C\uDFA5 ", NAME_VERSION))), preact.h("div", {
  406. className: css$3.body
  407. }, tabs.map(({
  408. comp
  409. }, i) => preact.h("div", {
  410. style: {
  411. display: tab === i ? 'block' : 'none'
  412. }
  413. }, comp))), preact.h("div", {
  414. className: css$3.controls
  415. }, preact.h("div", null, preact.h("button", {
  416. className: "btn primary small",
  417. onClick: onClickSave,
  418. type: "button"
  419. }, "OK"), preact.h("button", {
  420. className: "btn small",
  421. onClick: onClickCancel,
  422. type: "button"
  423. }, "Cancel")))));
  424. };
  425.  
  426. function _extends() {
  427. _extends = Object.assign || function (target) {
  428. for (var i = 1; i < arguments.length; i++) {
  429. var source = arguments[i];
  430.  
  431. for (var key in source) {
  432. if (Object.prototype.hasOwnProperty.call(source, key)) {
  433. target[key] = source[key];
  434. }
  435. }
  436. }
  437.  
  438. return target;
  439. };
  440.  
  441. return _extends.apply(this, arguments);
  442. }
  443.  
  444. const replaceFields = (str, {
  445. id,
  446. title,
  447. year
  448. }, encode = true) => str.replace(new RegExp('{{IMDB_TITLE}}', 'g'), encode ? encodeURIComponent(title) : title).replace(new RegExp('{{IMDB_ID}}', 'g'), id).replace(new RegExp('{{IMDB_YEAR}}', 'g'), year);
  449.  
  450. const checkResponse = (resp, site) => {
  451. // Likely a redirect to login page
  452. if (resp.responseHeaders && resp.responseHeaders.includes('Refresh: 0; url=')) {
  453. return FETCH_STATE.NO_ACCESS;
  454. } // There should be a responseText
  455.  
  456.  
  457. if (!resp.responseText) {
  458. return FETCH_STATE.ERROR;
  459. } // Detect Blogger content warning
  460.  
  461.  
  462. if (resp.responseText.includes('The blog that you are about to view may contain content only suitable for adults.')) {
  463. return FETCH_STATE.NO_ACCESS;
  464. } // Detect CloudFlare anti DDOS page
  465.  
  466.  
  467. if (resp.responseText.includes('Checking your browser before accessing')) {
  468. return FETCH_STATE.NO_ACCESS;
  469. } // Check site access
  470.  
  471.  
  472. if (site.noAccessMatcher) {
  473. const matchStrings = Array.isArray(site.noAccessMatcher) ? site.noAccessMatcher : [site.noAccessMatcher];
  474.  
  475. if (matchStrings.some(matchString => resp.responseText.includes(matchString))) {
  476. return FETCH_STATE.NO_ACCESS;
  477. }
  478. } // Check results
  479.  
  480.  
  481. if (Array.isArray(site.noResultsMatcher)) {
  482. // Advanced ways of checking, currently only EL_COUNT is supported
  483. const [checkType, selector, compType, number] = site.noResultsMatcher;
  484. const m = resp.responseHeaders.match(/content-type:\s([^\s;]+)/);
  485. const contentType = m ? m[1] : 'text/html';
  486. let doc;
  487.  
  488. try {
  489. const parser = new DOMParser();
  490. doc = parser.parseFromString(resp.responseText, contentType);
  491. } catch (e) {
  492. console.error('Could not parse document!');
  493. return FETCH_STATE.ERROR;
  494. }
  495.  
  496. switch (checkType) {
  497. case 'EL_COUNT':
  498. {
  499. let result;
  500.  
  501. try {
  502. result = doc.querySelectorAll(selector);
  503. } catch (err) {
  504. console.error(err);
  505. return FETCH_STATE.ERROR;
  506. }
  507.  
  508. if (compType === 'GT') {
  509. if (result.length > number) {
  510. return FETCH_STATE.RESULTS_FOUND;
  511. }
  512. }
  513.  
  514. if (compType === 'LT') {
  515. if (result.length < number) {
  516. return FETCH_STATE.RESULTS_FOUND;
  517. }
  518. }
  519.  
  520. break;
  521. }
  522. }
  523.  
  524. return FETCH_STATE.NO_RESULTS;
  525. }
  526.  
  527. const matchStrings = Array.isArray(site.noResultsMatcher) ? site.noResultsMatcher : [site.noResultsMatcher];
  528.  
  529. if (matchStrings.some(matchString => resp.responseText.includes(matchString))) {
  530. return FETCH_STATE.NO_RESULTS;
  531. }
  532.  
  533. return FETCH_STATE.RESULTS_FOUND;
  534. };
  535.  
  536. const useResultFetcher = (imdbInfo, site) => {
  537. const [fetchState, setFetchState] = hooks.useState(null);
  538. hooks.useEffect(() => {
  539. let xhr;
  540.  
  541. if (site.noResultsMatcher) {
  542. // Site supports result fetching
  543. const {
  544. url
  545. } = site;
  546. const isPost = Array.isArray(url);
  547. const opts = {
  548. timeout: 20000,
  549. onload: resp => setFetchState(checkResponse(resp, site)),
  550. onerror: resp => {
  551. console.error(`Failed to fetch results from URL '${url}': ${resp.statusText}`);
  552. setFetchState(FETCH_STATE.ERROR);
  553. },
  554. ontimeout: () => setFetchState(FETCH_STATE.TIMEOUT)
  555. };
  556.  
  557. if (isPost) {
  558. const [postUrl, fields] = url;
  559. opts.method = 'POST';
  560. opts.url = postUrl;
  561. opts.headers = {
  562. 'Content-Type': 'application/x-www-form-urlencoded'
  563. };
  564. opts.data = Object.keys(fields).map(key => {
  565. const val = replaceFields(fields[key], imdbInfo, false);
  566. return `${key}=${val}`;
  567. }).join('&');
  568. } else {
  569. opts.method = 'GET';
  570. opts.url = replaceFields(url, imdbInfo);
  571. }
  572.  
  573. xhr = GM.xmlHttpRequest(opts);
  574. setFetchState(FETCH_STATE.LOADING);
  575. }
  576.  
  577. return () => {
  578. if (xhr && xhr.abort) {
  579. xhr.abort();
  580. }
  581. };
  582. }, [imdbInfo, site]);
  583. return fetchState;
  584. };
  585.  
  586. var css_248z$2 = ".SiteLink_linkWrapper__2uDyT {\n display: inline-block;\n margin-right: 4px;\n}\n\n .SiteLink_linkWrapper__2uDyT img {\n vertical-align: baseline;\n}\n\n .SiteLink_linkWrapper__2uDyT a {\n white-space: pre-line;\n}\n\n .SiteLink_linkWrapper__2uDyT a > img {\n height: 16px;\n width: 16px;\n margin-right: 4px;\n}\n\n .SiteLink_linkWrapper__2uDyT .SiteLink_resultsIcon__3_V-k {\n margin-left: 4px;\n}\n";
  587. var css$2 = {"linkWrapper":"SiteLink_linkWrapper__2uDyT","resultsIcon":"SiteLink_resultsIcon__3_V-k"};
  588. styleInject(css_248z$2);
  589.  
  590. const ResultsIndicator = ({
  591. imdbInfo,
  592. site
  593. }) => {
  594. const fetchState = useResultFetcher(imdbInfo, site);
  595. let iconType;
  596. let title;
  597.  
  598. switch (fetchState) {
  599. case FETCH_STATE.LOADING:
  600. iconType = 'spinner';
  601. title = 'Loading…';
  602. break;
  603.  
  604. case FETCH_STATE.NO_RESULTS:
  605. iconType = 'x';
  606. title = 'No Results found!';
  607. break;
  608.  
  609. case FETCH_STATE.RESULTS_FOUND:
  610. iconType = 'tick';
  611. title = 'Results found!';
  612. break;
  613.  
  614. case FETCH_STATE.NO_ACCESS:
  615. iconType = 'lock';
  616. title = 'You have to login to this site!';
  617. break;
  618.  
  619. case FETCH_STATE.TIMEOUT:
  620. iconType = 'timeout';
  621. title = 'You have to login to this site!';
  622. break;
  623.  
  624. case FETCH_STATE.ERROR:
  625. iconType = 'error';
  626. title = 'Error fetching results! (See dev console for details)';
  627. break;
  628.  
  629. default:
  630. return null;
  631. }
  632.  
  633. return preact.h(Icon, {
  634. className: css$2.resultsIcon,
  635. title: title,
  636. type: iconType
  637. });
  638. };
  639.  
  640. const usePostLink = (url, openBlank, imdbInfo) => {
  641. const formEl = hooks.useRef();
  642. const isPost = Array.isArray(url);
  643. const href = isPost ? url[0] : replaceFields(url, imdbInfo, false);
  644.  
  645. const onClick = event => {
  646. if (isPost && formEl.current) {
  647. event.preventDefault();
  648. formEl.current.submit();
  649. }
  650. };
  651.  
  652. hooks.useEffect(() => {
  653. if (isPost) {
  654. const [postUrl, fields] = url;
  655. const form = document.createElement('form');
  656. form.action = postUrl;
  657. form.method = 'POST';
  658. form.style.display = 'none';
  659. form.target = openBlank ? '_blank' : '_self';
  660. Object.keys(fields).forEach(key => {
  661. const input = document.createElement('input');
  662. input.type = 'text';
  663. input.name = key;
  664. input.value = replaceFields(fields[key], imdbInfo, false);
  665. form.appendChild(input);
  666. });
  667. document.body.appendChild(form);
  668. formEl.current = form;
  669. }
  670.  
  671. return () => {
  672. if (formEl.current) {
  673. formEl.current.remove();
  674. }
  675. };
  676. });
  677. return [href, onClick];
  678. };
  679.  
  680. const Sep = () => preact.h(preact.Fragment, null, "\xA0", preact.h("span", {
  681. className: "ghost"
  682. }, "|"));
  683.  
  684. const SiteLink = ({
  685. config,
  686. imdbInfo,
  687. last,
  688. site
  689. }) => {
  690. const extraAttrs = config.open_blank ? {
  691. target: '_blank',
  692. rel: 'noreferrer'
  693. } : {};
  694. const [href, onClick] = usePostLink(site.url, config.open_blank, imdbInfo);
  695. return preact.h("span", {
  696. className: css$2.linkWrapper
  697. }, preact.h("a", _extends({
  698. className: "ipc-link ipc-link--base",
  699. href: href,
  700. onClick: onClick
  701. }, extraAttrs), preact.h(SiteIcon, {
  702. site: site
  703. }), preact.h("span", null, site.title)), preact.h(ResultsIndicator, {
  704. imdbInfo: imdbInfo,
  705. site: site
  706. }), last ? null : preact.h(Sep, null));
  707. };
  708.  
  709. var css_248z$1 = ".LinkList_linkList__rlGOn {\n line-height: 1.6rem\n}\n\n.LinkList_h4__2axTi {\n margin-top: 0.5rem\n}\n";
  710. var css$1 = {"linkList":"LinkList_linkList__rlGOn","h4":"LinkList_h4__2axTi"};
  711. styleInject(css_248z$1);
  712.  
  713. const LinkList = ({
  714. config,
  715. imdbInfo,
  716. sites
  717. }) => Object.entries(CATEGORIES).map(([category, categoryName]) => {
  718. const catSites = sites.filter(site => site.category === category && config.enabled_sites.includes(site.id));
  719.  
  720. if (!catSites.length) {
  721. return null;
  722. }
  723.  
  724. const caption = config.show_category_captions ? preact.h("h4", {
  725. className: css$1.h4
  726. }, categoryName) : null;
  727. return preact.h(preact.Fragment, null, caption, preact.h("div", {
  728. className: css$1.linkList
  729. }, catSites.map((site, i) => preact.h(SiteLink, {
  730. config: config,
  731. imdbInfo: imdbInfo,
  732. last: i === catSites.length - 1,
  733. site: site
  734. }))));
  735. });
  736.  
  737. var css_248z = ".App_configWrapper__2KuAE {\n position: absolute;\n right: 20px;\n top: 20px;\n}\n\n .App_configWrapper__2KuAE > button {\n background: transparent;\n border: none;\n cursor: pointer;\n outline: none;\n padding: 0;\n}\n\n .App_configWrapper__2KuAE > button > img {\n vertical-align: baseline;\n}\n";
  738. var css = {"configWrapper":"App_configWrapper__2KuAE"};
  739. styleInject(css_248z);
  740.  
  741. const restoreConfig = async () => JSON.parse(await GM.getValue(GM_CONFIG_KEY));
  742.  
  743. const saveConfig = async config => GM.setValue(GM_CONFIG_KEY, JSON.stringify(config));
  744.  
  745. const useConfig = () => {
  746. const [config, setConfig] = hooks.useState();
  747. hooks.useEffect(() => {
  748. restoreConfig().then(c => setConfig(c)).catch(() => setConfig(DEFAULT_CONFIG));
  749. }, []);
  750. hooks.useEffect(() => {
  751. if (config) {
  752. saveConfig(config);
  753. }
  754. }, [config]);
  755. return {
  756. config,
  757. setConfig
  758. };
  759. };
  760.  
  761. const loadSites = () => new Promise((resolve, reject) => GM.xmlHttpRequest({
  762. method: 'GET',
  763. url: SITES_URL,
  764. nocache: true,
  765.  
  766. onload({
  767. response,
  768. status,
  769. statusText
  770. }) {
  771. if (status === 200) {
  772. try {
  773. resolve(JSON.parse(response).sort((a, b) => a.title.localeCompare(b.title)));
  774. } catch (e) {
  775. reject(e);
  776. }
  777. } else {
  778. reject(new Error(`LTA: Could not load sites (URL=${SITES_URL}): ${status} ${statusText}`));
  779. }
  780. },
  781.  
  782. onerror({
  783. status,
  784. statusText
  785. }) {
  786. reject(new Error(`LTA: Could not load sites (URL=${SITES_URL}): ${status} ${statusText}`));
  787. }
  788.  
  789. }));
  790.  
  791. const useSites = () => {
  792. const [sites, setSites] = hooks.useState([]);
  793. hooks.useEffect(() => {
  794. loadSites().then(s => setSites(s));
  795. }, []);
  796. return sites;
  797. };
  798.  
  799. const App = ({
  800. imdbInfo
  801. }) => {
  802. const {
  803. config,
  804. setConfig
  805. } = useConfig();
  806. const sites = useSites();
  807. const [showConfig, setShowConfig] = hooks.useState(false);
  808. hooks.useEffect(() => {
  809. if (config && config.first_run) {
  810. setShowConfig(true);
  811. setConfig(prev => ({ ...prev,
  812. first_run: false
  813. }));
  814. }
  815. }, [config]);
  816.  
  817. if (!config || !sites.length) {
  818. return null;
  819. }
  820.  
  821. return preact.h(preact.Fragment, null, imdbInfo.layout === 'legacy' ? preact.h("hr", null) : null, preact.h("div", {
  822. className: css.configWrapper
  823. }, preact.h("button", {
  824. onClick: () => setShowConfig(cur => !cur),
  825. title: "Configure",
  826. type: "button"
  827. }, preact.h(Icon, {
  828. type: "cog"
  829. })), preact.h(Config, {
  830. config: config,
  831. setConfig: setConfig,
  832. setShow: setShowConfig,
  833. sites: sites,
  834. show: showConfig
  835. })), preact.h(LinkList, {
  836. config: config,
  837. imdbInfo: imdbInfo,
  838. sites: sites
  839. }));
  840. };
  841.  
  842. const detectLayout = mUrl => {
  843. // Currently there seem to be 3 different IMDb layouts:
  844. // 1) "legacy": URL ends with '/reference'
  845. if (['reference', 'combined'].includes(mUrl[2])) {
  846. return ['legacy', 'h3[itemprop=name]', '.titlereference-section-overview > *:last-child'];
  847. } // 2) "redesign2020": Redesign 2020
  848. // https://www.imdb.com/preferences/beta-control?e=tmd&t=in&u=/title/tt0163978/
  849.  
  850.  
  851. if (document.querySelector('[data-testid="hero-title-block__title"]')) {
  852. return ['redesign2020', 'title', '[class*=TitleMainBelowTheFold]'];
  853. } // 3) "new": The old default (has been around for many years)
  854.  
  855.  
  856. return ['new', 'h1', '.title-overview'];
  857. };
  858.  
  859. const parseImdbInfo = () => {
  860. // TODO: extract type (TV show, movie, ...)
  861. // Parse IMDb number and layout
  862. const mUrl = /^\/title\/tt([0-9]{7,8})\/([a-z]*)/.exec(window.location.pathname);
  863.  
  864. if (!mUrl) {
  865. throw new Error('LTA: Could not parse IMDb URL!');
  866. }
  867.  
  868. const [layout, titleSelector, containerSelector] = detectLayout(mUrl);
  869. const info = {
  870. id: mUrl[1],
  871. layout
  872. };
  873. info.title = document.querySelector(titleSelector).innerText.trim();
  874. const mTitle = /^(.+)\s+\((\d+)\)/.exec(info.title);
  875.  
  876. if (mTitle) {
  877. info.title = mTitle[1].trim();
  878. info.year = parseInt(mTitle[2].trim(), 10);
  879. }
  880.  
  881. return [info, containerSelector];
  882. };
  883.  
  884. const [imdbInfo, containerSelector] = parseImdbInfo();
  885. let injectionEl = document.querySelector(containerSelector);
  886.  
  887. if (!injectionEl) {
  888. throw new Error('LTA: Could not find target container!');
  889. }
  890.  
  891. if (imdbInfo.layout === 'redesign2020') {
  892. injectionEl = injectionEl.parentElement;
  893. }
  894.  
  895. const container = document.createElement('div');
  896. container.style.position = 'relative';
  897.  
  898. if (imdbInfo.layout === 'redesign2020') {
  899. container.style.padding = '0 var(--ipt-pageMargin)';
  900. container.style.minHeight = '50px';
  901. injectionEl.insertBefore(container, injectionEl.firstChild);
  902. } else {
  903. container.classList.add('article');
  904. injectionEl.appendChild(container);
  905. }
  906.  
  907. preact.render(preact.h(App, {
  908. imdbInfo: imdbInfo
  909. }), container);
  910.  
  911. }(preact, preactHooks));