Greasy Fork is available in English.

ČSFD Extended

Rozšíření profilů filmů na ČSFD o funkce jako je hodnocení IMDB či odkaz na justwatch.com.

  1. // ==UserScript==
  2. // @name ČSFD Extended
  3. // @name:en ČSFD Extended
  4. // @version 2.10.0
  5. // @description Rozšíření profilů filmů na ČSFD o funkce jako je hodnocení IMDB či odkaz na justwatch.com.
  6. // @description:en Extension of film profiles on ČSFD with features such as IMDb ratings or a link to justwatch.com.
  7. // @author Jakub Rychecký <jakub@rychecky.cz>
  8. // @require https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js
  9. // @license WTFPL 2
  10. // @include *csfd.cz/film/*
  11. // @include *csfd.sk/film/*
  12. // @namespace CSFD-E
  13. // ==/UserScript==
  14. /******/ (() => { // webpackBootstrap
  15. /******/ "use strict";
  16. var __webpack_exports__ = {};
  17.  
  18. ;// CONCATENATED MODULE: ./src/classes/Csfd.js
  19. class Csfd {
  20.  
  21. constructor(csfdPage) {
  22. this.csfdPage = csfdPage
  23. }
  24.  
  25. isLoggedIn() {
  26. return this.csfdPage.find('.my-rating').length > 0;
  27. }
  28.  
  29. getImdbCode() {
  30. let imdbButton = this.csfdPage.find('a.button-imdb');
  31.  
  32. return imdbButton.length > 0
  33. ? imdbButton.attr('href').match(/(tt\d+)/)[0]
  34. : null;
  35. }
  36.  
  37. getCurrentUserRatingDate() {
  38. let ratingDateInText = this.csfdPage.find('.current-user-rating > span').attr('title');
  39.  
  40. if (ratingDateInText === undefined) {
  41. return null;
  42. }
  43.  
  44. return ratingDateInText.match(/.+(\d{2}\.\d{2}\.\d{4})$/)[1];
  45. }
  46.  
  47. isMarkedAsWantToWatch() {
  48. let controlPanelText = this.csfdPage.find('.control-panel').text();
  49.  
  50. return controlPanelText.includes('Upravit ve Chci vidět')
  51. || controlPanelText.includes('Upraviť v Chcem vidieť');
  52. }
  53.  
  54. getLinkingDataMovieTitle() {
  55. let linkingDataJson = JSON.parse($('script[type="application/ld+json"]')[0].innerHTML);
  56.  
  57. return linkingDataJson.name + ' ' + linkingDataJson.dateCreated;
  58. }
  59.  
  60. }
  61.  
  62. ;// CONCATENATED MODULE: ./src/classes/ImdbRating.js
  63. class ImdbRating {
  64.  
  65. constructor(
  66. csfd,
  67. imdbRating,
  68. imdbVotes
  69. ) {
  70. this.csfd = csfd;
  71.  
  72. this.initializeImdbRating(imdbRating, imdbVotes);
  73. }
  74.  
  75. initializeImdbRating(
  76. imdbRating,
  77. imdbVotes
  78. ) {
  79. if (
  80. imdbRating === undefined
  81. || imdbRating === 'N/A'
  82. || imdbVotes === undefined
  83. || imdbVotes === 'N/A'
  84. ) {
  85. return;
  86. }
  87.  
  88. let imdbVotesSpan = $('<span>')
  89. .css({
  90. 'display': 'block',
  91. 'font-size': '9px',
  92. 'font-weight': 'normal',
  93. 'line-height': '10px',
  94. 'padding-bottom': '8px',
  95. })
  96. .html('<strong>' + imdbVotes + '</strong> hlasů');
  97.  
  98. let imdbRatingBox = $('<a>')
  99. .addClass('rating-average csfd-extended-imdb-rating')
  100. .css({
  101. 'display': 'block',
  102. 'color': '#000000',
  103. 'cursor': 'pointer',
  104. 'line-height': '60px',
  105. 'text-align': 'center',
  106. 'font-size': '40px',
  107. 'font-weight': 'bold',
  108. })
  109. .attr('href', 'https://www.imdb.com/title/' + this.csfd.getImdbCode())
  110. .html(imdbRating)
  111. .append(imdbVotesSpan);
  112.  
  113. imdbRatingBox
  114. .hover(
  115. (e) => {
  116. imdbRatingBox.css({
  117. 'background': '#F5BE18FF',
  118. })
  119. },
  120. (e) => {
  121. imdbRatingBox.css({
  122. 'background': '#F5C518',
  123. })
  124. },
  125. )
  126. .trigger('mouseleave');
  127.  
  128. imdbRatingBox.insertBefore(this.csfd.csfdPage.find('.my-rating'));
  129. }
  130.  
  131. }
  132.  
  133. ;// CONCATENATED MODULE: ./src/classes/Omdb.js
  134.  
  135.  
  136. class Omdb {
  137.  
  138. constructor(
  139. csfd,
  140. omdbApiKey,
  141. cache
  142. ) {
  143. this.csfd = csfd;
  144. this.omdbApiKey = omdbApiKey;
  145. this.cache = cache;
  146.  
  147. this.getResponse();
  148. }
  149.  
  150. getResponse() {
  151. let imdbCode = this.csfd.getImdbCode();
  152.  
  153. if (imdbCode === null) {
  154. return;
  155. }
  156.  
  157. let cacheItem = this.cache.getItem(imdbCode);
  158.  
  159. if (cacheItem !== null && !this.cache.isItemExpired(cacheItem)) {
  160. let responseFromCache = cacheItem.value;
  161.  
  162. new ImdbRating(
  163. this.csfd,
  164. responseFromCache.imdbRating,
  165. responseFromCache.imdbVotes
  166. );
  167.  
  168. return;
  169. }
  170.  
  171. let request = $.ajax({
  172. method: 'GET',
  173. url: 'https://omdbapi.com/',
  174. data: {
  175. apikey: this.omdbApiKey,
  176. i: imdbCode,
  177. r: 'json'
  178. },
  179. });
  180.  
  181. request.done((response) => {
  182. if (
  183. response.imdbRating !== undefined
  184. && response.imdbRating !== 'N/A'
  185. ) {
  186. this.cache.saveItem(imdbCode, response);
  187. }
  188.  
  189. new ImdbRating(
  190. this.csfd,
  191. response.imdbRating,
  192. response.imdbVotes
  193. )
  194.  
  195. this.response = response;
  196. });
  197. }
  198.  
  199. }
  200.  
  201. ;// CONCATENATED MODULE: ./src/classes/Toolbar.js
  202. class Toolbar {
  203.  
  204. constructor(
  205. csfd
  206. ) {
  207. this.csfd = csfd;
  208.  
  209. this.initializeToolbar();
  210. }
  211.  
  212. initializeToolbar() {
  213. let boxButtons = this.csfd.csfdPage.find('.box-rating-container .box-buttons');
  214.  
  215. let imdbCode = this.csfd.getImdbCode();
  216. let encodedLinkingDataMovieTitle = encodeURIComponent(this.csfd.getLinkingDataMovieTitle());
  217.  
  218. boxButtons.prepend(
  219. this.createButton(
  220. 'Titulky.com',
  221. null,
  222. 'http://www.titulky.com/?Fulltext=' + encodedLinkingDataMovieTitle
  223. ),
  224. this.createButton(
  225. 'Trakt.TV',
  226. null,
  227. 'https://trakt.tv/search/imdb?q=' + imdbCode
  228. ),
  229. this.createButton(
  230. 'JustWatch',
  231. null,
  232. 'https://www.justwatch.com/cz/vyhled%C3%A1n%C3%AD?q=' + encodedLinkingDataMovieTitle
  233. ),
  234. this.createButton(
  235. 'Google',
  236. null,
  237. 'https://www.google.cz/search?q=' + encodedLinkingDataMovieTitle
  238. ),
  239. this.createButton(
  240. 'YouTube',
  241. null,
  242. 'https://www.youtube.com/results?search_query=' + encodedLinkingDataMovieTitle
  243. ),
  244. this.createButton(
  245. 'BoxOffice',
  246. null,
  247. 'http://www.boxofficemojo.com/search/?q=' + encodedLinkingDataMovieTitle
  248. ),
  249. this.createButton(
  250. 'Webshare.cz',
  251. 'pirate',
  252. 'https://webshare.cz/#/search?what=' + encodedLinkingDataMovieTitle
  253. ),
  254. this.createButton(
  255. 'YIFY',
  256. 'pirate',
  257. 'https://www.google.cz/search?q=' + encodedLinkingDataMovieTitle + ' site:yts.mx OR site:yify-movies.net'
  258. ),
  259. this.createButton(
  260. 'Torrent',
  261. 'pirate',
  262. 'http://www.aiosearch.com/search/4/Torrents/' + encodedLinkingDataMovieTitle
  263. ),
  264. );
  265. }
  266.  
  267. createButton(
  268. name,
  269. style,
  270. url,
  271. ) {
  272. let backgroundColor = '#DE5254';
  273. let fontColor = '#FFF';
  274. let iconClass = 'icon-globe-circle';
  275.  
  276. if (style === 'pirate') {
  277. backgroundColor = '#A2A2A2';
  278. iconClass = 'icon-folder';
  279. }
  280.  
  281. let button = $('<a>')
  282. .attr('href', url)
  283. .addClass('button button-big')
  284. .css({
  285. 'background-color': backgroundColor,
  286. 'color': fontColor,
  287. 'padding-left': '6px',
  288. })
  289. .html('<i class="icon ' + iconClass + '"></i>' + name);
  290.  
  291. button.hover(
  292. (e) => {
  293. $(e.target).css({
  294. 'opacity': 1.0,
  295. });
  296. },
  297. (e) => {
  298. $(e.target).css({
  299. 'opacity': 0.95,
  300. });
  301. },
  302. );
  303.  
  304. button.trigger('mouseleave');
  305.  
  306. return button;
  307. }
  308.  
  309. }
  310.  
  311. ;// CONCATENATED MODULE: ./src/classes/UserRating.js
  312. class UserRating {
  313.  
  314. constructor(
  315. csfd
  316. ) {
  317. this.csfd = csfd;
  318.  
  319. this.initializeUserRating();
  320. }
  321.  
  322. initializeUserRating() {
  323. let currentUserRatingDate = this.csfd.getCurrentUserRatingDate();
  324.  
  325. if (currentUserRatingDate === null) {
  326. return;
  327. }
  328.  
  329. let currentUserRatingBoxTitle = this.csfd.csfdPage.find('.my-rating h3');
  330.  
  331. if (currentUserRatingBoxTitle.length === 0) {
  332. return;
  333. }
  334.  
  335. currentUserRatingBoxTitle.text('Hodnoceno ' + currentUserRatingDate);
  336. }
  337.  
  338. }
  339.  
  340. ;// CONCATENATED MODULE: ./src/classes/WantToWatch.js
  341. class WantToWatch {
  342.  
  343. constructor(
  344. csfd
  345. ) {
  346. this.csfd = csfd;
  347.  
  348. this.initializeWantToWatch();
  349. }
  350.  
  351. initializeWantToWatch() {
  352. if (!this.csfd.isMarkedAsWantToWatch()) {
  353. return;
  354. }
  355.  
  356. let wantToWatch = $('<a>')
  357. .attr('href', '?name=watchlist&do=modalWindow')
  358. .css({
  359. 'background': '#BA034F',
  360. 'border-top': '1px solid #D2D2D2',
  361. 'color': '#FFFFFF',
  362. 'display': 'block',
  363. 'opacity': 0.8,
  364. 'padding': '5px',
  365. 'text-align': 'center',
  366. })
  367. .html('👁️ Chci vidět');
  368.  
  369. wantToWatch.hover(
  370. (e) => {
  371. $(e.target).animate({
  372. 'opacity': 1.0,
  373. });
  374. },
  375. (e) => {
  376. $(e.target).animate({
  377. 'opacity': 0.8,
  378. });
  379. },
  380. );
  381.  
  382. this.csfd.csfdPage.find('.tabs.tabs-rating.rating-fan-switch').prepend(wantToWatch);
  383. }
  384.  
  385. }
  386.  
  387. ;// CONCATENATED MODULE: ./src/classes/ImageFloatingPreview.js
  388.  
  389.  
  390. class ImageFloatingPreview {
  391.  
  392. constructor(
  393. csfd
  394. ) {
  395. this.csfd = csfd;
  396.  
  397. this.initializeImageFloatingPreview();
  398. }
  399.  
  400. initializeImageFloatingPreview() {
  401. this.popup = $('<img>')
  402. .css({
  403. 'box-shadow': '5px 5px 14px 8px rgba(0,0,0,0.75)',
  404. 'z-index': 999,
  405. });
  406. $('body').append(this.popup);
  407.  
  408. $('.creators a')
  409. .bind('mouseenter', (e) => {
  410. let creatorUrl = $(e.target).attr('href');
  411.  
  412. this.hoverCreatorLink(creatorUrl);
  413. this.refreshPopupPosition(e.pageX, e.pageY);
  414. })
  415. .bind('mousemove', (e) => this.refreshPopupPosition(e.pageX, e.pageY))
  416. .bind('mouseleave', () => this.abort());
  417. }
  418.  
  419. showPopup(
  420. imageUrl
  421. ) {
  422. this.popup.attr('src', imageUrl);
  423. this.popup.show();
  424. }
  425.  
  426. hidePopup() {
  427. this.popup.attr('src', '');
  428. this.popup.hide();
  429. }
  430.  
  431. refreshPopupPosition(
  432. x,
  433. y
  434. ) {
  435. this.popup.css({
  436. 'position': 'absolute',
  437. 'left': x + 15,
  438. 'top': y + 15,
  439. })
  440. }
  441.  
  442. abort() {
  443. this.currentRequest.abort();
  444. this.hidePopup();
  445. }
  446.  
  447. hoverCreatorLink(
  448. url
  449. ) {
  450. this.currentRequest = $.ajax({
  451. method: 'GET',
  452. url: url,
  453. });
  454.  
  455. this.currentRequest.done((response) => {
  456. if (
  457. typeof response === 'object'
  458. && 'redirect' in response
  459. ) {
  460. this.hoverCreatorLink(response.redirect);
  461. return;
  462. }
  463.  
  464. let creatorImageUrl = $(response).find('.creator-profile-content>figure img').attr('src');
  465.  
  466. if (creatorImageUrl !== undefined) {
  467. this.showPopup(creatorImageUrl);
  468. }
  469. });
  470. }
  471.  
  472. }
  473.  
  474. ;// CONCATENATED MODULE: ./src/classes/CacheItem.js
  475. class CacheItem {
  476.  
  477. constructor(
  478. name,
  479. value,
  480. expireAt
  481. ) {
  482. this.name = name;
  483. this.expireAt = expireAt;
  484. this.value = value;
  485. }
  486.  
  487. }
  488.  
  489. ;// CONCATENATED MODULE: ./src/classes/Cache.js
  490.  
  491.  
  492. class Cache {
  493.  
  494. constructor(
  495. expirationInSeconds
  496. ) {
  497. this.expirationInSeconds = 600;
  498. this.namespace = 'csfd-extended';
  499. }
  500.  
  501. saveItem(
  502. key,
  503. value
  504. ) {
  505. let cacheItem = new CacheItem(
  506. this.addNamespaceToName(key),
  507. value,
  508. Math.floor(Date.now() / 1000) + this.expirationInSeconds
  509. )
  510.  
  511. localStorage.setItem(
  512. this.addNamespaceToName(key),
  513. JSON.stringify(cacheItem)
  514. )
  515. }
  516.  
  517. getItem(
  518. key
  519. ) {
  520. let cacheItem = localStorage.getItem(
  521. this.addNamespaceToName(key)
  522. );
  523.  
  524. return cacheItem !== null
  525. ? JSON.parse(cacheItem)
  526. : null;
  527. }
  528.  
  529. isItemExpired(
  530. caheItem
  531. ) {
  532. return caheItem.expireAt < Math.floor(Date.now() / 1000);
  533. }
  534.  
  535. addNamespaceToName(name) {
  536. return this.namespace + '.' + name;
  537. }
  538.  
  539. }
  540.  
  541. ;// CONCATENATED MODULE: ./src/index.js
  542.  
  543.  
  544.  
  545.  
  546.  
  547.  
  548.  
  549.  
  550. let cache = new Cache(7 * 24 * 3600);
  551.  
  552. let csfd = new Csfd($('div.page-content'));
  553. let omdb = new Omdb(csfd, 'ee2fe641', cache);
  554. let userRating = new UserRating(csfd);
  555. let wantToWatch = new WantToWatch(csfd);
  556. let toolbar = new Toolbar(csfd);
  557. let imageFloatingPreview = new ImageFloatingPreview(csfd);
  558.  
  559. /******/ })()
  560. ;