Add "Offtopic" discussion list on main page.

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

  1. // ==UserScript==
  2. // @name Add "Offtopic" discussion list on main page.
  3. // @namespace http://tampermonkey.net/
  4. // @version 1.1
  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. const nodeId = 8; // Раздел для отображения
  20.  
  21. const hostname = window.location.hostname;
  22. let baseURL = '';
  23.  
  24. if (hostname === 'lolz.live') {
  25. baseURL = 'https://lolz.live';
  26. } else if (hostname === 'zelenka.guru') {
  27. baseURL = 'https://zelenka.guru';
  28. } else if (hostname === 'lolz.guru') {
  29. baseURL = 'https://lolz.guru';
  30. } else {
  31. console.error('Неизвестный домен:', hostname);
  32. return;
  33. }
  34.  
  35. function addGlobalStyle(css) {
  36. const head = document.getElementsByTagName('head')[0];
  37. if (!head) { return; }
  38. const style = document.createElement('style');
  39. style.type = 'text/css';
  40. style.innerHTML = css;
  41. head.appendChild(style);
  42. }
  43.  
  44. addGlobalStyle(`
  45. .customDiscussionContainer {
  46. display: flex;
  47. gap: 20px;
  48. box-sizing: border-box;
  49. max-width: 1200px;
  50. margin: 0 auto;
  51. flex-wrap: nowrap;
  52. }
  53.  
  54. body.index .discussionList {
  55. max-width: 520px;
  56. flex: 0 0 400px;
  57. }
  58.  
  59. .customDiscussionList {
  60. max-height: 1040px;
  61. max-width: 400px;
  62. flex: 0 0 400px;
  63. height: auto;
  64. overflow: hidden;
  65. }
  66.  
  67. .customDiscussionList .loading,
  68. .customDiscussionList .error {
  69. text-align: center;
  70. padding: 20px;
  71. font-size: 16px;
  72. color: #555;
  73. }
  74.  
  75. .customDiscussionList .ForumViewMoreButton {
  76. display: none;
  77. }
  78.  
  79. .customDiscussionList .discussionListItems {
  80. display: flex;
  81. flex-direction: column;
  82. gap: 10px;
  83. }
  84.  
  85. @media (max-width: 900px) {
  86. .customDiscussionContainer {
  87. flex-direction: column;
  88. align-items: center;
  89. }
  90.  
  91. body.index .discussionList,
  92. .customDiscussionList {
  93. flex: 0 0 90%;
  94. max-width: 90%;
  95. }
  96. }
  97. `);
  98.  
  99. function createNewDiscussionList() {
  100. let container = document.querySelector('.customDiscussionContainer');
  101. if (!container) {
  102. container = document.createElement('div');
  103. container.className = 'customDiscussionContainer';
  104.  
  105. const existingDiscussionList = document.querySelector('body.index .discussionList');
  106. if (existingDiscussionList) {
  107. existingDiscussionList.parentNode.insertBefore(container, existingDiscussionList);
  108. container.appendChild(existingDiscussionList);
  109. } else {
  110. const bodyIndex = document.querySelector('body.index');
  111. if (bodyIndex) {
  112. bodyIndex.appendChild(container);
  113. }
  114. }
  115. }
  116.  
  117.  
  118. const newDiscussionList = document.createElement('div');
  119. newDiscussionList.className = 'discussionList customDiscussionList';
  120. newDiscussionList.innerHTML = `
  121. <div class="aboveThreadList">
  122. <form action="${baseURL}/forums/${nodeId}/" method="post" class="DiscussionListOptions">
  123. <input type="hidden" name="node_id" value="${nodeId}">
  124.  
  125. <div class="_universalSearchForm universalSearchForm">
  126. <input name="title" value="" class="SearchInputQuery _universalSearchInput universalSearchInput textCtrl" placeholder="Поиск тем" autocomplete="off">
  127. <i class="inputRelativeIcon fas fa-times" style="display: none;"></i>
  128. </div>
  129.  
  130. <input type="hidden" name="_xfToken" value="2312422,1728548767,49aac0543425624fb3896cd9087e7579a503d4c1">
  131. </form>
  132. </div>
  133.  
  134. <div class="discussionListItems" id="discussionListItems_${nodeId}">
  135. <div class="loading">Загрузка...</div>
  136. </div>
  137. `;
  138.  
  139. container.appendChild(newDiscussionList);
  140. loadDiscussionList(nodeId, newDiscussionList.querySelector(`#discussionListItems_${nodeId}`), true);
  141. addFilterHandlers(newDiscussionList);
  142.  
  143. const updateButton = document.querySelector('.UpdateFeedButton');
  144. if (updateButton) {
  145. updateButton.addEventListener('click', () => {
  146. const mainDiscussionList = document.querySelector('body.index .discussionList .discussionListItems');
  147. loadDiscussionList(1, mainDiscussionList, false);
  148. loadDiscussionList(nodeId, newDiscussionList.querySelector(`#discussionListItems_${nodeId}`), true);
  149. });
  150. }
  151. }
  152.  
  153. // Функция для загрузки списка обсуждений
  154. function loadDiscussionList(nodeId, container, limit = false) {
  155. let method = 'GET';
  156. let url = `${baseURL}/forums/${nodeId}/`;
  157. let params = null;
  158.  
  159.  
  160. if (nodeId === 835) {
  161. method = 'POST';
  162. url = `${baseURL}/forums/${nodeId}/`;
  163. params = new URLSearchParams();
  164. params.append('node_id', `${nodeId}`);
  165. params.append('title', '');
  166. params.append('_xfToken', '2312422,1728548767,49aac0543425624fb3896cd9087e7579a503d4c1');
  167. }
  168.  
  169. const xhr = new XMLHttpRequest();
  170. xhr.open(method, url, true);
  171. if (method === 'POST') {
  172. xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
  173. }
  174. xhr.onreadystatechange = function() {
  175. if (xhr.readyState === 4) {
  176. if (xhr.status === 200) {
  177. const parser = new DOMParser();
  178. const doc = parser.parseFromString(xhr.responseText, 'text/html');
  179. const discussionItems = doc.querySelector('.discussionListItems');
  180.  
  181. if (discussionItems) {
  182. let itemsHTML = discussionItems.innerHTML;
  183.  
  184. if (limit) {
  185. const tempDiv = document.createElement('div');
  186. tempDiv.innerHTML = itemsHTML;
  187.  
  188. const topics = tempDiv.querySelectorAll('.discussionListItem');
  189.  
  190. let limitedHTML = '';
  191. for (let i = 0; i < Math.min(10, topics.length); i++) {
  192. limitedHTML += topics[i].outerHTML;
  193. }
  194.  
  195. itemsHTML = limitedHTML;
  196. }
  197.  
  198. container.innerHTML = itemsHTML;
  199.  
  200. } else {
  201. container.innerHTML = '<div class="error">Не удалось загрузить темы.</div>';
  202. }
  203. } else {
  204. container.innerHTML = '<div class="error">Ошибка загрузки.</div>';
  205. }
  206. }
  207. };
  208. if (method === 'POST' && params) {
  209. xhr.send(params.toString());
  210. } else {
  211. xhr.send();
  212. }
  213. }
  214.  
  215.  
  216. function addFilterHandlers(discussionList) {
  217. const form = discussionList.querySelector('.DiscussionListOptions');
  218. if (!form) return;
  219.  
  220. form.addEventListener('submit', function(e) {
  221. e.preventDefault();
  222. const formData = new FormData(form);
  223. const params = new URLSearchParams();
  224.  
  225. for (const pair of formData.entries()) {
  226. params.append(pair[0], pair[1]);
  227. }
  228.  
  229. const nodeId = formData.get('node_id') || 835;
  230.  
  231. loadFilteredDiscussionList(nodeId, params, discussionList.querySelector('.discussionListItems'), true);
  232. });
  233. }
  234.  
  235. function loadFilteredDiscussionList(nodeId, params, container, limit = false) {
  236. const xhr = new XMLHttpRequest();
  237. xhr.open('POST', `${baseURL}/forums/${nodeId}/`, true);
  238. xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
  239. xhr.onreadystatechange = function() {
  240. if (xhr.readyState === 4) {
  241. if (xhr.status === 200) {
  242. const parser = new DOMParser();
  243. const doc = parser.parseFromString(xhr.responseText, 'text/html');
  244. const discussionItems = doc.querySelector('.discussionListItems');
  245.  
  246. if (discussionItems) {
  247. let itemsHTML = discussionItems.innerHTML;
  248.  
  249. if (limit) {
  250. const tempDiv = document.createElement('div');
  251. tempDiv.innerHTML = itemsHTML;
  252.  
  253. const topics = tempDiv.querySelectorAll('.discussionListItem');
  254.  
  255. let limitedHTML = '';
  256. for (let i = 0; i < Math.min(10, topics.length); i++) {
  257. limitedHTML += topics[i].outerHTML;
  258. }
  259.  
  260. itemsHTML = limitedHTML;
  261. }
  262.  
  263. container.innerHTML = itemsHTML;
  264. } else {
  265. container.innerHTML = '<div class="error">Не удалось загрузить темы.</div>';
  266. }
  267. } else {
  268. container.innerHTML = '<div class="error">Ошибка загрузки.</div>';
  269. }
  270. }
  271. };
  272. xhr.send(params.toString());
  273. }
  274.  
  275. function init() {
  276. createNewDiscussionList();
  277. }
  278.  
  279. window.addEventListener('load', function() {
  280. init();
  281. });
  282.  
  283. })();