// ==UserScript==
// @name YouTube検索結果「全てキューに入れて再生」ボタンを追加
// @description musictonicの代わり 右クリックだとシャッフル再生 e:カーソル下の動画をキューに入れる y:再生開始 Alt+c:視聴中の再生リストをURLにしてコピー
// @version 0.1.24
// @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
// @downloadURL none
// ==/UserScript==
(function() {
const USE_IMMEDIATE_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 DEBUG = 0; // 1:wait値を表示
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));
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;
}
var JS = (v) => { return JSON.stringify(v) }
let inYOUTUBE = location.hostname.match0(/^www\.youtube\.com|^youtu\.be/);
var videoDisplayedLast = 0;
var lastLength = 0;
var mllID = 0;
var kaisuu = 0;
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,#immediatePlaylistButton').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 (location.href.match0(/nicovideo/)) {
// ニコ動
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" && location.href.match0(/nicovideo/)) { // e::enqueue
e.preventDefault();
var ele = document.elementFromPoint(mousex, mousey);
var ancestorEle = getTitleFromParent(ele, 0, '//div[3]/ul[@class="list" and @data-video-list=""]/li[@data-nicoad-video=""]');
if (!ancestorEle) return false
let titleEle = eleget0('.//p[@class="itemTitle"]/a', ancestorEle);
if (!titleEle) return false
myqueue.push({ id: titleEle.href.replace(/^.+\/watch\/|\?.+/gmi, ""), title: titleEle.innerText.trim() })
myqueue = Array.from(new Set(myqueue.map(a => JSON.stringify(a)))).map(a => JSON.parse(a));
popup(`e:『${titleEle.textContent}』をキューに入れました(y:再生)\n${myqueue.map((c,i)=>`${1+i}) ${c.title} (${c.id})`).join("\n")}`)
return false;
}
if (key === "y" && !/\/watch/.test(location.href)) { // y::start playing
e.preventDefault();
var url = `${myqueue.map(c=>c.id).join(",")}を連続再生するURLがありません`
alert(url)
return false;
}
}
}, false)
return
}
*/
var esckey
document.addEventListener('keydown', e => {
if (e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA' || e.target.getAttribute('contenteditable') === 'true' || ((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 && !esckey) {
esckey = setInterval(() => { // esc::ミニプレイヤーを常に閉じる
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();
clearInterval(esckey)
esckey = 0
}
}, 701);
}
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'); //マウスが乗っている動画の枠
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) {
equeue.push(vID)
equeue = [...new Set(equeue)]
$('#immediatePlaylistButton').html(`Immediate
Playlist (${equeue.length}+)`)
}
}
var prevcue = eleget0('//yt-formatted-string[contains(text(),"キューに追加")]', 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)
if (prevcue) { prevcue?.click(); return false; }
var ances = box; //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) && USE_IMMEDIATE_PLAYLIST) { // Alt+c::視聴中の再生リストをURLにしてコピー
e.preventDefault();
let eles = elegeta('//ytd-playlist-panel-video-renderer[@id="playlist-items"]/a');
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');
let indexNo = indexEle && indexEle.textContent ? indexEle.textContent.match0(/(\d+)/mi) - 1 : 0;
let indexUrlQP = indexNo > 0 ? `&index=${indexNo}` : "";
elegeta("#link4bm").forEach(e => e.remove())
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 {
// プレイリストの動画の合計時間を算出、全部読み込みが終わっていないとしない
let playtimesum = (elegeta('//ytd-playlist-panel-video-renderer[@class="style-scope ytd-playlist-panel-renderer" and @watch-color-update="" and @id="playlist-items"]').slice(0, 49).length != elegeta('//a[@id="thumbnail" and @class="yt-simple-endpoint inline-block style-scope ytd-thumbnail" and @tabindex="-1" and @rel="null"]/div[@id="overlays"]/ytd-thumbnail-overlay-time-status-renderer[@overlay-style="DEFAULT"]/span[@id="text"]').slice(0, 49).length) ? "" :
(() => {
let sum = elegeta('//a[@id="thumbnail" and @class="yt-simple-endpoint inline-block style-scope ytd-thumbnail" and @tabindex="-1" and @rel="null"]/div[@id="overlays"]/ytd-thumbnail-overlay-time-status-renderer[@overlay-style="DEFAULT"]/span[@id="text"]').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 ? `