// ==UserScript== // @name YouTube検索結果「全てキューに入れて再生」ボタンを追加 // @description musictonicの代わり 右クリックだとシャッフル再生 e:カーソル下の動画をキューに入れる y:再生開始 Alt+c/Ctrl+x:視聴中のキューリストをURLにしてコピー // @version 0.2.4 // @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 // @noframes // @namespace https://greasyfork.org/users/181558 // @require https://code.jquery.com/jquery-3.7.1.min.js // @require https://code.jquery.com/ui/1.14.1/jquery-ui.min.js // @downloadURL none // ==/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 HIDE_DUPLICATED_VIDEO_IN_SEARCH_RESULTS = 1; // 1:検索結果に重複して出てきた動画を消す(実験的) 2:1+高速に消す 0:無効 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 HOME_THUMBNAIL_VIDEOS_SIZE = "width:35em; max-width:19vw; min-width:20em;"; // YouTubeトップ画面の動画のサムネイルのサイズ :"":無変更 const HOME_THUMBNAIL_SHORTS_SIZE = "max-width:20em;"; // YouTubeトップ画面のShort動画のサムネイルのサイズ :"":無変更 const CHANNEL_THUMBNAIL_VIDEO_SIZE = "width: 240px;"; // チャンネルの/videos画面の動画のサムネイルのサイズ :"":無変更 const WAIT_LOADING = 0; // 1:PLAY ALL押下時にボタン設置を待つ 0:待たない const EXPERIMENTAL_ALTERNATIVE_URL_FOR_INSTANT_PLAYLIST = 1; // 2:Instant Playlist用のURLを不具合回避のembed版にして遷移するようにする 1:embed版を開いた時通常の視聴ページに遷移する機能だけオン const DISPLAY_TOTAL_TIME_OF_PLAYLIST = 1; // 1:プレイリストの合計時間を表示する const DEBUG = 0; // 1:wait値を表示 // GM.addStyle('html{--ytd-grid-6-columns-width: 100%; --ytd-grid-max-width: 100%;}') // /videos画面でサムネイルが横にいくらでも並ぶようにする 2025.04 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-playlist-video-renderer , ytd-playlist-panel-video-renderer#playlist-items.style-scope.ytd-playlist-panel-renderer , :is( :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 , yt-lockup-view-model.ytd-watch-next-secondary-results-renderer.lockup):not(:has( ytm-shorts-lockup-view-model-v2)) , ytm-shorts-lockup-view-model-v2):not(:has( a[href*="list=RD"] , a[href*="list=OLAK"] , a[href*="list=PL"])))`; // IPに入れられる動画の枠 shorts含む const QBOX = `:is( ytd-playlist-video-renderer , :is( :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 , yt-lockup-view-model.ytd-watch-next-secondary-results-renderer.lockup):not(:has( ytm-shorts-lockup-view-model-v2)) , ytm-shorts-lockup-view-model-v2):not(:has( a[href*="list=RD"] , a[href*="list=OLAK"] , a[href*="list=PL"])))`; //キューを入れられる動画の枠 shorts含む const MENUBUTTON = `:is(button[aria-label="操作メニュー"] , button[aria-label="その他の操作"] , button[aria-label="Action menu"] , button[aria-label="More actions"])`; const itemIPL = (n) => elegeta(`:is(#dismissible a#video-title , #dismissible a#video-title-link , .style-scope.ytd-playlist-video-list-renderer a#video-title , #dismissible a.ytd-compact-video-renderer , #playlist-items a , #dismissible a#video-title , a.yt-lockup-metadata-view-model-wiz__title-link ):is([href*="/watch?v="] , [href*="/shorts/"] ) , a.ShortsLockupViewModelHostEndpoint.ShortsLockupViewModelHostOutsideMetadataEndpoint , a.yt-lockup-metadata-view-model-wiz__title , ytd-rich-grid-slim-media[mini-mode][is-short] div div a ${SHORTS} :visible`, n).filter(e => e?.closest('ytd-playlist-panel-video-renderer')?.style?.opacity != 0.5 && !e?.closest(".miniplayer")).filter(e => e?.closest(IPBOX)); // 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:visible')?.find(v => ["キューに追加", "Add to queue"].includes(v.textContent)) const QBOXlen = () => uniqVideo(elegeta(`${QBOX}:visible`)).length; // 画面に出ている動画の総数 const MENUBUTTONlen = () => uniqVideo(elegeta(`${QBOX}:visible`).filter(e => /svg/.test(e.innerHTML))).length; // …ボタン設置も完了した動画の総数 2025.03 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 = []; let vid = new WeakMap(); var playAllCount, playAllCount2; var myqueue = []; GF.sousa = Date.now() const ael = (ele, evts, cb, opt) => evts.split(" ").forEach(evt => ele?.addEventListener(evt, cb, opt)); 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", ``); 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, `

