FA Content Filter

Filters user-defined content while browsing Furaffinity.

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

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