Camwhores.tv Evolution

This script adds useful configurable features to the camwhores.tv site.

  1. // ==UserScript==
  2. // @name Camwhores.tv Evolution
  3. // @description This script adds useful configurable features to the camwhores.tv site.
  4. // @icon data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQAgMAAABinRfyAAAAIGNIUk0AAHomAACAhAAA+gAAAIDoAAB1MAAA6mAAADqYAAAXcJy6UTwAAAAMUExURQAAAP8ANwwA/////7gbQJkAAAABdFJOUwBA5thmAAAAAWJLR0QDEQxM8gAAAAd0SU1FB+gDHhIuCjXV/h8AAAA4SURBVAjXY2ANDQ1gEA0NDWEIYWBgZAhgAAIUghEiC1YHBhpMDRpIhBbXghUMXKtWLWBgWqHVAACjlwz/pN0YPwAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAyNC0wMy0zMFQxODo0NjowOSswMDowME+iXNIAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMjQtMDMtMzBUMTg6NDY6MDkrMDA6MDA+/+RuAAAAKHRFWHRkYXRlOnRpbWVzdGFtcAAyMDI0LTAzLTMwVDE4OjQ2OjEwKzAwOjAwMNiA/AAAAABJRU5ErkJggg==
  5. // @version 1.2.1
  6. // @license MIT
  7. // @namespace cw-evolution
  8. // @match https://www.camwhores.tv/*
  9. // @match https://camwhores.tv/*
  10. // @exclude *.camwhores.tv/*mode=async*
  11. // @grant none
  12. // ==/UserScript==
  13.  
  14. if (!location.href.startsWith('https://www.')) {
  15. location.href = location.href.replace('https://', 'https://www.');
  16. return;
  17. }
  18.  
  19. var config = Object.assign(
  20. {
  21. extendItemInformation: false,
  22. removeLocked: false,
  23. infiniteScroll: false,
  24. muteVideoOnLoad: false,
  25. customStyles: false,
  26. },
  27. JSON.parse(localStorage.getItem('cw-evolution-config'))
  28. );
  29.  
  30. var icons = {
  31. circleNotch: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!--!Font Awesome Free 6.5.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.--><path d="M222.7 32.1c5 16.9-4.6 34.8-21.5 39.8C121.8 95.6 64 169.1 64 256c0 106 86 192 192 192s192-86 192-192c0-86.9-57.8-160.4-137.1-184.1c-16.9-5-26.6-22.9-21.5-39.8s22.9-26.6 39.8-21.5C434.9 42.1 512 140 512 256c0 141.4-114.6 256-256 256S0 397.4 0 256C0 140 77.1 42.1 182.9 10.6c16.9-5 34.8 4.6 39.8 21.5z"/></svg>',
  32. lightbulb: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!--!Font Awesome Free 6.5.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.--><path d="M297.2 248.9C311.6 228.3 320 203.2 320 176c0-70.7-57.3-128-128-128S64 105.3 64 176c0 27.2 8.4 52.3 22.8 72.9c3.7 5.3 8.1 11.3 12.8 17.7l0 0c12.9 17.7 28.3 38.9 39.8 59.8c10.4 19 15.7 38.8 18.3 57.5H109c-2.2-12-5.9-23.7-11.8-34.5c-9.9-18-22.2-34.9-34.5-51.8l0 0 0 0c-5.2-7.1-10.4-14.2-15.4-21.4C27.6 247.9 16 213.3 16 176C16 78.8 94.8 0 192 0s176 78.8 176 176c0 37.3-11.6 71.9-31.4 100.3c-5 7.2-10.2 14.3-15.4 21.4l0 0 0 0c-12.3 16.8-24.6 33.7-34.5 51.8c-5.9 10.8-9.6 22.5-11.8 34.5H226.4c2.6-18.7 7.9-38.6 18.3-57.5c11.5-20.9 26.9-42.1 39.8-59.8l0 0 0 0 0 0c4.7-6.4 9-12.4 12.7-17.7zM192 128c-26.5 0-48 21.5-48 48c0 8.8-7.2 16-16 16s-16-7.2-16-16c0-44.2 35.8-80 80-80c8.8 0 16 7.2 16 16s-7.2 16-16 16zm0 384c-44.2 0-80-35.8-80-80V416H272v16c0 44.2-35.8 80-80 80z"/></svg>',
  33. eraser: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512"><!--!Font Awesome Free 6.5.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.--><path d="M290.7 57.4L57.4 290.7c-25 25-25 65.5 0 90.5l80 80c12 12 28.3 18.7 45.3 18.7H288h9.4H512c17.7 0 32-14.3 32-32s-14.3-32-32-32H387.9L518.6 285.3c25-25 25-65.5 0-90.5L381.3 57.4c-25-25-65.5-25-90.5 0zM297.4 416H288l-105.4 0-80-80L227.3 211.3 364.7 348.7 297.4 416z"/></svg>',
  34. infinite: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--!Font Awesome Free 6.5.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.--><path d="M0 241.1C0 161 65 96 145.1 96c38.5 0 75.4 15.3 102.6 42.5L320 210.7l72.2-72.2C419.5 111.3 456.4 96 494.9 96C575 96 640 161 640 241.1v29.7C640 351 575 416 494.9 416c-38.5 0-75.4-15.3-102.6-42.5L320 301.3l-72.2 72.2C220.5 400.7 183.6 416 145.1 416C65 416 0 351 0 270.9V241.1zM274.7 256l-72.2-72.2c-15.2-15.2-35.9-23.8-57.4-23.8C100.3 160 64 196.3 64 241.1v29.7c0 44.8 36.3 81.1 81.1 81.1c21.5 0 42.2-8.5 57.4-23.8L274.7 256zm90.5 0l72.2 72.2c15.2 15.2 35.9 23.8 57.4 23.8c44.8 0 81.1-36.3 81.1-81.1V241.1c0-44.8-36.3-81.1-81.1-81.1c-21.5 0-42.2 8.5-57.4 23.8L365.3 256z"/></svg>',
  35. mute: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512"><!--!Font Awesome Free 6.5.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.--><path d="M301.1 34.8C312.6 40 320 51.4 320 64V448c0 12.6-7.4 24-18.9 29.2s-25 3.1-34.4-5.3L131.8 352H64c-35.3 0-64-28.7-64-64V224c0-35.3 28.7-64 64-64h67.8L266.7 40.1c9.4-8.4 22.9-10.4 34.4-5.3zM425 167l55 55 55-55c9.4-9.4 24.6-9.4 33.9 0s9.4 24.6 0 33.9l-55 55 55 55c9.4 9.4 9.4 24.6 0 33.9s-24.6 9.4-33.9 0l-55-55-55 55c-9.4 9.4-24.6 9.4-33.9 0s-9.4-24.6 0-33.9l55-55-55-55c-9.4-9.4-9.4-24.6 0-33.9s24.6-9.4 33.9 0z"/></svg>',
  36. brush: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512"><!--!Font Awesome Free 6.5.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.--><path d="M339.3 367.1c27.3-3.9 51.9-19.4 67.2-42.9L568.2 74.1c12.6-19.5 9.4-45.3-7.6-61.2S517.7-4.4 499.1 9.6L262.4 187.2c-24 18-38.2 46.1-38.4 76.1L339.3 367.1zm-19.6 25.4l-116-104.4C143.9 290.3 96 339.6 96 400c0 3.9 .2 7.8 .6 11.6C98.4 429.1 86.4 448 68.8 448H64c-17.7 0-32 14.3-32 32s14.3 32 32 32H208c61.9 0 112-50.1 112-112c0-2.5-.1-5-.2-7.5z"/></svg>',
  37. download: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!--!Font Awesome Free 6.5.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.--><path d="M288 32c0-17.7-14.3-32-32-32s-32 14.3-32 32V274.7l-73.4-73.4c-12.5-12.5-32.8-12.5-45.3 0s-12.5 32.8 0 45.3l128 128c12.5 12.5 32.8 12.5 45.3 0l128-128c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0L288 274.7V32zM64 352c-35.3 0-64 28.7-64 64v32c0 35.3 28.7 64 64 64H448c35.3 0 64-28.7 64-64V416c0-35.3-28.7-64-64-64H346.5l-45.3 45.3c-25 25-65.5 25-90.5 0L165.5 352H64zm368 56a24 24 0 1 1 0 48 24 24 0 1 1 0-48z"/></svg>',
  38. thumbsUp: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!--!Font Awesome Free 6.5.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.--><path d="M313.4 32.9c26 5.2 42.9 30.5 37.7 56.5l-2.3 11.4c-5.3 26.7-15.1 52.1-28.8 75.2H464c26.5 0 48 21.5 48 48c0 18.5-10.5 34.6-25.9 42.6C497 275.4 504 288.9 504 304c0 23.4-16.8 42.9-38.9 47.1c4.4 7.3 6.9 15.8 6.9 24.9c0 21.3-13.9 39.4-33.1 45.6c.7 3.3 1.1 6.8 1.1 10.4c0 26.5-21.5 48-48 48H294.5c-19 0-37.5-5.6-53.3-16.1l-38.5-25.7C176 420.4 160 390.4 160 358.3V320 272 247.1c0-29.2 13.3-56.7 36-75l7.4-5.9c26.5-21.2 44.6-51 51.2-84.2l2.3-11.4c5.2-26 30.5-42.9 56.5-37.7zM32 192H96c17.7 0 32 14.3 32 32V448c0 17.7-14.3 32-32 32H32c-17.7 0-32-14.3-32-32V224c0-17.7 14.3-32 32-32z"/></svg>',
  39. };
  40.  
  41. function injectedScript(config, icons) {
  42. var processItems = () => {
  43. var friendRequestAEl = document.querySelector(
  44. '#list_messages_my_conversation_messages h2 a:last-child:not(.processed)'
  45. );
  46. if (friendRequestAEl) {
  47. friendRequestAEl.classList.add('processed');
  48. var profileReq = new XMLHttpRequest();
  49. profileReq.responseType = 'document';
  50. profileReq.addEventListener('load', function () {
  51. var videoCountEl;
  52. this.response.querySelectorAll('.item').forEach((it) => {
  53. if (it.innerText.includes('Posted:')) {
  54. videoCountEl = it;
  55. }
  56. });
  57. if (videoCountEl) {
  58. var userVideoCount = +videoCountEl.innerText.replace('Posted:', '').split(',')[0].replace('videos', '').trim();
  59. userVideoCount = isNaN(userVideoCount) ? 0 : userVideoCount;
  60. var videosRefEl = document.createElement('a');
  61. videosRefEl.classList.add('processed');
  62. videosRefEl.href = friendRequestAEl.href + 'videos/';
  63. videosRefEl.innerText += `(Videos: ${userVideoCount})`;
  64. videosRefEl.style.marginLeft = '4px';
  65. friendRequestAEl.parentNode.insertBefore(videosRefEl, friendRequestAEl.nextSibling);
  66. }
  67. });
  68. profileReq.open('GET', friendRequestAEl.href);
  69. profileReq.send();
  70. return;
  71. }
  72.  
  73. var videoEl = document.querySelector('video:not(.processed)');
  74. if (videoEl) {
  75. videoEl.classList.add('processed');
  76. var liEl = document.createElement('li');
  77. liEl.innerHTML = `<a href="${videoEl.src}" class="toggle-button download-button" target="_blank">${icons.download}</a>`;
  78. document.querySelector('.tabs-menu > ul').appendChild(liEl);
  79. }
  80.  
  81. if (!config.extendItemInformation && !config.removeLocked) {
  82. return;
  83. }
  84.  
  85. document.querySelectorAll('.item:not(.processed)').forEach((it) => {
  86. if (it.classList.contains('processed')) {
  87. return;
  88. }
  89.  
  90. it.classList.add('processed');
  91.  
  92. var privateEl = it.querySelector('.line-private');
  93. var isNonPrivate = privateEl === null;
  94.  
  95. if (!privateEl) {
  96. var imgWrapperEl = it.querySelector('.img');
  97. if (imgWrapperEl) {
  98. privateEl = document.createElement('a');
  99. privateEl.target = config.infiniteScroll ? '_blank' : '_self';
  100. privateEl.classList.add('line-private');
  101. imgWrapperEl.appendChild(privateEl);
  102. }
  103. } else {
  104. var aEl = document.createElement('a');
  105. aEl.target = config.infiniteScroll ? '_blank' : '_self';
  106. aEl.classList.add('line-private');
  107. privateEl.parentElement.replaceChild(aEl, privateEl);
  108. privateEl = aEl;
  109. }
  110.  
  111. if (!privateEl) {
  112. return;
  113. }
  114.  
  115. var lockedContainerIds = [
  116. 'list_members_my_conversations_items',
  117. 'list_members_my_friends_items',
  118. 'list_members_friends_items',
  119. ];
  120. if (
  121. lockedContainerIds.includes(it.parentElement.id) ||
  122. lockedContainerIds.includes(it.parentElement.parentElement.id)
  123. ) {
  124. return;
  125. }
  126.  
  127. var loaderEl = document.createElement('div');
  128. if (config.extendItemInformation) {
  129. loaderEl.classList.add('loader');
  130. loaderEl.innerHTML = icons.circleNotch;
  131. privateEl.parentElement.appendChild(loaderEl);
  132. }
  133.  
  134. privateEl.style.opacity = 0;
  135.  
  136. var req = new XMLHttpRequest();
  137. req.responseType = 'document';
  138. req.addEventListener('load', function () {
  139. var usernameLinkEl = this.response.querySelector('.username > a');
  140. privateEl.href = usernameLinkEl.href;
  141.  
  142. if (!this.response.querySelector('.message') || !config.removeLocked) {
  143. it.querySelector('img').style.opacity = '1';
  144. privateEl.innerText = this.response.querySelector('.username').innerText.trim();
  145. privateEl.style.color = 'black';
  146. privateEl.classList.add('with-transition');
  147. privateEl.style.opacity = 0.7;
  148. loaderEl.style.opacity = 0;
  149. loaderEl.style['pointer-events'] = 'none';
  150. }
  151.  
  152. if (config.extendItemInformation) {
  153. privateEl.style.background = isNonPrivate
  154. ? '#FFFFFF'
  155. : !this.response.querySelector('.message')
  156. ? '#7dc78a'
  157. : '#fc5729';
  158. }
  159.  
  160. if (this.response.querySelector('.message') && config.removeLocked) {
  161. it.remove();
  162. }
  163. });
  164. req.open('GET', it.querySelector('a:last-child').href);
  165. req.send();
  166. });
  167. };
  168.  
  169. var onDomUpdate = () => {
  170. processItems();
  171.  
  172. if (
  173. (document.querySelector('.filter') || document.querySelector('.sort')) &&
  174. !document.querySelector('.profile-list')
  175. ) {
  176. document.querySelectorAll('.box:not(.processed)').forEach((boxEl) => {
  177. boxEl.classList.add('processed');
  178. if (boxEl.parentElement.id === 'list_messages_my_conversation_messages') {
  179. return;
  180. }
  181. var h2El = boxEl.parentElement.querySelector(':scope > .headline h1, :scope > .headline h2');
  182. var paginationEl = boxEl.parentElement.querySelector(':scope > .pagination > .pagination-holder');
  183.  
  184. if (paginationEl) {
  185. var isLoading = false;
  186. var dataset = paginationEl.querySelector('.last > a')?.dataset;
  187. var isLastPage = dataset === undefined;
  188. if (isLastPage) {
  189. const pages = paginationEl.querySelectorAll('.page');
  190. dataset = pages[pages.length - 1].querySelector(':scope > a').dataset;
  191. }
  192. var o = Object.assign(
  193. Object.fromEntries(dataset.parameters.split(';').map((x) => [x.split(':')[0], x.split(':')[1]])),
  194. { block_id: dataset.blockId }
  195. );
  196. o.q = o.q ?? '';
  197. o.category_ids = o.category_ids ?? '';
  198. var maxPage = +(o['from_videos+from_albums'] ?? o['from_videos'] ?? o['from_my_videos']);
  199. if (isLastPage) {
  200. maxPage++;
  201. }
  202.  
  203. var firstPageReq = new XMLHttpRequest();
  204. firstPageReq.responseType = 'document';
  205. firstPageReq.addEventListener('load', function () {
  206. var firstPageItems = this.response.querySelectorAll('.item');
  207. var lastPageReq = new XMLHttpRequest();
  208. lastPageReq.responseType = 'document';
  209. lastPageReq.addEventListener('load', function () {
  210. var lastPageItems = this.response.querySelectorAll('.item');
  211. if (h2El) {
  212. if (h2El.innerText.includes('(')) {
  213. h2El.innerText = h2El.innerText.substr(0, h2El.innerText.indexOf('('));
  214. }
  215. h2El.innerText += ` (Results: ${
  216. firstPageItems.length * (maxPage - 1) + lastPageItems.length
  217. } - Pages: ${maxPage})`;
  218. }
  219. });
  220. lastPageReq.open(
  221. 'GET',
  222. `${location.href}?mode=async&function=get_block&block_id=${o.block_id}&q=${o.q}&category_ids=${o.category_ids}&sort_by=${o.sort_by}&from_videos=${maxPage}&from_albums=${maxPage}&from=${maxPage}&from_my_videos=${maxPage}`
  223. );
  224. lastPageReq.send();
  225. });
  226. firstPageReq.open(
  227. 'GET',
  228. `${location.href}?mode=async&function=get_block&block_id=${o.block_id}&q=${o.q}&category_ids=${o.category_ids}&sort_by=${o.sort_by}&from_videos=1&from_albums=1&from=1&from_my_videos=1`
  229. );
  230. firstPageReq.send();
  231.  
  232. if (config.infiniteScroll) {
  233. o['page'] = 2;
  234. paginationEl.innerHTML = '';
  235. var videosWrapperEl = paginationEl.parentNode.parentNode.querySelector('.list-videos').children[0];
  236. videosWrapperEl.querySelectorAll('a').forEach((aEl) => {
  237. aEl.target = '_blank';
  238. });
  239. var createIntermediateHeader = (page) => {
  240. var intermediateHeader = document.createElement('div');
  241. intermediateHeader.classList.add('intermediate-header');
  242. var intermediateHeaderSpan = document.createElement('span');
  243. intermediateHeaderSpan.innerText = `${page}/${maxPage}`;
  244. intermediateHeader.append(intermediateHeaderSpan);
  245. return intermediateHeader;
  246. }
  247. videosWrapperEl.prepend(createIntermediateHeader(1));
  248. new IntersectionObserver(function () {
  249. if (isLoading || o['page'] > maxPage) {
  250. return;
  251. }
  252. isLoading = true;
  253. var pageReq = new XMLHttpRequest();
  254. pageReq.responseType = 'document';
  255. pageReq.addEventListener('load', function () {
  256. var newItems = this.response.querySelectorAll('.item');
  257. newItems.forEach((el) => {
  258. el.querySelector('a').target = '_blank';
  259. });
  260. videosWrapperEl.append(createIntermediateHeader(o['page'] - 1));
  261. videosWrapperEl.append(...this.response.querySelectorAll('.item'));
  262. $('.lazy-load[data-original]', videosWrapperEl).Lazy({ attribute: 'data-original' }).thumbs();
  263. if (config.extendItemInformation || config.removeLocked) {
  264. processItems();
  265. }
  266. isLoading = false;
  267. });
  268. pageReq.open(
  269. 'GET',
  270. `${location.href}?mode=async&function=get_block&block_id=${o.block_id}&q=${o.q}&category_ids=${o.category_ids}&sort_by=${o.sort_by}&from_videos=${o['page']}&from_albums=${o['page']}&from=${o['page']}&from_my_videos=${maxPage}`
  271. );
  272. pageReq.send();
  273. o['page']++;
  274. }).observe(paginationEl);
  275. }
  276. } else if (h2El) {
  277. if (h2El.innerText.includes('(')) {
  278. h2El.innerText = h2El.innerText.substr(0, h2El.innerText.indexOf('('));
  279. }
  280. h2El.innerText += ` (Results: ${boxEl.querySelectorAll('.item').length} - Pages: 1)`;
  281. }
  282. });
  283. }
  284.  
  285. if (config.muteVideoOnLoad && typeof flowplayer !== 'undefined') {
  286. var wait = setInterval(() => {
  287. if (flowplayer() && !flowplayer().muted) {
  288. flowplayer().mute();
  289. clearInterval(wait);
  290. }
  291. }, 100);
  292. }
  293. };
  294.  
  295. var domUpdateDelay;
  296. new MutationObserver(() => {
  297. clearTimeout(domUpdateDelay);
  298. domUpdateDelay = setTimeout(() => {
  299. onDomUpdate();
  300. }, 10);
  301. }).observe(document, { attributes: false, childList: true, subtree: true });
  302. }
  303.  
  304. var scriptEl = document.createElement('script');
  305. scriptEl.appendChild(
  306. document.createTextNode('(' + injectedScript + ')(' + JSON.stringify(config) + ', ' + JSON.stringify(icons) + ');')
  307. );
  308. (document.body || document.head || document.documentElement).appendChild(scriptEl);
  309.  
  310. if (config.customStyles) {
  311. var customStylesEl = document.createElement('style');
  312. customStylesEl.textContent = `
  313. body {
  314. --cols: 2;
  315. }
  316.  
  317. @media only screen and (min-width: 600px) {
  318. body {
  319. --cols: 3;
  320. }
  321. }
  322.  
  323. @media only screen and (min-width: 900px) {
  324. body {
  325. --cols: 4;
  326. }
  327. }
  328. html {
  329. background: #202020;
  330. }
  331. .logo a {
  332. filter: brightness(0) invert(1);
  333. }
  334.  
  335. .list-videos > div:not(#list_videos_my_uploaded_videos_items),
  336. .list-albums > div,
  337. .list-members > div:not(#list_members_my_friends_items),
  338. .list-members > div > form,
  339. .list-videos > div > form {
  340. display: grid;
  341. grid-template-columns: repeat(var(--cols), 1fr);
  342. gap: 5px;
  343. margin: 0;
  344. }
  345.  
  346. .list-videos .item,
  347. .list-albums .item,
  348. .list-members .item,
  349. .list-members .item,
  350. .list-videos .item {
  351. width: auto !important;
  352. padding: 5px !important;
  353. margin: 0 !important;
  354. background: rgba(0,0,0,0.3);
  355. border-radius: 5px;
  356. }
  357.  
  358. .list-videos .item:hover,
  359. .list-albums .item:hover,
  360. .list-members .item:hover,
  361. .list-members .item:hover,
  362. .list-videos .item:hover {
  363. background: rgba(0,0,0,0.7);
  364. }
  365.  
  366. #list_videos_my_uploaded_videos_items,
  367. #list_members_my_friends_items{
  368. margin: 0;
  369. }
  370.  
  371. .list-videos > div > *:not(div):not(form) {
  372. display: none;
  373. }
  374.  
  375. .item .img {
  376. width: auto;
  377. height: auto;
  378. margin: -5px -5px 0;
  379. border-radius: 5px;
  380. aspect-ratio: 1.33;
  381. }
  382.  
  383. .item .title,
  384. .item .wrap {
  385. margin-left: 0;
  386. margin-right: 0;
  387. height: auto;
  388. }
  389. .item .line-private {
  390. width: auto;
  391. max-width: calc(100% - 24px);
  392. padding: 4px 8px;
  393. border-radius: 8px;
  394. bottom: 4px;
  395. right: 4px;
  396. left: auto;
  397. text-overflow: ellipsis;
  398. white-space: nowrap;
  399. overflow: hidden;
  400. }
  401. .item .loader {
  402. position: absolute;
  403. top: 0;
  404. left: 0;
  405. width: 100%;
  406. height: 100%;
  407. display: flex;
  408. align-items: center;
  409. justify-content: center;
  410. background: rgba(0, 0, 0, .4);
  411. color: white;
  412. transition: opacity .3s;
  413. }
  414. .item .loader svg {
  415. fill: currentcolor;
  416. width: 26px;
  417. height: 26px;
  418. animation: spin 1s linear infinite;
  419. }
  420. .box {
  421. background: transparent;
  422. padding: 0;
  423. }
  424. .intermediate-header {
  425. display: flex;
  426. color: rgba(255,255,255,.1);
  427. font-size: 4rem;
  428. line-height: 4rem;
  429. font-weight: 800;
  430. text-transform: uppercase;
  431. grid-column-start: 1;
  432. grid-column-end: 5;
  433. perspective: 50px;
  434. perspective-origin: left;
  435. }
  436. .intermediate-header:not(:first-child) {
  437. margin-top: 12px;
  438. }
  439. .intermediate-header span {
  440. transform: rotateY(10deg);
  441. transform-origin: left;
  442. }
  443. `;
  444. (document.body || document.head || document.documentElement).appendChild(customStylesEl);
  445. }
  446.  
  447. var pluginStylesEl = document.createElement('style');
  448. pluginStylesEl.textContent = `
  449. .item img {
  450. transition: opacity .3s cubic-bezier(0.79, 0.33, 0.14, 0.53);
  451. }
  452. .item .line-private:hover {
  453. opacity: 1 !important;
  454. }
  455.  
  456. .item .line-private.with-transition {
  457. transition: opacity .3s;
  458. }
  459. .pagination .prev,
  460. .pagination .next {
  461. display: inline;
  462. }
  463. .download-button svg {
  464. fill: currentcolor;
  465. height: 14px;
  466. vertical-align: top;
  467. }
  468.  
  469. .cw-evolution-options {
  470. position: fixed;
  471. bottom: 16px;
  472. right: 16px;
  473. height: 64px;
  474. width: 64px;
  475. border-radius: 64px;
  476. background: #171717;
  477. transition: height .3s cubic-bezier(0.79, 0.33, 0.14, 0.53);
  478. overflow: hidden;
  479. opacity: 0.8;
  480. }
  481.  
  482. .cw-evolution-options.opened:not(.locked) {
  483. height: 448px;
  484. }
  485.  
  486. .cw-evolution-options.locked .icon-wrapper > button:last-child {
  487. animation: spin 1s linear infinite;
  488. }
  489.  
  490. .cw-evolution-options.opened .icon-wrapper > button:last-child {
  491. transform: rotate(0);
  492. }
  493.  
  494. .cw-evolution-options .icon-wrapper {
  495. position: absolute;
  496. bottom: 0;
  497. right: 0;
  498. display: flex;
  499. flex-direction: column;
  500. width: 64px;
  501. }
  502.  
  503. .cw-evolution-options .icon-wrapper button {
  504. display: flex;
  505. align-items: center;
  506. justify-content: center;
  507. flex: 0 0 auto;
  508. width: 64px;
  509. height: 64px;
  510. background: transparent;
  511. border: none;
  512. padding: 0;
  513. border-radius: 64px;
  514. color: white;
  515. cursor: pointer;
  516. transition: transform .3s cubic-bezier(.51,.92,.24,1.15), color .3s cubic-bezier(0.79, 0.33, 0.14, 0.53);
  517. }
  518.  
  519. .cw-evolution-options .icon-wrapper button:last-child {
  520. transform: rotate(90deg);
  521. }
  522.  
  523. .cw-evolution-options .icon-wrapper button:not(:last-child).active {
  524. color: #51b523;
  525. }
  526.  
  527. .cw-evolution-options .icon-wrapper button:not(:last-child):not(.active) {
  528. color: #e0115f;
  529. }
  530.  
  531. .cw-evolution-options .icon-wrapper button svg {
  532. width: 26px;
  533. height: 26px;
  534. fill: currentColor;
  535. }
  536.  
  537. @keyframes spin {
  538. 100% {
  539. transform:rotate(360deg);
  540. }
  541. }
  542. `;
  543. (document.body || document.head || document.documentElement).appendChild(pluginStylesEl);
  544.  
  545. var optionsDialog = document.createElement('div');
  546. optionsDialog.classList.add('cw-evolution-options');
  547. var wrapper = document.createElement('div');
  548. wrapper.classList.add('icon-wrapper');
  549.  
  550. var addButton = (icon, title, isActive, callback) => {
  551. var b = document.createElement('button');
  552. b.title = title;
  553. b.innerHTML = icon;
  554. if (isActive) {
  555. b.classList.add('active');
  556. }
  557. b.addEventListener('click', callback);
  558. wrapper.append(b);
  559. };
  560.  
  561. var applyConfig = () => {
  562. localStorage.setItem('cw-evolution-config', JSON.stringify(config));
  563. optionsDialog.classList.add('locked');
  564. location.reload();
  565. };
  566.  
  567. addButton(
  568. icons.lightbulb,
  569. 'Extend videos in list views with additional Information, like the uploader name and if its a friend',
  570. config.extendItemInformation,
  571. function () {
  572. config.extendItemInformation = !this.classList.contains('active');
  573. applyConfig();
  574. }
  575. );
  576.  
  577. addButton(
  578. icons.eraser,
  579. 'Removes all private videos of non-friends from the liew views',
  580. config.removeLocked,
  581. function () {
  582. config.removeLocked = !this.classList.contains('active');
  583. applyConfig();
  584. }
  585. );
  586.  
  587. addButton(icons.infinite, 'Converts the pagination to infinite scroll', config.infiniteScroll, function () {
  588. config.infiniteScroll = !this.classList.contains('active');
  589. applyConfig();
  590. });
  591.  
  592. addButton(icons.mute, 'Mutes a video initially', config.muteVideoOnLoad, function () {
  593. config.muteVideoOnLoad = !this.classList.contains('active');
  594. applyConfig();
  595. });
  596.  
  597. addButton(icons.brush, 'Adds some UI style changes', config.customStyles, function () {
  598. config.customStyles = !this.classList.contains('active');
  599. applyConfig();
  600. });
  601.  
  602. addButton(icons.thumbsUp, 'Rate', true, function () {
  603. Object.assign(document.createElement('a'), {
  604. target: '_blank',
  605. rel: 'noopener noreferrer',
  606. href: 'https://sleazyfork.org/de/scripts/498458-camwhores-tv-evolution/feedback#post-discussion',
  607. }).click();
  608. });
  609.  
  610. addButton(icons.circleNotch, '', false, () => {
  611. if (!optionsDialog.classList.contains('locked')) {
  612. optionsDialog.classList.toggle('opened');
  613. }
  614. });
  615.  
  616. optionsDialog.append(wrapper);
  617. document.body.appendChild(optionsDialog);