nrudnyk
For exemple two scripts : first original 😮riginal :
// ==UserScript==
// @name PDF Viewer for somenor.net
// @namespace aria.pdf.viewer
// @version 1.5
// @description Force PDFs to open in Orion viewer new tab, map blob to original URL
// @author ExpertUserscripts2025
// @match https://somenor.net/sos38/ALP/*
// @run-at document-start
// @inject-into page
// @grant none
// @noframes
// ==/UserScript==
(function() {
'use strict';
// Config
const DEBUG = false;
const OPEN_POLICY = 'defer'; // Always defer for user gesture
// Global flags
const ONCE = (() => {
const k = Symbol.for('aria.pdf.patch.once');
if (window[k]) return window[k];
window[k] = { patched: {}, queue: [], userGesture: false };
return window[k];
})();
// Blob URL to original mapping
const blobUrlToOriginal = new Map();
// Utils
const log = (...args) => { if (DEBUG) console.debug('[PDFViewer]', ...args); };
const isPdfUrl = (url) => url && url.toLowerCase().endsWith('.pdf');
const sniffMagicPDF = async (buf) => {
if (!(buf instanceof ArrayBuffer) || buf.byteLength < 4) return false;
const view = new Uint8Array(buf, 0, 4);
return String.fromCharCode(...view) === '%PDF';
};
const openInNewTab = async (url) => {
if (!url) {
log('No URL provided for openInNewTab');
return;
}
log('Attempting to open direct URL:', url);
const newWin = window.open(url, 'blank', 'noopener,noreferrer');
if (newWin) {
log('Success: Opened via window.open:', url);
} else {
log('window.open blocked, using iframe fallback');
const iframe = document.createElement('iframe');
iframe.src = url;
iframe.style.cssText = 'width:100%;height:100vh;border:none';
const newWinFallback = window.open('about:blank', 'blank', 'noopener,noreferrer');
if (newWinFallback) {
newWinFallback.document.body.style.margin = '0';
newWinFallback.document.body.appendChild(iframe.cloneNode(true));
log('Success: Iframe opened in new tab');
} else {
log('ERROR: Pop-up blocked. Enable pop-ups for somenor.net in Orion > Preferences > Websites > Pop-up Windows');
}
setTimeout(() => iframe.remove(), 100);
}
};
const queueOpen = (url) => {
if (!url) return;
ONCE.queue.push(url);
log('Queued URL:', url);
processQueue();
};
const processQueue = () => {
if (OPEN_POLICY === 'defer' && !ONCE.userGesture) {
log('Queue waiting for user gesture');
return;
}
while (ONCE.queue.length) {
openInNewTab(ONCE.queue.shift());
}
};
const safeOpen = (url) => queueOpen(url);
const isPdfResponse = async (response) => {
const ct = response.headers.get('Content-Type') '';
const disp = response.headers.get('Content-Disposition') '';
log('Response headers:', { 'Content-Type': ct, 'Content-Disposition': disp });
const isPdfCt = ct.includes('application/pdf');
const isPdfDisp = disp.includes('attachment') && disp.match(/filename=[^;]*.pdf/i);
if (isPdfCt isPdfDisp isPdfUrl(response.url)) return true;
const clone = response.clone();
const buf = await clone.arrayBuffer();
return sniffMagicPDF(buf);
};
// Patches
const patchFetch = () => {
if (ONCE.patched.fetch) return;
ONCE.patched.fetch = true;
const origFetch = window.fetch;
const origBlob = Response.prototype.blob;
Response.prototype.blob = async function() {
if (this.isPdfResponse) {
const blobData = await origBlob.call(this);
const customBlob = new Blob([blobData], { type: blobData.type || 'application/pdf' });
customBlob.originalUrl = this.originalUrl;
log('Created custom PDF blob with original URL:', customBlob.originalUrl);
return customBlob;
}
return origBlob.call(this);
};
window.fetch = async function(...args) {
const response = await origFetch.apply(this, args);
if (await isPdfResponse(response)) {
response.isPdfResponse = true;
response.originalUrl = response.url;
log('Marked PDF response, opening URL:', response.url);
safeOpen(response.url);
}
return response;
};
log('Patched fetch + Response.blob');
};
const patchXHR = () => {
if (ONCE.patched.xhr) return;
ONCE.patched.xhr = true;
const origOpen = XMLHttpRequest.prototype.open;
const origSend = XMLHttpRequest.prototype.send;
XMLHttpRequest.prototype.open = function(method, url, ...args) {
this.pdfUrl = isPdfUrl(url);
this.pdfResponseUrl = url;
return origOpen.apply(this, [method, url, ...args]);
};
XMLHttpRequest.prototype.send = function(...args) {
const origOnLoad = this.onload;
this.onload = async (e) => {
let isPdf = false;
if (this.pdfUrl) {
isPdf = true;
} else {
const ct = this.getResponseHeader('Content-Type') '';
isPdf = ct.includes('application/pdf') (this.response && await sniffMagicPDF(this.response));
}
if (isPdf) {
log('XHR PDF detected, opening URL:', this.responseURL this.pdfResponseUrl);
safeOpen(this.responseURL || this.pdfResponseUrl);
// For blob responseType, create custom blob
if (this.responseType === 'blob') {
const origResponse = this.response;
const customBlob = new Blob([origResponse], { type: 'application/pdf' });
customBlob.originalUrl = this.responseURL || this.pdfResponseUrl;
Object.defineProperty(this, 'response', { value: customBlob, writable: false });
log('XHR: Created custom PDF blob with original URL:', customBlob.originalUrl);
}
}
if (origOnLoad) origOnLoad.call(this, e);
};
return origSend.apply(this, args);
};
log('Patched XHR');
};
const patchUrlCreateObject = () => {
if (ONCE.patched.urlCreate) return;
ONCE.patched.urlCreate = true;
const origCreate = URL.createObjectURL;
URL.createObjectURL = function(blob) {
const url = origCreate.call(URL, blob);
if (blob.originalUrl && blob.type && blob.type.includes('application/pdf')) {
blobUrlToOriginal.set(url, blob.originalUrl);
log('Mapped blob URL to original:', { blobUrl: url, original: blob._originalUrl });
}
return url;
};
// Periodic cleanup
setInterval(() => {
for (const [key] of blobUrlToOriginal) {
if (!URL.revokeObjectURL(key)) blobUrlToOriginal.delete(key); // If revoked, clean
}
}, 30000);
log('Patched URL.createObjectURL + cleanup');
};
const interceptClicks = () => {
if (ONCE.patched.clicks) return;
ONCE.patched.clicks = true;
document.addEventListener('click', (e) => {
const a = e.target.closest('a');
if (!a e.defaultPrevented) return;
let href = a.href;
const isBlob = href && href.startsWith('blob:');
const hasDownload = a.hasAttribute('download');
if (isPdfUrl(href) isBlob hasDownload) {
e.preventDefault();
let targetUrl = href;
if (isBlob) {
const original = blobUrlToOriginal.get(href);
targetUrl = original href;
if (original) log('Intercepted blob click, redirecting to original:', original);
else log('Intercepted blob click, fallback to blob (may download):', href);
}
safeOpen(targetUrl);
}
}, true);
log('Patched clicks');
};
const observeDynamic = () => {
if (ONCE.patched.observe) return;
ONCE.patched.observe = true;
const observer = new MutationObserver((mutations) => {
mutations.forEach((mut) => {
mut.addedNodes.forEach((node) => {
if (node.nodeType !== 1) return;
const anchors = node.querySelectorAll('a[href$=".pdf" i], a[download], a[href^="blob:"]');
anchors.forEach(a => {
a.addEventListener('click', (e) => {
e.preventDefault();
let href = a.href;
const isBlob = href.startsWith('blob:');
let targetUrl = href;
if (isBlob) {
const original = blobUrlToOriginal.get(href);
targetUrl = original || href;
}
safeOpen(targetUrl);
}, { once: true, capture: true });
});
});
});
});
if (document.body) {
observer.observe(document.body, { childList: true, subtree: true });
log('Started dynamic observer');
} else {
document.addEventListener('DOMContentLoaded', () => {
observer.observe(document.body, { childList: true, subtree: true });
log('Started dynamic observer after DOMContentLoaded');
});
}
};
const setupGestures = () => {
if (ONCE.patched.gestures) return;
ONCE.patched.gestures = true;
const handler = () => {
ONCE.userGesture = true;
processQueue();
};
['click', 'keydown'].forEach(ev => {
document.addEventListener(ev, handler, { passive: true });
});
log('Setup user gesture queue');
};
// Init
const init = () => {
patchFetch();
patchXHR();
patchUrlCreateObject();
interceptClicks();
observeDynamic();
setupGestures();
};
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', init);
} else {
init();
}
})();[FIN SECTION]same after restarting: after restart:
// ==UserScript== // @name PDF Blob Viewer for somenor.net // @namespace aria.pdf.viewer // @version 2.1.2-somenor // @description Stabilized PDF blob viewer for somenor.net/sos38/ALP - iframe + data: fallback // @author ExpertUserscripts2025 // @match https://somenor.net/sos38/ALP/* // @run-at document-start // @inject-into page // @grant none // @noframes // ==/UserScript== (() => { ''use strict''; const CONFIG = { OPEN_IN_NEW_TAB: true, WATCH_MS: 2000, MAX_MB: 50, DEBUG: false }; // Tuned for somenor: shorter watch, smaller max for medical reports const log = (...a) => CONFIG.DEBUG && console.debug(''[PDF-Blob-Somenor]'', ...a); const warn = (...a) => console.warn(''[PDF-Blob-Somenor]'', ...a); const error = (...a) => console.error(''[PDF-Blob-Somenor]'', ...a); const isPdfType = t => t && /application\/(pdf|x-pdf|acrobat)/i.test(t); const bytesLooksPdf = b => { try { const a = b instanceof ArrayBuffer ? new Uint8Array(b) : b; return a.length >= 5 && a[0] === 0x25 && a[1] === 0x50 && a[2] === 0x44 && a[3] === 0x46 && a[4] === 0x2D; } catch { return false; } }; const checkBlobSize = b => !CONFIG.MAX_MB b.size <= CONFIG.MAX_MB 1024 1024; const blobToDataURL = b => new Promise((res, rej) => { const r = new FileReader(); r.onload = () => res(r.result); r.onerror = () => rej(new Error(''Échec lecture blob'')); r.readAsDataURL(b); }); const buildViewerHTML = (src, title = ''document.pdf'') => <!DOCTYPE html><meta charset="utf-8"><title>${title}</title><style>html,body,iframe{height:100%;width:100%;margin:0;border:0}</style><iframe src="${src}" allowfullscreen></iframe>; const fillWindow = (win, html) => { try { win.document.open(); win.document.write(html); win.document.close(); return true; } catch { return false; } }; const openPlaceholder = () => CONFIG.OPEN_IN_NEW_TAB ? window.open(''about:blank'', ''blank'') : null; const blobMap = new Map(); const blobMetadata = new WeakMap(); const origCreate = URL.createObjectURL.bind(URL); URL.createObjectURL = function (obj) { const url = origCreate(obj); try { if (obj instanceof Blob) { const t = obj.type; if (isPdfType(t)) { blobMap.set(url, obj); blobMetadata.set(obj, { created: Date.now(), size: obj.size, type: t }); log(''PDF blob:'', url); } else if (!t t === ''application/octet-stream'') { obj.slice(0, 5).arrayBuffer().then(buf => { if (bytesLooksPdf(buf)) { blobMap.set(url, obj); log(''PDF détecté par magic number:'', url); } }).catch(() => {}); } } } catch (e) { error(''createObjectURL:'', e); } return url; }; const origRevoke = URL.revokeObjectURL.bind(URL); URL.revokeObjectURL = function (url) { if (blobMap.has(url)) { blobMap.delete(url); } return origRevoke(url); }; const resolveBlob = async url => { if (blobMap.has(url)) return blobMap.get(url); try { const resp = await fetch(url, { method: ''GET'', cache: ''no-store'', credentials: ''same-origin'' }); if (!resp.ok) throw new Error(HTTP ${resp.status}); const blob = await resp.blob(); if (isPdfType(blob.type)) return blob; if (!blob.type blob.type === ''application/octet-stream'') { const head = await blob.slice(0, 5).arrayBuffer(); if (bytesLooksPdf(head)) return blob; } return null; } catch (e) { error(''resolveBlob:'', url, e); return null; } }; const handleBlobPdfURL = async (url, filename, winHandle) => { try { const blob = await resolveBlob(url); if (!blob) return false; if (!checkBlobSize(blob)) { const sizeMB = (blob.size / 1024 / 1024).toFixed(2); if (confirm(PDF volumineux (${sizeMB}MB > ${CONFIG.MAX_MB}MB). Ouvrir quand même ?)) { (winHandle window).open(url, ''blank''); return true; } return false; } let viewURL = null; try { viewURL = URL.createObjectURL(blob); } catch {} const tgt = winHandle (CONFIG.OPEN_IN_NEW_TAB ? null : window); if (viewURL) { const htmlBlob = buildViewerHTML(viewURL, filename ''document.pdf''); if (tgt) { if (!fillWindow(tgt, htmlBlob)) tgt.location.href = viewURL; } else { document.open(); document.write(htmlBlob); document.close(); } setTimeout(async () => { try { const dataURL = await blobToDataURL(blob); const htmlData = buildViewerHTML(dataURL, filename ''document.pdf''); if (tgt && !tgt.closed) fillWindow(tgt, htmlData); else { document.open(); document.write(htmlData); document.close(); } } catch {} }, 300); return true; } const dataURL = await blobToDataURL(blob); const htmlData = buildViewerHTML(dataURL, filename ''document.pdf''); if (tgt) { if (!fillWindow(tgt, htmlData)) tgt.location.href = dataURL; } else { document.open(); document.write(htmlData); document.close(); } return true; } catch (e) { error(''handleBlobPdfURL:'', e); try { (winHandle window).open(url, ''_blank''); } catch {} return false; } }; const origOpen = window.open.bind(window); window.open = function (url, target, features) { try { if (typeof url === ''string'' && url.startsWith(''blob:'')) { const win = openPlaceholder(); handleBlobPdfURL(url, ''document.pdf'', win); return win null; } } catch (e) { error(''window.open:'', e); } return origOpen(url, target, features); }; const clickHandler = e => { const link = e.target?.closest?.(''a[href^="blob:"]''); if (!link) return; e.preventDefault(); e.stopPropagation(); const win = openPlaceholder(); const filename = link.download link.textContent?.trim() ''document.pdf''; handleBlobPdfURL(link.href, filename, win); }; document.addEventListener(''click'', clickHandler, true); document.addEventListener(''auxclick'', clickHandler, true); let watchUntil = 0; const observerActive = { value: false }; const armWatcher = () => { watchUntil = performance.now() + CONFIG.WATCH_MS; if (!observerActive.value) { observerActive.value = true; startObserver(); } }; [''click'', ''keydown'', ''submit'', ''change'', ''pointerdown''].forEach(evt => document.addEventListener(evt, armWatcher, { capture: true, passive: true })); const mo = new MutationObserver(muts => { if (performance.now() > watchUntil) { if (observerActive.value) { observerActive.value = false; mo.disconnect(); } return; } for (const m of muts) for (const node of m.addedNodes) { if (node.nodeType !== Node.ELEMENT_NODE) continue; const sel = ''a[href^="blob:"], iframe[src^="blob:"], embed[src^="blob:"], object[data^="blob:"]''; const els = []; if (node.matches?.(sel)) els.push(node); if (node.querySelectorAll) els.push(...node.querySelectorAll(sel)); for (const el of els) { const url = el.href el.src el.data; if (!url || !url.startsWith(''blob:'')) continue; if (el.tagName === ''A'') continue; handleBlobPdfURL(url, ''document.pdf'', openPlaceholder()); watchUntil = 0; return; } } }); const startObserver = () => { if (!document.documentElement) return setTimeout(startObserver, 10); mo.observe(document.documentElement, { childList: true, subtree: true }); }; setInterval(() => { const maxAge = 30 60 1000, now = Date.now(); for (const [u, b] of blobMap.entries()) { const meta = blobMetadata.get(b); if (meta && (now - meta.created) > maxAge) blobMap.delete(u); } }, 5 60 1000); window.addEventListener(''beforeunload'', () => blobMap.clear()); })();// ==UserScript== // @name New script // @namespace Violentmonkey Scripts // @match ://example.org/ // @grant none // @version 1.0 // @author - // @description 17/09/2025 21:38:28 // ==/UserScript==