// ==UserScript==
// @name thisvid infinite scroll and filter
// @license MIT
// @namespace http://tampermonkey.net/
// @version 2.5
// @description infinite scroll, filter: public, private, duration, positive/negative tags
// @author smartacephale
// @match https://thisvid.com/*
// @exclude https://thisvid.com/playlists/*
// @exclude https://thisvid.com/community/*
// @exclude https://thisvid.com/albums/*
// @exclude https://thisvid.com/my*
// @icon https://i.imgur.com/LAhknzo.jpeg
// @grant GM_addStyle
// @run-at document-end
// ==/UserScript==
const logo = `
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░║▓▓▓▀░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
░░░░░░░░░░░░░░░░░░░░░▒▒▒▒▒▒░░░░░░░░░░░░░░░░░▒▓▓▓▒░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
░░░░░░░░░░░░░░░▒▒▒▒▒▒▒▒▒▒▒▒░▒▒▒▒▒░░░░░░░░░░╫█▓▓▓▒░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
░░░░░░░░░░░░▒▒▒▒▒░▒░░░░░░░░░░▒▒▒▒▒▒@@▒▒▒▒╖░▓▓▓▓▓▒░░░░░░░░░░░░░▒▒▒▒▒░░░░░░░░░░░░░
░░░░░░░░░▒▒▒▒▒▒░▒░░░░░░░░░░░░░░░▒▒▒▒╣╣╢╢╣╣╣▓▓▓▓▓▓▓▓▓▓▓╣╣▒▒▒▒▒░░░░░░░░▒▒░░░░░░░░░
░░░░░░░░▒▒▒▒▒░░░░░░░░░░░░░░░░░░░░▒▒▒▒╢╢╫▓▓▓▓▓▓▓▓▓▓▓▓▓╣▒▒▒░░░░░░░░░░░░░░░░░░░░░░░
░░░░░░▒▒▒▒▒▒░░░░░░░░░░░░░░░░░░░░░░▒▒▒▒▒╢▓▓▓▓▓▓▓▓▓▓▓▒▒▒░░░░░░░░░░░░░░░░░░░░░░░░░░
░░░░░▒▒▒▒▒░░░░░░░░░░░░░░░░░░░░░░▒░░░▒▒▒▒╫▓▓▓▓▓▓▓▓▒▒░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
░░░░▒▒▒░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░▒▒╫▓▓▓▓▓▒▒▒░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
░░░▒▒▒▒░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░▒▒╫▓▓▓▓▒░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
░░▒▒▒▒▒░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░▒╢▓▓▓▒▒░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
░▒▒▒▒▒░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░▒╢╣▒▒░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
▒▒▒▒░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░▒▒▒╣▒▒▒░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
▒▒▒░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░▒▒▒▒▒▒╣╣╣▒▒▒▒░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
▒▒▒▒░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░▒▒▒▒╣╣▒▒▒╢▒▒▒▒▒▒▒░░░░░░░░░░░░░░░░░░░░░░░░░░░░
▒▒▒▒░░░░░░░░░░░░░░░░░░░░░░░░░░▒░▒▒▒▒▒▒▒╢╢╣▒▒╢╣▒▒▒▒▒▒▒▒░░░░░░░░░░░░░░░░░░░░░░░░░░
▒▒▒░░░░░░░░░░░░░░░░░░░░░░░░░▒▒▒▒▒▒▒▒▒▒▒╢╣╣▒▒╢▓▒▒▒▒▒▒▒▒▒░░░░░░░░░░░░░░░░░░░░░░░░░
▒▒▒░░░░░░░░░░░░░░░░░░░░░░░░░░░▒▒▒▒▒▒▒▒╢╫╣╣▒▒╢▓▒▒▒▒▒▒▒▒▒▒░░░░░░░░░░░░░░░░░░░░░░░░
▒▒░░░░░░░░░░░░░░░░░░░░░░░░░░▒▒▒▒▒▒▒░▒▒╣╢╣▒▒▒▒▓╣▒▒▒▒▒▒▒▒▒░░░░░░░░░░░░░░░░░░░░░░░░
▒▒░░░░░░░░░░░░░░░░░░░░░░░░░░▒▒▒░▒▒▒▒▒▒╫╣▒▒▒▒▒╫╣▒▒▒▒▒▒▒▒▒░░░░░░░░░░░░░░░░░░░░░░░░
▒▒▒░░░░░░░░░░░░░░░░░░░░░░░░░░▒▒▒▒▒▒░▒▒╢╢▒▒╢╣╣╢╫╣╣▒▒▒▒▒▒▒░░░░░░░░░░░░░░░░░░░░░░░░
▒▒░░░░░░░░░░░░░░░░░░░░░░░░░░░▒▒▒▒▒▒▒▒▒╢╫╣╣▒╢╣▒▒▒╣▒▒▒▒▒▒░░░░░░░░░░░░░░░░░░░░░░░░░
▒▒░░░░░░░░░░░░░░░░░░░░░░░░░░░░▒▒▒▒▒▒▒╢▒▒▒╢▓▓╣▒▒▒▒▒▒▒▒▒▒░░░░░░░░░░░░░░░░░░░░░░░░░
▒▒░░░░░░░░░░░░░░░░░░░░░░░░░░░░░▒▒▒▒▒▒▒▒▒▓▓╣▓╣▒▒▒▒░▒▒▒▒░░░░░░░░░░░░░░░░░░░░░░░░░░
▒▒▒░▒░░░░░░░░░░░░░░░░░░░░░░░░▒▒▒▒▒▒▒░▒▒▒╢▓▓▓╬▒▒▒▒▒▒▒▒░░░░░░░░░░░░░░░░░░░░░░░░░░░
▒▒▒░░░░░░░░░░░░░░░░░░░░░░░░░░▒▒▒▒▒▒▒▒▒▒▒▒╢▓╣▒▒▒▒▒▒▒░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
▒▒▒▒▒░░░░░░░░░░░░░░░░░░░░░░░░░▒▒▒▒▒▒▒▒▒▒▒╣╣▒▒▒▒▒▒▒░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
▒▒▒▒▒▒░░░░░░░░░░░░░░░░░░░░░░░░░░▒▒▒▒▒▒▒▒╣╣╣▒▒▒▒▒▒░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
▒▒▒▒▒▒▒░░░░░░░░░░░░░░░░░░░░░░░░░░▒▒▒▒▒▒▒╣╢╣▒▒▒░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
▒▒▒▒▒▒▒▒░░░░░░░░░░░░░░░░░░░░░░░░░░░▒▒▒▒▒╢▒╣▒▒▒░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
▒▒▒▒▒▒▒▒▒░░░░░░░░░░░░░░░░░░░░░░░░░░░░▒╢▓▓╣╫▓╣▒░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
▒▒▒▒▒▒▒▒▒▒▒░░░░░░░░░░░░░░░░░░░░░░░░▒▒▒╫▓▓▓▓▓▓╣▒▒░░░░░░░░░░░░░░░░░░░░░░░░░▒░░░░░░
▒▒▒▒▒▒▒▒▒▒▒▒▒░░░░░░░░░░░░░░░░░░░░▒▒▒▒╫▓▓▓▓▓▓▓▓▓╣╣▒▒░░░░░░░░░░░░░░░░░░░░▒▒░░░░░░░
▒▒▒▒▒▒▒▒╣▒╣▒▒▒░▒░░░░░░░░░░░░░░▒▒▒▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓╣╫╣▒▒░░░░░░░░░░░░░░▒▒░░░░░░░░░░
▒▒▒▒▒▒▒▒╢╣╢╢▓▓╣▒▒▒▒▒▒▒▒▒░░░▒▒▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓█▓▓▓▓▓╣▒▒▒▒▒▒▒░░░▒▒▒▒▒░░░░░░░░░░░
░░░░░░░░░░░░▒▒▒╨╨╢▒▒▒▒▒▒▒▒▒▒╨╨╜╜▒▒▒▒▒▒╜▒▒╨╨╨╨╨╨╨╨╨╨╨▀▒╫▒▒╜╣╫╝,░▒▒░]▒▒▒▒╨▒▒░╜Ñ▒╓─`.trim();
(function () {
'use strict';
console.clear();
console.log(logo);
class Utils {
static $ = (x, e = document.body) => e.querySelector(x);
static $$ = (x, e = document.body) => e.querySelectorAll(x);
static parseDOM = (html) => {
const parsed = new DOMParser().parseFromString(html, 'text/html');
return parsed.body.children.length > 1 ? parsed.body : parsed.body.firstElementChild;
}
static fetchHtml = (url) => fetch(url).then((r) => r.text()).then((h) => parseDOM(h));
static parseCSSUrl = (s) => s.replace(/url\("|\"\).*/g, '');
static timeToSeconds = (t) =>
(t.match(/\d+/gm) || ['0'])
.reverse()
.map((s, i) => parseInt(s) * 60 ** i)
.reduce((a, b) => a + b) || 0;
static circularShift = (n, c = 6, s = 1) => (n + s) % c || c;
static parseIntegerOr = (n, or) =>
Number.isInteger(parseInt(n)) ? parseInt(n) : or;
}
const {
$,
$$,
parseDOM,
fetchHtml,
parseCSSUrl,
circularShift,
timeToSeconds,
parseIntegerOr,
} = Utils;
class IntersectionObserver_ {
constructor(callback) {
this.callback = callback;
this.observer = this.createObserver();
}
createObserver() {
return new IntersectionObserver((entries, observer) => {
for (const entry of entries) {
if (entry.isIntersecting) {
this.callback(entry.target, observer);
}
}
});
}
observe(element) {
this.observer.observe(element);
}
}
class Tick {
constructor(interval) {
this.interval = interval;
}
start(callback) {
if (this.ticker) {
this.stop();
}
callback();
this.ticker = setInterval(callback, this.interval);
}
stop() {
clearInterval(this.ticker);
this.ticker = false;
}
}
class ReactiveLocalStorage {
constructor(data) {
if (data) {
Object.assign(this, data);
this.observeProps(this);
}
}
getLS(prop) {
return JSON.parse(localStorage.getItem(prop));
}
setLS(prop, value) {
localStorage.setItem(prop, JSON.stringify(value));
}
toObservable(obj, prop) {
const lsvalue = this.getLS(prop);
let value = lsvalue !== null ? lsvalue : obj[prop];
Object.defineProperty(obj, prop, {
get() {
return value;
},
set(newValue) {
this.setLS(prop, newValue);
value = newValue;
},
});
}
observeProps(obj) {
for (const [key, _] of Object.entries(obj)) {
this.toObservable(obj, key);
}
}
}
class DomManager {
constructor() {
this.data = new Map();
this.container = this.createThumbsContainer();
this.observables = [];
this.lazyImageObserver = new IntersectionObserver_((target, observer) => {
if (!this.isFiltered(target)) {
observer.unobserve(target);
this.delazify(target);
}
});
}
setObservation() {
for (const observable of this.observables) {
this.lazyImageObserver.observe(observable);
}
this.observables = [];
}
delazify(el) {
const img = el.firstElementChild.firstElementChild;
img.src = img.getAttribute('lazy-loading');
}
isFiltered(el) {
return el.className.includes('filtered');
}
filterPositiveTags = () => {
for (const [k, v] of this.data.entries()) {
const containTagsNot = state.filterPositiveTags.some(tag => !k.includes(tag));
v.element.classList.toggle('filtered-tag-pos', state.filterPositive && containTagsNot);
};
}
filterNegativeTags = () => {
for (const [k, v] of this.data.entries()) {
const containTags = state.filterNegativeTags.some(tag => k.includes(tag));
v.element.classList.toggle('filtered-tag-neg', state.filterNegative && containTags);
};
}
thumbIsPrivate(t) {
return t.firstElementChild.classList.contains('private');
}
filterPrivate = (filterPrivate = state.filterPrivate) => {
for (const v of this.data.values()) {
v.element.classList.toggle('filtered-private',
filterPrivate && this.thumbIsPrivate(v.element))
};
}
filterPublic = (filterPublic = state.filterPublic) => {
for (const v of this.data.values()) {
v.element.classList.toggle('filtered-public',
filterPublic && !this.thumbIsPrivate(v.element));
};
}
filterByDuration = () => {
const { filterDurationFrom: from, filterDurationTo: to, filterDuration } = state;
for (const v of this.data.values()) {
v.element.classList.toggle('filtered-duration',
filterDuration && (v.duration < from || v.duration > to));
};
}
runFilters() {
if (state.filterPrivate) this.filterPrivate();
if (state.filterPublic) this.filterPublic();
if (state.filterDuration) this.filterByDuration();
if (state.filterPositive) this.filterPositiveTags();
if (state.filterNegative) this.filterNegativeTags();
}
createThumbsContainer() {
return parseDOM('<div class="thumbs-items"></div>');
}
handleLoadedHTML = (htmlPage, mount, useGlobalContainer = true) => {
const thumbs = $$('.tumbpu', htmlPage);
const container = !useGlobalContainer ? this.createThumbsContainer() : this.container;
for (const thumbElement of thumbs) {
const url = thumbElement.getAttribute('href');
if (!url || this.data.has(url)) {
thumbElement.remove();
} else {
this.data.set(url, {
element: thumbElement,
duration: timeToSeconds($('.thumb > .duration', thumbElement).textContent),
});
const img = $('img', thumbElement);
const thumb2 = $('.private', thumbElement);
let imgSrc;
if (thumb2) {
imgSrc = parseCSSUrl(thumb2.style.background);
thumb2.style.removeProperty('background');
thumb2.removeAttribute('style');
} else {
imgSrc = img.getAttribute('data-original');
img.removeAttribute('data-original');
}
img.setAttribute('lazy-loading', imgSrc);
img.src = '';
img.classList.add('tracking');
img.removeAttribute('data-cnt');
container.appendChild(thumbElement);
this.observables.push(thumbElement);
}
}
this.runFilters(container);
this.setObservation();
mount.before(container);
};
}
class PreviewManager {
constructor() {
this.tick = new Tick(ANIMATION_DELAY);
}
iteratePreviewImages(src) {
return src.replace(/(\d)\.jpg$/, (_, n) => `${circularShift(parseInt(n))}.jpg`);
}
animatePreview = (e) => {
const { target: el, type, src } = e;
if (el.tagName === 'IMG' && el.classList.contains('tracking')) {
if (type === 'mouseout') {
this.tick.stop();
el.src = el.getAttribute('lazy-loading');
}
if (type === 'mouseover') {
if (!src) el.src = el.getAttribute('lazy-loading');
this.tick.start(() => {
el.src = this.iteratePreviewImages(el.src);
});
}
}
};
listen(e) {
e.addEventListener('mouseout', this.animatePreview);
e.addEventListener('mouseover', this.animatePreview);
}
}
class PaginationPageManager {
constructor() {
this.pagination = $('.pagination');
this.pagination.style.opacity = 0;
unsafeWindow.$('img[alt!="Private"]').off('mouseover');
unsafeWindow.$('img[alt!="Private"]').off('mouseout');
handleLoadedHTML(document.body, this.pagination);
previewManager.listen(this.pagination.parentElement);
this.offsetLast = this.getOffsetLast();
this.paginationGenerator = this.createNextPageGenerator();
this.generatorDone = false;
this.ui = new UI(true);
this.setPagIndex = (offset) =>
this.ui.setPagIndex(offset, this.offsetLast);
this.setPagIndex(this.getCurrentOffset());
this.paginationObserver = new IntersectionObserver_((target, observer) => {
this.generatorConsumer();
if (!this.generatorDone) {
observer.unobserve(target);
setTimeout(() => {
observer.observe(target);
}, SCROLL_RESET_DELAY);
}
});
this.paginationObserver.observe(this.pagination);
}
async generatorConsumer() {
const {
value: { url, offset } = {},
done,
} = this.paginationGenerator.next();
this.generatorDone = done;
if (!done) {
const nextPageHTML = await fetchHtml(url);
const prevScrollPos = document.documentElement.scrollTop;
handleLoadedHTML(nextPageHTML, this.pagination);
this.setPagIndex(offset);
window.scrollTo(0, prevScrollPos);
}
}
getCurrentOffset() {
return parseInt(window.location.pathname.split(/(\d+\/)$/)[1] || '1');
}
getOffsetLast() {
return parseInt(
$('.pagination-next').previousElementSibling.firstElementChild
.textContent,
);
}
createNextPageGenerator() {
let { origin, pathname, search } = window.location;
let offset;
[pathname, offset = '1'] = pathname.split(/(\d+\/)$/);
offset = parseInt(offset);
pathname = pathname === '/' ? '/latest-updates/' : pathname;
const offsetLast = this.getOffsetLast();
function* nextPageGenerator() {
for (let c = offset + 1; c <= offsetLast; c++) {
const url = `${origin}${pathname}${c}/${search}`;
console.log(url);
yield { url, offset: c };
}
}
return nextPageGenerator();
}
}
class Router {
constructor() {
this.route();
}
route() {
const { pathname, href } = window.location;
if ($('.pagination-next')) {
this.handlePaginationPage();
} else if (/\/members\/\d+\/$/.test(pathname)) {
this.handleMemberPage();
} else if (/\/tag\//.test(href) || /\/?q=.*/.test(href)) {
this.handlePageWithVideosButNoPagination();
}
}
handlePageWithVideosButNoPagination() {
const vid = $('.tumbpu');
if (!vid) return;
handleLoadedHTML(document.body, vid.parentElement);
previewManager.listen(vid.parentElement);
const ui = new UI(false);
}
handlePaginationPage() {
this.paginationManager = new PaginationPageManager();
}
handleMemberPage() {
const privates = $('#list_videos_private_videos_items');
if (privates) {
const mistakes = document.querySelectorAll('#list_videos_private_videos_items ~ div');
for (const m of mistakes) {
if (m.id === 'list_videos_favourite_videos') break;
privates.appendChild(m);
}
handleLoadedHTML(privates, privates, false);
}
const favorites = $('#list_videos_favourite_videos');
if (favorites) {
const mountTo = favorites.firstElementChild.nextElementSibling;
handleLoadedHTML(favorites, mountTo, false);
}
if (privates || favorites) {
previewManager.listen((privates || favorites).parentElement);
const ui = new UI(false);
}
}
}
class UI {
templateHTML = (haspag) => `
<div id="tapermonkey-app">
<div class="subbox">
<input type="checkbox" id="filterNegative" name="filterNegative" ${state.filterNegative ? 'checked' : ''}/>
<label for="filterNegative">negative filter</label>
<textarea id="filterNegativeText" placeholder="tag1, tag2,..">${state.filterNegativeTags.join(',')}</textarea>
</div>
<div class="subbox">
<input type="checkbox" id="filterPositive" name="filterPositive" ${state.filterPositive ? 'checked' : ''}/>
<label for="filterPositive">positive filter</label>
<textarea id="filterPositiveText" placeholder="tag1, tag2,..">${state.filterPositiveTags.join(',')}</textarea>
</div>
<div class="subbox">
<input type="checkbox" id="filterPrivate" name="filterPrivate" ${state.filterPrivate ? 'checked' : ''}/>
<label for="filterPrivate">filter private</label>
<input type="checkbox" id="filterPublic" name="filterPublic" ${state.filterPublic ? 'checked' : ''}/>
<label for="filterPublic">filter public</label>
${haspag ? '<span id="pagIndex">0/0</span>' : ''}
</div>
<div class="subbox">
<input type="checkbox" id="filterl" name="filterl" ${state.filterDuration ? 'checked' : ''}/>
<label for="filterl">filter duration seconds</label>
<label for="from">from</label>
<input type="number" placeholder="min sec" step="10"
min="0" max="72000" id="minL" name="from" value=${state.filterDurationFrom} />
<label for="to">to</label>
<input type="number" placeholder="max sec" step="10"
min="0" max="72000" id="maxL" name="to" value=${state.filterDurationTo} />
</div>
</div>`;
constructor(haspag = true) {
document.body.appendChild(parseDOM(this.templateHTML(haspag)));
this.tapermonkeyAppTemplate = document.querySelector('#tapermonkey-app');
this.control();
}
setPagIndex(index, total) {
$('#pagIndex').innerText = `${index}/${total}`;
}
stringToTags(s) {
return s.split(",").map(s => s.trim()).filter(s => s.length > 0);
}
control() {
this.tapermonkeyAppTemplate.addEventListener('input', (e) => {
const { id, value } = e.target;
if (id === 'filterNegativeText') {
state.filterNegativeTags = this.stringToTags(value);
filterNegativeTags()
}
if (id === 'filterPositiveText') {
state.filterPositiveTags = this.stringToTags(value);
filterPositiveTags();
}
});
this.tapermonkeyAppTemplate.addEventListener('click', (e) => {
const { id, checked, value } = e.target;
if (id === 'filterPublic') {
state.filterPublic = checked;
filterPublic();
}
if (id === 'filterPrivate') {
state.filterPrivate = checked;
filterPrivate();
}
if (id === 'filterNegative') {
state.filterNegative = checked;
filterNegativeTags()
}
if (id === 'filterPositive') {
state.filterPositive = checked;
filterPositiveTags();
}
if (id === 'filterl') {
state.filterDuration = checked;
filterByDuration();
}
if (id === 'minL') {
state.filterDurationFrom = parseIntegerOr(value, state.filterDurationFrom);
filterByDuration();
}
if (id === 'maxL') {
state.filterDurationTo = parseIntegerOr(value, state.filterDurationTo);
filterByDuration();
}
});
}
}
const SCROLL_RESET_DELAY = 500;
const ANIMATION_DELAY = 750;
const state = new ReactiveLocalStorage({
filterDurationFrom: 0,
filterDurationTo: 600,
filterDuration: false,
filterPrivate: false,
filterPublic: false,
infiniteScrollTriggered: false,
filterNegativeTags: [],
filterNegative: false,
filterPositiveTags: [],
filterPositive: false
});
const { filterPrivate, filterPublic, filterByDuration, filterPositiveTags, filterNegativeTags, handleLoadedHTML } = new DomManager();
const previewManager = new PreviewManager();
const router = new Router();
const tampermonkeyCSS = `
#tapermonkey-app {
background: #151515;
padding: 10px;
position: fixed;
z-index: 9999;
bottom: 10px;
right: 10px;
border-radius: 15px;
width: max-content;
box-shadow: 20px 20px 60px #000000, -20px -20px 60px #000000;
}
#tapermonkey-app .subbox {
background: #2c2c2c;
border-radius: 5px;
padding: 4px;
margin: 6px;
user-select: none;
display: flex;
}
#tapermonkey-app .subbox input[type=number] {
width: 5rem;
background: #26282b;
}
#tapermonkey-app .subbox input[type=checkbox] { margin-left: 5px; }
#tapermonkey-app .subbox label { margin: 0 10px 0 0; }
#pagIndex { text-align: end; margin-right: 5px; flex: 1;}
#tapermonkey-app textarea { flex: 1; height: 2rem; padding: 6px; }
#tapermonkey-app .subbox * {
padding-left: 8px;
float: none;
width: auto;
font-family: monospace;
font-size: 0.8rem;
align-self: center;
color: #969696;
}
.tracking { content-visibility: auto; }
.filtered-private, .filtered-duration, .filtered-public, .filtered-tag-pos, .filtered-tag-neg
{ display: none !important; } `;
GM_addStyle(tampermonkeyCSS);
})();