// ==UserScript==
// @name YouTube検索結果「全てキューに入れて再生」ボタンを追加
// @description musictonicの代わり 右クリックだとシャッフル再生 e:カーソル下の動画をキューに入れる y:再生開始 Alt+c/Ctrl+x:視聴中のキューリストをURLにしてコピー
// @version 0.2.1
// @run-at document-idle
// @match *://www.youtube.com/*
// @match *://www.youtube.com/
// @match file:///*
// @grant GM.setClipboard
// @grant GM.openInTab
// @grant GM.addStyle
// @grant GM_setValue
// @grant GM_getValue
// @grant GM_deleteValue
// @noframe
// @namespace https://greasyfork.org/users/181558
// @require https://code.jquery.com/jquery-3.3.1.min.js
// @require https://code.jquery.com/ui/1.13.2/jquery-ui.min.js
// ==/UserScript==
// @match *://*/*
(function() {
const USE_INSTANT_PLAYLIST = 0; // 0:機能6-8を無効にする 1:有効にし使用時に確認を表示する 2:有効にし確認しない
const YOUTUBE_WATCH_ALTC_VARIATIONS = 3; // 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 = 1; // 1:検索結果に割り込む「あなたへのおすすめ」「他の人はこちらも視聴しています」「家にいながら学ぶ」等を隠す 0:無効
const PRESERVE_INDEX = 0; // 1:機能6-8で最初に再生するトラックを保持
const INCLUDE_REEL_SHORTS = 1; // 1:機能6-8でリール棚のShorts動画を含める
const USE_PLAYALL = 1; // 1:PlayAllボタンを有効 0:無効
const YOUTUBE_WATCH_ALTC_EMBED_PLAYER_SIZE = `width="498" height="280"`; // ALT+C3回目の埋め込みプレイヤーのサイズ指定 322x181~
const SEARCH_RESULTS_THUMBNAIL_VIDEOS_WIDTH = "12.5"; // 検索結果の動画のサムネイルのサイズ "":無効
const SEARCH_RESULTS_THUMBNAIL_SHORTS_HEIGHT = "17"; // 検索結果のshortsのサムネイルのサイズ "":無効
const WAIT_LOADING = Math.random() > 0.5 ? 1 : 0; // PLAY ALL押下時にボタン設置を待つ
const EXPERIMENTAL_ALTERNATIVE_URL_FOR_INSTANT_PLAYLIST = 1; // 2:Instant Playlist用のURLを不具合回避のembed版にして遷移するようにする 1:embed版を開いた時通常の視聴ページに遷移する機能だけオン
const DISPLAY_TOTAL_TIME_OF_PLAYLIST = 0; // 1:プレイリストの合計時間を表示する
const DEBUG = 0; // 1:wait値を表示
const IPURL = EXPERIMENTAL_ALTERNATIVE_URL_FOR_INSTANT_PLAYLIST >= 2 ? `https://www.youtube.com/embed/?playlist=` : `https://www.youtube.com/watch_videos?video_ids=`;
const IPBOX = `:is(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 , ytd-compact-video-renderer , ytm-shorts-lockup-view-model-v2.shortsLockupViewModelHost)`; //キューを入れられる動画の枠 shorts含む
const QBOX = `:is(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 , ytd-compact-video-renderer):not(:has( ytm-shorts-lockup-view-model-v2))`; //キューを入れられる動画の枠 shorts除外
const MENUB = `:is(button[aria-label="操作メニュー"] , button[aria-label="その他の操作"] , button[aria-label="Action menu"] , button[aria-label="More actions"])`;
const itemIPL = () => elegeta(`#dismissible a#video-title[href*="/watch?v="] , #dismissible a#video-title-link[href*="/watch?v="] , .style-scope.ytd-playlist-video-list-renderer a#video-title[href*="/watch?v="] , #dismissible a.ytd-compact-video-renderer[href*="/watch?v="] , #playlist-items a[href*="/watch?v="] , #dismissible a#video-title[href*="/shorts/"] , #dismissible a#video-title-link[href*="/shorts/"] , .style-scope.ytd-playlist-video-list-renderer a#video-title[href*="/shorts/"] , #dismissible a.ytd-compact-video-renderer[href*="/shorts/"] , #playlist-items a[href*="/shorts/"] , ytd-rich-grid-slim-media[mini-mode][is-short] div div a ${SHORTS} , a.ShortsLockupViewModelHostEndpoint.ShortsLockupViewModelHostOutsideMetadataEndpoint:visible`).filter(e => e?.closest('ytd-playlist-panel-video-renderer')?.style?.opacity != 0.5 && !e?.closest(".miniplayer")); // for IPL
const QB = e => elegeta('yt-formatted-string.style-scope.ytd-menu-service-item-renderer , .yt-core-attributed-string.yt-list-item-view-model-wiz__title')?.find(v => ["キューに追加", "Add to queue"].includes(v.textContent))
const QBOXlen = () => elegeta(`${QBOX}:visible`).length; // 画面に出ている動画の総数
const MENUBlen = () => elegeta(`${QBOX}:visible`).filter(e => eleget0(`${MENUB} svg`, e)).length; // …ボタン設置も完了した動画の総数
var SHORTS = INCLUDE_REEL_SHORTS ? ` , a.shortsLockupViewModelHostEndpoint[href*="/shorts/"]` : ``;
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 mllID = 0;
var kaisuu = 0;
var equeueIP = []
var equeue = []
var playAllCount, playAllCount2;
var myqueue = [];
GF.sousa = Date.now()
const ael = (ele, evts, cb) => evts.split(" ").forEach(evt => ele?.addEventListener(evt, cb));
ael(document, "scroll visibilitychange yt-navigate-finish yt-playlist-data-updated", () => { GF.sousa = Date.now() });
let addstyle = {
added: [],
add: function(str) {
if (this.added.some(v => v[1] === str)) return;
var S = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" // var S="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_"
var d = Date.now()
var uid = Array.from(Array(12)).map(() => S[Math.floor((d + Math.random() * S.length) % S.length)]).join('')
document.head.insertAdjacentHTML("beforeend", `<style id="${uid}">${str}</style>`);
this.added.push([uid, str]);
return uid;
},
remove: function(str) { // str:登録したCSSでもaddでreturnしたuidでも良い
let uid = this.added.find(v => v[1] === str || v[0] === str)?.[0]
if (uid) {
eleget0(`#${uid}`)?.remove()
this.added = this.added.filter(v => v[0] !== uid)
}
}
}
if ((window.parent == window) && EXPERIMENTAL_ALTERNATIVE_URL_FOR_INSTANT_PLAYLIST >= 1 && lh(/https:\/\/www.youtube.com\/embed\/\?playlist\=/)) { // 匿名プレイリストの埋め込み用ページだったら正規視聴ページに遷移
GF.avoidID = setInterval(() => {
let err = eleget0('//div[contains(@class,"ytp-error-content-wrap")]/div/span[text()="動画を再生できません"]|//div[@class="ytp-error-content-wrap-reason"]/span[text()="Video unavailable"]') // 多分外部サイトでの埋め込み再生を禁止している動画
if (err) {
clearInterval(GF.avoidID)
if (Math.random() > 0.33) { // 2/3の確率で順番を変えてみる(1つめを埋め込み禁止動画じゃなくせるかも)
var sm = location.href.split(/\s/).map(v => { return [...v?.matchAll(/^(?:h?t?tps?:\/\/)?(?:youtu\.be\/|(?:m\.|www\.|)?youtube\.com\/(?:shorts\/|watch\?v=|embed\/|live\/))([a-zA-Z0-9_\-]{11})(?![a-zA-Z0-9_\-]{1})|^(?:h?t?tps?:\/\/)?www\.youtube\.com\/(?:watch_videos\?video_ids=|embed\/\?playlist=)([a-zA-Z0-9_\-,]{11,600})/gmi)]?.map(c => c.slice(1, 999)) })?.flat()?.flat()?.map(v => v?.split(","))?.flat()?.filter(c => /^[a-zA-Z0-9_\-]{11}$/.test(c)) // 書式が混在していても登場順に収納する
sm = sm.map(a => ({ rnd: Math.random(), val: a })).sort((a, b) => a.rnd - b.rnd).map(a => a.val);
location.href = `https://www.youtube.com/embed/?playlist=${sm.join(",")}`;
} else {
location.href = location.href?.replace(/https:\/\/www.youtube.com\/embed\/\?playlist\=/, "https://www.youtube.com/watch_videos?video_ids=");
}
return;
}
let err2 = eleget0('//div[contains(@class,"ytp-error-content-wrap-reason")]/span[text()="この動画は再生できません"]|//div[contains(@class,"ytp-error-content-wrap-reason")]/span[contains(text(),"This video is unavailable")]') // 多分TLGGが間に合ってない
if (err2) {
clearInterval(GF.avoidID)
if (pref("lastURL") != location.href) {
pref("lastURL", location.href);
begin(document.body, `<h2>Wait a few seconds...</h2>`)
setTimeout(() => location.reload(), 2000);
return
}
alert("数秒待ってリロードしてみると良いかもしれません")
return;
}
let t = eleget0('a.ytp-title-link.yt-uix-sessionlink')?.href?.match0(/^https:\/\/www\.youtube\.com\/watch\?list=.*$/)
if (t) {
clearInterval(GF.avoidID)
location.href = t;
return;
}
}, 333)
} else pref("lastURL", "");
if (USE_INSTANT_PLAYLIST) {
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 === "Shift+Ctrl+Y" || key === "Shift+Y") { // Shift+Y::
let option = key == "Shift+Ctrl+Y" ? "shuffle" : ""
let inp = [...elegeta('a:visible').map(e => e.href.replace("//yewtu.be/watch?v=", "//www.youtube.com/watch?v=").replace("//yewtu.be/shorts/", "//www.youtube.com/shorts/").replace(/\/\/yewtu\.be\/([a-zA-Z0-9_\-,]{11})(.*)/, "//youtu.be/$1$2").replace(/\/\/youtube\.com\/([a-zA-Z0-9_\-,]{11})(.*)/, "//youtu.be/$1$2").replace("//yewtu.be/", "//www.youtube.com/")).filter(v => /youtube\.com|youtu\.be/.test(v)), ...document?.body?.innerText?.split(/\n|\s/)?.filter(v => /youtube\.com|youtu\.be/.test(v)), ...elegeta('iframe[src*="youtube"]').map(e => e?.src)].join(" ");
if (inp || 1) {
var urlcap = inp.split(/\s/).map(v => { return [...v?.matchAll(/(?:h?t?tps?:\/\/)?(?:youtu\.be\/|(?:m\.|www\.|)?youtube\.com\/(?:shorts\/|watch\?v=|embed\/|live\/))([a-zA-Z0-9_\-]{11})(?![a-zA-Z0-9_\-]{1})|^(?:h?t?tps?:\/\/)?www\.youtube\.com\/(?:watch_videos\?video_ids=|embed\/\?playlist=)([a-zA-Z0-9_\-,]{11,600})/gmi)]?.map(c => c.slice(1, 999)) })?.flat()?.flat()?.map(v => v?.split(","))?.flat()?.filter(c => /^[a-zA-Z0-9_\-]{11}$/.test(c)) // 書式が混在していても登場順に収納する
inp = null;
if (urlcap?.length || 1) {
let urla = urlcap //urlcap.join(",").split(",").filter(c => /^[a-zA-Z0-9_\-]{11}$/.test(c)); // 動画IDは11桁
let urllen = urla.length;
let urla2 = [...new Set(urla)]; // 重複削除
if (option == "shuffle") urla2 = shuffle(urla2); // シャッフル
let urllen2 = urla2.length;
let urla3 = [...urla2].slice(0, 50); // 50件まで
let urlenum = urla3.join(",")
let url = `${IPURL}${urla2.join(",")}`
var enumUrl = []
for (let u = 0; u < urla2.length / 50; u++) {
enumUrl.push(`${IPURL}${ (urla2.slice(u*50, u*50+50).join(",") ) }`)
}
let [url0, urls, videos] = urlExtractAndConcat(option, urllen2 ? enumUrl.join(" \n\n") : "", urla2?.length);
if (urls?.length) {
setTimeout(() => {
confirm(`${videos}個の動画を${urls?.length}つのタブで開きますか?\n\n${urls?.join("\n\n")}`) && //GM.openInTab(enumUrl[0], true);
openUrls(escape(JS(urls)))
return;
}, 222)
}
}
}
}
})
}
if (!ld("youtube.com")) return; // youtube以外はここまで
// プレイリストの動画の合計時間を算出、全部読み込みが終わっていないとしない
function gettotal(max = 99999) {
let playlistItemLen = elegeta('ytd-playlist-panel-video-renderer#playlist-items.style-scope.ytd-playlist-panel-renderer:visible').slice(0, max).length
if (playlistItemLen) {
let palylistTime = elegeta('ytd-playlist-panel-video-renderer#playlist-items.style-scope.ytd-playlist-panel-renderer div.ytd-thumbnail-overlay-time-status-renderer:visible').slice(0, max)
if (palylistTime?.length == playlistItemLen) {
let sum = palylistTime.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)
let total = `${zeropad(2,sum/60/60|0)}:${zeropad(2,sum/60%60|0)}:${zeropad(2,sum%60)}`
return total;
}
}
return "";
}
if (DISPLAY_TOTAL_TIME_OF_PLAYLIST) {
setInterval(() => {
if (document.visibilityState != "visible") return;
if (!GF.time && lh(/^https:\/\/www\.youtube\.com\/watch\?v=.*\&list\=/)) { //&& document?.getElementById('playlist')) {
let total = gettotal()
if (total) {
eleget0('div#publisher-container.style-scope.ytd-playlist-panel-renderer:visible').insertAdjacentHTML("beforeend", `<div id="playlisttotaltime" style='margin: auto auto auto 1em; font-size: 1.3rem; font-weight: 400; color: var(--yt-spec-text-secondary); font-family: "Roboto","Arial",sans-serif;'>${total}</div>`);
GF.time = 1;
}
}
}, 4000);
}
ael(window, "yt-navigate-finish yt-playlist-data-updated", e => {
GF.time = 0;
$('#playlisttotaltime').remove()
})
function zeropad(pad, num) {
return String(num)?.padStart(pad, "0")
}
// Enhancer for YouTubeのミニプレイヤーをカーソルを避けるようにする
GF.avoidminiplayer = 0
let css = '#efyt-progress,body.efyt-mini-player._top-right #movie_player:not(.ytp-fullscreen),body.efyt-mini-player._bottom-right #movie_player:not(.ytp-fullscreen){left:1em !important; right:auto !important;}'
document.addEventListener("mousemove", e => {
if (GF.avoidminiplayer && !e?.target?.closest('ytd-watch-next-secondary-results-renderer.style-scope.ytd-watch-flexy')) {
GF.avoidminiplayer = 0
addstyle.remove(css)
return;
} else
if (!GF.avoidminiplayer && e?.target?.closest('ytd-watch-next-secondary-results-renderer.style-scope.ytd-watch-flexy')) { // .closestの方が:hoverより40倍ぐらい速い
addstyle.add(css)
GF.avoidminiplayer = 1
}
})
SEARCH_RESULTS_THUMBNAIL_VIDEOS_WIDTH && GM.addStyle(`.yt-lockup-view-model-wiz__content-image {max-width: ${SEARCH_RESULTS_THUMBNAIL_VIDEOS_WIDTH*1.9}em !important;}
ytd-search ytd-video-renderer ytd-thumbnail.ytd-video-renderer,ytd-search ytd-playlist-thumbnail,div#avatar-section.style-scope.ytd-channel-renderer{max-width: ${SEARCH_RESULTS_THUMBNAIL_VIDEOS_WIDTH}em !important;}`);
SEARCH_RESULTS_THUMBNAIL_SHORTS_HEIGHT && GM.addStyle(`.yt-lockup-view-model-wiz__content-image , ytd-search .yt-horizontal-list-renderer .yt-core-image--content-mode-scale-aspect-fill { object-fit: contain; }
.yt-lockup-view-model-wiz__content-image , ytd-search .yt-horizontal-list-renderer ytd-thumbnail.ytd-reel-item-renderer{max-height:${SEARCH_RESULTS_THUMBNAIL_SHORTS_HEIGHT}em !important;}`);
//URLの変化を監視
var href = location.href;
var observer = new MutationObserver(function(mutations) {
if (href !== location.href) {
href = location.href;
$('#playAllButton , #instantPlaylistButton').remove();
//elegeta('#playAllButton , #instantPlaylistButton').forEach(e => e?.remove());
elegeta('.yzqAttract , .yzqAttract2').forEach(e => e.classList.remove("yzqAttract"))
clearInterval(GF?.hideShort);
clearInterval(GF?.playAllCount)
GF.wari = 0
GF.sousa = Date.now()
setTimeout(() => {
GF.lastinner = "";
run()
}, 1500);
}
});
observer.observe(document, { childList: true, subtree: true });
setTimeout(() => { run(); }, 1009);
HIDE_SUGGEST && GM.addStyle(`ytd-search ytd-shelf-renderer,ytd-search ytd-horizontal-card-list-renderer{display:none !important;}`)
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);
/*
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} <span style="float:right">(${c.id})</span>`).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
}
*/
// youtube検索結果画面で「ショート」や「他の人はこちらも視聴しています」類の見出しをクリックでその動画を隠したり出したり
GM.addStyle('h2.style-scope.ytd-reel-shelf-renderer,h2.style-scope.ytd-shelf-renderer,div#title-text.style-scope.ytd-rich-list-header-renderer{cursor:pointer;}')
GM.addStyle('.hiddenInstance{text-decoration:underline overline line-through;}')
GM.addStyle('.zenqhidevideo{display:none !important; transition:all 0.5s;}')
let hiddenTitle = new Set()
document.addEventListener("mousedown", e => {
if (e.button === 0 && lh(/^https:\/\/www\.youtube\.com\/results\?search_query=/) && (e?.target?.matches('h2.style-scope.ytd-reel-shelf-renderer') || e?.target?.closest('h2.style-scope.ytd-reel-shelf-renderer,h2.style-scope.ytd-shelf-renderer,div#title-text.style-scope.ytd-rich-list-header-renderer'))) {
e.preventDefault();
e.stopPropagation();
if (e?.ctrlKey) { // Ctrl+左クリック:棚とショートをすべて消す
hideShelfShort()
} else { // 左クリック:その棚だけ隠したり出したり
let reelinner = e?.target?.closest('ytd-reel-shelf-renderer.style-scope.ytd-item-section-renderer,ytd-shelf-renderer.style-scope.ytd-item-section-renderer,ytd-horizontal-card-list-renderer.style-scope.ytd-item-section-renderer')?.querySelector('div#contents.style-scope.ytd-reel-shelf-renderer,ytd-vertical-list-renderer.style-scope.ytd-shelf-renderer,div#items.style-scope.ytd-horizontal-card-list-renderer,div#scroll-outer-container.style-scope.yt-horizontal-list-renderer')
if (!hiddenTitle.has(e.target)) { //.dataset.hide=((e.target?.dataset?.hide||0)+1)%2;alert(e.target.dataset.hide);
hiddenTitle.add(e.target);
$(reelinner).hide(111)
} else {
hiddenTitle.delete(e.target);
$(reelinner).show(111) //toggleClass("hiddenInnerInstance").toggle(111)
}
$(e?.target?.closest('span#title,yt-formatted-string#title.style-scope.ytd-rich-list-header-renderer')).toggleClass("hiddenInstance")
}
return false;
}
})
document.addEventListener("dblclick", e => {
if (e.button === 0 && lh(/^https:\/\/www\.youtube\.com\//) && !lh(/^https:\/\/www\.youtube\.com\/watch/) && e?.target?.matches('div#container.style-scope.ytd-search , div#container.style-scope.ytd-masthead , div#contentContainer.style-scope.tp-yt-app-drawer , div#guide-content.style-scope.ytd-app , ytd-two-column-search-results-renderer.style-scope.ytd-search')) {
e.preventDefault();
e.stopPropagation();
hideShelfShort()
return false;
}
})
function hideShelfShort() {
GF.wari = 1 - (GF?.wari || 0);
popupCenter(`Shortsを${GF.wari?"すべて隠します":"表示します"}`, !GF.wari ? "#888" : "#35a")
let css = `:is(ytd-reel-shelf-renderer.style-scope.ytd-item-section-renderer,ytd-shelf-renderer.style-scope.ytd-item-section-renderer,ytd-horizontal-card-list-renderer.style-scope.ytd-item-section-renderer) :is(div#contents.style-scope.ytd-reel-shelf-renderer,ytd-vertical-list-renderer.style-scope.ytd-shelf-renderer,div#items.style-scope.ytd-horizontal-card-list-renderer,div#scroll-outer-container.style-scope.yt-horizontal-list-renderer){display:none !important; } h2.style-scope.ytd-reel-shelf-renderer,h2.style-scope.ytd-shelf-renderer,div#title-text.style-scope.ytd-rich-list-header-renderer{opacity:0.5;}`
GF.wari ? addstyle.add(css) : addstyle.remove(css);
function popupCenter(text, bgcolor = text == "解除" ? "#888" : "#35a") {
if (!text) return
text = String(text).replace(/&/g, "&").replace(/"/g, """).replace(/'/g, "'").replace(/`/g, '`').replace(/</g, "<").replace(/>/g, ">").replace(/\n/gm, "<br>")
let e = end(document.body, `<span class="gkscbox" style="all:initial; position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); opacity:1; z-index:2147483647; font-weight:bold; margin:0px 1px; text-decoration:none !important; padding:2em 3em; border-radius:12px; background-color:${bgcolor}; color:white; " >${text}</span>`)
/*e.onclick = v => {
GM.setClipboard(v.target.innerText);
v.target.innerText = `「${v.target.innerText}」をクリップボードにコピーしました`
}*/
$(e).hide(0).fadeIn(155, (function(e) { return function() { setTimeout(() => { $(e).fadeOut(155, () => $(e).remove()) }, 222) } })(e))
}
if (GF.wari) {
clearInterval(GF.hideShort);
GF.hideShort = setInterval(() => {
if (document.visibilityState != "visible" || lh("/watch")) return;
elegeta(':is(ytd-video-renderer.style-scope.ytd-item-section-renderer , ytd-rich-item-renderer):not(.zenqhidevideo)').filter(e => eleget0('ytd-thumbnail-overlay-time-status-renderer[overlay-style="SHORTS"]', e))
.concat(elegeta('ytd-rich-item-renderer.style-scope.ytd-rich-shelf-renderer'))
.forEach(e => {
(GF.wari) ? e.classList.add('zenqhidevideo'): e.classList.remove('zenqhidevideo');
});
}, 999);
} else {
clearInterval(GF.hideShort);
elegeta(':is(ytd-video-renderer.style-scope.ytd-item-section-renderer , ytd-rich-item-renderer)').filter(e => eleget0('ytd-thumbnail-overlay-time-status-renderer[overlay-style="SHORTS"]', e))
.concat(elegeta('ytd-rich-item-renderer.style-scope.ytd-rich-shelf-renderer'))
.forEach(e => {
(GF.wari) ? e.classList.add('zenqhidevideo'): e.classList.remove('zenqhidevideo');
});
}
}
hoverHelp(e => e?.matches('h2.style-scope.ytd-reel-shelf-renderer') || e?.closest('h2.style-scope.ytd-reel-shelf-renderer,h2.style-scope.ytd-shelf-renderer,div#title-text.style-scope.ytd-rich-list-header-renderer') ? "クリック:隠す/再表示<br>Ctrl+クリック:この類の棚をすべて隠す/再表示" : "")
function hoverHelp(cb) {
let latest, helpEle;
document.addEventListener("mousemove", e => {
if (!e?.target) return;
if (latest != e?.target) {
helpEle?.remove()
const text = cb(e.target)
if (text) helpEle = end(document.body, `<div id="hoverHelpPopup" style="z-index:999999999; bottom:1em; right:1em; position:fixed; background-color:#ffffffe0; padding:1px 0.5em; border:1px solid #505050; font-size:15px; color:#505050; border-radius:0.75em;">${text}</div>`)
latest = e.target
if (document.elementFromPoint(e.clientX, e.clientY) == helpEle) {
helpEle.style.bottom = "";
helpEle.style.top = "1em";
}
}
})
}
GM.addStyle('.boxatt{ background-color:#efe !important; animation: pulse 1s 1; } @keyframes pulse { 0% { box-shadow: 0 0 0 0 #00ff88f0; } 100% { box-shadow: 0 0 10px 35px #ffffff00; } } .yenClickHighlight {outline: rgba(0, 255,128,0.7) solid 4px !important; }')
function boxatt(e) {
[e].flat().forEach(v => {
v.classList.add("boxatt")
setTimeout(() => v.classList.remove("boxatt"), 1000)
})
}
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 = eleget0(`${IPBOX}:hover`)
if (box && USE_INSTANT_PLAYLIST) { //IP先頭用に独自キューを覚えておく
var href = eleget0(':is(a[href*="/watch"],a[href*="/shorts/"])', 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)]
eleget0('#instantPlaylistButton').innerHTML = `Instant<BR>Playlist (${equeueIP.length}+)`;
if (USE_INSTANT_PLAYLIST) boxatt([box, eleget0('#instantPlaylistButton')])
}
}
var ancestorEle = eleget0(`${QBOX}:hover`)
if (!ancestorEle) return false
let menuButton = eleget0(MENUB, ancestorEle) // …ボタン
if (menuButton) {
setTimeout(() => { menuButton?.click() }, 0);
setTimeout(() => QB(ancestorEle)?.click(), 400)
if (!USE_INSTANT_PLAYLIST) boxatt([ancestorEle, eleget0('#instantPlaylistButton')])
}
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:: Ctrl+x:: 視聴中の再生リストを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 eles = elegeta('//ytd-playlist-panel-video-renderer[@id="playlist-items"]/a:visible').filter(e => e?.closest('ytd-playlist-panel-video-renderer')?.style?.opacity != 0.5);
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 = gettotal(50)
playtimesum = playtimesum ? " " + playtimesum : ""
let pl = location.href.match0(/\&list=((?:PL|UU)[a-zA-Z0-9_\-]+)/); //let pl = location.href.match0(/\&list=((?:PL|UU|UL)[a-zA-Z0-9_\-]+)/);
var cb = kaisuu == 2 ? `<div>\n<a rel="noopener noreferrer" href="${IPURL}${ (videoIDaAll?.slice(0,50)?.join(","))}">${sani(eleget0('span#video-title.style-scope.ytd-playlist-panel-video-renderer')?.textContent?.replace(/\n/gm," ")?.trim())} (${videoIDa?.length})${playtimesum?" "+playtimesum:""}</a><br>\n<iframe referrerpolicy="no-referrer" src="https://www.youtube.com/embed/${videoIDa[0]}?playlist=${ videoIDa.join(",")}" id="ytplayer" type="text/html" allowfullscreen allow="picture-in-picture" ${YOUTUBE_WATCH_ALTC_EMBED_PLAYER_SIZE} frameborder="0"></iframe>\n</div>\n\n` +
(pl ? `<div>\n<a rel="noopener noreferrer" href="https://www.youtube.com/watch?v=${location?.href?.match0(/\?v=([a-zA-Z0-9_\-]{11})/)||""}&list=${pl}">${sani(eleget0('yt-formatted-string.title.style-scope.ytd-playlist-panel-renderer.complex-string')?.textContent?.replace(/\s+|\n/gm," ")?.trim()||"")}</a><br>\n<iframe referrerpolicy="no-referrer" src="https://www.youtube.com/embed/${location?.href?.match0(/\?v=([a-zA-Z0-9_\-]{11})/)||""}?listType=playlist&list=${pl}" id="ytplayer" type="text/html" allowfullscreen allow="picture-in-picture" ${YOUTUBE_WATCH_ALTC_EMBED_PLAYER_SIZE} frameborder="0"></iframe>\n</div>` : "") :
//var cb = kaisuu == 2 ? `<div>\n<a rel="noopener noreferrer" href="${IPURL}${ (videoIDaAll?.slice(0,50)?.join(","))}">${sani(eleget0('span#video-title.style-scope.ytd-playlist-panel-video-renderer')?.textContent?.replace(/\n/gm," ")?.trim())} (${videoIDa?.length})${playtimesum?" "+playtimesum:""}</a><br>\n<iframe referrerpolicy="no-referrer" src="https://www.youtube.com/embed/${videoIDa[0]}?playlist=${ videoIDa.join(",")}" id="ytplayer" type="text/html" allowfullscreen allow="picture-in-picture" ${YOUTUBE_WATCH_ALTC_EMBED_PLAYER_SIZE} frameborder="0"></iframe>\n</div>\n\n` + (pl ? `<div>\n<a rel="noopener noreferrer" href="https://www.youtube.com/playlist?list=${pl}">${sani(eleget0('yt-formatted-string.title.style-scope.ytd-playlist-panel-renderer.complex-string')?.textContent?.replace(/\s+|\n/gm," ")?.trim()||"")}</a><br>\n<iframe referrerpolicy="no-referrer" src="https://www.youtube.com/embed?listType=playlist&list=${pl}" id="ytplayer" type="text/html" allowfullscreen allow="picture-in-picture" ${YOUTUBE_WATCH_ALTC_EMBED_PLAYER_SIZE} frameborder="0"></iframe>\n</div>` : "") :
// "https://www.youtube.com/watch_videos?video_ids=" + videoIDa.join(",") + indexUrlQP; // h181px~:hd thumbnail + "&cc_load_policy=1&cc_lang_pref=jpn"
`${IPURL}${videoIDa.join(",") + indexUrlQP}`; // h181px~:hd thumbnail + "&cc_load_policy=1&cc_lang_pref=jpn"
//var embedHTML = `<iframe referrerpolicy="no-referrer" src="${cb}" id="ytplayer" type="text/html" width=640 height=360 frameborder=0 allowfullscreen>`; // h181px~:hd thumbnail
var cb2 = cb
var cbEsc = (cb2).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"').replace(/'/g, ''')
var enumText = ``; // 50件ごとに分割(使わない)
for (let u = 0; u < videoIDaAll.length / 50; u++) {
enumText += `▶ ${u*50+1}/${videoIDaAll.length} ${videoIDaAll.slice(u*50,u*50+3).join(",")+(videoIDaAll.slice(u*50,u*50+50).length>3?",…":"")}\n${IPURL}${ (videoIDaAll.slice(u*50, u*50+50).join(",") ) }\n`
}
let transratedTitle = (eleget0('//h1[@class=\"title style-scope ytd-video-primary-info-renderer\"]/yt-formatted-string/font/font|//div[@id=\"title\" and contains(@class,\"style-scope ytd-watch-metadata\")]/h1/yt-formatted-string/font/font')?.innerText?.replace(/(?!= - YouTube)$/, " - YouTube")) || document.title
var uploader = eleget0('div.ytd-channel-name > yt-formatted-string.ytd-channel-name.complex-string[dir="auto"] > a.yt-formatted-string')?.textContent?.trim() || ""
if (!PRESERVE_INDEX) {
let firstVideo = eleget0('ytd-playlist-panel-video-renderer.ytd-playlist-panel-renderer:nth-child(1) > a.yt-simple-endpoint > div#container.ytd-playlist-panel-video-renderer > div.editable:last-child > h4 > span.style-scope')?.textContent?.trim()
transratedTitle = firstVideo ? firstVideo + " - YouTube" : transratedTitle;
var uploader = eleget0('//div[@id="items" and @class="playlist-items style-scope ytd-playlist-panel-renderer"]/ytd-playlist-panel-video-renderer[1]/a[contains(@class,"yt-simple-endpoint style-scope ytd-playlist-panel-video-renderer")]/div[@id="container"]/div[@id="meta"]/div[@id="byline-container"]/span')?.textContent?.trim() || ""
}
let desc = indexNo !== 0 ? "" : sani((eleget0('ytd-text-inline-expander#description-inline-expander.style-scope.ytd-watch-metadata')?.innerText || "")?.replace(/\n+|\s+/gm, " ")?.slice(0, 60)?.trim())
if (desc) desc = ` - ${desc}`
var title = `▶ ${PRESERVE_INDEX?indexNo+1:1}/${videoIDa.length} ${transratedTitle} - ${uploader}${desc}`;
if (disp) {
if (videoIDaAll.length <= 50) {
popup(kaisuu == 2 ? cb : transratedTitle + "\n" + cb, kaisuu == 2 ? "#822" : undefined, "right:0em; top:0em;max-width:40%;")
GM.setClipboard(kaisuu == 2 ? cb : transratedTitle + "\n" + cb2 + "\n");
} else {
popup(kaisuu == 2 ? cb : transratedTitle + "\n" + enumText, kaisuu == 2 ? "#822" : undefined, "right:0em; top:0em;max-width:40%;")
GM.setClipboard(kaisuu == 2 ? cb : transratedTitle + "\n" + enumText + "\n");
}
if (kaisuu != 2) $(`<div style="margin-left:4em;font-size:14px;" id="link4bm">${kaisuu==2?"埋め込み":"ブックマーク"}用リンク(${videoIDa.length})${playtimesum}<br><a href=${cb}>${title}</a></div>`).hide(0).insertAfter($('#logo')).on("click", () => pauseVideo()).show(150).delay(9999).hide(250, function() { $(this).remove() })
}
} else if (kaisuu == 1) { // ただの列挙
list = [...new Set(elegeta('//h4[@class=\"style-scope ytd-playlist-panel-video-renderer\"]/span[@id=\"video-title\"]:visible').filter(e => e?.closest('ytd-playlist-panel-video-renderer')?.style?.opacity != 0.5).map(e => { return e.textContent.trim() + " - YouTube\n" + e.closest('a').href.trim().replace(/&.*/, "") + "\n" }).map(a => JSON.stringify(a)))].map(a => JSON.parse(a)) //.join("")
if (disp) {
popup(list.join(""), "#303060") //popup(`(${list?.length})\n`+list.join(""), "#303060")
GM.setClipboard(list.join("") + "");
}
return list
}
}
return cb
} else { //キューやプレイリスト再生状態ではない
return;
}
}
function urlExtractAndConcat(option = "", ini = "", inilen = 0) { // url Extract & Concat
let cb = ini || makeUrlFromCuelist(0)
var inp = prompt(`${option=="shuffle"?"<シャッフル>\n\n":""}url Extract & Concat:\n複数のYouTubeの動画URLから連続再生URLを作ってクリップボードにコピーします\nYouTubeの動画URLを何行でも貼り付けてください\nYouTubeのURLになっていない行や文字列は全て読み飛ばされ、重複した動画は削除されます\n${ini?`\n${inilen} 個の今のページにあるYouTube動画URLが初期値として入力済みです\nこれを利用して前後に追加することも、削除して新しく入力することもできます\n\n${cb}\n`:cb?`\n今視聴中のキュー/プレイリストの動画が初期値として入力済みです\nこれを利用して前後に追加することも、削除して新しく入力することもできます\n\n${cb}\n`:""}\n対応書式:\nhttps://www.youtube.com/watch?v=動画ID\nhttps://www.youtube.com/shorts/動画ID\nhttps://youtu.be/動画ID\nhttps://www.youtube.com/watch_videos?video_ids=動画ID,動画ID,…\n\n`, cb ? ` ${cb} ` : "")
if (inp) {
var urlcap = [];
inp.split(/\s/).forEach(v => { urlcap = urlcap.concat(...[...v?.matchAll(/^(?:h?t?tps?:\/\/)?(?:m\.|www\.|)?youtube\.com\/(?:shorts\/|watch\?v=|embed\/|live\/)([a-zA-Z0-9_\-]{11})(?![a-zA-Z0-9_\-]{1})|^(?:h?t?tps?:\/\/)?youtu\.be\/([a-zA-Z0-9_\-]{11})(?![a-zA-Z0-9_\-]{1})|^(?:h?t?tps?:\/\/)?www\.youtube\.com\/(?:watch_videos\?video_ids=|embed\/\?playlist=)([a-zA-Z0-9_\-,]{11,600})/gmi)].map(c => c.slice(1, 999))).filter(w => w) }) // 書式が混在していても登場順に収納する
inp = null;
if (urlcap?.length) {
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)]; // 重複削除
if (option == "shuffle") urla2 = shuffle(urla2); // シャッフル
let urllen2 = urla2.length;
let urla3 = [...urla2].slice(0, 50); // 50件まで
let urlenum = urla3.join(",")
let url = `${IPURL}${urlenum}`
if (urla3 && urla3.length) {
var title = `▶ (${urla3.length}) ${urla3.slice(0,3).join(",")+(urla3.length>3?",…":"")}\n`
var enumText = ``
var enumUrl = []
for (let u = 0; u < urla2.length / 50; u++) {
enumText += `▶ ${u*50+1}/${urla2.length} ${urla2.slice(u*50,u*50+3).join(",")+(urla2.slice(u*50,u*50+50).length>3?",…":"")}\n${IPURL}${ (urla2.slice(u*50, u*50+50).join(",") ) }\n`
enumUrl.push(`${IPURL}${ (urla2.slice(u*50, u*50+50).join(",") ) }`)
}
let con = USE_INSTANT_PLAYLIST == 1 ? confirm(`${urllen}件の動画IDを抽出しました\n${urllen-urllen2}件の重複を削除しました\n\n下記(${urla2.length}件)をクリップボードにコピーしますか?\n\n${enumText}`) : 1;
if (con) {
if (ld("youtube.com") && eleget0("#logo")) $(`<div style="margin-left:4em;font-size:14px;" id="link4bm">${kaisuu==2?"埋め込み":"ブックマーク"}用リンク(${urla3.length})<br><a title="Shift+左クリック:以下をすべて開く(${urllen2})\n\n${enumUrl.join("\n\n")}" data-urls="${escape(JS(enumUrl))}" href=${url}>${title}</a></div>`).hide(0).insertAfter($('#logo')).show(150).delay(9999).hide(250, function() { $(this).remove() })
$('#link4bm').on("click", e => {
if (e.shiftKey) {
e.preventDefault()
e.stopPropagation()
openUrls(e.target.dataset.urls)
return [url, enumUrl, urllen2];
}
})
GM.setClipboard(enumText)
popup(enumText, undefined, "right:0em; top:0em;max-width:40%;")
return [url, enumUrl, urllen2];
}
}
}
} else return ["", [], 0];
}
function openUrls(urls) {
pauseVideo()
var enumURLsa = JP(unescape(urls))
enumURLsa.forEach((v, i) => { setTimeout(() => i == 0 ? GM.openInTab(v) : GM.openInTab(v, true), i * 5000) }) // Shift+左クリック
}
function hideSuggest() {
if (HIDE_SUGGEST && location.href.indexOf('www.youtube.com/results?') !== -1) {
[
// ['ytd-shelf-renderer,ytd-horizontal-card-list-renderer', // 縦1列「○○の最新の動画をお見逃しなく」「他の人はこちらも検索」
'#contents>ytd-horizontal-card-list-renderer', // 縦1列「他の人はこちらも検索」
'ytd-shelf-renderer', // 縦1列「他の人はこちらも視聴しています」「あなたへのおすすめ」「~の最新の動画をお見逃しなく」「関連する検索から」
//`//span[text()="他の人はこちらのショート動画も視聴しています"]/ancestor::ytd-reel-shelf-renderer`, // 「他の人はこちらのショート動画も視聴しています」
`ytd-reel-shelf-renderer:text*=他の人はこちらのショート動画も視聴しています`, // 「他の人はこちらのショート動画も視聴しています」
].forEach(xp => {
$(elegeta(xp)).css({ "outline": "6px dashed #f00" }).hide(HIDE_SUGGEST, function() { $(this).remove() }); // 検索結果に割り込むサジェストを隠す
});
}
}
function run(node = document) {
if (lh(/\/\/www\.youtube\.com\/(?:\?bp=.*)?$/) ||
location.href.match(/https:\/\/www\.youtube\.com\/results\?.*(q=|search_query=)/) || location.href.match(/https:\/\/www\.youtube\.com\/results\?.*(q=|search_query=)/) ||
location.href.match(/\/\/www\.youtube\.com\/(?:channel\/|c\/|user\/|@)[^\/]+\/search/) ||
(location.href.match(/\/\/www\.youtube\.com\/(?:channel\/|c\/|user\/|@)[^\/]+/) && !(location.href.match("/community|/channels|/about|/playlists"))) ||
location.href.match("//www.youtube.com/playlist") ||
location.href.match("//www.youtube.com/hashtag/") ||
location.href.match("//www.youtube.com/watch")) {
var place = eleget0('//div[@id="center" and @class="style-scope ytd-masthead"]');
} else return;
if (location.href.match("//www.youtube.com/hashtag/")) document.title = `${et('h1.dynamic-text-view-model-wiz__h1 > span')} ${et('yt-content-metadata-view-model.page-header-view-model-wiz__page-header-content-metadata')} - YouTube`;
if (place) {
addstyle.add(`.yzqAttract {
outline:4px solid #80f8;
background:#80f1;
}
.yzqAttract2 {
outline:4px solid #02f8;
background:#02f1;
}`) // box-shadow:0px 0px 4px 4px #92f, inset 0 0 100px #fe2;
// instant Playlist Button ipl::
if (USE_INSTANT_PLAYLIST) {
$('#instantPlaylistButton , #extractAndConcatButton').remove();
//$('ytd-topbar-logo-renderer#logo:not([data-rcli])').on("contextmenu", () => { return false; }); // ytアイコン右クリック
elegeta('ytd-topbar-logo-renderer#logo:not([data-rcli])').forEach(e => e?.addEventListener("contextmenu", e => {
e?.stopPropagation();
e?.preventDefault();
return false;
}, true)); // ytアイコン右クリック
var e = eleget0('ytd-topbar-logo-renderer#logo:not([data-rcli])')
if (e) {
e.dataset.rcli = 1;
e?.addEventListener("mouseup", e => {
if (e.button != 2) return;
if (e.ctrlKey || Date.now() - GF?.logocli > 400) { // ytロゴ右長押しかCtrl+ytロゴ右クリック
urlExtractAndConcat("shuffle")
} else {
urlExtractAndConcat();
}
return false;
})
e?.addEventListener("mousedown", e => {
if (e.button != 2) return;
GF.logocli = Date.now()
})
}
var instantPlaylistButton = after(place, `<span class="ignoreMe" style="cursor:pointer;color:var(--yt-spec-icon-active-other); text-align:center; font-size:15px;" id="instantPlaylistButton">Instant<br>Playlist${equeueIP.length?" ("+equeueIP.length+"+)":""}</span>`);
instantPlaylistButton?.addEventListener("contextmenu", e => {
e?.stopPropagation();
e?.preventDefault();
return false;
}, true)
instantPlaylistButton?.addEventListener("mousedown", e => {
if (e.button == 0) playAllG(e);
if (e.button == 2) playAllG(e, "shuffle");
return false;
});
// instantPlaylistButton?.addEventListener("mouseenter", e => itemIPL().forEach(e => e.classList.add("yzqAttract2")))
instantPlaylistButton?.addEventListener("mouseenter", e => itemIPL().forEach(e => (e?.closest(IPBOX) || e)?.classList?.add("yzqAttract2")))
instantPlaylistButton?.addEventListener("mouseleave", e => elegeta('.yzqAttract2').forEach(e => e.classList.remove("yzqAttract2")))
instantPlaylistButton?.addEventListener("mousemove", e => {
var [url, len, lenmax, enumURLsa] = getUrla();
instantPlaylistButton.title = `クリックで画面に出ている動画を限定公開プレイリストにして再生 (${len}/${lenmax})\n(右クリックだとシャッフル、Ctrl+で新しいタブで開く、Shift+で51件以上も分割して開く)\nGenerate playlist from all displayed videos and open instantly (right-click to shuffle)\n\n${enumURLsa?.join("\n\n")}`;
return false;
});
}
// play all button pa::
if (USE_PLAYALL) {
$('#playAllButton').remove();
var playAllButton = after(place, '<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)\nCtrl+だと再生を始めない" id="playAllButton">Play All</span>')
playAllButton?.addEventListener("mouseenter", e => elegeta(`${QBOX}:visible`).forEach(e => e.classList.add("yzqAttract")))
playAllButton?.addEventListener("mouseleave", e => elegeta('.yzqAttract').forEach(e => e.classList.remove("yzqAttract")))
playAllButton?.addEventListener("contextmenu", e => {
e?.stopPropagation();
e?.preventDefault();
return false;
}, true)
playAllButton.addEventListener("mousedown", e => {
e.stopPropagation();
e.preventDefault();
setTimeout(() => playAll(e?.button != 0 ? "shuffle" : "", e), 20);
return false;
});
clearInterval(GF?.playAllCount)
GF.playAllCount = setInterval(() => {
if (Date.now() - GF?.sousa > 15000) return; // 15秒以上スクロールしてなければしない
let qb = QBOXlen() //elegeta(`${QBOX}:visible`)
let mb = MENUBlen() //qb.filter(e=>eleget0(`${MENUB} svg`, e)).length
//DEBUG && $(item()).css({ "outline": "3px dotted #80f" })
//DEBUG && $(itemWhole()).css({ "outline": "3px dotted #f0f" })
let size = qb == mb ? mb : `${mb}/${qb}`;
GF.inner = `Play All<br>(${size})${GF?.PAadd||""}${DEBUG ? "<br>wait:" + wait : ""}`
if (GF.lastinner != GF.inner) playAllButton.innerHTML = GF.inner;
GF.lastinner = GF.inner;
}, 1000);
//}
}
}
}
function playAllG(e, option = false) {
requestAnimationFrame(pauseVideo);
var [url, len, lenmax, enumURLsa] = getUrla(option)
if (len > 0)
if (USE_INSTANT_PLAYLIST == 2 || (USE_INSTANT_PLAYLIST == 1 && confirm(`下記を開きます。よろしいですか?` + (e.shiftKey ? `(${lenmax})\n\n${enumURLsa.join("\n\n")}` : `(${len})\n\n${url}`)))) {
if (e.shiftKey) {
enumURLsa.forEach((v, i) => { setTimeout(() => i == 0 ? GM.openInTab(v) : GM.openInTab(v, true), i * 5000) }) // Shift+左クリック
} else
if (e.ctrlKey) { GM.openInTab(url) } else { location.href = url }
}
}
function debugEle(ele, col = "random", additionalInfo = "") {
if (ele && (DEBUG || col.indexOf("forced") !== -1)) {
if (col.indexOf("random") !== -1) col = '#' + (0x1000000 + (Math.random()) * 0xffffff).toString(16).substr(1, 6);
// if (col.indexOf("random") !== -1) col = '#' + ("000".map(c=>"89abcdef"[Math.random()*8]));
//ele.style.outline = "3px dotted " + col;
ele.style.boxShadow = " 0px 0px 4px 4px " + col + "30, inset 0 0 100px " + col + "20";
ele.dataset.yododebugele = ""
//ele.outerHTML+=additionalInfo;
}
}
function getUrla(option) {
let videoEle = itemIPL()
if (videoEle.length) {
DEBUG && videoEle.forEach(e => debugEle(e, "#8000ff")) //$(videoEle).css({"outline":"3px dotted #84f"})
if (lh("youtube.com/playlist")) videoEle = videoEle.filter(e => !eleget0('//div/h2/span[@class="style-scope ytd-shelf-renderer" and contains(text(),"おすすめのプレイリスト")]', e.closest(`ytd-item-section-renderer`))) // プレイリスト画面の下に出るおすすめプレイリストを除外
var videoIDa = [...new Set(videoEle.map(v => v.href.match0(/\/watch\?v=([a-zA-Z0-9_\-]{11})/) || v.href.match0(/\/shorts\/([a-zA-Z0-9_\-]{11})/)).filter(v => v))]
var videoIDaMax = [...new Set([...equeueIP, ...((option === "shuffle") ? shuffle(videoIDa) : videoIDa)])]
if (videoIDa.length) {
videoIDa = ((option === "shuffle") ? shuffle(videoIDa) : videoIDa)
var videoIDa = [...new Set([...equeueIP, ...videoIDa])].slice(0, 50)
let url = `${IPURL}${videoIDa.join(",")}`
var enumURLsa = []
for (let u = 0; u < videoIDaMax.length / 50; u++) {
enumURLsa.push(`${IPURL}${ (videoIDaMax.slice(u*50, u*50+50).join(",") ) }`)
}
return [url, videoIDa.length, videoIDaMax.length, enumURLsa]
}
} else return ["", 0, 0];
}
function pauseVideo() {
let e = eleget0('video');
// if (e) { e.pause(); } else { setTimeout(pauseVideo, 17) }
if (e) { e.pause(); } else { requestAnimationFrame(pauseVideo) }
}
async function playAll(option = false, ev) {
if (WAIT_LOADING) {
if (QBOXlen() > MENUBlen()) GF.PAadd = "<br>Wait..." //⏳
await waitTrue(() => QBOXlen() == MENUBlen(), 20000)
GF.PAadd = ""
}
pauseVideo();
/*
// eでQを入れた要素を重複回避のため消す
var box = elegeta(`${QBOX}:visible`).filter(v => {
var href = eleget0('a', v)?.href;
var vID = href?.match0(/\?v=([a-zA-Z0-9_\-]{11})/) || href?.match0(/\/shorts\/([a-zA-Z0-9_\-]{11})/);
return equeue.includes(vID)
}).forEach(v => v.remove()); //マウスが乗っている動画の枠
*/
let d = 0;
let videoEle = elegeta(`${QBOX}:visible`)
videoEle = videoEle.filter(v => elegeta(`a`, v).every(v => !equeue.includes(v?.href?.match0(/\?v=([a-zA-Z0-9_\-]{11})/) || v?.href?.match0(/\/shorts\/([a-zA-Z0-9_\-]{11})/)))) // eでQを入れた要素を重複回避のため消す
let videoLength = videoEle.length
let i = 0;
for (let e of (option == "shuffle" ? shuffle(videoEle) : videoEle)) { //console.log(wait,videoLength)
eleget0(`${MENUB}`, e)?.click();
await sleep(wait / 2)
QB(e)?.click();
await sleep(wait + (videoLength / 20))
if (ev.ctrlKey) { //IP先頭用に独自キューを覚えておく
// var href = eleget0('a', e?.closest('ytd-compact-video-renderer.style-scope.ytd-watch-next-secondary-results-renderer,ytd-video-renderer,ytd-rich-item-renderer,ytd-grid-video-renderer,ytd-playlist-video-renderer'))?.href
var href = eleget0('a', e)?.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<BR>Playlist (${equeueIP.length}+)`)
}
}
if ((i++) >= 199) break; // キューは200件までしか入らないので時間節約
}
await sleep(200 + wait) //d += 100 + wait * Math.min(7000, Math.max(2000, waitLast)) / 1000 + videoLength / 3;
if (!(location.href.match(/\/watch\?v=/))) {
if (!ev.ctrlKey) {
await waitcli('//div[contains(@class,"ytp-miniplayer-play-button-container")]/button|//button[contains(@class,"ytp-play-button-playlist")]');
await waitcli('//div/button[contains(@class,"ytp-miniplayer-expand-watch-page-button")]:visible')
equeueIP = []
equeue = []
}
} else {
await sleep(100 + wait); //d += 100 + wait;
eleget0('video')?.play(); //let e = eleget0('//video'); if (e) { e.play(); } //setTimeout(() => { let e = eleget0('//video'); if (e) { e.play(); } }, d);
}
}
function sleep(ms) { return new Promise(resolve => setTimeout(resolve, ms)) }
async function waitcli(sel) {
await waitTrue(() => eleget0(sel))
eleget0(sel)?.click()
}
function waitTrue(testfunc, timeout = 5000) {
let interval;
return Promise.race([new Promise(resolve => setTimeout(() => {
clearInterval(interval);
resolve();
}, timeout)), new Promise(resolve => {
interval = setInterval(() => {
if (testfunc()) {
clearInterval(interval);
resolve();
}
}, 100);
})]);
}
function shuffle(array) {
return array.map(a => ({ rnd: Math.random(), val: a })).sort((a, b) => a.rnd - b.rnd).map(a => a.val);
}
function cli(xpath, wait, mode = "", cb = null) { // mode: infinity:押せるまで監視し続ける
setTimeout(() => {
let ele = eleget0(xpath);
if (ele) { ele.click(); if (cb) cb(); } else if (mode === "infinity") { cli(xpath, 17, mode) }
}, wait);
if (eleget0(xpath)) { return true } else { return false }
}
function et(x) {
return eleget0(x)?.textContent?.trim() || "";
}
function elegeta(xpath, node = document) {
if (!xpath || !node) return [];
let xpath2 = xpath.replace(/:inscreen|:visible|:text\*=[^:]*/g, "") // text*=~中で:は使えない
let array = []
try {
if (!/^\.?\//.test(xpath)) {
array = [...node.querySelectorAll(xpath2)]
} else {
var snap = node.evaluate("." + xpath2, node, null, XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null)
let l = snap.snapshotLength
for (var i = 0; i < l; i++) array[i] = snap.snapshotItem(i)
}
if (/:visible/.test(xpath)) array = array.filter(e => e.offsetHeight)
if (/:inscreen/.test(xpath)) array = array.filter(e => { var eler = e.getBoundingClientRect(); return (eler.bottom >= 0 && eler.right >= 0 && eler.left <= document.documentElement.clientWidth && eler.top <= document.documentElement.clientHeight) }) // 画面内に1ピクセルでも入っている
if (/:text\*=./.test(xpath)) { let text = xpath.replace(/^.*:text\*=([^:]*)$/, "$1"); if (text) array = array.filter(e => new RegExp(text).test(e?.textContent)) }
} catch (e) { alert(`XPath/CSS構文にエラーがあるかもしれません\n2023/12以前にインストールしたFirefoxを使っている場合はabout:configでlayout.css.has-selector.enabled を true にすると解決するかもしれません\n\n${e}\n\n${xpath}`); return []; }
//} catch (e) { alert(e); return []; }
return array
}
function eleget0(xpath, node = document) {
if (!xpath || !node) return null;
if (/:inscreen|:visible|:text\*=/.test(xpath)) return elegeta(xpath, node)?.shift();
if (!/^\.?\//.test(xpath)) return node.querySelector(xpath);
try {
var ele = document.evaluate("." + xpath, node, null, XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null);
return ele.snapshotLength > 0 ? ele.snapshotItem(0) : null;
} catch (e) { alert(`XPath/CSS構文にエラーがあるかもしれません\n2023/12以前にインストールしたFirefoxを使っている場合はabout:configでlayout.css.has-selector.enabled を true にすると解決するかもしれません\n\n${e}\n\n${xpath}`); return null; }
// } catch (e) { alert(e + "\n" + xpath + "\n" + JSON.stringify(node)); 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 = "#6080ff", additionalStyle = "right:0em; top:0em;") {
text = "" + text
var e = document.getElementById("cccboxaq");
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>")
//alert(bgcolor)//bgcolor = bgcolor || (/www\.translatetheweb\.com|\.translate\.goog\/|translate\.google\.com|\/embed\//gmi.test(location.href + " " + text) ? "#822" : "#6080ff");
document.body.insertAdjacentHTML("beforeend", `<span id="cccboxaq" class="${cID}" style="all:initial; max-height:100vh; overflow-y:auto; scrollbar-width:thin; 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() { return function() { elegeta(`.${cID}`).forEach(e => e?.remove()) } })(cID), 4000);
mllID = setTimeout(() => elegeta(`.${cID}`).forEach(e => e?.remove()), 4000, cID);
// mllID = setTimeout((function() { return function() { $(`.${cID}`).remove(); } })(cID), 4000);
// ele.onclick = () => { $(`.${cID}`).remove(); if (mllID) { clearTimeout(mllID); } }
ele.onclick = () => { elegeta(`.${cID}`).forEach(e => e?.remove()); if (mllID) { clearTimeout(mllID); } }
}
function rndID(n = 11) {
var S = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" // var S = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_"
return Array.from(Array(n)).map(() => S[Math.floor(Math.random() * S.length)]).join('')
}
function ct(callback, name = "test", time = 10) { let i = 0; let st = Date.now(); while (Date.now() - st < 1000) { i++, callback() } console.log(`${name} ${i}回実行 / 1sec , ${1000/i}ミリ秒/1実行 ${callback.toString().slice(0,55)}`) } // 速度測定(一瞬で終わるもの)
function JS(v) { return JSON.stringify(v) }
function JP(v) { return JSON.parse(v) }
function lh(re) { let tmp = location.href.match(re); if (!tmp) { return null } else if (tmp.length > 1) { return tmp[1] } else return tmp[0] } // gフラグ不可
function ld(re) { let tmp = location.hostname.match(re); if (!tmp) { return null } else if (tmp.length > 1) { return tmp[1] } else return tmp[0] } // gフラグ不可
function sani(s) { return s?.replace(/&/g, "&")?.replace(/"/g, """)?.replace(/'/g, "'")?.replace(/`/g, '`')?.replace(/</g, "<")?.replace(/>/g, ">") || "" }
function before(e, html) { e?.insertAdjacentHTML('beforebegin', html); return e?.previousElementSibling; }
function begin(e, html) { e?.insertAdjacentHTML('afterbegin', html); return e?.firstChild; }
function end(e, html) { e?.insertAdjacentHTML('beforeend', html); return e?.lastChild; }
function after(e, html) { e?.insertAdjacentHTML('afterend', html); return e?.nextElementSibling; }
function pref(name, store = null) { // prefs(name,data)で書き込み(数値でも文字列でも配列でもオブジェクトでも可)、prefs(name)で読み出し
if (store === null) { // 読み出し
let data = GM_getValue(name) || GM_getValue(name);
if (data == undefined) return null; // 値がない
if (data.substring(0, 1) === "[" && data.substring(data.length - 1) === "]") { // 配列なのでJSONで返す
// try { return JSON.parse(data || '[]'); } catch (e) {
try {
let a = JSON.parse(data || '[]');
return a
} catch (e) {
alert("データベースがバグってるのでクリアします\n" + e);
pref(name, []);
return;
}
// } else return data;
} else {
return data;
}
}
if (store === "" || (Array.isArray(store) && store?.length == 0)) { // 書き込み、削除
GM_deleteValue(name);
return;
} else if (typeof store === "string") { // 書き込み、文字列
GM_setValue(name, store);
return store;
} else { // 書き込み、配列
// try { GM_setValue(name, JSON.stringify(store)); } catch (e) {
try {
let a = JSON.stringify(store);
GM_setValue(name, a);
} catch (e) {
alert("データベースがバグってるのでクリアします\n" + e);
pref(name, "");
}
return store;
}
}
})();