Wait a few seconds...

`) 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\/(?:watch\?v=)?|(?: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以外はここまで // トップ普通動画 HOME_THUMBNAIL_VIDEOS_SIZE && GM.addStyle(`ytd-two-column-browse-results-renderer.style-scope.ytd-browse.grid.grid-disabled ytd-rich-item-renderer[rendered-from-rich-grid]{${HOME_THUMBNAIL_VIDEOS_SIZE}}`) // 2025.04.23 // トップshort //HOME_THUMBNAIL_SHORTS_SIZE && GM.addStyle(`div.shortsLockupViewModelHostThumbnailContainer{max-width:18em;} `) HOME_THUMBNAIL_SHORTS_SIZE && GM.addStyle(`div.style-scope.ytd-rich-shelf-renderer:nth-child(2 of div.style-scope.ytd-rich-shelf-renderer) > div.style-scope > ytd-rich-item-renderer.style-scope[class*="ytd-rich-shelf-renderer"]{${HOME_THUMBNAIL_SHORTS_SIZE}`) 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;}`); CHANNEL_THUMBNAIL_VIDEO_SIZE && GM.addStyle(`ytd-rich-item-renderer[rendered-from-rich-grid] { ${CHANNEL_THUMBNAIL_VIDEO_SIZE} }`) // プレイリストの動画の合計時間を算出、全部読み込みが終わっていないとしない 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", `
${total}
`); 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 } }) //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() vid = new WeakMap(); setTimeout(() => { GF.lastinner = ""; run() }, 1500); } }); observer.observe(document, { childList: true, subtree: true }); setTimeout(() => { run(); }, 1009); if (HIDE_DUPLICATED_VIDEO_IN_SEARCH_RESULTS) { setInterval(() => { if (lh(/^https:\/\/www\.youtube\.com\/results\?search_query=|^https:\/\/www\.youtube\.com\/feed\/trending|^https:\/\/www\.youtube\.com\/$/)) { duplicatedVideo(elegeta(`${IPBOX}:visible`))?.[1]?.forEach(e => { if (HIDE_DUPLICATED_VIDEO_IN_SEARCH_RESULTS == 2) e?.remove(); else { let s = getComputedStyle(e); e.animate([{ transformOrigin: 'top left', transform: (s.transform === 'none' ? '' : s.transform || "") || 'scale(1)', opacity: 1, height: e.offsetHeight + 'px', width: e.offsetWidth + 'px' }, { transformOrigin: 'top left', transform: (s.transform === 'none' ? '' : s.transform || "") + 'scale(0)', opacity: 0, height: '0px', width: '0px' }], { duration: 999, easing: 'ease', fill: 'none' }).onfinish = () => e?.remove(); //e.style.display = 'none'; } }) } }, 3500) } 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} (${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 } */ // 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+左クリック:棚とショートをすべて消す 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); } 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動画と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); 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') ? "クリック:隠す/再表示
Ctrl+クリック:この類の棚をすべて隠す/再表示" : "") function hoverHelp(cb) { let latest, helpEle; document.addEventListener("mousemove", e => { if (!e?.target || !e?.target instanceof Element) return; if (latest != e?.target) { helpEle?.remove() const text = cb(e.target) if (text) helpEle = end(document.body, `
${text}
`) 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', async 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(); // まずIPに入れる 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
Playlist (${equeueIP.length}+)`; if (USE_INSTANT_PLAYLIST) boxatt([box, eleget0('#instantPlaylistButton')]) //[box, eleget0('#instantPlaylistButton')].forEach(e => e?.animate([{ backgroundColor: "#0022ff22", boxShadow: " 0 0 0 0 #0022ff88" }, { backgroundColor: "#0022ff00", boxShadow: "0 0 10px 35px #0022ff00" }], 999)) } } // PAに入れる var ancestorEle = eleget0(`${QBOX}:hover`) if (!ancestorEle) return false let menuButton = eleget0(MENUBUTTON, ancestorEle) // …ボタン if (menuButton && USE_PLAYALL) { menuButton?.click() //setTimeout(() => { menuButton?.click() }, 0); await clickQB(() => QB(ancestorEle), () => QB(ancestorEle)?.click(), 3000); //setTimeout(() => alert(QB(ancestorEle))+QB(ancestorEle)?.click(), 400) // 2025.03なんかこれあると二重に入るようになったので消す if (!USE_INSTANT_PLAYLIST) boxatt([ancestorEle]) //[!USE_INSTANT_PLAYLIST ? ancestorEle : menuButton, eleget0('span#playAllButton')].forEach(e => e.animate([{ backgroundColor: "#8800ff22", boxShadow: " 0 0 0 0 #8800ff88" }, { backgroundColor: "#8800ff00", boxShadow: "0 0 10px 35px #8800ff00" }], 999)) } 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(); makeUrlFromQueuelist(1, kaisuu) kaisuu = ++kaisuu % YOUTUBE_WATCH_ALTC_VARIATIONS; } }, false); //タイムスタンプをAlt+左クリック(または右クリック)で上にスクロールせずに再生位置を移動 ael(document, "click contextmenu", function(e) { let ele = eleget0('div#time', e?.target?.closest('a#endpoint')) || (e?.target?.matches('a.yt-core-attributed-string__link.yt-core-attributed-string__link--call-to-action-color') && e?.target); if (!((e?.type == "contextmenu" || e?.altKey) && /^[0-9\:]+$/.test(ele?.textContent?.trim()))) return; let sec = ele?.textContent?.trim()?.split(':')?.reverse()?.reduce((a, b, i) => a + (b * ([1, 60, 3600, 86400][i])), 0); let ve = eleget0(`video`); if (sec && ve) { ve.currentTime = sec; ve?.play(); ele?.animate([{ outline: "4px solid #065fd4" }, { outline: "4px solid #065fd400" }], 666) //e.preventDefault() + e.stopPropagation() + e.stopImmediatePropagation();return false; } }, true); return; function makeUrlFromQueuelist(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').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 ? `
\n${sani(eleget0('span#video-title.style-scope.ytd-playlist-panel-video-renderer')?.textContent?.replace(/\n/gm," ")?.trim())} (${videoIDa?.length})${playtimesum?" "+playtimesum:""}
\n\n
\n\n` + (pl ? `
\n${sani(eleget0('yt-formatted-string.title.style-scope.ytd-playlist-panel-renderer.complex-string')?.textContent?.replace(/\s+|\n/gm," ")?.trim()||"")}
\n\n
` : "") : `${IPURL}${videoIDa.join(",") + indexUrlQP}`; // h181px~:hd thumbnail + "&cc_load_policy=1&cc_lang_pref=jpn" var cb2 = cb var cbEsc = (cb2).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) $(`
${kaisuu==2?"埋め込み":"ブックマーク"}用リンク(${videoIDa.length})${playtimesum}
${title}
`).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 || makeUrlFromQueuelist(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")) $(`
${kaisuu==2?"埋め込み":"ブックマーク"}用リンク(${urla3.length})
${title}
`).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) { [ '#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") || location.href.match("//www.youtube.com/gaming") || location.href.match("//www.youtube.com/feed/news_destination") || location.href.match("//www.youtube.com/feed/trending")) { var place = eleget0('//div[@id="center" and @class="style-scope ytd-masthead"]'); if (!place) { setTimeout(run, 2222); return } } 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:#80f2; } .yzqAttract2 { outline:4px solid #02f8; background:#02f2; } .yzqAttract3 { opacity:0.5; } `) // 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, `Instant
Playlist${equeueIP.length?" ("+equeueIP.length+"+)":""}
`); instantPlaylistButton?.addEventListener("contextmenu", e => { e?.stopPropagation(); e?.preventDefault(); return false; }, true) instantPlaylistButton?.addEventListener("mousedown", e => { if (e.button == 0) openInstantPlaylist(e); if (e.button == 2) openInstantPlaylist(e, "shuffle"); return false; }); instantPlaylistButton?.addEventListener("mouseenter", e => { let vs = duplicatedVideo(elegeta(`${IPBOX}:visible`)); vs[0].filter(e => itemIPL(e)).forEach(e => e?.classList?.add("yzqAttract2")) vs[1].filter(e => itemIPL(e)).forEach(e => e?.classList?.add("yzqAttract3")) }) instantPlaylistButton?.addEventListener("mouseleave", e => $('.yzqAttract , .yzqAttract2 , .yzqAttract3').removeClass("yzqAttract yzqAttract2 yzqAttract3")) 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, 'Play All') clearInterval(GF?.playAllCount) GF.playAllCount = setInterval(() => { if (Date.now() - GF?.sousa > 15000) return; // 15秒以上スクロールしてなければしない let qb = QBOXlen() //elegeta(`${QBOX}:visible`) let mb = MENUBUTTONlen() //qb.filter(e=>eleget0(`${MENUBUTTON} svg`, e)).length //DEBUG && $(item()).css({ "outline": "3px dotted #80f" }) //DEBUG && $(itemWhole()).css({ "outline": "3px dotted #f0f" }) let size = qb == mb ? mb : `${mb}/${qb}`; // console.log(qb,mb,size,GF.inner, GF.lastinner, playAllButton) GF.inner = `Play All
(${size})${GF?.PAadd||""}${DEBUG ? "
wait:" + wait : ""}` if (GF.lastinner != GF.inner) { $('#playAllButton').remove(); var playAllButton = after(place, `${GF.inner}`) playAllButton?.addEventListener("mouseenter", e => { let vs = duplicatedVideo(elegeta(`${QBOX}:visible`)); vs[0].filter(e => itemIPL(e)).forEach(e => e?.classList?.add("yzqAttract")) vs[1].filter(e => itemIPL(e)).forEach(e => e?.classList?.add("yzqAttract3")) }) playAllButton?.addEventListener("mouseleave", e => $('.yzqAttract , .yzqAttract2 , .yzqAttract3').removeClass("yzqAttract yzqAttract2 yzqAttract3")) 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; }); } GF.lastinner = GF.inner; }, 1000); } } } function openInstantPlaylist(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() > MENUBUTTONlen()) GF.PAadd = "
Wait..." //⏳ await waitTrue(() => QBOXlen() == MENUBUTTONlen(), 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を入れた要素を重複回避のため消す videoEle = uniqVideo(videoEle); let videoLength = videoEle.length let i = 0; for (let e of (option == "shuffle" ? shuffle(videoEle) : videoEle)) { //console.log(wait,videoLength) e?.classList?.remove("yzqAttract") eleget0(`${MENUBUTTON}`, e)?.click(); await sleep(wait / 2) await clickQB(() => QB(e), () => QB(e)?.click(), 3000); await sleep(wait + (videoLength / 20)) if (ev.ctrlKey) { //IP先頭用に独自キューを覚えておく 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
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 uniqVideo(videoBoxes) { videoBoxes.filter(e => !vid.has(e)).forEach(e => vid.set(e, videoIDInBox(e))) //ct(()=> videoBoxes.reduce((a, b) => a.every(v => vid.get(v) != vid.get(b)) ? [...a, b] : a, [])) //return videoBoxes.reduce((a, b) => a.every(v => vid.get(v) != vid.get(b)) ? [...a, b] : a, []) return videoBoxes.reduce((a, b) => a.every(v => vid.get(v) != vid.get(b)) ? (a.push(b), a) : a, []); //test 85回実行 / 1sec ,11.764705882352942ミリ秒/1実行 ()=>{ videoBoxes.reduce((a, b) => a.every(v => vid //test 1590回実行 / 1sec , 0.628930817610062ミリ秒/1実行 ()=>{ videoBoxes.forEach(e=>e.dataset.vid=videoID //test 14989回実行 / 1sec , 0.066715591433718ミリ秒/1実行 ()=>{ videoBoxes.forEach(e => vid.set(e,videoIDInBo //test 676149回実行 / 1sec , 0.001478963956169ミリ秒/1実行 ()=>{ videoBoxes.filter(e=>!vid.has(e)).forEach(e = } function duplicatedVideo(videoBoxes) { let uniq = uniqVideo(videoBoxes) return [videoBoxes, videoBoxes.filter(e => !uniq.includes(e))]; } function videoIDInBox(ele) { return elegeta(`a`, ele).reduce((a, b) => a + " " + b.href, "").match0(/(?:\/shorts\/|\?v=)([a-zA-Z0-9_\-]{11})/) } 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(/\n/gm, "
") //alert(bgcolor)//bgcolor = bgcolor || (/www\.translatetheweb\.com|\.translate\.goog\/|translate\.google\.com|\/embed\//gmi.test(location.href + " " + text) ? "#822" : "#6080ff"); document.body.insertAdjacentHTML("beforeend", `${ text }`) var ele = document.body.lastChild mllID = setTimeout(() => elegeta(`.${cID}`).forEach(e => e?.remove()), 4000, cID); 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,88)}`) } // 速度測定(一瞬で終わるもの) 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, ">") || "" } 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; } } 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; } } function popupCenter(text, bgcolor = text == "解除" ? "#888" : "#35a") { if (!text) return text = String(text).replace(/&/g, "&").replace(/"/g, """).replace(/'/g, "'").replace(/`/g, '`').replace(//g, ">").replace(/\n/gm, "
") let e = end(document.body, `${text}`) /*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)) } async function clickQB(till, job, timeout) { return new Promise((resolve, reject) => { (function loop(etime) { if (till()) { job(); resolve(); } else if (Date.now() < etime) { requestAnimationFrame(() => loop(etime)); } else resolve(); })(Date.now() + timeout) }) } })()