您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
A script to deal with the automatic skipping of fixed length intros/outros for your favourite Youtube channels.
当前为
// ==UserScript== // @name Youtube Automatic BS Skip Edited // @namespace https://gf.qytechs.cn/en/scripts/392459-youtube-automatic-bs-skip // @version 2.9.1DaileAlimoMIT_edit20230401 // @description A script to deal with the automatic skipping of fixed length intros/outros for your favourite Youtube channels. // @license MIT // @match https://www.youtube.com/* // @icon https://www.google.com/s2/favicons?sz=64&domain=youtube.com // @grant GM.setValue // @grant GM.getValue // @grant GM.addStyle // @require http://code.jquery.com/jquery-latest.js // ==/UserScript== //https://gf.qytechs.cn/scripts/392459-youtube-automatic-bs-skip/code/Youtube%20Automatic%20BS%20Skip.user.js const app = "YouTube Automatic BS Skip"; const version = '2.9.1DaileAlimoMIT_edit20230401'; const debug = false; const log = function(line){if (debug) console.log(line)} // Elements const controlUI_ID = "outro-controls"; const modal_ID = "modal"; const progressBar_ID = "progress-bar"; 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"; // Actions const pauseOnOutro = "pause-on-outro"; const nextOnOutro = "next-on-outro"; const apply_ID = "apply"; // ev - 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. const mutobs = function({selectors = [], aliases = [], mutators = {}, callback = (selectors = {}), error}) { 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); } }; // add indicators to the progress bar. const setupProgressBar = function(selector) { log('called setupProgressBar'); // add intro indicator to progress bar if (document.getElementById(`${progressBar_ID}-intro`) == null){ log('created intro indicator'); selector.prepend( $(`<div id="${progressBar_ID}-intro">`).addClass("ytp-load-progress").css({ "left": "0%", "transform": "scaleX(0)", }) ); } // add outro indicator to progress bar if (document.getElementById(`${progressBar_ID}-outro`) == null) { log('created outro indicator'); 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() { log("destroying progressbars"); if($(`#${progressBar_ID}-intro`).remove()){log("removed intro bar");} if($(`#${progressBar_ID}-outro`).remove()){log("removed outro bar");} }; // update the indecators on the progressbar. const updateProgressbars = 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 - (outroFraction * 100)}%`, "transform": `scaleX(${outroFraction})`, "background-color": "green", }); }; const setupControls = function(selector) { // Its easier to modify if we don't chain jquery.append($()) to build the html components var controls = document.getElementById(controlUI_ID); if (controls == null) { log('adding controls to video'); controls = selector.prepend(` <button id="${controlUI_ID}" class="ytp-button" title="YABSS" aria-label="YABSS"> <div class="ytp-autonav-toggle-button-container"> <svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 0 24 24" width="24"><path d="M0 0h24v24H0z" fill="none"/><path fill="white" d="M19 9l1.25-2.75L23 5l-2.75-1.25L19 1l-1.25 2.75L15 5l2.75 1.25L19 9zm-7.5.5L9 4 6.5 9.5 1 12l5.5 2.5L9 20l2.5-5.5L17 12l-5.5-2.5zM19 15l-1.25 2.75L15 19l2.75 1.25L19 23l1.25-2.75L23 19l-2.75-1.25L19 15z"/></svg> </div> </button> `); } if (document.getElementById(modal_ID) == null) { log('adding modal to DOM'); $('body').append(` <div id="${modal_ID}"> <div id="${modal_ID}-escape"></div> <div id="${modal_ID}-content"> <div id="${channelTxt_ID}">Loading Channel</div> <h2 id="${controlUI_ID}-title" class="d-flex justify-space-between">YouTube Automatic BS Skip ${version} <a href="https://www.buymeacoffee.com/JustDai" target="_blank"> <svg xmlns="http://www.w3.org/2000/svg" enable-background="new 0 0 24 24" height="24" viewBox="0 0 24 24" width="24"> <g><path d="M0,0h24v24H0V0z" fill="none"></path></g> <g fill="#ffffff"><path d="M18.5,3H6C4.9,3,4,3.9,4,5v5.71c0,3.83,2.95,7.18,6.78,7.29c3.96,0.12,7.22-3.06,7.22-7v-1h0.5c1.93,0,3.5-1.57,3.5-3.5 S20.43,3,18.5,3z M16,5v3H6V5H16z M18.5,8H18V5h0.5C19.33,5,20,5.67,20,6.5S19.33,8,18.5,8z M4,19h16v2H4V19z"></path></g> </svg> </a> </h2> <div id="${controlUI_ID}-control-wrapper"> <div class="w-100 d-flex justify-space-around align-center"> <label for="${introLen_ID}">Intro</label> <input type="number" min="0" id="${introLen_ID}" placeholder="unset" class="input"> </div> <div class="w-100 d-flex justify-space-around align-center"> <label for="${outroLen_ID}">Outro</label> <input type="number" min="0" id="${outroLen_ID}" placeholder="unset" class="input"> </div> <div class="pa"> <div> <label for="${controlUI_ID}-outro-action-group">Action on outro:</label> </div> <fieldset id="${controlUI_ID}-outro-action-group" class="d-flex"> <div> <label for="${pauseOnOutro}">Pause Video</label> <input type="radio" name="outro-action-group" id="${pauseOnOutro}"> </div> <div> <label for="${nextOnOutro}">Play Next Video</label> <input type="radio" name="outro-action-group" id="${nextOnOutro}" checked="checked"> </div> </fieldset> </div> <tp-yt-paper-button id="${apply_ID}" class="style-scope ytd-video-secondary-info-renderer d-flex justify-center align-center" style-target="host" role="button" elevation="3" aria-disabled="false">${apply_ID}</tp-yt-paper-button> </div> </div> </div>` ); } return controls; }; const destroyControls = function(){ log("destroying controls"); if($(`#${controlUI_ID}`).remove()){log("removed controls");} if($(`#${modal_ID}`).remove()){log("removed modal");} }; const updateControls = ({introPlaceholderTxt, outroPlaceholderTxt, channelTxt, introTxt, outroTxt, actions}) => { if (introPlaceholderTxt) { $(`#${introLen_ID}`).attr("placeholder", introPlaceholderTxt); } if (outroPlaceholderTxt) { $(`#${outroLen_ID}`).attr("placeholder", outroPlaceholderTxt); } if (introTxt) { $(`#${introTime_ID}`).text(introTxt); } if (outroTxt) { $(`#${outroTime_ID}`).text(outroTxt); } if (channelTxt) { $(`#${channelTxt_ID}`).text(channelTxt); $(`#${controlUI_ID}`).attr('title', channelTxt); } if (actions) { (actions.outro)? $(`#${nextOnOutro}`).attr("checked", "checked") : $(`#${pauseOnOutro}`).attr("checked", "checked"); } //$(`#${modal_ID}`).removeClass("show"); }; ;(_=>{ var selectors= [".video-stream", ".ytp-right-controls", ".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": function(selector) { let channel = selector.first().text(); log(`validating channel: ${channel}`); if (channel === "") { return false; } return channel; }, }, callback= async function(selectors) { // var channel = selectors.channel; var introTargetId = channel.split(" ").join("_") + "-intro"; var outroTargetId = channel.split(" ").join("_") + "-outro"; var outroAction = channel.split(" ").join("_") + "-outro-action"; log("loaded channel: " + channel); // var loadedIntroSetInSeconds = await GM.getValue(introTargetId, 0); var loadedOutroSetInSeconds = await GM.getValue(outroTargetId, 0); var playNextOnOutro = await GM.getValue(outroAction, true); // log("intro set: " + loadedIntroSetInSeconds); log("outro set: " + loadedOutroSetInSeconds); log(`outro action: ${(playNextOnOutro)? "skip to next video": "pause"}`); // updateControls({ introPlaceholderTxt: (loadedIntroSetInSeconds <= 0)? "unset": loadedIntroSetInSeconds, outroPlaceholderTxt: (loadedOutroSetInSeconds <= 0)? "unset": loadedOutroSetInSeconds, channelTxt: channel, introTxt: loadedIntroSetInSeconds, outroTxt: loadedOutroSetInSeconds, actions: { outro: playNextOnOutro, }, }); // const bindToStream = async 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; let paused = false; let loadedIntroSetInSeconds = await GM.getValue(introTargetId, 0); let loadedOutroSetInSeconds = await 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){ // use pause to prevent timeupdate after we have clicked pause button // there is a slight delay from when pause button is clicked, to when the timeupdates are stopped. if (paused) { return setTimeout(1000, () => {paused = false}); } // let currentTime = this.currentTime; let duration = this.duration; // if (duration && !progressBarDone) { progressBarDone = true; updateProgressbars(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 or pause the stream. if(currentTime >= duration - loadedOutroSetInSeconds && loadedOutroSetInSeconds > 0){ if (playNextOnOutro) { $(".ytp-next-button")[0].click(); } else { paused = true; $(".ytp-play-button")[0].click(); } } }); }; // handle apply outro in seconds // log("bind to click"); // Control popup toggle button click listener $(`#${controlUI_ID}`).on('click', () => { log("toggle modal"); $(`#${modal_ID}`).toggleClass("show"); }); $(`#${modal_ID}-escape`).on('click', () => { log("clicked outside of modal"); $(`#${modal_ID}`).removeClass("show"); }); // Apply button click listener $(`#${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(); }); // Pause on outro radio button change $(`#${pauseOnOutro}`).on("change", function(){ // pause on outro playNextOnOutro = false; GM.setValue(outroAction, playNextOnOutro); }); // Next on outro radio button change $(`#${nextOnOutro}`).on("change", function(){ // skip to next on outro playNextOnOutro = true; GM.setValue(outroAction, playNextOnOutro); }); // bindToStream(); }, error=function(e) { log(e); destroyControls(); destroyProgressBar(); } var l='init' var cp=t=>{ var d=(_=>new Date())()-t if(d>500){ if(l!=document.URL){ if(l!=='init'){ destroyProgressBar(); destroyControls(); } l = document.URL; } requestAnimationFrame(_=>cp((_=>new Date())())) }else{ requestAnimationFrame(_=>cp(t)) } if(l.includes("watch")) try { mutobs({ selectors: selectors, aliases: aliases, mutators: mutators, callback: callback, error: error, }); } catch (e) {log(e.message)} } requestAnimationFrame(_=>cp((_=>new Date())())) })(); // Write the CSS rules to the DOM GM.addStyle(` #${modal_ID}-escape { position: fixed; left: 0; top: 0; width: 100vw; height: 100vh; z-index: 1000; } #${modal_ID} { display: none; position: fixed; left: 0; top: 0; width: 100vw; height: 100vh; z-index: 999; background: rgba(0,0,0,.8); } #${modal_ID}.show { display: flex; } #${modal_ID}-content { margin: auto; width: 30%; height: auto; background-color: var(--yt-live-chat-action-panel-background-color); border-radius: 6px 6px 6px; border: 1px solid white; padding: 15px; color: white; z-index: 1001; } #${introLen_ID},#${outroLen_ID} { font-size: 1.2em; padding: .4em; border-radius: .5em; border: none; width: 80% } #${apply_ID} { position: relative; border: 1px solid white; transition: background-color .2s ease-in-out } #${apply_ID}:hover { background-color: rgba(255,255,255,0.3); } #${controlUI_ID} { height: 100%; padding: 0; margin: 0; bottom: 45%; position: relative; } #${controlUI_ID} svg { position: relative; top: 20%; left: 20%; } #${controlUI_ID}-panel { margin-right: 1em; vertical-align:top } #${controlUI_ID} > * { display: inline-block; max-height: 100%; } #${controlUI_ID}-title { padding: 2px; } #${controlUI_ID}-outro-action-group { padding: .5em; } #${controlUI_ID}-outro-action-group > div { display: block; margin: auto; text-align-last: justify; } #${controlUI_ID}-control-wrapper > * { padding-top: 1em; } #action-radios { display: none; } #action-radios .actions{ padding-left: 2px; text-align: left; background-color: black; color: white; } #${introLen_ID},#${outroLen_ID} { margin-right: 2px; } #${channelTxt_ID} { position: relative; top: -3.5em; margin-bottom: -1.5em; } .w-100 { width: 100% !important; } .input { padding: .2em; } .d-flex { display: flex; } .justify-center { justify-content: center; } .justify-space-around { justify-content: space-around; } .justify-space-between { justify-content: space-between; } .align-center { align-items: center; } .pa { padding: 1em; } `);
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址