User JS: Difference between revisions

From Soyjak Wiki, The Free Soycyclopedia
Jump to navigationJump to search
m (Added reddit-x)
(new filtering script + collapsible code)
Line 21: Line 21:


* Replaces "Bump" and "Sage" with upvote and downvote icons
* Replaces "Bump" and "Sage" with upvote and downvote icons
 
<i>Expand to view the script</i>
<pre>// ==UserScript==
{| class="mw-collapsible mw-collapsed"
|+
|<pre>// ==UserScript==
// @name        SoyParty-X
// @name        SoyParty-X
// @namespace    datamining
// @namespace    datamining
Line 208: Line 210:
})();
})();
</pre>
</pre>
|}
====Post Filters====
Allows you to filter posts based on comments, subject, name, and tripcode
To access filters click on options button.
<i>Expand to view the script</i>
{| class="mw-collapsible mw-collapsed"
|+
|<pre>
/*
* post-menu.js - adds dropdown menu to posts
*
* Creates a global Menu object with four public methods:
*
*  Menu.onclick(fnc)
*    registers a function to be executed after button click, before the menu is displayed
*  Menu.add_item(id, text[, title])
*    adds an item to the top level of menu
*  Menu.add_submenu(id, text)
*    creates and returns a List object through which to manipulate the content of the submenu
*  Menu.get_submenu(id)
*    returns the submenu with the specified id from the top level menu
*
*  The List object contains all the methods from Menu except onclick()
*
*  Example usage:
*    Menu.add_item('filter-menu-hide', 'Hide post');
*    Menu.add_item('filter-menu-unhide', 'Unhide post');
*
*    submenu = Menu.add_submenu('filter-menu-add', 'Add filter');
*        submenu.add_item('filter-add-post-plus', 'Post +', 'Hide post and all replies');
*        submenu.add_item('filter-add-id', 'ID');
* Usage:
*  $config['additional_javascript'][] = 'js/jquery.min.js';
*  $config['additional_javascript'][] = 'js/post-menu.js';
*/
$(document).ready(function () {
var List = function (menuId, text) {
this.id = menuId;
this.text = text;
this.items = [];
this.add_item = function (itemId, text, title) {
this.items.push(new Item(itemId, text, title));
};
this.list_items = function () {
var array = [];
var i, length, obj, $ele;
if ($.isEmptyObject(this.items))
return;
length = this.items.length;
for (i = 0; i < length; i++) {
obj = this.items[i];
$ele = $('<li>', {id: obj.id}).text(obj.text);
if ('title' in obj) $ele.attr('title', obj.title);
if (obj instanceof Item) {
$ele.addClass('post-item');
} else {
$ele.addClass('post-submenu');
$ele.prepend(obj.list_items());
$ele.append($('<span>', {class: 'post-menu-arrow'}).text('»'));
}
array.push($ele);
}
return $('<ul>').append(array);
};
this.add_submenu = function (menuId, text) {
var ele = new List(menuId, text);
this.items.push(ele);
return ele;
};
this.get_submenu = function (menuId) {
for (var i = 0; i < this.items.length; i++) {
if ((this.items[i] instanceof Item) || this.items[i].id != menuId) continue;
return this.items[i];
}
};
};
var Item = function (itemId, text, title) {
this.id = itemId;
this.text = text;
// optional
if (typeof title != 'undefined') this.title = title;
};
function buildMenu(e) {
var pos = $(e.target).offset();
var i, length;
var $menu = $('<div class="post-menu"></div>').append(mainMenu.list_items());
//  execute registered click handlers
length = onclick_callbacks.length;
for (i = 0; i < length; i++) {
onclick_callbacks[i](e, $menu);
}
//  set menu position and append to page
$menu.css({top: pos.top, left: pos.left + 20});
$('body').append($menu);
}
function addButton(post) {
var $ele = $(post);
$ele.find('input.delete').after(
$('<a>', {href: '#', class: 'post-btn', title: 'Post menu'}).text('▶')
);
}
/* * * * * * * * * *
    Public methods
* * * * * * * * * */
var Menu = {};
var mainMenu = new List();
var onclick_callbacks = [];
Menu.onclick = function (fnc) {
onclick_callbacks.push(fnc);
};
Menu.add_item = function (itemId, text, title) {
mainMenu.add_item(itemId, text, title);
};
Menu.add_submenu = function (menuId, text) {
return mainMenu.add_submenu(menuId, text);
};
Menu.get_submenu = function (id) {
return mainMenu.get_submenu(id);
};
window.Menu = Menu;
/* * * * * * * *
    Initialize
* * * * * * * */
/*  Styling
*/
var $ele, cssStyle, cssString;
$ele = $('<div>').addClass('post reply').hide().appendTo('body');
cssStyle = $ele.css(['border-top-color']);
cssStyle.hoverBg = $('body').css('background-color');
$ele.remove();
cssString =
'\n/*** Generated by post-menu ***/\n' +
'.post-menu {position: absolute; font-size: 12px; line-height: 1.3em;}\n' +
'.post-menu ul {\n' +
'    background-color: '+ cssStyle['border-top-color'] +'; border: 1px solid #666;\n' +
'    list-style: none; padding: 0; margin: 0; white-space: nowrap;\n}\n' +
'.post-menu .post-submenu{white-space: normal; width: 90px;}' +
'.post-menu .post-submenu>ul{white-space: nowrap; width: auto;}' +
'.post-menu li {cursor: pointer; position: relative; padding: 4px 4px; vertical-align: middle;}\n' +
'.post-menu li:hover {background-color: '+ cssStyle.hoverBg +';}\n' +
'.post-menu ul ul {display: none; position: absolute;}\n' +
'.post-menu li:hover>ul {display: block; left: 100%; margin-top: -3px;}\n' +
'.post-menu-arrow {float: right; margin-left: 10px;}\n' +
'.post-menu.hidden, .post-menu .hidden {display: none;}\n' +
'.post-btn {transition: transform 0.1s; width: 15px; text-align: center; font-size: 10pt; opacity: 0.8; text-decoration: none; margin: -6px 0px 0px -5px !important; display: inline-block;}\n' +
'.post-btn:hover {opacity: 1;}\n' +
'.post-btn-open {transform: rotate(90deg);}\n';
if (!$('style.generated-css').length) $('<style class="generated-css">').appendTo('head');
$('style.generated-css').html($('style.generated-css').html() + cssString);
/*  Add buttons
*/
$('.reply:not(.hidden), .thread>.op').each(function () {
addButton(this);
});
/*  event handlers
  */
$('form[name=postcontrols]').on('click', '.post-btn', function (e) {
e.preventDefault();
var post = e.target.parentElement.parentElement;
$('.post-menu').remove();
if ($(e.target).hasClass('post-btn-open')) {
$('.post-btn-open').removeClass('post-btn-open');
} else {
//  close previous button
$('.post-btn-open').removeClass('post-btn-open');
$(post).find('.post-btn').addClass('post-btn-open');
buildMenu(e);
}
});
$(document).on('click', function (e){
if ($(e.target).hasClass('post-btn') || $(e.target).hasClass('post-submenu'))
return;
$('.post-menu').remove();
$('.post-btn-open').removeClass('post-btn-open');
});
// on new posts
$(document).on('new_post', function (e, post) {
addButton(post);
});
$(document).trigger('menu_ready');
});
// Post Filters
if (active_page === 'thread' || active_page === 'index' || active_page === 'catalog' || active_page === 'ukko') {
$(document).on('menu_ready', function () {
'use strict';
// returns blacklist object from storage
function getList() {
return JSON.parse(localStorage.postFilter);
}
// stores blacklist into storage and reruns the filter
function setList(blacklist) {
localStorage.postFilter = JSON.stringify(blacklist);
$(document).trigger('filter_page');
}
// unit: seconds
function timestamp() {
return Math.floor((new Date()).getTime() / 1000);
}
function initList(list, boardId, threadId) {
if (typeof list.postFilter[boardId] == 'undefined') {
list.postFilter[boardId] = {};
list.nextPurge[boardId] = {};
}
if (typeof list.postFilter[boardId][threadId] == 'undefined') {
list.postFilter[boardId][threadId] = [];
}
list.nextPurge[boardId][threadId] = {timestamp: timestamp(), interval: 86400};  // 86400 seconds == 1 day
}
function addFilter(type, value, useRegex) {
var list = getList();
var filter = list.generalFilter;
var obj = {
type: type,
value: value,
regex: useRegex
};
for (var i=0; i<filter.length; i++) {
if (filter[i].type == type && filter[i].value == value && filter[i].regex == useRegex)
return;
}
filter.push(obj);
setList(list);
drawFilterList();
}
function removeFilter(type, value, useRegex) {
var list = getList();
var filter = list.generalFilter;
for (var i=0; i<filter.length; i++) {
if (filter[i].type == type && filter[i].value == value && filter[i].regex == useRegex) {
filter.splice(i, 1);
break;
}
}
setList(list);
drawFilterList();
}
function nameSpanToString(el) {
var s = '';
$.each($(el).contents(), function(k,v) {
if (v.nodeName === 'IMG')
s=s+$(v).attr('alt')
if (v.nodeName === '#text')
s=s+v.nodeValue
});
return s.trim();
}
var blacklist = {
add: {
post: function (boardId, threadId, postId, hideReplies) {
var list = getList();
var filter = list.postFilter;
initList(list, boardId, threadId);
for (var i in filter[boardId][threadId]) {
if (filter[boardId][threadId][i].post == postId) return;
}
filter[boardId][threadId].push({
post: postId,
hideReplies: hideReplies
});
setList(list);
},
uid: function (boardId, threadId, uniqueId, hideReplies) {
var list = getList();
var filter = list.postFilter;
initList(list, boardId, threadId);
for (var i in filter[boardId][threadId]) {
if (filter[boardId][threadId][i].uid == uniqueId) return;
}
filter[boardId][threadId].push({
uid: uniqueId,
hideReplies: hideReplies
});
setList(list);
}
},
remove: {
post: function (boardId, threadId, postId) {
var list = getList();
var filter = list.postFilter;
// thread already pruned
if (typeof filter[boardId] == 'undefined' || typeof filter[boardId][threadId] == 'undefined')
return;
for (var i=0; i<filter[boardId][threadId].length; i++) {
if (filter[boardId][threadId][i].post == postId) {
filter[boardId][threadId].splice(i, 1);
break;
}
}
if ($.isEmptyObject(filter[boardId][threadId])) {
delete filter[boardId][threadId];
delete list.nextPurge[boardId][threadId];
if ($.isEmptyObject(filter[boardId])) {
delete filter[boardId];
delete list.nextPurge[boardId];
}
}
setList(list);
},
uid: function (boardId, threadId, uniqueId) {
var list = getList();
var filter = list.postFilter;
// thread already pruned
if (typeof filter[boardId] == 'undefined' || typeof filter[boardId][threadId] == 'undefined')
return;
for (var i=0; i<filter[boardId][threadId].length; i++) {
if (filter[boardId][threadId][i].uid == uniqueId) {
filter[boardId][threadId].splice(i, 1);
break;
}
}
if ($.isEmptyObject(filter[boardId][threadId])) {
delete filter[boardId][threadId];
delete list.nextPurge[boardId][threadId];
if ($.isEmptyObject(filter[boardId])) {
delete filter[boardId];
delete list.nextPurge[boardId];
}
}
setList(list);
}
}
};
/*
*  hide/show the specified thread/post
*/
function hide(ele) {
var $ele = $(ele);
if ($(ele).data('hidden'))
return;
$(ele).data('hidden', true);
if ($ele.hasClass('op')) {
$ele.parent().find('.body, .files, .video-container').not($ele.children('.reply').children()).hide();
// hide thread replies on index view
if (active_page == 'index' || active_page == 'ukko') $ele.parent().find('.omitted, .reply:not(.hidden), post_no, .mentioned, br').hide();
} else {
// normal posts
$ele.children('.body, .files, .video-container').hide();
}
}
function show(ele) {
var $ele = $(ele);
$(ele).data('hidden', false);
if ($ele.hasClass('op')) {
$ele.parent().find('.body, .files, .video-container').show();
if (active_page == 'index') $ele.parent().find('.omitted, .reply:not(.hidden), post_no, .mentioned, br').show();
} else {
// normal posts
$ele.children('.body, .files, .video-container').show();
}
}
/*
*  create filter menu when the button is clicked
*/
function initPostMenu(pageData) {
var Menu = window.Menu;
var submenu;
Menu.add_item('filter-menu-hide', _('Hide post'));
Menu.add_item('filter-menu-unhide', _('Unhide post'));
submenu = Menu.add_submenu('filter-menu-add', _('Add filter'));
submenu.add_item('filter-add-post-plus', _('Post +'), _('Hide post and all replies'));
submenu.add_item('filter-add-id', _('ID'));
submenu.add_item('filter-add-id-plus', _('ID +'), _('Hide ID and all replies'));
submenu.add_item('filter-add-name', _('Name'));
submenu.add_item('filter-add-trip', _('Tripcode'));
submenu = Menu.add_submenu('filter-menu-remove', _('Remove filter'));
submenu.add_item('filter-remove-id', _('ID'));
submenu.add_item('filter-remove-name', _('Name'));
submenu.add_item('filter-remove-trip', _('Tripcode'));
Menu.onclick(function (e, $buffer) {
var ele = e.target.parentElement.parentElement;
var $ele = $(ele);
var threadId = $ele.parent().attr('id').replace('thread_', '');
var boardId = $ele.parent().data('board');
var postId = $ele.find('.post_no').not('[id]').text();
if (pageData.hasUID) {
var postUid = $ele.find('.poster_id').text();
}
var postName;
var postTrip = '';
if (!pageData.forcedAnon) {
postName = (typeof $ele.find('.name').contents()[0] == 'undefined') ? '' : nameSpanToString($ele.find('.name')[0]);
postTrip = $ele.find('.trip').text();
}
/*  display logic and bind click handlers
*/
// unhide button
if ($ele.data('hidden')) {
$buffer.find('#filter-menu-unhide').click(function () {
//  if hidden due to post id, remove it from blacklist
//  otherwise just show this post
blacklist.remove.post(boardId, threadId, postId);
show(ele);
});
$buffer.find('#filter-menu-hide').addClass('hidden');
} else {
$buffer.find('#filter-menu-unhide').addClass('hidden');
$buffer.find('#filter-menu-hide').click(function () {
blacklist.add.post(boardId, threadId, postId, false);
});
}
//  post id
if (!$ele.data('hiddenByPost')) {
$buffer.find('#filter-add-post-plus').click(function () {
blacklist.add.post(boardId, threadId, postId, true);
});
} else {
$buffer.find('#filter-add-post-plus').addClass('hidden');
}
// UID
if (pageData.hasUID && !$ele.data('hiddenByUid')) {
$buffer.find('#filter-add-id').click(function () {
blacklist.add.uid(boardId, threadId, postUid, false);
});
$buffer.find('#filter-add-id-plus').click(function () {
blacklist.add.uid(boardId, threadId, postUid, true);
});
$buffer.find('#filter-remove-id').addClass('hidden');
} else if (pageData.hasUID) {
$buffer.find('#filter-remove-id').click(function () {
blacklist.remove.uid(boardId, threadId, postUid);
});
$buffer.find('#filter-add-id').addClass('hidden');
$buffer.find('#filter-add-id-plus').addClass('hidden');
} else {
// board doesn't use UID
$buffer.find('#filter-add-id').addClass('hidden');
$buffer.find('#filter-add-id-plus').addClass('hidden');
$buffer.find('#filter-remove-id').addClass('hidden');
}
//  name
if (!pageData.forcedAnon && !$ele.data('hiddenByName')) {
$buffer.find('#filter-add-name').click(function () {
addFilter('name', postName, false);
});
$buffer.find('#filter-remove-name').addClass('hidden');
} else if (!pageData.forcedAnon) {
$buffer.find('#filter-remove-name').click(function () {
removeFilter('name', postName, false);
});
$buffer.find('#filter-add-name').addClass('hidden');
} else {
// board has forced anon
$buffer.find('#filter-remove-name').addClass('hidden');
$buffer.find('#filter-add-name').addClass('hidden');
}
//  tripcode
if (!pageData.forcedAnon && !$ele.data('hiddenByTrip') && postTrip !== '') {
$buffer.find('#filter-add-trip').click(function () {
addFilter('trip', postTrip, false);
});
$buffer.find('#filter-remove-trip').addClass('hidden');
} else if (!pageData.forcedAnon && postTrip !== '') {
$buffer.find('#filter-remove-trip').click(function () {
removeFilter('trip', postTrip, false);
});
$buffer.find('#filter-add-trip').addClass('hidden');
} else {
// board has forced anon
$buffer.find('#filter-remove-trip').addClass('hidden');
$buffer.find('#filter-add-trip').addClass('hidden');
}
/*  hide sub menus if all items are hidden
*/
if (!$buffer.find('#filter-menu-remove > ul').children().not('.hidden').length) {
$buffer.find('#filter-menu-remove').addClass('hidden');
}
if (!$buffer.find('#filter-menu-add > ul').children().not('.hidden').length) {
$buffer.find('#filter-menu-add').addClass('hidden');
}
});
}
/*
*  hide/unhide thread on index view
*/
function quickToggle(ele, threadId, pageData) {
/*if ($(ele).find('.hide-thread-link').length)
$('.hide-thread-link').remove();*/
if ($(ele).hasClass('op') && !$(ele).find('.hide-thread-link').length) {
$('<a class="hide-thread-link" style="float:left;margin-right:5px" href="javascript:void(0)">[' + ($(ele).data('hidden') ? '+' : '&ndash;') + ']</a>')
.insertBefore($(ele).find(':not(h2,h2 *):first'))
.click(function() {
var postId = $(ele).find('.post_no').not('[id]').text();
var hidden = $(ele).data('hidden');
var boardId = $(ele).parents('.thread').data('board');
if (hidden) {
blacklist.remove.post(boardId, threadId, postId, false);
$(this).html('[&ndash;]');
} else {
blacklist.add.post(boardId, threadId, postId, false);
$(this).text('[+]');
}
});
}
}
/*
*  determine whether the reply post should be hidden
*  - applies to all posts on page load or filtering rule change
*  - apply to new posts on thread updates
*  - must explicitly set the state of each attributes because filter will reapply to all posts after filtering rule change
*/
function filter(post, threadId, pageData) {
var $post = $(post);
var list = getList();
var postId = $post.find('.post_no').not('[id]').text();
var name, trip, uid, subject, comment;
var i, length, array, rule, pattern;  // temp variables
var boardId       = $post.data('board');
if (!boardId) boardId = $post.parents('.thread').data('board');
var localList  = pageData.localList;
var noReplyList = pageData.noReplyList;
var hasUID      = pageData.hasUID;
var forcedAnon  = pageData.forcedAnon;
var hasTrip = ($post.find('.trip').length > 0);
var hasSub = ($post.find('.subject').length > 0);
$post.data('hidden', false);
$post.data('hiddenByUid', false);
$post.data('hiddenByPost', false);
$post.data('hiddenByName', false);
$post.data('hiddenByTrip', false);
$post.data('hiddenBySubject', false);
$post.data('hiddenByComment', false);
// add post with matched UID to localList
if (hasUID &&
typeof list.postFilter[boardId] != 'undefined' &&
typeof list.postFilter[boardId][threadId] != 'undefined') {
uid = $post.find('.poster_id').text();
array = list.postFilter[boardId][threadId];
for (i=0; i<array.length; i++) {
if (array[i].uid == uid) {
$post.data('hiddenByUid', true);
localList.push(postId);
if (array[i].hideReplies) noReplyList.push(postId);
break;
}
}
}
// match localList
if (localList.length) {
if ($.inArray(postId, localList) != -1) {
if ($post.data('hiddenByUid') !== true) $post.data('hiddenByPost', true);
hide(post);
}
}
// matches generalFilter
if (!forcedAnon)
name = (typeof $post.find('.name').contents()[0] == 'undefined') ? '' : nameSpanToString($post.find('.name')[0]);
if (!forcedAnon && hasTrip)
trip = $post.find('.trip').text();
if (hasSub)
subject = $post.find('.subject').text();
array = $post.find('.body').contents().filter(function () {if ($(this).text() !== '') return true;}).toArray();
array = $.map(array, function (ele) {
return $(ele).text().trim();
});
comment = array.join(' ');
for (i = 0, length = list.generalFilter.length; i < length; i++) {
rule = list.generalFilter[i];
if (rule.regex) {
pattern = new RegExp(rule.value);
switch (rule.type) {
case 'name':
if (!forcedAnon && pattern.test(name)) {
$post.data('hiddenByName', true);
hide(post);
}
break;
case 'trip':
if (!forcedAnon && hasTrip && pattern.test(trip)) {
$post.data('hiddenByTrip', true);
hide(post);
}
break;
case 'sub':
if (hasSub && pattern.test(subject)) {
$post.data('hiddenBySubject', true);
hide(post);
}
break;
case 'com':
if (pattern.test(comment)) {
$post.data('hiddenByComment', true);
hide(post);
}
break;
}
} else {
switch (rule.type) {
case 'name':
if (!forcedAnon && rule.value == name) {
$post.data('hiddenByName', true);
hide(post);
}
break;
case 'trip':
if (!forcedAnon && hasTrip && rule.value == trip) {
$post.data('hiddenByTrip', true);
hide(post);
}
break;
case 'sub':
pattern = new RegExp('\\b'+ rule.value+ '\\b');
if (hasSub && pattern.test(subject)) {
$post.data('hiddenBySubject', true);
hide(post);
}
break;
case 'com':
pattern = new RegExp('\\b'+ rule.value+ '\\b');
if (pattern.test(comment)) {
$post.data('hiddenByComment', true);
hide(post);
}
break;
}
}
}
// check for link to filtered posts
$post.find('.body a').not('[rel="nofollow"]').each(function () {
var replyId = $(this).text().match(/^>>(\d+)$/);
if (!replyId)
return;
replyId = replyId[1];
if ($.inArray(replyId, noReplyList) != -1) {
hide(post);
}
});
// post didn't match any filters
if (!$post.data('hidden')) {
show(post);
}
}
/*  (re)runs the filter on the entire page
*/
function filterPage(pageData) {
var list = getList();
if (active_page != 'catalog') {
// empty the local and no-reply list
pageData.localList = [];
pageData.noReplyList = [];
$('.thread').each(function () {
var $thread = $(this);
// disregard the hidden threads constructed by post-hover.js
if ($thread.css('display') == 'none')
return;
var threadId = $thread.attr('id').replace('thread_', '');
var boardId = $thread.data('board');
var op = $thread.children('.op')[0];
var i, array;  // temp variables
// add posts to localList and noReplyList
if (typeof list.postFilter[boardId] != 'undefined' && typeof list.postFilter[boardId][threadId] != 'undefined') {
array = list.postFilter[boardId][threadId];
for (i=0; i<array.length; i++) {
if ( typeof array[i].post == 'undefined')
continue;
pageData.localList.push(array[i].post);
if (array[i].hideReplies) pageData.noReplyList.push(array[i].post);
}
}
// run filter on OP
filter(op, threadId, pageData);
quickToggle(op, threadId, pageData);
// iterate filter over each post
if (!$(op).data('hidden') || active_page == 'thread') {
$thread.find('.reply').not('.hidden').each(function () {
filter(this, threadId, pageData);
});
}
});
} else {
var postFilter = list.postFilter[pageData.boardId];
var $collection = $('.mix');
if ($.isEmptyObject(postFilter))
return;
// for each thread that has filtering rules
// check if filter contains thread OP and remove the thread from catalog
$.each(postFilter, function (key, thread) {
var threadId = key;
$.each(thread, function () {
if (this.post == threadId) {
$collection.filter('[data-id='+ threadId +']').remove();
}
});
});
}
}
function initStyle() {
var $ele, cssStyle, cssString;
$ele = $('<div>').addClass('post reply').hide().appendTo('body');
cssStyle = $ele.css(['background-color', 'border-color']);
cssStyle.hoverBg = $('body').css('background-color');
$ele.remove();
cssString = '\n/*** Generated by post-filter ***/\n' +
'#filter-control input[type=text] {width: 130px;}' +
'#filter-control input[type=checkbox] {vertical-align: middle;}' +
'#filter-control #clear {float: right;}\n' +
'#filter-container {margin-top: 20px; border: 1px solid; height: 270px; overflow: auto;}\n' +
'#filter-list {width: 100%; border-collapse: collapse;}\n' +
'#filter-list th {text-align: center; height: 20px; font-size: 14px; border-bottom: 1px solid;}\n' +
'#filter-list th:nth-child(1) {text-align: center; width: 70px;}\n' +
'#filter-list th:nth-child(2) {text-align: left;}\n' +
'#filter-list th:nth-child(3) {text-align: center; width: 58px;}\n' +
'#filter-list tr:not(#header) {height: 22px;}\n' +
'#filter-list tr:nth-child(even) {background-color:rgba(255, 255, 255, 0.5);}\n' +
'#filter-list td:nth-child(1) {text-align: center; width: 70px;}\n' +
'#filter-list td:nth-child(3) {text-align: center; width: 58px;}\n' +
'#confirm {text-align: right; margin-bottom: -18px; padding-top: 2px; font-size: 14px; color: #FF0000;}';
if (!$('style.generated-css').length) $('<style class="generated-css">').appendTo('head');
$('style.generated-css').html($('style.generated-css').html() + cssString);
}
function drawFilterList() {
var list = getList().generalFilter;
var $ele = $('#filter-list');
var $row, i, length, obj, val;
var typeName = {
name: 'name',
trip: 'tripcode',
sub: 'subject',
com: 'comment'
};
$ele.empty();
$ele.append('<tr id="header"><th>Type</th><th>Content</th><th>Remove</th></tr>');
for (i = 0, length = list.length; i < length; i++) {
obj = list[i];
// display formatting
val = (obj.regex) ? '/'+ obj.value +'/' : obj.value;
$row = $('<tr>');
$row.append(
'<td>'+ typeName[obj.type] +'</td>',
'<td>'+ val +'</td>',
$('<td>').append(
$('<a>').html('X')
.addClass('del-btn')
.attr('href', '#')
.data('type', obj.type)
.data('val', obj.value)
.data('useRegex', obj.regex)
)
);
$ele.append($row);
}
}
function initOptionsPanel() {
if (window.Options && !Options.get_tab('filter')) {
Options.add_tab('filter', 'list', _('Filters'));
Options.extend_tab('filter',
'<div id="filter-control">' +
'<select>' +
'<option value="name">'+_('Name')+'</option>' +
'<option value="trip">'+_('Tripcode')+'</option>' +
'<option value="sub">'+_('Subject')+'</option>' +
'<option value="com">'+_('Comment')+'</option>' +
'</select>' +
'<input type="text">' +
'<input type="checkbox">' +
'regex ' +
'<button id="set-filter">'+_('Add')+'</button>' +
'<button id="clear">'+_('Clear all filters')+'</button>' +
'<div id="confirm" class="hidden">' +
_('This will clear all filtering rules including hidden posts.')+' <a id="confirm-y" href="#">'+_('yes')+'</a> | <a id="confirm-n" href="#">'+_('no')+'</a>' +
'</div>' +
'</div>' +
'<div id="filter-container"><table id="filter-list"></table></div>'
);
drawFilterList();
// control buttons
$('#filter-control').on('click', '#set-filter', function () {
var type = $('#filter-control select option:selected').val();
var value = $('#filter-control input[type=text]').val();
var useRegex = $('#filter-control input[type=checkbox]').prop('checked');
//clear the input form
$('#filter-control input[type=text]').val('');
addFilter(type, value, useRegex);
drawFilterList();
});
$('#filter-control').on('click', '#clear', function () {
$('#filter-control #clear').addClass('hidden');
$('#filter-control #confirm').removeClass('hidden');
});
$('#filter-control').on('click', '#confirm-y', function (e) {
e.preventDefault();
$('#filter-control #clear').removeClass('hidden');
$('#filter-control #confirm').addClass('hidden');
setList({
generalFilter: [],
postFilter: {},
nextPurge: {},
lastPurge: timestamp()
});
drawFilterList();
});
$('#filter-control').on('click', '#confirm-n', function (e) {
e.preventDefault();
$('#filter-control #clear').removeClass('hidden');
$('#filter-control #confirm').addClass('hidden');
});
// remove button
$('#filter-list').on('click', '.del-btn', function (e) {
e.preventDefault();
var $ele = $(e.target);
var type = $ele.data('type');
var val = $ele.data('val');
var useRegex = $ele.data('useRegex');
removeFilter(type, val, useRegex);
});
}
}
/*
*  clear out pruned threads
*/
function purge() {
var list = getList();
var board, thread, boardId, threadId;
var deferred;
var requestArray = [];
var successHandler = function (boardId, threadId) {
return function () {
// thread still alive, keep it in the list and increase the time between checks.
var list = getList();
var thread = list.nextPurge[boardId][threadId];
thread.timestamp = timestamp();
thread.interval = Math.floor(thread.interval * 1.5);
setList(list);
};
};
var errorHandler = function (boardId, threadId) {
return function (xhr) {
if (xhr.status == 404) {
var list = getList();
delete list.nextPurge[boardId][threadId];
delete list.postFilter[boardId][threadId];
if ($.isEmptyObject(list.nextPurge[boardId])) delete list.nextPurge[boardId];
if ($.isEmptyObject(list.postFilter[boardId])) delete list.postFilter[boardId];
setList(list);
}
};
};
if ((timestamp() - list.lastPurge) < 86400)  // less than 1 day
return;
for (boardId in list.nextPurge) {
board = list.nextPurge[boardId];
for (threadId in board) {
thread = board[threadId];
if (timestamp() > (thread.timestamp + thread.interval)) {
// check if thread is pruned
deferred = $.ajax({
cache: false,
url: '/'+ boardId +'/res/'+ threadId +'.json',
success: successHandler(boardId, threadId),
error: errorHandler(boardId, threadId)
});
requestArray.push(deferred);
}
}
}
// when all requests complete
$.when.apply($, requestArray).always(function () {
var list = getList();
list.lastPurge = timestamp();
setList(list);
});
}
function init() {
if (typeof localStorage.postFilter === 'undefined') {
localStorage.postFilter = JSON.stringify({
generalFilter: [],
postFilter: {},
nextPurge: {},
lastPurge: timestamp()
});
}
var pageData = {
boardId: board_name,  // get the id from the global variable
localList: [],  // all the blacklisted post IDs or UIDs that apply to the current page
noReplyList: [],  // any posts that replies to the contents of this list shall be hidden
hasUID: (document.getElementsByClassName('poster_id').length > 0),
forcedAnon: ($('th:contains(Name)').length === 0)  // tests by looking for the Name label on the reply form
};
initStyle();
initOptionsPanel();
initPostMenu(pageData);
filterPage(pageData);
// on new posts
$(document).on('new_post', function (e, post) {
var threadId;
if ($(post).hasClass('reply')) {
threadId = $(post).parents('.thread').attr('id').replace('thread_', '');
} else {
threadId = $(post).attr('id').replace('thread_', '');
post = $(post).children('.op')[0];
}
filter(post, threadId, pageData);
quickToggle(post, threadId, pageData);
});
$(document).on('filter_page', function () {
filterPage(pageData);
});
// shift+click on catalog to hide thread
if (active_page == 'catalog') {
$(document).on('click', '.mix', function(e) {
if (e.shiftKey) {
var threadId = $(this).data('id').toString();
var postId = threadId;
blacklist.add.post(pageData.boardId, threadId, postId, false);
}
});
}
// clear out the old threads
purge();
}
init();
});
if (typeof window.Menu !== "undefined") {
$(document).trigger('menu_ready');
}
}
</pre>
|}
==== Preview for Youtube Videos ====
==== Preview for Youtube Videos ====
Show preview for youtube video links. <pre>(function youtubeInlinePreviews() {
Show preview for youtube video links.  
 
<i>Expand to view the script</i>
{| class="mw-collapsible mw-collapsed"
|+
|<pre>(function youtubeInlinePreviews() {


const re = /(?:youtube\.com\/watch\?v=|youtu\.be\/)(.{11})/;
const re = /(?:youtube\.com\/watch\?v=|youtu\.be\/)(.{11})/;
Line 236: Line 1,322:


})();</pre>
})();</pre>
|}


