coomer-optimizer

Improves coomer.party

  1. // ==UserScript==
  2. // @name coomer-optimizer
  3. // @namespace https://coomer.su/
  4. // @version 3.2
  5. // @description Improves coomer.party
  6. // @match https://coomer.su/*
  7. // @grant GM_addStyle
  8. // @grant GM_setClipboard
  9. // @license MIT
  10. // @run-at document-end
  11. // @homepage https://greasyfork.org/en/scripts/468758-coomer-optimizer
  12. // ==/UserScript==
  13.  
  14. (function () {
  15. 'use strict';
  16.  
  17. GM_addStyle('.card--dislike {border-color: hsl(0, 100%, 50%); border-style: solid; border-width: 2px; }');
  18. GM_addStyle('.card--nevermet {border-color: hsl(120, 100%, 20%); border-style: solid; border-width: 2px; }');
  19.  
  20.  
  21. /**
  22. * Extract the summary object from the URL
  23. */
  24. const extractSummary = function (url, orUser) {
  25. let m = url.match(/user\/([^\/]+)/);
  26. if (m) {
  27. let userKey = key(m[1]);
  28. let summary = JSON.parse(localStorage.getItem(userKey));
  29. if (summary && !summary.user) {
  30. summary.user = m[1];
  31. }
  32. // console.debug('URL: ', url, " --> key: ", userKey, " --> summary: ", summary);
  33. return summary ? summary : (orUser ? m[1] : summary);
  34. } else {
  35. // console.debug('URL: ', url, " --> no user");
  36. return false;
  37. }
  38.  
  39. }
  40.  
  41. const key = function (_usr) {
  42. return `copt_${_usr}`;
  43. }
  44.  
  45. const getEl = function(_root_el, _tag, _class) {
  46. let _el;
  47. if (_root_el.tagName == _tag.toUpperCase() && _root_el.classList.contains(_class)) {
  48. _el = _root_el;
  49. } else if (_root_el.querySelector) {
  50. _el = _root_el.querySelector(`${_tag}.${_class}`);
  51. }
  52. return _el;
  53. }
  54.  
  55. const rootOptimizer = function (_root) {
  56. let userPageURLMatch = window.location.pathname.match(/\/user\/([^/]+)$/);
  57. let userPostsPageURLMatch = window.location.pathname.match(/\/user\/([^/]+)\/post/);
  58. let artistsPageURLMatch = window.location.pathname.match(/\/artists/);
  59. let postsPageURLMatch = window.location.pathname.match(/\/posts/);
  60.  
  61. //user page
  62. if (userPageURLMatch || userPostsPageURLMatch) {
  63. let pageUser = false;
  64. let userPageCSSPrefix = false;
  65. if (userPageURLMatch) {
  66. pageUser = userPageURLMatch[1];
  67. userPageCSSPrefix = 'user-header';
  68. } else if (userPostsPageURLMatch) {
  69. pageUser = userPostsPageURLMatch[1];
  70. userPageCSSPrefix = 'post';
  71. }
  72. console.debug(`User page detected for ${pageUser}`);
  73.  
  74. //adding styles
  75. GM_addStyle('span.user-header__dislike-icon::before { content: "\uD83D\uDC4E"; }');
  76.  
  77. let pageUserKey = key(pageUser);
  78.  
  79. let today = new Date().toISOString().split('T')[0]; // Get the current date in ISO format
  80.  
  81. //load or create the summary data for the user
  82. let summary = localStorage.getItem(pageUserKey);
  83. //If no summary data, we create it
  84. if (!summary) {
  85. summary = {
  86. user: pageUser,
  87. visits: 1,
  88. previousVisit: false,
  89. lastVisit: today,
  90. disliked: false
  91. };
  92. } else {
  93. summary = JSON.parse(summary);
  94. }
  95.  
  96. //Increment the number of visits if the page has been visited not today
  97. if (summary.lastVisit !== today) {
  98. summary.visits += 1;
  99. summary.previousVisit = summary.lastVisit;
  100. summary.lastVisit = today;
  101. if (!summary.user) {
  102. summary.user = pageUser;
  103. }
  104. }
  105.  
  106. //Store the summary
  107. console.debug(`User ${pageUser}:`, summary);
  108. localStorage.setItem(pageUserKey, JSON.stringify(summary));
  109.  
  110. //header optimization ====================================================
  111.  
  112. //summary text
  113. let summaryElement = document.createElement('span');
  114. if (summary.previousVisit) {
  115. summaryElement.textContent = `- Visited ${summary.visits} times, last visit on ${summary.previousVisit}`;
  116. } else {
  117. summaryElement.textContent = `- First time visit`;
  118. }
  119.  
  120. // dislike button
  121. let dislikeButton = document.createElement('button');
  122. dislikeButton.type = 'button';
  123. dislikeButton.className = `${userPageCSSPrefix}__favourite`;
  124.  
  125. let iconElement = document.createElement('span');
  126. iconElement.className = `${userPageCSSPrefix}__fav-icon`
  127. iconElement.textContent = summary.disliked ? '👍🏻' : '👎';
  128. dislikeButton.appendChild(iconElement);
  129.  
  130. let textElement = document.createElement('span');
  131. textElement.className = `${userPageCSSPrefix}__fav-text`;
  132. textElement.textContent = summary.disliked ? 'Un-dislike' : 'Dislike';
  133. dislikeButton.appendChild(textElement);
  134. dislikeButton.addEventListener('click', () => {
  135. summary.disliked = !summary.disliked;
  136. localStorage.setItem(pageUserKey, JSON.stringify(summary));
  137. iconElement.textContent = summary.disliked ? '👍🏻' : '👎';
  138. textElement.textContent = summary.disliked ? 'Un-dislike' : 'Dislike';
  139. });
  140.  
  141. let _summary_element, _parent_cssClass;
  142. if (userPageURLMatch) {
  143. _summary_element = document.createElement('div');
  144. _summary_element.appendChild(summaryElement);
  145. _parent_cssClass = `${userPageCSSPrefix}__info`
  146. } else {
  147. _summary_element = summaryElement;
  148. _parent_cssClass = `${userPageCSSPrefix}__published`
  149. }
  150.  
  151. //header
  152. let _pardiv = getEl(_root, 'div', _parent_cssClass);
  153. if (_pardiv) {
  154. _pardiv.appendChild(_summary_element);
  155. }
  156.  
  157. //actions
  158. let _actdiv = getEl(_root, 'div', `${userPageCSSPrefix}__actions`);
  159. if (_actdiv) {
  160. _actdiv.appendChild(dislikeButton);
  161. }
  162.  
  163. // footer optimization ======================================================================
  164.  
  165. // bottom header repeater
  166. let navMenu = getEl(_root, 'nav', 'post__nav-links');
  167. let commentSection = getEl(_root, 'footer', 'post__footer');
  168.  
  169. if (navMenu && commentSection) {
  170. commentSection.appendChild(navMenu.cloneNode(true));
  171. }
  172.  
  173. // user post page
  174. if (userPostsPageURLMatch
  175. && (_root.querySelectorAll || _root.tagName == 'H2')) {
  176. // var headers = _root.getElementsByTagName('h2');
  177. var headers = _root.tagName == 'H2' ? [_root] : _root.querySelectorAll('h2');
  178. for (var i = 0; i < headers.length; i++) {
  179. if (headers[i].innerHTML == 'Downloads') {
  180. let copyDownloadsButton = document.createElement('button');
  181. copyDownloadsButton.type = 'button';
  182. copyDownloadsButton.className = `${userPageCSSPrefix}__favourite`;
  183.  
  184. let textElement = document.createElement('span');
  185. textElement.className = `${userPageCSSPrefix}__fav-text`;
  186. textElement.textContent = 'Copy';
  187. copyDownloadsButton.appendChild(textElement);
  188. copyDownloadsButton.addEventListener('click', () => {
  189. var attachemnts = '';
  190. var c = 0;
  191. document.querySelectorAll('a.post__attachment-link').forEach(l => {
  192. attachemnts += l.href + '\n';
  193. c++;
  194. });
  195. GM_setClipboard(attachemnts);
  196. console.log('copied (', c, ')');
  197. });
  198.  
  199. headers[i].appendChild(copyDownloadsButton);
  200. }
  201. }
  202. }
  203.  
  204. } else if (/* artist pages */ artistsPageURLMatch) {
  205. console.debug('Artist list page detected');
  206.  
  207. let enrichUserCard = function (c) {
  208. let summary = extractSummary(c.href);
  209. if (summary) {
  210. c.className += ' co-parsed';
  211.  
  212. let serviceLabel = c.querySelector('span.user-card__service');
  213. let visitedLabel = serviceLabel.cloneNode(true);
  214. visitedLabel.textContent = `# ${summary.visits}`;
  215. visitedLabel.style.marginLeft = '4px';
  216. visitedLabel.style.backgroundColor = 'rgb(240, 140, 207)';
  217. serviceLabel.insertAdjacentElement('afterend', visitedLabel);
  218.  
  219. if (summary.previousVisit) {
  220. let daysAfterLabel = visitedLabel.cloneNode(true);
  221. let daysFromLastVisit = Math.floor((new Date() - new Date(summary.previousVisit)) / (1000 * 60 * 60 * 24));
  222. daysAfterLabel.textContent = `📅 ${daysFromLastVisit}`
  223. daysAfterLabel.title = `Last visit on ${summary.previousVisit}`;
  224. visitedLabel.insertAdjacentElement('afterend', daysAfterLabel);
  225. }
  226.  
  227. if (summary.disliked) {
  228. c.className += ' card--dislike';
  229. }
  230. } else {
  231. c.className += ' card--nevermet';
  232. }
  233. }
  234.  
  235. if (_root.tagName == 'A' && _root.classList.contains('user-card')) {
  236. enrichUserCard(_root);
  237. } else if (_root.querySelectorAll) {
  238. _root.querySelectorAll('a.user-card').forEach(card => {
  239. enrichUserCard(card);
  240. });
  241. }
  242. } else if (/*post list pages */ postsPageURLMatch) {
  243. console.debug('Post list page detected');
  244.  
  245. let enrichCard = function(card) {
  246. let summary = extractSummary(card.querySelector('a').href, true);
  247. console.debug(summary);
  248. if (summary) {
  249. card.className += ' co-parsed';
  250.  
  251. //if there is a summary
  252. if (summary.user) {
  253. console.debug('sum', summary);
  254. //replace attachment number with username
  255. card.querySelector('footer > div').textContent = `${summary.user} (${summary.visits})`;
  256.  
  257. if (summary.disliked) {
  258. card.className += ' card--dislike';
  259. }
  260. } else {
  261. card.querySelector('footer > div').textContent = summary;
  262. card.className += ' card--nevermet';
  263. }
  264. }
  265. }
  266.  
  267. if (_root.tagName == 'ARTICLE' && _root.classList.contains('post-card')) {
  268. enrichCard(_root);
  269. } else if (_root.querySelectorAll) {
  270. _root.querySelectorAll('article.post-card').forEach(card => {
  271. enrichCard(card);
  272. })
  273. }
  274.  
  275. }
  276.  
  277. }
  278.  
  279. // Create a MutationObserver to watch for changes in the DOM
  280. var observer = new MutationObserver(function (mutationsList, observer) {
  281. for (const mutation of mutationsList) {
  282.  
  283. if (mutation.type === 'childList'
  284. && mutation.target
  285. && mutation.addedNodes.length > 0) {
  286.  
  287. let _root = mutation.addedNodes[0];
  288. rootOptimizer(_root);
  289. }
  290. }
  291. });
  292.  
  293.  
  294. // Define the options for the observer
  295. const observerOptions = {
  296. childList: true,
  297. subtree: true
  298. };
  299.  
  300. // Start observing the document body for changes
  301. observer.observe(document.body, observerOptions);
  302.  
  303. })();