FA Content Filter

Filters user-defined content while browsing Furaffinity.

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