🏠 Home 

s Youtube Automatic BS Skip 2.9.1 fork May2023 bugfix

Fork of YouTube Automatic BS Skip 2.9.1 by JustDaile https://greasyfork.org/en/scripts/392459-youtube-automatic-bs-skip . Automatic skipping of fixed length intros/outros for your favourite Youtube channels.


Install this script?
  1. // ==UserScript==
  2. // @name s Youtube Automatic BS Skip 2.9.1 fork May2023 bugfix
  3. // @namespace https://greasyfork.org/en/scripts/392459-youtube-automatic-bs-skip
  4. // @version 2.9.1.13
  5. // @description Fork of YouTube Automatic BS Skip 2.9.1 by JustDaile https://greasyfork.org/en/scripts/392459-youtube-automatic-bs-skip . Automatic skipping of fixed length intros/outros for your favourite Youtube channels.
  6. // @license MIT
  7. // @match https://www.youtube.com/*
  8. // @icon https://www.google.com/s2/favicons?sz=64&domain=youtube.com
  9. // @grant GM_setValue
  10. // @grant GM_getValue
  11. // @grant GM_addStyle
  12. // @require http://code.jquery.com/jquery-latest.js
  13. // @run-at document-start
  14. // ==/UserScript==
  15. console.log(`${GM.info.script.name} run`)
  16. //https://greasyfork.org/scripts/392459-youtube-automatic-bs-skip/code/Youtube%20Automatic%20BS%20Skip.user.js
  17. const app = "YouTube Automatic BS Skip";
  18. const version = '2.9.1_DaileAlimoMIT_edited';
  19. const debug = false;
  20. const log = function(line){if (debug) console.log(line)}
  21. // Elements
  22. const controlUI_ID = "outro-controls";
  23. const modal_ID = "modal";
  24. const progressBar_ID = "progress-bar";
  25. const introLen_ID = "intro-length";
  26. const outroLen_ID = "outro-length";
  27. const channelTxt_ID = "channel_txt";
  28. // Actions
  29. const pauseOnOutro = "pause-on-outro";
  30. const nextOnOutro = "next-on-outro";
  31. const apply_ID = "apply";
  32. // add indicators to the progress bar.
  33. const setupProgressBar = function(selector) {
  34. log('called setupProgressBar');
  35. if($(`#${progressBar_ID}-intro`).remove()){log("removed intro bar");}
  36. if($(`#${progressBar_ID}-outro`).remove()){log("removed outro bar");}
  37. // add intro indicator to progress bar
  38. if (!document.getElementById(`${progressBar_ID}-intro`)){
  39. log('created intro indicator');
  40. selector.prepend(
  41. $(`<div id="${progressBar_ID}-intro">`).addClass("ytp-load-progress").css({
  42. "left": "0%",
  43. "transform": "scaleX(0)",
  44. })
  45. );
  46. }
  47. // add outro indicator to progress bar
  48. if (!document.getElementById(`${progressBar_ID}-outro`)) {
  49. log('created outro indicator');
  50. selector.prepend(
  51. $(`<div id="${progressBar_ID}-outro">`).addClass("ytp-load-progress").css({
  52. "left": "100%",
  53. "transform": "scaleX(0)",
  54. })
  55. );
  56. }
  57. return [`${progressBar_ID}-intro`, `${progressBar_ID}-outro`];
  58. };
  59. // update the indecators on the progressbar.
  60. const updateProgressbars = function(intro, outro, duration) {
  61. // update the intro progress bar
  62. let introBar = $(`#${progressBar_ID}-intro`);
  63. var introFraction = intro / duration;
  64. introBar.css({
  65. "left": "0%",
  66. "transform": `scaleX(${introFraction})`,
  67. "background-color": "green",
  68. });
  69. // update the outro progress bar
  70. let outroBar = $(`#${progressBar_ID}-outro`);
  71. var outroFraction = outro / duration;
  72. outroBar.css({
  73. "left": `${100 - (outroFraction * 100)}%`,
  74. "transform": `scaleX(${outroFraction})`,
  75. "background-color": "green",
  76. });
  77. };
  78. const setupControls = function(selector) {
  79. // Its easier to modify if we don't chain jquery.append($()) to build the html components
  80. if($(`#${controlUI_ID}`).remove()){log("removed controls");}
  81. var controls = document.getElementById(controlUI_ID);
  82. if (controls == null) {
  83. log('adding controls to video');
  84. controls = selector.prepend(`
  85. <button id="${controlUI_ID}" class="ytp-button loading" title="YABSS" aria-label="YABSS">
  86. <div class="ytp-autonav-toggle-button-container">
  87. <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>
  88. </div>
  89. </button>
  90. `);
  91. }
  92. if($(`#${modal_ID}`).remove()){log("removed modal");}
  93. if (document.getElementById(modal_ID) == null) {
  94. log('adding modal to DOM');
  95. $('body').append(`
  96. <div id="${modal_ID}">
  97. <div id="${modal_ID}-escape"></div>
  98. <div id="${modal_ID}-content">
  99. <div id="${channelTxt_ID}">Loading Channel</div>
  100. <h2 id="${controlUI_ID}-title" class="d-flex justify-space-between">YouTube Automatic BS Skip ${version}
  101. <a href="https://www.buymeacoffee.com/JustDai" target="_blank">
  102. <svg xmlns="http://www.w3.org/2000/svg" enable-background="new 0 0 24 24" height="24" viewBox="0 0 24 24" width="24">
  103. <g><path d="M0,0h24v24H0V0z" fill="none"></path></g>
  104. <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>
  105. </svg>
  106. </a>
  107. </h2>
  108. <div id="${controlUI_ID}-control-wrapper">
  109. <div class="w-100 d-flex justify-space-around align-center">
  110. <label for="${introLen_ID}">Intro</label>
  111. <input type="number" min="0" id="${introLen_ID}" placeholder="unset" class="input">
  112. </div>
  113. <div class="w-100 d-flex justify-space-around align-center">
  114. <label for="${outroLen_ID}">Outro</label>
  115. <input type="number" min="0" id="${outroLen_ID}" placeholder="unset" class="input">
  116. </div>
  117. <div class="pa">
  118. <div>
  119. <label for="${controlUI_ID}-outro-action-group">Action on outro <span id='${controlUI_ID}-outro-settings'>(Disabled. Assign outro time to enable)</span></label>
  120. </div>
  121. <fieldset id="${controlUI_ID}-outro-action-group" class="d-flex">
  122. <div>
  123. <label for="${pauseOnOutro}">Pause Video</label>
  124. <input type="radio" name="outro-action-group" id="${pauseOnOutro}">
  125. </div>
  126. <div>
  127. <label for="${nextOnOutro}">Play Next Video</label>
  128. <input type="radio" name="outro-action-group" id="${nextOnOutro}" checked="checked">
  129. </div>
  130. </fieldset>
  131. </div>
  132. <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>
  133. </div>
  134. </div>
  135. </div>`
  136. );
  137. var validate=x=>{
  138. var y=$(`#${x}`).val().toString()
  139. if(y && y != "" && parseInt(y) != NaN){
  140. if (y < 0) y = 0
  141. return Number(y)
  142. }
  143. return
  144. }
  145. $(`#${controlUI_ID}`).on('click', () => { $(`#${modal_ID}`).toggleClass("show"); });
  146. $(`#${modal_ID}-escape`).on('click', () => { $(`#${modal_ID}`).removeClass("show"); });
  147. $(`#${outroLen_ID}`).on('input', () => {
  148. var outroSeconds = validate(outroLen_ID)
  149. if(outroSeconds!=undefined) $(`#${controlUI_ID}-outro-settings`)[outroSeconds>0?'addClass':'removeClass']('hide')
  150. })
  151. $(`#${apply_ID}`).on("click", function() {
  152. var introSeconds = validate(introLen_ID)
  153. if(introSeconds!=undefined) updateapp({intro: introSeconds})
  154. var outroSeconds = validate(outroLen_ID)
  155. if(outroSeconds!=undefined) updateapp({outro: outroSeconds})
  156. updateapp({outact: $(`#${nextOnOutro}`).get(0).checked?true:false })
  157. $(`#${modal_ID}`).removeClass("show")
  158. });
  159. }
  160. return controls;
  161. };
  162. const updateControls = ({loadedIntroSetInSeconds, loadedOutroSetInSeconds, channelTxt, tooltipTxt, loading, playNextOnOutro}) => {
  163. if (channelTxt!=undefined) $(`#${channelTxt_ID}`).text(channelTxt)
  164. if (tooltipTxt!=undefined) $(`#${controlUI_ID}`).attr('title', tooltipTxt)
  165. if (loading!=undefined) $(`#${controlUI_ID}`)[loading?'addClass':'removeClass']('loading')
  166. if (loadedIntroSetInSeconds!=undefined) $(`#${introLen_ID}`).attr("placeholder", loadedIntroSetInSeconds <= 0? "unset": loadedIntroSetInSeconds)
  167. if (loadedOutroSetInSeconds!=undefined){
  168. $(`#${outroLen_ID}`).attr("placeholder", loadedOutroSetInSeconds <= 0? "unset": loadedOutroSetInSeconds)
  169. $(`#${controlUI_ID}-outro-settings`)[loadedOutroSetInSeconds>0?'addClass':'removeClass']('hide')
  170. }
  171. if (playNextOnOutro!=undefined){
  172. $(`#${nextOnOutro}`).get(0).checked= playNextOnOutro?"checked":false
  173. $(`#${pauseOnOutro}`).get(0).checked= playNextOnOutro?false:"checked"
  174. }
  175. };
  176. var introid=x=>x.split(" ").join("_") + "-intro"
  177. var outroid=x=>x.split(" ").join("_") + "-outro"
  178. var outactid=x=>x.split(" ").join("_") + "-outro-action"
  179. var curchannel
  180. const CHNLOAD='loading'
  181. var loadedIntroSetInSeconds
  182. var loadedOutroSetInSeconds
  183. var playNextOnOutro
  184. var updateapp=({channel,intro,outro,outact})=>{
  185. if(channel) curchannel=channel
  186. if(curchannel===CHNLOAD){
  187. updateControls({
  188. channelTxt: 'N/A',
  189. tooltipTxt: 'YABSS Loading',
  190. loading: true,
  191. })
  192. }else{
  193. if(intro!=undefined) GM_setValue(introid(curchannel), intro)
  194. if(outro!=undefined) GM_setValue(outroid(curchannel), outro)
  195. loadedIntroSetInSeconds = Number(GM_getValue(introid(curchannel), 0))
  196. loadedOutroSetInSeconds = Number(GM_getValue(outroid(curchannel), 0))
  197. if(outact!=undefined) GM_setValue(outactid(curchannel),outact)
  198. playNextOnOutro = GM_getValue(outactid(curchannel), false)
  199. updateControls({
  200. channelTxt: curchannel,
  201. tooltipTxt: `${curchannel}\nIntro: ${loadedIntroSetInSeconds}s\nOutro: ${loadedOutroSetInSeconds}s${loadedOutroSetInSeconds>0?playNextOnOutro?' (Play Next)':' (Pause)':''}`,
  202. loading: false,
  203. loadedIntroSetInSeconds: loadedIntroSetInSeconds,
  204. loadedOutroSetInSeconds: loadedOutroSetInSeconds,
  205. playNextOnOutro: playNextOnOutro
  206. })
  207. }
  208. }
  209. //
  210. var vs
  211. var curdur
  212. var vsloop=e=>{
  213. var v=e.target
  214. if (curdur != v.duration && !isNaN(v.duration) ) { //do these one time per duration change
  215. curdur = v.duration
  216. updateProgressbars(loadedIntroSetInSeconds, loadedOutroSetInSeconds, v.duration)
  217. if(v.currentTime < loadedIntroSetInSeconds) v.currentTime = loadedIntroSetInSeconds
  218. }
  219. if(loadedOutroSetInSeconds>0 && v.duration-loadedOutroSetInSeconds <= v.currentTime){
  220. v.pause()
  221. if(playNextOnOutro) $(".ytp-next-button")[0].click();
  222. }
  223. }
  224. var vsupdate=_=>{
  225. if(vs!=document.querySelector('.video-stream')){
  226. vs=document.querySelector('.video-stream')
  227. vs.addEventListener('timeupdate',vsloop)
  228. }
  229. setTimeout(vsupdate,1)
  230. }
  231. vsupdate()
  232. //
  233. const forkJoinJQExistCheck = function({selectors,func1,func2}) {
  234. var $sins=selectors.map(s=>$(s))
  235. if($sins.filter(x=>x.length /*.length jquery existence check */ ).length===selectors.length){
  236. $sins.map((s,i)=>func1[i](s))
  237. func2()
  238. }
  239. };
  240. //
  241. var lasturl
  242. var cp=_=>{
  243. if(lasturl!=document.URL){
  244. updateapp({channel:CHNLOAD})
  245. if(document.URL.includes("watch?")){
  246. forkJoinJQExistCheck({
  247. selectors: [ '.video-stream', ".ytp-right-controls", ".ytp-progress-bar"],
  248. func1:[
  249. _=>{},
  250. setupControls,
  251. setupProgressBar
  252. ],
  253. func2: async function() {
  254. lasturl=document.URL
  255. updateapp({channel:CHNLOAD})
  256. var thisurl=document.URL
  257. var channel = await fetch(thisurl).then(a=>a.text()).then(a=>a.match(/"author":"(.*?)"/)[1])
  258. if(thisurl===document.URL){ // not switched to another video while fetching channel/author
  259. curdur=0
  260. if(true){ // a redundant intro skip for the beginning few seconds to tackle 'timeupdate' latency
  261. var v=document.querySelector('.video-stream')
  262. var p=!v.paused
  263. v.pause()
  264. vsloop({target:v})
  265. if(p) v.play()
  266. }
  267. updateapp({channel:channel})
  268. }
  269. }
  270. })
  271. }
  272. }
  273. setTimeout(cp,100)
  274. }
  275. ;(_=>cp())();
  276. // Write the CSS rules to the DOM
  277. GM_addStyle(`
  278. #${modal_ID}-escape {
  279. position: fixed;
  280. left: 0;
  281. top: 0;
  282. width: 100vw;
  283. height: 100vh;
  284. z-index: 1000;
  285. }
  286. #${modal_ID} {
  287. display: none;
  288. position: fixed;
  289. left: 0;
  290. top: 0;
  291. width: 100vw;
  292. height: 100vh;
  293. z-index: 999;
  294. background: rgba(0,0,0,.8);
  295. }
  296. #${modal_ID}.show {
  297. display: flex;
  298. }
  299. #${modal_ID}-content {
  300. margin: auto;
  301. width: 30%;
  302. height: auto;
  303. background-color: var(--yt-live-chat-action-panel-background-color);
  304. border-radius: 6px 6px 6px;
  305. border: 1px solid white;
  306. padding: 15px;
  307. color: white;
  308. z-index: 1001;
  309. }
  310. #${introLen_ID},#${outroLen_ID} {
  311. font-size: 1.2em;
  312. padding: .4em;
  313. border-radius: .5em;
  314. border: none;
  315. width: 80%
  316. }
  317. #${apply_ID} {
  318. position: relative;
  319. border: 1px solid white;
  320. transition: background-color .2s ease-in-out
  321. }
  322. #${apply_ID}:hover {
  323. background-color: rgba(255,255,255,0.3);
  324. }
  325. #${controlUI_ID}.loading {
  326. opacity:.3
  327. }
  328. #${controlUI_ID} {
  329. height: 100%;
  330. padding: 0;
  331. margin: 0;
  332. bottom: 45%;
  333. position: relative;
  334. }
  335. #${controlUI_ID} svg {
  336. position: relative;
  337. top: 20%;
  338. left: 20%;
  339. }
  340. #${controlUI_ID}-panel {
  341. margin-right: 1em;
  342. vertical-align:top
  343. }
  344. #${controlUI_ID} > * {
  345. display: inline-block;
  346. max-height: 100%;
  347. }
  348. #${controlUI_ID}-title {
  349. padding: 2px;
  350. }
  351. #${controlUI_ID}-outro-action-group {
  352. padding: .5em;
  353. }
  354. #${controlUI_ID}-outro-action-group > div {
  355. display: block;
  356. margin: auto;
  357. text-align-last: justify;
  358. }
  359. #${controlUI_ID}-control-wrapper > * {
  360. padding: 1em;
  361. }
  362. #action-radios {
  363. display: none;
  364. }
  365. #action-radios .actions{
  366. padding-left: 2px;
  367. text-align: left;
  368. background-color: black;
  369. color: white;
  370. }
  371. #${introLen_ID},#${outroLen_ID} {
  372. margin-right: 2px;
  373. }
  374. #${channelTxt_ID} {
  375. position: relative;
  376. top: -3.5em;
  377. margin-bottom: -1.5em;
  378. }
  379. .w-100 {
  380. width: 100% !important;
  381. }
  382. .input {
  383. padding: .2em;
  384. }
  385. .d-flex {
  386. display: flex;
  387. }
  388. .justify-center {
  389. justify-content: center;
  390. }
  391. .justify-space-around {
  392. justify-content: space-around;
  393. }
  394. .justify-space-between {
  395. justify-content: space-between;
  396. }
  397. .align-center {
  398. align-items: center;
  399. }
  400. #${controlUI_ID}-outro-settings.hide{
  401. display:none
  402. }
  403. `);