Add additional discussion list on main page.

Добавляет дополнительный блок с темами из определенного раздела на главную страницу.

  1. // ==UserScript==
  2. // @name Add additional discussion list on main page.
  3. // @namespace http://tampermonkey.net/
  4. // @version 1.0
  5. // @description Добавляет дополнительный блок с темами из определенного раздела на главную страницу.
  6. // @author Yowori
  7. // @match https://lolz.live/*
  8. // @match https://zelenka.guru/*
  9. // @match https://lolz.guru/*
  10. // @icon https://i.imgur.com/xnJeB3f.png
  11. // @grant none
  12. // @run-at document-end
  13. // @license MIT
  14. // ==/UserScript==
  15.  
  16. (function() {
  17. 'use strict';
  18.  
  19. let savedNodeId = localStorage.getItem('customDiscussionNodeId');
  20. const defaultNodeId = savedNodeId ? parseInt(savedNodeId) : 8;
  21. let nodeId = isNaN(defaultNodeId) ? 8 : defaultNodeId;
  22.  
  23. const isHidden = localStorage.getItem('customDiscussionIsHidden') === 'true';
  24.  
  25. const hostname = window.location.hostname;
  26. let baseURL = '';
  27.  
  28. if (hostname === 'lolz.live') {
  29. baseURL = 'https://lolz.live';
  30. } else if (hostname === 'zelenka.guru') {
  31. baseURL = 'https://zelenka.guru';
  32. } else if (hostname === 'lolz.guru') {
  33. baseURL = 'https://lolz.guru';
  34. } else {
  35. console.error('Неизвестный домен:', hostname);
  36. return;
  37. }
  38.  
  39. function addGlobalStyle(css) {
  40. const head = document.getElementsByTagName('head')[0];
  41. if (!head) { return; }
  42. const style = document.createElement('style');
  43. style.type = 'text/css';
  44. style.innerHTML = css;
  45. head.appendChild(style);
  46. }
  47.  
  48. addGlobalStyle(`
  49. .customDiscussionContainer {
  50. display: flex;
  51. gap: 20px;
  52. box-sizing: border-box;
  53. max-width: 1200px;
  54. flex-wrap: nowrap;
  55. position: relative;
  56. }
  57.  
  58. body.index .discussionList {
  59. max-width: 520px;
  60. flex: 0 0 400px;
  61. transition: max-width 0.3s ease; /* Добавлено плавное изменение */
  62. }
  63.  
  64. .customDiscussionList {
  65. max-height: 1040px;
  66. max-width: 400px;
  67. flex: 0 0 400px;
  68. height: auto;
  69. overflow: hidden;
  70. position: relative;
  71. transition: max-width 0.3s ease, display 0.3s ease; /* Добавлено плавное изменение */
  72. }
  73.  
  74. .customDiscussionList .loading,
  75. .customDiscussionList .error {
  76. text-align: center;
  77. font-size: 16px;
  78. color: #555;
  79. }
  80.  
  81. .customDiscussionList .ForumViewMoreButton {
  82. display: none;
  83. }
  84.  
  85. .customDiscussionList .discussionListItems {
  86. display: flex;
  87. flex-direction: column;
  88. gap: 10px;
  89. }
  90.  
  91. .toggleButton {
  92. padding: 5px 10px;
  93. cursor: pointer;
  94. background-color: transparent;
  95. color: white;
  96. border: none;
  97. border-radius: 4px;
  98. font-size: 14px;
  99. margin-left: 10px;
  100. margin-top: 10px;
  101. }
  102.  
  103.  
  104. .showButton {
  105. display: none;
  106. cursor: pointer;
  107. background-color: transparent;
  108. color: white;
  109. border: none;
  110. border-radius: 4px;
  111. font-size: 14px;
  112. padding: 5px 10px;
  113. }
  114.  
  115.  
  116. @media (max-width: 900px) {
  117. .customDiscussionContainer {
  118. flex-direction: column;
  119. align-items: center;
  120. }
  121.  
  122. body.index .discussionList,
  123. .customDiscussionList {
  124. flex: 0 0 90%;
  125. max-width: 90%;
  126. }
  127. }
  128. `);
  129.  
  130. function createHideButtons(discussionList) {
  131. const hideButton = document.createElement('button');
  132. hideButton.textContent = 'Скрыть';
  133. hideButton.className = 'toggleButton hideButton';
  134. hideButton.style.float = 'right';
  135. hideButton.style.marginTop = '10px';
  136. hideButton.addEventListener('click', () => {
  137. hideDiscussionList();
  138. });
  139.  
  140. const nodeIdButton = document.createElement('button');
  141. nodeIdButton.textContent = 'Раздел';
  142. nodeIdButton.className = 'toggleButton nodeIdButton';
  143. nodeIdButton.style.float = 'right';
  144. nodeIdButton.style.marginTop = '10px';
  145. nodeIdButton.style.marginRight = '10px';
  146. nodeIdButton.addEventListener('click', () => {
  147. const newNodeId = prompt('Введите ID раздела:', nodeId);
  148. if (newNodeId !== null) {
  149. const parsedNodeId = parseInt(newNodeId);
  150. if (!isNaN(parsedNodeId)) {
  151. nodeId = parsedNodeId;
  152. localStorage.setItem('customDiscussionNodeId', nodeId);
  153. const container = discussionList.querySelector('.discussionListItems');
  154. loadDiscussionList(nodeId, container, true);
  155. } else {
  156. alert('Некорректный ID.');
  157. }
  158. }
  159. });
  160.  
  161. const aboveThreadList = discussionList.querySelector('.aboveThreadList');
  162. if (aboveThreadList) {
  163. aboveThreadList.style.position = 'relative';
  164. aboveThreadList.appendChild(nodeIdButton);
  165. aboveThreadList.appendChild(hideButton);
  166. }
  167. }
  168.  
  169. function createShowButton() {
  170. const showButton = document.createElement('button');
  171. showButton.textContent = 'Показать';
  172. showButton.className = 'showButton';
  173. showButton.style.position = 'absolute';
  174. showButton.style.top = '10px';
  175. showButton.style.right = '10px';
  176. showButton.addEventListener('click', () => {
  177. showDiscussionList();
  178. });
  179.  
  180. const mainDiscussionList = document.querySelector('body.index .discussionList');
  181. if (mainDiscussionList) {
  182. mainDiscussionList.style.position = 'relative';
  183. mainDiscussionList.appendChild(showButton);
  184. }
  185. }
  186.  
  187. function hideDiscussionList() {
  188. const customList = document.querySelector('.customDiscussionList');
  189. if (customList) {
  190. customList.style.display = 'none';
  191. localStorage.setItem('customDiscussionIsHidden', 'true');
  192. }
  193.  
  194. const mainDiscussionList = document.querySelector('body.index .discussionList');
  195. if (mainDiscussionList) {
  196. mainDiscussionList.style.maxWidth = 'none';
  197. }
  198.  
  199. const showButton = document.querySelector('.showButton');
  200. const hideButton = document.querySelector('.hideButton');
  201. if (showButton) {
  202. showButton.style.display = 'block';
  203. }
  204. if (hideButton) {
  205. hideButton.style.display = 'none';
  206. }
  207. }
  208.  
  209. function showDiscussionList() {
  210. const customList = document.querySelector('.customDiscussionList');
  211. if (customList) {
  212. customList.style.display = 'block';
  213. localStorage.setItem('customDiscussionIsHidden', 'false');
  214. }
  215.  
  216. const mainDiscussionList = document.querySelector('body.index .discussionList');
  217. if (mainDiscussionList) {
  218. mainDiscussionList.style.maxWidth = '520px';
  219. }
  220.  
  221. const showButton = document.querySelector('.showButton');
  222. const hideButton = document.querySelector('.hideButton');
  223. if (showButton) {
  224. showButton.style.display = 'none';
  225. }
  226. if (hideButton) {
  227. hideButton.style.display = 'block';
  228. }
  229. }
  230.  
  231. function createNewDiscussionList() {
  232. let container = document.querySelector('.customDiscussionContainer');
  233. if (!container) {
  234. container = document.createElement('div');
  235. container.className = 'customDiscussionContainer';
  236.  
  237. const existingDiscussionList = document.querySelector('body.index .discussionList');
  238. if (existingDiscussionList) {
  239. existingDiscussionList.parentNode.insertBefore(container, existingDiscussionList.nextSibling);
  240. container.appendChild(existingDiscussionList);
  241. } else {
  242. const bodyIndex = document.querySelector('body.index');
  243. if (bodyIndex) {
  244. bodyIndex.appendChild(container);
  245. }
  246. }
  247. }
  248.  
  249. const newDiscussionList = document.createElement('div');
  250. newDiscussionList.className = 'discussionList customDiscussionList';
  251. newDiscussionList.innerHTML = `
  252. <div class="aboveThreadList">
  253. <form action="${baseURL}/forums/${nodeId}/" method="post" class="DiscussionListOptions">
  254. <input type="hidden" name="node_id" value="${nodeId}">
  255.  
  256. <div class="_universalSearchForm universalSearchForm">
  257. <input name="title" value="" class="SearchInputQuery _universalSearchInput universalSearchInput textCtrl" placeholder="Поиск тем" autocomplete="off">
  258. <i class="inputRelativeIcon fas fa-times" style="display: none;"></i>
  259. </div>
  260.  
  261. <input type="hidden" name="_xfToken" value="2312422,1728548767,49aac0543425624fb3896cd9087e7579a503d4c1">
  262. </form>
  263. </div>
  264.  
  265. <div class="discussionListItems" id="discussionListItems_${nodeId}">
  266. <div class="loading">Загрузка...</div>
  267. </div>
  268. `;
  269.  
  270. container.appendChild(newDiscussionList);
  271. loadDiscussionList(nodeId, newDiscussionList.querySelector(`#discussionListItems_${nodeId}`), true);
  272. addFilterHandlers(newDiscussionList);
  273. createHideButtons(newDiscussionList);
  274.  
  275. createShowButton();
  276.  
  277. const updateButton = document.querySelector('.UpdateFeedButton');
  278. if (updateButton) {
  279. updateButton.addEventListener('click', () => {
  280. const mainDiscussionListItems = document.querySelector('body.index .discussionList .discussionListItems');
  281. loadDiscussionList(1, mainDiscussionListItems, false);
  282. loadDiscussionList(nodeId, newDiscussionList.querySelector(`#discussionListItems_${nodeId}`), true);
  283. });
  284. }
  285.  
  286. if (isHidden) {
  287. hideDiscussionList();
  288. } else {
  289. const mainDiscussionList = document.querySelector('body.index .discussionList');
  290. if (mainDiscussionList) {
  291. mainDiscussionList.style.maxWidth = '520px';
  292. }
  293. }
  294. }
  295.  
  296. function loadDiscussionList(nodeId, container, limit = false) {
  297. let method = 'GET';
  298. let url = `${baseURL}/forums/${nodeId}/`;
  299. let params = null;
  300.  
  301. if (nodeId === 835) {
  302. method = 'POST';
  303. url = `${baseURL}/forums/${nodeId}/`;
  304. params = new URLSearchParams();
  305. params.append('node_id', `${nodeId}`);
  306. params.append('title', '');
  307. params.append('_xfToken', '2312422,1728548767,49aac0543425624fb3896cd9087e7579a503d4c1');
  308. }
  309.  
  310. const xhr = new XMLHttpRequest();
  311. xhr.open(method, url, true);
  312. if (method === 'POST') {
  313. xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
  314. }
  315. xhr.onreadystatechange = function() {
  316. if (xhr.readyState === 4) {
  317. if (xhr.status === 200) {
  318. const parser = new DOMParser();
  319. const doc = parser.parseFromString(xhr.responseText, 'text/html');
  320. const discussionItems = doc.querySelector('.discussionListItems');
  321.  
  322. if (discussionItems) {
  323. let itemsHTML = discussionItems.innerHTML;
  324.  
  325. if (limit) {
  326. const tempDiv = document.createElement('div');
  327. tempDiv.innerHTML = itemsHTML;
  328.  
  329. const topics = tempDiv.querySelectorAll('.discussionListItem');
  330.  
  331. let limitedHTML = '';
  332. for (let i = 0; i < Math.min(10, topics.length); i++) {
  333. limitedHTML += topics[i].outerHTML;
  334. }
  335.  
  336. itemsHTML = limitedHTML;
  337. }
  338.  
  339. container.innerHTML = itemsHTML;
  340.  
  341. } else {
  342. return;
  343. }
  344. } else {
  345. return;
  346. }
  347. }
  348. };
  349. if (method === 'POST' && params) {
  350. xhr.send(params.toString());
  351. } else {
  352. xhr.send();
  353. }
  354. }
  355.  
  356.  
  357. function addFilterHandlers(discussionList) {
  358. const form = discussionList.querySelector('.DiscussionListOptions');
  359. if (!form) return;
  360.  
  361. form.addEventListener('submit', function(e) {
  362. e.preventDefault();
  363. const formData = new FormData(form);
  364. const params = new URLSearchParams();
  365.  
  366. for (const pair of formData.entries()) {
  367. params.append(pair[0], pair[1]);
  368. }
  369.  
  370. const nodeId = formData.get('node_id') || nodeId;
  371.  
  372. loadFilteredDiscussionList(nodeId, params, discussionList.querySelector('.discussionListItems'), true);
  373. });
  374. }
  375.  
  376. function loadFilteredDiscussionList(nodeId, params, container, limit = false) {
  377. const xhr = new XMLHttpRequest();
  378. xhr.open('POST', `${baseURL}/forums/${nodeId}/`, true);
  379. xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
  380. xhr.onreadystatechange = function() {
  381. if (xhr.readyState === 4) {
  382. if (xhr.status === 200) {
  383. const parser = new DOMParser();
  384. const doc = parser.parseFromString(xhr.responseText, 'text/html');
  385. const discussionItems = doc.querySelector('.discussionListItems');
  386.  
  387. if (discussionItems) {
  388. let itemsHTML = discussionItems.innerHTML;
  389.  
  390. if (limit) {
  391. const tempDiv = document.createElement('div');
  392. tempDiv.innerHTML = itemsHTML;
  393.  
  394. const topics = tempDiv.querySelectorAll('.discussionListItem');
  395.  
  396. let limitedHTML = '';
  397. for (let i = 0; i < Math.min(10, topics.length); i++) {
  398. limitedHTML += topics[i].outerHTML;
  399. }
  400.  
  401. itemsHTML = limitedHTML;
  402. }
  403.  
  404. container.innerHTML = itemsHTML;
  405. } else {
  406. container.innerHTML = '<div class="error">Не удалось загрузить темы.</div>';
  407. }
  408. } else {
  409. container.innerHTML = '<div class="error">Ошибка загрузки.</div>';
  410. }
  411. }
  412. };
  413. xhr.send(params.toString());
  414. }
  415.  
  416. function init() {
  417. createNewDiscussionList();
  418. }
  419.  
  420. window.addEventListener('load', function() {
  421. init();
  422. });
  423.  
  424. })();