Show video preview on hover for DoodStream video links
// ==UserScript==
// @name DoodStream Video Hover Preview
// @namespace http://tampermonkey.net/
// @version 0.3
// @description Show video preview on hover for DoodStream video links
// @author You
// @match *://doodstream.com/*
// @match *://dsvplay.com/*
// @match *://nobodyhome.ws/*
// @grant GM_xmlhttpRequest
// ==/UserScript==
(function() {
'use strict';
// Base configuration for most domains
const BASE_CONFIG = {
embedPath: '/e/',
videoPath: '/d/',
thumbnailPatterns: [
/https:\/\/img\.doodcdn\.io\/splash\/[a-zA-Z0-9]+\.jpg/,
/https:\/\/[^"'\s]+\.jpg/,
/https:\/\/[^"'\s]+\.png/,
/https:\/\/[^"'\s]+\.webp/
]
};
// Function to create tooltip
function createTooltip() {
const tooltip = document.createElement('div');
tooltip.id = 'video-tooltip';
tooltip.style.position = 'fixed';
tooltip.style.background = 'rgba(0,0,0,0.8)';
tooltip.style.color = 'white';
tooltip.style.padding = '10px';
tooltip.style.borderRadius = '5px';
tooltip.style.zIndex = '10000';
tooltip.style.display = 'none';
tooltip.style.pointerEvents = 'none';
document.body.appendChild(tooltip);
return tooltip;
}
// Function to create "Copy All Links" button
function createCopyAllButton() {
const button = document.createElement('button');
button.type = 'button';
button.className = 'btn btn-success ml-2';
button.innerHTML = '<i class="fad fa-copy"></i> Copy All Links';
button.addEventListener('click', () => {
copyAllLinks(button);
});
// Find the btn-group div and append the button
const btnGroup = document.querySelector('.btn-group');
if (btnGroup) {
btnGroup.appendChild(button);
} else {
// Fallback to fixed position if btn-group not found
button.style.position = 'fixed';
button.style.top = '20px';
button.style.right = '20px';
button.style.zIndex = '10001';
document.body.appendChild(button);
}
return button;
}
// Function to extract file code from DoodStream URL
function extractFileCode(url) {
const match = url.match(/\/[de]\/([a-z0-9]+)/i);
return match ? match[1] : null;
}
// Function to copy all links
async function copyAllLinks(button) {
const originalHTML = button.innerHTML;
button.innerHTML = '<i class="fad fa-spinner fa-spin"></i> Loading...';
button.disabled = true;
const videoLinks = document.querySelectorAll('a[href*="/d/"], a[href*="/e/"], a[href*="dood.to"], a[href*="dood.so"], a[href*="dood.la"], a[href*="doodstream.com"]');
if (videoLinks.length === 0) {
button.innerHTML = '<i class="fad fa-times"></i> No links found';
setTimeout(() => {
button.innerHTML = originalHTML;
button.disabled = false;
}, 2000);
return;
}
const results = [];
let processed = 0;
for (const link of videoLinks) {
const fileCode = extractFileCode(link.href);
if (!fileCode) continue;
// Check cache first
if (previewUrlCache.has(fileCode)) {
const previewUrl = previewUrlCache.get(fileCode);
results.push(`[img]${previewUrl}[/img]\n${link.href}`);
processed++;
button.innerHTML = `<i class="fad fa-spinner fa-spin"></i> ${processed}/${videoLinks.length}`;
continue;
}
// Fetch preview URL
await new Promise((resolve) => {
GM_xmlhttpRequest({
method: 'GET',
url: `https://dsvplay.com/e/${fileCode}`,
onload: function(response) {
let previewUrl = null;
for (const pattern of BASE_CONFIG.thumbnailPatterns) {
const match = response.responseText.match(pattern);
if (match) {
previewUrl = match[0];
break;
}
}
if (previewUrl) {
previewUrlCache.set(fileCode, previewUrl);
results.push(`[img]${previewUrl}[/img]\n${link.href}`);
} else {
results.push(link.href);
}
processed++;
button.innerHTML = `<i class="fad fa-spinner fa-spin"></i> ${processed}/${videoLinks.length}`;
resolve();
},
onerror: function() {
results.push(link.href);
processed++;
button.innerHTML = `<i class="fad fa-spinner fa-spin"></i> ${processed}/${videoLinks.length}`;
resolve();
}
});
});
// Small delay to avoid overwhelming the server
await new Promise(resolve => setTimeout(resolve, 100));
}
// Copy to clipboard
const text = results.join('\n\n');
navigator.clipboard.writeText(text).then(() => {
button.innerHTML = `<i class="fad fa-check"></i> Copied ${results.length} links!`;
setTimeout(() => {
button.innerHTML = originalHTML;
button.disabled = false;
}, 2000);
}).catch(() => {
button.innerHTML = '<i class="fad fa-times"></i> Copy failed';
setTimeout(() => {
button.innerHTML = originalHTML;
button.disabled = false;
}, 2000);
});
}
// Get all DoodStream video links
const videoLinks = document.querySelectorAll('a[href*="/d/"], a[href*="/e/"], a[href*="dood.to"], a[href*="dood.so"], a[href*="dood.la"], a[href*="doodstream.com"]');
if (videoLinks.length === 0) {
console.log('No DoodStream video links found');
return;
}
const tooltip = createTooltip();
// Wait a bit for the page to load before adding the button
setTimeout(() => {
createCopyAllButton();
}, 1000);
// Cache successful preview src by fileCode to avoid reloading
const previewCache = new Map();
const previewUrlCache = new Map();
// Function to add hover listeners to links
function addHoverListeners(links) {
links.forEach(link => {
const fileCode = extractFileCode(link.href);
if (!fileCode || link.hasAttribute('data-hover-added')) return;
link.setAttribute('data-hover-added', 'true');
console.log('Adding hover to:', link.href, 'fileCode:', fileCode);
link.addEventListener('mouseenter', (e) => {
console.log('Hovering on:', link.href);
const rect = e.target.getBoundingClientRect();
tooltip.style.left = rect.left + 'px';
tooltip.style.top = (rect.top - 200) + 'px'; // Position above
// Show embed iframe for preview
const embedUrl = `https://dsvplay.com/e/${fileCode}`;
const iframe = document.createElement('iframe');
iframe.src = embedUrl;
iframe.style.width = '320px';
iframe.style.height = '180px';
iframe.style.border = '0';
iframe.style.display = 'block';
iframe.style.pointerEvents = 'none';
tooltip.innerHTML = '';
tooltip.appendChild(iframe);
tooltip.style.display = 'block';
});
link.addEventListener('mouseleave', () => {
tooltip.style.display = 'none';
});
// Add copy button
const copyLinkBtn = document.createElement('button');
copyLinkBtn.textContent = 'Copy Link';
copyLinkBtn.style.fontSize = '12px';
copyLinkBtn.style.padding = '2px 5px';
copyLinkBtn.style.marginLeft = '10px';
link.parentNode.insertBefore(copyLinkBtn, link.nextSibling);
copyLinkBtn.addEventListener('click', () => {
const cached = previewUrlCache.get(fileCode);
if (cached) {
const text = `[img]${cached}[/img]\n${link.href}`;
navigator.clipboard.writeText(text).then(() => {
console.log('Copied link');
});
} else {
GM_xmlhttpRequest({
method: 'GET',
url: `https://dsvplay.com/e/${fileCode}`,
onload: function(response) {
let previewUrl = null;
for (const pattern of BASE_CONFIG.thumbnailPatterns) {
const match = response.responseText.match(pattern);
if (match) {
previewUrl = match[0];
break;
}
}
if (previewUrl) {
previewUrlCache.set(fileCode, previewUrl);
const text = `[img]${previewUrl}[/img]\n${link.href}`;
navigator.clipboard.writeText(text).then(() => {
console.log('Copied link');
});
}
}
});
}
});
});
}
// Initial links
addHoverListeners(videoLinks);
// Watch for new links (for dynamic content)
const observer = new MutationObserver((mutations) => {
mutations.forEach((mutation) => {
mutation.addedNodes.forEach((node) => {
if (node.nodeType === Node.ELEMENT_NODE) {
const newLinks = node.querySelectorAll ? node.querySelectorAll('a[href*="/d/"], a[href*="/e/"], a[href*="dood.to"], a[href*="dood.so"], a[href*="dood.la"], a[href*="doodstream.com"]') : [];
addHoverListeners(newLinks);
if (node.tagName === 'A' && (node.href.includes('/d/') || node.href.includes('/e/') || node.href.includes('dood.to') || node.href.includes('dood.so') || node.href.includes('dood.la') || node.href.includes('doodstream.com'))) {
addHoverListeners([node]);
}
}
});
});
});
observer.observe(document.body, { childList: true, subtree: true });
})();