您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Set outro for any youtube channel and will automatically skip to next video when time is reached.
当前为
// ==UserScript== // @name Youtube outro skip // @namespace http://tampermonkey.net/ // @version 2.0 // @description Set outro for any youtube channel and will automatically skip to next video when time is reached. // @author Daile Alimo // @match https://www.youtube.com/* // @grant GM_setValue // @grant GM_getValue // @require http://code.jquery.com/jquery-latest.js // ==/UserScript== /* globals $ whenReady */ const version = 2.0; const debug = true; const log = function(line) { if (debug) { console.log(line); } } // whenReady - Keep checking the DOM until these given selectors are found. // Invokes a callback function on complete that contains an object containing the JQuery element(s) for the given selectors accessable with aliases if given. // // selectors[] - Each selector to await. // aliases[] - An alias for each selector/mutator. // mutators{} - Associative array/object that given alias as key and function as value and selector as arguments returns a calculated result in place of its selector. // callback - Function that is called when all selectors, containing each selector or its mutators returned value if applicable. // error - Function that is called when an error such as retries exceeded occurs. // maxRetries - The total number of times the whenReady will recur before calling the error function. const whenReady = function({selectors = [], aliases = [], mutators = {}, callback = (selectors = {}), error, maxRetries = 5}) { let ready = {}; let found = 0; for(let i in selectors){ let $sel = $(selectors[i]); if ($sel.length) { let index = aliases[i] ? aliases[i]: i; if (mutators[index]) { ready[index] = mutators[index]($sel); if (ready[index]){ found++; } } else { ready[index] = $sel; found++; } } } if (found === selectors.length) { return callback(ready); } setTimeout(function(){ if (maxRetries >= 1) { return whenReady({ selectors: selectors, aliases: aliases, mutators: mutators, callback: callback, maxRetries: --maxRetries }); } if (error !== undefined) { error("max retries exceeded"); } }, 500); }; // let initializationRequired = false; const destroy = function(afterDetroyed){ // // make sure we have no events binded, in the case fn() was called by interval on URL change // this will ensure that we can create clean controls for the current playlist without accidentally // having events persisting in the background. // log("destroying.."); if ($(".video-stream").unbind()) {log("unbinding .video-stream");} if ($("#set-outro").unbind()){log("unbinding #set-outro");} if ($("#outro-controls").remove()){log("removed controls");} if ($("#outro-bar").remove()){log("removed progressbar");} initializationRequired = false; afterDetroyed(); } // validateChannel - ensure we get a channel name out of the channel name element const validateChannel = function(selector) { let channel = selector.first().text(); log("validating channel: " + channel); if (channel === "") { return false; } return channel; } // const progressBar_ID = "progress-bar"; // add indicators to the progress bar. const setupProgressBar = function(selector) { destroyProgressBar(); // add intro indicator to progress bar selector.prepend( $("<div id='" + progressBar_ID + "-intro'>").addClass("ytp-load-progress").css({ "left": "0%", "transform": "scaleX(0)", }) ); // add outro indicator to progress bar selector.prepend( $("<div id='" + progressBar_ID + "-outro'>").addClass("ytp-load-progress").css({ "left": "100%", "transform": "scaleX(0)", }) ); return [progressBar_ID + "-intro", progressBar_ID + "-outro"]; } // destroy the indicators added to the progressbar. const destroyProgressBar = function() { if($("#" + progressBar_ID + "-intro").remove()){log("removed intro bar");} if($("#" + progressBar_ID + "-outro").remove()){log("removed outro bar");} } // create the indecators on the progressbar. const createProgressBars = function(intro, outro, duration) { // update the intro progress bar let introBar = $("#" + progressBar_ID + "-intro"); var introFraction = intro / duration; introBar.css({ "left": "0%", "transform": "scaleX(" + introFraction + ")", "background-color": "green", }); // update the outro progress bar let outroBar = $("#" + progressBar_ID + "-outro"); var outroFraction = outro / duration; outroBar.css({ "left": (100 - (outroBar * 100)) + "%", "transform": "scaleX(" + outroFraction + ")", "background-color": "green", }); } // const controlUI_ID = "outro-controls"; const introTime_ID = "intro-set"; const outroTime_ID = "outro-set"; const introLen_ID = "intro-length"; const outroLen_ID = "outro-length"; const channelTxt_ID = "channel_txt"; const apply_ID = "apply"; const setupControls = function(selector) { destroyControls(); return selector.prepend( $("<div id='" + controlUI_ID + "'>").append( $("<h3>Youtube Skip Outro Controller " + version + " - <a href='https://www.buymeacoffee.com/JustDai' target='_blank'>Support me <3</a></h3>").css({ "padding": "2px", }) ).append( $("<input type='number' min='0' id='" + introLen_ID + "' placeholder='loading channel'/>") ).append( $("<input type='number' min='0' id='" + outroLen_ID + "' placeholder='loading channel'/>") ).append( $("<button id='" + apply_ID + "'>apply</button>") ).append( $("<div><span id='channel_txt'>loading</span> intro set: <span id='" + introTime_ID + "'>0</span> seconds outro set: <span id='" + outroTime_ID + "'>0</span> seconds</div>").css({ "padding": "2px", }) ).css({ "margin": "2px", "textAlign": "right", "color": "#666666", }) ); } const destroyControls = function(){ if($("#" + controlUI_ID).remove()){log("removed controls");} } const updateControls = ({introPlaceholderTxt, outroPlaceholderTxt, channelTxt, introTxt, outroTxt}) => { if (introPlaceholderTxt) { $("#" + introLen_ID).attr("placeholder", introPlaceholderTxt); } if (outroPlaceholderTxt) { $("#" + outroLen_ID).attr("placeholder", outroPlaceholderTxt); } if (channelTxt) { $("#" + channelTxt_ID).text(channelTxt); } if (introTxt) { $("#" + introTime_ID).text(introTxt); } if (outroTxt) { $("#" + outroTime_ID).text(outroTxt); } } (function(setupAndBind) { "use strict"; // // detect page change hashchange not working // so check every 3 seconds if current URL matches URL we started with. // handle appropriately. // var l = document.URL; if (l.includes("watch")) { setupAndBind(); } setInterval(function() { // check initializationRequired flag and if set, destroy and reinitialize. if (initializationRequired) { log("forced to destroy"); destroy(function() { log("rebuilding.."); setupAndBind(); }); } if (l != document.URL){ l = document.URL; if (l === "https://www.youtube.com/") { // ignore home destroy(function() { log("complete destruction"); }); } else if (l.includes("watch")) { log("channel changed"); initializationRequired = true } } }, 3000); })(function() { // ignore home if (document.URL === "https://www.youtube.com/"){ log("ignoring home"); return; } // whenReady({ // .ytp-progress-list selectors: [".video-stream", "#primary > #primary-inner", ".ytp-progress-bar", "#meta-contents #text.ytd-channel-name,.ytp-ce-channel-title > a"], aliases: ["stream", "container", "progressBar", "channel"], mutators: { "container": setupControls, "progressBar": setupProgressBar, "channel": validateChannel, }, callback: function(selectors) { // let channel = selectors.channel; let introTargetId = channel.split(" ").join("_") + "-intro"; let outroTargetId = channel.split(" ").join("_") + "-outro"; log("loaded channel: " + channel); // var loadedIntroSetInSeconds = GM_getValue(introTargetId) || 0; var loadedOutroSetInSeconds = GM_getValue(outroTargetId) || 0; log("intro set: " + loadedIntroSetInSeconds); log("outro set: " + loadedOutroSetInSeconds); // updateControls({ introPlaceholderTxt: (loadedIntroSetInSeconds <= 0)? "Set intro here..": loadedIntroSetInSeconds, outroPlaceholderTxt: (loadedOutroSetInSeconds <= 0)? "Set outro here..": loadedOutroSetInSeconds, channelTxt: channel, introTxt: loadedIntroSetInSeconds, outroTxt: loadedOutroSetInSeconds, }); // // const bindToStream = function(){ // hook video timeupdate, wait for outro and hit next button when time reached // if update time less than intro, skip to intro time log("binding events"); let progressBarDone = false; var loadedIntroSetInSeconds = GM_getValue(introTargetId) || 0; var loadedOutroSetInSeconds = GM_getValue(outroTargetId) || 0; log("intro set: " + loadedIntroSetInSeconds); log("outro set: " + loadedOutroSetInSeconds); // // set duration here and call writeProgressBars selectors.stream.unbind("timeupdate").on("timeupdate", function(e){ var currentTime = this.currentTime; var duration = this.duration; // if (duration && !progressBarDone) { progressBarDone = true; createProgressBars(loadedIntroSetInSeconds, loadedOutroSetInSeconds, duration); } // If current time less than intro, skip past intro. if(currentTime < loadedIntroSetInSeconds) { this.currentTime = loadedIntroSetInSeconds; } // If current time greater or equal to outro, click next button. if(currentTime >= duration - loadedOutroSetInSeconds){ $(".ytp-next-button")[0].click(); } }); }; // // handle apply outro in seconds // log("bind to click"); $("#" + apply_ID).on("click", function(e) { log("updating intro/outro skip"); var introSeconds = $("#" + introLen_ID).val().toString(); var outroSeconds = $("#" + outroLen_ID).val().toString(); if(introSeconds && introSeconds != "" && parseInt(introSeconds) != NaN){ if (introSeconds < 0) { introSeconds = 0; } // save outro in local storage GM_setValue(introTargetId, introSeconds); } if(outroSeconds && outroSeconds != "" && parseInt(outroSeconds) != NaN){ if (outroSeconds < 0) { outroSeconds = 0; } // save outro in local storage GM_setValue(outroTargetId, outroSeconds); } // update the intro/outro time on the controls updateControls({ introTxt: introSeconds, outroTxt: outroSeconds }); bindToStream(); }); bindToStream(); }, error: function(e) { log(e); destroyControls(); destroyProgressBar(); }, }); });