// ==UserScript==
// @name YouTube検索結果「全てキューに入れて再生」ボタンを追加
// @description musictonicの代わり 右クリックだとシャッフル再生 e:カーソル下の動画をキューに入れる y:再生開始
// @version 0.1.15
// @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
// @namespace http://greasyfork.icu/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 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 / 15)));
const DEBUG = 0; // 1:wait値を表示
var videoDisplayedLast = 0;
var lastLength = 0;
var playAllCount;
//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);
}
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);
var mousex = 0;
var mousey = 0;
document.addEventListener('keydown', e => {
if (e.target.tagName != 'INPUT' && e.target.tagName != 'TEXTAREA' && e.target.getAttribute('contenteditable') != 'true') {
var key = (e.shiftKey ? "Shift+" : "") + (e.altKey ? "Alt+" : "") + (e.ctrlKey ? "Ctrl+" : "") + e.key;
if (key === "e") { // e::enqueue
e.preventDefault();
var ele = document.elementFromPoint(mousex, mousey);
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(() => { ancestorEle.style.opacity = 0.5 }, 0)
setTimeout(() => { ancestorEle.style.opacity = 0.5 }, 17 * 2)
setTimeout(() => { ancestorEle.style.opacity = 1 }, 17 * 4)
setTimeout(() => { menuButton[0].click() }, 0);
//alert("1 " + ancestorEle.outerHTML)
setTimeout(() => {
let queue = eleget0('//yt-formatted-string[text()="キューに追加"]|//yt-formatted-string[text()="Add to queue"]');
//alert("2 " + queue.outerHTML)
if (queue) {
queue.click();
}
}, 100)
}
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;
}
}
}, false)
document.addEventListener("mousemove", function(e) {
mousex = e.clientX;
mousey = e.clientY;
}, false);
return;
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: #000; text-shadow: -1px -1px 0 #fff;" 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"]');
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);
}
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 [];
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;
}
})();