Semprot Helper
// ==UserScript==
// @name Semprot Helpers
// @description Semprot Helper
// @author Pommpol
// @version v2.1.1
// @match https://www.semprot.com/threads/*
// @match https://www.semprot.com/*
// @icon https://www.google.com/s2/favicons?sz=64&domain=semprot.com
// @grant GM_getValue
// @grant GM_setValue
// @grant GM_xmlhttpRequest
// @license MIT
// @namespace https://greasyfork.org/users/1558525
// ==/UserScript==
(function () {
"use strict";
const URLPattern = {
isDood(url) {
const doodUrl = ["dsvplay.com", "myvidplay.com"];
return doodUrl.some((domain) => url.hostname.endsWith(domain));
},
isStreamtape(url) {
const streamtapeURL = ["streamtape.com"];
return streamtapeURL.some((domain) => url.hostname.endsWith(domain));
},
isVidara(url) {
const vidaraURL = ["vidara.to"];
return vidaraURL.some((domain) => url.hostname.endsWith(domain));
},
};
// --- 1. CONFIG & STATE ---
const SETTINGS_MAP = [
{
name: "imageBamLoader",
tip: "Get full size ImageBam",
value: GM_getValue("imageBamLoader", true),
},
{
name: "videoEmbeder",
tip: "Embed video streams",
value: GM_getValue("videoEmbeder", true),
},
];
// --- 2. STYLES ---
const injectStyles = () => {
const style = document.createElement("style");
style.innerHTML = `
.sh-panel { position: fixed; width: 220px; padding: 10px; background: rgba(0,0,0,0.85); color: white; border-radius: 5px; z-index: 9999; font-family: Arial; cursor: grab; }
.sh-panel2 { display : flex;flex-direction : column;align-items : flex-start;position: fixed; width: 220px; padding: 10px; background: rgba(0,0,0,1); color: white; border-radius: 5px; z-index: 9999; font-family: Arial; cursor: grab; }
.sh-btn { position: fixed; z-index: 9999; padding: 8px 12px; border-radius: 5px; border: 1px solid white; background: rgba(0,0,0,0.8); color: white; cursor: pointer; left: 20px; bottom: 20px; }
.sh-tooltip { position: relative; top: -40px; right: -120px; background: #595959db; color: #ededed; padding: 5px; border-radius: 4px; font-size: 12px; }
.hidden { display: none; }
.swapping { filter: blur(5px) grayscale(80%); transition: filter 0.5s ease; cursor: wait; }
.iframe-wrapper { position: relative; margin-top: 10px; }
.sh-close-btn { position: absolute; top: 0; right: 0; background: red; color: white; border-radius: 10%; border : 2px solid white; cursor: pointer; padding : 0.5rem; margin : 0;}
.spin{ animation: spin 5s linear infinite;}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
`;
document.head.appendChild(style);
};
// --- 3. SERVICES (API/Network) ---
const MediaService = {
async fetchImageBam(url) {
console.log(`Tying to get ${url}...`);
var date = new Date();
date.setTime(date.getTime() + (6 * 60 * 60 * 1000));
const expires = date.toUTCString();
return new Promise((resolve) => {
GM_xmlhttpRequest({
method: "GET",
url: url,
headers: { Cookie: `sfw_inter=1; nsfw_inter=1;expires=${expires}; path=/` },
onload: (res) => {
const doc = new DOMParser().parseFromString(
res.responseText,
"text/html",
);
const img = doc.querySelector("img.main-image");
resolve(img ? img.src : null);
},
});
});
},
async fetchImageBox(url) {
return new Promise((resolve) => {
GM_xmlhttpRequest({
method: "GET",
url: url,
onload: (res) => {
const doc = new DOMParser().parseFromString(
res.responseText,
"text/html",
);
const img = doc.querySelector("#img");
resolve(img ? img.src : null);
},
});
});
},
};
// --- 4. UI COMPONENTS ---
const UI = {
createButton(text, className, onClick) {
const btn = document.createElement("button");
btn.innerText = text;
btn.className = className;
btn.onclick = onClick;
return btn;
},
createToggle(setting) {
const label = document.createElement("label");
label.style.display = "block";
label.innerHTML = `${setting.name}: `;
const input = document.createElement("input");
input.type = "checkbox";
input.checked = setting.value;
input.onchange = () => GM_setValue(setting.name, input.checked);
label.appendChild(input);
return label;
},
createLink(url, text) {
const a = document.createElement("a");
a.innerText = text;
a.href = url;
return a;
},
};
class ThreadOperation {
constructor(url) {
this.url = new URL(url);
}
getThreadStarter() {
return document.querySelector(".username.u-concealed").innerText;
}
getSlug() {
return this.url.pathname.split("/")[2];
}
getHost() {
return this.url.hostname;
}
getThreadUrlOnly() {
return "https://" + this.getHost() + "/threads/" + this.getSlug();
}
}
// --- 6. CORE LOGIC / HANDLERS ---
const App = {
observer: null,
init() {
injectStyles();
this.setupObserver();
this.renderUI();
this.bindGlobalEvents();
},
setupObserver() {
this.observer = new IntersectionObserver(
(entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting && entry.target.dataset.pendingSrc) {
const img = entry.target;
img.src = img.dataset.pendingSrc;
img.onload = () => {
img.classList.remove("swapping");
img.classList.contains("img-large")
? img.classList.remove("img-large")
: img.classList.add("img-large");
delete img.dataset.pendingSrc;
};
this.observer.unobserve(img);
}
});
},
{ rootMargin: "100px" },
);
},
renderUI() {
const mainBtn = UI.createButton("Tools", "sh-btn", () => {
mainBtn.style.display = "none";
panel.style.display = "block";
});
const panel = document.createElement("div");
panel.className = "sh-panel hidden";
const panel2 = document.createElement("div");
panel2.className = "sh-panel2";
const t = new ThreadOperation(window.location.href);
panel2.appendChild(
UI.createButton("wkwk", "", () => {
panel2.appendChild(
UI.createLink(t.getThreadUrlOnly(), t.getThreadStarter()),
);
}),
);
// panel2.innerHTML = ThreadOP.getThreadStart();
const backBtn = UI.createButton("← Back", "", () => {
panel.style.display = "none";
mainBtn.style.display = "block";
});
// Restore position
const pos = GM_getValue("panelPos", { left: "20px", bottom: "20px" });
const pos2 = GM_getValue("panel2Pos", { left: "10px", bottom: "50px" });
Object.assign(panel.style, pos);
Object.assign(panel2.style, pos);
panel.appendChild(backBtn);
SETTINGS_MAP.forEach((s) => panel.appendChild(UI.createToggle(s)));
document.body.append(mainBtn, panel, panel2);
this.makeDraggable(mainBtn);
this.makeDraggable(panel);
// this.makeDraggable2(panel2);
},
makeDraggable(el) {
let isDragging = false,
offset = [0, 0];
el.onmousedown = (e) => {
// if (["INPUT", "BUTTON"].includes(e.target.tagName)) return;
isDragging = true;
offset = [el.offsetLeft - e.clientX, el.offsetTop - e.clientY];
};
document.onmousemove = (e) => {
if (!isDragging) return;
el.style.left = e.clientX + offset[0] + "px";
el.style.top = e.clientY + offset[1] + "px";
};
document.onmouseup = () => {
if (isDragging) {
isDragging = false;
if (el.classList.contains("sh-panel"))
GM_setValue("panelPos", { left: el.style.left, top: el.style.top });
if (el.classList.contains("sh-panel2"))
GM_setValue("panel2Pos", {
left: el.style.left,
top: el.style.top,
});
}
};
},
makeDraggable2(el) {
let isDragging = false,
offset = [0, 0];
el.onmousedown = (e) => {
// if (["INPUT", "BUTTON"].includes(e.target.tagName)) return;
isDragging = true;
offset = [el.offsetLeft - e.clientX, el.offsetTop - e.clientY];
};
document.onmousemove = (e) => {
if (!isDragging) return;
el.style.left = e.clientX + offset[0] + "px";
el.style.top = e.clientY + offset[1] + "px";
};
document.onmouseup = () => {
if (isDragging) {
isDragging = false;
if (el.classList.contains("sh-panel"))
GM_setValue("panelPos", { left: el.style.left, top: el.style.top });
if (el.classList.contains("sh-panel2"))
GM_setValue("panel2Pos", {
left: el.style.left,
top: el.style.top,
});
}
};
},
bindGlobalEvents() {
document.addEventListener(
"click",
async (e) => {
const target = e.target;
const link = target.closest("a");
const url = new URL(link.href);
let embedUrl = null;
// Video Embedding Logic
if (link && GM_getValue("videoEmbeder", true)) {
if (url.hostname.includes("sendvid.com")) {
embedUrl = `https://${url.hostname}/embed/${url.pathname}`;
}
else if (
URLPattern.isStreamtape(url) ||
URLPattern.isDood(url) ||
URLPattern.isVidara(url)
) {
embedUrl = `https://${url.hostname}/e/${url.pathname.split("/")[2]}`;
}
if (embedUrl) {
e.preventDefault();
this.spawnIframe(link, embedUrl);
}
}
// ImageBam Logic
if (GM_getValue("imageBamLoader", true)){
if (target.tagName === "IMG") {
if (target.classList.contains("swapping")) return
const parentLink = target.closest("a");
if (parentLink?.href.includes("imagebam")) {
e.preventDefault();
if (target.classList.contains("img-large")) {
const oriUrl = target.dataset.url;
if (oriUrl) this.swapImage(target, oriUrl);
} else {
const fullSrc = await MediaService.fetchImageBam(
parentLink.href,
);
if (fullSrc) this.swapImage(target, fullSrc);
}
}
}
else {
if (url.hostname.includes("imagebam.com")) {
e.preventDefault();
if (link.querySelector('img') !== null) return;
if (link.classList.contains("append-img")) return;
const img = document.createElement("img");
img.className = "spin";
link.className = "append-img";
img.onclick = function () {
link.classList.remove("append-img");
this.remove(); // destroys itself
};
img.src ="https://cdn-icons-png.flaticon.com/128/3305/3305803.png";
link.appendChild(img);
const full = await MediaService.fetchImageBam(url);
if (full) {
img.classList.remove("spin");
this.swapImage(img, full);
}
}
}
}
},
true,
);
},
swapImage(img, newSrc) {
console.log("trying to swap")
console.log(img);
img.classList.add("swapping");
img.dataset.pendingSrc = newSrc;
this.observer.observe(img);
},
spawnIframe(anchor, url) {
const wrapper = document.createElement("div");
wrapper.className = "iframe-wrapper";
wrapper.innerHTML = `
<button class="sh-close-btn">Close</button>
<iframe width="560" height="315" src="${url}" frameborder="0" allowfullscreen></iframe>
`;
wrapper.querySelector(".sh-close-btn").onclick = () => wrapper.remove();
anchor.after(wrapper);
},
};
App.init();
})();