==== Filter tripchuds ====
==== Filter tripchuds ====
To use it, change "!!TRIP GOES HERE" to any trip you want to filter (ex: !incelchud1 or !!incelchud1), can also be used to filter names if you change <blockquote> (/class="trip">!!TRIP GOES HERE</) </blockquote> to <blockquote> (/class="name">name of the chudcel you want to filter</) </blockquote>
To use it, change "!!TRIP GOES HERE" to any trip you want to filter (ex: !incelchud1 or !!incelchud1), can also be used to filter names if you change <blockquote> (/class="trip">!!TRIP GOES HERE</) </blockquote> to <blockquote> (/class="name">name of the chudcel you want to filter</) </blockquote>
if you want to filter multiple tripchuds, you have to do this <blockquote> (/class="trip">!!FIRST TRIP CHUD|!SECOND TRIPCHUD|!THIRD TRIPCHUD</) </blockquote>
if you want to filter multiple tripchuds, you have to do this <blockquote> (/class="trip">!!FIRST TRIP CHUD|!SECOND TRIPCHUD|!THIRD TRIPCHUD</) </blockquote>
<pre>$(".post")
 
<i>Expand to view the script</i>
{| class="mw-collapsible mw-collapsed"
|+
|<pre>$(".post")
   .filter(function (index) {
   .filter(function (index) {
     return this.innerHTML.match(/class="trip">!!TRIP GOES HERE</);
     return this.innerHTML.match(/class="trip">!!TRIP GOES HERE</);
Line 255: Line 1,346:
localStorage.favorites = '[]';
localStorage.favorites = '[]';
}</pre>
}</pre>
 
|}
==== Hide tripchud posts from catalog ====
==== Hide tripchud posts from catalog ====
Hides all tripchud posts from catalog, no exceptions.
Hides all tripchud posts from catalog, no exceptions.
<pre>// --- start: purgeTripsFromCatalog ---
 
<i>Expand to view the script</i>
{| class="mw-collapsible mw-collapsed"
|+
|<pre>// --- start: purgeTripsFromCatalog ---


(function purgeTripsFromCatalog() {
(function purgeTripsFromCatalog() {
Line 278: Line 1,373:


// --- end: purgeTripsFromCatalog ---</pre>
// --- end: purgeTripsFromCatalog ---</pre>
|}

Revision as of 05:44, 25 October 2021

On soyjak.party users can add custom javascript under Options->User JS in the top right of the page to customize their browsing experience.

Code Snippets

SoyParty-X

Features:

  • Detects flood posts and hides them (you can set amount of lines and words to hide)
  • Forced anonymity (hides namefags, can be disabled)
  • Hides emailfags or sagefags (can be disabled for sagefags)
  • Highlights triple parentheses
  • Highlights datamining (makes posts glow)
  • Inline Youtube Previews (+invidious support)
  • Replaces "Gem" and "Coal" with minecraft icons
  • Replaces "Bump" and "Sage" with upvote and downvote icons

Expand to view the script

// ==UserScript==
// @name         SoyParty-X
// @namespace    datamining
// @version      0.2
// @description  Cure the cancer that is killing soyjak.party
// @author       Chud (You)
// @match        https://soyjak.party/*
// @icon         https://soyjak.party/static/favicon.png
// @grant        none
// ==/UserScript==

/* eslint-env jquery */

/*

Changelog:

0.2:

- hidePosts() now detects flood posts.

0.1:

- hidePosts()
- forcedAnonymity()
- highlightTripleParentheses()
- highlightDatamining()
- inlineYoutubePreviews()
- replaceCoalAndGemsWithIcon()
- replaceBumpAndSageWithIcons()

*/

(function SoyPartyX() {
  // true = hide posts where the e-mail is "sage".
  const _hideMailtoSage = true;

  // true = scrub email, trip, and force all names to be "Chud".
  // - Emailfags and tripfags are already hidden.
  // - Namefags aren't hidden, but turning this on will anonymize them.
  // false = don't change posts (default).
  const _enableForcedAnonymity = false;

  // Sets the limit for a post to be considered a flood post.
  // If one of the criteria is met, the post is hidden.
  const floodThresholdLines = 30;
  const floodThresholdCharacters = 3000;

  hidePosts();
  forcedAnonymity(); // Must come AFTER hidePosts()
  highlightTripleParentheses();
  highlightDatamining();
  inlineYoutubePreviews();
  replaceCoalAndGemsWithIcon();
  replaceBumpAndSageWithIcons();

  function hidePosts() {
    $(".post").each((i, el) => {
      const $el = $(el);
      const reasons = [];

      const isOp = $el.hasClass("op");

      if ($el.has(".trip").length) {
        reasons.push("tripfag");
      }
      if (_hideMailtoSage && $el.has('a.email[href^="mailto:sage"]').length) {
        reasons.push("sagefag");
      }
      if ($el.has('a.email:not([href^="mailto:sage"])').length) {
        reasons.push("emailfag");
      }

      const body = $el.has(".body");
      const bodyLines = body.html().split("<br>").length;
      const bodyLength = body.text().length;

      if (
        bodyLines > floodThresholdLines ||
        bodyLength > floodThresholdCharacters
      ) {
        reasons.push(
          `possible flooding: ${bodyLength} characters in ${bodyLines} lines`
        );
      }

      if (reasons.length) {
        const $notice = $("<div>")
          .addClass(`post ${isOp ? "op" : "reply"}`)
          .html(
            `<div class='body'><em>Post hidden (${reasons.join(
              ", "
            )}). Click to show.</em></div>`
          )
          .after($("<br>"));
        $notice.click(() => {
          $el.show();
          if (isOp) $el.prev(".files").show();
          $notice.hide();
        });
        $el.after($notice);
        $el.hide();
        if (isOp) $el.prev(".files").hide();
      }
    });
  }

  function forcedAnonymity() {
    if (!_enableForcedAnonymity) return;
    // Remove all emails.
    $("a.email").prop("outerHTML", "<span class='name'>Chud</span>");
    // Remove all tripcodes.
    $(".trip").prop("outerHTML", "");
    // Set all names to Chud.
    // Make sure not to overwrite (You)'s.
    $(".name")
      .filter((i, el) => !$(el).has(".own_post").length)
      .text("Chud");
  }

  function replaceWordWithIcon(re, icon) {
    const matchesRe = (index, post) => $(post).html().match(re);

    const template = (match) =>
      `<img src="${icon}" style="max-height:2em; vertical-align:middle">`;

    const applyTemplate = (index, post) => {
      const $post = $(post);
      const html = $post.html();
      $post.html(html.replace(re, template));
    };

    $("div.body").filter(matchesRe).each(applyTemplate);
  }

  function replaceCoalAndGemsWithIcon() {
    replaceWordWithIcon(/coal/gi, "https://i.imgur.com/O9iRcRv.png");
    replaceWordWithIcon(/gems?/gi, "https://i.imgur.com/BvjFdau.png");
  }

  function replaceBumpAndSageWithIcons() {
    // replaceWordWithIcon(/bump/gi, "https://i.imgur.com/zM2xOGh.png");
    // replaceWordWithIcon(/sage/gi, "https://i.imgur.com/2bsauzj.png");
    replaceWordWithIcon(/bump/gi, "https://i.imgur.com/Y7cpsW0.png");
    replaceWordWithIcon(/\bsage\b/gi, "https://i.imgur.com/ZarQtY3.png");
  }

  function highlightTripleParentheses() {
    const re = /\(\(\(.+?\)\)\)/g;
    const hasRe = (i, post) => post.innerHTML.match(re);

    const template = (match) =>
      `<span style='background-color:white;color:#0038B8;font-family:monospace;'>${match}</span>`;
    const applyTemplate = (i, post) => {
      post.innerHTML = post.innerHTML.replace(re, template);
    };

    $("div.body").filter(hasRe).each(applyTemplate);
  }

  function highlightDatamining() {
    const reGlowie =
      /data(\s*|-)min(ing|er|ed)|(sell|selling|sold)\s+(my|our)?\s+data|cuckflare|cloudflare|cloud fleur/i;
    const hasReGlowie = (i, post) => post.innerHTML.match(reGlowie);
    const applyTemplate = (i, post) =>
      $(post).css({
        backgroundColor: "#D7EFD7",
        boxShadow: "#66FF66 0 0 2rem 0",
      });
    $(".reply").filter(hasReGlowie).each(applyTemplate);
  }

  function inlineYoutubePreviews() {
    const re = /(?:youtu\.be\/|\/watch\?v=)(.{11})/;
    const previewTemplate = (videoId) =>
      `<a href="https://youtube.com/watch?v=${videoId}">https://youtube.com/watch?v=${videoId}</a><br><img style="max-width:255px;max-height:255px" src="https://i.ytimg.com/vi/${videoId}/hqdefault.jpg" /><br><em>Watch on <a href="https://yewtu.be/${videoId}">Invidious</a> (less datamining)</em><br>`;
    $(".body a")
      .filter(function (i) {
        return $(this).prop("href").match(re);
      })
      .each(function (i) {
        $(this).prop("outerHTML", previewTemplate(this.href.match(re)[1]));
      });
  }
})();

Post Filters

Allows you to filter posts based on comments, subject, name, and tripcode To access filters click on options button.

Expand to view the script

/*
 * post-menu.js - adds dropdown menu to posts
 *
 * Creates a global Menu object with four public methods:
 *
 *   Menu.onclick(fnc)
 *     registers a function to be executed after button click, before the menu is displayed
 *   Menu.add_item(id, text[, title])
 *     adds an item to the top level of menu
 *   Menu.add_submenu(id, text)
 *     creates and returns a List object through which to manipulate the content of the submenu
 *   Menu.get_submenu(id)
 *     returns the submenu with the specified id from the top level menu
 *
 *   The List object contains all the methods from Menu except onclick()
 *
 *   Example usage:
 *     Menu.add_item('filter-menu-hide', 'Hide post');
 *     Menu.add_item('filter-menu-unhide', 'Unhide post');
 *
 *     submenu = Menu.add_submenu('filter-menu-add', 'Add filter');
 *         submenu.add_item('filter-add-post-plus', 'Post +', 'Hide post and all replies');
 *         submenu.add_item('filter-add-id', 'ID');
 *  
 * Usage:
 *   $config['additional_javascript'][] = 'js/jquery.min.js';
 *   $config['additional_javascript'][] = 'js/post-menu.js';
 */
$(document).ready(function () {

var List = function (menuId, text) {
	this.id = menuId;
	this.text = text;
	this.items = [];

	this.add_item = function (itemId, text, title) {
		this.items.push(new Item(itemId, text, title));
	};
	this.list_items = function () {
		var array = [];
		var i, length, obj, $ele;

		if ($.isEmptyObject(this.items))
			return;

		length = this.items.length;
		for (i = 0; i < length; i++) {
			obj = this.items[i];

			$ele = $('<li>', {id: obj.id}).text(obj.text);
			if ('title' in obj) $ele.attr('title', obj.title);

			if (obj instanceof Item) {
				$ele.addClass('post-item');
			} else {
				$ele.addClass('post-submenu');

				$ele.prepend(obj.list_items());
				$ele.append($('<span>', {class: 'post-menu-arrow'}).text('»'));
			}

			array.push($ele);
		}

		return $('<ul>').append(array);
	};
	this.add_submenu = function (menuId, text) {
		var ele = new List(menuId, text);
		this.items.push(ele);
		return ele;
	};
	this.get_submenu = function (menuId) {
		for (var i = 0; i < this.items.length; i++) {
			if ((this.items[i] instanceof Item) || this.items[i].id != menuId) continue;
			return this.items[i];
		}
	};
};

var Item = function (itemId, text, title) {
	this.id = itemId;
	this.text = text;

	// optional
	if (typeof title != 'undefined') this.title = title;
};

function buildMenu(e) {
	var pos = $(e.target).offset();
	var i, length;

	var $menu = $('<div class="post-menu"></div>').append(mainMenu.list_items());

	//  execute registered click handlers
	length = onclick_callbacks.length;
	for (i = 0; i < length; i++) {
		onclick_callbacks[i](e, $menu);
	}

	//  set menu position and append to page
	 $menu.css({top: pos.top, left: pos.left + 20});
	 $('body').append($menu);
}

function addButton(post) {
	var $ele = $(post);
	$ele.find('input.delete').after(
		$('<a>', {href: '#', class: 'post-btn', title: 'Post menu'}).text('▶')
	);
}


/* * * * * * * * * *
    Public methods
 * * * * * * * * * */
var Menu = {};
var mainMenu = new List();
var onclick_callbacks = [];

Menu.onclick = function (fnc) {
	onclick_callbacks.push(fnc);
};

Menu.add_item = function (itemId, text, title) {
	mainMenu.add_item(itemId, text, title);
};

Menu.add_submenu = function (menuId, text) {
	return mainMenu.add_submenu(menuId, text);
};

Menu.get_submenu = function (id) {
	return mainMenu.get_submenu(id);
};

window.Menu = Menu;


/* * * * * * * *
    Initialize
 * * * * * * * */

/*  Styling
 */
var $ele, cssStyle, cssString;

$ele = $('<div>').addClass('post reply').hide().appendTo('body');
cssStyle = $ele.css(['border-top-color']);
cssStyle.hoverBg = $('body').css('background-color');
$ele.remove();

cssString =
	'\n/*** Generated by post-menu ***/\n' +
	'.post-menu {position: absolute; font-size: 12px; line-height: 1.3em;}\n' +
	'.post-menu ul {\n' +
	'    background-color: '+ cssStyle['border-top-color'] +'; border: 1px solid #666;\n' +
	'    list-style: none; padding: 0; margin: 0; white-space: nowrap;\n}\n' +
	'.post-menu .post-submenu{white-space: normal; width: 90px;}' +
	'.post-menu .post-submenu>ul{white-space: nowrap; width: auto;}' +
	'.post-menu li {cursor: pointer; position: relative; padding: 4px 4px; vertical-align: middle;}\n' +
	'.post-menu li:hover {background-color: '+ cssStyle.hoverBg +';}\n' +
	'.post-menu ul ul {display: none; position: absolute;}\n' +
	'.post-menu li:hover>ul {display: block; left: 100%; margin-top: -3px;}\n' +
	'.post-menu-arrow {float: right; margin-left: 10px;}\n' +
	'.post-menu.hidden, .post-menu .hidden {display: none;}\n' +
	'.post-btn {transition: transform 0.1s; width: 15px; text-align: center; font-size: 10pt; opacity: 0.8; text-decoration: none; margin: -6px 0px 0px -5px !important; display: inline-block;}\n' +
	'.post-btn:hover {opacity: 1;}\n' +
	'.post-btn-open {transform: rotate(90deg);}\n';

if (!$('style.generated-css').length) $('<style class="generated-css">').appendTo('head');
$('style.generated-css').html($('style.generated-css').html() + cssString);

/*  Add buttons
 */
$('.reply:not(.hidden), .thread>.op').each(function () {
	addButton(this);
 });

 /*  event handlers
  */
$('form[name=postcontrols]').on('click', '.post-btn', function (e) {
	e.preventDefault();
	var post = e.target.parentElement.parentElement;
	$('.post-menu').remove();

	if ($(e.target).hasClass('post-btn-open')) {
		$('.post-btn-open').removeClass('post-btn-open');
	} else {
		//  close previous button
		$('.post-btn-open').removeClass('post-btn-open');
		$(post).find('.post-btn').addClass('post-btn-open');

		buildMenu(e);
	}
});

$(document).on('click', function (e){
	if ($(e.target).hasClass('post-btn') || $(e.target).hasClass('post-submenu'))
		return;

	$('.post-menu').remove();
	$('.post-btn-open').removeClass('post-btn-open');
});

// on new posts
$(document).on('new_post', function (e, post) {
	addButton(post);
});

$(document).trigger('menu_ready');
});


// Post Filters
if (active_page === 'thread' || active_page === 'index' || active_page === 'catalog' || active_page === 'ukko') {
	$(document).on('menu_ready', function () {
		'use strict';
		
		// returns blacklist object from storage
		function getList() {
			return JSON.parse(localStorage.postFilter);
		}

		// stores blacklist into storage and reruns the filter
		function setList(blacklist) {
			localStorage.postFilter = JSON.stringify(blacklist);
			$(document).trigger('filter_page');
		}

		// unit: seconds
		function timestamp() {
			return Math.floor((new Date()).getTime() / 1000);
		}

		function initList(list, boardId, threadId) {
			if (typeof list.postFilter[boardId] == 'undefined') {
				list.postFilter[boardId] = {};
				list.nextPurge[boardId] = {};
			}
			if (typeof list.postFilter[boardId][threadId] == 'undefined') {
				list.postFilter[boardId][threadId] = [];
			}
			list.nextPurge[boardId][threadId] = {timestamp: timestamp(), interval: 86400};  // 86400 seconds == 1 day
		}

		function addFilter(type, value, useRegex) {
			var list = getList();
			var filter = list.generalFilter;
			var obj = {
				type: type,
				value: value,
				regex: useRegex
			};

			for (var i=0; i<filter.length; i++) {
				if (filter[i].type == type && filter[i].value == value && filter[i].regex == useRegex)
					return;
			}

			filter.push(obj);
			setList(list);
			drawFilterList();
		}

		function removeFilter(type, value, useRegex) {
			var list = getList();
			var filter = list.generalFilter;

			for (var i=0; i<filter.length; i++) {
				if (filter[i].type == type && filter[i].value == value && filter[i].regex == useRegex) {
					filter.splice(i, 1);
					break;
				}
			}

			setList(list);
			drawFilterList();
		}

		function nameSpanToString(el) {
			var s = ''; 

			$.each($(el).contents(), function(k,v) {
				if (v.nodeName === 'IMG')
					s=s+$(v).attr('alt')
				
				if (v.nodeName === '#text')
					s=s+v.nodeValue
			});
			return s.trim();
		}

		var blacklist = {
			add: {
				post: function (boardId, threadId, postId, hideReplies) {
					var list = getList();
					var filter = list.postFilter;

					initList(list, boardId, threadId);

					for (var i in filter[boardId][threadId]) {
						if (filter[boardId][threadId][i].post == postId) return;
					}
					filter[boardId][threadId].push({
						post: postId,
						hideReplies: hideReplies
					});
					setList(list);
				},
				uid: function (boardId, threadId, uniqueId, hideReplies) {
					var list = getList();
					var filter = list.postFilter;

					initList(list, boardId, threadId);

					for (var i in filter[boardId][threadId]) {
						if (filter[boardId][threadId][i].uid == uniqueId) return;
					}
					filter[boardId][threadId].push({
						uid: uniqueId,
						hideReplies: hideReplies
					});
					setList(list);
				}
			},
			remove: {
				post: function (boardId, threadId, postId) {
					var list = getList();
					var filter = list.postFilter;

					// thread already pruned
					if (typeof filter[boardId] == 'undefined' || typeof filter[boardId][threadId] == 'undefined')
						return;

					for (var i=0; i<filter[boardId][threadId].length; i++) {
						if (filter[boardId][threadId][i].post == postId) {
							filter[boardId][threadId].splice(i, 1);
							break;
						}
					}

					if ($.isEmptyObject(filter[boardId][threadId])) {
						delete filter[boardId][threadId];
						delete list.nextPurge[boardId][threadId];

						if ($.isEmptyObject(filter[boardId])) {
							delete filter[boardId];
							delete list.nextPurge[boardId];
						}
					}
					setList(list);
				},
				uid: function (boardId, threadId, uniqueId) {
					var list = getList();
					var filter = list.postFilter;

					// thread already pruned
					if (typeof filter[boardId] == 'undefined' || typeof filter[boardId][threadId] == 'undefined')
						return;

					for (var i=0; i<filter[boardId][threadId].length; i++) {
						if (filter[boardId][threadId][i].uid == uniqueId) {
							filter[boardId][threadId].splice(i, 1);
							break;
						}
					}

					if ($.isEmptyObject(filter[boardId][threadId])) {
						delete filter[boardId][threadId];
						delete list.nextPurge[boardId][threadId];

						if ($.isEmptyObject(filter[boardId])) {
							delete filter[boardId];
							delete list.nextPurge[boardId];
						}
					}
					setList(list);
				}
			}
		};

		/* 
		 *  hide/show the specified thread/post
		 */
		function hide(ele) {
			var $ele = $(ele);

			if ($(ele).data('hidden'))
				return;

			$(ele).data('hidden', true);
			if ($ele.hasClass('op')) {
				$ele.parent().find('.body, .files, .video-container').not($ele.children('.reply').children()).hide();

				// hide thread replies on index view
				if (active_page == 'index' || active_page == 'ukko') $ele.parent().find('.omitted, .reply:not(.hidden), post_no, .mentioned, br').hide();
			} else {
				// normal posts
				$ele.children('.body, .files, .video-container').hide();
			}
		}
		function show(ele) {
			var $ele = $(ele);

			$(ele).data('hidden', false);
			if ($ele.hasClass('op')) {
				$ele.parent().find('.body, .files, .video-container').show();
				if (active_page == 'index') $ele.parent().find('.omitted, .reply:not(.hidden), post_no, .mentioned, br').show();
			} else {
				// normal posts
				$ele.children('.body, .files, .video-container').show();
			}
		}

		/* 
		 *  create filter menu when the button is clicked
		 */
		function initPostMenu(pageData) {
			var Menu = window.Menu;
			var submenu;
			Menu.add_item('filter-menu-hide', _('Hide post'));
			Menu.add_item('filter-menu-unhide', _('Unhide post'));

			submenu = Menu.add_submenu('filter-menu-add', _('Add filter'));
				submenu.add_item('filter-add-post-plus', _('Post +'), _('Hide post and all replies'));
				submenu.add_item('filter-add-id', _('ID'));
				submenu.add_item('filter-add-id-plus', _('ID +'), _('Hide ID and all replies'));
				submenu.add_item('filter-add-name', _('Name'));
				submenu.add_item('filter-add-trip', _('Tripcode'));

			submenu = Menu.add_submenu('filter-menu-remove', _('Remove filter'));
				submenu.add_item('filter-remove-id', _('ID'));
				submenu.add_item('filter-remove-name', _('Name'));
				submenu.add_item('filter-remove-trip', _('Tripcode'));

			Menu.onclick(function (e, $buffer) {
				var ele = e.target.parentElement.parentElement;
				var $ele = $(ele);

				var threadId = $ele.parent().attr('id').replace('thread_', '');
				var boardId = $ele.parent().data('board');
				var postId = $ele.find('.post_no').not('[id]').text();
				if (pageData.hasUID) {
					var postUid = $ele.find('.poster_id').text();
				}

				var postName;
				var postTrip = '';
				if (!pageData.forcedAnon) {
					postName = (typeof $ele.find('.name').contents()[0] == 'undefined') ? '' : nameSpanToString($ele.find('.name')[0]);
					postTrip = $ele.find('.trip').text();
				}

				/*  display logic and bind click handlers
				 */

				 // unhide button
				if ($ele.data('hidden')) {
					$buffer.find('#filter-menu-unhide').click(function () {
						//  if hidden due to post id, remove it from blacklist
						//  otherwise just show this post
						blacklist.remove.post(boardId, threadId, postId);
						show(ele);
					});
					$buffer.find('#filter-menu-hide').addClass('hidden');
				} else {
					$buffer.find('#filter-menu-unhide').addClass('hidden');
					$buffer.find('#filter-menu-hide').click(function () {
						blacklist.add.post(boardId, threadId, postId, false);
					});
				}

				//  post id
				if (!$ele.data('hiddenByPost')) {
					$buffer.find('#filter-add-post-plus').click(function () {
						blacklist.add.post(boardId, threadId, postId, true);
					});
				} else {
					$buffer.find('#filter-add-post-plus').addClass('hidden');
				}

				// UID
				if (pageData.hasUID && !$ele.data('hiddenByUid')) {
					$buffer.find('#filter-add-id').click(function () {
						blacklist.add.uid(boardId, threadId, postUid, false);
					});
					$buffer.find('#filter-add-id-plus').click(function () {
						blacklist.add.uid(boardId, threadId, postUid, true);
					});

					$buffer.find('#filter-remove-id').addClass('hidden');
				} else if (pageData.hasUID) {
					$buffer.find('#filter-remove-id').click(function () {
						blacklist.remove.uid(boardId, threadId, postUid);
					});

					$buffer.find('#filter-add-id').addClass('hidden');
					$buffer.find('#filter-add-id-plus').addClass('hidden');
				} else {
					// board doesn't use UID
					$buffer.find('#filter-add-id').addClass('hidden');
					$buffer.find('#filter-add-id-plus').addClass('hidden');
					$buffer.find('#filter-remove-id').addClass('hidden');
				}

				//  name
				if (!pageData.forcedAnon && !$ele.data('hiddenByName')) {
					$buffer.find('#filter-add-name').click(function () {
						addFilter('name', postName, false);
					});

					$buffer.find('#filter-remove-name').addClass('hidden');
				} else if (!pageData.forcedAnon) {
					$buffer.find('#filter-remove-name').click(function () {
						removeFilter('name', postName, false);
					});

					$buffer.find('#filter-add-name').addClass('hidden');
				} else {
					// board has forced anon
					$buffer.find('#filter-remove-name').addClass('hidden');
					$buffer.find('#filter-add-name').addClass('hidden');
				}

				//  tripcode
				if (!pageData.forcedAnon && !$ele.data('hiddenByTrip') && postTrip !== '') {
					$buffer.find('#filter-add-trip').click(function () {
						addFilter('trip', postTrip, false);
					});

					$buffer.find('#filter-remove-trip').addClass('hidden');
				} else if (!pageData.forcedAnon && postTrip !== '') {
					$buffer.find('#filter-remove-trip').click(function () {
						removeFilter('trip', postTrip, false);
					});

					$buffer.find('#filter-add-trip').addClass('hidden');
				} else {
					// board has forced anon
					$buffer.find('#filter-remove-trip').addClass('hidden');
					$buffer.find('#filter-add-trip').addClass('hidden');
				}

				/*  hide sub menus if all items are hidden
				 */
				if (!$buffer.find('#filter-menu-remove > ul').children().not('.hidden').length) {
					$buffer.find('#filter-menu-remove').addClass('hidden');
				}
				if (!$buffer.find('#filter-menu-add > ul').children().not('.hidden').length) {
					$buffer.find('#filter-menu-add').addClass('hidden');
				}
			});
		}

		/* 
		 *  hide/unhide thread on index view
		 */
		function quickToggle(ele, threadId, pageData) {
			/*if ($(ele).find('.hide-thread-link').length)
				$('.hide-thread-link').remove();*/

			if ($(ele).hasClass('op') && !$(ele).find('.hide-thread-link').length) {
				$('<a class="hide-thread-link" style="float:left;margin-right:5px" href="javascript:void(0)">[' + ($(ele).data('hidden') ? '+' : '–') + ']</a>')
					.insertBefore($(ele).find(':not(h2,h2 *):first'))
					.click(function() {
						var postId = $(ele).find('.post_no').not('[id]').text();
						var hidden = $(ele).data('hidden');
						var boardId = $(ele).parents('.thread').data('board');
					
						if (hidden) {
							blacklist.remove.post(boardId, threadId, postId, false);
							$(this).html('[–]');
						} else {
							blacklist.add.post(boardId, threadId, postId, false);
							$(this).text('[+]');
						}
					});
			}
		}

		/*
		 *  determine whether the reply post should be hidden
		 *   - applies to all posts on page load or filtering rule change
		 *   - apply to new posts on thread updates
		 *   - must explicitly set the state of each attributes because filter will reapply to all posts after filtering rule change
		 */
		function filter(post, threadId, pageData) {
			var $post = $(post);

			var list = getList();
			var postId = $post.find('.post_no').not('[id]').text();
			var name, trip, uid, subject, comment;
			var i, length, array, rule, pattern;  // temp variables

			var boardId	      = $post.data('board');
			if (!boardId) boardId = $post.parents('.thread').data('board');

			var localList   = pageData.localList;
			var noReplyList = pageData.noReplyList;
			var hasUID      = pageData.hasUID;
			var forcedAnon  = pageData.forcedAnon;

			var hasTrip = ($post.find('.trip').length > 0);
			var hasSub = ($post.find('.subject').length > 0);

			$post.data('hidden', false);
			$post.data('hiddenByUid', false);
			$post.data('hiddenByPost', false);
			$post.data('hiddenByName', false);
			$post.data('hiddenByTrip', false);
			$post.data('hiddenBySubject', false);
			$post.data('hiddenByComment', false);

			// add post with matched UID to localList
			if (hasUID &&
				typeof list.postFilter[boardId] != 'undefined' &&
				typeof list.postFilter[boardId][threadId] != 'undefined') {
				uid = $post.find('.poster_id').text();
				array = list.postFilter[boardId][threadId];

				for (i=0; i<array.length; i++) {
					if (array[i].uid == uid) {
						$post.data('hiddenByUid', true);
						localList.push(postId);
						if (array[i].hideReplies) noReplyList.push(postId);
						break;
					}
				}
			}

			// match localList
			if (localList.length) {
				if ($.inArray(postId, localList) != -1) {
					if ($post.data('hiddenByUid') !== true) $post.data('hiddenByPost', true);
					hide(post);
				}
			}

			// matches generalFilter
			if (!forcedAnon)
				name = (typeof $post.find('.name').contents()[0] == 'undefined') ? '' : nameSpanToString($post.find('.name')[0]);
			if (!forcedAnon && hasTrip)
				trip = $post.find('.trip').text();
			if (hasSub)
				subject = $post.find('.subject').text();

			array = $post.find('.body').contents().filter(function () {if ($(this).text() !== '') return true;}).toArray();
			array = $.map(array, function (ele) {
				return $(ele).text().trim();
			});
			comment = array.join(' ');


			for (i = 0, length = list.generalFilter.length; i < length; i++) {
				rule = list.generalFilter[i];

				if (rule.regex) {
					pattern = new RegExp(rule.value);
					switch (rule.type) {
						case 'name':
							if (!forcedAnon && pattern.test(name)) {
								$post.data('hiddenByName', true);
								hide(post);
							}
							break;
						case 'trip':
							if (!forcedAnon && hasTrip && pattern.test(trip)) {
								$post.data('hiddenByTrip', true);
								hide(post);
							}
							break;
						case 'sub':
							if (hasSub && pattern.test(subject)) {
								$post.data('hiddenBySubject', true);
								hide(post);
							}
							break;
						case 'com':
							if (pattern.test(comment)) {
								$post.data('hiddenByComment', true);
								hide(post);
							}
							break;
					}
				} else {
					switch (rule.type) {
						case 'name':
							if (!forcedAnon && rule.value == name) {
								$post.data('hiddenByName', true);
								hide(post);
							}
							break;
						case 'trip':
							if (!forcedAnon && hasTrip && rule.value == trip) {
								$post.data('hiddenByTrip', true);
								hide(post);
							}
							break;
						case 'sub':
							pattern = new RegExp('\\b'+ rule.value+ '\\b');
							if (hasSub && pattern.test(subject)) {
								$post.data('hiddenBySubject', true);
								hide(post);
							}
							break;
						case 'com':
							pattern = new RegExp('\\b'+ rule.value+ '\\b');
							if (pattern.test(comment)) {
								$post.data('hiddenByComment', true);
								hide(post);
							}
							break;
					}
				}
			}

			// check for link to filtered posts
			$post.find('.body a').not('[rel="nofollow"]').each(function () {
				var replyId = $(this).text().match(/^>>(\d+)$/);

				if (!replyId)
					return;

				replyId = replyId[1];
				if ($.inArray(replyId, noReplyList) != -1) {
					hide(post);
				}
			});

			// post didn't match any filters
			if (!$post.data('hidden')) {
				show(post);
			}
		}

		/*  (re)runs the filter on the entire page
		 */
		 function filterPage(pageData) {
			var list = getList();

			if (active_page != 'catalog') {

				// empty the local and no-reply list
				pageData.localList = [];
				pageData.noReplyList = [];

				$('.thread').each(function () {
					var $thread = $(this);
					// disregard the hidden threads constructed by post-hover.js
					if ($thread.css('display') == 'none')
						return;

					var threadId = $thread.attr('id').replace('thread_', '');
					var boardId = $thread.data('board');
					var op = $thread.children('.op')[0];
					var i, array;  // temp variables

					// add posts to localList and noReplyList
					if (typeof list.postFilter[boardId] != 'undefined' && typeof list.postFilter[boardId][threadId] != 'undefined') {
						array = list.postFilter[boardId][threadId];
						for (i=0; i<array.length; i++) {
							if ( typeof array[i].post == 'undefined')
								continue;

							pageData.localList.push(array[i].post);
							if (array[i].hideReplies) pageData.noReplyList.push(array[i].post);
						}
					}
					// run filter on OP
					filter(op, threadId, pageData);
					quickToggle(op, threadId, pageData);

					// iterate filter over each post
					if (!$(op).data('hidden') || active_page == 'thread') {
						$thread.find('.reply').not('.hidden').each(function () {
							filter(this, threadId, pageData);
						});
					}

				});
			} else {
				var postFilter = list.postFilter[pageData.boardId];
				var $collection = $('.mix');

				if ($.isEmptyObject(postFilter))
					return;

				// for each thread that has filtering rules
				// check if filter contains thread OP and remove the thread from catalog
				$.each(postFilter, function (key, thread) {
					var threadId = key;
					$.each(thread, function () {
						if (this.post == threadId) {
							$collection.filter('[data-id='+ threadId +']').remove();
						}
					});
				});
			}
		 }

		function initStyle() {
			var $ele, cssStyle, cssString;

			$ele = $('<div>').addClass('post reply').hide().appendTo('body');
			cssStyle = $ele.css(['background-color', 'border-color']);
			cssStyle.hoverBg = $('body').css('background-color');
			$ele.remove();

			cssString = '\n/*** Generated by post-filter ***/\n' +
				'#filter-control input[type=text] {width: 130px;}' +
				'#filter-control input[type=checkbox] {vertical-align: middle;}' +
				'#filter-control #clear {float: right;}\n' +
				'#filter-container {margin-top: 20px; border: 1px solid; height: 270px; overflow: auto;}\n' +
				'#filter-list {width: 100%; border-collapse: collapse;}\n' +
				'#filter-list th {text-align: center; height: 20px; font-size: 14px; border-bottom: 1px solid;}\n' +
				'#filter-list th:nth-child(1) {text-align: center; width: 70px;}\n' +
				'#filter-list th:nth-child(2) {text-align: left;}\n' +
				'#filter-list th:nth-child(3) {text-align: center; width: 58px;}\n' +
				'#filter-list tr:not(#header) {height: 22px;}\n' +
				'#filter-list tr:nth-child(even) {background-color:rgba(255, 255, 255, 0.5);}\n' +
				'#filter-list td:nth-child(1) {text-align: center; width: 70px;}\n' +
				'#filter-list td:nth-child(3) {text-align: center; width: 58px;}\n' +
				'#confirm {text-align: right; margin-bottom: -18px; padding-top: 2px; font-size: 14px; color: #FF0000;}';

			if (!$('style.generated-css').length) $('<style class="generated-css">').appendTo('head');
			$('style.generated-css').html($('style.generated-css').html() + cssString);
		}

		function drawFilterList() {
			var list = getList().generalFilter;
			var $ele = $('#filter-list');
			var $row, i, length, obj, val;

			var typeName = {
				name: 'name',
				trip: 'tripcode',
				sub: 'subject',
				com: 'comment'
			};

			$ele.empty();

			$ele.append('<tr id="header"><th>Type</th><th>Content</th><th>Remove</th></tr>');
			for (i = 0, length = list.length; i < length; i++) {
				obj = list[i];

				// display formatting
				val = (obj.regex) ? '/'+ obj.value +'/' : obj.value;

				$row = $('<tr>');
				$row.append(
					'<td>'+ typeName[obj.type] +'</td>',
					'<td>'+ val +'</td>',
					$('<td>').append(
						$('<a>').html('X')
							.addClass('del-btn')
							.attr('href', '#')
							.data('type', obj.type)
							.data('val', obj.value)
							.data('useRegex', obj.regex)
					)
				);
				$ele.append($row);
			}
		}

		function initOptionsPanel() {
			if (window.Options && !Options.get_tab('filter')) {
				Options.add_tab('filter', 'list', _('Filters'));
				Options.extend_tab('filter',
					'<div id="filter-control">' +
						'<select>' +
							'<option value="name">'+_('Name')+'</option>' +
							'<option value="trip">'+_('Tripcode')+'</option>' +
							'<option value="sub">'+_('Subject')+'</option>' +
							'<option value="com">'+_('Comment')+'</option>' +
						'</select>' +
						'<input type="text">' +
						'<input type="checkbox">' +
						'regex ' +
						'<button id="set-filter">'+_('Add')+'</button>' +
						'<button id="clear">'+_('Clear all filters')+'</button>' +
						'<div id="confirm" class="hidden">' +
							_('This will clear all filtering rules including hidden posts.')+' <a id="confirm-y" href="#">'+_('yes')+'</a> | <a id="confirm-n" href="#">'+_('no')+'</a>' +
						'</div>' +
					'</div>' +
					'<div id="filter-container"><table id="filter-list"></table></div>'
				);
				drawFilterList();

				// control buttons
				$('#filter-control').on('click', '#set-filter', function () {
					var type = $('#filter-control select option:selected').val();
					var value = $('#filter-control input[type=text]').val();
					var useRegex = $('#filter-control input[type=checkbox]').prop('checked');

					//clear the input form
					$('#filter-control input[type=text]').val('');

					addFilter(type, value, useRegex);
					drawFilterList();
				});
				$('#filter-control').on('click', '#clear', function () {
					$('#filter-control #clear').addClass('hidden');
					$('#filter-control #confirm').removeClass('hidden');
				});
				$('#filter-control').on('click', '#confirm-y', function (e) {
					e.preventDefault();

					$('#filter-control #clear').removeClass('hidden');
					$('#filter-control #confirm').addClass('hidden');
					setList({
						generalFilter: [],
						postFilter: {},
						nextPurge: {},
						lastPurge: timestamp()
					});
					drawFilterList();
				});
				$('#filter-control').on('click', '#confirm-n', function (e) {
					e.preventDefault();

					$('#filter-control #clear').removeClass('hidden');
					$('#filter-control #confirm').addClass('hidden');
				});


				// remove button
				$('#filter-list').on('click', '.del-btn', function (e) {
					e.preventDefault();

					var $ele = $(e.target);
					var type = $ele.data('type');
					var val = $ele.data('val');
					var useRegex = $ele.data('useRegex');

					removeFilter(type, val, useRegex);
				});
			}
		}

		/* 
		 *  clear out pruned threads
		 */
		function purge() {
			var list = getList();
			var board, thread, boardId, threadId;
			var deferred;
			var requestArray = [];

			var successHandler = function (boardId, threadId) {
				return function () {
					// thread still alive, keep it in the list and increase the time between checks.
					var list = getList();
					var thread = list.nextPurge[boardId][threadId];

					thread.timestamp = timestamp();
					thread.interval = Math.floor(thread.interval * 1.5);
					setList(list);
				};
			};
			var errorHandler = function (boardId, threadId) {
				return function (xhr) {
					if (xhr.status == 404) {
						var list = getList();

						delete list.nextPurge[boardId][threadId];
						delete list.postFilter[boardId][threadId];
						if ($.isEmptyObject(list.nextPurge[boardId])) delete list.nextPurge[boardId];
						if ($.isEmptyObject(list.postFilter[boardId])) delete list.postFilter[boardId];
						setList(list);
					}
				};
			};

			if ((timestamp() - list.lastPurge) < 86400)  // less than 1 day
				return;
			
			for (boardId in list.nextPurge) {
				board = list.nextPurge[boardId];
				for (threadId in board) {
					thread = board[threadId];
					if (timestamp() > (thread.timestamp + thread.interval)) {
						// check if thread is pruned
						deferred = $.ajax({
							cache: false,
							url: '/'+ boardId +'/res/'+ threadId +'.json',
							success: successHandler(boardId, threadId),
							error: errorHandler(boardId, threadId)
						});
						requestArray.push(deferred);
					}
				}
			}

			// when all requests complete
			$.when.apply($, requestArray).always(function () {
				var list = getList();
				list.lastPurge = timestamp();
				setList(list);
			});
		}

		function init() {
			if (typeof localStorage.postFilter === 'undefined') {
				localStorage.postFilter = JSON.stringify({
					generalFilter: [],
					postFilter: {},
					nextPurge: {},
					lastPurge: timestamp()
				});
			}

			var pageData = {
				boardId: board_name,  // get the id from the global variable
				localList: [],  // all the blacklisted post IDs or UIDs that apply to the current page
				noReplyList: [],  // any posts that replies to the contents of this list shall be hidden
				hasUID: (document.getElementsByClassName('poster_id').length > 0),
				forcedAnon: ($('th:contains(Name)').length === 0)  // tests by looking for the Name label on the reply form
			};

			initStyle();
			initOptionsPanel();
			initPostMenu(pageData);
			filterPage(pageData);

			// on new posts
			$(document).on('new_post', function (e, post) {
				var threadId;

				if ($(post).hasClass('reply')) {
					threadId = $(post).parents('.thread').attr('id').replace('thread_', '');
				} else {
					threadId = $(post).attr('id').replace('thread_', '');
					post = $(post).children('.op')[0];
				}

				filter(post, threadId, pageData);
				quickToggle(post, threadId, pageData);
			});

			$(document).on('filter_page', function () {
				filterPage(pageData);
			});

			// shift+click on catalog to hide thread
			if (active_page == 'catalog') {
				$(document).on('click', '.mix', function(e) {
					if (e.shiftKey) {
						var threadId = $(this).data('id').toString();
						var postId = threadId;
						blacklist.add.post(pageData.boardId, threadId, postId, false);
					}
				});
			}

			// clear out the old threads
			purge();
		}
		init();
	});
	
	if (typeof window.Menu !== "undefined") {
		$(document).trigger('menu_ready');
	}
}

Preview for Youtube Videos

Show preview for youtube video links.

Expand to view the script

(function youtubeInlinePreviews() {

const re = /(?:youtube\.com\/watch\?v=|youtu\.be\/)(.{11})/;

const previewTemplate = (videoId) => `<img

style="max-width:255px;max-height:255px"

src="https://i.ytimg.com/vi/${videoId}/hqdefault.jpg"

/>`;

Array.from(document.querySelectorAll(".body a"))

.filter((link) => link.href.match(re))

.forEach((link) => {

const videoId = link.href.match(re)[1];

const inlinePreview = previewTemplate(videoId);

link.innerHTML = inlinePreview;

});

})();

Filter tripchuds

To use it, change "!!TRIP GOES HERE" to any trip you want to filter (ex: !incelchud1 or !!incelchud1), can also be used to filter names if you change

(/class="trip">!!TRIP GOES HERE</)

to

(/class="name">name of the chudcel you want to filter</)

if you want to filter multiple tripchuds, you have to do this

(/class="trip">!!FIRST TRIP CHUD|!SECOND TRIPCHUD|!THIRD TRIPCHUD</)

Expand to view the script

$(".post")
  .filter(function (index) {
    return this.innerHTML.match(/class="trip">!!TRIP GOES HERE</);
  })
  .each(function (index) {
    let whatToHide = "#" + this.id;
    if (this.id.startsWith("op_")) {
      whatToHide = "#" + this.id.replace("op_", "thread_");
    }
    $(whatToHide).hide();
  });

if (!localStorage.favorites) {
	localStorage.favorites = '[]';
}

Hide tripchud posts from catalog

Hides all tripchud posts from catalog, no exceptions.

Expand to view the script

// --- start: purgeTripsFromCatalog ---

(function purgeTripsFromCatalog() {
  // If on a catalog page
  if (document.location.href.match(/catalog\.html$/)) {
    // Call the API for that page
    fetch(document.location.href.replace(".html", ".json"))
      .then((res) => res.json())
      .then((json) =>
        json
          // Find the threads where OP is using a tripcode
          .reduce((acc, cur) => [...acc, ...cur.threads], [])
          .filter((op) => op.trip)
          // Hide them
          .forEach((filtered) => $(`div[data-id='${filtered.no}']`).hide())
      );
  }
})();

// --- end: purgeTripsFromCatalog ---