YouTube 以播放時間長度排序播放清單

使用官方API以播放時間長度排序清單

  1. // ==UserScript==
  2.  
  3. // @name YouTube sort playlists by play time length
  4. // @name:zh-TW YouTube 以播放時間長度排序播放清單
  5. // @name:zh-CN YouTube 以播放时间长度排序播放清单
  6. // @name:ja YouTube でプレイリストを再生時間順に並べ替える
  7. // @description Sorting playlists by play time length use internal API .
  8. // @description:zh-TW 使用官方API以播放時間長度排序清單
  9. // @description:zh-CN 使用官方API以播放时间长度排序清单
  10. // @description:ja 再生時間の長さによるプレイリストの並べ替えには、内部 API を使用します。
  11. // @copyright 2023, HrJasn (https://gf.qytechs.cn/zh-TW/users/142344-jasn-hr)
  12. // @license MIT
  13. // @icon https://www.google.com/s2/favicons?domain=www.youtube.com
  14. // @homepageURL https://gf.qytechs.cn/zh-TW/users/142344-jasn-hr
  15. // @supportURL https://gf.qytechs.cn/zh-TW/users/142344-jasn-hr
  16. // @version 1.6
  17. // @namespace https://gf.qytechs.cn/zh-TW/users/142344-jasn-hr
  18. // @grant none
  19. // @match http*://www.youtube.com/*
  20. // @exclude http*://www.google.com/*
  21.  
  22. // ==/UserScript==
  23.  
  24. (() => {
  25. console.log("YouTube sort playlists by play time length is loading.");
  26. let setCookie = (name,value,days) => {
  27. let expires = "";
  28. if (days) {
  29. let date = new Date();
  30. date.setTime(date.getTime() + (days*24*60*60*1000));
  31. expires = "; expires=" + date.toUTCString();
  32. }
  33. document.cookie = name + "=" + (value || "") + expires + "; path=/";
  34. };
  35. let getCookie=(name) =>{
  36. let nameEQ = name + "=";
  37. let ca = document.cookie.split(';');
  38. for(let i=0;i < ca.length;i++) {
  39. let c = ca[i];
  40. while (c.charAt(0)==' ') c = c.substring(1,c.length);
  41. if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length,c.length);
  42. }
  43. return null;
  44. };
  45. let eraseCookie=(name) =>{
  46. document.cookie = name +'=; Path=/; Expires=Thu, 01 Jan 1970 00:00:01 GMT;';
  47. };
  48. if (typeof Array.prototype.equals === "undefined") {
  49. Array.prototype.equals = function( array ) {
  50. return this.length == array.length &&
  51. this.every( function(this_i,i) { return this_i == array[i] } )
  52. };
  53. };
  54. if (typeof Array.prototype.move === "undefined") {
  55. Array.prototype.move = function(from, to, on = 1) {
  56. return this.splice(to, 0, ...this.splice(from, on)), this
  57. };
  58. };
  59. let oldLH = '';
  60. let observerYSPBPTL;
  61. observerYSPBPTL = new MutationObserver( (mutations) => {
  62. let ypvlse = null;
  63. if( (oldLH !== window.location.href) && (ypvlse = document.querySelector('div#icon-label')) ){
  64. oldLH = window.location.href;
  65. let gck = JSON.parse(getCookie('CustomSortStatus'));
  66. let ypvlmtArr = {
  67. 'en':'Play time length',
  68. 'zh-TW':'播放時長',
  69. 'zh-CN':'播放时长',
  70. 'ja': 'プレイ時間'
  71. };
  72. let ypvlmt = ypvlmtArr[(navigator.userLanguage || navigator.language || navigator.browserLanguage || navigator.systemLanguage)] || ypvlmtArr.en;
  73. function searchObj(path, obj, target) {
  74. for (let k in obj) {
  75. if (obj.hasOwnProperty(k)){
  76. if(obj[k] === target){
  77. return path + "['" + k + "']";
  78. } else if (typeof obj[k] === 'object') {
  79. let result = searchObj(path + "['" + k + "']", obj[k], target);
  80. if (result){
  81. return result;
  82. };
  83. };
  84. };
  85. };
  86. return false;
  87. };
  88. function getObjPathParent(srcPath){
  89. let tgPath = null;
  90. if( (srcPath) && (srcPath.replace) ){tgPath = srcPath.replace(/\[[^\[\]]+\]$/,'')};
  91. return tgPath;
  92. };
  93. let getFndPath = getObjPathParent(getObjPathParent(getObjPathParent(searchObj("ytInitialData",ytInitialData,document.querySelector('ytd-playlist-video-list-renderer div#contents ytd-playlist-video-renderer a[href *= "/watch?v="]').href.match(/v=([^\=\&]+)&?/)[1]))));
  94. let ypvricarr = [];
  95. let MutationObserverTimerYSPBPTL3;
  96. let ypvlmevntfn = (evnt) => {
  97. evnt.preventDefault();
  98. evnt.stopPropagation();
  99. evnt.stopImmediatePropagation();
  100. console.log(evnt);
  101. getFndPath = getObjPathParent(getObjPathParent(getObjPathParent(searchObj("ytInitialData",ytInitialData,document.querySelector('ytd-playlist-video-list-renderer div#contents ytd-playlist-video-renderer a[href *= "/watch?v="]').href.match(/v=([^\=\&]+)&?/)[1]))));
  102. ypvricarr = [];
  103. try{
  104. ypvricarr = [...eval('(' + getFndPath + ')')];
  105. }catch(err){
  106. console.log(err);
  107. };
  108. if(ypvricarr.length != 0){
  109. let ypvrearr = [];
  110. let orgetih = evnt.target.innerHTML;
  111. if(orgetih == (' ' + ypvlmt + '↑')){
  112. ypvrearr = [...ypvricarr].sort((a,b)=>{
  113. return parseInt(b.playlistVideoRenderer.lengthSeconds) - parseInt(a.playlistVideoRenderer.lengthSeconds);
  114. });
  115. } else if( (orgetih == (' ' + ypvlmt + '↓')) || (orgetih == (' ' + ypvlmt + '↑↓')) ){
  116. ypvrearr = [...ypvricarr].sort((a,b)=>{
  117. return parseInt(a.playlistVideoRenderer.lengthSeconds) - parseInt(b.playlistVideoRenderer.lengthSeconds);
  118. });
  119. } else {
  120. ypvrearr = [...ypvricarr].sort((a,b)=>{
  121. return parseInt(a.playlistVideoRenderer.lengthSeconds) - parseInt(b.playlistVideoRenderer.lengthSeconds);
  122. });
  123. orgetih = (' ' + ypvlmt + '↓');
  124. evnt.target.innerHTML = orgetih;
  125. };
  126. function IsrtSrtSim(carr1,carr2) {
  127. let cnt = 0;
  128. let mxle = null;
  129. let arr1 = [...carr1];
  130. let arr2 = [...carr2];
  131. while (!arr1.equals(arr2)){
  132. mxle = arr2.reduce((a,b)=>{
  133. let al = Math.abs(arr2.indexOf(a) - arr1.indexOf(a));
  134. let bl = Math.abs(arr2.indexOf(b) - arr1.indexOf(b));
  135. return ( al > bl ? a : b );
  136. });
  137. if(mxle && (arr1.indexOf(mxle) !== arr2.indexOf(mxle))){
  138. arr1 = arr1.move(arr1.indexOf(mxle), arr2.indexOf(mxle));
  139. cnt++;
  140. };
  141. };
  142. return cnt;
  143. };
  144. let ttcnts = IsrtSrtSim(ypvricarr,ypvrearr);
  145. console.log(ttcnts);
  146. orgetih = (orgetih == (' ' + ypvlmt + '↑'))?(' ' + ypvlmt + '↓'):(' ' + ypvlmt + '↑');
  147. if(gck = JSON.parse(getCookie('CustomSortStatus'))){
  148. gck.BtnStr = orgetih;
  149. setCookie('CustomSortStatus',JSON.stringify(gck),null);
  150. } else {
  151. setCookie('CustomSortStatus',JSON.stringify({"BtnStr":orgetih}),null);
  152. }
  153. evnt.target.innerHTML = orgetih;
  154. console.log(ypvrearr);
  155. if(MutationObserverTimerYSPBPTL3){
  156. clearTimeout(MutationObserverTimerYSPBPTL3);
  157. };
  158. MutationObserverTimerYSPBPTL3 = setTimeout(() => {
  159. if(ypvrearr.length != 0){
  160. let ot = document.title, ftd = 0.5;
  161. async function getSApiSidHash(SAPISID, origin) {
  162. function sha1(str) {
  163. return window.crypto.subtle
  164. .digest("SHA-1", new TextEncoder().encode(str))
  165. .then((buf) => {
  166. return Array.prototype.map
  167. .call(new Uint8Array(buf), (x) => ("00" + x.toString(16)).slice(-2))
  168. .join("")
  169. });
  170. };
  171. const TIMESTAMP_MS = Date.now();
  172. const digest = await sha1(`${TIMESTAMP_MS} ${SAPISID} ${origin}`);
  173. return `${TIMESTAMP_MS}_${digest}`;
  174. };
  175. async function fetchYTMoveAPI(actions,playlistId){
  176. return fetch("https://www.youtube.com/youtubei/v1/browse/edit_playlist?key=" + ytcfg.data_.INNERTUBE_API_KEY + "&prettyPrint=false", {
  177. "headers": {
  178. "accept": "*/*",
  179. "authorization": "SAPISIDHASH " + await getSApiSidHash(document.cookie.split("SAPISID=")[1].split("; ")[0], window.origin),
  180. "content-type": "application/json"
  181. },
  182. "body": JSON.stringify({
  183. "context": {
  184. "client": {
  185. clientName: "WEB",
  186. clientVersion: ytcfg.data_.INNERTUBE_CLIENT_VERSION
  187. }
  188. },
  189. "actions": actions,
  190. "playlistId": playlistId
  191. }),
  192. "method": "POST"
  193. });
  194. };
  195. async function moveYTItem(evnt,ypvlmt,ypvricarr,ypvrearr,ttcnts,ttcntst,mxle,ytactsjson,startTime,endTime,tdavg){
  196. let nsttArr = {
  197. 'en' : ' ' + ypvlmt + ' ( Remain ' + ttcnts + ' steps . )',
  198. 'zh-TW' : ' ' + ypvlmt + ' ( 剩餘 ' + ttcnts + ' 步。 )',
  199. 'zh-CN' : ' ' + ypvlmt + ' ( 剩余 ' + ttcnts + ' 步。 )',
  200. 'ja' : ' ' + ypvlmt + ' ( 残る ' + ttcnts + ' ステップ。 )',
  201. };
  202. let nstt = nsttArr[(navigator.userLanguage || navigator.language || navigator.browserLanguage || navigator.systemLanguage)] || ypvlmtArr.en;
  203. evnt.target.innerHTML = nstt;
  204. document.title = ot + nstt;
  205. console.log('Fetching: ',mxle);
  206. console.log('Move ' + ypvricarr.indexOf(mxle) + ' to ' + ypvrearr.indexOf(mxle));
  207. try {
  208. await fetchYTMoveAPI(ytactsjson,oldLH.match(/\?list=([^=&\?]+)&?/)[1]);
  209. ypvricarr = ypvricarr.move(ypvricarr.indexOf(mxle), ypvrearr.indexOf(mxle));
  210. ttcnts--;
  211. } catch (err) {
  212. console.error(err.message);
  213. };
  214. endTime = new Date();
  215. let timeDiff = endTime - startTime;
  216. tdavg = (tdavg + (timeDiff/1000)) / 2;
  217. tdavg = Math.round(tdavg*10)/10;
  218. evnt.target.style.transition = 'all ' + tdavg + 's';
  219. evnt.target.style.boxShadow = 'inset -' + evnt.target.offsetWidth*(ttcnts/ttcntst) + 'px 0px rgba(255, 255, 255, 0.2)';
  220. startTime = endTime;
  221. return {"evnt" : evnt ,
  222. "ypvlmt" : ypvlmt ,
  223. "ypvricarr" : ypvricarr ,
  224. "ypvrearr" : ypvrearr ,
  225. "ttcnts" : ttcnts ,
  226. "ttcntst" : ttcntst ,
  227. "mxle" : mxle ,
  228. "ytactsjson" : ytactsjson ,
  229. "startTime" : startTime ,
  230. "endTime" : endTime ,
  231. "tdavg" : tdavg};
  232. };
  233. async function getPosts(){
  234. let reg = /\<meta name="description" content\=\"(.+?)\"/;
  235. let ttcntst = ttcnts;
  236. let startTime = new Date(), endTime, tdavg = ftd;
  237. if(ttcntst < ypvrearr.length){
  238. while (!ypvricarr.equals(ypvrearr)){
  239. let mxle = null;
  240. mxle = ypvrearr.reduce((a,b)=>{
  241. let al = Math.abs(ypvrearr.indexOf(a) - ypvricarr.indexOf(a));
  242. let bl = Math.abs(ypvrearr.indexOf(b) - ypvricarr.indexOf(b));
  243. return ( al > bl ? a : b );
  244. });
  245. if(mxle && (ypvricarr.indexOf(mxle) !== ypvrearr.indexOf(mxle))){
  246. let ytactsjson;
  247. if(ypvricarr.indexOf(mxle) < ypvrearr.indexOf(mxle)){
  248. ytactsjson = [{
  249. "action": "ACTION_MOVE_VIDEO_AFTER",
  250. "setVideoId": mxle.playlistVideoRenderer.setVideoId,
  251. "movedSetVideoIdPredecessor": ypvricarr[ypvrearr.indexOf(mxle)].playlistVideoRenderer.setVideoId
  252. }];
  253. } else if (ypvrearr.indexOf(mxle) === 0) {
  254. ytactsjson = [{
  255. "action": "ACTION_MOVE_VIDEO_AFTER",
  256. "setVideoId": mxle.playlistVideoRenderer.setVideoId
  257. }];
  258. } else {
  259. ytactsjson = [{
  260. "action": "ACTION_MOVE_VIDEO_AFTER",
  261. "setVideoId": mxle.playlistVideoRenderer.setVideoId,
  262. "movedSetVideoIdPredecessor": ypvricarr[ypvrearr.indexOf(mxle)-1].playlistVideoRenderer.setVideoId
  263. }];
  264. };
  265. let mytird = await moveYTItem(evnt,ypvlmt,ypvricarr,ypvrearr,ttcnts,ttcntst,mxle,ytactsjson,startTime,endTime,tdavg);
  266. evnt = mytird.evnt;
  267. ypvlmt = mytird.ypvlmt;
  268. ypvricarr = mytird.ypvricarr;
  269. ypvrearr = mytird.ypvrearr;
  270. ttcnts = mytird.ttcnts;
  271. ttcntst = mytird.ttcntst;
  272. mxle = mytird.mxle;
  273. ytactsjson = mytird.ytactsjson;
  274. startTime = mytird.startTime;
  275. endTime = mytird.endTime;
  276. tdavg = mytird.tdavg;
  277. ypvricarr = ypvricarr.move(ypvricarr.indexOf(mxle), ypvrearr.indexOf(mxle));
  278. };
  279. };
  280. } else {
  281. ttcnts = ypvrearr.length - 1;
  282. ttcntst = ttcnts;
  283. for(let ypvrei=0;ypvrei<ypvrearr.length;ypvrei++){
  284. let mxle = ypvrearr[ypvrearr.length - ypvrei - 1];
  285. let ytactsjson = [{
  286. "action": "ACTION_MOVE_VIDEO_AFTER",
  287. "setVideoId": mxle.playlistVideoRenderer.setVideoId
  288. }];
  289. let nsttArr = {
  290. 'en' : ' ' + ypvlmt + ' ( Remain ' + ttcnts + ' steps . )',
  291. 'zh-TW' : ' ' + ypvlmt + ' ( 剩餘 ' + ttcnts + ' 步。 )',
  292. 'zh-CN' : ' ' + ypvlmt + ' ( 剩余 ' + ttcnts + ' 步。 )',
  293. 'ja' : ' ' + ypvlmt + ' ( 残る ' + ttcnts + ' ステップ。 )',
  294. };let mytird = await moveYTItem(evnt,ypvlmt,ypvricarr,ypvrearr,ttcnts,ttcntst,mxle,ytactsjson,startTime,endTime,tdavg);
  295. evnt = mytird.evnt;
  296. ypvlmt = mytird.ypvlmt;
  297. ypvricarr = mytird.ypvricarr;
  298. ypvrearr = mytird.ypvrearr;
  299. ttcnts = mytird.ttcnts;
  300. ttcntst = mytird.ttcntst;
  301. mxle = mytird.mxle;
  302. ytactsjson = mytird.ytactsjson;
  303. startTime = mytird.startTime;
  304. endTime = mytird.endTime;
  305. tdavg = mytird.tdavg;
  306. ypvricarr = ypvricarr.move(ypvricarr.indexOf(mxle), ypvrearr.indexOf(mxle));
  307. };
  308. };
  309. };
  310. if(!ypvricarr.equals(ypvrearr)){
  311. evnt.target.style.transition = 'all ' + ftd + 's';
  312. getPosts().then(()=>{
  313. document.title = ot;
  314. evnt.target.innerHTML = orgetih;
  315. evnt.target.style = '';
  316. console.log('Done. ');
  317. window.location.href = window.location.href;
  318. });
  319. };
  320. };
  321. },1000);
  322. } else {
  323. if(gck = JSON.parse(getCookie('CustomSortStatus'))){
  324. gck.LastAct = 'Sorting';
  325. setCookie('CustomSortStatus',JSON.stringify(gck),null);
  326. } else {
  327. setCookie('CustomSortStatus',JSON.stringify({'LastAct':'Sorting'}),null);
  328. };
  329. window.location.href = window.location.href;
  330. };
  331. };
  332. let ypvlmes = [[...ypvlse.parentNode.children].find(cn => cn.innerText.match(ypvlmt))];
  333. console.log(ypvlmes);
  334. if( (ypvlmes) && (ypvlmes.length !== 0) ){
  335. ypvlmes.forEach((ypvlme)=>{
  336. if(ypvlme){
  337. ypvlme.removeEventListener('click',ypvlmevntfn);
  338. ypvlme.remove();
  339. };
  340. });
  341. };
  342. let yvlmen = '';
  343. if( !(gck = JSON.parse(getCookie('CustomSortStatus'))) ){
  344. yvlmen = ypvlmt + '↑↓';
  345. } else {
  346. yvlmen = gck.BtnStr;
  347. };
  348. let ypvlme = ypvlse.cloneNode(true);
  349. ypvlme.innerHTML = yvlmen;
  350. ypvlse.parentNode.insertBefore(ypvlme,ypvlse.nextSibling);
  351. ypvlme.addEventListener('click',ypvlmevntfn);
  352. if(gck = JSON.parse(getCookie('CustomSortStatus'))){
  353. if(gck.LastAct == 'Sorting'){
  354. gck.LastAct = 'Nothing';
  355. setCookie('CustomSortStatus',JSON.stringify(gck),null);
  356. //ypvlme.click();
  357. };
  358. } else {
  359. setCookie('CustomSortStatus',JSON.stringify({'LastAct':'Nothing'}),null);
  360. };
  361. try{
  362. ypvricarr = [...eval('(' + getFndPath + ')')];
  363. }catch(err){
  364. let nfrstrArr = {
  365. 'en':'click to fresh page one time.',
  366. 'zh-TW':'按下以重新整理一次',
  367. 'zh-CN':'按下以重新整理一次',
  368. 'ja': '押してページを 1 回更新します'
  369. };
  370. let nfrst = nfrstrArr[(navigator.userLanguage || navigator.language || navigator.browserLanguage || navigator.systemLanguage)] || nfrstrArr.en;
  371. ypvlme.innerHTML = (' ' + ypvlmt + ' ( ' + nfrst +' ) ');
  372. console.log(err);
  373. };
  374. console.log("YouTube sort playlists by play time length is loaded.");
  375. };
  376. });
  377. observerYSPBPTL.observe(document, {attributes:true, childList:true, subtree:true});
  378. })();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址