// ==UserScript== // @name YouTube検索結果「全てキューに入れて再生」ボタンを追加 // @description musictonicの代わり 右クリックだとシャッフル再生 e:カーソル下の動画をキューに入れる y:再生開始 Alt+c/Ctrl+x:視聴中のキューリストをURLにしてコピー // @version 0.1.36 // @run-at document-idle // @match *://www.youtube.com/* // @match *://www.youtube.com/ // @require https://code.jquery.com/jquery-3.6.4.min.js // @require https://code.jquery.com/ui/1.13.2/jquery-ui.min.js // @grant GM.setClipboard // @grant GM.openInTab // @namespace https://greasyfork.org/users/181558 // @downloadURL none // ==/UserScript== (function() { const USE_INSTANT_PLAYLIST = 0; // 0:機能6-8を無効にする 1:有効にし使用時に確認を表示する 2:有効にし確認しない const YOUTUBE_WATCH_ALTC_VARIATIONS = 2; // Alt+cの機能を何番目まで使うか 1:連続再生URL 2:単独再生URLの列挙 3:iframe埋め込み用HTML const CLOSE_MINI_PLAYER_ALWAYS = 1; // 1:Escでミニプレイヤーを常に閉じる const AGREE_TO_CONTINUE_ALWAYS = 1; // 1:無操作一時停止を常に解除 const HIDE_SUGGEST = 1000; // 1-:検索結果に割り込む「あなたへのおすすめ」「他の人はこちらも視聴しています」「家にいながら学ぶ」を隠す const PRESERVE_INDEX = 0; // 1:機能6-8で最初に再生するトラックを保持 const INCLUDE_REEL_SHORTS = 1; // 1:機能6-8でリール棚のShorts動画を含める var SHORTS = INCLUDE_REEL_SHORTS ? ",ytd-reel-item-renderer.style-scope.yt-horizontal-list-renderer a" : ""; const DEBUG = 0; // 1:wait値を表示 const EXPERIMENTAL_FASTMODE = 1; // 1:実験的な高速モードを使用 0:旧モード const COE = 1; // chrome以外のウエイト係数 取りこぼす時は大きく const COE_CHROME = 1; // chromeのウエイト係数 取りこぼす時は大きく const CHROME = (window.navigator.userAgent.toLowerCase().indexOf('chrome') != -1); const WAIT_FIRST = CHROME ? 700 : 200; // 取りこぼす時は大きく const WAIT_MIN = CHROME ? 190 : 160; // 取りこぼす時は大きく 50- const WAIT_MAX = 300; // 取りこぼす時は大きく 250- const waitLast = performance.now() * 1; // 現在の負荷 const wait = EXPERIMENTAL_FASTMODE ? (CHROME ? 40 : 40) : Math.round((Math.min(WAIT_MAX, Math.max(WAIT_MIN, waitLast / 10))) * (CHROME ? COE_CHROME : COE)); String.prototype.match0 = function(re) { let tmp = this.match(re); if (!tmp) { return null } else if (tmp.length > 1) { return tmp[1] } else return tmp[0] } // gフラグ不可 function adja(place = document.body, pos, html) { return place ? (place.insertAdjacentHTML(pos, html), place) : null; } let inYOUTUBE = location.hostname.match0(/^www\.youtube\.com|^youtu\.be/); let GF = {} var videoDisplayedLast = 0; var lastLength = 0; var mllID = 0; var kaisuu = 0; var equeueIP = [] var equeue = [] var playAllCount, playAllCount2; var myqueue = []; //URLの変化を監視 var href = location.href; var observer = new MutationObserver(function(mutations) { if (href !== location.href) { href = location.href; $('#playAllButton,#instantPlaylistButton').remove(); setTimeout(() => { lastLength = 0; run() }, 1500); } }); observer.observe(document, { childList: true, subtree: true }); setTimeout(() => { run(); }, 1009); setInterval(() => { hideSuggest() }, 1511); if (AGREE_TO_CONTINUE_ALWAYS) { setInterval(() => { if (!lh(/youtube\.com\/watch\?v=/)) return; if (eleget0('YTD-APP YTD-POPUP-CONTAINER TP-YT-PAPER-DIALOG YT-CONFIRM-DIALOG-RENDERER DIV TP-YT-PAPER-DIALOG-SCROLLABLE DIV YT-FORMATTED-STRING:visible:text*=動画が一時停止されました。続きを視聴しますか|Video paused. Continue watching')) eleget0('//ytd-app/ytd-popup-container/tp-yt-paper-dialog[@style-target="host"]/yt-confirm-dialog-renderer/div[last()]/div[contains(@class,"buttons style-scope yt-confirm-dialog-renderer")]/yt-button-renderer[3]/yt-button-shape/button[@aria-label="Yes" or @aria-label="はい"]/yt-touch-feedback-shape/div[contains(@class,"yt-spec-touch-feedback-shape yt-spec-touch-feedback-shape--touch-response")]/div[last()]:visible')?.click() }, 3001) } var mousex = 0; var mousey = 0; document.addEventListener("mousemove", function(e) { mousex = e.clientX; mousey = e.clientY; }, false); document.addEventListener('keydown', e => { if (e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA' || e.target.isContentEditable || ((e.target.closest('#chat-messages,ytd-comments-header-renderer') || document.activeElement?.closest('#chat-messages,ytd-comments-header-renderer')))) return; var key = (e.shiftKey ? "Shift+" : "") + (e.altKey ? "Alt+" : "") + (e.ctrlKey ? "Ctrl+" : "") + e.key; if (key === "Escape" && CLOSE_MINI_PLAYER_ALWAYS) { // esc::ミニプレイヤーを常に閉じる for (let i = 0; i < 20; i++) { setTimeout(() => { elegeta('tp-yt-paper-dialog .yt-core-attributed-string.yt-core-attributed-string--white-space-no-wrap:visible').filter(e => /プレーヤーを閉じる/.test(e.textContent)).forEach(e => e?.click()) }, i * 200) equeue = [] } } if (key === "e") { // e::enqueue e.preventDefault(); var ele = document.elementFromPoint(mousex, mousey); // var box = ele.closest('ytd-video-renderer,ytd-rich-item-renderer,ytd-grid-video-renderer,ytd-playlist-video-renderer'); //マウスが乗っている動画の枠 var box = ele.closest(`ytd-video-renderer,ytd-rich-item-renderer,ytd-grid-video-renderer,ytd-playlist-video-renderer,ytd-reel-item-renderer.style-scope.yt-horizontal-list-renderer`); //マウスが乗っている動画の枠 if (box) { //IP先頭用に独自キューを覚えておく var href = eleget0('//a', box)?.href; var vID = href?.match0(/\?v=([a-zA-Z0-9_\-]{11})/) || href?.match0(/\/shorts\/([a-zA-Z0-9_\-]{11})/); if (vID) { equeueIP.push(vID) equeueIP = [...new Set(equeueIP)] equeue.push(vID) equeue = [...new Set(equeue)] $('#instantPlaylistButton').html(`Instant
Playlist (${equeueIP.length}+)`) } } var prevcue = eleget0('//ytd-thumbnail-overlay-toggle-button-renderer[@aria-label="キューに追加"]/yt-icon[2]|.//ytd-thumbnail-overlay-toggle-button-renderer[@aria-label="Add to queue"]/yt-icon[2]', box) if (prevcue) { prevcue?.click(); return false; } var prevcue = eleget0('//a[@id="thumbnail"]/div/ytd-thumbnail-overlay-toggle-button-renderer[last()]/yt-icon[@class="style-scope ytd-thumbnail-overlay-toggle-button-renderer"]', box) var ances = box; if (ances) { var cuebutton = elegeta('ytd-thumbnail.style-scope.ytd-grid-video-renderer a div ytd-thumbnail-overlay-toggle-button-renderer:last-child yt-icon#icon,ytd-thumbnail.ytd-compact-video-renderer a div ytd-thumbnail-overlay-toggle-button-renderer:last-child yt-icon#icon', ances)[0] if (cuebutton) { cuebutton?.click() ances.style.opacity = "0.25" setTimeout(() => { ances.style.opacity = "0.5" }, 17 * 2) setTimeout(() => { ances.style.opacity = "1" }, 17 * 3) return false } } var ancestorEle = getTitleFromParent(ele, 0, '//ytd-item-section-renderer|//ytd-playlist-video-renderer|//ytd-grid-video-renderer|//div[@id="dismissible" and @class="style-scope ytd-video-renderer"]|//div[@id="dismissible" and @class="style-scope ytd-rich-grid-media"]|//ytd-compact-video-renderer'); if (!ancestorEle) return false let menuButton = elegeta('//yt-icon[@class="style-scope ytd-menu-renderer"]', ancestorEle); if (menuButton.length == 1) { setTimeout(() => { let queue = eleget0('//yt-formatted-string[text()="キューに追加"]|//yt-formatted-string[text()="Add to queue"]'); if (queue) { queue.click(); setTimeout(() => { ancestorEle.style.opacity = 0.5 }, 0) setTimeout(() => { ancestorEle.style.opacity = 0.5 }, 17 * 2) setTimeout(() => { ancestorEle.style.opacity = 1 }, 17 * 4) } }, 200) setTimeout(() => { menuButton[0].click() }, 0); } return false; } if (key === "y" && !/\/watch/.test(location.href)) { // y::start playing e.preventDefault(); cli('//div[contains(@class,"ytp-miniplayer-play-button-container")]/button|//button[contains(@class,"ytp-play-button-playlist")]') if (!(location.href.match(/\/watch\?v=/))) cli('//div/button[contains(@class,"ytp-miniplayer-expand-watch-page-button")]:visible', 111, "infinity"); setTimeout(() => { let e = eleget0('//video'); if (e) { e.play(); } }, 222); return false; } if (/^Alt\+c$|^Ctrl\+x$/.test(key) && /\/watch/.test(location.href) && USE_INSTANT_PLAYLIST) { // Alt+c::視聴中の再生リストをURLにしてコピー e.preventDefault(); makeUrlFromCuelist(1, kaisuu) kaisuu = ++kaisuu % YOUTUBE_WATCH_ALTC_VARIATIONS; } }, false) return; function makeUrlFromCuelist(disp = 1, kaisuu) { // disp:1:表示する 0:urlを作って返すだけ if (/\/watch/.test(location.href) && USE_INSTANT_PLAYLIST) { // Alt+c::視聴中の再生リストをURLにしてコピー let eles = elegeta('//ytd-playlist-panel-video-renderer[@id="playlist-items"]/a:visible'); let videoIDa = [...new Set(eles.map(c => c.href.match0(/\?v=([a-zA-Z0-9_\-]{11})/)))].slice(0, 50); // 重複削除 let videoIDaAll = [...new Set(eles.map(c => c.href.match0(/\?v=([a-zA-Z0-9_\-]{11})/)))]; // 重複削除 if (eles.length) { let indexEle = eleget0('//yt-formatted-string[@class="index-message style-scope ytd-playlist-panel-renderer"]/span[1]|//div/div[@id="secondary-inner" and @class="style-scope ytd-watch-flexy"]/ytd-playlist-panel-renderer[@id="playlist" and @class="style-scope ytd-watch-flexy" and @js-panel-height="" and @collapsible="" and @playlist-type="TLPQ"]/div/div[1]/div[@id="header-contents"]/div[@id="header-top-row" and contains(@class,"style-scope ytd-playlist-panel-renderer")]/div[@id="header-description"]/div/div/span:visible'); let indexNo = indexEle && indexEle.textContent ? indexEle.textContent.match0(/(\d+)/mi) - 1 : 0; let indexUrlQP = (PRESERVE_INDEX && indexNo > 0 && indexNo < 50) ? `&index=${indexNo}` : ""; elegeta("#link4bm").forEach(e => e.remove()) if (kaisuu == 0 || kaisuu == 2 || disp == 0) { // プレイリストの動画の合計時間を算出、全部読み込みが終わっていないとしない let playtimesum = (elegeta('ytd-playlist-panel-video-renderer#playlist-items.style-scope.ytd-playlist-panel-renderer:visible').slice(0, 49).length != elegeta('ytd-playlist-panel-video-renderer#playlist-items.style-scope.ytd-playlist-panel-renderer span#text.style-scope.ytd-thumbnail-overlay-time-status-renderer:visible').slice(0, 49).length) ? "" : (() => { let sum = elegeta('ytd-playlist-panel-video-renderer#playlist-items.style-scope.ytd-playlist-panel-renderer span#text.style-scope.ytd-thumbnail-overlay-time-status-renderer:visible').reduce((p, e) => { let t = e?.innerText?.trim() let h = t?.match0(/(\d+)\:\d+\:\d+$/) || 0 let m = t?.match0(/(\d+)\:\d+$/) || 0 let s = t?.match0(/(\d+)$/) || 0 p += h * 60 * 60 + m * 60 + s * 1 return p }, 0) return ` ${sum/60/60|0}:${sum/60%60|0}:${sum%60}` })() var cb = kaisuu == 2 ? `` : "https://www.youtube.com/watch_videos?video_ids=" + videoIDa.join(",") + indexUrlQP; var embedHTML = `