Panel Control XFilter 2.4.57 (c) tapeavion

Hide posts by keywords with the dashboard and hide posts from verified accounts

  1. // ==UserScript==
  2. // @name Panel Control XFilter 2.4.57 (c) tapeavion
  3. // @namespace http://tampermonkey.net/
  4. // @version 2.4.57
  5. // @description Hide posts by keywords with the dashboard and hide posts from verified accounts
  6. // @author gullampis810
  7. // @match https://x.com/*
  8. // @match https://x.com/i/grok*
  9. // @match https://blank.org/*
  10. // @grant GM_registerMenuCommand
  11. // @grant GM_unregisterMenuCommand
  12. // @grant GM_setValue
  13. // @grant GM_getValue
  14. // @license MIT
  15. // @icon https://www.pinclipart.com/picdir/big/450-4507608_twitter-circle-clipart.png
  16. // ==/UserScript==
  17.  
  18. (function () {
  19. "use strict";
  20.  
  21. // ===== Настройки и инициализация ===== //
  22. const STORAGE_KEY = "hiddenKeywords";
  23. const FAVORITE_USERS_KEY = "favoriteUsers";
  24. let hiddenKeywords = JSON.parse(localStorage.getItem(STORAGE_KEY)) || [];
  25. let favoriteUsers = JSON.parse(localStorage.getItem(FAVORITE_USERS_KEY)) || [];
  26. let unblockedKeywords = JSON.parse(localStorage.getItem("unblockedKeywords")) || [];
  27. let hideVerifiedAccounts = false;
  28. let hideNonVerifiedAccounts = JSON.parse(localStorage.getItem("hideNonVerifiedAccounts")) || false;
  29. let isShowingFavorites = false;
  30. const languageFilters = {
  31. english: /[a-zA-Z]/,
  32. russian: /[А-Яа-яЁё]/,
  33. japanese: /[\p{Script=Hiragana}\p{Script=Katakana}\p{Script=Han}]/u,
  34. ukrainian: /[А-Яа-яІіЄєЇїҐґ]/,
  35. belarusian: /[А-Яа-яЎўЁёІі]/,
  36. tatar: /[А-Яа-яӘәӨөҮүҖҗ]/,
  37. mongolian: /[\p{Script=Mongolian}]/u,
  38. chinese: /[\p{Script=Han}]/u,
  39. german: /[a-zA-ZßÄäÖöÜü]/,
  40. polish: /[a-zA-ZąćęłńóśźżĄĆĘŁŃÓŚŹŻ]/,
  41. french: /[a-zA-Zàâçéèêëîïôûùüÿ]/,
  42. swedish: /[a-zA-ZåäöÅÄÖ]/,
  43. estonian: /[a-zA-ZäõöüÄÕÖÜ]/,
  44. danish: /[a-zA-Z帿ŨÆ]/,
  45. turkish: /[a-zA-ZıİçÇğĞöÖşŞüÜ]/,
  46. portuguese: /[a-zA-Zàáâãçéêíóôõúü]/,
  47. };
  48. let activeLanguageFilters = {};
  49.  
  50. // Получение имени пользователя из профиля
  51. function getUsernameFromProfile() {
  52. const profileUrl = window.location.href;
  53. const match = profileUrl.match(/x\.com\/([^\?\/]+)/);
  54. if (match && match[1]) {
  55. return match[1].toLowerCase();
  56. }
  57. const usernameElement = document.querySelector('a[href*="/"][role="link"] div[dir="ltr"] span');
  58. return usernameElement ? usernameElement.textContent.replace('@', '').toLowerCase() : '';
  59. }
  60.  
  61. // ===== Сохранение в localStorage ===== //
  62. function saveKeywords() {
  63. localStorage.setItem(STORAGE_KEY, JSON.stringify(hiddenKeywords));
  64. }
  65.  
  66. function saveFavoriteUsers() {
  67. localStorage.setItem(FAVORITE_USERS_KEY, JSON.stringify(favoriteUsers));
  68. }
  69.  
  70. function saveUnblockedKeywords() {
  71. localStorage.setItem("unblockedKeywords", JSON.stringify(unblockedKeywords));
  72. }
  73.  
  74. // ===== Функция для обновления фильтров по языкам ===== //
  75. function updateLanguageFilter(language) {
  76. if (activeLanguageFilters[language]) {
  77. delete activeLanguageFilters[language];
  78. } else {
  79. activeLanguageFilters[language] = languageFilters[language];
  80. }
  81. hidePosts();
  82. }
  83.  
  84. // ===== Проверка языка текста ===== //
  85. function isTextInLanguage(text) {
  86. for (const [language, regex] of Object.entries(activeLanguageFilters)) {
  87. if (regex.test(text)) {
  88. return true;
  89. }
  90. }
  91. return false;
  92. }
  93.  
  94. // ===== Функция скрытия постов ===== //
  95. function hidePosts() {
  96. document.querySelectorAll("article").forEach((article) => {
  97. const textContent = article.innerText.toLowerCase();
  98. const isVerifiedAccount = article.querySelector('[data-testid="icon-verified"]');
  99. const usernameElement = article.querySelector('a[href*="/"]');
  100. const username = usernameElement ? usernameElement.getAttribute("href").slice(1).toLowerCase() : "";
  101.  
  102. const isFavoriteUser = favoriteUsers.includes(username);
  103.  
  104. if (isFavoriteUser) {
  105. article.style.display = "";
  106. return;
  107. }
  108.  
  109. const matchesKeyword = hiddenKeywords.some((keyword) => {
  110. try {
  111. return new RegExp(keyword, "i").test(textContent);
  112. } catch (e) {
  113. return textContent.includes(keyword.toLowerCase());
  114. }
  115. });
  116.  
  117. const shouldHideVerified = hideVerifiedAccounts && isVerifiedAccount;
  118. const shouldHideNonVerified = hideNonVerifiedAccounts && !isVerifiedAccount;
  119. const shouldHideByLanguage = isTextInLanguage(textContent);
  120. const shouldHideByKeyword = matchesKeyword;
  121.  
  122. if (shouldHideVerified || shouldHideNonVerified || shouldHideByLanguage || shouldHideByKeyword) {
  123. article.style.display = "none";
  124. } else {
  125. article.style.display = "";
  126. }
  127. });
  128. }
  129.  
  130. // ===== Debounce для оптимизации ===== //
  131. function debounce(func, wait) {
  132. let timeout;
  133. return function (...args) {
  134. clearTimeout(timeout);
  135. timeout = setTimeout(() => func.apply(this, args), wait);
  136. };
  137. }
  138.  
  139. const debouncedHidePosts = debounce(hidePosts, 200);
  140.  
  141. // ===== Создание панели управления ===== //
  142. const panel = document.createElement("div");
  143. panel.style.position = "fixed";
  144. panel.style.bottom = "62px";
  145. panel.style.right = "180px";
  146. panel.style.width = "335px";
  147. panel.style.height = "510px";
  148. panel.style.padding = "8px";
  149. panel.style.fontFamily = "Arial, sans-serif";
  150. panel.style.backgroundColor = "#34506c";
  151. panel.style.color = "#fff";
  152. panel.style.borderRadius = "8px";
  153. panel.style.boxShadow = "0 0 10px rgba(0,0,0,0.5)";
  154. panel.style.zIndex = "9999";
  155. panel.style.overflow = "auto";
  156. panel.style.transition = "height 0.3s ease";
  157.  
  158. panel.innerHTML = `
  159. <h3 style="margin: 0; font-size: 16px;">Hiding Control</h3>
  160. <h4 class="version-text">v2.4.57</h4>
  161. <div style="display: flex; align-items: center; gap: 5px; margin: 10px 0;">
  162. <input id="keywordInput" type="text" placeholder="Enter the word or @username" style="width: calc(100% - 95px); height: 40px; padding: 5px; border-radius: 5px; border: none; background: #15202b; color: #fff;">
  163. <button id="addKeyword" style="min-width: 79px; max-width: 80px; padding: 8px; background: #203142; color: white; border: none; border-radius: 5px; height: 40px;">Add it</button>
  164. </div>
  165. <div style="display: flex; flex-wrap: wrap; gap: 5px; position: relative;">
  166. <div style="display: flex; align-items: center; gap: 5px;">
  167. <div id="searchWrapper" style="position: relative;">
  168. <input id="searchInput" type="text" placeholder="Search keywords or users" style="width: 240px; height: 40px; padding: 5px; border-radius: 5px; border: none; background-color: #15202b; color: #fff;">
  169. <span id="clearSearch" style="display: none; position: absolute; right: 10px; top: 50%; transform: translateY(-50%); color: #fff; cursor: pointer;">✖</span>
  170. </div>
  171. <button id="openLanguagePopup" style="width: 80px; padding: 8px; background: #203142; color: white; border: none; border-radius: 5px; height: 40px; font-size: 13px; font-weight: bold;">Language Filtering</button>
  172. </div>
  173. <button id="exportKeywords" style="flex: 1; min-width: 60px; max-width: 70px; padding: 8px; background: #203142; color: white; border: none; border-radius: 5px; height: 40px;">Export</button>
  174. <button id="importKeywords" style="flex: 1; min-width: 60px; max-width: 70px; padding: 8px; background: #203142; color: white; border: none; border-radius: 5px; height: 40px;">Import</button>
  175. <button id="toggleBlockKeywords" style="flex: 1; min-width: 80px; max-width: 90px; padding: 8px; background: #203142; color: white; border: none; border-radius: 5px; height: 40px; font-size: 13px; font-weight: bold;">Unblock All</button>
  176. <button id="clearKeywords" style="flex: 1; min-width: 60px; max-width: 80px; padding: 8px; background: #203142; color: white; border: none; border-radius: 5px; height: 40px;">Clear all</button>
  177. <button id="toggleVerifiedPosts" style="width: 242px; padding: 8px; background: #203142; color: white; border: none; border-radius: 5px; height: 40px; font-size: 13px; font-weight: bold;">Hide verified accounts: Click to Enable</button>
  178. <button id="toggleNonVerifiedPosts" style="width: 242px; padding: 8px; background: #203142; color: white; border: none; border-radius: 5px; height: 40px; font-size: 13px; font-weight: bold;">Hide non-verified accounts: Turn ON</button>
  179. <button id="toggleFavoriteUsers" style="width: 80px; padding: 8px; background: #203142; color: white; border: none; border-radius: 5px; height: 40px; font-size: 13px; font-weight: bold;">Favorite Users</button>
  180. </div>
  181. <div id="listLabel" style="margin-top: 10px; font-size: 14px; color: #fff;">List of Keywords</div>
  182. <ul id="keywordList" style="list-style: position: relative; inside; padding: 0; margin-top: 5px; font-size: 14px; color: #fff; max-height: 135px; overflow-y: auto; border: 1px solid #ccc; border-radius: 5px; background-color: #15202b; box-shadow: 0 2px 5px rgba(0,0,0,0.3);"></ul>
  183. `;
  184.  
  185. document.body.appendChild(panel);
  186.  
  187. const searchInput = document.getElementById("searchInput");
  188. const clearSearch = document.getElementById("clearSearch");
  189.  
  190. searchInput.addEventListener("input", () => {
  191. clearSearch.style.display = searchInput.value.trim() ? "block" : "none";
  192. updateKeywordList();
  193. });
  194.  
  195. clearSearch.addEventListener("click", () => {
  196. searchInput.value = "";
  197. clearSearch.style.display = "none";
  198. updateKeywordList();
  199. });
  200.  
  201. const lengthFilterInput = document.createElement("input");
  202. lengthFilterInput.type = "number";
  203. lengthFilterInput.placeholder = "Min length";
  204. lengthFilterInput.style.width = "80px";
  205. lengthFilterInput.style.marginTop = "10px";
  206. panel.appendChild(lengthFilterInput);
  207.  
  208. lengthFilterInput.addEventListener("change", () => {
  209. debouncedHidePosts();
  210. });
  211.  
  212. // ===== Предотвращение перетаскивания для поля ввода ===== //
  213. const keywordInput = document.getElementById("keywordInput");
  214. keywordInput.addEventListener("mousedown", (event) => {
  215. event.stopPropagation();
  216. });
  217.  
  218. // ===== Перетаскивание панели ===== //
  219. let isDragging = false;
  220. let offsetX = 0;
  221. let offsetY = 0;
  222.  
  223. panel.addEventListener("mousedown", (event) => {
  224. isDragging = true;
  225. offsetX = event.clientX - panel.offsetLeft;
  226. offsetY = event.clientY - panel.offsetTop;
  227. panel.style.cursor = "grabbing";
  228. });
  229.  
  230. document.addEventListener("mousemove", (event) => {
  231. if (isDragging) {
  232. panel.style.left = event.clientX - offsetX + "px";
  233. panel.style.top = event.clientY - offsetY + "px";
  234. }
  235. });
  236.  
  237. document.addEventListener("mouseup", () => {
  238. isDragging = false;
  239. panel.style.cursor = "grab";
  240. });
  241.  
  242. // ===== Создание попапа для языков ===== //
  243. const languagePopup = document.createElement("div");
  244. languagePopup.style.display = "none";
  245. languagePopup.style.position = "fixed";
  246. languagePopup.style.top = "460px";
  247. languagePopup.style.right = "65px";
  248. languagePopup.style.transform = "translate(-52%, 7%)";
  249. languagePopup.style.backgroundColor = "#34506c";
  250. languagePopup.style.padding = "20px";
  251. languagePopup.style.borderRadius = "8px";
  252. languagePopup.style.zIndex = "10000";
  253. languagePopup.style.width = "8%";
  254. languagePopup.style.boxShadow = "0 0 10px rgba(0,0,0,0.5)";
  255. languagePopup.style.fontFamily = "Arial, sans-serif";
  256.  
  257. for (const language in languageFilters) {
  258. const checkbox = document.createElement("input");
  259. checkbox.type = "checkbox";
  260. checkbox.id = `lang-${language}`;
  261. checkbox.name = language;
  262.  
  263. const label = document.createElement("label");
  264. label.htmlFor = `lang-${language}`;
  265. label.textContent = language.charAt(0).toUpperCase() + language.slice(1);
  266.  
  267. const wrapper = document.createElement("div");
  268. wrapper.appendChild(checkbox);
  269. wrapper.appendChild(label);
  270. languagePopup.appendChild(wrapper);
  271. }
  272.  
  273. const closeButton = document.createElement("button");
  274. closeButton.textContent = "X";
  275. closeButton.style.position = "relative";
  276. closeButton.style.width = "40px";
  277. closeButton.style.height = "40px";
  278. closeButton.style.borderRadius = "50%";
  279. closeButton.style.backgroundColor = "#203142";
  280. closeButton.style.color = "#fff";
  281. closeButton.style.border = "none";
  282. closeButton.style.display = "flex";
  283. closeButton.style.alignItems = "center";
  284. closeButton.style.justifyContent = "center";
  285. closeButton.style.cursor = "pointer";
  286. closeButton.style.marginTop = "10px";
  287. closeButton.style.left = "82%";
  288. closeButton.style.top = "56px";
  289. closeButton.addEventListener("click", () => {
  290. languagePopup.style.display = "none";
  291. });
  292. languagePopup.appendChild(closeButton);
  293.  
  294. document.body.appendChild(languagePopup);
  295.  
  296. document.getElementById("openLanguagePopup").addEventListener("click", () => {
  297. languagePopup.style.display = "block";
  298. });
  299.  
  300. const warningText = document.createElement("div");
  301. warningText.textContent = "⚠️it may stops working";
  302. warningText.style.color = "#ffcc00";
  303. warningText.style.fontSize = "14px";
  304. warningText.style.marginBottom = "10px";
  305. warningText.style.textAlign = "end";
  306. warningText.style.right = "38px";
  307. warningText.style.position = "relative";
  308. warningText.style.top = "20px";
  309. languagePopup.appendChild(warningText);
  310.  
  311. for (const language in languageFilters) {
  312. document.getElementById(`lang-${language}`).addEventListener("change", () => {
  313. updateLanguageFilter(language);
  314. });
  315. }
  316.  
  317. // ===== Стили для подсветки и скроллбара ===== //
  318. const style = document.createElement("style");
  319. style.textContent = `
  320. button {
  321. transition: box-shadow 0.3s, transform 0.3s;
  322. }
  323. button:hover {
  324. box-shadow: 0 0 10px rgba(255, 255, 255, 0.7);
  325. }
  326. button:active {
  327. transform: scale(0.95);
  328. box-shadow: 0 0 5px rgba(255, 255, 255, 0.7);
  329. }
  330. #keywordList::-webkit-scrollbar {
  331. width: 25px;
  332. }
  333. #keywordList::-webkit-scrollbar-thumb {
  334. background-color: #C1A5EF;
  335. border-radius: 8px;
  336. border: 3px solid #4F3E6A;
  337. height: 80px;
  338. }
  339. #keywordList::-webkit-scrollbar-thumb:hover {
  340. background-color: #C6AEFF;
  341. }
  342. #keywordList::-webkit-scrollbar-thumb:active {
  343. background-color: #B097C9;
  344. }
  345. #keywordList::-webkit-scrollbar-track {
  346. background: #455565;
  347. border-radius: 0px 0px 8px 0px;
  348. }
  349. .version-text {
  350. left: 275px;
  351. position: relative;
  352. bottom: 18px;
  353. color: #15202b;
  354. margin: 0;
  355. font-size: 14px;
  356. width: 47px;
  357. }
  358. #keywordInput {
  359. cursor: #ffffff59;
  360. background: #15202b;
  361. }
  362. #favoriteUserButton:hover img {
  363. filter: brightness(1.2);
  364. }
  365. #favoriteUserButton:active img {
  366. transform: scale(0.9);
  367. }
  368. `;
  369. document.head.appendChild(style);
  370.  
  371. // ===== Кнопка переключения панели FilterX ===== //
  372. let isSwitchOn = localStorage.getItem("isSwitchOn") === "true";
  373. const toggleButton = document.createElement("div");
  374. toggleButton.style.display = "flex";
  375. toggleButton.style.alignItems = "center";
  376. toggleButton.style.gap = "10px";
  377. toggleButton.style.background = " #15202b";
  378. toggleButton.style.border = "4px solid #6c7e8e";
  379. toggleButton.style.borderRadius = "45px";
  380. toggleButton.style.padding = "8px 12px";
  381. toggleButton.style.marginTop = "10px";
  382. toggleButton.style.cursor = "pointer";
  383. toggleButton.style.width = "auto";
  384. toggleButton.className = "css-175oi2r r-16y2uox";
  385.  
  386. function isGrokPage() {
  387. return window.location.href.startsWith("https://x.com/i/grok");
  388. }
  389.  
  390. const toggleLabel = document.createElement("label");
  391. toggleLabel.style.display = "inline-block";
  392. toggleLabel.style.width = "50px";
  393. toggleLabel.style.height = "25px";
  394. toggleLabel.style.borderRadius = "25px";
  395. toggleLabel.style.backgroundColor = isSwitchOn ? " #425364" : " #0d1319";
  396. toggleLabel.style.position = "relative";
  397. toggleLabel.style.cursor = "pointer";
  398. toggleLabel.style.transition = "background-color 0.3s";
  399. toggleLabel.style.top = "0px";
  400. toggleLabel.style.left = "75px";
  401.  
  402. const toggleSwitch = document.createElement("div");
  403. toggleSwitch.style.position = "absolute";
  404. toggleSwitch.style.width = "21px";
  405. toggleSwitch.style.height = "21px";
  406. toggleSwitch.style.borderRadius = "50%";
  407. toggleSwitch.style.backgroundColor = " #6c7e8e";
  408. toggleSwitch.style.top = "2px";
  409. toggleSwitch.style.left = isSwitchOn ? "calc(100% - 23px)" : "2px";
  410. toggleSwitch.style.transition = "left 0.3s ease";
  411. toggleSwitch.style.boxShadow = "rgb(21, 32, 43) -1px 1px 4px 1px";
  412.  
  413. function toggleSwitchState(event) {
  414. event.stopPropagation();
  415. isSwitchOn = !isSwitchOn;
  416. localStorage.setItem("isSwitchOn", isSwitchOn.toString());
  417. toggleSwitch.style.left = isSwitchOn ? "calc(100% - 23px)" : "2px";
  418. toggleLabel.style.backgroundColor = isSwitchOn ? " #425364" : " #0d1319";
  419.  
  420. if (isSwitchOn) {
  421. panel.style.display = "block";
  422. setTimeout(() => {
  423. panel.style.height = "485px";
  424. }, 10);
  425. } else {
  426. panel.style.height = "0px";
  427. setTimeout(() => {
  428. panel.style.display = "none";
  429. }, 300);
  430. }
  431. isPanelVisible = isSwitchOn;
  432. localStorage.setItem("panelVisible", isPanelVisible.toString());
  433. }
  434.  
  435. const debouncedToggleSwitchState = debounce(toggleSwitchState, 300);
  436.  
  437. toggleButton.addEventListener("click", debouncedToggleSwitchState);
  438. toggleLabel.appendChild(toggleSwitch);
  439. toggleButton.appendChild(toggleLabel);
  440.  
  441. const toggleText = document.createElement("span");
  442. toggleText.textContent = "FilterX";
  443. toggleText.style.color = " #6c7e8e";
  444. toggleText.style.fontFamily = "Arial, sans-serif";
  445. toggleText.style.fontSize = "16px";
  446. toggleText.style.fontWeight = "bold";
  447. toggleText.style.position = "absolute";
  448. toggleText.style.top = "12px";
  449. toggleText.style.left = "25px";
  450. toggleButton.appendChild(toggleText);
  451.  
  452. function waitForPostButton(callback) {
  453. const interval = setInterval(() => {
  454. const postButton = document.querySelector("[data-testid='SideNav_NewTweet_Button']");
  455. const postButtonContainer = postButton?.parentElement;
  456. if (postButtonContainer) {
  457. clearInterval(interval);
  458. callback(postButtonContainer);
  459. } else {
  460. console.warn("Контейнер кнопки 'Опубликовать пост' не найден");
  461. }
  462. }, 500);
  463. }
  464.  
  465. waitForPostButton((postButtonContainer) => {
  466. toggleButton.style.display = isGrokPage() ? "none" : "flex";
  467. postButtonContainer.appendChild(toggleButton);
  468. });
  469.  
  470. // ===== Управление высотой панели ===== //
  471. let isPanelVisible = localStorage.getItem("panelVisible") === "true";
  472.  
  473. function togglePanel() {
  474. if (isPanelVisible) {
  475. panel.style.height = "0px";
  476. setTimeout(() => {
  477. panel.style.display = "none";
  478. }, 300);
  479. } else {
  480. panel.style.display = "block";
  481. setTimeout(() => {
  482. panel.style.height = "510px";
  483. }, 10);
  484. }
  485. isPanelVisible = !isPanelVisible;
  486. localStorage.setItem("panelVisible", isPanelVisible.toString());
  487. }
  488.  
  489. toggleButton.addEventListener("click", togglePanel);
  490.  
  491. if (isPanelVisible) {
  492. panel.style.height = "510px";
  493. panel.style.display = "block";
  494. } else {
  495. panel.style.height = "0px";
  496. panel.style.display = "none";
  497. }
  498.  
  499. // ===== Элементы управления ===== //
  500. const addKeywordBtn = document.getElementById("addKeyword");
  501. const clearKeywordsBtn = document.getElementById("clearKeywords");
  502. const exportKeywordsBtn = document.getElementById("exportKeywords");
  503. const importKeywordsBtn = document.getElementById("importKeywords");
  504. const toggleVerifiedBtn = document.getElementById("toggleVerifiedPosts");
  505. const toggleNonVerifiedBtn = document.getElementById("toggleNonVerifiedPosts");
  506. const toggleBlockBtn = document.getElementById("toggleBlockKeywords");
  507. const openLanguagePopupBtn = document.getElementById("openLanguagePopup");
  508. const toggleFavoriteUsersBtn = document.getElementById("toggleFavoriteUsers");
  509. const keywordList = document.getElementById("keywordList");
  510.  
  511. // ===== Обработчики событий ===== //
  512. addKeywordBtn.addEventListener("click", () => {
  513. const inputValue = keywordInput.value.trim();
  514. if (inputValue) {
  515. if (isShowingFavorites && inputValue.startsWith("@")) {
  516. const username = inputValue.slice(1).toLowerCase();
  517. if (!favoriteUsers.includes(username)) {
  518. favoriteUsers.push(username);
  519. saveFavoriteUsers();
  520. updateKeywordList();
  521. debouncedHidePosts();
  522. }
  523. } else if (!isShowingFavorites && !hiddenKeywords.includes(inputValue)) {
  524. hiddenKeywords.push(inputValue);
  525. saveKeywords();
  526. updateKeywordList();
  527. debouncedHidePosts();
  528. }
  529. keywordInput.value = "";
  530. }
  531. });
  532.  
  533. toggleNonVerifiedBtn.textContent = `Hide non-verified accounts: ${hideNonVerifiedAccounts ? "Turn OFF" : "Turn ON"}`;
  534. toggleNonVerifiedBtn.addEventListener("click", () => {
  535. hideNonVerifiedAccounts = !hideNonVerifiedAccounts;
  536. localStorage.setItem("hideNonVerifiedAccounts", JSON.stringify(hideNonVerifiedAccounts));
  537. toggleNonVerifiedBtn.textContent = `Hide non-verified accounts: ${hideNonVerifiedAccounts ? "Turn OFF" : "Turn ON"}`;
  538. hidePosts();
  539. });
  540.  
  541. clearKeywordsBtn.addEventListener("click", () => {
  542. if (confirm("Are you sure you want to clear the list?")) {
  543. if (isShowingFavorites) {
  544. favoriteUsers = [];
  545. saveFavoriteUsers();
  546. } else {
  547. hiddenKeywords = [];
  548. unblockedKeywords = [];
  549. saveKeywords();
  550. saveUnblockedKeywords();
  551. }
  552. updateKeywordList();
  553. hidePosts();
  554. }
  555. });
  556.  
  557. exportKeywordsBtn.addEventListener("click", () => {
  558. const data = isShowingFavorites ? favoriteUsers : hiddenKeywords;
  559. const dataStr = `data:text/json;charset=utf-8,${encodeURIComponent(JSON.stringify(data))}`;
  560. const downloadAnchor = document.createElement("a");
  561. downloadAnchor.setAttribute("href", dataStr);
  562. downloadAnchor.setAttribute("download", isShowingFavorites ? "favorite_users.json" : "hidden_keywords.json");
  563. document.body.appendChild(downloadAnchor);
  564. downloadAnchor.click();
  565. document.body.removeChild(downloadAnchor);
  566. });
  567.  
  568. importKeywordsBtn.addEventListener("click", () => {
  569. const input = document.createElement("input");
  570. input.type = "file";
  571. input.accept = "application/json";
  572. input.addEventListener("change", (event) => {
  573. const file = event.target.files[0];
  574. const reader = new FileReader();
  575. reader.onload = () => {
  576. try {
  577. const importedData = JSON.parse(reader.result);
  578. if (Array.isArray(importedData)) {
  579. if (isShowingFavorites) {
  580. favoriteUsers = [...new Set([...favoriteUsers, ...importedData.map(u => u.startsWith("@") ? u.slice(1).toLowerCase() : u.toLowerCase())])];
  581. saveFavoriteUsers();
  582. } else {
  583. hiddenKeywords = [...new Set([...hiddenKeywords, ...importedData])];
  584. saveKeywords();
  585. }
  586. updateKeywordList();
  587. hidePosts();
  588. } else {
  589. alert("Incorrect file format.");
  590. }
  591. } catch (e) {
  592. alert("Error reading the file.");
  593. }
  594. };
  595. reader.readAsText(file);
  596. });
  597. input.click();
  598. });
  599.  
  600. toggleBlockBtn.addEventListener("click", () => {
  601. if (!isShowingFavorites) {
  602. if (hiddenKeywords.length > 0) {
  603. unblockedKeywords = [...hiddenKeywords];
  604. hiddenKeywords = [];
  605. toggleBlockBtn.textContent = "Block All";
  606. } else {
  607. hiddenKeywords = [...unblockedKeywords];
  608. unblockedKeywords = [];
  609. toggleBlockBtn.textContent = "Unblock All";
  610. }
  611. saveKeywords();
  612. saveUnblockedKeywords();
  613. updateKeywordList();
  614. hidePosts();
  615. }
  616. });
  617.  
  618. toggleBlockBtn.textContent = hiddenKeywords.length > 0 ? "Unblock All" : "Block All";
  619.  
  620. toggleVerifiedBtn.addEventListener("click", () => {
  621. hideVerifiedAccounts = !hideVerifiedAccounts;
  622. toggleVerifiedBtn.textContent = `Hide verified accounts: ${hideVerifiedAccounts ? "Turn OFF" : "Turn ON"}`;
  623. hidePosts();
  624. });
  625.  
  626. toggleFavoriteUsersBtn.addEventListener("click", () => {
  627. isShowingFavorites = !isShowingFavorites;
  628. toggleFavoriteUsersBtn.textContent = isShowingFavorites ? "Keywords" : "Favorite Users";
  629. keywordInput.placeholder = isShowingFavorites ? "Enter @username" : "Enter the word";
  630. updateKeywordList();
  631. });
  632.  
  633. openLanguagePopupBtn.addEventListener("click", () => {
  634. const panelRect = panel.getBoundingClientRect();
  635. languagePopup.style.top = `${panelRect.top - 320}px`;
  636. languagePopup.style.left = `${panelRect.right - 10}px`;
  637. languagePopup.style.display = "block";
  638. });
  639.  
  640. // ===== Обновление списка ключевых слов или избранных пользователей ===== //
  641. function updateKeywordList() {
  642. const list = document.getElementById("keywordList");
  643. const label = document.getElementById("listLabel");
  644. const searchInput = document.getElementById("searchInput");
  645. const searchQuery = searchInput ? searchInput.value.trim().toLowerCase() : "";
  646. list.innerHTML = "";
  647.  
  648. if (isShowingFavorites) {
  649. label.textContent = "Favorite Users";
  650. const filteredUsers = favoriteUsers.filter(user => user.toLowerCase().includes(searchQuery));
  651. if (filteredUsers.length === 0) {
  652. list.textContent = searchQuery ? "No matches found" : "Нет";
  653. } else {
  654. filteredUsers.forEach((user, index) => {
  655. const listItem = document.createElement("li");
  656. listItem.textContent = `@${user}`;
  657. listItem.style.marginBottom = "5px";
  658.  
  659. const deleteButton = document.createElement("button");
  660. deleteButton.textContent = "❌";
  661. deleteButton.style.marginLeft = "10px";
  662. deleteButton.style.backgroundColor = "#f44336";
  663. deleteButton.style.color = "#fff";
  664. deleteButton.style.border = "none";
  665. deleteButton.style.borderRadius = "3px";
  666. deleteButton.style.cursor = "pointer";
  667. deleteButton.addEventListener("click", () => {
  668. favoriteUsers.splice(favoriteUsers.indexOf(user), 1);
  669. saveFavoriteUsers();
  670. updateKeywordList();
  671. hidePosts();
  672. });
  673.  
  674. listItem.appendChild(deleteButton);
  675. list.appendChild(listItem);
  676. });
  677. }
  678. } else {
  679. label.textContent = "List of Keywords";
  680. const filteredKeywords = hiddenKeywords.filter(keyword => keyword.toLowerCase().includes(searchQuery));
  681. if (filteredKeywords.length === 0) {
  682. list.textContent = searchQuery ? "No matches found" : "Нет";
  683. } else {
  684. filteredKeywords.forEach((keyword, index) => {
  685. const listItem = document.createElement("li");
  686. listItem.textContent = keyword;
  687. listItem.style.marginBottom = "5px";
  688.  
  689. const deleteButton = document.createElement("button");
  690. deleteButton.textContent = "❌";
  691. deleteButton.style.marginLeft = "10px";
  692. deleteButton.style.backgroundColor = "#f44336";
  693. deleteButton.style.color = "#fff";
  694. deleteButton.style.border = "none";
  695. deleteButton.style.borderRadius = "3px";
  696. deleteButton.style.cursor = "pointer";
  697. deleteButton.addEventListener("click", () => {
  698. hiddenKeywords.splice(hiddenKeywords.indexOf(keyword), 1);
  699. saveKeywords();
  700. updateKeywordList();
  701. hidePosts();
  702. });
  703.  
  704. listItem.appendChild(deleteButton);
  705. list.appendChild(listItem);
  706. });
  707. }
  708. }
  709. }
  710.  
  711. // ===== Создание кнопки избранного в профиле ===== //
  712. function addFavoriteButtonToProfile() {
  713. const buttonContainer = document.querySelector('.css-175oi2r.r-obd0qt.r-18u37iz.r-1w6e6rj.r-1h0z5md.r-dnmrzs');
  714. if (!buttonContainer || buttonContainer.querySelector('#favoriteUserButton')) {
  715. return;
  716. }
  717.  
  718. const username = getUsernameFromProfile();
  719. if (!username) {
  720. return;
  721. }
  722.  
  723. const isFavorite = favoriteUsers.includes(username);
  724.  
  725. const favoriteButton = document.createElement('button');
  726. favoriteButton.id = 'favoriteUserButton';
  727. favoriteButton.setAttribute('aria-label', isFavorite ? 'Удалить из избранного' : 'Добавить в избранное');
  728. favoriteButton.setAttribute('role', 'button');
  729. favoriteButton.type = 'button';
  730. favoriteButton.className = 'css-175oi2r r-sdzlij r-1phboty r-rs99b7 r-lrvibr r-6gpygo r-1wron08 r-2yi16 r-1qi8awa r-1loqt21 r-o7ynqc r-6416eg r-1ny4l3l css-175oi2r r-18u37iz r-1wtj0ep';
  731. favoriteButton.style.borderColor = 'rgb(83, 100, 113)';
  732. favoriteButton.style.backgroundColor = 'rgba(0, 0, 0, 0)';
  733.  
  734. const buttonContent = document.createElement('div');
  735. buttonContent.dir = 'ltr';
  736. buttonContent.className = 'css-146c3p1 r-bcqeeo r-qvutc0 r-1qd0xha r-q4m81j r-a023e6 r-rjixqe r-b88u0q r-1awozwy r-6koalj r-18u37iz r-16y2uox r-1777fci';
  737. buttonContent.style.color = 'rgb(239, 243, 244)';
  738.  
  739. const icon = document.createElement('img');
  740. // PNG из вашего SVG в base64
  741. icon.src = 'data:image/png;base64,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';
  742. icon.style.width = '25px';
  743. icon.style.height = '25px';
  744. icon.style.filter = isFavorite ? 'hue-rotate(300deg) saturate(2)' : 'none'; // для изменения цвета
  745. icon.setAttribute('aria-hidden', 'true');
  746. icon.className = 'r-4qtqp9 r-yyyyoo r-dnmrzs r-bnwqim r-lrvibr r-m6rgpd r-z80fyv r-19wmn03';
  747.  
  748. buttonContent.appendChild(icon);
  749. favoriteButton.appendChild(buttonContent);
  750.  
  751. favoriteButton.addEventListener('click', () => {
  752. if (isFavorite) {
  753. favoriteUsers = favoriteUsers.filter(user => user !== username);
  754. favoriteButton.setAttribute('aria-label', 'Добавить в избранное');
  755. icon.style.filter = 'none';
  756. } else if (!favoriteUsers.includes(username)) {
  757. favoriteUsers.push(username);
  758. favoriteButton.setAttribute('aria-label', 'Удалить из избранного');
  759. icon.style.filter = 'hue-rotate(300deg) saturate(2)';
  760. }
  761. saveFavoriteUsers();
  762. updateKeywordList();
  763. debouncedHidePosts();
  764. });
  765.  
  766. buttonContainer.insertBefore(favoriteButton, buttonContainer.firstChild);
  767. }
  768.  
  769. // ===== Автоматическое скрытие панели при открытии фото ===== //
  770. function isPhotoViewerOpen() {
  771. const currentUrl = window.location.href;
  772. const isPhotoOpen = /\/photo\/\d+$/.test(currentUrl);
  773. const photoModal = document.querySelector('div[aria-label="Image"]') || document.querySelector('div[data-testid="imageViewer"]');
  774. return isPhotoOpen || !!photoModal;
  775. }
  776.  
  777. function updateToggleButtonVisibility() {
  778. toggleButton.style.display = isGrokPage() ? "none" : "flex";
  779. }
  780.  
  781. function toggleElementsVisibility() {
  782. const isPhotoOpen = isPhotoViewerOpen();
  783. if (isPhotoOpen || isGrokPage()) {
  784. panel.style.display = "none";
  785. toggleButton.style.display = "none";
  786. } else {
  787. if (isPanelVisible) {
  788. panel.style.display = "block";
  789. panel.style.height = "510px";
  790. }
  791. toggleButton.style.display = "flex";
  792. }
  793. }
  794.  
  795. // ===== Наблюдение за изменениями DOM ===== //
  796. const observer = new MutationObserver(() => {
  797. debouncedHidePosts();
  798. });
  799. observer.observe(document.body, { childList: true, subtree: true });
  800.  
  801. const profileObserver = new MutationObserver(() => {
  802. if (window.location.href.includes('x.com/') && !window.location.href.includes('/status/')) {
  803. addFavoriteButtonToProfile();
  804. }
  805. });
  806. profileObserver.observe(document.body, { childList: true, subtree: true });
  807.  
  808. toggleElementsVisibility();
  809. window.addEventListener("popstate", () => {
  810. updateToggleButtonVisibility();
  811. toggleElementsVisibility();
  812. });
  813.  
  814. const urlObserver = new MutationObserver(() => {
  815. updateToggleButtonVisibility();
  816. toggleElementsVisibility();
  817. });
  818. urlObserver.observe(document.body, { childList: true, subtree: true });
  819.  
  820. document.addEventListener("click", (event) => {
  821. if (event.target.closest('div[aria-label="Close"]') || event.target.closest('div[data-testid="imageViewer-close"]')) {
  822. setTimeout(toggleElementsVisibility, 100);
  823. }
  824. });
  825.  
  826. // ===== Инициализация ===== //
  827. updateKeywordList();
  828. hidePosts();
  829. })();