// ==UserScript== // @name Invidious save video progress // @namespace http://tampermonkey.net/ // @version 0.1 // @description Saves video progress on invidio.us and adds some more functonality // @author Noruf // @match https://invidio.us/* // @match https://invidious.snopyta.org/* // @grant none // @downloadURL none // ==/UserScript== (function() { 'use strict'; const timestamps = localStorage.timestamps ? JSON.parse(localStorage.timestamps) : {}; let search = window.location.search; const isVideoPage = window.location.pathname.includes('watch'); const videoId = isVideoPage? window.location.search.match(/v=(.*?)(&|$)/)[1]: ' '; var url = new URL(window.location.href); var time = url.searchParams.get("t"); if(!time&×tamps[videoId]){ search = replaceQueryParam('t', timestamps[videoId], search); window.location.replace(window.location.pathname + search); } onReadyEvent(addHistoryButton); onReadyEvent(changeLinks); if(isVideoPage) onReadyEvent(videoProgressMain); onReadyEvent(addCopyYoutubeLinkButton); function replaceQueryParam(param, newval, search) { const regex = new RegExp("([?;&])" + param + "[^&;]*[;&]?"); const query = search.replace(regex, "$1").replace(/&$/, ''); return (query.length > 2 ? query + "&" : "?") + (newval ? param + "=" + newval : ''); } function onReadyEvent(callback){ // in case the document is already rendered if (document.readyState!='loading') callback(); // modern browsers else if (document.addEventListener) document.addEventListener('DOMContentLoaded', callback); // IE <= 8 else {document.attachEvent('onreadystatechange', function(){ if (document.readyState=='complete') callback(); });} } function addCopyYoutubeLinkButton(){ if(!isVideoPage)return; const a = document.querySelector('a[href*="youtube"'); const p = document.createElement("a"); p.appendChild(document.createTextNode(" copy")); a.parentElement.append(p); p.onclick = () => { copyToClipboard(a.href); }; const altP = document.createElement("p"); const altLink = document.createElement("a"); altLink.appendChild(document.createTextNode("Alternate source")); altLink.href = `https://invidious.snopyta.org/watch?v=${videoId}`; altP.appendChild(altLink); const ul = p.parentElement.parentElement; ul.insertBefore(altP,ul.childNodes[2]); } function addHistoryButton(){ const userfield = document.querySelector('.user-field'); const newdiv = document.createElement('div'); newdiv.className = 'pure-u-1-4'; userfield.prepend(newdiv); const anchor = document.createElement('a'); anchor.href = '/feed/history'; anchor.className = 'pure-menu-heading'; newdiv.append(anchor); const i = document.createElement('i'); i.className = 'icon ion-md-time'; anchor.append(i); } function changeLinks(){ const thumbnails = document.querySelectorAll('div.thumbnail'); thumbnails.forEach(t =>{ const a = t.parentElement; const href = a.href; if(!href.includes("watch"))return; const id = href.match(/v=(.*?)(&|$)/)[1]; if(timestamps[id]){ a.href = `${href}&t=${timestamps[id]}s`; } if(isVideoPage)return; const YT = href.replace(window.location.host,'youtube.com'); const copy = document.createElement("a"); copy.appendChild(document.createTextNode("copy")); const open = document.createElement("a"); open.href = YT; open.appendChild(document.createTextNode("open")); const div = document.createElement('h5'); div.style['text-align'] = 'right'; div.style['margin-top'] = '-5%'; div.append('YT link: ',copy,' ',open); a.parentElement.append(div); copy.onclick = () => { copyToClipboard(YT); }; }); } function videoProgressMain (){ const player = document.querySelector('video'); player.onpause = () => {saveProgress(false)}; window.addEventListener('beforeunload', function (e) { saveProgress(false); e.returnValue = ''; // Chrome requires returnValue to be set. }); const saveToClipboard = document.createElement("BUTTON"); saveToClipboard.className = "pure-button"; saveToClipboard.appendChild(document.createTextNode("Save To Clipboard")) document.querySelector('#subscribe').parentElement.appendChild(saveToClipboard); const message = document.createElement("span"); document.querySelector('#Genre').parentElement.appendChild(message); saveToClipboard.onclick = () => { saveProgress(true); } function saveProgress(doCopy){ const time = Math.floor(document.querySelector('video').currentTime); if(isNaN(player.duration))return; if(doCopy){ copyToClipboard(getURL(time)); } timestamps[videoId] = time; if(time < 60 || player.duration - time < 60) { delete timestamps[videoId]; message.innerHTML = `Timestamp not saved!`; } else{ message.innerHTML = `Saved at ${convertSeconds(time)}`; } history.replaceState( {} , '', replaceQueryParam('t',time,window.location.pathname + window.location.search)); localStorage.timestamps = JSON.stringify(timestamps); } function getURL(seconds){ return `https://invidio.us/watch?v=${videoId}&t=${seconds}s`; } function convertSeconds(seconds){ return new Date(seconds * 1000).toISOString().substr(11, 8); } } function copyToClipboard(text) { const textArea = document.createElement("textarea"); textArea.value = text; textArea.style.position = 'fixed'; textArea.style.top = 0; textArea.style.left = 0; textArea.style.width = '2em'; textArea.style.height = '2em'; textArea.style.padding = 0; textArea.style.border = 'none'; textArea.style.outline = 'none'; textArea.style.boxShadow = 'none'; textArea.style.background = 'transparent'; document.body.appendChild(textArea); textArea.focus(); textArea.select(); try { const successful = document.execCommand('copy'); const msg = successful ? 'successful' : 'unsuccessful'; console.log('Copying text command was ' + msg); } catch (err) { console.error('Oops, unable to copy', err); } document.body.removeChild(textArea); } })();