Autoplay in frame video previews plus case insensitive tag blocking
// ==UserScript==
// @name P0rnXP Enhanced
// @namespace http://tampermonkey.net/
// @version 1.4.0
// @description Autoplay in frame video previews plus case insensitive tag blocking
// @include /^https?:\/\/([^\/.]+\.)?porn-xp\.[^\/]+\/.*$/
// @include /^https?:\/\/([^\/.]+\.)?pornxp\.[^\/]+\/.*$/
// @include /^https?:\/\/([^\/.]+\.)?xpxp\.[^\/]+\/.*$/
// @include /^https?:\/\/([^\/.]+\.)?pxp\.[^\/]+\/.*$/
// @grant GM_addStyle
// @grant GM_registerMenuCommand
// @run-at document-end
// @license MIT
// ==/UserScript==
(function () {
'use strict';
const STORAGE_KEY = 'pxp_filters_v3';
const DEFAULTS = { blockedTags: [], hideBlocked: true };
const $ = (s, c = document) => c.querySelector(s);
const $$ = (s, c = document) => [...c.querySelectorAll(s)];
const escapeHtml = str => String(str).replace(/[&<>"']/g, c => ({'&':'&','<':'<','>':'>','"':'"',"'":'''}[c]));
// Safe state loading to prevent crashes from corrupted storage
let state = Object.assign({}, DEFAULTS);
try {
const saved = JSON.parse(localStorage.getItem(STORAGE_KEY) || '{}');
if (Array.isArray(saved.blockedTags)) state.blockedTags = saved.blockedTags;
if (typeof saved.hideBlocked === 'boolean') state.hideBlocked = saved.hideBlocked;
} catch (e) {
localStorage.removeItem(STORAGE_KEY);
}
let blockedSet = new Set(state.blockedTags.map(t => String(t).toLowerCase()));
const syncState = () => {
state.blockedTags = [...blockedSet];
localStorage.setItem(STORAGE_KEY, JSON.stringify(state));
applyFilter();
refreshPanelTagList();
};
// Wrapped in try-catch to prevent crashes on malformed URL tags
const parseTagName = href => {
try {
const m = href?.match(/\/tags\/([^/?#]+)/i);
return m ? decodeURIComponent(m[1]).toLowerCase() : null;
} catch { return null; }
};
const getItemTags = cont => $$('.item_tags a', cont).map(a => parseTagName(a.getAttribute('href'))).filter(Boolean);
function applyFilter() {
$$('.item_cont').forEach(cont => {
const blocked = getItemTags(cont).some(t => blockedSet.has(t));
if (blocked) {
cont.style.display = state.hideBlocked ? 'none' : '';
cont.style.opacity = state.hideBlocked ? '' : '0.2';
cont.style.filter = state.hideBlocked ? '' : 'blur(5px) grayscale(100%)';
cont.style.pointerEvents = state.hideBlocked ? '' : 'none';
} else {
cont.style.cssText = '';
}
});
}
const visObserver = new IntersectionObserver(entries => {
entries.forEach(e => {
const vid = e.target.querySelector('video.pxp-preview');
if (!vid) return;
if (e.isIntersecting) {
if (!vid.src && e.target.dataset.preview) vid.src = e.target.dataset.preview;
vid.style.opacity = '1';
vid.play().catch(() => {});
} else {
vid.pause();
vid.style.opacity = '0';
}
});
}, { rootMargin: '200px', threshold: 0.1 });
function initPreviews() {
$$('.item.preview').forEach(item => {
if (item.querySelector('video.pxp-preview')) return;
const thumb = $('.item_thumb', item);
if (!item.dataset.preview || !thumb) return;
const video = document.createElement('video');
video.className = 'pxp-preview';
video.loop = video.muted = video.playsInline = true;
video.preload = 'none';
thumb.appendChild(video);
visObserver.observe(item);
});
}
function addBlockButtons() {
$$('.item_tags a').forEach(a => {
if (a.querySelector('.pxp-block-btn')) return;
const tag = parseTagName(a.getAttribute('href'));
if (!tag) return;
const btn = document.createElement('span');
btn.className = 'pxp-block-btn';
btn.textContent = '×';
btn.title = `Block "${tag}"`;
btn.addEventListener('click', e => {
e.preventDefault(); e.stopPropagation();
blockedSet.add(tag); syncState();
});
a.appendChild(btn);
});
}
function refreshPanelTagList() {
const list = $('#pxp-taglist');
if (!list) return;
const tags = [...blockedSet].filter(Boolean);
list.innerHTML = tags.length ? tags.map(tag =>
`<div class="pxp-tag"><span>${escapeHtml(tag)}</span><button data-tag="${escapeHtml(tag)}">×</button></div>`
).join('') : '<div class="pxp-empty">No blocked tags</div>';
list.querySelectorAll('button').forEach(btn => btn.onclick = () => {
blockedSet.delete(btn.dataset.tag);
syncState();
});
}
function buildPanel() {
const panel = document.createElement('div');
panel.id = 'pxp-settings';
panel.innerHTML = `
<div class="pxp-header"><span>Filters</span><button class="pxp-close">×</button></div>
<div class="pxp-body">
<label class="pxp-row"><input type="checkbox" id="pxp-hide" ${state.hideBlocked ? 'checked' : ''}><span>Hide blocked items</span></label>
<div class="pxp-section">Blocked Tags</div>
<div class="pxp-taglist" id="pxp-taglist"></div>
<div class="pxp-addtag">
<input type="text" id="pxp-newtag" placeholder="Enter tag name">
<button id="pxp-add">Block</button>
</div>
</div>`;
panel.querySelector('.pxp-close').onclick = () => panel.classList.remove('open');
panel.querySelector('#pxp-hide').onchange = e => { state.hideBlocked = e.target.checked; syncState(); };
const addTag = () => {
const input = panel.querySelector('#pxp-newtag');
const tag = input.value.trim().toLowerCase();
if (tag && !blockedSet.has(tag)) { blockedSet.add(tag); syncState(); }
input.value = '';
};
panel.querySelector('#pxp-add').onclick = addTag;
panel.querySelector('#pxp-newtag').onkeydown = e => { if (e.key === 'Enter') addTag(); };
refreshPanelTagList();
return panel;
}
GM_addStyle(`
.item_thumb { position: relative !important; overflow: hidden !important; }
.item_thumb img { display: block !important; position: relative !important; z-index: 1 !important; }
video.pxp-preview {
position: absolute !important; top: 0 !important; left: 0 !important;
width: 100% !important; height: 100% !important; object-fit: cover !important;
z-index: 2 !important; opacity: 0; transition: opacity .25s ease; pointer-events: none;
}
#pxp-settings {
position:fixed; top:60px; right:-320px; width:280px; max-width:calc(100vw - 32px); z-index:99999;
background: rgba(0, 0, 0, 0.75); backdrop-filter: blur(24px) saturate(180%); -webkit-backdrop-filter: blur(24px) saturate(180%);
border:1px solid rgba(255,255,255,0.08); border-radius:16px; color:#ccc;
font-family: -apple-system, BlinkMacSystemFont, "system-ui", sans-serif; font-size:13px;
transition: right .35s cubic-bezier(0.34, 1.56, 0.64, 1); box-shadow:0 10px 40px rgba(0,0,0,.8); overflow:hidden;
box-sizing: border-box;
}
#pxp-settings.open { right:16px; }
.pxp-header { display:flex; justify-content:space-between; align-items:center; padding:14px 16px; border-bottom:1px solid rgba(255,255,255,0.08); font-weight:600; font-size:15px; color:#fff; background:rgba(255,255,255,0.03);}
.pxp-close { background:rgba(255,255,255,0.1); border:none; color:#fff; font-size:14px; cursor:pointer; width:24px; height:24px; border-radius:50%; display:flex; align-items:center; justify-content:center; transition:background .2s; }
.pxp-close:hover { background:rgba(255,255,255,0.2); }
.pxp-body { padding:16px; }
.pxp-row { display:flex; align-items:center; gap:10px; margin-bottom:12px; cursor:pointer; color:#ddd; }
.pxp-section { margin:16px 0 8px; font-weight:600; color:rgba(255,255,255,0.4); text-transform:uppercase; font-size:11px; letter-spacing:1px; }
.pxp-taglist { max-height:180px; overflow-y:auto; margin-bottom:12px; display:flex; flex-direction:column; gap:8px; -webkit-overflow-scrolling:touch; }
.pxp-taglist::-webkit-scrollbar { width:4px; } .pxp-taglist::-webkit-scrollbar-thumb { background:rgba(255,255,255,0.2); border-radius:2px; }
.pxp-empty { color:rgba(255,255,255,0.3); font-style:italic; padding:8px 0; text-align:center; }
.pxp-tag { display:flex; justify-content:space-between; align-items:center; background:rgba(255,255,255,0.06); padding:8px 12px; border-radius:8px; font-size:12px; color:#eee; }
.pxp-tag button { background:none; border:none; color:#ff6b6b; cursor:pointer; font-size:16px; line-height:1; padding:0 0 0 8px; }
.pxp-addtag { display:flex; gap:8px; }
.pxp-addtag input { flex:1; background:rgba(255,255,255,0.08); border:1px solid rgba(255,255,255,0.1); color:#fff; padding:10px 12px; border-radius:8px; font-size:12px; outline:none; box-sizing:border-box; }
.pxp-addtag input:focus { border-color:rgba(255,255,255,0.3); background:rgba(255,255,255,0.12); }
.pxp-addtag button { background:rgba(255,255,255,0.12); color:#fff; border:1px solid rgba(255,255,255,0.15); padding:8px 16px; border-radius:8px; cursor:pointer; font-size:12px; font-weight:500; transition:.2s; }
.pxp-addtag button:hover { background:rgba(255,255,255,0.2); }
#pxp-toggle { position:fixed; top:16px; right:16px; z-index:99999; background:rgba(0,0,0,0.6); backdrop-filter:blur(12px); -webkit-backdrop-filter:blur(12px); color:#fff; border:1px solid rgba(255,255,255,0.1); padding:10px 18px; border-radius:20px; cursor:pointer; font-weight:600; font-size:13px; box-shadow:0 4px 15px rgba(0,0,0,.4); transition:.2s; }
#pxp-toggle:hover { background:rgba(0,0,0,0.8); transform:scale(1.05); }
.item_tags a { position:relative; padding-right:14px!important; }
.pxp-block-btn { display:inline-block; margin-left:4px; color:#ff6b6b; cursor:pointer; font-weight:bold; font-size:12px; opacity:0; transition:opacity .15s; }
.item_tags a:hover .pxp-block-btn { opacity:1; }
@media (max-width: 480px) {
#pxp-toggle { padding:8px 14px; font-size:12px; top:10px; right:10px; }
#pxp-settings { top:50px; }
}
`);
GM_registerMenuCommand('Reset Filters', () => {
localStorage.removeItem(STORAGE_KEY);
state = { ...DEFAULTS };
blockedSet = new Set();
syncState();
initPreviews();
});
function init() {
let toggle = $('#pxp-toggle') || document.createElement('button');
toggle.id = 'pxp-toggle';
toggle.textContent = 'Filters';
toggle.onclick = () => {
let panel = $('#pxp-settings');
if (!panel) { panel = buildPanel(); document.body.appendChild(panel); }
panel.classList.toggle('open');
};
if (!toggle.parentElement) document.body.appendChild(toggle);
syncState();
initPreviews();
addBlockButtons();
let pending;
new MutationObserver(m => {
// Only trigger if new video containers or tags are added to the DOM
const hasRelevantMutations = m.some(r => [...r.addedNodes].some(n =>
n.nodeType === 1 &&
(n.matches?.('.item_cont, .item.preview, .item_tags') || n.querySelector?.('.item_cont, .item.preview, .item_tags'))
));
if (!hasRelevantMutations) return;
clearTimeout(pending);
pending = setTimeout(() => {
if (!$('#pxp-toggle')) document.body.appendChild(toggle); // Re-add toggle if site SPA removes it
initPreviews();
addBlockButtons();
applyFilter();
}, 150);
}).observe(document.body, { childList: true, subtree: true });
}
if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', init);
else init();
})();