您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
A script to deal with the automatic skipping of fixed length intros/outros for your favourite Youtube channels.
当前为
// ==UserScript== // @name s Youtube Automatic BS Skip Edited // @namespace https://gf.qytechs.cn/en/scripts/392459-youtube-automatic-bs-skip // @version 2.9.1.6 // @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 // @run-at document-start // ==/UserScript== console.log(`${GM.info.script.name} run`) //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.1_DaileAlimoMIT_edited'; 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"; // add indicators to the progress bar. const setupProgressBar = function(selector) { log('called setupProgressBar'); if($(`#${progressBar_ID}-intro`).remove()){log("removed intro bar");} if($(`#${progressBar_ID}-outro`).remove()){log("removed outro bar");} // add intro indicator to progress bar if (!document.getElementById(`${progressBar_ID}-intro`)){ 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`)) { 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`]; }; // 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 if($(`#${controlUI_ID}`).remove()){log("removed controls");} var controls = document.getElementById(controlUI_ID); if (controls == null) { log('adding controls to video'); controls = selector.prepend(` <button id="${controlUI_ID}" class="ytp-button loading" 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($(`#${modal_ID}`).remove()){log("removed modal");} 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>` ); $(`#${controlUI_ID}`).on('click', () => { log("toggle modal"); $(`#${modal_ID}`).toggleClass("show"); }); } return controls; }; const updateControls = ({introPlaceholderTxt, outroPlaceholderTxt, channelTxt, tooltipTxt, 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) if (tooltipTxt) $(`#${controlUI_ID}`).attr('title', tooltipTxt) if (actions) { (actions.outro)? $(`#${nextOnOutro}`).attr("checked", "checked") : $(`#${pauseOnOutro}`).attr("checked", "checked"); } //$(`#${modal_ID}`).removeClass("show"); }; const bindToVidStrm = function(vidstrm,loadedIntroSetInSeconds,loadedOutroSetInSeconds){ // 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 introskipdone let paused = false; // set duration here and call writeProgressBars vidstrm.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(currentTime < loadedIntroSetInSeconds && !introskipdone ) this.currentTime = loadedIntroSetInSeconds; if( loadedIntroSetInSeconds <= currentTime ) introskipdone = true // If current time greater or equal to outro, click next button or pause the vidstrm. if(currentTime >= duration - loadedOutroSetInSeconds && loadedOutroSetInSeconds > 0){ if (!playNextOnOutro) paused = true; $(".ytp-play-button")[0].click(); } }); }; const forkJoinJQExistCheck = function({selectors = [], aliases = [], func1 = {}, func2 = async _=>{} }) { var $sins=selectors.map(s=>$(s)) if($sins.filter(x=>x.length /*.length jquery existence check */ ).length===selectors.length){ var $souts = {}; $sins.map(($sin,i)=>{ var dictname = aliases[i] ? aliases[i]: i $souts[dictname] = func1[dictname]? func1[dictname]($sin) : $sin }) func2($souts); } }; ;(_=>{ var lasturl var cp=_=>{ if(lasturl!=document.URL && document.URL.includes("watch")){ $(".video-stream").unbind("timeupdate") forkJoinJQExistCheck({ selectors: [".video-stream", ".ytp-right-controls", ".ytp-progress-bar"], aliases: ["vidstrm", "container", "progressBar"], func1: { "container": setupControls, "progressBar": setupProgressBar }, func2: async function($souts) { lasturl = document.URL updateControls({ channelTxt: 'N/A', tooltipTxt: 'YABSS Loading', }) var thisurl=document.URL var channel = await fetch(thisurl).then(a=>a.text()).then(a=>a.match(/"author":"(.*?)"/)[1]); // var introTargetId = channel.split(" ").join("_") + "-intro"; var outroTargetId = channel.split(" ").join("_") + "-outro"; var loadedIntroSetInSeconds = await GM.getValue(introTargetId, 0); var loadedOutroSetInSeconds = await GM.getValue(outroTargetId, 0); var outroAction = channel.split(" ").join("_") + "-outro-action"; var playNextOnOutro = await GM.getValue(outroAction, true); // if(thisurl===document.URL) { // not switched to another video while fetching channel/author // a redundant intro skip for the beginning few seconds to tackle 'timeupdate' latency var se=$souts.vidstrm.get(0) var p=!se.paused if(p) se.pause() if(se.currentTime < loadedIntroSetInSeconds) se.currentTime = loadedIntroSetInSeconds; if(p) se.play() // bindToVidStrm($souts.vidstrm, loadedIntroSetInSeconds, loadedOutroSetInSeconds); updateControls({ introPlaceholderTxt: (loadedIntroSetInSeconds <= 0)? "unset": loadedIntroSetInSeconds, outroPlaceholderTxt: (loadedOutroSetInSeconds <= 0)? "unset": loadedOutroSetInSeconds, channelTxt: channel, tooltipTxt: channel, introTxt: loadedIntroSetInSeconds, outroTxt: loadedOutroSetInSeconds, actions: { outro: playNextOnOutro, }, }) $(`#${controlUI_ID}`).removeClass('loading'); log("loaded channel: " + channel); log("intro set: " + loadedIntroSetInSeconds); log("outro set: " + loadedOutroSetInSeconds); log(`outro action: ${(playNextOnOutro)? "skip to next video": "pause"}`); // log("bind to click"); // Control popup toggle button click listener $(`#${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 }); bindToVidStrm($souts.vidstrm, introSeconds, outroSeconds); }); // 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); }); } } }) } requestAnimationFrame(cp) } cp() })(); // 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}.loading { opacity:.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或关注我们的公众号极客氢云获取最新地址