FA Content Filter

Filters user-defined content while browsing Furaffinity.

当前为 2025-02-19 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name FA Content Filter
  3. // @namespace fa-filter
  4. // @description Filters user-defined content while browsing Furaffinity.
  5. // @include *://www.furaffinity.net/*
  6. // @require https://ajax.googleapis.com/ajax/libs/jquery/3.7.1/jquery.min.js
  7. // @version 1.7.2
  8. // @grant GM.getValue
  9. // @grant GM.setValue
  10. // @grant GM.deleteValue
  11. // @grant GM.openInTab
  12. // @grant GM_addStyle
  13. // ==/UserScript==
  14.  
  15. // === WARNING ===
  16. // THE TAG FUNCTIONS ARE COMMENTED OUT IN ORDER TO PREVENT ACCIDENTAL DDoS DETECTION ON FURAFFINITY.
  17. this.$ = this.jQuery = jQuery.noConflict(true);
  18.  
  19. // Shitty workaround, but w/e
  20. async function main() {
  21.  
  22. GM_addStyle(`
  23. section.gallery figure { padding-bottom: 62px; }
  24. section.gallery figcaption p:nth-of-type(2) { white-space: normal; overflow: visible; }
  25. .faf-filter-table-row:hover { background-color: #888888; }
  26. .faf-hidden {
  27. background-color: #FFBBBB !important;
  28. color: #FF0000 !important;
  29. }
  30. .faf-hidden b figcaption {
  31. background-color: rgba(0, 0, 0, 0.95) !important;
  32. }
  33. .faf-hidden a:link, .faf-hidden a:visited, .faf-hidden a h3 { color: #FF2222 !important; }
  34. `);
  35.  
  36. const VERSION_NUMBER = 1.7;
  37. // === INITIALIZE USER ARRAY ===
  38. var userArray = JSON.parse(await GM.getValue('userList', '{}'));
  39. var wordFilter = JSON.parse(await GM.getValue('wordFilter', '[]'));
  40.  
  41. // === INITIALIZE USER SETTINGS ===
  42. var fafInDropdown = await GM.getValue('fafInDropdown', false);
  43.  
  44. // === GENERAL TEMPORARY VARIABLES ===
  45. var filterEnabled = {['subs']:true, ['shouts']:true, ['coms']:true, ['notifications']:true};
  46.  
  47. // === FILTER ===
  48. var parseSettings = function() {
  49. if (!(userArray instanceof Array)) {
  50. $.each(userArray, function(username, data) {
  51. if (data['subs'] === 1) { hideSubmissions(username); }
  52. if (data['shouts'] === 1) { hideShouts(username); }
  53. if (data['coms'] === 1) { hideComments(username); }
  54. if (data['notifications'] === 1) { hideNotifications(username); }
  55. });
  56. }
  57. };
  58.  
  59. var applyWordFilters = function() {
  60. const wordFilterExpTxt = '\\b(' + $.map(wordFilter, escapeRegex).join('|') + ')\\b';
  61. $('figure figcaption a[href^="/view"]').each(function() {
  62. var wordFilterExp = new RegExp(wordFilterExpTxt, 'i');
  63. if (wordFilterExp.test($(this).text())) {
  64. var mainElement = $(this).closest('figure');
  65. stylizeHidden(mainElement);
  66. mainElement.addClass('hidden-sub').hide();
  67.  
  68. if (!filterEnabled.subs) {
  69. mainElement.show();
  70. }
  71. }
  72. });
  73. };
  74.  
  75.  
  76. // === SAVE ===
  77. function writeSettings() {
  78. GM.setValue('userList', JSON.stringify(userArray));
  79. GM.setValue('wordFilter', JSON.stringify(wordFilter));
  80. GM.setValue('versionNumber', JSON.stringify(VERSION_NUMBER));
  81. }
  82.  
  83. // === FUNCTIONS ===
  84. // Hide user submissions
  85. function hideSubmissions(username) {
  86. var submissionBeta = $('figure.u-' + escapeRegex(username));
  87. var submissionFavesBeta = $('figure[data-user="u-' + escapeRegex(username) + '"]');
  88. var submissionInboxBeta = $('a[href="/user/' + username + '"]').closest('figure');
  89.  
  90. stylizeHidden(submissionBeta);
  91. stylizeHidden(submissionFavesBeta);
  92. stylizeHidden(submissionInboxBeta);
  93.  
  94. submissionBeta.addClass('faf-hidden hidden-sub').hide();
  95. submissionFavesBeta.addClass('faf-hidden hidden-sub').hide();
  96. submissionInboxBeta.find('input').prop('checked', true);
  97. submissionInboxBeta.addClass('faf-hidden hidden-sub').hide();
  98.  
  99. if (!filterEnabled['subs']) {
  100. submissionBeta.show();
  101. submissionInboxBeta.show();
  102. }
  103. }
  104.  
  105. function showSubmissions(username) {
  106. // Browse/Submissions
  107. var submission1 = $('b[id^="sid_"] a[href="/user/' + username + '/"]').closest('b');
  108. var submissionBeta = $('figure.u-' + escapeRegex(username));
  109. var submissionFavesBeta = $('figure[data-user="u-' + escapeRegex(username) + '"]');
  110. var submissionInboxBeta = $('a[href^="/user/' + username + '"]').closest('figure');
  111.  
  112. undoStylize(submission1);
  113. undoStylize(submissionBeta);
  114. undoStylize(submissionFavesBeta);
  115. undoStylize(submissionInboxBeta);
  116.  
  117. // Mark Submissions as Checked
  118. submission1.children('small').children('input').prop('checked', false);
  119. submission1.removeClass('faf-hidden hidden-sub').show();
  120. submissionBeta.removeClass('faf-hidden hidden-sub').show();
  121. submissionFavesBeta.removeClass('faf=hidden hidden-sub').show();
  122. submissionInboxBeta.removeClass('faf-hidden hidden-sub').show();
  123. submissionInboxBeta.find('input').prop('checked', false);
  124.  
  125. // Favorites/Front Page
  126. var submission2 = $('b[id^="sid_"] img[src$="#' + username + '"]').closest('b');
  127. undoStylize(submission2);
  128. submission2.removeClass('hidden-sub').show();
  129. }
  130.  
  131. // Hide user shouts
  132. function hideShouts(username) {
  133. // Classic
  134. var shout = $('table[id^="shout-"] td.alt1 img[alt="' + username + '"]').closest('table[id^="shout-"]');
  135. shout.addClass('faf-hidden hidden-shout').hide();
  136. stylizeHidden(shout.find('table'));
  137. shout.next('br').addClass('hidden-shout-br').hide();
  138.  
  139. // Beta
  140. var shoutBeta = $('.comment_container img.comment_useravatar[alt="' + username + '"]').closest('.comment_container');
  141. shoutBeta.addClass('faf-hidden hidden-shout').hide();
  142.  
  143. // We want to only highlight and check
  144. var shoutManageBeta = $('table[id^="shout-"] .comments-flex-item-icon img[alt="' + username +'"]').closest('table[id^="shout-"]');
  145. shoutManageBeta.addClass('faf-hidden hidden-shout');
  146. stylizeHidden(shoutManageBeta.find('.comments-userline-flex'));
  147. stylizeHidden(shoutManageBeta.find('.comment_text'));
  148. shoutManageBeta.find('input[type="checkbox"]').prop('checked', true);
  149. }
  150.  
  151. function showShouts(username) {
  152. // Beta
  153. var shoutBeta = $('.comment_container img.comment_useravatar[alt="' + username + '"]').closest('.comment_container');
  154. shoutBeta.removeClass('faf-hidden hidden-shout').show();
  155. }
  156.  
  157. // Hide user comments and threads
  158. function hideComments(username) {
  159. // Classic
  160. var comments = $('.container-comment td.icon img[alt="' + username + '"]').closest('.container-comment');
  161.  
  162. $(comments).each(function() {
  163. // Hide comment and get width
  164. if (!($(this).hasClass('hidden-comment'))) {
  165. var width = Number($(this).addClass('hidden-comment').hide().attr('width').slice(0,-1));
  166. var current = $(this).next('.container-comment');
  167.  
  168. // Iterate through comments until there's a width that is greater than or equal
  169. while (true) {
  170. if (current.length) {
  171. if (Number(current.attr('width').slice(0,-1)) < width) {
  172. current.addClass('hidden-comment').hide();
  173. current = current.next('.container-comment');
  174. } else {
  175. break;
  176. }
  177. } else {
  178. break;
  179. }
  180. }
  181. }
  182. });
  183.  
  184. // Beta
  185. var commentsBeta = $('.comments-list comment-container img[alt="' + username + '"], .comments-journal .comment_container .avatar-desktop img[alt="' + username + '"]').closest('.comment_container');
  186. $(commentsBeta).addClass('faf-hidden');
  187.  
  188. $(commentsBeta).each(function() {
  189. // Get width, then hide comment
  190. if (!($(this).hasClass('hidden-comment'))) {
  191. var width = $(this).width();
  192. var current = $(this).next('.comment_container');
  193.  
  194. $(this).addClass('hidden-comment').hide();
  195.  
  196. // Iterate through the comments until there's a width that is greater than or equal
  197. while (true) {
  198. if (current.length) {
  199. if (current.width() < width) {
  200. current.addClass('hidden-comment').hide();
  201. current = current.next('.comment_container');
  202. } else {
  203. break;
  204. }
  205. } else {
  206. break;
  207. }
  208. }
  209. }
  210. });
  211. }
  212.  
  213. function showComments(username) {
  214. var comments = $('.comments-list comment-container img[alt="' + username + '"], .comments-journal .comment_container .avatar-desktop img[alt="' + username + '"]').closest('.comment_container');
  215. $(comments).removeClass('faf-hidden');
  216.  
  217. $(comments).each(function() {
  218. if ($(this).hasClass('hidden-comment')) {
  219. var width = $(this).width();
  220. var current = $(this).next('.comment_container');
  221.  
  222. $(this).removeClass('hidden-comment').show();
  223.  
  224. // Iterate through the comments until there's a width that is greater than or equal
  225. while (true) {
  226. if (current.length) {
  227. if (current.width() < width) {
  228. current.removeClass('hidden-comment').show();
  229. current = current.next('.comment_container');
  230. } else {
  231. break;
  232. }
  233. } else {
  234. break;
  235. }
  236. }
  237. }
  238. });
  239. }
  240.  
  241. // Hide user notifications
  242. function hideNotifications(username) {
  243. var notification = $('.message-stream a[href="/user/' + username + '/"]').closest('li');
  244. notification.addClass('hidden-notification faf-hidden').hide();
  245. stylizeHidden(notification);
  246. notification.children('input').prop('checked', true);
  247.  
  248. // Classic only
  249. notification.children('table').children('tbody').children('tr').children('td').children('.checkbox').children('input').prop('checked', true);
  250. }
  251.  
  252. function stylizeHidden(item) {
  253. $(item).css({'cssText': 'background-color: #FFBBBB !important'});
  254. $(item).css('color', '#FF0000');
  255. $('a:link', item).css('color', '#FF0000');
  256. $('a:visited', item).css('color', '#FF0000');
  257. }
  258.  
  259. function undoStylize(item) {
  260. $(item).css('background-color', '');
  261. $(item).css('color', '');
  262. $('a:link', item).css('color', '');
  263. $('a:visited', item).css('color', '');
  264. }
  265.  
  266. // === UI ===
  267. // == Filter Toggle ==
  268. // Submissions
  269. function filtersSubs() {
  270. // Remove all pre-existing UI for soft-refresh
  271. $('[id="faf-toggle-subs"]').remove();
  272. $('.faf-remove-user-external').remove();
  273. $('.faf-add-user-external').remove();
  274.  
  275. if ($('.hidden-sub').length > 0) {
  276. if (isBeta()) {
  277. // Beta
  278. $display = '<li class="lileft"><a class="top-heading" id="faf-toggle-subs" href="#!"><div class="sprite-nuke menu-space-saver hideonmobile"></div>Toggle Filtered Submissions (' + $('.hidden-sub').length + ')</a></li>';
  279. $('.lileft').last().after($display);
  280. $searchDisplay = '<div class="alignright" style="padding-top: 1rem;"><button class="button standard" type="button" id="faf-toggle-subs">Toggle Filtered Submissions (' + $('.hidden-sub').length + ')</button></div>';
  281. $('.search-flex-container .alignright').first().after($searchDisplay);
  282. } else {
  283. // Classic
  284. $display = '<li><a id="faf-toggle-subs" href="#!">⚠ Toggle Filtered Submissions (' + $('.hidden-sub').length + ')</a></li>';
  285. $('.search-box-container').first().before($display);
  286. }
  287. } else {
  288. filterEnabled['subs'] = true;
  289. }
  290.  
  291. $('figure').each(function() {
  292. var username = $(this).attr('class').match('u-([^\\s]+)');
  293. if (!username) {
  294. username = $(this).attr('data-user').match('u-([^\\s]+)');
  295. }
  296. if (username) {
  297. username = username[1];
  298. if (username in userArray && userArray[username]['subs'] === 1) {
  299. $(this).find('figcaption p:nth-of-type(2)').append('<a style="color: #FF5555!important;" class="faf-remove-user-external faf-ex-subs" id="faf-' + username + '" href="#!" title="Remove ' + username + ' from filter">[Unfilter]</a>');
  300. } else {
  301. $(this).find('figcaption p:nth-of-type(2)').append('<a style="color: #FF5555!important;" class="faf-add-user-external faf-ex-subs" id="faf-' + username + '" href="#!" title="Add ' + username + ' to filter">[Filter]</a>');
  302. }
  303. }
  304. });
  305. }
  306.  
  307. // Followed Submissions
  308. function filtersSubsFollow() {
  309. if ($('.hidden-sub').length > 0) {
  310. if (isBeta()) {
  311. $display = '<div class="button-nav-item"><button class="button mobile-button" id="faf-toggle-subs" type="button">Toggle Filtered Submissions (' + $('.hidden-sub').length + ')</button></div>';
  312. $('.actions').css('max-width', '700px');
  313. } else {
  314. $display = '<input id="faf-toggle-subs" class="button" type="button" value="Toggle Filtered Submissions (' + $('.hidden-sub').length + ')"></input>';
  315. }
  316. $('.actions').append($display);
  317. }
  318. }
  319.  
  320. // Shouts
  321. function filtersShouts() {
  322. // Remove all pre-existing UI for soft-refresh
  323. $('[id="faf-toggle-shouts"]').remove();
  324. $('.faf-remove-user-external').parent().remove();
  325. $('.faf-add-user-external').parent().remove();
  326.  
  327. if ($('.hidden-shout').length > 0) {
  328. $display = '<input id="faf-toggle-shouts" class="button" type="button" value="Toggle Filtered Shouts (' + $('.hidden-shout').length + ')"></input>';
  329. // Classic
  330. $('table[id^="shout-"]').first().prevAll('table.maintable:first').append($display);
  331. // Beta
  332. $($display).insertAfter($('.shout-post-form'));
  333. }
  334.  
  335. // Beta
  336. $('.userpage-layout .comment_container').each(function() {
  337. var username = $(this).find('img.comment_useravatar').attr('alt');
  338. if (username) {
  339. if (username in userArray && userArray[username]['shouts'] === 1) {
  340. $(this).find('comment-footer').prepend('<span><a style="color: #FF5555!important;" class="faf-remove-user-external faf-ex-shouts" id="faf-' + username + '" href="#!" title="Show ' + username + '\'s shouts">[Unfilter]</a></span>');
  341. } else {
  342. $(this).find('comment-footer').prepend('<span><a style="color: #FF5555!important;" class="faf-add-user-external faf-ex-shouts" id="faf-' + username + '" href="#!" title="Hide ' + username + '\'s shouts">[Filter]</a></span>');
  343. }
  344. }
  345. })
  346. }
  347.  
  348. // Shouts (Controls, Beta Only)
  349. function filtersShoutsControl() {
  350. if ($('.hidden-shout').length > 0) {
  351. $display = '<button id="faf-toggle-shouts" class="button mobile-button" type="button" value="Toggle Filtered Shouts (' + $('.hidden-shout').length + ')">Toggle Filtered Shouts (' + $('.hidden-shout').length + ')</button>';
  352. $('.section-divider').last().append($display);
  353. $('.hidden-shout input').prop('checked', true);
  354. }
  355. }
  356.  
  357. // Comments
  358. function filtersComments() {
  359. // Remove all pre-existing UI for soft-refresh
  360. $('[id="faf-toggle-comments"]').remove();
  361. $('.faf-remove-user-external').parent().remove();
  362. $('.faf-add-user-external').parent().remove();
  363.  
  364. if ($('.hidden-comment').length > 0) {
  365. const display = '<tr><td><input id="faf-toggle-comments" class="button" type="button" value="Toggle Filtered Comments (' + $('.hidden-comment').length + ')"></input></td></tr>';
  366. if (isBeta()) {
  367. // Beta
  368. $(display).insertBefore('#comments-submission, #comments-journal');
  369. } else {
  370. // Classic
  371. $display = '<tr><td><input id="faf-toggle-comments" class="button" type="button" value="Toggle Filtered Comments (' + $('.hidden-comment').length + ')"></input></td></tr>';
  372. $('#comments-submission').parent().parent().before($display);
  373. }
  374. }
  375.  
  376. // Beta
  377. $('.comments-list .comment_container').each(function() {
  378. var username = $(this).find('img.comment_useravatar').attr('alt');
  379. if (username) {
  380. if (username in userArray && userArray[username]['coms'] === 1) {
  381. $(this).find('comment-footer').prepend('<span><a style="color: #FF5555!important; position: relative; bottom: -8px;" class="faf-remove-user-external faf-ex-coms" id="faf-' + username + '" href="#!" title="Show ' + username + '\'s comments">[Unfilter]</a></span>');
  382. } else {
  383. $(this).find('comment-footer').prepend('<span><a style="color: #FF5555!important; position: relative; bottom: -8px;" class="faf-add-user-external faf-ex-coms" id="faf-' + username + '" href="#!" title="Hide ' + username + '\'s comments">[Filter]</a></span>');
  384. }
  385. }
  386. });
  387. // Classic
  388. $('#comments-submission table').each(function() {
  389. var username = $(this).find('img.avatar').attr('alt');
  390. if (username) {
  391. if (username in userArray && userArray[username]['coms'] === 1) {
  392. $(this).find('.replyto-name').append('<span>&nbsp;<a style="color: #FF5555!important;" class="faf-remove-user-external faf-ex-coms" id="faf-' + username + '" href="#!" title="Show ' + username + '\'s comments">[Unfilter]</a></span>');
  393. } else {
  394. $(this).find('.replyto-name').append('<span>&nbsp;<a style="color: #FF5555!important;" class="faf-add-user-external faf-ex-coms" id="faf-' + username + '" href="#!" title="Hide ' + username + '\'s comments">[Filter]</a></span>');
  395. }
  396. }
  397. })
  398. }
  399.  
  400. // Notifications
  401. function filtersNotifications() {
  402. if ($('.hidden-notification').length > 0) {
  403. $display = '<input id="faf-toggle-notifications" class="button" type="button" value="Toggle Filtered Notifications (' + $('.hidden-notification').length + ')"></input>';
  404. $('.global-controls').append($display);
  405. $('.global_controls').append($display);
  406.  
  407. // = Notification Count =
  408. // Classic
  409. if ($('fieldset[id^="messages-watches"] .hidden-notification').length > 0)
  410. $('fieldset[id^="messages-watches"] h3').append(' (' + $('fieldset[id^="messages-watches"] .hidden-notification').length + ' filtered)');
  411. if ($('fieldset[id^="messages-comments-submission"] .hidden-notification').length > 0)
  412. $('fieldset[id^="messages-comments-submission"] h3').append(' (' + $('fieldset[id^="messages-comments-submission"] .hidden-notification').length + ' filtered)');
  413. if ($('fieldset[id^="messages-shouts"] .hidden-notification').length > 0)
  414. $('fieldset[id^="messages-shouts"] h3').append(' (' + $('fieldset[id^="messages-shouts"] .hidden-notification').length + ' filtered)');
  415. if ($('fieldset[id^="messages-favorites"] .hidden-notification').length > 0)
  416. $('fieldset[id^="messages-favorites"] h3').append(' (' + $('fieldset[id^="messages-favorites"] .hidden-notification').length + ' filtered)');
  417.  
  418. // Beta
  419. if ($('div[id^="messages-watches"] .hidden-notification').length > 0)
  420. $('div[id^="messages-watches"] h2').append(' (' + $('div[id^="messages-watches"] .hidden-notification').length + ' filtered)');
  421. if ($('div[id^="messages-comments-submission"] .hidden-notification').length > 0)
  422. $('div[id^="messages-comments-submission"] h2').append(' (' + $('div[id^="messages-comments-submission"] .hidden-notification').length + ' filtered)');
  423. if ($('div[id^="messages-shouts"] .hidden-notification').length > 0)
  424. $('div[id^="messages-shouts"] h2').append(' (' + $('div[id^="messages-shouts"] .hidden-notification').length + ' filtered)');
  425. if ($('div[id^="messages-favorites"] .hidden-notification').length > 0)
  426. $('div[id^="messages-favorites"] h2').append(' (' + $('div[id^="messages-favorites"] .hidden-notification').length + ' filtered)');
  427. if ($('div[id^="messages-journals"] .hidden-notification').length > 0)
  428. $('div[id^="messages-journals"] h2').append(' (' + $('div[id^="messages-journals"] .hidden-notification').length + ' filtered)');
  429. }
  430. }
  431.  
  432. // == Buttons ==
  433. // Show/Hide Submissions
  434. $(document.body).on('click', '#faf-toggle-subs', function() {
  435. $('.hidden-sub').toggle();
  436. filterEnabled['subs'] = !filterEnabled['subs'];
  437. });
  438.  
  439. // Show/Hide Shouts
  440. $(document.body).on('click', '#faf-toggle-shouts', function() {
  441. $('.hidden-shout').toggle();
  442. $('.hidden-shout-br').toggle();
  443. filterEnabled['shouts'] = !filterEnabled['shouts'];
  444. });
  445.  
  446. // Show/Hide Comments
  447. $(document.body).on('click', '#faf-toggle-comments', function() {
  448. $('.hidden-comment').toggle();
  449. filterEnabled['coms'] = !filterEnabled['coms'];
  450. });
  451.  
  452. // Show/Hide Notifications
  453. $(document.body).on('click', '#faf-toggle-notifications', function() {
  454. $('.hidden-notification').toggle();
  455. filterEnabled['notifications'] = !filterEnabled['notifications'];
  456. });
  457.  
  458. // == External Filters ==
  459. // Add submission filter outside of settings
  460. $(document.body).on('click', '.faf-add-user-external', function() {
  461. var addUser = $(this).attr('id').match('faf-(.*)')[1];
  462. var filterType = $(this).attr('class').split(/\s+/).find((c) => c.startsWith('faf-ex-')).substring(7);
  463.  
  464. // Add to array
  465. if (!(addUser in userArray)) {
  466. userArray[addUser] = {
  467. 'subs': filterType === 'subs' ? 1 : 0,
  468. 'shouts': filterType === 'shouts' ? 1 : 0,
  469. 'coms': filterType === 'coms' ? 1 : 0,
  470. 'notifications': filterType === 'notifications' ? 1 : 0
  471. };
  472. } else {
  473. switch(filterType) {
  474. case 'subs':
  475. userArray[addUser]['subs'] = 1;
  476. break;
  477. case 'coms':
  478. userArray[addUser]['coms'] = 1;
  479. break;
  480. case 'shouts':
  481. userArray[addUser]['shouts'] = 1;
  482. break;
  483. case 'notifications':
  484. userArray[addUser]['notifications'] = 1;
  485. break;
  486. }
  487. }
  488.  
  489. // Hide, replace link, and save
  490. switch(filterType) {
  491. case 'subs':
  492. hideSubmissions(addUser);
  493. filtersSubs();
  494. break;
  495. case 'coms':
  496. hideComments(addUser);
  497. filtersComments();
  498. break;
  499. case 'shouts':
  500. hideShouts(addUser);
  501. filtersShouts();
  502. break;
  503. case 'notifications':
  504. userArray[addUser]['notifications'] = 1;
  505. filtersNotifications();
  506. break;
  507. }
  508. writeSettings();
  509. });
  510.  
  511. // Remove submission filter outside of settings
  512. $(document.body).on('click', '.faf-remove-user-external', function() {
  513. var removeUser = $(this).attr('id').match('faf-(.*)')[1];
  514. var filterType = $(this).attr('class').split(/\s+/).find((c) => c.startsWith('faf-ex-')).substring(7);
  515.  
  516. // Remove from array, show, replace link
  517. if (removeUser in userArray) {
  518. switch(filterType) {
  519. case 'subs':
  520. userArray[removeUser]['subs'] = 0;
  521. showSubmissions(removeUser);
  522. filtersSubs();
  523. break;
  524. case 'coms':
  525. userArray[removeUser]['coms'] = 0;
  526. showComments(removeUser);
  527. filtersComments();
  528. break;
  529. case 'shouts':
  530. userArray[removeUser]['shouts'] = 0;
  531. showShouts(removeUser);
  532. filtersShouts();
  533. break;
  534. case 'notifications':
  535. userArray[removeUser]['notifications'] = 0;
  536. filtersNotifications();
  537. break;
  538. }
  539. }
  540.  
  541. // Save
  542. writeSettings();
  543. });
  544.  
  545. // == User Settings ==
  546. function displaySettings() {
  547. // Navbar link
  548. if (fafInDropdown) {
  549. $('<h3><a target="_blank" href="/controls/site-settings#fa-filter" style="font-weight: bold;">FA Filter</a></h3>').prependTo($('.navhideonmobile .submenu-trigger').last().find('.column'));
  550. } else {
  551. $('<li><a target="_blank" href="/controls/site-settings#fa-filter" style="font-weight: bold;">FA Filter</a></li>').insertAfter($('li.message-bar-desktop'));
  552. }
  553. $('<h2><a target="_blank" href="/controls/site-settings#fa-filter">FA Filter</a></h2>').insertAfter($('.mobile-menu .nav-ac-container').last());
  554. // Navbar link (Classic)
  555. $('<li class="noblock"><a target="_blank" href="/controls/site-settings#fa-filter">FA Filter</a></li>').insertAfter($('li#sfw-toggle'));
  556.  
  557. if (window.location.pathname.lastIndexOf('/controls/site-settings', 0) === 0) {
  558. if (isBeta()) {
  559. // Beta HTML Code
  560. var settingsDisplay = `<section>
  561. <div class="section-header">
  562. <h2 id="fa-filter">FA Filter</h2>
  563. </div>
  564. <div class="section-body">
  565. <div class="control-panel-item-container">
  566. <div class="control-panel-item-name"><h4>Filter Location</h4></div>
  567. <div class="control-panel-item-description">
  568. <p>Choose where you would like the location of the FA Filter link. Choice autosaves and changes will be shown on refresh.</p>
  569. </div>
  570. <div class="control-panel-item-options">
  571. <input id="faf-button-location-bar" type="radio" name="faf-button-location" value="false">
  572. <label for="faf-button-location-bar">Header</label>
  573. <br>
  574. <input id="faf-button-location-drop" type="radio" name="faf-button-location" value="true">
  575. <label for="faf-button-location-drop">Settings List (Gear icon)</label>
  576. </div>
  577. </div>
  578. <div class="control-panel-item-container">
  579. <div class="control-panel-item-name"><h4>Add a User</h4></div>
  580. <div class="control-panel-item-description">
  581. <p>Tired of seeing somebody\'s contributions on the site? Add them to your filter list!<br/>Enter in the username of the person you want to filter, which is the username that would appear after "furaffinity.net/user/".</p>
  582. </div>
  583. <div class="control-panel-item-options">
  584. <input class="textbox" type="text" id="faf-add-username" maxlength="50"></input>&nbsp;&nbsp;&nbsp;<input id="faf-add" class="button" type="button" value="Add" />
  585. </div>
  586. </div>
  587. <div class="control-panel-item-container">
  588. <div class="control-panel-item-name"><h4>Word Filter</h4></div>
  589. <div class="control-panel-item-description">
  590. <p>Block submissions with specific words or phrases in their titles from showing up while browsing. One entry per line.</p>
  591. </div>
  592. <div class="control-panel-item-options">
  593. <textarea id="faf-wordfilter" name="faf-wordfilter" rows="4" style="min-height:110px" class="textbox textbox100 textareasize"></textarea>
  594. </div>
  595. </div>
  596. <div class="control-panel-item-container">
  597. <div class="control-panel-item-name"><h4>Validate Filters</h4></div>
  598. <div class="control-panel-item-description">
  599. <p>This double-checks to make sure that your filtered usernames are correct and, optionally, removes users that don\'t have any enabled filters. This automatically saves the list.</p>
  600. <strong class="highlight">Validate Only</strong> - Simply validates that the usernames that have been entered are correctly formatted.<br/>
  601. <strong class="highlight">Validate and Remove Unused</strong> - Does the same as above and also removes any users with zero filters in the list.<br/>
  602. </div>
  603. <div class="control-panel-item-options">
  604. <select name="faf-validate-options" id="select-faf-validate-options" class="styled">
  605. <option value="v" selected="selected">Vaildate Only</option>
  606. <option value="vr">Validate and Remove Unused</option>
  607. </select><br/><input id="faf-validate" class="button" type="button" value="Apply" style="margin-top: 10px;"/><br/>
  608. <span class="faf-validate-status" style="font-weight: bold; color: #009900; display: none;">Validated! 0 user(s) have been modified or removed.</span>
  609. </div>
  610. </div>
  611. <div class="control-panel-item-container">
  612. <div class="control-panel-item-name"><h4>Export/Import Data</h4></div>
  613. <div class="control-panel-item-description">
  614. <p>Export your filters or import them from somewhere else.</p>
  615. </div>
  616. <div class="control-panel-item-options">
  617. <input class="textbox" type="text" id="faf-raw-port" style="margin-bottom: 10px; width: 100%;" placeholder="Paste your filter data here..."></input><br/>
  618. <input id="faf-port-clear" class="button" type="button" value="Clear" />&nbsp;&nbsp;&nbsp;<input id="faf-import" class="button" type="button" value="Import" />&nbsp;&nbsp;&nbsp;<input id="faf-export" class="button" type="button" value="Export" /><br/>
  619. <span class="faf-import-status" style="font-weight: bold; color: #FF6666; display: none;">Invalid data!</span>
  620. </div>
  621. </div>
  622. <div class="section-options">
  623. <span class="faf-update-status" style="font-weight: bold; color: #006600; display: none;">Update successful!</span>&nbsp;&nbsp;<input class="button mobile-button faf-update-btn" id="faf-update-top" type="button" value="Apply Filters (FA Filter)">
  624. </div>
  625. <br/>
  626. <div class="activity-periods-list">
  627. <table class="container faf-list faf-list-beta" width="100%" cellspacing="0" cellpadding="0" border="0" style="padding:0 15px 10px 15px">
  628. <tbody>
  629. <tr>
  630. <td class="p10t p5r p5b"><h3>Username</h3></td>
  631. <td class="p10t p5r p5b" width="200px"><h3>Submissions</h3></td>
  632. <td class="p10t p5r p5b" width="200px"><h3>Shouts</h3></td>
  633. <td class="p10t p5r p5b" width="200px"><h3>Comments</h3></td>
  634. <td class="p10t p5r p5b" width="200px"><h3>Notifications</h3></td>
  635. </tr>
  636. </tbody>
  637. </table>
  638. </div>
  639. <div class="section-options">
  640. <span class="faf-update-status" style="font-weight: bold; color: #006600; display: none;">Update successful!</span>&nbsp;&nbsp;<input class="button mobile-button faf-update-btn" id="faf-update-bottom" type="button" value="Apply Filters (FA Filter)">
  641. </div>
  642. </div>
  643. </section>`;
  644. $(settingsDisplay).insertBefore($('section').last());
  645. } else {
  646. // Classic HTML Code
  647. var classicSettingsDisplay = `<table id="fa-filter" cellpadding="0" cellspacing="1" border="0" class="section maintable"><tbody>
  648. <tr><td height="22" class="cat links">&nbsp;<strong>FA Filter</strong></td></tr>
  649. <tr><td class="alt1 addpad ucp-site-settings" align="center">
  650. <table cellspacing="1" cellpadding="0" border="0"><tbody>
  651. <tr>
  652. <th><strong>Add a User</strong></th>
  653. <td><input type="text" id="faf-add-username" maxlength="50"></input>&nbsp;<input id="faf-add" class="button" type="button" value="Add User"></td>
  654. <td class="option-description">
  655. <h3>Hide a user\'s contributions to the site.</h3>
  656. <p>Tired of seeing somebody\'s contributions on the site? Add them to your filter list!<br>Note: Enter in the username of the person you want to filter, which is the username that would appear after "furaffinity.net/user/".</p>
  657. </td>
  658. </tr>
  659. <tr>
  660. <th><strong>Validate Filters</strong></th>
  661. <td>
  662. <select name="faf-validate-options" id="select-faf-validate-options" class="styled">
  663. <option value="v" selected="selected">Vaildate Filters Only</option>
  664. <option value="vr">Validate and Remove Unused Filters</option>
  665. </select>&nbsp;<input id="faf-validate" class="button" type="button" value="Apply" /><br/>
  666. <span class="faf-validate-status" style="font-weight: bold; color: #009900; display: none;">Validated! 0 user(s) have been modified or removed.</span>
  667. </td>
  668. <td class="option-description">
  669. <h3>Clean up everything and revalidate filtered usernames.</h3>
  670. <p>This double-checks to make sure that your filtered usernames are correct and, optionally, removes users that don\'t have any enabled filters.<br/><strong>Note:</strong> This automatically saves the list.</p>
  671. </td>
  672. </tr>
  673. <tr>
  674. <th><strong>Export/Import Data</strong></th>
  675. <td>
  676. <input type="text" id="faf-raw-port" style="margin-bottom: 10px;" placeholder="Paste your filter data here..."></input><br/>
  677. <input id="faf-port-clear" class="button" type="button" value="Clear" />&nbsp;&nbsp;&nbsp;<input id="faf-import" class="button" type="button" value="Import" />&nbsp;&nbsp;&nbsp;<input id="faf-export" class="button" type="button" value="Export" /><br/>
  678. <span class="faf-import-status" style="font-weight: bold; color: #FF6666; display: none;">Invalid data!</span>
  679. </td>
  680. <td class="option-description">
  681. <h3>Grab your filters to send to another browser.</h3>
  682. <p>Export your filters or import them from somewhere else.</p>
  683. </td>
  684. <tr>
  685. <th class="noborder" style="vertical-align: text-top;"><strong style="position: relative; top: 25px;">Modify Filters</strong></th>
  686. <td class="noborder">
  687. <table cellspacing="0" cellpadding="0" border="0" class="faf-list faf-list-classic">
  688. <tr><th><strong>Username</strong></th><th><strong>Submissions</strong></th><th><strong>Shouts</strong></th><th><strong>Comments</strong></th><th><strong>Notifications</strong></th></tr>
  689. </table>
  690. <br><br><input class="button faf-update-btn" id="faf-update" type="button" value="Apply Filters (FA Filter)"> <span class="faf-update-status" style="font-weight: bold; color: #006600; display: none;">Update successful!</span>
  691. </td>
  692. <td class="option-description noborder">
  693. <h3>Choose what items you don\'t want to see.</h3>
  694. <p>If you still want to see some of the things that a user contributes, you can control that here.</p>
  695. </td>
  696. </tr>
  697. </tbody></table>
  698. </td></tr>
  699. </tbody></table>`;
  700. $('form').last().append(classicSettingsDisplay);
  701. }
  702.  
  703. // Populate word filter
  704. $('#faf-wordfilter').val(wordFilter.join('\n'));
  705. // Populate list
  706. $.each(userArray, function(username, data) {
  707. addFilterUser(username, data);
  708. });
  709. // Populate settings
  710. if (fafInDropdown) {
  711. $('#faf-button-location-drop').prop('checked', true);
  712. } else {
  713. $('#faf-button-location-bar').prop('checked', true);
  714. }
  715. }
  716. }
  717.  
  718. // Display user in the filter table
  719. function addFilterUser(username, data) {
  720. if (isBeta()) {
  721. // Beta
  722. var rowBeta = '<tr class="faf-filter-table-row" id="filter-' + username + '"><td class="p5r" valign="middle" width="auto"><a class="faf-remove" id="faf-rm-' + username + '" href="#!">[x]</a> ' + username + '</td>';
  723. if (data['subs'] === 1) { rowBeta += '<td class="p5r" valign="middle" width="auto"><input id="faf-check-subs-' + username + '" type="checkbox" checked="checked"></td>'; } else { rowBeta += '<td class="p5r" valign="middle" width="auto"><input id="faf-check-subs-' + username + '" type="checkbox"></td>'; }
  724. if (data['shouts'] === 1) { rowBeta += '<td class="p5r" valign="middle" width="auto"><input id="faf-check-shouts-' + username + '" type="checkbox" checked="checked"></td>'; } else { rowBeta += '<td class="p5r" valign="middle" width="auto"><input id="faf-check-shouts-' + username + '" type="checkbox"></td>'; }
  725. if (data['coms'] === 1) { rowBeta += '<td class="p5r" valign="middle" width="auto"><input id="faf-check-coms-' + username + '" type="checkbox" checked="checked"></td>'; } else { rowBeta += '<td class="p5r" valign="middle" width="auto"><input id="faf-check-coms-' + username + '" type="checkbox"></td>'; }
  726. if (data['notifications'] === 1) { rowBeta += '<td class="p5r" valign="middle" width="auto"><input id="faf-check-notifications-' + username + '" type="checkbox" checked="checked"></td>'; } else { rowBeta += '<td class="p5r" valign="middle" width="auto"><input id="faf-check-notifications-' + username + '" type="checkbox"></td>'; }
  727.  
  728. rowBeta += '</tr>';
  729.  
  730. $('table.faf-list tr:last').after(rowBeta);
  731. } else {
  732. // Classic
  733. var row = '<tr class="checked" id="filter-' + username + '"><td class="noborder"><a class="faf-remove fonthighlight" id="faf-rm-' + username + '" href="#!">[x]</a> ' + username + '</td>';
  734. if (data['subs'] === 1) { row += '<td class="noborder"><input id="faf-check-subs-' + username + '" type="checkbox" checked="checked"></td>'; } else { row += '<td class="noborder"><input id="faf-check-subs-' + username + '" type="checkbox"></td>'; }
  735. if (data['shouts'] === 1) { row += '<td class="noborder"><input id="faf-check-shouts-' + username + '" type="checkbox" checked="checked"></td>'; } else { row += '<td class="noborder"><input id="faf-check-shouts-' + username + '" type="checkbox"></td>'; }
  736. if (data['coms'] === 1) { row += '<td class="noborder"><input id="faf-check-coms-' + username + '" type="checkbox" checked="checked"></td>'; } else { row += '<td class="noborder"><input id="faf-check-coms-' + username + '" type="checkbox"></td>'; }
  737. if (data['notifications'] === 1) { row += '<td class="noborder"><input id="faf-check-notifications-' + username + '" type="checkbox" checked="checked"></td>'; } else { row += '<td class="noborder"><input id="faf-check-notifications-' + username + '" type="checkbox"></td>'; }
  738.  
  739. row += '</tr>';
  740.  
  741. $('table.faf-list tr:last').after(row);
  742. }
  743. }
  744.  
  745. // Add
  746. $(document.body).on('click', '#faf-add', function() {
  747. var username = $.trim($('#faf-add-username').val());
  748. $('#faf-add-username').val('');
  749. if (username !== '') {
  750. username = username.toLowerCase();
  751. username = username.replace(/[_]/g, '');
  752. if (!(username in userArray)) {
  753. userArray[username] = {'subs':1, 'shouts':1, 'coms':1, 'notifications':1};
  754. addFilterUser(username, userArray[username]);
  755. }
  756. }
  757. });
  758.  
  759. // Remove
  760. $(document.body).on('click', 'a.faf-remove', function(event) {
  761. var username = event.target.id.substr(7);
  762. delete userArray[username];
  763.  
  764. var userEsc = escapeRegex(username);
  765.  
  766. $('table.faf-list tr#filter-' + userEsc).remove();
  767. });
  768.  
  769. // Update
  770. $(document.body).on('click', '.faf-update-btn', function() {
  771. // User Filters
  772. $('.faf-list tr[id^="filter-"]').each(function() {
  773. var username = this.id.substr(7);
  774. var vals = {'subs':0, 'shouts':0, 'coms':0, 'notifications':0};
  775. var userEsc = escapeRegex(username);
  776.  
  777. // Check checkboxes
  778. if ($('#faf-check-subs-' + userEsc).is(':checked')) { vals['subs'] = 1; }
  779. if ($('#faf-check-shouts-' + userEsc).is(':checked')) { vals['shouts'] = 1; }
  780. if ($('#faf-check-coms-' + userEsc).is(':checked')) { vals['coms'] = 1; }
  781. if ($('#faf-check-notifications-' + userEsc).is(':checked')) { vals['notifications'] = 1; }
  782.  
  783. userArray[username] = vals;
  784. });
  785.  
  786. // Word Filters
  787. var rawWordFilter = $('#faf-wordfilter').val().trim();
  788. wordFilter = [];
  789. $.each(rawWordFilter.split('\n'), function() {
  790. if ($.trim(this)) {
  791. wordFilter.push($.trim(this).toLowerCase());
  792. }
  793. });
  794.  
  795. // Save
  796. writeSettings();
  797.  
  798. // Display message
  799. $('.faf-update-status').fadeIn('slow');
  800. setTimeout(function() {
  801. $('.faf-update-status').fadeOut('slow');
  802. }, 5000);
  803. });
  804.  
  805. // Validate
  806. $(document.body).on('click', '#faf-validate', function() {
  807. var modCount = 0;
  808. // Validate
  809. $.each(userArray, function(username, data) {
  810. var tempUsername = username;
  811. tempUsername = tempUsername.trim();
  812. if (tempUsername !== '') {
  813. tempUsername = tempUsername.toLowerCase();
  814. tempUsername = tempUsername.replace(/[_ ]/g, '');
  815. if (tempUsername !== username) {
  816. userArray[tempUsername] = data;
  817. delete userArray[username];
  818. $('tr[id="filter-' + username + '"]').remove();
  819. modCount++;
  820. }
  821. }
  822. });
  823.  
  824. // Remove empty
  825. if ($('#select-faf-validate-options').val() === 'vr') {
  826. $.each(userArray, function(username, data) {
  827. var isEmpty = true;
  828. $.each(data, function(entity, value) {
  829. if (value === 1) {
  830. isEmpty = false;
  831. }
  832. });
  833. if (isEmpty) {
  834. delete userArray[username];
  835. $('tr[id="filter-' + username + '"]').remove();
  836. modCount++;
  837. }
  838. });
  839. }
  840.  
  841. // Save
  842. writeSettings();
  843.  
  844. // Display message
  845. $('.faf-validate-status').text('Validated! ' + modCount + ' user(s) have been modified or removed.');
  846. $('.faf-validate-status').fadeIn('slow');
  847. setTimeout(function() {
  848. $('.faf-validate-status').fadeOut('slow');
  849. }, 5000);
  850. });
  851.  
  852. // IMPORT/EXPORT
  853. // Clear
  854. $(document.body).on('click', '#faf-port-clear', function() {
  855. $('#faf-raw-port').val('');
  856. });
  857.  
  858. // Export
  859. $(document.body).on('click', '#faf-export', function() {
  860. var exportVal = {'versionNumber': VERSION_NUMBER, 'wordFilter': wordFilter, 'userList': userArray};
  861. $('#faf-raw-port').val(JSON.stringify(exportVal));
  862. });
  863.  
  864. // Import
  865. $(document.body).on('click', '#faf-import', async function() {
  866. if ($.trim($('#faf-raw-port').val()).length > 0) {
  867. var importJson = null;
  868. try {
  869. importJson = JSON.parse($('#faf-raw-port').val());
  870.  
  871. await validateAndImportData(importJson);
  872.  
  873. writeSettings();
  874.  
  875. $('.faf-import-status').css('color', '#006600').text('Import successful!');
  876. $('.faf-import-status').fadeIn('slow');
  877. setTimeout(function() {
  878. $('.faf-import-status').fadeOut('slow');
  879. }, 5000);
  880. } catch (e) {
  881. $('.faf-import-status').css('color', '#FF6666').text('Invalid data!');
  882. $('.faf-import-status').fadeIn('slow');
  883. setTimeout(function() {
  884. $('.faf-import-status').fadeOut('slow');
  885. }, 5000);
  886. return;
  887. }
  888. }
  889. });
  890.  
  891. // SETTINGS
  892. $(document.body).on('change', 'input[type=radio][name=faf-button-location]', async function() {
  893. fafInDropdown = this.value === "true";
  894. GM.setValue('fafInDropdown', fafInDropdown);
  895. });
  896.  
  897. // === UTILITIES ===
  898. function escapeRegex(str) {
  899. return str.replace(/[-\/\\^$*+?.()|[\]{}~]/g, '\\$&');
  900. }
  901.  
  902. function isBeta() {
  903. return $('body').attr('data-static-path') === '/themes/beta';
  904. }
  905.  
  906. // IMPORT FUNCTIONALITY - UPDATE WITH EACH MAJOR UPDATE
  907. async function validateAndImportData(jsonData) {
  908. switch (jsonData.versionNumber) {
  909. case '1.7':
  910. // Version 1.7 - User data and title filter
  911. wordFilter = jsonData.wordFilter;
  912. case '1.6':
  913. default:
  914. // Version 1.6 - User data only
  915. $.each(jsonData.userList, function(user, filters) {
  916. // Validate each user and filter
  917. $.each(filters, function(type, value) {
  918. if (value != 0 && value != 1) {
  919. throw "Invalid value.";
  920. }
  921. });
  922. userArray[user] = {
  923. 'subs': filters.subs,
  924. 'shouts': filters.shouts,
  925. 'coms': filters.coms,
  926. 'notifications': filters.notifications
  927. }
  928. });
  929. }
  930. }
  931.  
  932. displaySettings();
  933.  
  934. setTimeout(parseSettings, 50);
  935. if (wordFilter !== undefined && wordFilter.length > 0) {
  936. setTimeout(applyWordFilters, 50);
  937. }
  938. //setTimeout(parseTagSettings, 100);
  939. // Submissions
  940. if (window.location.pathname.lastIndexOf('/browse', 0) === 0) setTimeout(filtersSubs, 100);
  941. else if (window.location.pathname.lastIndexOf('/favorites', 0) === 0) setTimeout(filtersSubs, 100);
  942. else if (window.location.pathname.lastIndexOf('/msg/submissions', 0) === 0) setTimeout(filtersSubsFollow, 100);
  943. // Shouts
  944. else if (window.location.pathname.lastIndexOf('/user', 0) === 0) setTimeout(filtersShouts, 100);
  945. else if (window.location.pathname.lastIndexOf('/controls/shouts', 0) === 0) setTimeout(filtersShoutsControl, 100);
  946. // Comments
  947. else if (window.location.pathname.lastIndexOf('/view', 0) === 0) setTimeout(filtersComments, 100);
  948. else if (window.location.pathname.lastIndexOf('/journal', 0) === 0) setTimeout(filtersComments, 100);
  949. // Notifications
  950. else if (window.location.pathname.lastIndexOf('/msg/others', 0) === 0) setTimeout(filtersNotifications, 100);
  951. else setTimeout(filtersSubs, 100);
  952. }
  953.  
  954. main();