// ==UserScript==
// @name nicovideo downloader
// @description script for downloading videos from the nicovideo website
// @namespace kwlNjR37xBCMkr76P5eKA88apmOClCfZ
// @author kwlNjR37xBCMkr76P5eKA88apmOClCfZ
// @version 0002
// @match *://www.nicovideo.jp/watch/sm*
// @match *://ext.nicovideo.jp/?sm*
// @website https://greasyfork.org/en/scripts/548403
// @icon data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgZmlsbD0ibm9uZSI+PHBhdGggZmlsbD0iIzI1MjUyNSIgZD0iTTIwLjM4NC4wMjJIMy42MTZBMy41OTUgMy41OTUgMCAwIDAgLjAyMiAzLjYxNnYxNi43NjhhMy41OTQgMy41OTQgMCAwIDAgMy41OTQgMy41OTRoMTYuNzY4YTMuNTkzIDMuNTkzIDAgMCAwIDMuNTk0LTMuNTk0VjMuNjE2QTMuNTk0IDMuNTk0IDAgMCAwIDIwLjM4NC4wMjIiLz48cGF0aCBmaWxsPSIjRkZGIiBkPSJNMjAuMjEzIDUuNzI0aC01LjgxNmwyLjM4OC0yLjI3NWEuODUuODUgMCAwIDAgLjA0MS0xLjE4Mi44LjggMCAwIDAtMS4xNTMtLjA0M0wxMiA1LjcyNGwtMy42NzMtMy41YS44LjggMCAwIDAtMS4xNTMuMDQzLjg1Ljg1IDAgMCAwIC4wNDIgMS4xODJsMi4zODcgMi4yNzVIMy43ODhBMS44IDEuOCAwIDAgMCAyIDcuNTM1djEwLjg2M2MwIDEgLjgwMiAxLjgxMiAxLjc4OCAxLjgxMmgyLjI2NmwxLjM1IDEuNTlhLjUxNy41MTcgMCAwIDAgLjgxNiAwbDEuMzUtMS41OWg0Ljg2bDEuMzUgMS41OWEuNTE3LjUxNyAwIDAgMCAuODE2IDBsMS4zNS0xLjU5aDIuMjY2Yy45OSAwIDEuNzg4LS44MTEgMS43ODgtMS44MTJWNy41MzVjMC0xLS43OTktMS44MS0xLjc4Ny0xLjgxIi8+PC9zdmc+
// @grant GM.setValue
// @grant GM.getValue
// @license CC0 1.0
// ==/UserScript==
(function() {
'use strict';
let targetContainer;
let observer;
const SCRIPT_SRC_720p = "https://www.nicozon.net/js/bookmarklet_old_720p.js";
const SCRIPT_SRC_480p = "https://www.nicozon.net/js/bookmarklet_old_480p.js";
const SCRIPT_SRC_360p = "https://www.nicozon.net/js/bookmarklet_old_360p.js";
const SCRIPT_SRC_audio = "https://www.nicozon.net/js/bookmarklet_old_audio.js";
// Log script initialization
console.log('NicoVideo Downloader script initialized');
// Function to extract video ID from URL
function getVideoId() {
try {
const url = window.location.href;
const match = url.match(/sm(\d+)/);
const videoId = match ? match[0] : null;
if (!videoId) {
console.error('Could not extract video ID from URL:', url);
} else {
console.log('Extracted video ID:', videoId);
}
return videoId;
} catch (error) {
console.error('Error extracting video ID:', error);
return null;
}
}
// Function to create download links
function createDownloadLinks() {
try {
const videoId = getVideoId();
if (!videoId) {
console.error('Cannot create download links without video ID');
return null;
}
// Create container for links
const linksContainer = document.createElement('div');
linksContainer.className = 'download-links';
linksContainer.style.cssText = `
display: inline-block;
margin-left: 2px;
`;
const svgContainer = document.createElement('span');
svgContainer.style.cssText = `
display: inline-flex;
align-items: center;
justify-content: center;
`;
svgContainer.title = "Download";
svgContainer.innerHTML = `
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" class="w_font h_font icon icon-tabler icons-tabler-outline icon-tabler-download">
<path stroke="none" d="M-2.016-2.016h28.038v28.038H-2.016V-2.016Z" fill="none"/>
<path d="M2.657,17.844v2.336c0,1.29,1.046,2.336,2.336,2.336h14.019c1.29,0,2.336-1.046,2.336-2.336v-2.336"/>
<path d="M6.162,10.835l5.841,5.841,5.841-5.841"/>
<path d="M12.003,2.657v14.019"/>
</svg>
`;
linksContainer.appendChild(svgContainer);
const label = document.createElement('span');
label.textContent = ':';
label.style.marginLeft = '3px';
label.style.marginRight = '6px';
linksContainer.appendChild(label);
// Create download options
const options = [
{ text: '720p', title: 'Download video (720p)', quality: '720p', script: SCRIPT_SRC_720p },
{ text: '480p', title: 'Download video (480p)', quality: '480p', script: SCRIPT_SRC_480p },
{ text: '360p', title: 'Download video (360p)', quality: '360p', script: SCRIPT_SRC_360p },
{ text: '♫', title: 'Download audio', quality: 'audio', script: SCRIPT_SRC_audio }
];
options.forEach((option, index) => {
const link = document.createElement('a');
link.href = `https://ext.nicovideo.jp/?${videoId}`;
link.textContent = option.text;
link.title = option.title;
link.dataset.quality = option.quality;
link.dataset.script = option.script;
link.style.cssText = `
color: #ff9900;
text-decoration: none;
font-weight: bold;
cursor: pointer;
`;
link.addEventListener('mouseover', function() {
this.style.textDecoration = 'underline';
});
link.addEventListener('mouseout', function() {
this.style.textDecoration = 'none';
});
link.addEventListener('click', async function(e) {
e.preventDefault();
try {
// Store the selected quality using GM.setValue
await GM.setValue('nicovideoDownloadQuality', this.dataset.quality);
await GM.setValue('nicovideoDownloadScript', this.dataset.script);
console.log('Stored download preference:', this.dataset.quality);
window.open(this.href, '_blank');
} catch (error) {
console.error('Error storing download preference:', error);
alert('Error saving download preference. Please check console for details.');
}
});
linksContainer.appendChild(link);
// Add separator except for the last item
if (index < options.length - 1) {
const separator = document.createElement('span');
separator.textContent = '|';
separator.style.cssText = 'color: #ccc; margin: 0 4px;';
linksContainer.appendChild(separator);
}
});
console.log('Download links created successfully');
return linksContainer;
} catch (error) {
console.error('Error creating download links:', error);
return null;
}
}
function addDownloadLinks() {
try {
targetContainer = document.querySelector('.d_flex.gap_x2.text-layer_lowEm.fs_s.fw_bold.white-space_nowrap.ai_center');
if (targetContainer) {
// Check if links already exist to avoid duplicates
if (!targetContainer.querySelector('.download-links')) {
const linksContainer = createDownloadLinks();
if (linksContainer) {
targetContainer.appendChild(linksContainer);
console.log('Download links added to page');
// Stop observing once we've successfully added the links
if (observer) {
observer.disconnect();
console.log('MutationObserver disconnected');
}
}
} else {
console.log('Download links already exist, skipping');
}
} else {
console.log('Target container not found yet');
return false;
}
return true;
} catch (error) {
console.error('Error adding download links:', error);
return false;
}
}
// Function to handle different page types
async function handlePage() {
try {
console.log('Handling page:', window.location.hostname);
const videoId = getVideoId();
if (!videoId) {
console.error('No video ID found, stopping execution');
return;
}
if (window.location.hostname === 'www.nicovideo.jp') {
console.log('On main nicovideo page');
// Try immediately first
const success = addDownloadLinks();
// If not found, set up MutationObserver to watch for DOM changes
if (!success) {
console.log('Setting up MutationObserver to watch for target container');
observer = new MutationObserver(function(mutations) {
console.log('DOM mutation observed', mutations.length, 'changes');
mutations.forEach(function(mutation) {
if (mutation.addedNodes && mutation.addedNodes.length > 0) {
addDownloadLinks();
}
});
});
// Start observing the document body for added nodes
observer.observe(document.body, {
childList: true,
subtree: true
});
// Set a timeout as fallback
/*setTimeout(() => {
if (observer) {
observer.disconnect();
console.log('MutationObserver disconnected after timeout');
}
const finalAttempt = addDownloadLinks();
if (!finalAttempt) {
console.error('Failed to add download links after timeout');
}
}, 30000); // 30 seconds as fallback*/
}
}
else if (window.location.hostname === 'ext.nicovideo.jp') {
console.log('On ext.nicovideo page');
// On ext.nicovideo page, check if we have a stored quality preference
const quality = await GM.getValue('nicovideoDownloadQuality');
const scriptSrc = await GM.getValue('nicovideoDownloadScript');
console.log('Retrieved stored values - Quality:', quality, '; Script:', scriptSrc);
const p = document.createElement("p");
p.textContent = "Download: " + (quality || 'No quality selected');
document.body.appendChild(p);
// Clean up stored values
await GM.setValue('nicovideoDownloadQuality', '');
await GM.setValue('nicovideoDownloadScript', '');
console.log('Cleared stored values');
if (scriptSrc) {
console.log('Loading download script:', scriptSrc);
try {
const script = document.createElement('script');
script.setAttribute('charset', 'utf-8');
script.src = scriptSrc;
script.onload = function() {
console.log('Download script loaded successfully');
};
script.onerror = function() {
console.error('Failed to load download script:', scriptSrc);
alert('Failed to load download script. Please try again.');
};
document.body.appendChild(script);
} catch (error) {
console.error('Error loading download script:', error);
}
} else {
console.log('No script source found, creating download links instead');
const linksContainer = createDownloadLinks();
if (linksContainer) {
document.body.appendChild(linksContainer);
}
}
} else {
console.warn('Unsupported hostname:', window.location.hostname);
}
} catch (error) {
console.error('Error in handlePage function:', error);
}
}
// Run when DOM is ready
if (document.readyState === 'loading') {
console.log('DOM not ready, adding DOMContentLoaded listener');
document.addEventListener('DOMContentLoaded', function() {
console.log('DOMContentLoaded fired');
handlePage();
});
} else {
console.log('DOM already ready, executing immediately');
handlePage();
}
})();
// https://www.youtube.com/watch?v=sztJ8cO4mak
// https://www.nicozon.net/