Greasy Fork is available in English.
Simple Youtube Video Downloader
当前为
// ==UserScript==
// @name Youtube Video Downloader
// @namespace http://tampermonkey.net/
// @author fb
// @version 1.1
// @description Simple Youtube Video Downloader
// @match https://www.youtube.com/watch*
// @grant GM_xmlhttpRequest
// @connect p.oceansaver.in
// @license GPL-3.0-or-later
// @run-at document-end
// ==/UserScript==
(function() {
'use strict';
let animInterval = null;
let originalText = '';
const observer = new MutationObserver((mutations, obs) => {
const actionsInner = document.querySelector('#end');
if (!actionsInner) return;
obs.disconnect();
injectUI(actionsInner);
});
observer.observe(document.body, { childList: true, subtree: true });
function injectUI(container) {
if (document.getElementById('download-button')) return;
const wrapper = document.createElement('div');
wrapper.style.display = 'flex';
wrapper.style.alignItems = 'center';
wrapper.style.marginRight = "10px";
const select = document.createElement('select');
select.id = 'format';
select.className = 'doc';
select.style.marginRight = '8px';
select.innerHTML = `
<optgroup label="Audio">
<option value="mp3">MP3</option>
<option value="m4a">M4A</option>
<option value="webm">WEBM</option>
<option value="aac">AAC</option>
<option value="flac">FLAC</option>
<option value="opus">OPUS</option>
<option value="ogg">OGG</option>
<option value="wav">WAV</option>
</optgroup>
<optgroup label="Video">
<option value="360">MP4 (360p)</option>
<option value="480">MP4 (480p)</option>
<option value="720">MP4 (720p)</option>
<option selected value="1080">MP4 (1080p)</option>
<option value="1440">MP4 (1440p)</option>
<option value="4k">WEBM (4K)</option>
</optgroup>
`;
const style = document.createElement('style');
style.textContent = `
:root {
--btn-bg: #272727;
--btn-hover-bg: #3f3f3f;
--btn-color: #fff;
--btn-radius: 18px;
--btn-padding: 0 20px;
--btn-font: 500 14px/36px "Roboto","Arial",sans-serif;
--btn-cursor: pointer;
}
#download-button,
#format {
display: inline-flex;
align-items: center;
color: var(--btn-color);
background-color: var(--btn-bg);
border: none;
border-radius: var(--btn-radius);
padding: var(--btn-padding);
white-space: nowrap;
text-transform: none;
font: var(--btn-font);
cursor: var(--btn-cursor);
transition: background-color .2s ease;
}
#download-button:hover,
#format:hover {
background-color: var(--btn-hover-bg);
}
#download-button {
padding-left: 40px;
background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cg fill='none' stroke='white' stroke-width='0.8' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M12 4v12'/%3E%3Cpath d='M8 12l4 4 4-4'/%3E%3Cpath d='M4 18h16'/%3E%3C/g%3E%3C/svg%3E");
background-repeat: no-repeat;
background-position: 8px center;
background-size: 28px;
}
#format {
appearance: none;
-webkit-appearance: none;
-moz-appearance: none;
padding-right: 40px;
background-image:
url("data:image/svg+xml;charset=US-ASCII,%3Csvg%20width%3D'10'%20height%3D'7'%20xmlns%3D'http%3A//www.w3.org/2000/svg'%3E%3Cpath%20d%3D'M0%200l5%207%205-7z'%20fill%3D'%23fff'/%3E%3C/svg%3E");
background-repeat: no-repeat;
background-position: right 12px center;
}
#format::-ms-expand {
display: none;
}
`;
document.head.appendChild(style);
const btn = document.createElement('button');
btn.id = 'download-button';
btn.textContent = 'Download';
btn.addEventListener('click', startDownload);
wrapper.appendChild(select);
wrapper.appendChild(btn);
container.insertAdjacentElement('afterbegin', wrapper);
}
function startDownload() {
const btn = document.getElementById('download-button');
if (!btn) return;
const fmt = document.getElementById('format').value;
const videoUrl = encodeURIComponent(window.location.href);
const initUrl = `https://p.oceansaver.in/ajax/download.php?format=${fmt}&url=${videoUrl}`;
startButtonAnimation(btn);
GM_xmlhttpRequest({
method: 'GET',
url: initUrl,
responseType: 'json',
onload(res) {
const data = res.response;
if (!data || !data.success) {
stopButtonAnimation();
alert('❌ Failed to initialize download');
return;
}
pollProgress(data.progress_url);
},
onerror() {
stopButtonAnimation();
alert('❌ Network error while starting download');
}
});
}
function pollProgress(progressUrl) {
const intervalId = setInterval(() => {
GM_xmlhttpRequest({
method: 'GET',
url: progressUrl,
responseType: 'json',
onload(res) {
const p = res.response;
if (!p) {
clearInterval(intervalId);
stopButtonAnimation();
return;
}
if (p.success) {
clearInterval(intervalId);
triggerFileDownload(p.download_url);
stopButtonAnimation();
} else {
console.log(`Download progress: ${p.progress || 'unknown'}`);
}
},
onerror() {
console.error('Error polling download progress');
clearInterval(intervalId);
stopButtonAnimation();
}
});
}, 1500);
}
function triggerFileDownload(url) {
const a = document.createElement('a');
a.href = url;
a.download = '';
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
}
function startButtonAnimation(btn) {
originalText = btn.textContent;
let dots = 0;
animInterval = setInterval(() => {
dots = (dots + 1) % 4;
btn.textContent = 'Downloading' + '.'.repeat(dots);
}, 500);
}
function stopButtonAnimation() {
const btn = document.getElementById('download-button');
if (animInterval) {
clearInterval(animInterval);
animInterval = null;
}
if (btn) {
btn.disabled = false;
btn.textContent = originalText || 'Download';
}
}
})();