mirror of
https://github.com/ergosteur/misc-userscripts.git
synced 2026-04-19 13:09:33 -04:00
add current script collection
This commit is contained in:
33
apollocafe-image-upgrader.user.js
Normal file
33
apollocafe-image-upgrader.user.js
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
// ==UserScript==
|
||||||
|
// @name Apollo Cafe Image Upgrader
|
||||||
|
// @namespace https://apollo.cafe/
|
||||||
|
// @version 1.0
|
||||||
|
// @description Replace all imagedelivery.net URLs ending in /2x with /4x on apollo.cafe
|
||||||
|
// @match https://apollo.cafe/*
|
||||||
|
// @match http://apollo.cafe/*
|
||||||
|
// @icon https://apollo.cafe/favicon.ico
|
||||||
|
// @grant none
|
||||||
|
// ==/UserScript==
|
||||||
|
|
||||||
|
(function() {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
// Function to replace /2x -> /4x in all <img> tags
|
||||||
|
function upgradeImages() {
|
||||||
|
document.querySelectorAll('img[src*="imagedelivery.net"]').forEach(img => {
|
||||||
|
img.src = img.src.replace(/\/2x$/, '/4x');
|
||||||
|
});
|
||||||
|
|
||||||
|
// Also replace inline HTML / CSS background URLs if needed
|
||||||
|
document.querySelectorAll('[style*="imagedelivery.net"]').forEach(el => {
|
||||||
|
el.style.backgroundImage = el.style.backgroundImage.replace(/\/2x(['"]?\)?$)/, '/4x$1');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run once at page load
|
||||||
|
upgradeImages();
|
||||||
|
|
||||||
|
// Run again whenever DOM changes (for infinite scroll, SPA, etc.)
|
||||||
|
const observer = new MutationObserver(() => upgradeImages());
|
||||||
|
observer.observe(document.body, { childList: true, subtree: true });
|
||||||
|
})();
|
||||||
135
expand-tco-links.user.js
Normal file
135
expand-tco-links.user.js
Normal file
@@ -0,0 +1,135 @@
|
|||||||
|
// ==UserScript==
|
||||||
|
// @name Expand t.co links (live replace, Location header fixed)
|
||||||
|
// @namespace ergosteur
|
||||||
|
// @version 0.7
|
||||||
|
// @description Expand t.co shortlinks into final URLs by parsing Location header
|
||||||
|
// @match https://twitter.com/*
|
||||||
|
// @match https://x.com/*
|
||||||
|
// @grant GM_registerMenuCommand
|
||||||
|
// @grant GM_xmlhttpRequest
|
||||||
|
// ==/UserScript==
|
||||||
|
|
||||||
|
(function() {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
function showTextareaDialog(title, callback) {
|
||||||
|
const overlay = document.createElement("div");
|
||||||
|
overlay.style.position = "fixed";
|
||||||
|
overlay.style.top = 0;
|
||||||
|
overlay.style.left = 0;
|
||||||
|
overlay.style.width = "100%";
|
||||||
|
overlay.style.height = "100%";
|
||||||
|
overlay.style.background = "rgba(0,0,0,0.7)";
|
||||||
|
overlay.style.zIndex = 999999;
|
||||||
|
|
||||||
|
const box = document.createElement("div");
|
||||||
|
box.style.position = "absolute";
|
||||||
|
box.style.top = "50%";
|
||||||
|
box.style.left = "50%";
|
||||||
|
box.style.transform = "translate(-50%, -50%)";
|
||||||
|
box.style.background = "#fff";
|
||||||
|
box.style.padding = "20px";
|
||||||
|
box.style.borderRadius = "8px";
|
||||||
|
box.style.maxWidth = "600px";
|
||||||
|
box.style.width = "80%";
|
||||||
|
box.style.display = "flex";
|
||||||
|
box.style.flexDirection = "column";
|
||||||
|
|
||||||
|
const label = document.createElement("div");
|
||||||
|
label.textContent = title;
|
||||||
|
label.style.marginBottom = "8px";
|
||||||
|
|
||||||
|
const textarea = document.createElement("textarea");
|
||||||
|
textarea.style.width = "100%";
|
||||||
|
textarea.style.height = "300px";
|
||||||
|
textarea.style.whiteSpace = "pre";
|
||||||
|
|
||||||
|
const controls = document.createElement("div");
|
||||||
|
controls.style.marginTop = "8px";
|
||||||
|
controls.style.display = "flex";
|
||||||
|
controls.style.justifyContent = "space-between";
|
||||||
|
controls.style.alignItems = "center";
|
||||||
|
|
||||||
|
const button = document.createElement("button");
|
||||||
|
button.textContent = "Expand";
|
||||||
|
|
||||||
|
const status = document.createElement("div");
|
||||||
|
status.textContent = "Ready.";
|
||||||
|
|
||||||
|
controls.appendChild(button);
|
||||||
|
controls.appendChild(status);
|
||||||
|
|
||||||
|
box.appendChild(label);
|
||||||
|
box.appendChild(textarea);
|
||||||
|
box.appendChild(controls);
|
||||||
|
overlay.appendChild(box);
|
||||||
|
document.body.appendChild(overlay);
|
||||||
|
|
||||||
|
button.onclick = () => {
|
||||||
|
const val = textarea.value.trim();
|
||||||
|
if (!val) {
|
||||||
|
overlay.remove();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
callback(textarea, status);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function expandOne(link) {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
GM_xmlhttpRequest({
|
||||||
|
method: "GET", // GET is safer than HEAD for t.co
|
||||||
|
url: link,
|
||||||
|
redirect: "manual", // we want the Location header
|
||||||
|
onload: (resp) => {
|
||||||
|
let out = link;
|
||||||
|
|
||||||
|
// responseHeaders is a string in Tampermonkey
|
||||||
|
const headers = resp.responseHeaders.split(/\r?\n/);
|
||||||
|
const locLine = headers.find(h => /^location:/i.test(h));
|
||||||
|
if (locLine) {
|
||||||
|
out = locLine.split(/:\s*/i)[1].trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
resolve(out);
|
||||||
|
},
|
||||||
|
onerror: () => resolve(link)
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function expandLinks() {
|
||||||
|
showTextareaDialog("Paste t.co links (one per line):", async (textarea, status) => {
|
||||||
|
let lines = textarea.value.split(/\r?\n/);
|
||||||
|
const total = lines.length;
|
||||||
|
|
||||||
|
for (let i = 0; i < total; i++) {
|
||||||
|
const line = lines[i].trim();
|
||||||
|
if (!line) continue;
|
||||||
|
|
||||||
|
status.textContent = `Expanding ${i + 1}/${total}...`;
|
||||||
|
const full = await expandOne(line);
|
||||||
|
|
||||||
|
lines[i] = full;
|
||||||
|
textarea.value = lines.join("\n");
|
||||||
|
|
||||||
|
textarea.scrollTop = textarea.scrollHeight; // auto-scroll
|
||||||
|
}
|
||||||
|
|
||||||
|
status.textContent = `Done! Expanded ${total} links.`;
|
||||||
|
|
||||||
|
const blob = new Blob([lines.join("\n")], {type: "text/plain"});
|
||||||
|
const url = URL.createObjectURL(blob);
|
||||||
|
|
||||||
|
const dl = document.createElement("a");
|
||||||
|
dl.href = url;
|
||||||
|
dl.download = "expanded_links.txt";
|
||||||
|
dl.textContent = "⬇ Download results";
|
||||||
|
dl.style.marginLeft = "10px";
|
||||||
|
|
||||||
|
status.appendChild(dl);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
GM_registerMenuCommand("Expand t.co Links", expandLinks);
|
||||||
|
})();
|
||||||
345
lazyload-link-collector.user.js
Normal file
345
lazyload-link-collector.user.js
Normal file
@@ -0,0 +1,345 @@
|
|||||||
|
// ==UserScript==
|
||||||
|
// @name Extract Links (Universal, Configurable Patterns)
|
||||||
|
// @namespace ergosteur
|
||||||
|
// @version 2.9
|
||||||
|
// @description Universal lazy-scroll link extractor with config, HUD, stop button, and Tampermonkey menu
|
||||||
|
// @match *://*/*
|
||||||
|
// @grant GM_registerMenuCommand
|
||||||
|
// ==/UserScript==
|
||||||
|
|
||||||
|
(function () {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const found = new Set();
|
||||||
|
|
||||||
|
// 🔧 Defaults + persisted settings
|
||||||
|
let hrefPattern = localStorage.hrefPattern || "https://t.co/";
|
||||||
|
let textPattern = localStorage.textPattern || "drive.google.com";
|
||||||
|
let useHrefPattern = localStorage.useHrefPattern !== "false"; // default true
|
||||||
|
let useTextPattern = localStorage.useTextPattern !== "false"; // default true
|
||||||
|
let maxScrolls = parseInt(localStorage.maxScrolls || "0", 10); // 0 = unlimited
|
||||||
|
let autoShowButton = localStorage.autoShowButton === "true"; // default false
|
||||||
|
|
||||||
|
// Scrolling behavior
|
||||||
|
const STEP_FRACTION = 0.9;
|
||||||
|
const SCROLL_DELAY_MS = 700;
|
||||||
|
const SETTLE_DELAY_MS = 800;
|
||||||
|
const STALL_LIMIT = 8;
|
||||||
|
const HARD_CAP = 2000;
|
||||||
|
|
||||||
|
// Progress HUD
|
||||||
|
let progressEl = null;
|
||||||
|
let stopFlag = false;
|
||||||
|
|
||||||
|
function showProgress() {
|
||||||
|
if (!progressEl) {
|
||||||
|
progressEl = document.createElement("div");
|
||||||
|
Object.assign(progressEl.style, {
|
||||||
|
position: "fixed",
|
||||||
|
top: "20px",
|
||||||
|
left: "20px",
|
||||||
|
zIndex: "1000000",
|
||||||
|
background: "rgba(0,0,0,0.7)",
|
||||||
|
color: "white",
|
||||||
|
padding: "6px 10px",
|
||||||
|
borderRadius: "4px",
|
||||||
|
fontSize: "13px",
|
||||||
|
fontFamily: "monospace",
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
gap: "8px"
|
||||||
|
});
|
||||||
|
|
||||||
|
const label = document.createElement("span");
|
||||||
|
label.id = "progress-label";
|
||||||
|
progressEl.appendChild(label);
|
||||||
|
|
||||||
|
const stopBtn = document.createElement("button");
|
||||||
|
stopBtn.innerText = "⏹ Stop";
|
||||||
|
Object.assign(stopBtn.style, {
|
||||||
|
background: "#d9534f",
|
||||||
|
color: "white",
|
||||||
|
border: "none",
|
||||||
|
padding: "2px 6px",
|
||||||
|
borderRadius: "3px",
|
||||||
|
cursor: "pointer",
|
||||||
|
fontSize: "12px"
|
||||||
|
});
|
||||||
|
stopBtn.onclick = () => { stopFlag = true; };
|
||||||
|
progressEl.appendChild(stopBtn);
|
||||||
|
|
||||||
|
document.body.appendChild(progressEl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateProgress(rounds) {
|
||||||
|
const spinner = ["⠋","⠙","⠹","⠸","⠼","⠴","⠦","⠧","⠇","⠏"][rounds % 10];
|
||||||
|
const label = document.getElementById("progress-label");
|
||||||
|
if (label) {
|
||||||
|
label.textContent = `${spinner} Rounds: ${rounds} | Links: ${found.size}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function hideProgress() {
|
||||||
|
if (progressEl) { progressEl.remove(); progressEl = null; }
|
||||||
|
}
|
||||||
|
|
||||||
|
function extractLinks() {
|
||||||
|
document.querySelectorAll("a").forEach(a => {
|
||||||
|
const href = a.href || "";
|
||||||
|
const text = a.textContent || "";
|
||||||
|
const hrefOk = !useHrefPattern || (hrefPattern && href.includes(hrefPattern));
|
||||||
|
const textOk = !useTextPattern || (textPattern && text.includes(textPattern));
|
||||||
|
if (hrefOk && textOk && href) {
|
||||||
|
if (!found.has(href)) {
|
||||||
|
found.add(href);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function wait(ms) { return new Promise(r => setTimeout(r, ms)); }
|
||||||
|
|
||||||
|
async function scrollToTopAndSettle() {
|
||||||
|
const html = document.documentElement;
|
||||||
|
const prev = html.style.scrollBehavior;
|
||||||
|
html.style.scrollBehavior = "auto";
|
||||||
|
window.scrollTo(0, 0);
|
||||||
|
html.style.scrollBehavior = prev || "";
|
||||||
|
const t0 = performance.now();
|
||||||
|
while (window.scrollY !== 0 && performance.now() - t0 < 1500) {
|
||||||
|
await wait(50);
|
||||||
|
}
|
||||||
|
await wait(SETTLE_DELAY_MS);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function autoScrollAndExtract() {
|
||||||
|
found.clear();
|
||||||
|
stopFlag = false;
|
||||||
|
|
||||||
|
await scrollToTopAndSettle();
|
||||||
|
showProgress();
|
||||||
|
|
||||||
|
const scroller = document.scrollingElement || document.documentElement;
|
||||||
|
let lastHeight = scroller.scrollHeight;
|
||||||
|
let lastFound = 0;
|
||||||
|
let rounds = 0;
|
||||||
|
let stall = 0;
|
||||||
|
|
||||||
|
while (!stopFlag && rounds < HARD_CAP) {
|
||||||
|
rounds++;
|
||||||
|
const step = Math.max(64, Math.floor(window.innerHeight * STEP_FRACTION));
|
||||||
|
window.scrollBy(0, step);
|
||||||
|
await wait(SCROLL_DELAY_MS);
|
||||||
|
|
||||||
|
extractLinks();
|
||||||
|
updateProgress(rounds);
|
||||||
|
|
||||||
|
const newHeight = scroller.scrollHeight;
|
||||||
|
const atBottom = (scroller.scrollTop + window.innerHeight + 2) >= newHeight;
|
||||||
|
const heightGrew = newHeight > lastHeight + 2;
|
||||||
|
const foundGrew = found.size > lastFound;
|
||||||
|
|
||||||
|
if (heightGrew || foundGrew || !atBottom) stall = 0;
|
||||||
|
else stall++;
|
||||||
|
|
||||||
|
lastHeight = newHeight;
|
||||||
|
lastFound = found.size;
|
||||||
|
|
||||||
|
if ((maxScrolls > 0 && rounds >= maxScrolls) || (atBottom && stall >= STALL_LIMIT)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
hideProgress();
|
||||||
|
showOverlay([...found], false);
|
||||||
|
}
|
||||||
|
|
||||||
|
function showOverlay(links, configOnly = false) {
|
||||||
|
const old = document.getElementById("extract-box");
|
||||||
|
if (old) old.remove();
|
||||||
|
|
||||||
|
const container = document.createElement("div");
|
||||||
|
container.id = "extract-box";
|
||||||
|
Object.assign(container.style, {
|
||||||
|
position: "fixed",
|
||||||
|
top: "60px",
|
||||||
|
right: "20px",
|
||||||
|
width: "480px",
|
||||||
|
zIndex: "999999",
|
||||||
|
background: "white",
|
||||||
|
border: "2px solid black",
|
||||||
|
padding: "10px",
|
||||||
|
fontSize: "12px"
|
||||||
|
});
|
||||||
|
|
||||||
|
// Href config
|
||||||
|
const hrefRow = document.createElement("div");
|
||||||
|
const hrefChk = document.createElement("input");
|
||||||
|
hrefChk.type = "checkbox";
|
||||||
|
hrefChk.checked = useHrefPattern;
|
||||||
|
hrefChk.onchange = () => {
|
||||||
|
useHrefPattern = hrefChk.checked;
|
||||||
|
localStorage.useHrefPattern = useHrefPattern;
|
||||||
|
};
|
||||||
|
const hrefLbl = document.createElement("label");
|
||||||
|
hrefLbl.innerText = " Href pattern:";
|
||||||
|
const hrefInp = document.createElement("input");
|
||||||
|
hrefInp.type = "text";
|
||||||
|
hrefInp.value = hrefPattern;
|
||||||
|
Object.assign(hrefInp.style, { width: "70%", marginLeft: "6px" });
|
||||||
|
hrefInp.onchange = () => {
|
||||||
|
hrefPattern = hrefInp.value.trim();
|
||||||
|
localStorage.hrefPattern = hrefPattern;
|
||||||
|
};
|
||||||
|
hrefRow.append(hrefChk, hrefLbl, hrefInp);
|
||||||
|
|
||||||
|
// Text config
|
||||||
|
const textRow = document.createElement("div");
|
||||||
|
const textChk = document.createElement("input");
|
||||||
|
textChk.type = "checkbox";
|
||||||
|
textChk.checked = useTextPattern;
|
||||||
|
textChk.onchange = () => {
|
||||||
|
useTextPattern = textChk.checked;
|
||||||
|
localStorage.useTextPattern = useTextPattern;
|
||||||
|
};
|
||||||
|
const textLbl = document.createElement("label");
|
||||||
|
textLbl.innerText = " Text pattern:";
|
||||||
|
const textInp = document.createElement("input");
|
||||||
|
textInp.type = "text";
|
||||||
|
textInp.value = textPattern;
|
||||||
|
Object.assign(textInp.style, { width: "70%", marginLeft: "6px" });
|
||||||
|
textInp.onchange = () => {
|
||||||
|
textPattern = textInp.value.trim();
|
||||||
|
localStorage.textPattern = textPattern;
|
||||||
|
};
|
||||||
|
textRow.append(textChk, textLbl, textInp);
|
||||||
|
|
||||||
|
// Max scroll config
|
||||||
|
const maxRow = document.createElement("div");
|
||||||
|
const maxLbl = document.createElement("label");
|
||||||
|
maxLbl.innerText = " Max scrolls (0 = unlimited):";
|
||||||
|
const maxInp = document.createElement("input");
|
||||||
|
maxInp.type = "number";
|
||||||
|
maxInp.min = "0";
|
||||||
|
maxInp.value = maxScrolls;
|
||||||
|
Object.assign(maxInp.style, { width: "60px", marginLeft: "6px" });
|
||||||
|
maxInp.onchange = () => {
|
||||||
|
maxScrolls = parseInt(maxInp.value, 10) || 0;
|
||||||
|
localStorage.maxScrolls = maxScrolls;
|
||||||
|
};
|
||||||
|
maxRow.append(maxLbl, maxInp);
|
||||||
|
|
||||||
|
// Auto show button config
|
||||||
|
const autoRow = document.createElement("div");
|
||||||
|
const autoChk = document.createElement("input");
|
||||||
|
autoChk.type = "checkbox";
|
||||||
|
autoChk.checked = autoShowButton;
|
||||||
|
autoChk.onchange = () => {
|
||||||
|
autoShowButton = autoChk.checked;
|
||||||
|
localStorage.autoShowButton = autoShowButton;
|
||||||
|
};
|
||||||
|
const autoLbl = document.createElement("label");
|
||||||
|
autoLbl.innerText = " Show floating button automatically";
|
||||||
|
autoRow.append(autoChk, autoLbl);
|
||||||
|
|
||||||
|
container.append(hrefRow, textRow, maxRow, autoRow);
|
||||||
|
|
||||||
|
if (!useHrefPattern && !useTextPattern) {
|
||||||
|
const warn = document.createElement("div");
|
||||||
|
warn.textContent = "⚠ Warning: Both filters are off. All links will be collected.";
|
||||||
|
Object.assign(warn.style, { color: "red", margin: "6px 0" });
|
||||||
|
container.appendChild(warn);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!configOnly) {
|
||||||
|
const box = document.createElement("textarea");
|
||||||
|
box.value = links.join("\n");
|
||||||
|
Object.assign(box.style, { width: "100%", height: "200px", margin: "8px 0" });
|
||||||
|
container.appendChild(box);
|
||||||
|
|
||||||
|
const dl = document.createElement("button");
|
||||||
|
dl.innerText = "⬇ Download .txt";
|
||||||
|
Object.assign(dl.style, {
|
||||||
|
background: "#1da1f2", color: "white", border: "none",
|
||||||
|
padding: "6px 10px", borderRadius: "4px", cursor: "pointer", marginRight: "8px"
|
||||||
|
});
|
||||||
|
dl.onclick = () => {
|
||||||
|
const blob = new Blob([links.join("\n")], { type: "text/plain" });
|
||||||
|
const url = URL.createObjectURL(blob);
|
||||||
|
const a = document.createElement("a");
|
||||||
|
a.href = url; a.download = "extracted_links.txt";
|
||||||
|
document.body.appendChild(a); a.click(); a.remove();
|
||||||
|
URL.revokeObjectURL(url);
|
||||||
|
};
|
||||||
|
container.appendChild(dl);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start + Close
|
||||||
|
const start = document.createElement("button");
|
||||||
|
start.innerText = "▶ Start";
|
||||||
|
Object.assign(start.style, {
|
||||||
|
background: "#1da1f2", color: "white", border: "none",
|
||||||
|
padding: "6px 10px", borderRadius: "4px", cursor: "pointer", marginRight: "8px"
|
||||||
|
});
|
||||||
|
start.onclick = () => { container.remove(); autoScrollAndExtract(); };
|
||||||
|
|
||||||
|
const close = document.createElement("button");
|
||||||
|
close.innerText = "❌ Close";
|
||||||
|
Object.assign(close.style, {
|
||||||
|
background: "#aaa", color: "white", border: "none",
|
||||||
|
padding: "6px 10px", borderRadius: "4px", cursor: "pointer"
|
||||||
|
});
|
||||||
|
close.onclick = () => container.remove();
|
||||||
|
|
||||||
|
container.append(start, close);
|
||||||
|
document.body.appendChild(container);
|
||||||
|
|
||||||
|
if (!configOnly) {
|
||||||
|
alert(`✅ Extracted ${links.length} links (href: ${useHrefPattern ? hrefPattern : "ANY"}, text: ${useTextPattern ? textPattern : "ANY"}).`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function addSplitButton() {
|
||||||
|
if (document.getElementById("extract-btn")) return;
|
||||||
|
|
||||||
|
const wrap = document.createElement("div");
|
||||||
|
wrap.id = "extract-btn";
|
||||||
|
Object.assign(wrap.style, {
|
||||||
|
position: "fixed", top: "20px", right: "20px", zIndex: "999999",
|
||||||
|
display: "flex", borderRadius: "4px", overflow: "hidden",
|
||||||
|
boxShadow: "0 2px 4px rgba(0,0,0,0.2)"
|
||||||
|
});
|
||||||
|
|
||||||
|
const mainBtn = document.createElement("button");
|
||||||
|
mainBtn.innerText = "Extract Links";
|
||||||
|
Object.assign(mainBtn.style, {
|
||||||
|
background: "#1da1f2", color: "white", border: "none",
|
||||||
|
padding: "8px 12px", cursor: "pointer", fontSize: "14px", flex: "1"
|
||||||
|
});
|
||||||
|
mainBtn.onclick = autoScrollAndExtract;
|
||||||
|
|
||||||
|
const cfgBtn = document.createElement("button");
|
||||||
|
cfgBtn.innerText = "⛭";
|
||||||
|
Object.assign(cfgBtn.style, {
|
||||||
|
background: "#0d8ddb", color: "white", border: "none",
|
||||||
|
padding: "8px 10px", cursor: "pointer", fontSize: "14px", width: "40px"
|
||||||
|
});
|
||||||
|
cfgBtn.onclick = () => showOverlay([], true);
|
||||||
|
|
||||||
|
wrap.append(mainBtn, cfgBtn);
|
||||||
|
document.body.appendChild(wrap);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tampermonkey menu items
|
||||||
|
if (typeof GM_registerMenuCommand !== "undefined") {
|
||||||
|
GM_registerMenuCommand("Show Button", addSplitButton);
|
||||||
|
GM_registerMenuCommand("Extract Links", autoScrollAndExtract);
|
||||||
|
GM_registerMenuCommand("Config", () => showOverlay([], true));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Auto-show only if enabled
|
||||||
|
if (autoShowButton) {
|
||||||
|
setInterval(addSplitButton, 2000);
|
||||||
|
}
|
||||||
|
})();
|
||||||
177
luxweather-tweaks.user.js
Normal file
177
luxweather-tweaks.user.js
Normal file
@@ -0,0 +1,177 @@
|
|||||||
|
// ==UserScript==
|
||||||
|
// @name LuxWeather Frame & Scale Toggle (Settings Integration)
|
||||||
|
// @namespace http://tampermonkey.net/
|
||||||
|
// @version 1.8
|
||||||
|
// @description Toggle CRT frame and scaling (Off, Default, 4:3, 16:9, Stretch) via settings panel with localStorage + keyboard shortcuts
|
||||||
|
// @match https://luxweather.com/*
|
||||||
|
// @run-at document-idle
|
||||||
|
// ==/UserScript==
|
||||||
|
|
||||||
|
(function () {
|
||||||
|
const LS_KEY_FRAME = 'luxweather-frame-on';
|
||||||
|
const LS_KEY_SCALE = 'luxweather-scale-mode';
|
||||||
|
|
||||||
|
let frameOn = localStorage.getItem(LS_KEY_FRAME) !== 'false';
|
||||||
|
let scaleMode = parseInt(localStorage.getItem(LS_KEY_SCALE) || '0');
|
||||||
|
|
||||||
|
const style = document.createElement('style');
|
||||||
|
style.innerHTML = `
|
||||||
|
.no-frame .tv--kitty::after,
|
||||||
|
.no-frame .tv--regular::after,
|
||||||
|
.no-frame .tv--apple::after {
|
||||||
|
background-image: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.scale-default .tv {
|
||||||
|
--width: 360;
|
||||||
|
--height: 270;
|
||||||
|
}
|
||||||
|
.scale-default .tv--kitty .tv-screen,
|
||||||
|
.scale-default .tv--regular .tv-screen,
|
||||||
|
.scale-default .tv--apple .tv-screen {
|
||||||
|
top: 50% !important;
|
||||||
|
left: 50% !important;
|
||||||
|
transform: translate(-50%, -50%) !important;
|
||||||
|
width: calc((360 / var(--width)) * 100%);
|
||||||
|
height: calc((270 / var(--height)) * 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.scale-fit .tv,
|
||||||
|
.scale-stretch .tv {
|
||||||
|
display: flex !important;
|
||||||
|
align-items: center !important;
|
||||||
|
justify-content: center !important;
|
||||||
|
width: 100vw !important;
|
||||||
|
height: 100vh !important;
|
||||||
|
margin: 0 !important;
|
||||||
|
padding: 0 !important;
|
||||||
|
background: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.scale-fit .tv-screen,
|
||||||
|
.scale-stretch .tv-screen {
|
||||||
|
position: relative !important;
|
||||||
|
background: none !important;
|
||||||
|
top: unset !important;
|
||||||
|
left: unset !important;
|
||||||
|
transform: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.scale-fit .tv-screen {
|
||||||
|
width: 100vw;
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.scale-4-3 .tv-screen {
|
||||||
|
aspect-ratio: 4 / 3;
|
||||||
|
max-height: 100vh;
|
||||||
|
max-width: calc(100vh * (4 / 3));
|
||||||
|
}
|
||||||
|
|
||||||
|
.scale-16-9 .tv-screen {
|
||||||
|
aspect-ratio: 16 / 9;
|
||||||
|
max-height: 100vh;
|
||||||
|
max-width: calc(100vh * (16 / 9));
|
||||||
|
}
|
||||||
|
|
||||||
|
.scale-stretch .tv-screen {
|
||||||
|
width: 100vw !important;
|
||||||
|
height: 100vh !important;
|
||||||
|
object-fit: fill !important;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
document.head.appendChild(style);
|
||||||
|
|
||||||
|
function applyState() {
|
||||||
|
document.body.classList.toggle('no-frame', !frameOn);
|
||||||
|
|
||||||
|
document.body.classList.remove(
|
||||||
|
'scale-default',
|
||||||
|
'scale-fit',
|
||||||
|
'scale-4-3',
|
||||||
|
'scale-16-9',
|
||||||
|
'scale-stretch'
|
||||||
|
);
|
||||||
|
|
||||||
|
switch (scaleMode) {
|
||||||
|
case 1:
|
||||||
|
document.body.classList.add('scale-default'); break;
|
||||||
|
case 2:
|
||||||
|
document.body.classList.add('scale-fit', 'scale-4-3'); break;
|
||||||
|
case 3:
|
||||||
|
document.body.classList.add('scale-fit', 'scale-16-9'); break;
|
||||||
|
case 4:
|
||||||
|
document.body.classList.add('scale-stretch'); break;
|
||||||
|
}
|
||||||
|
|
||||||
|
localStorage.setItem(LS_KEY_FRAME, frameOn);
|
||||||
|
localStorage.setItem(LS_KEY_SCALE, scaleMode);
|
||||||
|
}
|
||||||
|
|
||||||
|
function injectSettingsToggles() {
|
||||||
|
const settings = document.getElementById('settings_menu');
|
||||||
|
if (!settings || settings.querySelector('#toggle_frame')) return;
|
||||||
|
|
||||||
|
const group = document.createElement('div');
|
||||||
|
group.className = 'settings_group';
|
||||||
|
group.innerHTML = `
|
||||||
|
<div class="settings_label">Custom Display</div>
|
||||||
|
<div class="settings_options">
|
||||||
|
<label><input type="checkbox" id="toggle_frame"> Bezel</label>
|
||||||
|
<br>
|
||||||
|
<label>Scale:
|
||||||
|
<select id="toggle_scale">
|
||||||
|
<option value="0">Off</option>
|
||||||
|
<option value="1">Default</option>
|
||||||
|
<option value="2">Fit 4:3</option>
|
||||||
|
<option value="3">Fit 16:9</option>
|
||||||
|
<option value="4">Stretch</option>
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
settings.appendChild(group);
|
||||||
|
|
||||||
|
const frameCheckbox = document.getElementById('toggle_frame');
|
||||||
|
const scaleSelect = document.getElementById('toggle_scale');
|
||||||
|
|
||||||
|
frameCheckbox.checked = frameOn;
|
||||||
|
scaleSelect.value = scaleMode;
|
||||||
|
|
||||||
|
frameCheckbox.addEventListener('change', () => {
|
||||||
|
frameOn = frameCheckbox.checked;
|
||||||
|
applyState();
|
||||||
|
});
|
||||||
|
|
||||||
|
scaleSelect.addEventListener('change', () => {
|
||||||
|
scaleMode = parseInt(scaleSelect.value);
|
||||||
|
applyState();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Keyboard shortcuts
|
||||||
|
window.addEventListener('keydown', (e) => {
|
||||||
|
if (e.altKey && e.key === 'f') {
|
||||||
|
frameOn = !frameOn;
|
||||||
|
applyState();
|
||||||
|
} else if (e.altKey && e.key === 's') {
|
||||||
|
scaleMode = (scaleMode + 1) % 5;
|
||||||
|
applyState();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
applyState();
|
||||||
|
|
||||||
|
const observeSettings = new MutationObserver(() => {
|
||||||
|
setTimeout(injectSettingsToggles, 50);
|
||||||
|
});
|
||||||
|
|
||||||
|
const interval = setInterval(() => {
|
||||||
|
const settings = document.getElementById('settings_menu');
|
||||||
|
if (settings) {
|
||||||
|
clearInterval(interval);
|
||||||
|
injectSettingsToggles();
|
||||||
|
observeSettings.observe(document.body, { childList: true, subtree: true });
|
||||||
|
}
|
||||||
|
}, 300);
|
||||||
|
})();
|
||||||
49
yamaha-avr-webui-unlock.user.js
Normal file
49
yamaha-avr-webui-unlock.user.js
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
// ==UserScript==
|
||||||
|
// @name Yamaha AVR Full UI Unlock
|
||||||
|
// @namespace http://tampermonkey.net/
|
||||||
|
// @version 1.4
|
||||||
|
// @description Unlocks the full web UI for the Yamaha RX-V573 by spoofing the model name to a high-end model.
|
||||||
|
// @match http://rx-v573.local/*
|
||||||
|
// @grant none
|
||||||
|
// @run-at document-start
|
||||||
|
// ==/UserScript==
|
||||||
|
|
||||||
|
(function() {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
// Intercept the setter for the g_Info object to modify it as soon as it's created.
|
||||||
|
Object.defineProperty(window, 'g_Info', {
|
||||||
|
get: function() {
|
||||||
|
return window._g_Info_Original;
|
||||||
|
},
|
||||||
|
set: function(value) {
|
||||||
|
console.log('Tampermonkey: Intercepted g_Info setter. Original modelName is:', value.modelName);
|
||||||
|
|
||||||
|
// Set the modelName to a high-end model for any basic checks.
|
||||||
|
//value.modelName = 'RX-V3073';
|
||||||
|
|
||||||
|
// Override ALL model-checking functions to always return true.
|
||||||
|
// This is the most reliable way to unlock everything.
|
||||||
|
value.blnModelTypeHigh = function() { return false; };
|
||||||
|
value.blnModelType3073 = function() { return false; };
|
||||||
|
value.blnModelType820 = function() { return false; };
|
||||||
|
value.blnModelType673 = function() { return false; };
|
||||||
|
value.blnModelTypeUnder573 = function() { return true; };
|
||||||
|
|
||||||
|
// This is the function you found that works. Override it for good measure.
|
||||||
|
value.blnOnlyFriendlyNameAvail = function() { return false; };
|
||||||
|
|
||||||
|
// Also override the HDMI and Zone checks to ensure the UI displays those controls.
|
||||||
|
//value.m_blnHasHdmi = true;
|
||||||
|
//value.m_blnZone2 = true;
|
||||||
|
//value.m_blnZone3 = true;
|
||||||
|
//value.m_blnZone4 = true;
|
||||||
|
|
||||||
|
//console.log('Tampermonkey: All model type checks have been spoofed to return true.');
|
||||||
|
|
||||||
|
// Store the modified object for the rest of the script to use.
|
||||||
|
window._g_Info_Original = value;
|
||||||
|
},
|
||||||
|
configurable: true
|
||||||
|
});
|
||||||
|
})();
|
||||||
29
youtube-shorts-redirect.user.js
Normal file
29
youtube-shorts-redirect.user.js
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
// ==UserScript==
|
||||||
|
// @name YouTube Shorts Redirect
|
||||||
|
// @namespace http://tampermonkey.net/
|
||||||
|
// @version 0.1
|
||||||
|
// @description Redirects YouTube Shorts links to the standard watch page.
|
||||||
|
// @author You
|
||||||
|
// @match https://www.youtube.com/shorts/*
|
||||||
|
// @grant none
|
||||||
|
// @run-at document-start
|
||||||
|
// ==/UserScript==
|
||||||
|
|
||||||
|
(function() {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
// Get the current URL
|
||||||
|
const currentUrl = window.location.href;
|
||||||
|
|
||||||
|
// Check if the URL matches the Shorts pattern (e.g., https://www.youtube.com/shorts/v1de0id)
|
||||||
|
if (currentUrl.includes('youtube.com/shorts/')) {
|
||||||
|
// Extract the video ID, which is the part after the last '/'
|
||||||
|
const videoId = currentUrl.substring(currentUrl.lastIndexOf('/') + 1);
|
||||||
|
|
||||||
|
// Construct the new URL in the standard watch format
|
||||||
|
const newUrl = 'https://www.youtube.com/watch?v=' + videoId;
|
||||||
|
|
||||||
|
// Redirect the browser to the new URL
|
||||||
|
window.location.replace(newUrl);
|
||||||
|
}
|
||||||
|
})();
|
||||||
Reference in New Issue
Block a user