Kinorium.com – Enhanced [Ath]

Kinorium.com enhancements: user collections usability, links to extra streaming providers, native lazy loading of images etc.

  1. // ==UserScript==
  2. // @name Kinorium.com – Enhanced [Ath]
  3. // @name:ru Kinorium.com – Улучшенный [Ath]
  4. // @name:uk Kinorium.com – Покращений [Ath]
  5. // @name:be Kinorium.com – Удасканалены [Ath]
  6. // @name:bg Kinorium.com – Подобрен [Ath]
  7. // @name:tt Kinorium.com – Яхшыртылган [Ath]
  8. // @name:sl Kinorium.com – Izboljšan [Ath]
  9. // @name:sr Kinorium.com – Poboljšan [Ath]
  10. // @name:ka Kinorium.com – გაუმჯობესებული [Ath]
  11. // @description Kinorium.com enhancements: user collections usability, links to extra streaming providers, native lazy loading of images etc.
  12. // @description:ru Улучшения для Kinorium.com: удобство работы с пользовательскими коллекциями, ссылки на дополнительные онлайн-кинотеатры, нативная ленивая загрузка изображений и т.д.
  13. // @description:uk Покращення для Kinorium.com: зручність роботи з користувацькими колекціями, посилання на додаткові онлайн-кінотеатри, нативна лінива завантаження зображень тощо.
  14. // @description:be Удасканаленні для Kinorium.com: зручнасць працы з карыстальніцкімі калекцыямі, спасылкі на дадатковыя анлайн-кінатэатры, натыўная ленівая загрузка здымкаў і г.д.
  15. // @description:bg Подобрения за Kinorium.com: удобство при работата с потребителски колекции, връзки към допълнителни онлайн кинотеатри, нативно мързеливо зареждане на изображения и т.н.
  16. // @description:tt Kinorium.com өчен яхшыртулар: кулланучы коллекцияләре белән эш итү җиңеллеге, өстәмә онлайн-кинотеатрларга сылтамалар, туган ленивая загрузка изображений һ.б.
  17. // @description:sl Izboljšave za Kinorium.com: uporabnost uporabniških zbirk, povezave do dodatnih spletnih kinodvoran, domače leno nalaganje slik itd.
  18. // @description:sr Poboljšanja za Kinorium.com: upotrebljivost korisničkih kolekcija, linkovi ka dodatnim online bioskopima, nativno lenjo učitavanje slika itd.
  19. // @description:ka Kinorium.com-ის გაუმჯობესება: მომხმარებლის კოლექციების გამოყენების მარტივება, დამატებითი ონლაინ-კინოთეატრების ბმულები, ნატიური ზარმაცი იმიჯების ჩატვირთვა და სხვა.
  20. // @namespace athari
  21. // @author Athari (https://github.com/Athari)
  22. // @copyright © Prokhorov ‘Athari’ Alexander, 2024–2025
  23. // @license MIT
  24. // @homepageURL https://github.com/Athari/AthariUserJS
  25. // @supportURL https://github.com/Athari/AthariUserJS/issues
  26. // @version 1.8.0
  27. // @icon https://www.google.com/s2/favicons?sz=64&domain=kinorium.com
  28. // @match https://*.kinorium.com/*
  29. // @match https://*.wco.tv/*
  30. // @grant unsafeWindow
  31. // @grant GM_getValue
  32. // @grant GM_setValue
  33. // @grant GM_getResourceText
  34. // @grant GM_getResourceURL
  35. // @grant GM_info
  36. // @grant GM_registerMenuCommand
  37. // @run-at document-start
  38. // @require https://cdn.jsdelivr.net/npm/string@3.3.3/dist/string.min.js
  39. // @require https://cdn.jsdelivr.net/npm/@athari/monkeyutils@0.5.6/monkeyutils.u.min.js
  40. // @resource script-microdata https://cdn.jsdelivr.net/npm/@cucumber/microdata@2.1.0/dist/esm/src/index.min.js
  41. // @resource script-urlpattern https://cdn.jsdelivr.net/npm/urlpattern-polyfill/dist/urlpattern.js
  42. // @resource font-neucha-latin https://fonts.gstatic.com/s/neucha/v17/q5uGsou0JOdh94bfvQlt.woff2
  43. // @resource img-cinema-default https://images.kinorium.com/web/vod/vod_channels.svg
  44. // @resource img-cinema-rezka https://rezka.ag/templates/hdrezka/images/hdrezka-logo.png
  45. // @resource img-cinema-kisscartoon https://img.cartooncdn.xyz/themes/v3/images/logo.png
  46. // @resource img-cinema-wco https://www.wco.tv/logo.png
  47. // @resource img-cinema-mults https://mults.info/img/logo.png
  48. // @resource img-cinema-kinobox data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="16" height="32" viewBox="0 0 16 32"><polygon points="3,11 10,16 3,21" fill="%23eee" stroke="%23eee" stroke-width="4" stroke-linejoin="round" /></svg>
  49. // @resource img-cinema-youtube https://upload.wikimedia.org/wikipedia/commons/e/e1/Logo_of_YouTube_%282015-2017%29.svg
  50. // @resource img-cinema-dailymotion https://upload.wikimedia.org/wikipedia/commons/a/ac/Dailymotion_logo_%282015%29.svg
  51. // @resource img-cinema-vkvideo https://upload.wikimedia.org/wikipedia/commons/8/80/VK_Video.svg
  52. // @tag athari
  53. // ==/UserScript==
  54.  
  55. (async () => {
  56. 'use strict'
  57.  
  58. const { assignDeep, delay, waitForDocumentReady, h, u, f, utf8ToWin1251, toUrl, matchLocation, download, attempt, throwError, overrideProperty, overrideXmlHttpRequest, reviveConsole, wrapElement, ress, scripts, els, opts } =
  59. //require("../@athari-monkeyutils/monkeyutils.u"); // TODO
  60. athari.monkeyutils;
  61.  
  62. const hostKinorium = "*\.kinorium\.com";
  63. const hostWco = "*\.wco\.tv";
  64. const res = ress(), script = scripts(res);
  65. const eld = doc => els(doc, {
  66. dlgCollections: ".collectionWrapper.collectionsWindow",
  67. collectionCaches: ".collection_cache", lstCollection: ".collectionList, .filmList, .statuses", athMovieUserList: ".ath-movie-ulist",
  68. lazyImages: "img[data-preload], img[src*='/img/blank'][style^='background:']",
  69. lstCinemaButtons: ".film-page__buttons-cinema",
  70. item: ".item", itemComment: ".statusText", itemInfo: ".info", statusWidget: ".statusWidget",
  71. athItemComment: ".ath-item-status", athItemCommentRating: ".ath-item-status-rating",
  72. lnkTrailer: ".trailers__list .trailers__link, .trailer.item.video",
  73. mnuUser: ".userMenu",
  74. lstSites: ".sites_page .sites", lnkSiteDataHref: ".sites_page .sites a[href='#']:is([data-original-url], [data-url])", btnDelSite: ".sites_page .sites .delReport",
  75. filmLeftPanel: ".film-page_leftContent",
  76. }), el = eld(document);
  77. const ctls = ctl => els(ctl, {
  78. ctlMovieItem: ".item.movie", ctlColItemSpan: "span:is(.title, .icon, .cnt)", ctlColItemIcon: ".collectionList span.icon",
  79. checkbox: "input[type=checkbox]",
  80. });
  81. const opt = opts({
  82. listUserCollections: true, iconifyUserCollections: true, addExtraCinemaSources: true,
  83. commentsBelowRatings: true, directLinksToTrailers: true, addExternalLinks: true,
  84. nativeLazyImages: true,
  85. });
  86. const strs = {
  87. en: {
  88. listUserCollections: "List user collections",
  89. iconifyUserCollections: "Iconify user collections",
  90. addExtraCinemaSources: "Add extra cinema sources",
  91. nativeLazyImages: "Native lazy images",
  92. commentsBelowRatings: "Comments below ratings",
  93. directLinksToTrailers: "Direct links to videos",
  94. addExternalLinks: "External movie links in sidebar",
  95. watchMovieOn: "watch “%0%” on %1%",
  96. },
  97. ru: {
  98. listUserCollections: "Список коллекций юзера",
  99. iconifyUserCollections: "Иконки у коллекций юзера",
  100. addExtraCinemaSources: "Дополнительные кинотеатры",
  101. nativeLazyImages: "Нативные ленивые картинки",
  102. commentsBelowRatings: "Комментарии под оценками",
  103. directLinksToTrailers: "Прямые ссылки на видео",
  104. addExternalLinks: "Внешние ссылки на фильм сбоку",
  105. watchMovieOn: "смотреть «%0%» на %1%",
  106. },
  107. uk: {
  108. listUserCollections: "Список колекцій користувача",
  109. iconifyUserCollections: "Іконки у колекціях користувача",
  110. addExtraCinemaSources: "Додаткові кінотеатри",
  111. nativeLazyImages: "Нативні ліниві зображення",
  112. commentsBelowRatings: "Коментарі під оцінками",
  113. directLinksToTrailers: "Прямі посилання на відео",
  114. addExternalLinks: "Зовнішні посилання на фільм збоку",
  115. watchMovieOn: "дивитися «%0%» на %1%",
  116. },
  117. };
  118. const op = {};
  119.  
  120. const { log: consoleLog } = unsafeWindow.console;
  121. unsafeWindow.console.log = (...args) => args[0]?.includes?.("Не пиши парсер") ? throwError("fuck you") : consoleLog(...args);
  122. const consoleRevived = reviveConsole(unsafeWindow);
  123.  
  124. overrideXmlHttpRequest(unsafeWindow, {
  125. on: {
  126. load: async (_, load) => {
  127. load?.();
  128. await delay(0);
  129. if (!op.isInitialized)
  130. return;
  131. op.doCommentsBelowRatings();
  132. op.doListUserCollections();
  133. op.doNativeLazyImages();
  134. },
  135. },
  136. });
  137.  
  138. await consoleRevived;
  139. S.extendPrototype();
  140. Object.assign(globalThis, globalThis.URLPattern ? null : await script.urlpattern);
  141. console.debug("GM info", GM_info);
  142. //GM_registerMenuCommand("Config", e => alert(e), { accessKey: 'a', title: "Config Enhancer" });
  143.  
  144. //overrideProperty(unsafeWindow, 'loadedTimestamp', v => (v.setFullYear(3000), v));
  145.  
  146. console.log(await waitForDocumentReady());
  147. const { USER_ID: userId, PRO: userPro } = unsafeWindow;
  148. const language = { ua: 'uk' }[unsafeWindow.LANGUAGE ?? ''] ?? unsafeWindow.LANGUAGE ?? '';
  149. const str = strs[language] ?? strs.en;
  150.  
  151. let murl = null;
  152.  
  153. if (matchLocation(hostWco)) {
  154. if ((murl = matchLocation(hostWco, { pathname: "/cartoon-list", hash: "#\\?ath\\:*" })) != null) {
  155. for (const [k, v] of new URLSearchParams(location.hash.slice(1))) {
  156. const [ , cmdOp, ...cmdArgs ] = k.split(":");
  157. switch (cmdOp) {
  158. case 'el':
  159. const ctl = cmdArgs.reduce((a, v) => a[v], el);
  160. const [ cmdProp, cmdValue ] = v.split("=");
  161. ctl[cmdProp] = decodeURIComponent(cmdValue);
  162. if (cmdProp === 'value')
  163. setTimeout(() =>
  164. [ 'keydown', 'change', 'keyup' ].forEach(e => ctl.dispatchEvent(new Event(e))));
  165. break;
  166. }
  167. }
  168. }
  169. return;
  170. }
  171.  
  172. el.tag.head.insertAdjacentHTML('beforeEnd', /*html*/`
  173. <style>
  174. @font-face {
  175. font-family: 'Neucha';
  176. font-style: normal;
  177. font-weight: 400;
  178. font-display: block;
  179. src: url(${res.font.neucha.latin.url}) format('woff2');
  180. }
  181.  
  182. body {
  183. --ath-header-color-gray: #000c;
  184. --ath-text-color-gray-light: #777;
  185. &.body-dark {
  186. --ath-header-color-gray: #fffc;
  187. --ath-text-color-gray-light: #bbb;
  188. }
  189. }
  190.  
  191. .ath-movie-ulist {
  192. display: block;
  193. margin: 4rem 40rem 4rem 0rem;
  194. font-size: 14rem;
  195. color: #555;
  196. .body-dark & {
  197. color: #aaa;
  198. }
  199. .filmList__item-wrap-title & {
  200. margin: 4rem 80rem 4rem 110rem;
  201. }
  202. a {
  203. color: inherit !important;
  204. font-size: 14rem;
  205. text-decoration: inherit;
  206. &:hover {
  207. color: #f53 !important;
  208. }
  209. }
  210. }
  211.  
  212. .collectionWrapper.collectionsWindow .collectionList span.icon {
  213. background: none;
  214. text-align: center;
  215. font-size: 20rem;
  216. line-height: 32rem;
  217. opacity: 0.8;
  218. }
  219.  
  220. :is(.film-page__buttons-cinema, .ath-film-page__buttons-cinema) li {
  221. vertical-align: top;
  222. }
  223. .ath-film-page__buttons-cinema {
  224. li {
  225. display: inline-block;
  226. margin: 10rem 10rem 0 0;
  227. }
  228. }
  229. .ath-cinema:not(#\0) {
  230. width: 88px;
  231. height: 32px;
  232. text-align: center;
  233. color: #eee;
  234. background-color: #444;
  235. border-radius: 2rem;
  236. a:has(> &) {
  237. text-decoration: none !important;
  238. }
  239. &.ath-cinema-rezka {
  240. background: #282828 url(${res.img.cinema.rezka.data}) no-repeat 2px 3px / 83px 57px;
  241. filter: brightness(1.8) blur(0.5px);
  242. }
  243. &.ath-cinema-mults {
  244. background: #444 url(${res.img.cinema.mults.data}) no-repeat center 5px / 100px 24px;
  245. filter: grayscale(1);
  246. }
  247. &.ath-cinema-kisscartoon {
  248. /*background: #444 url(${res.img.cinema.kisscartoon.data}) no-repeat -1px -1px / 93px 35px;
  249. filter: grayscale(1);*/
  250. &::after {
  251. content: "KissCart👀n";
  252. display: block;
  253. width: fit-content;
  254. font: 600 21rem / 32px Times New Roman, emoji;
  255. transform: scaleX(0.8);
  256. transform-origin: left center;
  257. white-space: nowrap;
  258. background: linear-gradient(#eee 10px, #999 22px);
  259. -webkit-text-stroke: #eee 0.01px;
  260. -webkit-text-fill-color: transparent;
  261. -webkit-background-clip: text;
  262. }
  263. }
  264. &.ath-cinema-wco {
  265. background: #444 url(${res.img.cinema.wco.data}) no-repeat 0px -4px / 160px 40px;
  266. filter: grayscale(1);
  267. }
  268. &.ath-cinema-reyohoho {
  269. font: 600 22rem / 32px Neucha, Impact, sans-serif;
  270. letter-spacing: 1px;
  271. &::after {
  272. content: "ReYohoho";
  273. }
  274. }
  275. &.ath-cinema-kinobox {
  276. font: 600 18rem / 32px Arial, sans-serif;
  277. &::before {
  278. content: "";
  279. display: inline-block;
  280. width: 16px;
  281. height: 32px;
  282. vertical-align: top;
  283. background: url('${res.img.cinema.kinobox.url}') no-repeat center;
  284. }
  285. &::after {
  286. content: "Kinobox";
  287. display: inline;
  288. }
  289. }
  290. &.ath-cinema-kinogo {
  291. font: 600 19rem / 32px Times New Roman, sans-serif;
  292. letter-spacing: 1px;
  293. &::after {
  294. content: "KINOGO";
  295. }
  296. }
  297. &.ath-cinema-youtube {
  298. background: #bbb url(${res.img.cinema.youtube.data}) no-repeat center / 80px 26px;
  299. filter: invert(1) grayscale(1);
  300. }
  301. &.ath-cinema-dailymotion {
  302. background: #bbb url(${res.img.cinema.dailymotion.data}) no-repeat center / 84px 20px;
  303. filter: invert(1) grayscale(1);
  304. }
  305. &.ath-cinema-vkvideo {
  306. background: #444 url(${res.img.cinema.vkvideo.data}) no-repeat 2px center / 24px 24px;
  307. font: 600 15rem / 34px Arial, sans-serif;
  308. filter: grayscale(1);
  309. &::after {
  310. content: "VK Video";
  311. padding-left: 28px;
  312. }
  313. }
  314. }
  315.  
  316. .trailers__item {
  317. display: flex;
  318. flex-flow: column;
  319. .away-transparency {
  320. display: none;
  321. }
  322. }
  323. .trailer.item.video {
  324. height: auto !important;
  325. }
  326. .ath-trailer-title,
  327. .ath-trailer-link {
  328. display: block;
  329. max-width: 260rem;
  330. padding: 5rem 0 0 0;
  331. font-size: 13rem;
  332. text-decoration: inherit;
  333. color: #777;
  334. .body-dark & {
  335. color: #999;
  336. }
  337. .trailer.item.video & {
  338. padding: 3rem 5rem;
  339. }
  340. }
  341. .ath-trailer-title {
  342. font-weight: 500;
  343. }
  344.  
  345. .item .info .ath-item-status {
  346. display: grid;
  347. grid-template-columns: 32rem 1fr;
  348. gap: 20rem;
  349. min-height: 32rem;
  350. margin: 8rem 0;
  351. .ath-item-status-rating {
  352. margin: -10rem auto 0;
  353. .statusWidget {
  354. margin: 0;
  355. width: auto;
  356. min-width: 32rem;
  357. }
  358. }
  359. .statusText {
  360. display: block;
  361. align-self: end;
  362. margin: 0;
  363. font-size: 15rem;
  364. line-height: 1.3;
  365. color: var(--ath-text-color-gray-light);
  366. }
  367. }
  368.  
  369. .film-page_leftContent {
  370. .sites {
  371. .sites-page__title-group {
  372. margin: 15rem 0 8rem 0;
  373. font-size: 18rem;
  374. color: var(--ath-header-color-gray);
  375. }
  376. .sites-pages__item {
  377. margin: 6rem 0;
  378. a {
  379. font-size: 14rem;
  380. color: #21b0d0;
  381. text-decoration: none;
  382. &:hover {
  383. color: #ff5032;
  384. }
  385. .sites_page__flag {
  386. margin: 0 7rem 0 0;
  387. }
  388. img {
  389. display: inline;
  390. width: 16rem;
  391. height: 16rem;
  392. margin: 0 5rem 0 0;
  393. vertical-align: middle;
  394. }
  395. }
  396. }
  397. }
  398. }
  399.  
  400. .ath-menu-options {
  401. padding: 0 5rem;
  402. &:hover {
  403. background: rgba(33, 176, 208, .25);
  404. }
  405. .title {
  406. max-width: 240rem !important;
  407. }
  408. label {
  409. white-space: nowrap;
  410. }
  411. }
  412. </style>`);
  413.  
  414. (op.addOptionsMenu = () => attempt("add options menu", () => {
  415. const chks = [];
  416. const tplCheckbox = (id) => (
  417. chks.push({ id }), /*html*/`
  418. <label class="title">
  419. <input type="checkbox" id="ath-${id}" ${opt[id] ? 'checked' : ""}> ${str[id]}
  420. </label>`);
  421. const userScriptStr = (prop, def) => GM_info.script[`${prop}_i18n`]?.[language] ?? GM_info.script[prop] ?? def;
  422. el.mnuUser.insertAdjacentHTML('beforeEnd', /*html*/`
  423. <li class="settings settings_border ath-menu-options">
  424. <span class="icon"></span>
  425. <div class="title">
  426. <label title="${h(userScriptStr('description'))}">${h(userScriptStr('name'))} ${GM_info.script.version}</label>
  427. ${tplCheckbox('listUserCollections')}
  428. ${tplCheckbox('iconifyUserCollections')}
  429. ${tplCheckbox('addExtraCinemaSources')}
  430. ${tplCheckbox('commentsBelowRatings')}
  431. ${tplCheckbox('directLinksToTrailers')}
  432. ${tplCheckbox('addExternalLinks')}
  433. ${tplCheckbox('nativeLazyImages')}
  434. </div>
  435. </li>`);
  436. for (let chk of chks)
  437. el.id[`ath-${chk.id}`].onchange = (e) => opt[chk.id] = e.target.checked;
  438. }))();
  439.  
  440. let userCollections = [];
  441. (op.doListUserCollections = () => attempt("list user collections under movie title", () => {
  442. for (let elwCache of el.wrap.all.collectionCaches) {
  443. const cache = JSON.parse(elwCache.self.dataset.cache);
  444. console.debug("collection cache", cache);
  445. let { items: movies, ulist: collections } = cache;
  446. userCollections = /*userCollections.concat(collections)*/collections;
  447. const elwLstCollection = elwCache.wrap.parent.lstCollection;
  448. if (!opt.listUserCollections || movies == null || elwLstCollection == null || elwLstCollection.athMovieUserList != null)
  449. return;
  450. for (let [ itemId, uids ] of Object.entries(movies)) {
  451. const itemUlist = uids
  452. .map(uid => collections.filter(l => l.ulist_id == uid)[0])
  453. .sort((a, b) => a.sequence - b.sequence)
  454. .map(u => /*html*/`
  455. <a href="/user/${userId}/collection/movie/${u.ulist_id}/"
  456. >${u.icon != null ? `${u.icon} ` : ""}${u.title.replace(/[^\p{L}\p{N} -]/ug, '').trim()}</a>`);
  457. for (let elTitle of elwLstCollection.self.querySelectorAll(`:is(.movie-title__text, .link-info-movie-type-film)[data-id="${itemId}"]`))
  458. (elTitle.closest("h3") ?? elTitle.closest("a")).insertAdjacentHTML('afterEnd', /*html*/`
  459. <div class="ath-movie-ulist">${itemUlist.length > 0 ? itemUlist.join(", ") : "—"}<div>`);
  460. }
  461. }
  462. console.info("user collections", userCollections);
  463. }))();
  464.  
  465. (op.doNativeLazyImages = () => attempt("switch to native lazy loading of images", () => {
  466. if (!opt.nativeLazyImages)
  467. return;
  468. for (let elImage of el.all.lazyImages)
  469. assignDeep(elImage, {
  470. loading: 'lazy',
  471. style: { opacity: 1 },
  472. src: elImage.dataset.preload ?? elImage.style.backgroundImage?.replace(/url\("?(.*?)"?\)/, "$1"),
  473. });
  474. }))();
  475.  
  476. (op.doAddExtraCinemaSources = () => attempt("add extra external links", async () => {
  477. if ((murl = matchLocation(hostKinorium, { pathname: "/:movieId/" })) != null) {
  478. if (!opt.addExtraCinemaSources)
  479. return;
  480. const { microdata/*, microdataAll*/ } = await script.microdata;
  481. const movie = microdata("http://schema.org/Movie", document);
  482. console.log(movie);
  483. const titleRu = movie.name;
  484. const titleRu1251 = utf8ToWin1251(titleRu.replace(/ё/gi, "е").toLowerCase(), { encode: true });
  485. const titleOrig = movie.alternativeHeadline?.length > 0 ? movie.alternativeHeadline : titleRu;
  486. const cinemas = [
  487. { id: 'rezka', name: "HDRezka", url: `https://rezka.ag/search/?do=search&subaction=search&q=${u(titleOrig)}` },
  488. { id: 'reyohoho', name: "ReYohoho", url: `https://reyohoho.github.io/reyohoho/#search=${u(titleOrig)}` },
  489. { id: 'kisscartoon', name: "KissCartoon", url: `https://kisscartoon.sh/Search/?s=${u(titleOrig)}` },
  490. { id: 'wco', name: "WatchCartoonOnline", url: `https://www.wco.tv/cartoon-list#?ath:el:id:anime-search=${u(`value=${u(titleOrig)}`)}` },
  491. { id: 'mults', name: "Mults", url: `https://mults.info/mults/?wp=1&wd=1&s=${titleRu1251}` },
  492. { id: 'kinobox', name: "Kinobox", url: `https://kinohost.web.app/search?query=${u(titleOrig)}` },
  493. { id: 'kinogo', name: "Kinogo", url: `https://kinogo.fun/search/${u(titleRu)}` },
  494. { id: 'youtube', name: "YouTube", url: `https://youtube.com/results?search_query=${u(titleOrig)}` },
  495. { id: 'dailymotion', name: "DailyMotion", url: `https://dailymotion.com/search/${u(titleOrig)}/top-results` },
  496. { id: 'vkvideo', name: "VKVideo", url: `https://vkvideo.ru/?q=${u(titleRu)}` },
  497. ];
  498. el.lstCinemaButtons.insertAdjacentHTML('beforeEnd', cinemas.map(c => /*html*/`
  499. <li>
  500. <a title='${h(f(str.watchMovieOn, movie.name, c.name))}' href="${h(c.url.decodeHTMLEntities())}" target="_top">
  501. <div class="ath-cinema ath-cinema-${c.id}"></div>
  502. </a>
  503. </li>`).join(""));
  504. // fucking uBlock
  505. if (el.lstCinemaButtons.computedStyleMap().get('display').value === 'none') {
  506. el.lstCinemaButtons.style = "display: flex !important; flex-flow: row wrap !important;";
  507. el.lstCinemaButtons.classList.replace('film-page__buttons-cinema', 'ath-film-page__buttons-cinema');
  508. }
  509. }
  510. }))();
  511.  
  512. (op.doAddExternalLinks = () => attempt("add external links", async () => {
  513. if ((murl = matchLocation(hostKinorium, { pathname: "/:movieId/" })) != null) {
  514. if (!opt.addExternalLinks)
  515. return;
  516. const sitesEl = eld(await download(`/${murl.movieId}/sites/`, 'html'));
  517. for (const elLink of sitesEl.all.lnkSiteDataHref)
  518. elLink.href = elLink.dataset.originalUrl ?? elLink.dataset.url;
  519. for (const btnDelSite of sitesEl.all.btnDelSite)
  520. btnDelSite.remove();
  521. el.filmLeftPanel.insertAdjacentElement('beforeEnd', sitesEl.lstSites);
  522. }
  523. }))();
  524.  
  525. (op.doCommentsBelowRatings = () => attempt("show comments after user comments", () => {
  526. if (!opt.commentsBelowRatings)
  527. return;
  528. for (const elwComment of el.wrap.all.itemComment) {
  529. const elwItem = elwComment.wrap.parent.item;
  530. if (elwItem.athItemComment != null)
  531. continue;
  532. elwItem.itemInfo.insertAdjacentHTML('beforeEnd', /*html*/`
  533. <div class="ath-item-status">
  534. <div class="ath-item-status-rating"></div>
  535. </div>`);
  536. elwItem.athItemCommentRating.insertAdjacentElement('beforeEnd', elwItem.all.statusWidget.at(-1));
  537. elwItem.athItemComment.insertAdjacentElement('beforeEnd', elwComment.self);
  538. }
  539. }))();
  540.  
  541. (op.doDirectLinksToTrailers = () => attempt("add direct trailer links", () => {
  542. if (!opt.directLinksToTrailers)
  543. return;
  544. for (let lnkTrailer of el.all.lnkTrailer) {
  545. const elLnkTrailer = els(lnkTrailer);
  546. const trailer = { ...lnkTrailer.dataset, ...elLnkTrailer.tag.img?.dataset };
  547. const trailerUrl = toUrl(trailer.video);
  548. const trailerTitle = {
  549. "www.youtube.com": "YouTube",
  550. }[trailerUrl.hostname] ?? "Video";
  551. const trailerUrlHref = trailerUrl.toString()
  552. .replace("https://www.youtube.com/embed/", "https://www.youtube.com/watch?v=");
  553. if (lnkTrailer.classList.contains('item'))
  554. wrapElement(lnkTrailer, 'DIV', { copyAttrs: true });
  555. lnkTrailer.insertAdjacentHTML('beforeEnd', /*html*/`
  556. <span class="ath-trailer-title">${h(lnkTrailer.title)}</span>`);
  557. lnkTrailer.insertAdjacentHTML('afterEnd', /*html*/`
  558. <a class="ath-trailer-link" href="${h(trailerUrlHref)}">${h(trailerTitle)}</a>`);
  559. }
  560. }))();
  561.  
  562. (op.iconifyCollections = (force = false) => {
  563. if (!opt.iconifyUserCollections)
  564. return;
  565. const dlgCollections = el.dlgCollections;
  566. if (dlgCollections == null || (!force && dlgCollections.classList.contains('ath-iconified')))
  567. return;
  568. dlgCollections.classList.add('ath-iconified');
  569. const ctl = ctls(dlgCollections);
  570. let i = 0;
  571. for (let icon of ctl.all.ctlColItemIcon)
  572. icon.innerText = userCollections[i++].icon;
  573. })();
  574.  
  575. [ 'click', 'mouseup' ].forEach(e => document.addEventListener(e, async e => {
  576. console.debug("document event", e.type, e.target, e);
  577. const ctl = ctls(e.target);
  578. const pctl = ctls(e.target.parentElement);
  579. // Collection list checkbox
  580. if (e.type == 'click' && ctl.is.ctlColItemSpan && pctl.is.ctlMovieItem) {
  581. e.preventDefault();
  582. pctl.checkbox.click();
  583. }
  584. await delay(0);
  585. if (e.type == 'mouseup') {
  586. op.iconifyCollections(true);
  587. }
  588. }));
  589.  
  590. op.isInitialized = true;
  591.  
  592. for (;;) {
  593. attempt("iconify user collections popup", () => {
  594. op.iconifyCollections();
  595. });
  596. await delay(200);
  597. }
  598. })();