Skip sponsors - using Sponsorblock API on m.youtube.com (for Kiwi browser and others)

Skip sponsors/SelfPromo, adds a highlight.

当前为 2024-09-08 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name Skip sponsors - using Sponsorblock API on m.youtube.com (for Kiwi browser and others)
  3. // @namespace http://your-namespace.com
  4. // @version 2.1
  5. // @description Skip sponsors/SelfPromo, adds a highlight.
  6. // @author Sp0kz
  7. // @match https://m.youtube.com/*
  8. // @icon https://www.google.com/s2/favicons?sz=64&domain=youtube.com
  9. // @grant none
  10. // @run-at document-end
  11. // ==/UserScript==
  12.  
  13. (function() {
  14. 'use strict';
  15. //sha256 library
  16. var sha256=function a(b){function c(a,b){return a>>>b|a<<32-b}for(var d,e,f=Math.pow,g=f(2,32),h="length",i="",j=[],k=8*b[h],l=a.h=a.h||[],m=a.k=a.k||[],n=m[h],o={},p=2;64>n;p++)if(!o[p]){for(d=0;313>d;d+=p)o[d]=p;l[n]=f(p,.5)*g|0,m[n++]=f(p,1/3)*g|0}for(b+="\x80";b[h]%64-56;)b+="\x00";for(d=0;d<b[h];d++){if(e=b.charCodeAt(d),e>>8)return;j[d>>2]|=e<<(3-d)%4*8}for(j[j[h]]=k/g|0,j[j[h]]=k,e=0;e<j[h];){var q=j.slice(e,e+=16),r=l;for(l=l.slice(0,8),d=0;64>d;d++){var s=q[d-15],t=q[d-2],u=l[0],v=l[4],w=l[7]+(c(v,6)^c(v,11)^c(v,25))+(v&l[5]^~v&l[6])+m[d]+(q[d]=16>d?q[d]:q[d-16]+(c(s,7)^c(s,18)^s>>>3)+q[d-7]+(c(t,17)^c(t,19)^t>>>10)|0),x=(c(u,2)^c(u,13)^c(u,22))+(u&l[1]^u&l[2]^l[1]&l[2]);l=[w+x|0].concat(l),l[4]=l[4]+w|0}for(d=0;8>d;d++)l[d]=l[d]+r[d]|0}for(d=0;8>d;d++)for(e=3;e+1;e--){var y=l[d]>>8*e&255;i+=(16>y?0:"")+y.toString(16)}return i}; /*https://geraintluff.github.io/sha256/sha256.min.js (public domain)*/
  17.  
  18. let index3=false;
  19. const info_elem = document.createElement('div');
  20. let seekInput;
  21. let gradientStops = [];
  22. let skipSegments = [];
  23. let redDiv = document.createElement('div');
  24. let highlightSpan = document.createElement('span');
  25. const skipButton = document.createElement('button');
  26. skipButton.style.width="50px";
  27. skipButton.style.fontSize="40px";
  28. skipButton.textContent = '>>';
  29.  
  30. skipButton.className=('skip-button-S');
  31. skipButton.style.zIndex='8888';
  32. skipButton.style.background='transparent';
  33. skipButton.style.display="none";
  34. skipButton.style.textShadow = `
  35. -1px -1px 0 #000,
  36. 1px -1px 0 #000,
  37. -1px 1px 0 #000,
  38. 1px 1px 0 #000`;
  39. function getYouTubeVideoID(url) {
  40. const match = url.match(/(?:v=|\/)([a-zA-Z0-9_-]{11})/);
  41. return match ? match[1] : null;
  42. }
  43. let previousUrl = window.location.href;
  44. ///////
  45. setInterval(() => {
  46. if ((window.location.href !== previousUrl) && !window.location.href.includes('#searching')){
  47. previousUrl = window.location.href;
  48. if(window.location.href.includes('watch?v=')){
  49. if(document.querySelector('div[role="slider"]')) document.querySelector('div[role="slider"]').style.background="none";
  50. gradientStops = [];
  51. skipSegments = [];
  52. index3=false;
  53. redDiv.style.display='none';
  54. highlightSpan.style.opacity='0';
  55. skipButton.style.display='none';
  56. }
  57. else {
  58. skipButton.style.display='none';
  59. }
  60. }
  61. /////////
  62. if(document.querySelector('div[role="slider"]') && window.location.href.includes('watch?v=') && !index3) {
  63. index3=true;
  64. if(document.querySelector('div[role="slider"]')) document.querySelector('div[role="slider"]').style.background="none";
  65. const videoID = getYouTubeVideoID(window.location.href);
  66. const hash = sha256(videoID).substr(0,4);
  67. const video_obj = document.querySelector("video");
  68. let url = `https://sponsor.ajay.app/api/skipSegments/${hash}?service=YouTube&categories=%5B%22sponsor%22,%22poi_highlight%22,%22selfpromo%22%5D`;
  69. (async () => {
  70. try {
  71. const response = await fetch(url);
  72. if (!response.ok) {
  73. throw new Error(`HTTP error! Sponsorblock server down! Status: ${response.status}`);
  74. }
  75. const data = await response.json();
  76. if (!Array.isArray(data)) {
  77. throw new Error("Unexpected response format");
  78. }
  79. for (const video of data) {
  80. if (video.videoID != videoID) continue;
  81. info_elem.innerText = `(${video.segments.length} segments)`;
  82. const cat_n = video.segments.map(e=>e.category).sort()
  83. .reduce((acc,e) => (acc[e]=(acc[e]||0)+1, acc), {});
  84. info_elem.title = Object.entries(cat_n).map(e=>e.join(': ')).join(', ');
  85. for (const segment of video.segments) {
  86. const [start, stop] = segment.segment;
  87. let startPosition = ((start ) / (video_obj.duration)) * 100;
  88. let stopPosition = ((stop) / (video_obj.duration)) * 100;
  89. if (segment.category === "sponsor" ){
  90. gradientStops.push(`transparent ${startPosition+ 0.5}%, green ${startPosition + 0.5}%, green ${stopPosition}%, transparent ${stopPosition}%, transparent ${stopPosition}%`);
  91. skipSegments.push({ start, stop });
  92. }
  93. else if (segment.category === "selfpromo" ){
  94. gradientStops.push(`transparent ${startPosition}%, yellow ${startPosition}%, yellow ${stopPosition}%, transparent ${stopPosition}%, transparent ${stopPosition}%`);
  95. skipSegments.push({ start, stop });
  96. }
  97. else if (segment.category === "poi_highlight"){
  98. stopPosition+=1;
  99. seekInput = document.querySelector('div[role="slider"]');
  100. redDiv.style.position = 'absolute';
  101. redDiv.style.left = `${startPosition}%`;
  102. redDiv.style.width = "4px";
  103. redDiv.style.height = '15px';
  104. redDiv.style.pointerEvents='none';
  105. redDiv.title="Highlight";
  106. redDiv.style.display='block';
  107. redDiv.style.zIndex="666";
  108. redDiv.style.backgroundColor = 'red';
  109. highlightSpan.textContent = 'Highlight';
  110. highlightSpan.style.position = 'absolute';
  111. highlightSpan.style.bottom = '32px';
  112. highlightSpan.style.left = `${startPosition}%`;
  113. highlightSpan.style.marginLeft="5px";
  114. highlightSpan.style.transform = 'translateX(-50%)';
  115. highlightSpan.style.backgroundColor = 'black';
  116. highlightSpan.style.zIndex="5555";
  117. highlightSpan.style.fontSize="13px";
  118. highlightSpan.style.color = 'white';
  119. highlightSpan.style.padding = '2px 5px';
  120. highlightSpan.style.display="none";
  121. highlightSpan.style.opacity='1';
  122. skipButton.style.display="block";
  123. skipButton.addEventListener('touchstart', () => {
  124. document.querySelector('video').pause();
  125. document.querySelector('video').currentTime = start;
  126. document.querySelector('video').play();
  127. });
  128. skipButton.addEventListener('click', () => {
  129. document.querySelector('video').pause();
  130. document.querySelector('video').currentTime = start;
  131. document.querySelector('video').play();
  132. });
  133. skipButton.addEventListener('dblclick', () => {
  134. document.querySelector('video').pause();
  135. document.querySelector('video').currentTime = start;
  136. document.querySelector('video').play();
  137. });
  138. }
  139. if (segment.category != "sponsor" && segment.category != "selfpromo") continue;
  140. }
  141. video_obj.removeEventListener("timeupdate", handletimeupdate);
  142. video_obj.addEventListener("timeupdate", handletimeupdate);
  143. function handletimeupdate() {
  144. for (const { start, stop } of skipSegments) {
  145. if (video_obj.currentTime >= start && video_obj.currentTime < stop - 1) {
  146. video_obj.currentTime = stop;
  147. }
  148. }
  149. }
  150. setTimeout(() => {
  151. const allGradientStops = gradientStops.join(', ');
  152. seekInput = document.querySelector('div[role="slider"]');
  153. if(seekInput){
  154. seekInput.style.backgroundSize = `100% 25%`;
  155. seekInput.style.backgroundPosition = `center center`;
  156. seekInput.style.backgroundRepeat = 'no-repeat';
  157. seekInput.style.backgroundImage=' ';
  158. seekInput.style.backgroundImage = `linear-gradient(to right, ${allGradientStops})`;
  159. document.querySelector('div[role="slider"')?.parentNode.appendChild(redDiv);
  160. document.querySelector('div[role="slider"]')?.parentNode.appendChild(highlightSpan);
  161. skipButton.title="Skip To Highlight";
  162. if (document.querySelector('.player-controls-top')) document.querySelector('.player-controls-top').insertBefore(skipButton,document.querySelector('.player-controls-top').firstChild);
  163. document.querySelector('div[role="slider"]')?.addEventListener('mousemove', function(event) {
  164. highlightSpan.style.display = "block";
  165.  
  166. });
  167.  
  168. document.querySelector('div[role="slider"]')?.parentNode.addEventListener('mouseout', function(event) {
  169. highlightSpan.style.display = "none";
  170. });
  171. }
  172. }, 600);
  173. }
  174. } catch (error) {
  175. console.error("Error fetching data:", error);
  176. }
  177. })();
  178. }
  179. }, 1000);
  180. })();