Greasy Fork

Greasy Fork is available in English.

YouTube検索結果「全てキューに入れて再生」ボタンを追加

musictonicの代わり 右クリックだとシャッフル再生 e:カーソル下の動画をキューに入れる y:再生開始 Alt+c/Ctrl+x:視聴中のキューリストをURLにしてコピー

当前为 2024-05-08 提交的版本,查看 最新版本

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name YouTube検索結果「全てキューに入れて再生」ボタンを追加
// @description musictonicの代わり 右クリックだとシャッフル再生 e:カーソル下の動画をキューに入れる y:再生開始 Alt+c/Ctrl+x:視聴中のキューリストをURLにしてコピー
// @version      0.1.40
// @run-at document-idle
// @match *://www.youtube.com/*
// @match *://www.youtube.com/
// @require https://code.jquery.com/jquery-3.6.4.min.js
// @require https://code.jquery.com/ui/1.13.2/jquery-ui.min.js
// @grant GM.setClipboard
// @grant       GM.openInTab
// @grant GM.addStyle
// @namespace http://greasyfork.icu/users/181558
// ==/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.5em"; // 検索結果の動画のサムネイルのサイズ "":無効
  const SEARCH_RESULTS_THUMBNAIL_SHORTS_HEIGHT = "17em"; // 検索結果のshortsのサムネイルのサイズ "":無効

  const DEBUG = 0; // 1:wait値を表示
  var SHORTS = INCLUDE_REEL_SHORTS ? ",ytd-reel-item-renderer.style-scope.yt-horizontal-list-renderer div div a,ytd-rich-grid-slim-media div div a[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 lastLength = 0;
  var mllID = 0;
  var kaisuu = 0;
  var equeueIP = []
  var equeue = []

  var playAllCount, playAllCount2;
  var myqueue = [];

  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 (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+Y") {
        let option = "";
        let inp = [...elegeta('a:visible').map(e => e.href).filter(v => /youtube\.com|youtu\.be/.test(v)), ...document?.body?.innerText?.split(/\n|\s/)?.filter(v => /youtube\.com|youtu\.be/.test(v))].join(" ");
        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=([a-zA-Z0-9_\-,]{11,600})/gmi)].map(c => c.slice(1, 999))).filter(w => w) }) // 書式が混在していても登場順に収納する
          inp = null;
          if (urlcap) {
            let urla = urlcap.join(",").split(",").filter(c => /^[a-zA-Z0-9_\-]{11}$/.test(c)); // 動画IDは11桁
            let urllen = urla.length;
            let urla2 = [...new Set(urla)]; // 重複削除
            if (option == "shuffle") urla2 = shuffle(urla2); // シャッフル
            let urllen2 = urla2.length;
            let urla3 = [...urla2].slice(0, 50); // 50件まで
            let urlenum = urla3.join(",")
            let url = `https://www.youtube.com/watch_videos?video_ids=${urla2.join(",")}`
            var enumUrl = []
            for (let u = 0; u < urla2.length / 50; u++) {
              enumUrl.push(`https://www.youtube.com/watch_videos?video_ids=${ (urla2.slice(u*50, u*50+50).join(",") ) }`)
            }
            let [url0, urls, videos] = urlExtractAndConcat("", 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 "";
  }
  setInterval(() => {
    if (!GF.time && lh(/^https:\/\/www\.youtube\.com\/watch\?v=/)) {
      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;
      }
    }
  }, 2000);

  ["yt-navigate-finish", "yt-playlist-data-updated"].forEach(v => {
    window.addEventListener(v, () => {
      GF.time = 0
      eleget0('#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(`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} !important;}`);
  SEARCH_RESULTS_THUMBNAIL_SHORTS_HEIGHT && GM.addStyle(`ytd-search .yt-horizontal-list-renderer .yt-core-image--content-mode-scale-aspect-fill { object-fit: contain; } ytd-search .yt-horizontal-list-renderer ytd-thumbnail.ytd-reel-item-renderer{max-height:${SEARCH_RESULTS_THUMBNAIL_SHORTS_HEIGHT} !important;}`);

  //URLの変化を監視
  var href = location.href;
  var observer = new MutationObserver(function(mutations) {
    if (href !== location.href) {
      href = location.href;
      $('#playAllButton,#instantPlaylistButton').remove();
      setTimeout(() => {
        lastLength = 0;
        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;}`)
  //setInterval(() => { hideSuggest() }, 1511);

  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;}')
  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) {
        GF.wari = 1 - (GF?.wari || 0);
        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')
        $(reelinner).toggle(111)
      }
      return false;
    }
  })

  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 = ele.closest('ytd-video-renderer,ytd-rich-item-renderer,ytd-grid-video-renderer,ytd-playlist-video-renderer'); //マウスが乗っている動画の枠
      //var box = ele.closest(`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`); //マウスが乗っている動画の枠
      var box = eleget0(`: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):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)]
          $('#instantPlaylistButton').html(`Instant<BR>Playlist (${equeueIP.length}+)`)
          boxatt([box, eleget0('#instantPlaylistButton')])
          //eleget0('#instantPlaylistButton')?.classList?.add("boxatt");
        }
      }

      var prevcue = eleget0('//ytd-thumbnail-overlay-toggle-button-renderer[@aria-label="キューに追加"]/yt-icon[2]|.//ytd-thumbnail-overlay-toggle-button-renderer[@aria-label="Add to queue"]/yt-icon[2]', box)
      if (prevcue) { prevcue?.click(); return false; }
      var prevcue = eleget0('//a[@id="thumbnail"]/div/ytd-thumbnail-overlay-toggle-button-renderer[last()]/yt-icon[@class="style-scope ytd-thumbnail-overlay-toggle-button-renderer"]', box)
      var ances = box;

      if (ances) {
        var cuebutton = elegeta('ytd-thumbnail.style-scope.ytd-grid-video-renderer a div ytd-thumbnail-overlay-toggle-button-renderer:last-child yt-icon#icon,ytd-thumbnail.ytd-compact-video-renderer a div ytd-thumbnail-overlay-toggle-button-renderer:last-child yt-icon#icon', ances)[0]
        if (cuebutton) {
          cuebutton?.click()
          ances.style.opacity = "0.25"
          setTimeout(() => { ances.style.opacity = "0.5" }, 17 * 2)
          setTimeout(() => { ances.style.opacity = "1" }, 17 * 3)
          return false
        }
      }

      var ancestorEle = getTitleFromParent(ele, 0, '//ytd-item-section-renderer|//ytd-playlist-video-renderer|//ytd-grid-video-renderer|//div[@id="dismissible" and @class="style-scope ytd-video-renderer"]|//div[@id="dismissible" and @class="style-scope ytd-rich-grid-media"]|//ytd-compact-video-renderer');
      if (!ancestorEle) return false
      let menuButton = elegeta('//yt-icon[@class="style-scope ytd-menu-renderer"]', ancestorEle);
      if (menuButton.length == 1) {
        setTimeout(() => {
          let queue = elegeta('yt-formatted-string.style-scope.ytd-menu-service-item-renderer span')?.find(v => ["キューに追加", "Add to Queue"].includes(v.textContent)) //        let queue = eleget0('//span[text()="キューに追加"]|//span[text()="Add to queue"]'); //let queue = eleget0('//yt-formatted-string[text()="キューに追加"]|//yt-formatted-string[text()="Add to queue"]');
          if (queue) {
            queue.click();
            setTimeout(() => { ancestorEle.style.opacity = 0.5 }, 0)
            setTimeout(() => { ancestorEle.style.opacity = 0.5 }, 17 * 2)
            setTimeout(() => { ancestorEle.style.opacity = 1 }, 17 * 4)
          }
        }, 200)
        setTimeout(() => { menuButton[0].click() }, 0);
      }
      return false;
    }

    if (key === "y" && !/\/watch/.test(location.href)) { // y::start playing
      e.preventDefault();
      cli('//div[contains(@class,"ytp-miniplayer-play-button-container")]/button|//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)
          /*(elegeta('ytd-playlist-panel-video-renderer#playlist-items.style-scope.ytd-playlist-panel-renderer:visible').slice(0, 49).length != elegeta('ytd-playlist-panel-video-renderer#playlist-items.style-scope.ytd-playlist-panel-renderer span#text.style-scope.ytd-thumbnail-overlay-time-status-renderer:visible').slice(0, 49).length) ? "" :
                      (() => {
                        let sum = elegeta('ytd-playlist-panel-video-renderer#playlist-items.style-scope.ytd-playlist-panel-renderer span#text.style-scope.ytd-thumbnail-overlay-time-status-renderer:visible').reduce((p, e) => {
                          let t = e?.innerText?.trim()
                          let h = t?.match0(/(\d+)\:\d+\:\d+$/) || 0
                          let m = t?.match0(/(\d+)\:\d+$/) || 0
                          let s = t?.match0(/(\d+)$/) || 0
                          p += h * 60 * 60 + m * 60 + s * 1
                          return p
                        }, 0)
                        return ` ${sum/60/60|0}:${sum/60%60|0}:${sum%60}`
                      })()*/
          playtimesum = playtimesum ? " " + playtimesum : ""
          let pl = location.href.match0(/\&list=(PL[a-zA-Z0-9_\-]+)/);
          var cb = kaisuu == 2 ? `<div>\n<a rel="noopener noreferrer" href="https://www.youtube.com/watch_videos?video_ids=${ (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"
          //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, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;').replace(/'/g, '&#39;')

          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?",…":"")}\nhttps://www.youtube.com/watch_videos?video_ids=${ (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 title = `▶ ${indexNo+1}/${videoIDa.length} ${transratedTitle}`
          if (disp) {
            if (videoIDaAll.length <= 50) {
              popup(kaisuu == 2 ? cb : transratedTitle + "\n" + cb, "", "right:0em; top:0em;max-width:40%;")
              GM.setClipboard(kaisuu == 2 ? cb : transratedTitle + "\n" + cb2 + "\n");
            } else {
              popup(kaisuu == 2 ? cb : transratedTitle + "\n" + enumText, "", "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').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("")
          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今のページにあるYouTube動画URL(${inilen})が初期値として入力済みです\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\/)([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=([a-zA-Z0-9_\-,]{11,600})/gmi)].map(c => c.slice(1, 999))).filter(w => w) }) // 書式が混在していても登場順に収納する //https://www.youtube.com/live/
      //inp.split(/\s/).forEach(v => { urlcap = urlcap.concat(...[...v?.matchAll(/^(?:h?t?tps?:\/\/)?(?:m\.|www\.)?youtube\.com\/(?:shorts\/|watch\?v=|embed\/)([a-zA-Z0-9_\-]{11})|^(?:h?t?tps?:\/\/)?youtu\.be\/([a-zA-Z0-9_\-]{11})|^(?:h?t?tps?:\/\/)?www\.youtube\.com\/watch_videos\?video_ids=([a-zA-Z0-9_\-,]{11,600})/gmi)].map(c => c.slice(1, 999))).filter(w => w) }) // 書式が混在していても登場順に収納する
      //inp.split(/\s/).forEach(v => { urlcap = urlcap.concat(...[...v?.matchAll(/^(?:h?ttps?:\/\/)?www\.youtube\.com\/watch\?v=([a-zA-Z0-9_\-]{11})|^(?:h?ttps?:\/\/)?youtu\.be\/([a-zA-Z0-9_\-]{11})|^(?:h?ttps?:\/\/)?www\.youtube\.com\/shorts\/([a-zA-Z0-9_\-]{11})|^(?:h?ttps?:\/\/)?www\.youtube\.com\/watch_videos\?video_ids=([a-zA-Z0-9_\-,]{11,600})/gmi)].map(c => c.slice(1, 999))).filter(w => w) }) // 書式が混在していても登場順に収納する
      inp = null;
      if (urlcap) {
        let urla = urlcap.join(",").split(",").filter(c => /^[a-zA-Z0-9_\-]{11}$/.test(c)); // 動画IDは11桁
        let urllen = urla.length;
        let urla2 = [...new Set(urla)]; // 重複削除
        if (option == "shuffle") urla2 = shuffle(urla2); // シャッフル
        let urllen2 = urla2.length;
        let urla3 = [...urla2].slice(0, 50); // 50件まで
        let urlenum = urla3.join(",")
        let url = `https://www.youtube.com/watch_videos?video_ids=${urlenum}`
        if (urla3 && urla3.length) {
          var title = `▶ (${urla3.length}) ${urla3.slice(0,3).join(",")+(urla3.length>3?",…":"")}\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?",…":"")}\nhttps://www.youtube.com/watch_videos?video_ids=${ (urla2.slice(u*50, u*50+50).join(",") ) }\n`
            enumUrl.push(`https://www.youtube.com/watch_videos?video_ids=${ (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) {
            //            $(`<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() })
            if (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, "", "right:0em; top:0em;max-width:40%;")
            return [url, enumUrl, urllen2];
          }
        }
      }
    }
  }

  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 (location.href == "https://www.youtube.com/" ||
    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/watch")) {
      var place = eleget0('//div[@id="center" and @class="style-scope ytd-masthead"]');
    } else return;

    if (place) {
      if (USE_INSTANT_PLAYLIST) {
        $('#instantPlaylistButton,#extractAndConcatButton').remove();
        $('ytd-topbar-logo-renderer#logo:not([data-rcli])').on("contextmenu", () => { return false; }); // 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 = $(`<span class="ignoreMe" style="cursor:pointer;color:var(--yt-spec-icon-active-other); text-align:center; font-size:15px; " title="クリックで画面に出ている動画を限定公開プレイリストにして再生(右クリックだとシャッフル)\nGenerate playlist from all displayed videos and open instantly (right-click to shuffle)" id="instantPlaylistButton">Instant<br>Playlist${equeueIP.length?" ("+equeueIP.length+"+)":""}</span>`)
        instantPlaylistButton.insertAfter(place);
        instantPlaylistButton.on("contextmenu", e => false)
        instantPlaylistButton.on("mousedown", (e) => {
          if (e.button == 0) playAllG(e);
          if (e.button == 2) playAllG(e, "shuffle");
          return false;
        });

        instantPlaylistButton.on("mousemove", () => {
          var [url, len, lenmax, enumURLsa] = getUrla();
          //          $('#instantPlaylistButton').attr("title", `クリックで画面に出ている動画を限定公開プレイリストにして再生 (${len}/${lenmax})\n(右クリックだとシャッフル、Ctrl+で新しいタブで開く)\nGenerate playlist from all displayed videos and open instantly (right-click to shuffle)\n\n${url}`);
          $('#instantPlaylistButton').attr("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;
        });
      }
      if (USE_PLAYALL) {
        $('#playAllButton').remove();
        var playAllButton = $('<span class="ignoreMe" style="cursor:pointer;color:var(--yt-spec-icon-active-other); text-align:center; font-size:15px; " title="クリックで画面に出ている動画を全てキューに入れて再生(右クリックだとシャッフル)\nEnqueue all displayed videos and start playing (right-click to shuffle)\nCtrl+だと再生を始めない" id="playAllButton">Play All</span>')
        playAllButton.insertAfter(place);
        playAllButton.on("contextmenu", (e) => { e.preventDefault(); return false; });
        playAllButton.on("mousedown", (e) => { playAll(e?.button != 0 ? "shuffle" : "", e); return false; });
        if (!playAllCount) {
          playAllCount = setInterval(() => {
            let currentLength = elegeta('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:visible').filter(e => !e.querySelector('ytd-rich-grid-slim-media')).length;
            DEBUG && $(elegeta('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:visible').filter(e => !e.querySelector('ytd-rich-grid-slim-media'))).css({ "outline": "3px dotted #f0f" })
            //let currentLength = elegeta('yt-icon.style-scope.ytd-menu-renderer:not(ytd-reel-item-renderer .ytd-menu-renderer):visible').length;
            if (lastLength != currentLength) $('#playAllButton').html("Play All (" + currentLength + ")" + (DEBUG ? "<br>wait:" + wait : ""));
            lastLength = currentLength;
          }, 1000);
        }
      }
    }
  }

  function playAllG(e, option = false) {
    setTimeout(pauseVideo, 17);
    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 = 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}:visible`);
    let videoEle = 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}:visible`).filter(e => e?.closest('ytd-playlist-panel-video-renderer')?.style?.opacity != 0.5);
    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 = `https://www.youtube.com/watch_videos?video_ids=${videoIDa.join(",")}`

        var enumURLsa = []
        for (let u = 0; u < videoIDaMax.length / 50; u++) {
          enumURLsa.push(`https://www.youtube.com/watch_videos?video_ids=${ (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) }
  }

  function playAll(option = false, ev) {
    pauseVideo(); //setTimeout(pauseVideo, 17);
    //var box = elegeta('ytd-video-renderer,ytd-rich-item-renderer,ytd-grid-video-renderer,ytd-playlist-video-renderer:visible').filter(v => {
    var box = elegeta('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: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()); //マウスが乗っている動画の枠

    //elegeta('ytd-video-renderer,ytd-rich-item-renderer,ytd-grid-video-renderer,ytd-playlist-video-renderer').filter(v => !v.offsetHeight).forEach(e => e.remove())

    let d = 0;
    let videoEle = elegeta('//yt-icon[@class="style-scope ytd-menu-renderer"]:visible').filter(e => 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')?.offsetHeight)

    let videoLength = videoEle.length
    let i = 0;
    for (let e of (option == "shuffle" ? shuffle(videoEle) : videoEle)) {
      setTimeout(() => { e.click() }, d);
      if (d == 0) d += WAIT_FIRST + (videoLength * 2); // ?
      setTimeout(() => {
        let queue = elegeta('yt-formatted-string.style-scope.ytd-menu-service-item-renderer span')?.find(v => ["キューに追加", "Add to Queue"].includes(v.textContent)) //        let queue = eleget0('//span[text()="キューに追加"]|//span[text()="Add to queue"]'); //let queue = eleget0('//yt-formatted-string[text()="キューに追加"]|//yt-formatted-string[text()="Add to queue"]');
        if (queue) queue.click();
      }, d + wait / 2);
      d += 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
        if (href) {
          equeueIP.push(href?.match0(/\?v=([a-zA-Z0-9_\-]{11})/) || href?.match0(/\/shorts\/([a-zA-Z0-9_\-]{11})/))
          equeueIP = [...new Set(equeueIP)]
          $('#instantPlaylistButton').html(`Instant<BR>Playlist (${equeueIP.length}+)`)
        }
      }

      if ((i++) >= 199) break; // キューは200件までしか入らないので時間節約
    }
    d += 100 + wait * Math.min(7000, Math.max(2000, waitLast)) / 1000 + videoLength / 3;
    d = Math.max(d, CHROME ? 1500 : 800)
    if (!(location.href.match(/\/watch\?v=/))) {
      if (!ev.ctrlKey) {
        cli('//div[contains(@class,"ytp-miniplayer-play-button-container")]/button|//button[contains(@class,"ytp-play-button-playlist")]', d, "infinity", () => {
          d += 200 + wait * Math.min(7000, Math.max(2000, waitLast)) / 1000 + videoLength / 3;
          cli('//div/button[contains(@class,"ytp-miniplayer-expand-watch-page-button")]:visible', 300 + wait + videoLength / 3, "infinity")
        })
      }
    } else {
      d += 100 + wait;
      setTimeout(() => { let e = eleget0('//video'); if (e) { e.play(); } }, d);
    }
  }

  function shuffle(array) {
    for (let i = array.length - 1; i >= 0; i--) {
      const j = Math.floor(Math.random() * (i + 1));
      [array[i], array[j]] = [array[j], array[i]];
    }
    return array;
  }

  function cli(xpath, wait, mode = "", 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 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 = document.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(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(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 = "", 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, "&amp;").replace(/"/g, "&quot;").replace(/'/g, "&#39;").replace(/`/g, '&#x60;').replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/\n/gm, "<br>")
    bgcolor = bgcolor || (/www\.translatetheweb\.com|\.translate\.goog\/|translate\.google\.com|\/embed\//gmi.test(location.href + " " + text) ? "#822" : "#6080ff");
    document.body.insertAdjacentHTML("beforeend", `<span id="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() { $(`.${cID}`).remove(); } })(cID), 4000);
    ele.onclick = () => { $(`.${cID}`).remove(); if (mllID) { clearTimeout(mllID); } }
  }

  function rndID(n = 11) {
    var S = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_"
    return Array.from(Array(n)).map(() => S[Math.floor(Math.random() * S.length)]).join('')
  }

  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`) } // 速度測定
  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, "&amp;")?.replace(/"/g, "&quot;")?.replace(/'/g, "&#39;")?.replace(/`/g, '&#x60;')?.replace(/</g, "&lt;")?.replace(/>/g, "&gt;") || "" }

})();