// ==UserScript==
// @name Coursera Screenshot Downloader
// @namespace https://github.com/shiquda/shiquda_UserScript
// @supportURL https://github.com/shiquda/shiquda_UserScript/issues
// @version 0.3
// @description Add screenshot functionality to all videos with toggle options
// @author shiquda
// @match https://www.coursera.org/*
// @grant GM_getValue
// @grant GM_setValue
// @grant GM_registerMenuCommand
// @license MIT
// @downloadURL none
// ==/UserScript==
(function () {
'use strict';
// Camera icon SVG
const cameraSVG = ``;
// Check mark SVG
const checkSVG = ``;
// Screenshot settings
let settings = {
download: true,
copyToClipboard: false
};
// Load settings from storage
function loadSettings() {
const savedSettings = GM_getValue('screenshotSettings');
if (savedSettings) {
settings = JSON.parse(savedSettings);
console.log('Loaded settings:', settings);
} else {
console.log('No saved settings found, using defaults.');
}
}
// Save settings to storage
function saveSettings() {
GM_setValue('screenshotSettings', JSON.stringify(settings));
console.log('Settings saved:', settings);
}
// Register menu commands for toggling settings
function registerMenuCommands() {
GM_registerMenuCommand(`Download ${settings.download ? '✅' : '❌'}`, () => {
settings.download = !settings.download;
saveSettings();
console.log(`Download setting toggled to ${settings.download}`);
location.reload();
});
GM_registerMenuCommand(`Copy to Clipboard ${settings.copyToClipboard ? '✅' : '❌'}`, () => {
settings.copyToClipboard = !settings.copyToClipboard;
saveSettings();
console.log(`Copy to Clipboard setting toggled to ${settings.copyToClipboard}`);
location.reload();
});
}
// Function to replace invalid filename characters with underscores
function sanitizeFilename(filename) {
return filename.replace(/[<>:"/\\|?*]+/g, '_');
}
// Initialize video elements
function initVideos() {
// Get all video elements
const videos = document.querySelectorAll('video');
videos.forEach(video => {
// Avoid adding button multiple times
if (video.dataset.screenshotAdded) return;
video.dataset.screenshotAdded = "true";
// Create screenshot button
const btn = document.createElement('button');
btn.innerHTML = cameraSVG;
btn.style.cssText = `
bottom: 40px;
right: 10px;
background: rgba(0,0,0,0.5);
border: none;
border-radius: 4px;
padding: 5px;
cursor: pointer;
color: white;
z-index: 9999;
transition: background 0.3s;
`;
// Add hover effect
btn.addEventListener('mouseenter', () => {
btn.style.background = 'rgba(0,0,0,0.7)';
});
btn.addEventListener('mouseleave', () => {
btn.style.background = 'rgba(0,0,0,0.5)';
});
// Add button to video container
const container = video.parentNode.querySelector('.icon-container') || video.parentNode;
container.style.position = 'relative';
container.appendChild(btn);
// Add click event
btn.addEventListener('click', async () => {
try {
const canvas = document.createElement('canvas');
canvas.width = video.videoWidth;
canvas.height = video.videoHeight;
const ctx = canvas.getContext('2d');
ctx.drawImage(video, 0, 0);
const currentTime = Math.floor(video.currentTime);
const pageTitle = document.title;
const sanitizedTitle = sanitizeFilename(pageTitle);
// Download image if enabled
if (settings.download) {
const link = document.createElement('a');
link.download = `${sanitizedTitle}_${currentTime}s.png`;
link.href = canvas.toDataURL();
link.click();
console.log('Screenshot downloaded.');
}
// Copy to clipboard if enabled
if (settings.copyToClipboard) {
canvas.toBlob(async (blob) => {
try {
await navigator.clipboard.write([
new ClipboardItem({ 'image/png': blob })
]);
console.log('Screenshot copied to clipboard.');
// Change button to check mark
const originalHTML = btn.innerHTML;
btn.innerHTML = checkSVG;
// Disable button to prevent multiple clicks
btn.disabled = true;
// Revert back after 2 seconds
setTimeout(() => {
btn.innerHTML = originalHTML;
btn.disabled = false;
}, 1000);
} catch (err) {
console.error('Failed to copy to clipboard:', err);
}
}, 'image/png');
}
} catch (error) {
console.error('Screenshot failed:', error);
}
});
});
}
// Observe DOM changes for dynamically loaded content
const observer = new MutationObserver(mutations => {
if (document.querySelector('video')) initVideos();
});
// Start observing the entire document
observer.observe(document.body, {
childList: true,
subtree: true
});
// Load settings, register menu commands, and initialize
loadSettings();
registerMenuCommands();
initVideos();
})();