您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
musictonicの代わり 右クリックだとシャッフル再生 e:カーソル下の動画をキューに入れる y:再生開始 Alt+c:視聴中の再生リストをURLにしてコピー
当前为
// ==UserScript== // @name YouTube検索結果「全てキューに入れて再生」ボタンを追加 // @description musictonicの代わり 右クリックだとシャッフル再生 e:カーソル下の動画をキューに入れる y:再生開始 Alt+c:視聴中の再生リストをURLにしてコピー // @version 0.1.20 // @run-at document-idle // @match *://www.youtube.com/* // @match *://www.youtube.com/ // @require https://code.jquery.com/jquery-3.4.1.min.js // @require https://code.jquery.com/ui/1.12.1/jquery-ui.min.js // @grant GM.setClipboard // @namespace https://greasyfork.org/users/181558 // ==/UserScript== (function() { const CLOSE_MINI_PLAYER_ALWAYS = 1; // 1:Escでミニプレイヤーを常に閉じる const AGREE_TO_CONTINUE_ALWAYS = 1; // 1:無操作一時停止を常に解除 const HIDE_SUGGEST = 1000; // 1-:検索結果に割り込む「あなたへのおすすめ」「他の人はこちらも視聴しています」「家にいながら学ぶ」を隠す const YOUTUBE_WATCH_ALTC_VARIATIONS = 2; // Alt+Cの機能を何番目まで使うか 1:連続再生URL 2:単独再生URLの列挙 3:iframe埋め込み用HTML const CONFIRM_AT_CREATE_FROM_URLS = 1; // 1:列挙URLからのURL作成(YouTubeロゴ右クリック|Alt+C)時に確認する 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 = Math.round((Math.min(WAIT_MAX, Math.max(WAIT_MIN, waitLast / 10))) * (CHROME ? COE_CHROME : COE)); const DEBUG = Math.random() > 0.1 ? 1 : 0; // 0; // 1:wait値を表示 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フラグ不可 let inYOUTUBE = location.hostname.match0(/^www\.youtube\.com|^youtu\.be/); var videoDisplayedLast = 0; var lastLength = 0; var mllID = 0; var kaisuu = 0; var playAllCount; var myqueue = []; //URLの変化を監視 var href = location.href; var observer = new MutationObserver(function(mutations) { if (href !== location.href) { href = location.href; $('#playAllButton').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 (eleget0('//yt-formatted-string[text()="動画が一時停止されました。続きを視聴しますか?"]')) { elegeta('//yt-formatted-string[@class="style-scope yt-button-renderer style-blue-text size-default" and text()="はい"]').forEach(e => e.click()); } }, 3001); } var mousex = 0; var mousey = 0; document.addEventListener("mousemove", function(e) { mousex = e.clientX; mousey = e.clientY; }, false); if (CLOSE_MINI_PLAYER_ALWAYS) setInterval(() => { // ミニプレイヤーを常に閉じる let e = eleget0('//yt-formatted-string[@id="text" and @class="style-scope yt-button-renderer style-blue-text size-default" and text()="プレーヤーを閉じる"]'); if (e) e.click(); }, 701); $('ytd-logo yt-icon#logo-icon').on("contextmenu", () => { makeContPlay(); return false; }) document.addEventListener('keydown', e => { if (e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA' || e.target.getAttribute('contenteditable') === 'true' || (inYOUTUBE && (document.activeElement.closest('#chat-messages') || document.activeElement.closest('ytd-comments-header-renderer')))) return; var key = (e.shiftKey ? "Shift+" : "") + (e.altKey ? "Alt+" : "") + (e.ctrlKey ? "Ctrl+" : "") + e.key; if (key === "e") { // e::enqueue e.preventDefault(); var prevcue = eleget0('//yt-formatted-string[contains(text(),"キューに追加")]') 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"]') if (prevcue) { prevcue?.click(); return false; } var ele = document.elementFromPoint(mousex, mousey); var ances = ele?.closest('.ytd-grid-renderer,.ytd-compact-video-renderer') 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[@aria-label=\"再生(k)\"]|//button[@class="ytp-play-button ytp-button" and @aria-label="Play (k)"]') if (!(location.href.match(/\/watch\?v=/))) cli('//div[@class="ytp-miniplayer-scrim"]/button[@aria-label="拡大(i)"]|//div[@class="ytp-miniplayer-scrim"]/button[@aria-label="Expand (i)"]', 111, "infinity"); setTimeout(() => { let e = eleget0('//video'); if (e) { e.play(); } }, 222); return false; } if (key === "Alt+c" && /\/watch/.test(location.href)) { // Alt+c::視聴中の再生リストをURLにしてコピー e.preventDefault(); let eles = elegeta('//ytd-playlist-panel-video-renderer[@id="playlist-items"]/a'); let eles2 = [...new Set(eles.map(c => c.href.match0(/\?v=([^&]+)/)))].slice(0, 50); // 重複削除 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'); let indexNo = indexEle && indexEle.textContent ? indexEle.textContent.match0(/(\d+)/mi) - 1 : 0; let indexUrlQP = indexNo > 0 ? `&index=${indexNo}` : ""; elegeta("#link4bm").forEach(e => e.remove()) $("#logo").css({ "margin-right": "4em" }) if (kaisuu == 1) { list = [...new Set(elegeta('//h4[@class=\"style-scope ytd-playlist-panel-video-renderer\"]/span[@id=\"video-title\"]').map(e => { return e.textContent.trim() + "\n" + e.closest('a').href.trim().replace(/&.*/, "") + "\n" }).map(a => JSON.stringify(a)))].map(a => JSON.parse(a)).join("") popup(list, "#303060") GM.setClipboard(list + ""); } else { var cb = kaisuu == 2 ? `<iframe referrerpolicy="no-referrer" src="https://www.youtube.com/embed/${eles2[0]}?playlist=${ eles2.join(",")}" id="ytplayer" type="text/html" allowfullscreen="" allow="picture-in-picture" width="320" height="180" frameborder="0"></p></iframe>` : "https://www.youtube.com/watch_videos?video_ids=" + eles2.join(",") + indexUrlQP; var embedHTML = `<iframe referrerpolicy="no-referrer" src="${cb}" id="ytplayer" type="text/html" width=320 height=180 frameborder=0 allowfullscreen>` var cb2 = cb var cbEsc = (cb2).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"').replace(/'/g, ''') var title = `▶ ${indexNo+1}/${eles2.length} ${document.title}` popup(kaisuu == 2 ? cb : document.title + "\n" + cb, "", "right:0em; top:0em;max-width:40%;") GM.setClipboard(kaisuu == 2 ? cb : document.title + "\n" + cb2 + "\n"); if (kaisuu != 2) $(`<div style="font-size:14px;" id="link4bm">${kaisuu==2?"埋め込み":"ブックマーク"}用リンク(${eles2.length})<br><a href=${cb}>${title}</a></div>`).hide(0).insertAfter($('#logo')).show(150).delay(9999).hide(250, function() { $(this).remove() }) } kaisuu = ++kaisuu % YOUTUBE_WATCH_ALTC_VARIATIONS; return false } else { //キューやプレイリスト再生状態ではない makeContPlay() return false } } }, false) return; function makeContPlay() { var inp = prompt("Alt+C:\nYouTubeの再生URLから連続再生URLを作ってクリップボードにコピーします\nYouTubeの再生URLを何行でも貼り付けてください\n再生URL以外の文字列や重複した動画は無視されます\n\n対応書式:\nhttps://www.youtube.com/watch?v=動画ID\nhttps://youtu.be/動画ID\nhttps://www.youtube.com/watch_videos?video_ids=動画ID,動画ID,…\n\n") if (inp) { var urlcap = [...inp.matchAll(/ttps?:\/\/www\.youtube\.com\/watch\?v=([a-zA-Z0-9_\-]{11})|ttps?:\/\/youtu\.be\/([a-zA-Z0-9_\-]{11})|ttps?:\/\/www\.youtube\.com\/watch_videos\?video_ids=([a-zA-Z0-9_\-,]{11,600})/gmi)].map(c => c.slice(1, 999)); if (urlcap) { let urla = urlcap.join(",").split(",").filter(c => /^[a-zA-Z0-9_\-]{11}$/.test(c)); // 動画IDは11桁 let urllen = urla.length; let urla2 = [...new Set(urla)] let urllen2 = urla2.length; let urla3 = [...urla2].slice(0, 50) let urlenum = urla3.join(",") let url = `https://www.youtube.com/watch_videos?video_ids=${urlenum}` if (urla3 && urla3.length) { var title = `▶ (${urla3.length}) ${urla3.slice(0,3).join(",")+(urla3.length>3?",…":"")}` let con = CONFIRM_AT_CREATE_FROM_URLS ? confirm(`${urllen}件の動画IDを抽出しました\n${urllen-urllen2}件の重複を削除しました\n\n下記(${urla3.length}件)をクリップボードにコピーしますか?\n\n${title}\n${url}`) : 1; if (con) { GM.setClipboard(title + "\n" + url + "\n") $("#logo").css({ "margin-right": "4em" }) $(`<div style="font-size:14px;" id="link4bm">${kaisuu==2?"埋め込み":"ブックマーク"}用リンク(${urla3.length})<br><a href=${url}>${title}</a></div>`).hide(0).insertAfter($('#logo')).show(150).delay(9999).hide(250, function() { $(this).remove() }) popup(title + "\n" + url, "", "right:0em; top:0em;max-width:40%;") } } // } } } } function hideSuggest() { if (HIDE_SUGGEST && location.href.indexOf('www.youtube.com/results?') !== -1) { ['//div/div/span[@id="title" and (text()="Learn while you\'re at home" or text()="For you" or text()="People also watched" or text()="家にいながら学ぶ" or text()="あなたへのおすすめ" or text()="他の人はこちらも視聴しています")]/../../../../..', // 縦横 '//div[@class="style-scope ytd-shelf-renderer"]/h2[@class="style-scope ytd-shelf-renderer"]/span[@id="title" and contains(@class,"style-scope ytd-shelf-renderer") and (text()="Learn while you\'re at home" or text()="For you" or text()="People also watched" or text()="家にいながら学ぶ" or text()="あなたへのおすすめ" or text()="他の人はこちらも視聴しています")]/../../../..', // 縦1列 ].forEach(xp => { $(elegeta(xp)).hide(HIDE_SUGGEST, function() { $(this).remove() }); // 検索結果に割り込むサジェストを隠す }); } } function run(node = document) { if (location.href == "https://www.youtube.com/" || location.href.match(/https:\/\/www\.youtube\.com\/results\?.*(q=|search_query=)/) || location.href.match("//www.youtube.com/channel/.*/search|//www.youtube.com/user/.*/search") || (location.href.match("//www.youtube.com/channel/|//www.youtube.com/c/|//www.youtube.com/user/") && !(location.href.match("/community|/channels|/about|/playlists"))) || location.href.match("//www.youtube.com/playlist") || location.href.match("//www.youtube.com/watch")) { var place = eleget0('//div[@id="center" and @class="style-scope ytd-masthead"]'); } else return; if (place) { $('#playAllButton').remove(); var playAllButton = $('<span class="ignoreMe" style="cursor:pointer;color:var(--yt-spec-icon-active-other); text-align:center; font-size:15px; " title="クリックで画面に出ている動画を全てキューに入れて再生(右クリックだとシャッフル)\nEnqueue all displayed videos and start playing (right-click to shuffle)" id="playAllButton">Play All</span>') playAllButton.insertAfter(place); playAllButton.on("contextmenu", () => { playAll("shuffle"); return false; }); playAllButton.on("click", () => { playAll(); return false; }); if (!playAllCount) { playAllCount = setInterval(() => { let currentLength = elegeta('//yt-icon[@class="style-scope ytd-menu-renderer"]').length; if (lastLength != currentLength) $('#playAllButton').html("Play All (" + currentLength + ")" + (DEBUG ? "<br>wait:" + wait : "")); lastLength = currentLength; }, 1000); } } } function pauseVideo() { let e = eleget0('//video'); if (e) { e.pause(); } else { setTimeout(pauseVideo, 17) } } function playAll(option = false) { setTimeout(pauseVideo, 17); let videoLength = elegeta('//yt-icon[@class="style-scope ytd-menu-renderer"]', document, 0).length; //notifyMe(videoLength * 2) elegeta('//ytd-rich-item-renderer|//div[@id="dismissible"]', document.body, 0).forEach(e => { e.remove(); }); let d = 0; let videoEle = elegeta('//yt-icon[@class="style-scope ytd-menu-renderer"]'); let i = 0; for (let e of (option == "shuffle" ? shuffle(videoEle) : videoEle)) { setTimeout(() => { e.click() }, d); if (d == 0) d += WAIT_FIRST + (videoLength * 2); // ? setTimeout(() => { let queue = eleget0('//yt-formatted-string[text()="キューに追加"]|//yt-formatted-string[text()="Add to queue"]'); if (queue) queue.click(); }, d + wait / 2); d += wait + (videoLength / 5); if ((i++) > 201) break; // キューは200件までしか入らないので時間節約 } d += wait * Math.min(7000, Math.max(2000, waitLast)) / 1000 + videoLength / 3; cli('//div[contains(@class,\"ytp-miniplayer-play-button-container\")]/button[@aria-label=\"再生(k)\"]|//button[@class="ytp-play-button ytp-button" and @aria-label="Play (k)"]', d); d += wait * Math.min(7000, Math.max(2000, waitLast)) / 1000 + videoLength / 3; if (!(location.href.match(/\/watch\?v=/))) cli('//div[@class="ytp-miniplayer-scrim"]/button[@aria-label="拡大(i)"]|//div[@class="ytp-miniplayer-scrim"]/button[@aria-label="Expand (i)"]', d, "infinity"); d += wait; setTimeout(() => { let e = eleget0('//video'); if (e) { e.play(); } }, d); } function shuffle(array) { for (let i = array.length - 1; i >= 0; i--) { const j = Math.floor(Math.random() * (i + 1)); [array[i], array[j]] = [array[j], array[i]]; } return array; } function cli(xpath, wait, mode = "") { // mode: infinity:押せるまで監視し続ける setTimeout(() => { let ele = eleget0(xpath); if (ele) { ele.click(); } else if (mode === "infinity") { cli(xpath, 200, mode) } }, wait); if (eleget0(xpath)) { return true } else { return false } } function elegeta(xpath, node = document, onlyVisible = 1) { if (!xpath) return []; if (!/^\.?\//.test(xpath)) return /:visible$/.test(xpath) ? [...node.querySelectorAll(xpath.replace(/:visible$/, ""))].filter(e => e.offsetHeight) : [...node.querySelectorAll(xpath)] try { var array = []; var ele = document.evaluate("." + xpath, node, null, XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null); let j = 0; for (var i = 0; i < ele.snapshotLength; i++) { let ei = ele.snapshotItem(i); if (ei.offsetHeight) { if (onlyVisible) { array[j++] = ei; } } else { if (!onlyVisible) { array[j++] = ei; } } } return array; } catch (e) { return []; } } function eleget0(xpath, node = document) { if (!xpath) return null; try { var ele = document.evaluate(xpath, node, null, XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null); if (ele.snapshotLength < 1) return ""; let ei = ele.snapshotItem(0); if (ei.offsetHeight) return ei; return ""; } catch (e) { return null; } } function notifyMe(body, title = "") { if (!("Notification" in window)) return; else if (Notification.permission == "granted") new Notification(title, { body: body }); else if (Notification.permission !== "denied") Notification.requestPermission().then(function(permission) { if (permission === "granted") new Notification(title, { body: body }); }); } function getTitleFromParent(ele, nodisplay = 0, ancestorXP) { // ele要素の親の出品物タイトルを返す if (elegeta(ancestorXP).includes(ele)) return ele; for (let i = 0; i < (9); i++) { var ele2 = elegeta(ancestorXP, ele); if (ele2.length === 1) { return ele2[0]; } if (ele === document) return; ele = ele.parentNode; if (elegeta(ancestorXP).includes(ele)) return ele } return; } function popup(text, bgcolor = "", additionalStyle = "right:0em; top:0em;") { var e = document.getElementById("cccbox"); var cID = rndID(11); if (e) { e.remove(); } if (mllID) { clearTimeout(mllID); } if (!(text > "")) return; text = text.replace(/&/g, "&").replace(/"/g, """).replace(/'/g, "'").replace(/`/g, '`').replace(/</g, "<").replace(/>/g, ">").replace(/\n/gm, "<br>") bgcolor = bgcolor || (/www\.translatetheweb\.com|\.translate\.goog\/|translate\.google\.com|\/embed\//gmi.test(location.href + " " + text) ? "#822" : "#6080ff"); document.body.insertAdjacentHTML("beforeend", `<span id="cccbox" class="${cID}" style="all:initial; position: fixed; z-index:2147483647; opacity:1; word-break:break-all; font-size:${Math.max(11,15-(text.length/300)-((text.match(/<br>/gm)||[]).length/50))}px; font-weight:bold; margin:0px 1px; text-decoration:none !important; text-align:none; padding:1px 6px 1px 6px; border-radius:12px; background-color:${bgcolor}; color:white; ${additionalStyle}">${ text }</span>`) var ele = document.body.lastChild mllID = setTimeout(function() { $(`.${cID}`).remove(); }, 4000); ele.onclick = () => { $(`.${cID}`).remove(); if (mllID) { clearTimeout(mllID); } } } function rndID(n = 11) { var S = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_" return Array.from(Array(n)).map(() => S[Math.floor(Math.random() * S.length)]).join('') } })();