Unlimited MAL Ignore list

Ignore an unlimited amount of users. Comes with custom settings: delete the entire post, replace the content of the message with a custom message, replace or delete the avatar, and keep or delete the signature.

  1. // ==UserScript==
  2. // @name Unlimited MAL Ignore list
  3. // @namespace http://tampermonkey.net/
  4. // @version 0.89
  5. // @description Ignore an unlimited amount of users. Comes with custom settings: delete the entire post, replace the content of the message with a custom message, replace or delete the avatar, and keep or delete the signature.
  6. // @author Only_Brad
  7. // @author ShaggyZE
  8. // @match https://myanimelist.net/*
  9. // @run-at document-end
  10. // @grant GM_setValue
  11. // @grant GM_getValue
  12. // @grant GM_registerMenuCommand
  13. // ==/UserScript==
  14.  
  15. (function() {
  16. const FORUM_OPTIONS = "editprofile.php?go=forumoptions";
  17. const POSTS_URL = "forum/?topicid";
  18. const LAST_POST_URL = "forum";
  19. const TOPICS_URL = "forum/?board";
  20. const TOPICS_SEARCH_URL = "forum/search?";
  21. const CLUB_TOPICS_URL_1 = "clubs.php";
  22. const CLUB_TOPICS_URL_2 = "forum/?clubid";
  23. const PROFILE_URL = "profile";
  24. const COMTOCOM_URL = "comtocom.php";
  25. const COMMENTS_URL = "comments.php";
  26. const BLACKLIST_URL = "https://myanimelist.net/blacklist";
  27.  
  28. const BLACKLIST_KEY = "ignore-list";
  29. const SETTINGS_KEY = "ignore-list-settings";
  30.  
  31. const YOU_SELECTOR = ".header-profile-link";
  32. const POST_USERS_SELECTOR = ".profile";
  33. const TOPIC_USERS_SELECTOR = ".forum_postusername a";
  34. const MESSAGE_SELECTOR = ".content [id^=message]";
  35. const QUOTE_SELECTOR = ".quotetext";
  36. const AVATAR_SELECTOR = ".forum-icon";
  37. const USER_PROFILE_SELECTOR = "[href^='/profile']";
  38. const USER_INFO_SELECTOR = "[id^=messageuser]";
  39. const USERINFO_SELECTOR = ".username";
  40. const USERINFO_SELECTOR1 = ".userstatus";
  41. const USERINFO_SELECTOR2 = ".userinfo.joined";
  42. const USERINFO_SELECTOR3 = ".userinfo.posts";
  43. const USERINFO_SELECTOR4 = ".custom-forum-title";
  44. const USERINFO_SELECTOR5 = ".modified";
  45. const USERINFO_SELECTOR6 = ".icon-team-title";
  46. const SIGNATURE_SELECTOR = ".sig";
  47. const FORUM_MESSAGE_SELECTOR = "[id^=msg]";
  48. const FORUM_MSG_NAME_SELECTOR = ".username a";
  49. const LAST_POST_LIST_CONTAINER_SELECTOR = "li.clearfix";
  50. const LAST_POST_LIST_USER_SELECTOR = "span.date a[href*='/profile/']";
  51. const LAST_POST_LIST_AVATAR_SELECTOR = LAST_POST_LIST_CONTAINER_SELECTOR + " > a[href*='/profile/']";
  52. const LAST_POST_TABLE_CONTAINER_SELECTOR = "td.forum_boardrow1[align='right'][width='130']";
  53. const LAST_POST_TABLE_USER_SELECTOR = "a[href*='/profile/']";
  54. const FORUM_QUOTE_NAME_SELECTOR = ".quotetext > strong > a";
  55. const FORUM_REPLY_NAME_SELECTOR = ".js-replyto-target";
  56. const REPLIED_CONTAINER_SELECTOR = ".replied.show";
  57. const FORUM_ACTION_BAR_SELECTOR = "[id^=postEditButtons]";
  58. const PROFILE_MSG_SELECTOR = "[id^=comBox]";
  59. const PROFILE_MSG_NAME_SELECTOR = ".text a.fw-b";
  60. const PROFILE_MSG_AVATAR_SELECTOR = ".image";
  61. const PROFILE_MSG_TEXT_SELECTOR = ".text .comment-text";
  62. const PROFILE_MSG_ACTION_BAR_SELECTOR = ".text > div.pb8 > a";
  63. const COMTOCOM_SELECTOR = "[id^=comBox]";
  64. const COMTOCOM_NAME_SELECTOR = ".dark_text a";
  65. const COMTOCOM_AVATAR_SELECTOR = ".picSurround a";
  66. const COMTOCOM_TEXT_SELECTOR = "[id^=comtext]";
  67. const COMTOCOM_ACTION_BAR_SELECTOR = ".dark_text a";
  68.  
  69. const IGNORE = 0;
  70. const REPLACE = 1;
  71. const DO_NOTHING = 2;
  72.  
  73. let blacklist;
  74. let settings;
  75.  
  76. const MIGRATION_COMPLETE_KEY = 'ignore-list-migration-complete';
  77.  
  78. const defaultSettings = {
  79. replaceUsername: false,
  80. replaceAvatar: true,
  81. replaceProfileAvatar: false,
  82. removeSignatures: true,
  83. removeUserinfo: true,
  84. lastpostMode: REPLACE,
  85. quoteMode: IGNORE,
  86. replyMode: IGNORE,
  87. postMode: REPLACE,
  88. profileMsgMode: IGNORE,
  89. removeTopics: true,
  90. UnBlacklistUsername: false,
  91. removeUnBlacklist: false,
  92. customLastPost: "removed-user",
  93. customQuote: "",
  94. customReply: "",
  95. customPost: "",
  96. customAvatar: "",
  97. customProfileMsg: "",
  98. customProfileAvatar: "",
  99. specificCustomLastPost: {},
  100. specificCustomQuote: {},
  101. specificCustomReply: {},
  102. specificCustomPost: {},
  103. specificCustomProfileMsg: {}
  104. };
  105.  
  106. GM_registerMenuCommand("Backup Settings to localStorage", backupToLocalStorage);
  107.  
  108. //routing
  109. if (window.location.href.includes(FORUM_OPTIONS)) {
  110. AddBlacklistLink();
  111. } else if (window.location.href.includes(POSTS_URL)) {
  112. handlePosts();
  113. handleQuotes();
  114. handleReplies();
  115. } else if (
  116. window.location.href.includes(TOPICS_URL) ||
  117. window.location.href.includes(TOPICS_SEARCH_URL) ||
  118. window.location.href.includes(CLUB_TOPICS_URL_1) ||
  119. window.location.href.includes(CLUB_TOPICS_URL_2)
  120. ) {
  121. handleTopics();
  122. handleLastPost();
  123. } else if (window.location.href.includes(PROFILE_URL)) {
  124. handleProfileMsgs();
  125. } else if (window.location.href.includes(COMTOCOM_URL)) {
  126. handleComToCom();
  127. } else if (window.location.href.includes(COMMENTS_URL)) {
  128. handleComToCom();
  129. } else if (window.location.href === BLACKLIST_URL) {
  130. handleBlacklist();
  131. } else if (window.location.href.includes(LAST_POST_URL)) {
  132. handleLastPost();
  133. if (settings.replaceAvatar) replaceAvatar();
  134. }
  135.  
  136. //GM_addStyle equivalent that works on firefox
  137. function addStyle(css) {
  138. const style = document.getElementById("addStyleBy8626") || (function() {
  139. const style = document.createElement('style');
  140. style.type = 'text/css';
  141. style.id = "addStyleBy8626";
  142. document.head.appendChild(style);
  143. return style;
  144. })();
  145. style.innerHTML += css;
  146. }
  147.  
  148. // --- Function to create a temporary message box ---
  149. function showTemporaryMessage(message, duration = 3000) {
  150. // Remove any existing message boxes first
  151. const existingMessageBox = document.getElementById('gm_backup_message');
  152. if (existingMessageBox) {
  153. existingMessageBox.remove();
  154. }
  155.  
  156. // Create the message box element
  157. const messageBox = document.createElement('div');
  158. messageBox.id = 'gm_backup_message';
  159. messageBox.textContent = message;
  160. messageBox.style.cssText = `
  161. position: fixed;
  162. top: 20px;
  163. left: 50%;
  164. transform: translateX(-50%);
  165. padding: 10px 20px;
  166. background-color: #4CAF50; /* Green background */
  167. color: white;
  168. border-radius: 5px;
  169. z-index: 10000; /* High z-index to be on top */
  170. opacity: 0.9;
  171. font-family: sans-serif;
  172. font-size: 14px;
  173. box-shadow: 0 2px 5px rgba(0,0,0,0.2);
  174. `;
  175.  
  176. // Append to the body
  177. document.body.appendChild(messageBox);
  178.  
  179. // Remove the message box after the specified duration
  180. setTimeout(() => {
  181. messageBox.remove();
  182. }, duration);
  183. }
  184.  
  185.  
  186. // --- Function to perform the backup to localStorage ---
  187. function backupToLocalStorage() {
  188. try {
  189. // Retrieve the current data from GM_setValue
  190. // Use the default values in case GM_getValue returns null, although
  191. // if your script is running, settings/blacklist should be populated.
  192. // Retrieving directly from GM_getValue ensures we get the saved state,
  193. // not just the current in-memory state if it hasn't been saved yet.
  194. const currentSettingsJson = GM_getValue(SETTINGS_KEY, JSON.stringify(defaultSettings));
  195. const currentBlacklistJson = GM_getValue(BLACKLIST_KEY, '[]');
  196.  
  197. // Save the data to localStorage
  198. localStorage.setItem(SETTINGS_KEY, currentSettingsJson);
  199. localStorage.setItem(BLACKLIST_KEY, currentBlacklistJson);
  200.  
  201. console.log('Settings and Blacklist backed up to localStorage.');
  202. showTemporaryMessage('Backup to localStorage complete!');
  203.  
  204. } catch (e) {
  205. console.error('Error during backup to localStorage:', e);
  206. showTemporaryMessage('Backup failed!', 5000); // Show error message longer
  207. }
  208. }
  209.  
  210. // loadBlackList function now handles migration for BOTH blacklist and settings
  211. function loadBlackList() {
  212. const isMigrationComplete = GM_getValue(MIGRATION_COMPLETE_KEY, false);
  213. let loadedBlacklist; // Temporary variable for blacklist data
  214. let loadedSettings; // Temporary variable for settings data
  215.  
  216. if (!isMigrationComplete) {
  217. // Migration not complete, check localStorage for BOTH keys
  218. const localStorageBlacklist = localStorage.getItem(BLACKLIST_KEY);
  219. const localStorageSettings = localStorage.getItem(SETTINGS_KEY);
  220.  
  221. if (localStorageBlacklist !== null || localStorageSettings !== null) {
  222. // Data found in localStorage for at least one item, attempt migration
  223. console.log('localStorage data found, attempting migration for both blacklist and settings to GM_setValue...');
  224.  
  225. // --- Handle Blacklist Migration ---
  226. if (localStorageBlacklist !== null) {
  227. try {
  228. loadedBlacklist = JSON.parse(localStorageBlacklist);
  229. // Ensure it's an array before saving
  230. if (!Array.isArray(loadedBlacklist)) {
  231. console.warn('localStorage blacklist is not an array, resetting to empty array.');
  232. loadedBlacklist = [];
  233. }
  234. GM_setValue(BLACKLIST_KEY, JSON.stringify(loadedBlacklist));
  235. console.log('Blacklist migrated from localStorage.');
  236. } catch (e) {
  237. console.error('Error parsing localStorage blacklist during migration.', e);
  238. // On error, load from GM_setValue instead for this run
  239. const gmBlacklist = GM_getValue(BLACKLIST_KEY, '[]');
  240. loadedBlacklist = JSON.parse(gmBlacklist);
  241. console.log('Blacklist loaded from GM_setValue due to localStorage parse error.');
  242. }
  243. } else {
  244. // No blacklist in localStorage, load from GM_setValue
  245. const gmBlacklist = GM_getValue(BLACKLIST_KEY, '[]');
  246. loadedBlacklist = JSON.parse(gmBlacklist);
  247. // Ensure it's an array even if loading from GM failed initially
  248. if (!Array.isArray(loadedBlacklist)) {
  249. console.warn('Blacklist loaded from GM_setValue is not an array, resetting to empty array.');
  250. loadedBlacklist = [];
  251. }
  252. console.log('Blacklist not found in localStorage, loaded from GM_setValue.');
  253. }
  254.  
  255. // --- Handle Settings Migration ---
  256. if (localStorageSettings !== null) {
  257. try {
  258. loadedSettings = JSON.parse(localStorageSettings);
  259. GM_setValue(SETTINGS_KEY, JSON.stringify(loadedSettings));
  260. console.log('Settings migrated from localStorage.');
  261. } catch (e) {
  262. console.error('Error parsing localStorage settings during migration.', e);
  263. // On error, load from GM_setValue instead for this run
  264. const gmSettings = GM_getValue(SETTINGS_KEY, JSON.stringify(defaultSettings));
  265. loadedSettings = JSON.parse(gmSettings);
  266. console.log('Settings loaded from GM_setValue due to localStorage parse error.');
  267. }
  268. } else {
  269. // No settings in localStorage, load from GM_setValue
  270. const gmSettings = GM_getValue(SETTINGS_KEY, JSON.stringify(defaultSettings));
  271. loadedSettings = JSON.parse(gmSettings);
  272. console.log('Settings not found in localStorage, loaded from GM_setValue.');
  273. }
  274.  
  275. // *** Set the migration complete flag in GM_setValue AFTER attempting both migrations ***
  276. GM_setValue(MIGRATION_COMPLETE_KEY, true);
  277. console.log('Migration to GM_setValue marked as complete.');
  278.  
  279. } else {
  280. // No data found in localStorage for either key, perform standard load from GM_setValue
  281. console.log('No localStorage data found for migration. Loading both from GM_setValue...');
  282. const gmBlacklist = GM_getValue(BLACKLIST_KEY, '[]');
  283. loadedBlacklist = JSON.parse(gmBlacklist);
  284. // Ensure blacklist is an array
  285. if (!Array.isArray(loadedBlacklist)) {
  286. console.warn('Blacklist loaded from GM_setValue is not an array, resetting to empty array.');
  287. loadedBlacklist = [];
  288. GM_setValue(BLACKLIST_KEY, '[]'); // Save the corrected default
  289. }
  290.  
  291. const gmSettings = GM_getValue(SETTINGS_KEY, JSON.stringify(defaultSettings));
  292. loadedSettings = JSON.parse(gmSettings);
  293. }
  294.  
  295. } else {
  296. // Migration is complete, load BOTH directly from GM_setValue
  297. console.log('Migration complete. Loading blacklist and settings directly from GM_setValue...');
  298. const gmBlacklist = GM_getValue(BLACKLIST_KEY, '[]');
  299. loadedBlacklist = JSON.parse(gmBlacklist);
  300. // Ensure blacklist is an array
  301. if (!Array.isArray(loadedBlacklist)) {
  302. console.warn('Blacklist loaded from GM_setValue is not an array, resetting to empty array.');
  303. loadedBlacklist = [];
  304. GM_setValue(BLACKLIST_KEY, '[]'); // Save the corrected default
  305. }
  306.  
  307. const gmSettings = GM_getValue(SETTINGS_KEY, JSON.stringify(defaultSettings));
  308. loadedSettings = JSON.parse(gmSettings);
  309. }
  310.  
  311. // Assign the loaded data to the script's variables after the entire loading process
  312. blacklist = loadedBlacklist;
  313. settings = loadedSettings;
  314.  
  315.  
  316. // Ensure nested objects and missing properties exist for settings
  317. // This handles cases where loaded data might be from an older version or parsed poorly
  318. let settingsChangedAfterLoad = false;
  319. if (!settings.specificCustomLastPost) { settings.specificCustomLastPost = {}; settingsChangedAfterLoad = true; }
  320. if (!settings.specificCustomQuote) { settings.specificCustomQuote = {}; settingsChangedAfterLoad = true; }
  321. if (!settings.specificCustomReply) { settings.specificCustomReply = {}; settingsChangedAfterLoad = true; }
  322. if (!settings.specificCustomPost) { settings.specificCustomPost = {}; settingsChangedAfterLoad = true; }
  323. if (!settings.specificCustomProfileMsg) { settings.specificCustomProfileMsg = {}; settingsChangedAfterLoad = true; }
  324.  
  325. for (const key in defaultSettings) {
  326. if (Object.prototype.hasOwnProperty.call(defaultSettings, key)) {
  327. if (typeof defaultSettings[key] === 'object' && defaultSettings[key] !== null && !Array.isArray(defaultSettings[key])) {
  328. continue; // Skip specificCustom objects
  329. }
  330. if (settings[key] === undefined) {
  331. settings[key] = defaultSettings[key];
  332. settingsChangedAfterLoad = true;
  333. console.log(`Settings: Added missing key "${key}" with default value after load.`);
  334. }
  335. }
  336. }
  337.  
  338. // Save the potentially updated settings back to GM_setValue if defaults were added
  339. if (settingsChangedAfterLoad) {
  340. GM_setValue(SETTINGS_KEY, JSON.stringify(settings));
  341. console.log('Settings updated with missing defaults and saved to GM_setValue.');
  342. }
  343.  
  344. // Note: The migration complete flag is handled earlier in this function.
  345. // Blacklist is assigned directly above and doesn't need a separate save for defaults
  346. // unless it failed parsing and was reset to []. That save happens inside the checks.
  347. }
  348.  
  349. // loadSettings function now only loads from GM_setValue
  350. function loadSettings() {
  351. // In this revised approach, loadBlackList handles the initial loading
  352. // and migration for both settings and blacklist on the first run.
  353. // loadSettings should now assume that initial loading has occurred (or will
  354. // occur via loadBlackList if loadSettings is somehow called first on a fresh run).
  355. // It simply loads the current state of settings from GM_setValue.
  356.  
  357. console.log('Loading settings from GM_setValue...');
  358. const gmSettings = GM_getValue(SETTINGS_KEY, JSON.stringify(defaultSettings));
  359. settings = JSON.parse(gmSettings);
  360.  
  361. // Ensure nested objects and missing properties exist for settings
  362. let settingsChanged = false;
  363. if (!settings.specificCustomLastPost) { settings.specificCustomLastPost = {}; settingsChanged = true; }
  364. if (!settings.specificCustomQuote) { settings.specificCustomQuote = {}; settingsChanged = true; }
  365. if (!settings.specificCustomReply) { settings.specificCustomReply = {}; settingsChanged = true; }
  366. if (!settings.specificCustomPost) { settings.specificCustomPost = {}; settingsChanged = true; }
  367. if (!settings.specificCustomProfileMsg) { settings.specificCustomProfileMsg = {}; settingsChanged = true; }
  368.  
  369. for (const key in defaultSettings) {
  370. if (Object.prototype.hasOwnProperty.call(defaultSettings, key)) {
  371. if (typeof defaultSettings[key] === 'object' && defaultSettings[key] !== null && !Array.isArray(defaultSettings[key])) {
  372. continue;
  373. }
  374. if (settings[key] === undefined) {
  375. settings[key] = defaultSettings[key];
  376. settingsChanged = true;
  377. console.log(`Settings: Added missing key "${key}" with default value.`);
  378. }
  379. }
  380. }
  381.  
  382. // Save the potentially updated settings back to GM_setValue if defaults were added
  383. if (settingsChanged) {
  384. GM_setValue(SETTINGS_KEY, JSON.stringify(settings));
  385. console.log('Settings updated with missing defaults and saved to GM_setValue.');
  386. }
  387. }
  388.  
  389.  
  390. // saveBlackList remains the same, always saving to GM_setValue
  391. function saveBlackList() {
  392. GM_setValue(BLACKLIST_KEY, JSON.stringify(blacklist));
  393. }
  394.  
  395. // saveSetting remains the same, always saving to GM_setValue
  396. function saveSetting(key, value) {
  397. // Update the in-memory settings object first
  398. if (typeof key === "object") {
  399. if (settings[key.key] && typeof settings[key.key] === 'object') {
  400. settings[key.key][key.subkey] = value;
  401. } else {
  402. console.error(`Attempted to set subkey on non-object settings property: ${key.key}`);
  403. }
  404. } else {
  405. settings[key] = value;
  406. }
  407. // Save the entire updated settings object to GM_setValue
  408. GM_setValue(SETTINGS_KEY, JSON.stringify(settings));
  409. }
  410. function AddBlacklistLink() {
  411. //wip
  412. }
  413.  
  414. //functions called by the routers
  415. function handlePosts() {
  416. loadBlackList();
  417. loadSettings();
  418. addPostsBlackListButtons();
  419.  
  420. switch (settings.postMode) {
  421. case IGNORE:
  422. ignorePosts();
  423. return;
  424. case REPLACE:
  425. replacePosts();
  426. break;
  427. case DO_NOTHING:
  428. break;
  429. default:
  430. saveSetting("postMode", REPLACE);
  431. replacePosts();
  432. break;
  433. }
  434.  
  435.  
  436. if (settings.replaceAvatar) replaceAvatar();
  437. if (settings.removeSignatures) removeSignatures();
  438. if (settings.removeUserinfo) removeUserinfo();
  439. if (settings.replaceUsername) replaceUsername();
  440. }
  441.  
  442. function handleLastPost() {
  443. loadBlackList();
  444. loadSettings();
  445.  
  446. switch (settings.lastpostMode) {
  447. case IGNORE:
  448. ignoreLastPost();
  449. return;
  450. case REPLACE:
  451. replaceLastPost();
  452. break;
  453. case DO_NOTHING:
  454. break;
  455. default:
  456. saveSetting("lastpostMode", IGNORE);
  457. ignoreLastPost();
  458. break;
  459. }
  460. }
  461.  
  462. function handleQuotes() {
  463. loadBlackList();
  464. loadSettings();
  465.  
  466. switch (settings.quoteMode) {
  467. case IGNORE:
  468. ignoreQuotes();
  469. return;
  470. case REPLACE:
  471. replaceQuotes();
  472. break;
  473. case DO_NOTHING:
  474. break;
  475. default:
  476. saveSetting("quoteMode", IGNORE);
  477. ignoreQuotes();
  478. break;
  479. }
  480. }
  481.  
  482. function handleReplies() {
  483. loadBlackList();
  484. loadSettings();
  485.  
  486. switch (settings.replyMode) {
  487. case IGNORE:
  488. ignoreReplies();
  489. return;
  490. case REPLACE:
  491. replaceReplies();
  492. break;
  493. case DO_NOTHING:
  494. break;
  495. default:
  496. saveSetting("replyMode", IGNORE);
  497. ignoreReplies();
  498. break;
  499. }
  500. }
  501.  
  502. function handleTopics() {
  503. loadBlackList();
  504. loadSettings();
  505. if (settings.removeTopics) {
  506. ignoreTopics();
  507. }
  508. }
  509.  
  510. function handleProfileMsgs() {
  511. loadBlackList();
  512. loadSettings();
  513. addProfileMsgBlackListButtons();
  514.  
  515. switch (settings.profileMsgMode) {
  516. case IGNORE:
  517. ignoreProfileMsgs();
  518. return;
  519. case REPLACE:
  520. replaceProfileMsg();
  521. break;
  522. case DO_NOTHING:
  523. break;
  524. default:
  525. saveSetting("profileMsgMode", IGNORE);
  526. ignoreProfileMsgs();
  527. break;
  528. }
  529.  
  530. if (settings.replaceProfileAvatar) replaceProfileAvatar();
  531. }
  532.  
  533. function handleComToCom() {
  534. loadBlackList();
  535. loadSettings();
  536. addComToComBlackListButtons();
  537.  
  538. switch (settings.profileMsgMode) {
  539. case IGNORE:
  540. ignoreComToCom();
  541. return;
  542. case REPLACE:
  543. replaceComToCom();
  544. break;
  545. case DO_NOTHING:
  546. break;
  547. default:
  548. saveSetting("profileMsgMode", IGNORE);
  549. ignoreComToCom();
  550. break;
  551. }
  552.  
  553. if (settings.replaceProfileAvatar) replaceComToComAvatar();
  554. }
  555.  
  556. function handleBlacklist() {
  557. loadBlackList();
  558. loadSettings();
  559.  
  560. document.title = "Blacklist - MyAnimeList.net";
  561.  
  562. //remove the 404 stuff
  563. document.querySelector("h1").textContent = "Ignore List";
  564. document.querySelector(".error404").remove();
  565.  
  566. //CSS
  567. addStyle(".flex{display:flex;gap:20px;margin-top:10px;}.user{display:flex;margin:10px}.name{margin-right:20px}.name{border-bottom:solid #000 1px}.name[contenteditable]{min-width:100px;border-bottom:solid #000 1px}.name[contenteditable]:focus{border:none;outline:solid red 5px}.page-common #content{display:flex;justify-content:center;}.settings{display:flex;gap:25px;}.settings>*{padding: 25px;}.customPost{width:100% !important;}.select-users{font-size:1rem;padding:10px;font-weight:bold;}");
  568.  
  569. //HTML for the blacklist
  570. document.getElementById("content").innerHTML =
  571. `<div data-blacklist class="black-list"></div>
  572. <div data-add-user class="add-user">
  573. <div data-user class="user">
  574. <div data-name class="name" contenteditable="true" onclick="this.focus()"></div>
  575. <button data-add class="add">Add</div>
  576. </div>
  577. </div>`
  578.  
  579. //HTML for the settings
  580. const settings = document.createElement("div");
  581. settings.innerHTML = `
  582. <h2>Settings</h2>
  583. <form>
  584. <div class="settings">
  585. <div class="posts">
  586. <h3>Posts</h3>
  587. <div class="form-check">
  588. <input class="form-check-input" type="radio" name="posts" id="doNothingPosts" data-clickable-setting="doNothingPosts">
  589. <label class="form-check-label" for="doNothingPosts">
  590. Do nothing
  591. </label>
  592. </div>
  593. <div class="form-check">
  594. <input class="form-check-input" type="radio" name="posts" id="hidePosts" data-clickable-setting="hidePosts">
  595. <label class="form-check-label" for="hidePosts">
  596. Hide posts
  597. </label>
  598. </div>
  599. <div class="form-check">
  600. <input class="form-check-input" type="radio" name="posts" id="replacePosts" data-clickable-setting="replacePosts">
  601. <label class="form-check-label" for="replacePosts">
  602. Replace posts with a custom message
  603. </label>
  604. </div>
  605. <textarea class="form-control customPost" name="customPost" id="customPost" data-text-setting="customPost"></textarea>
  606. </div>
  607. <div class="posts-extra">
  608. <h3>Posts extra options</h3>
  609. <div class="form-check">
  610. <input class="form-check-input" type="checkbox" name="replaceUsername" id="replaceUsername" data-clickable-setting="replaceUsername">
  611. <label class="form-check-label" for="replaceUsername">
  612. Replace user name with a custom name
  613. </label>
  614. <input class="form-control" type="text" name="customUsername" id="customUsername" data-text-setting="customUsername" placeholder="removed-user">
  615. <br>
  616. <small>Leave it empty to remove the username</small>
  617. </div>
  618. <div class="form-check">
  619. <input class="form-check-input" type="checkbox" name="replaceAvatar" id="replaceAvatar" data-clickable-setting="replaceAvatar">
  620. <label class="form-check-label" for="replaceAvatar">
  621. Replace avatars with a custom avatar
  622. </label>
  623. <input class="form-control" type="text" name="customAvatar" id="customAvatar" data-text-setting="customAvatar">
  624. <br>
  625. <small>Leave it empty to remove the avatar</small>
  626. </div>
  627. <div class="form-check" style="margin-top: 10px;">
  628. <input class="form-check-input" type="checkbox" name="removeSignatures" id="removeSignatures" data-clickable-setting="removeSignatures">
  629. <label class="form-check-label" for="removeSignatures">
  630. Hide the signature
  631. </label>
  632. </div>
  633. <div class="form-check" style="margin-top: 10px;">
  634. <input class="form-check-input" type="checkbox" name="removeUserinfo" id="removeUserinfo" data-clickable-setting="removeUserinfo">
  635. <label class="form-check-label" for="removeUserinfo">
  636. Hide user info
  637. </label>
  638. </div>
  639. <small style="margin-top: 20px; display: block;"><strong>These settings have no effect if the Posts setting is set to "Hide Posts"</strong></small>
  640. </div>
  641. <div class="quotes">
  642. <h3>Quotes</h3>
  643. <div class="form-check">
  644. <input class="form-check-input" type="radio" name="quotes" id="doNothingQuotes" data-clickable-setting="doNothingQuotes">
  645. <label class="form-check-label" for="doNothingQuotes">
  646. Do nothing
  647. </label>
  648. </div>
  649. <div class="form-check">
  650. <input class="form-check-input" type="radio" name="quotes" id="hideQuotes" data-clickable-setting="hideQuotes">
  651. <label class="form-check-label" for="hideQuotes">
  652. Hide quotes
  653. </label>
  654. </div>
  655. <div class="form-check">
  656. <input class="form-check-input" type="radio" name="quotes" id="replaceQuotes" data-clickable-setting="replaceQuotes">
  657. <label class="form-check-label" for="replaceQuotes">
  658. Replace quotes with a custom message
  659. </label>
  660. </div>
  661. <textarea class="form-control customPost" name="customQuote" id="customQuote" data-text-setting="customQuote"></textarea>
  662. </div>
  663. <div class="replies">
  664. <h3>Replies</h3>
  665. <div class="form-check">
  666. <input class="form-check-input" type="radio" name="replies" id="doNothingReplies" data-clickable-setting="doNothingReplies">
  667. <label class="form-check-label" for="doNothingReplies">
  668. Do nothing
  669. </label>
  670. </div>
  671. <div class="form-check">
  672. <input class="form-check-input" type="radio" name="replies" id="hideReplies" data-clickable-setting="hideReplies">
  673. <label class="form-check-label" for="hideReplies">
  674. Hide replies
  675. </label>
  676. </div>
  677. <div class="form-check">
  678. <input class="form-check-input" type="radio" name="replies" id="replaceReplies" data-clickable-setting="replaceReplies">
  679. <label class="form-check-label" for="replaceReplies">
  680. Replace replies with a custom message
  681. </label>
  682. </div>
  683. <textarea class="form-control customPost" name="customReply" id="customReply" data-text-setting="customReply"></textarea>
  684. </div>
  685. </div>
  686. <div class="settings">
  687. <div class="topics">
  688. <h3>Topics</h3>
  689. <div class="form-check">
  690. <input class="form-check-input" type="checkbox" name="removeTopics" id="removeTopics" data-clickable-setting="removeTopics">
  691. <label class="form-check-label" for="removeTopics">
  692. Hide user created topics
  693. </label>
  694. </div>
  695. </div>
  696. <div class="lastpost">
  697. <h3>Last Post</h3>
  698. <div class="form-check">
  699. <input class="form-check-input" type="radio" name="lastpost" id="doNothingLastPost" data-clickable-setting="doNothingLastPost">
  700. <label class="form-check-label" for="doNothingLastPost">
  701. Do nothing
  702. </label>
  703. </div>
  704. <div class="form-check">
  705. <input class="form-check-input" type="radio" name="lastpost" id="hideLastPost" data-clickable-setting="hideLastPost">
  706. <label class="form-check-label" for="hideLastPost">
  707. Hide last posts
  708. </label>
  709. </div>
  710. <div class="form-check">
  711. <input class="form-check-input" type="radio" name="lastpost" id="replaceLastPost" data-clickable-setting="replaceLastPost">
  712. <label class="form-check-label" for="replaceLastPost">
  713. Replace user name with a custom name
  714. </label>
  715. <br>
  716. <input class="form-control" type="text" name="customLastPost" id="customLastPost" data-text-setting="customLastPost" placeholder="removed-user">
  717. <br>
  718. <small>Leave it empty to remove the username</small>
  719. </div>
  720. </div>
  721. </div>
  722. <div class="settings">
  723. <div class="profile-messages">
  724. <h3>Profile messages</h3>
  725. <div class="form-check">
  726. <input class="form-check-input" type="radio" name="profileMsgs" id="doNothingProfileMsgs" data-clickable-setting="doNothingProfileMsgs">
  727. <label class="form-check-label" for="doNothingProfileMsgs">
  728. Do nothing
  729. </label>
  730. </div>
  731. <div class="form-check">
  732. <input class="form-check-input" type="radio" name="profileMsgs" id="hideProfileMsgs" data-clickable-setting="hideProfileMsgs">
  733. <label class="form-check-label" for="hideProfileMsgs">
  734. Hide profile messages
  735. </label>
  736. </div>
  737. <div class="form-check">
  738. <input class="form-check-input" type="radio" name="profileMsgs" id="replaceProfileMsgs" data-clickable-setting="replaceProfileMsgs">
  739. <label class="form-check-label" for="replaceProfileMsgs">
  740. Replace profile messages with a custom message
  741. </label>
  742. </div>
  743. <textarea class="form-control customPost" name="customProfileMsg" id="customProfileMsg" data-text-setting="customProfileMsg"></textarea>
  744. </div>
  745. <div class="profile-messages-extra">
  746. <h3>Profile messages extra options</h3>
  747. <div class="form-check">
  748. <input class="form-check-input" type="checkbox" name="replaceProfileUsername" id="replaceProfileUsername" data-clickable-setting="replaceProfileUsername">
  749. <label class="form-check-label" for="replaceProfileUsername">
  750. Replace profile user with a custom name
  751. </label>
  752. <br>
  753. <input class="form-control" type="text" name="customProfileUsername" id="customProfileUsername" data-text-setting="customProfileUsername" placeholder="removed-user">
  754. <br>
  755. <small>Leave it empty to remove the avatar</small>
  756. </div>
  757. <div class="form-check">
  758. <input class="form-check-input" type="checkbox" name="replaceProfileAvatar" id="replaceProfileAvatar" data-clickable-setting="replaceProfileAvatar">
  759. <label class="form-check-label" for="replaceProfileAvatar">
  760. Replace profile message avatar with a custom avatar
  761. </label>
  762. <br>
  763. <input class="form-control" type="text" name="customProfileAvatar" id="customProfileAvatar" data-text-setting="customProfileAvatar">
  764. <br>
  765. <small>Leave it empty to remove the avatar</small>
  766. </div>
  767. <small style="margin-top: 20px; display: block;"><strong>These settings have no effect if the Profile Messages setting is set to "Hide profile messages"</strong></small>
  768. </div>
  769. <div class="unblacklist">
  770. <h3>UnBlacklist</h3>
  771. <div class="form-check">
  772. <input class="form-check-input" type="checkbox" name="UnBlacklistUsername" id="UnBlacklistUsername" data-clickable-setting="UnBlacklistUsername">
  773. <label class="form-check-label" for="UnBlacklistUsername">
  774. Show user name when hovering<br>on UnBlacklist buttons/links
  775. </label>
  776. </div>
  777. <div class="form-check">
  778. <input class="form-check-input" type="checkbox" name="removeUnBlacklist" id="removeUnBlacklist" data-clickable-setting="removeUnBlacklist">
  779. <label class="form-check-label" for="removeUnBlacklist">
  780. Hide UnBlacklist buttons/links
  781. </label>
  782. </div>
  783. <small style="margin-top: 20px; display: block;"><strong>These settings have no effect if the Posts setting<br>is set to "Hide Posts" or Profile Messages setting<br>is set to "Hide profile messages"</strong></small>
  784. </div>
  785. </div>
  786. <select name="users" class="select-users" data-select-users></select>
  787. <div class="flex">
  788. <div class="form-check">
  789. <label class="form-check-label" for="specificCustomPost">
  790. Replace this user's posts with a specific custom message
  791. </label>
  792. <textarea class="form-control customPost" name="specificCustomPost" id="specificCustomPost" data-text-setting="specificCustomPost"></textarea>
  793. </div>
  794. <div class="form-check">
  795. <label class="form-check-label" for="specificCustomLastPost">
  796. Replace the last posts username with a specific custom message
  797. </label>
  798. <textarea class="form-control customPost" name="specificCustomLastPost" id="specificCustomLastPost" data-text-setting="specificCustomLastPost" placeholder="type a user name like removed-user"></textarea>
  799. </div>
  800. <div class="form-check">
  801. <label class="form-check-label" for="specificCustomProfileMsg">
  802. Replace this user's profile messages with a custom message
  803. </label>
  804. <textarea class="form-control customPost" name="specificCustomProfileMsg" id="specificCustomProfileMsg" data-text-setting="specificCustomProfileMsg"></textarea>
  805. </div>
  806. <div class="form-check">
  807. <label class="form-check-label" for="specificCustomQuote">
  808. Replace this user's quotes with a specific custom message
  809. </label>
  810. <textarea class="form-control customPost" name="specificCustomQuote" id="specificCustomQuote" data-text-setting="specificCustomQuote"></textarea>
  811. </div>
  812. <div class="form-check">
  813. <label class="form-check-label" for="specificCustomReply">
  814. Replace this user's replies with a specific custom message
  815. </label>
  816. <textarea class="form-control customPost" name="specificCustomReply" id="specificCustomReply" data-text-setting="specificCustomReply"></textarea>
  817. </div>
  818. </div>
  819. </form>`;
  820.  
  821. document.getElementById("content").insertAdjacentElement("afterend", settings);
  822.  
  823. startEventListeners();
  824. loadSettingsIntoInputs();
  825. }
  826.  
  827. function clickedSetting(e) {
  828. const input = e.target;
  829. switch (input.dataset.clickableSetting) {
  830. case "doNothingLastPost":
  831. saveSetting("lastpostMode", DO_NOTHING);
  832. break;
  833. case "hideLastPost":
  834. saveSetting("lastpostMode", IGNORE);
  835. break;
  836. case "replaceLastPost":
  837. saveSetting("lastpostMode", REPLACE);
  838. break;
  839. case "doNothingQuotes":
  840. saveSetting("quoteMode", DO_NOTHING);
  841. break;
  842. case "hideQuotes":
  843. saveSetting("quoteMode", IGNORE);
  844. break;
  845. case "replaceQuotes":
  846. saveSetting("quoteMode", REPLACE);
  847. break;
  848. case "doNothingReplies":
  849. saveSetting("replyMode", DO_NOTHING);
  850. break;
  851. case "hideReplies":
  852. saveSetting("replyMode", IGNORE);
  853. break;
  854. case "replaceReplies":
  855. saveSetting("replyMode", REPLACE);
  856. break;
  857. case "doNothingPosts":
  858. saveSetting("postMode", DO_NOTHING);
  859. break;
  860. case "hidePosts":
  861. saveSetting("postMode", IGNORE);
  862. break;
  863. case "replacePosts":
  864. saveSetting("postMode", REPLACE);
  865. break;
  866. case "replaceUsername":
  867. saveSetting("replaceUsername", input.checked);
  868. break;
  869. case "replaceAvatar":
  870. saveSetting("replaceAvatar", input.checked);
  871. break;
  872. case "removeTopics":
  873. saveSetting("removeTopics", input.checked);
  874. break;
  875. case "UnBlacklistUsername":
  876. saveSetting("UnBlacklistUsername", input.checked);
  877. break;
  878. case "removeUnBlacklist":
  879. saveSetting("removeUnBlacklist", input.checked);
  880. break;
  881. case "removeSignatures":
  882. saveSetting("removeSignatures", input.checked);
  883. break;
  884. case "removeUserinfo":
  885. saveSetting("removeUserinfo", input.checked);
  886. break;
  887. case "doNothingProfileMsgs":
  888. saveSetting("profileMsgMode", DO_NOTHING);
  889. break;
  890. case "hideProfileMsgs":
  891. saveSetting("profileMsgMode", IGNORE);
  892. break;
  893. case "replaceProfileMsgs":
  894. saveSetting("profileMsgMode", REPLACE);
  895. break;
  896. case "replaceProfileAvatar":
  897. saveSetting("replaceProfileAvatar", input.checked);
  898. break;
  899. default:
  900. return;
  901. }
  902. }
  903.  
  904. function textSetting(e) {
  905. const input = e.target;
  906. switch (input.dataset.textSetting) {
  907. case "customLastPost":
  908. saveSetting("customLastPost", input.value);
  909. break;
  910. case "customQuote":
  911. saveSetting("customQuote", input.value);
  912. break;
  913. case "customReply":
  914. saveSetting("customReply", input.value);
  915. break;
  916. case "customPost":
  917. saveSetting("customPost", input.value);
  918. break;
  919. case "customUsername":
  920. saveSetting("customUsername", input.value);
  921. break;
  922. case "customAvatar":
  923. saveSetting("customAvatar", input.value);
  924. break;
  925. case "customProfileMsg":
  926. saveSetting("customProfileMsg", input.value);
  927. break;
  928. case "customProfileAvatar":
  929. saveSetting("customProfileAvatar", input.value);
  930. break;
  931. case "specificCustomProfileMsg":
  932. {
  933. const selectedUser = document.querySelector("[data-select-users]").value;
  934. saveSetting({ key: "specificCustomProfileMsg", subkey: selectedUser }, input.value);
  935. break;
  936. }
  937. case "specificCustomPost":
  938. {
  939. const selectedUser = document.querySelector("[data-select-users]").value;
  940. saveSetting({ key: "specificCustomPost", subkey: selectedUser }, input.value);
  941. break;
  942. }
  943. case "specificCustomQuote":
  944. {
  945. const selectedUser = document.querySelector("[data-select-users]").value;
  946. saveSetting({ key: "specificCustomQuote", subkey: selectedUser }, input.value);
  947. break;
  948. }
  949. case "specificCustomReply":
  950. {
  951. const selectedUser = document.querySelector("[data-select-users]").value;
  952. saveSetting({ key: "specificCustomReply", subkey: selectedUser }, input.value);
  953. break;
  954. }
  955. case "specificCustomLastPost":
  956. {
  957. const selectedUser = document.querySelector("[data-select-users]").value;
  958. saveSetting({ key: "specificCustomLastPost", subkey: selectedUser }, input.value);
  959. break;
  960. }
  961. default:
  962. return;
  963. }
  964. }
  965.  
  966. function startEventListeners() {
  967. document.querySelector("[data-add]").addEventListener("click", addNode);
  968. document.querySelectorAll("[data-clickable-setting]").forEach(clickable => {
  969. clickable.addEventListener("click", clickedSetting);
  970. });
  971. document.querySelectorAll("[data-text-setting]").forEach(text => {
  972. text.addEventListener("input", textSetting);
  973. });
  974. document.querySelector("[data-select-users]").addEventListener("change", loadSpecificMessages)
  975. blacklist.forEach(createNode);
  976. blacklist.forEach(addUserToSelect);
  977. }
  978.  
  979. function loadSettingsIntoInputs() {
  980. switch (settings.lastpostMode) {
  981. case DO_NOTHING:
  982. document.getElementById("doNothingLastPost").checked = true;
  983. break;
  984. case IGNORE:
  985. document.getElementById("hideLastPost").checked = true;
  986. break;
  987. case REPLACE:
  988. document.getElementById("replaceLastPost").checked = true;
  989. break;
  990. default:
  991. document.getElementById("hideLastPost").checked = true;
  992. saveSetting("lastpostMode", IGNORE);
  993. break;
  994. }
  995.  
  996. switch (settings.quoteMode) {
  997. case DO_NOTHING:
  998. document.getElementById("doNothingQuotes").checked = true;
  999. break;
  1000. case IGNORE:
  1001. document.getElementById("hideQuotes").checked = true;
  1002. break;
  1003. case REPLACE:
  1004. document.getElementById("replaceQuotes").checked = true;
  1005. break;
  1006. default:
  1007. document.getElementById("hideQuotes").checked = true;
  1008. saveSetting("quoteMode", IGNORE);
  1009. break;
  1010. }
  1011.  
  1012. switch (settings.replyMode) {
  1013. case DO_NOTHING:
  1014. document.getElementById("doNothingReplies").checked = true;
  1015. break;
  1016. case IGNORE:
  1017. document.getElementById("hideReplies").checked = true;
  1018. break;
  1019. case REPLACE:
  1020. document.getElementById("replaceReplies").checked = true;
  1021. break;
  1022. default:
  1023. document.getElementById("hideReplies").checked = true;
  1024. saveSetting("replyMode", IGNORE);
  1025. break;
  1026. }
  1027.  
  1028. switch (settings.postMode) {
  1029. case DO_NOTHING:
  1030. document.getElementById("doNothingPosts").checked = true;
  1031. break;
  1032. case IGNORE:
  1033. document.getElementById("hidePosts").checked = true;
  1034. break;
  1035. case REPLACE:
  1036. document.getElementById("replacePosts").checked = true;
  1037. break;
  1038. default:
  1039. document.getElementById("hidePosts").checked = true;
  1040. saveSetting("postMode", IGNORE);
  1041. break;
  1042. }
  1043.  
  1044. switch (settings.profileMsgMode) {
  1045. case DO_NOTHING:
  1046. document.getElementById("doNothingProfileMsgs").checked = true;
  1047. break;
  1048. case IGNORE:
  1049. document.getElementById("hideProfileMsgs").checked = true;
  1050. break;
  1051. case REPLACE:
  1052. document.getElementById("replaceProfileMsgs").checked = true;
  1053. break;
  1054. default:
  1055. document.getElementById("hideProfileMsgs").checked = true;
  1056. saveSetting("profileMsgMode", IGNORE);
  1057. break;
  1058. }
  1059.  
  1060. if (settings.removeTopics) {
  1061. document.getElementById("removeTopics").checked = true;
  1062. }
  1063. if (settings.UnBlacklistUsername) {
  1064. document.getElementById("UnBlacklistUsername").checked = true;
  1065. }
  1066. if (settings.removeUnBlacklist) {
  1067. document.getElementById("removeUnBlacklist").checked = true;
  1068. }
  1069. if (settings.removeSignatures) {
  1070. document.getElementById("removeSignatures").checked = true;
  1071. }
  1072. if (settings.removeUserinfo) {
  1073. document.getElementById("removeUserinfo").checked = true;
  1074. }
  1075. if (settings.replaceUsername) {
  1076. document.getElementById("replaceUsername").checked = true;
  1077. }
  1078. if (settings.replaceAvatar) {
  1079. document.getElementById("replaceAvatar").checked = true;
  1080. }
  1081. if (settings.replaceProfileAvatar) {
  1082. document.getElementById("replaceProfileAvatar").checked = true;
  1083. }
  1084.  
  1085. document.getElementById("customLastPost").value = settings.customLastPost || "";
  1086. document.getElementById("customQuote").value = settings.customQuote || "";
  1087. document.getElementById("customReply").value = settings.customReply || "";
  1088. document.getElementById("customPost").value = settings.customPost || "";
  1089. document.getElementById("customUsername").value = settings.customUsername || "";
  1090. document.getElementById("customAvatar").value = settings.customAvatar || "";
  1091. document.getElementById("customProfileMsg").value = settings.customProfileMsg || "";
  1092. document.getElementById("customProfileAvatar").value = settings.customProfileAvatar || "";
  1093.  
  1094. document.querySelector("[data-select-users]").dispatchEvent(new Event("change"));
  1095. }
  1096.  
  1097. function alterLastPost(action) {
  1098. const listItems = document.querySelectorAll(LAST_POST_LIST_CONTAINER_SELECTOR);
  1099. const tableCells = document.querySelectorAll(LAST_POST_TABLE_CONTAINER_SELECTOR);
  1100. const allLastPostElements = [...listItems, ...tableCells];
  1101. allLastPostElements.forEach((item, index) => {
  1102. let usernameElement = null;
  1103. let user = null;
  1104. if (item.matches(LAST_POST_LIST_CONTAINER_SELECTOR)) {
  1105. usernameElement = item.querySelector(LAST_POST_LIST_USER_SELECTOR);
  1106. } else if (item.matches(LAST_POST_TABLE_CONTAINER_SELECTOR)) {
  1107. usernameElement = item.querySelector(LAST_POST_TABLE_USER_SELECTOR);
  1108. } else {
  1109. return;
  1110. }
  1111. if (!usernameElement) return;
  1112. user = usernameElement.textContent.trim();
  1113. if (!blacklist.includes(user)) return;
  1114. action(item, user, usernameElement);
  1115. });
  1116. }
  1117.  
  1118. function alterQuotes(action) {
  1119. document.querySelectorAll(QUOTE_SELECTOR).forEach(quotes => {
  1120. if (quotes.querySelector(FORUM_QUOTE_NAME_SELECTOR) == null) return;
  1121. const quoteuser = quotes.querySelector(FORUM_QUOTE_NAME_SELECTOR).textContent;
  1122. let user = quoteuser.replace(' said:','');
  1123. if (!blacklist.includes(user)) return;
  1124. let quote = quotes;
  1125. action(quote, user);
  1126. });
  1127. }
  1128.  
  1129. function alterReplies(action) {
  1130. document.querySelectorAll(REPLIED_CONTAINER_SELECTOR).forEach(replyContainer => {
  1131. const usernameElement = replyContainer.querySelector(FORUM_REPLY_NAME_SELECTOR);
  1132. if (!usernameElement) return;
  1133. const fullText = usernameElement.textContent;
  1134. const usernameMatch = fullText.match(/^Reply to (.+)/);
  1135. let user = null;
  1136. if (usernameMatch && usernameMatch[1]) {
  1137. user = usernameMatch[1].trim();
  1138. }
  1139. if (!blacklist.includes(user)) return;
  1140. action(replyContainer, user);
  1141. });
  1142. }
  1143.  
  1144. function alterPosts(action) {
  1145. document.querySelectorAll(POST_USERS_SELECTOR).forEach(user => {
  1146. if (!blacklist.includes(user.querySelector(FORUM_MSG_NAME_SELECTOR).textContent)) return;
  1147. let post = user.parentNode;
  1148. //for (let i = 0; i < 4; i++) {
  1149. //post = post.parentNode;
  1150. //}
  1151. action(post, user);
  1152. });
  1153. }
  1154.  
  1155. function alterProfileMessages(action) {
  1156. document.querySelectorAll(PROFILE_MSG_SELECTOR).forEach(profileMessage => {
  1157. const username = profileMessage.querySelector(PROFILE_MSG_NAME_SELECTOR).textContent;
  1158. if (!blacklist.includes(username)) return;
  1159. action(profileMessage, username);
  1160. });
  1161. }
  1162.  
  1163. function alterComToCom(action) {
  1164. document.querySelectorAll(COMTOCOM_SELECTOR).forEach(comMessage => {
  1165. const username = comMessage.querySelector(COMTOCOM_NAME_SELECTOR).textContent;
  1166. if (!blacklist.includes(username)) return;
  1167. action(comMessage, username);
  1168. });
  1169. }
  1170.  
  1171. function ignoreLastPost() {
  1172. alterLastPost(lastPostItem => {
  1173. lastPostItem.style.display = "none";
  1174. });
  1175. }
  1176.  
  1177. function replaceLastPost() {
  1178. alterLastPost((lastPostItem, user, usernameElement) => {
  1179. const specificCustomLastPost = settings.specificCustomLastPost ? settings.specificCustomLastPost[user] : null;
  1180. const replacementHTML = specificCustomLastPost ? specificCustomLastPost : (settings.customLastPost || "");
  1181. const tempDiv = document.createElement('div');
  1182. tempDiv.innerHTML = replacementHTML;
  1183. const fragment = document.createDocumentFragment();
  1184. while (tempDiv.firstChild) {
  1185. fragment.appendChild(tempDiv.firstChild);
  1186. }
  1187. const parent = usernameElement.parentNode;
  1188. if (parent) {
  1189. parent.replaceChild(fragment, usernameElement);
  1190. }
  1191. });
  1192. }
  1193.  
  1194. function ignoreQuotes() {
  1195. alterQuotes(quote => {
  1196. quote.style.display = "none";
  1197. //post.previousElementSibling.style.display = "none";
  1198. });
  1199. }
  1200.  
  1201. function replaceQuotes() {
  1202. alterQuotes((quote, user) => {
  1203. const specificCustomQuote = settings.specificCustomQuote[user];
  1204. quote.innerHTML = specificCustomQuote ? specificCustomQuote : settings.customQuote;
  1205. });
  1206. }
  1207.  
  1208. function ignoreReplies() {
  1209. alterReplies(replyContainer => {
  1210. replyContainer.style.display = "none";
  1211. });
  1212. }
  1213.  
  1214. function replaceReplies() {
  1215. alterReplies((replyContainer, user) => {
  1216. const specificCustomReply = settings.specificCustomReply ? settings.specificCustomReply[user] : null;
  1217. replyContainer.innerHTML = specificCustomReply ? specificCustomReply : (settings.customReply || "");
  1218. });
  1219. }
  1220.  
  1221. function ignorePosts() {
  1222. alterPosts(post => {
  1223. post.style.display = "none";
  1224. post.previousElementSibling.style.display = "none";
  1225. });
  1226. }
  1227.  
  1228. function replacePosts() {
  1229. alterPosts((post, user) => {
  1230. const username = user.querySelector(FORUM_MSG_NAME_SELECTOR).textContent;
  1231. const specificCustomPost = settings.specificCustomPost[username];
  1232. post.querySelector(MESSAGE_SELECTOR).innerHTML = specificCustomPost ? specificCustomPost : settings.customPost;
  1233. });
  1234. }
  1235.  
  1236. function replaceProfileMsg() {
  1237. alterProfileMessages((profileMessage, username) => {
  1238. const specificCustomProfileMsg = settings.specificCustomProfileMsg[username];
  1239. profileMessage.querySelector(PROFILE_MSG_TEXT_SELECTOR).innerHTML = specificCustomProfileMsg ? specificCustomProfileMsg : settings.customProfileMsg;
  1240. });
  1241. }
  1242.  
  1243. function replaceComToCom() {
  1244. alterComToCom((comMessage, username) => {
  1245. const specificCustomProfileMsg = settings.specificCustomProfileMsg[username];
  1246. comMessage.querySelector(COMTOCOM_TEXT_SELECTOR).innerHTML = specificCustomProfileMsg ? specificCustomProfileMsg : settings.customProfileMsg;
  1247. });
  1248. }
  1249.  
  1250. function ignoreTopics() {
  1251. document.querySelectorAll(TOPIC_USERS_SELECTOR).forEach(user => {
  1252. if (!blacklist.includes(user.textContent)) return;
  1253. user.closest("tr").style.display = "none";
  1254. });
  1255. }
  1256. function ignoreProfileMsgs() {
  1257. alterProfileMessages(profileMessage => {
  1258. profileMessage.style.display = "none";
  1259. });
  1260. }
  1261.  
  1262. function ignoreComToCom() {
  1263. alterComToCom(comMessage => {
  1264. comMessage.style.display = "none";
  1265. });
  1266. }
  1267.  
  1268. function replaceUsername() {
  1269. alterPosts((post, user) => {
  1270. user.querySelector(FORUM_MSG_NAME_SELECTOR).innerHTML = `<a href="${user.querySelector(USER_PROFILE_SELECTOR).href}">${settings.customUsername}</a>`;
  1271. });
  1272. }
  1273.  
  1274. function alterLastPostAvatars(action) {
  1275. document.querySelectorAll(LAST_POST_LIST_AVATAR_SELECTOR).forEach(avatarLink => {
  1276. const userMatch = avatarLink.href.split('/').pop();
  1277. if (userMatch && blacklist.includes(userMatch)) {
  1278. action(avatarLink, userMatch);
  1279. }
  1280. });
  1281. }
  1282.  
  1283. function replaceAvatar() {
  1284. alterPosts((post, userElement) => {
  1285. const avatar = userElement.querySelector(AVATAR_SELECTOR);
  1286. if (!avatar) {
  1287. if (settings.customAvatar === "") return;
  1288. const newAvatarLink = document.createElement("a");
  1289. const profileLinkElement = userElement.querySelector(USER_PROFILE_SELECTOR);
  1290. if (profileLinkElement) {
  1291. newAvatarLink.href = profileLinkElement.href;
  1292. } else {
  1293. newAvatarLink.href = "#";
  1294. }
  1295. newAvatarLink.className = "forum-icon";
  1296. newAvatarLink.innerHTML = `
  1297. <img class=" lazyloaded" data-src="${settings.customAvatar}" vspace="2" border="0" src="${settings.customAvatar}" width="100" height="125">`;
  1298. const userInfoElement = userElement.querySelector(USER_INFO_SELECTOR);
  1299. if (userInfoElement) {
  1300. userInfoElement.insertAdjacentElement('afterend', newAvatarLink);
  1301. } else {
  1302. userElement.appendChild(newAvatarLink);
  1303. }
  1304. } else {
  1305. if (settings.customAvatar === "") {
  1306. avatar.style.display = "none";
  1307. return;
  1308. }
  1309. const img = avatar.querySelector("img");
  1310. if (img) {
  1311. img.src = settings.customAvatar;
  1312. img.setAttribute("data-src", settings.customAvatar);
  1313. img.setAttribute("width", 100);
  1314. img.setAttribute("height", 125);
  1315. }
  1316. }
  1317. });
  1318. alterLastPostAvatars((avatarLink, user) => {
  1319. if (settings.customAvatar === "") {
  1320. avatarLink.style.display = "none";
  1321. return;
  1322. }
  1323. const img = avatarLink.querySelector("img");
  1324. if (img) {
  1325. img.src = settings.customAvatar;
  1326. img.setAttribute("data-src", settings.customAvatar);
  1327. img.setAttribute("width", 20);
  1328. img.setAttribute("height", 25);
  1329. } else {
  1330. if (settings.customAvatar !== "") {
  1331. const replacementImg = document.createElement('img');
  1332. replacementImg.src = settings.customAvatar;
  1333. replacementImg.setAttribute("data-src", settings.customAvatar);
  1334. replacementImg.setAttribute("width", 20);
  1335. replacementImg.setAttribute("height", 25);
  1336. const parent = avatarLink.parentNode;
  1337. if (parent) {
  1338. parent.replaceChild(replacementImg, avatarLink);
  1339. }
  1340. }
  1341. }
  1342. });
  1343. }
  1344.  
  1345. function replaceProfileAvatar() {
  1346. alterProfileMessages(profileMessage => {
  1347. const avatar = profileMessage.querySelector(PROFILE_MSG_AVATAR_SELECTOR);
  1348.  
  1349. if (settings.customProfileAvatar === "") {
  1350. avatar.style.display = "none";
  1351. return;
  1352. }
  1353.  
  1354. const img = avatar.querySelector("img");
  1355. img.src = settings.customProfileAvatar;
  1356. img.setAttribute("data-src", settings.customProfileAvatar);
  1357. });
  1358. }
  1359.  
  1360. function replaceComToComAvatar() {
  1361. alterComToCom(comMessage => {
  1362. const avatar = comMessage.querySelector(COMTOCOM_AVATAR_SELECTOR);
  1363.  
  1364. if (settings.customProfileAvatar === "") {
  1365. avatar.style.display = "none";
  1366. return;
  1367. }
  1368.  
  1369. const img = avatar.querySelector("img");
  1370. img.src = settings.customProfileAvatar;
  1371. img.setAttribute("data-src", settings.customProfileAvatar);
  1372. });
  1373. }
  1374.  
  1375. function removeSignatures() {
  1376. alterPosts(post => {
  1377. const signature = post.querySelector(SIGNATURE_SELECTOR);
  1378. if (!signature) return;
  1379. signature.style.display = "none";
  1380. });
  1381. }
  1382.  
  1383. function removeUserinfo() {
  1384. alterPosts(post => {
  1385. const userinfo = post.querySelector(USERINFO_SELECTOR);
  1386. if (!userinfo) return;
  1387. userinfo.style.display = "none";
  1388. const userinfo1 = post.querySelector(USERINFO_SELECTOR1);
  1389. if (!userinfo1) return;
  1390. userinfo1.style.display = "none";
  1391. const userinfo2 = post.querySelector(USERINFO_SELECTOR2);
  1392. if (!userinfo2) return;
  1393. userinfo2.style.display = "none";
  1394. const userinfo3 = post.querySelector(USERINFO_SELECTOR3);
  1395. if (!userinfo3) return;
  1396. userinfo3.style.display = "none";
  1397. const userinfo4 = post.querySelector(USERINFO_SELECTOR4);
  1398. if (userinfo4) {
  1399. userinfo4.style.display = "none";
  1400. }
  1401. const userinfo5 = post.querySelector(USERINFO_SELECTOR5);
  1402. if (userinfo5) {
  1403. userinfo5.style.display = "none";
  1404. }
  1405. const userinfo6 = post.querySelector(USERINFO_SELECTOR6);
  1406. if (userinfo6) {
  1407. userinfo6.style.display = "none";
  1408. }
  1409. });
  1410. }
  1411. function addPostsBlackListButtons() {
  1412. document.querySelectorAll(FORUM_MESSAGE_SELECTOR).forEach(forumMessage => {
  1413. const actionBar = forumMessage.querySelector(FORUM_ACTION_BAR_SELECTOR);
  1414. const username = forumMessage.querySelector(FORUM_MSG_NAME_SELECTOR).textContent;
  1415. if (!blacklist.includes(username)) {
  1416. addBlackListButton(actionBar, username);
  1417. } else {
  1418. addUnBlackListButton(actionBar, username);
  1419. }
  1420. });
  1421. }
  1422.  
  1423. function addProfileMsgBlackListButtons() {
  1424. document.querySelectorAll(PROFILE_MSG_SELECTOR).forEach(profileMessage => {
  1425. let actionBar = profileMessage.querySelector(PROFILE_MSG_ACTION_BAR_SELECTOR);
  1426. const username = profileMessage.querySelector(PROFILE_MSG_NAME_SELECTOR).textContent;
  1427.  
  1428. //this happens when you are looking at someone elses profile, create the actionBar.
  1429. if (!actionBar) {
  1430. actionBar = document.createElement("div");
  1431. actionBar.className = "postActions ar mt4";
  1432. profileMessage.querySelector(PROFILE_MSG_TEXT_SELECTOR).insertAdjacentElement("afterend", actionBar);
  1433. }
  1434.  
  1435. if (!blacklist.includes(username)) {
  1436. addBlackListLink(actionBar, username, " | ");
  1437. } else {
  1438. addUnBlackListLink(actionBar, username, " | ");
  1439. }
  1440. });
  1441. }
  1442.  
  1443. function addComToComBlackListButtons() {
  1444. document.querySelectorAll(COMTOCOM_SELECTOR).forEach(comMessage => {
  1445. let actionBar = comMessage.querySelector(COMTOCOM_ACTION_BAR_SELECTOR);
  1446. const username = comMessage.querySelector(COMTOCOM_NAME_SELECTOR).textContent;
  1447.  
  1448. //this happens when you manually enter the url of com-to-com between 2 users other than you.
  1449. if (!actionBar) {
  1450. const actionBarContainer = document.createElement("div");
  1451. actionBarContainer.style.marginTop = "10px";
  1452. actionBar = document.createElement("small");
  1453. actionBarContainer.appendChild(actionBar);
  1454. comMessage.querySelector(COMTOCOM_TEXT_SELECTOR).insertAdjacentElement("afterend", actionBarContainer);
  1455. }
  1456.  
  1457. if (!blacklist.includes(username)) {
  1458. addBlackListLink(actionBar, username, " | ");
  1459. } else {
  1460. addUnBlackListLink(actionBar, username, " | ");
  1461. }
  1462. });
  1463. }
  1464.  
  1465. function addBlackListLink(actionBar, username, separator) {
  1466. const you = document.querySelector(YOU_SELECTOR).textContent;
  1467. if (username == you) return
  1468. const a = document.createElement("a");
  1469. a.href = "javascript:void(0)";
  1470. a.textContent = "Blacklist User";
  1471. a.dataset.username = username;
  1472. a.onclick = blacklistUser;
  1473.  
  1474. actionBar.after(a);
  1475.  
  1476. if (separator) {
  1477. actionBar.after(document.createTextNode(separator));
  1478. }
  1479. }
  1480.  
  1481. function addUnBlackListLink(actionBar, username, separator) {
  1482. if (settings.removeUnBlacklist) return;
  1483. const a = document.createElement("a");
  1484. a.href = "javascript:void(0)";
  1485. a.textContent = "UnBlacklist User";
  1486. if (settings.UnBlacklistUsername) a.title = username;
  1487. a.dataset.username = username;
  1488. a.onclick = blacklistUser;
  1489.  
  1490. actionBar.after(a);
  1491.  
  1492. if (separator) {
  1493. actionBar.after(document.createTextNode(separator));
  1494. }
  1495. }
  1496.  
  1497. function addBlackListButton(actionBar, username, separator) {
  1498. const you = document.querySelector(YOU_SELECTOR).textContent;
  1499. if (username == you) return
  1500. const a = document.createElement("button");
  1501. a.href = "javascript:void(0)";
  1502. a.textContent = "Blacklist User";
  1503. a.classList.add("mal-btn");
  1504. a.classList.add("secondary");
  1505. a.classList.add("small");
  1506. a.classList.add("outline");
  1507. a.classList.add("noborder");
  1508. a.dataset.username = username;
  1509. a.onclick = blacklistUser;
  1510.  
  1511. if (actionBar.childElementCount > 0 && separator) {
  1512. actionBar.prepend(document.createTextNode(separator));
  1513. }
  1514. actionBar.prepend(a);
  1515. }
  1516.  
  1517. function addUnBlackListButton(actionBar, username, separator) {
  1518. if (settings.removeUnBlacklist) return;
  1519. const a = document.createElement("button");
  1520. a.href = "javascript:void(0)";
  1521. a.textContent = "UnBlacklist User";
  1522. a.classList.add("mal-btn");
  1523. a.classList.add("secondary");
  1524. a.classList.add("small");
  1525. a.classList.add("outline");
  1526. a.classList.add("noborder");
  1527. if (settings.UnBlacklistUsername) a.title = username;
  1528. a.dataset.username = username;
  1529. a.onclick = blacklistUser;
  1530.  
  1531. if (actionBar.childElementCount > 0 && separator) {
  1532. actionBar.prepend(document.createTextNode(separator));
  1533. }
  1534. actionBar.prepend(a);
  1535. }
  1536.  
  1537. function blacklistUser(e) {
  1538. const username = e.target.dataset.username;
  1539.  
  1540. if (blacklist.includes(username)) {
  1541. removeUser(username);
  1542. window.location.reload();
  1543. } else {
  1544. addUser(username);
  1545. window.location.reload();
  1546. }
  1547. }
  1548.  
  1549. //Add a user to the blacklist
  1550. function addUser(username) {
  1551. blacklist.push(username);
  1552. saveBlackList();
  1553. }
  1554.  
  1555. //Remove a user from the blacklist if it's there
  1556. function removeUser(userName) {
  1557. blacklist = blacklist.filter(name => userName !== name);
  1558. saveBlackList();
  1559. }
  1560.  
  1561. //remove the user node from the html code and then update the localStorage
  1562. function removeNode(e) {
  1563. const row = e.target.parentNode;
  1564. const name = row.querySelector("[data-name]").textContent;
  1565. row.remove();
  1566. removeUser(name);
  1567. removeUserFromSelect(name);
  1568. }
  1569.  
  1570. //modify the user node from the html code and then update the localStorage
  1571. function saveNode(e) {
  1572. const newName = e.target.textContent;
  1573. const previousName = e.target.dataset.previousName;
  1574.  
  1575. previousName && removeUser(previousName);
  1576.  
  1577. if (newName !== "") {
  1578. addUser(newName);
  1579. e.target.dataset.previousName = newName;
  1580. } else {
  1581. e.target.parentNode.remove();
  1582. }
  1583. }
  1584.  
  1585. //add a new user node to the html code and then update the localStorage
  1586. function addNode(e) {
  1587. const node = e.target.parentNode;
  1588. const usernameNode = node.querySelector("[data-name]");
  1589. const username = usernameNode.textContent;
  1590. usernameNode.textContent = "";
  1591.  
  1592. if (!blacklist.includes(username)) {
  1593. createNode(username);
  1594. addUser(username);
  1595. addUserToSelect(username);
  1596. }
  1597. }
  1598.  
  1599. //create the user node then add it to the html code
  1600. function createNode(username) {
  1601. const newUser = document.createElement("div");
  1602. newUser.setAttribute("data-user", "");
  1603. newUser.className = "user";
  1604. newUser.innerHTML = `<div data-name class="name" contenteditable="true" onclick="this.focus()" data-previous-name="${username}">${username}</div>
  1605. <button data-remove class="remove">Remove</button>`;
  1606. newUser.querySelector("[data-name]").addEventListener("focusout", saveNode);
  1607. newUser.querySelector("[data-remove]").addEventListener("click", removeNode);
  1608. document.querySelector("[data-blacklist]").append(newUser);
  1609. }
  1610.  
  1611. //add the users inside the user select element
  1612. function addUserToSelect(username) {
  1613. const selectUser = document.querySelector("[data-select-users]");
  1614. const option = document.createElement("option");
  1615. option.value = option.textContent = username;
  1616. selectUser.appendChild(option);
  1617. }
  1618.  
  1619. //remove the user from the select list
  1620. function removeUserFromSelect(username) {
  1621. const userOption = document.querySelector(`[data-select-users] [value="${username}"]`);
  1622. if (userOption) userOption.remove();
  1623. }
  1624.  
  1625. //load a custom post and custom profile message of a specific blacklisted user into the 2 text areas designated for these inputs
  1626. function loadSpecificMessages(e) {
  1627. const userCustomLastPost = settings.specificCustomLastPost[e.target.value];
  1628. const userCustomQuote = settings.specificCustomQuote[e.target.value];
  1629. const userCustomReply = settings.specificCustomReply[e.target.value];
  1630. const userCustomPost = settings.specificCustomPost[e.target.value];
  1631. const userCustomProfileMsg = settings.specificCustomProfileMsg[e.target.value];
  1632. const customLastPost = document.getElementById("specificCustomLastPost");
  1633. const customQuote = document.getElementById("specificCustomQuote");
  1634. const customReply = document.getElementById("specificCustomReply");
  1635. const customPost = document.getElementById("specificCustomPost");
  1636. const customProfileMsg = document.getElementById("specificCustomProfileMsg");
  1637. customLastPost.value = userCustomLastPost ? userCustomLastPost : "";
  1638. customQuote.value = userCustomQuote ? userCustomQuote : "";
  1639. customReply.value = userCustomReply ? userCustomReply : "";
  1640. customPost.value = userCustomPost ? userCustomPost : "";
  1641. customProfileMsg.value = userCustomProfileMsg ? userCustomProfileMsg : "";
  1642. }
  1643. })();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址