Greasy Fork

Greasy Fork is available in English.

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

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

当前为 2022-09-04 提交的版本,查看 最新版本

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name YouTube検索結果「全てキューに入れて再生」ボタンを追加
// @description musictonicの代わり 右クリックだとシャッフル再生 e:カーソル下の動画をキューに入れる y:再生開始 Alt+c:視聴中の再生リストをURLにしてコピー
// @version      0.1.23
// @run-at document-idle
// @match *://www.youtube.com/*
// @match *://www.youtube.com/
// @require https://code.jquery.com/jquery-3.4.1.min.js
// @require https://code.jquery.com/ui/1.12.1/jquery-ui.min.js
// @grant GM.setClipboard
// @namespace http://greasyfork.icu/users/181558
// ==/UserScript==

(function() {
  const USE_IMMEDIATE_PLAYLIST = 0; // 1:機能6-8を有効にする 0:無効
  const CONFIRM_AT_CREATE_FROM_URLS = 1; // 1:機能6-8時に確認する 0:確認しない
  const YOUTUBE_WATCH_ALTC_VARIATIONS = 2; // Alt+Cの機能を何番目まで使うか 1:連続再生URL 2:単独再生URLの列挙 3:iframe埋め込み用HTML
  const CLOSE_MINI_PLAYER_ALWAYS = 1; // 1:Escでミニプレイヤーを常に閉じる
  const AGREE_TO_CONTINUE_ALWAYS = 1; // 1:無操作一時停止を常に解除
  const HIDE_SUGGEST = 1000; // 1-:検索結果に割り込む「あなたへのおすすめ」「他の人はこちらも視聴しています」「家にいながら学ぶ」を隠す
  const DEBUG = 0; // 1:wait値を表示

  const COE = 1; // chrome以外のウエイト係数 取りこぼす時は大きく
  const COE_CHROME = 1; // chromeのウエイト係数 取りこぼす時は大きく

  const CHROME = (window.navigator.userAgent.toLowerCase().indexOf('chrome') != -1);
  const WAIT_FIRST = CHROME ? 700 : 200; // 取りこぼす時は大きく
  const WAIT_MIN = CHROME ? 190 : 160; // 取りこぼす時は大きく 50-
  const WAIT_MAX = 300; // 取りこぼす時は大きく 250-
  const waitLast = performance.now() * 1; // 現在の負荷
  const wait = Math.round((Math.min(WAIT_MAX, Math.max(WAIT_MIN, waitLast / 10))) * (CHROME ? COE_CHROME : COE));

  String.prototype.match0 = function(re) { let tmp = this.match(re); if (!tmp) { return null } else if (tmp.length > 1) { return tmp[1] } else return tmp[0] } // gフラグ不可
  function adja(place = document.body, pos, html) {
    return place ? (place.insertAdjacentHTML(pos, html), place) : null;
  }
  var JS = (v) => { return JSON.stringify(v) }
  let inYOUTUBE = location.hostname.match0(/^www\.youtube\.com|^youtu\.be/);

  var videoDisplayedLast = 0;
  var lastLength = 0;
  var mllID = 0;
  var kaisuu = 0;
  var equeue = []

  var playAllCount, playAllCount2;
  var myqueue = [];

  //URLの変化を監視
  var href = location.href;
  var observer = new MutationObserver(function(mutations) {
    if (href !== location.href) {
      href = location.href;
      $('#playAllButton,#playAllButton2').remove();
      setTimeout(() => {
        lastLength = 0;
        run()
      }, 1500);
    }
  });
  observer.observe(document, { childList: true, subtree: true });
  setTimeout(() => { run(); }, 1009);
  setInterval(() => { hideSuggest() }, 1511);
  if (AGREE_TO_CONTINUE_ALWAYS) {
    setInterval(() => {
      if (eleget0('//yt-formatted-string[text()="動画が一時停止されました。続きを視聴しますか?"]')) {
        elegeta('//yt-formatted-string[@class="style-scope yt-button-renderer style-blue-text size-default" and text()="はい"]').forEach(e => e.click());
      }
    }, 3001);
  }

  var mousex = 0;
  var mousey = 0;
  document.addEventListener("mousemove", function(e) {
    mousex = e.clientX;
    mousey = e.clientY;
  }, false);

  var esckey
  document.addEventListener('keydown', e => {
    if (e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA' || e.target.getAttribute('contenteditable') === 'true' || ((e.target.closest('#chat-messages,ytd-comments-header-renderer') || document.activeElement?.closest('#chat-messages,ytd-comments-header-renderer')))) return;
    var key = (e.shiftKey ? "Shift+" : "") + (e.altKey ? "Alt+" : "") + (e.ctrlKey ? "Ctrl+" : "") + e.key;

    if (key === "Escape" && CLOSE_MINI_PLAYER_ALWAYS && !esckey) {
      esckey = setInterval(() => { // esc::ミニプレイヤーを常に閉じる
        let e = eleget0('//yt-formatted-string[@id="text" and @class="style-scope yt-button-renderer style-blue-text size-default" and text()="プレーヤーを閉じる"]');
        if (e) {
          e.click();
          clearInterval(esckey)
          esckey = 0
        }
      }, 701);
    }

    if (key === "e") { // e::enqueue
      e.preventDefault();
      var ele = document.elementFromPoint(mousex, mousey);

      var box = ele.closest('ytd-video-renderer,ytd-rich-item-renderer,ytd-grid-video-renderer'); //マウスが乗っている動画の枠
      if (box) { //IP先頭用に独自キューを覚えておく
        var href = eleget0('a', box)?.href;
        var vID = href?.match0(/\?v=([a-zA-Z0-9_\-]{11})/) || href?.match0(/\/shorts\/([a-zA-Z0-9_\-]{11})/);
        if (vID) {
          equeue.push(vID)
          equeue = [...new Set(equeue)]
          $('#playAllButton2').html(`Immediate<BR>Playlist (${equeue.length}+)`)
        }
      }

      var prevcue = eleget0('//yt-formatted-string[contains(text(),"キューに追加")]', box)
      if (prevcue) { prevcue?.click(); return false; }
      var prevcue = eleget0('//a[@id="thumbnail"]/div/ytd-thumbnail-overlay-toggle-button-renderer[last()]/yt-icon[@class="style-scope ytd-thumbnail-overlay-toggle-button-renderer"]', box)
      if (prevcue) { prevcue?.click(); return false; }

      var ances = box; //ele?.closest('.ytd-grid-renderer,.ytd-compact-video-renderer')

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

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

    if (key === "y" && !/\/watch/.test(location.href)) { // y::start playing
      e.preventDefault();
      cli('//div[contains(@class,\"ytp-miniplayer-play-button-container\")]/button[@aria-label=\"再生(k)\"]|//button[@class="ytp-play-button ytp-button" and @aria-label="Play (k)"]')
      if (!(location.href.match(/\/watch\?v=/))) cli('//div[@class="ytp-miniplayer-scrim"]/button[@aria-label="拡大(i)"]|//div[@class="ytp-miniplayer-scrim"]/button[@aria-label="Expand (i)"]', 111, "infinity");
      setTimeout(() => { let e = eleget0('//video'); if (e) { e.play(); } }, 222);
      return false;
    }
    if (key === "Alt+c" && /\/watch/.test(location.href) && USE_IMMEDIATE_PLAYLIST) { // Alt+c::視聴中の再生リストをURLにしてコピー
      e.preventDefault();
      let eles = elegeta('//ytd-playlist-panel-video-renderer[@id="playlist-items"]/a');
      let eles2 = [...new Set(eles.map(c => c.href.match0(/\?v=([a-zA-Z0-9_\-]{11})/)))].slice(0, 50); // 重複削除
      if (eles.length) {
        let indexEle = eleget0('//yt-formatted-string[@class="index-message style-scope ytd-playlist-panel-renderer"]/span[1]|//div/div[@id="secondary-inner" and @class="style-scope ytd-watch-flexy"]/ytd-playlist-panel-renderer[@id="playlist" and @class="style-scope ytd-watch-flexy" and @js-panel-height="" and @collapsible="" and @playlist-type="TLPQ"]/div/div[1]/div[@id="header-contents"]/div[@id="header-top-row" and contains(@class,"style-scope ytd-playlist-panel-renderer")]/div[@id="header-description"]/div/div/span');
        let indexNo = indexEle && indexEle.textContent ? indexEle.textContent.match0(/(\d+)/mi) - 1 : 0;
        let indexUrlQP = indexNo > 0 ? `&index=${indexNo}` : "";
        elegeta("#link4bm").forEach(e => e.remove())
        //$("#logo").css({ "margin-right": "4em" })
        if (kaisuu == 1) {
          list = [...new Set(elegeta('//h4[@class=\"style-scope ytd-playlist-panel-video-renderer\"]/span[@id=\"video-title\"]').map(e => { return e.textContent.trim() + "\n" + e.closest('a').href.trim().replace(/&.*/, "") + "\n" }).map(a => JSON.stringify(a)))].map(a => JSON.parse(a)).join("")
          popup(list, "#303060")
          GM.setClipboard(list + "");
        } else {
          // プレイリストの動画の合計時間を算出、全部読み込みが終わっていないとしない
          let playtimesum = (elegeta('//ytd-playlist-panel-video-renderer[@class="style-scope ytd-playlist-panel-renderer" and @watch-color-update="" and @id="playlist-items"]').slice(0, 49).length != elegeta('//a[@id="thumbnail" and @class="yt-simple-endpoint inline-block style-scope ytd-thumbnail" and @tabindex="-1" and @rel="null"]/div[@id="overlays"]/ytd-thumbnail-overlay-time-status-renderer[@overlay-style="DEFAULT"]/span[@id="text"]').slice(0, 49).length) ? "" :
            (() => {
              let sum = elegeta('//a[@id="thumbnail" and @class="yt-simple-endpoint inline-block style-scope ytd-thumbnail" and @tabindex="-1" and @rel="null"]/div[@id="overlays"]/ytd-thumbnail-overlay-time-status-renderer[@overlay-style="DEFAULT"]/span[@id="text"]').reduce((p, e) => {
                let t = e?.innerText?.trim()
                let h = t?.match0(/(\d+)\:\d+\:\d+$/) || 0
                let m = t?.match0(/(\d+)\:\d+$/) || 0
                let s = t?.match0(/(\d+)$/) || 0
                p += h * 60 * 60 + m * 60 + s * 1
                return p
              }, 0)
              return ` ${sum/60/60|0}:${sum/60%60|0}:${sum%60}`
            })()
          //adja(eleget0('//div[1]/div[@id="header-contents" and @class="style-scope ytd-playlist-panel-renderer"]/div[1]/div[@class="style-scope ytd-playlist-panel-renderer"]/div[@id="publisher-container" and @class="style-scope ytd-playlist-panel-renderer"]'),"afterend",`<div>${playtimesum}</div>`)

          var cb = kaisuu == 2 ? `<iframe referrerpolicy="no-referrer" src="https://www.youtube.com/embed/${eles2[0]}?playlist=${ eles2.join(",")}" id="ytplayer" type="text/html" allowfullscreen="" allow="picture-in-picture" width="320" height="180" frameborder="0"></p></iframe>` : "https://www.youtube.com/watch_videos?video_ids=" + eles2.join(",") + indexUrlQP;
          var embedHTML = `<iframe referrerpolicy="no-referrer" src="${cb}" id="ytplayer" type="text/html" width=320 height=180 frameborder=0 allowfullscreen>`
          var cb2 = cb
          var cbEsc = (cb2).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;').replace(/'/g, '&#39;')
          var title = `▶ ${indexNo+1}/${eles2.length} ${document.title}`
          popup(kaisuu == 2 ? cb : document.title + "\n" + cb, "", "right:0em; top:0em;max-width:40%;")
          GM.setClipboard(kaisuu == 2 ? cb : document.title + "\n" + cb2 + "\n");
          if (kaisuu != 2) $(`<div style="margin-left:4em;font-size:14px;" id="link4bm">${kaisuu==2?"埋め込み":"ブックマーク"}用リンク(${eles2.length})${playtimesum}<br><a href=${cb}>${title}</a></div>`).hide(0).insertAfter($('#logo')).show(150).delay(9999).hide(250, function() { $(this).remove() })
        }
        kaisuu = ++kaisuu % YOUTUBE_WATCH_ALTC_VARIATIONS;
        return false
      } else { //キューやプレイリスト再生状態ではない
        // makeContPlay()
        // return false
      }
    }
  }, false)

  return;

  function makeContPlay(option = "") {
    var inp = prompt(`${option=="shuffle"?"<シャッフル>\n\n":""}url Extract & Concat:\n複数のYouTubeの動画URLから連続再生URLを作ってクリップボードにコピーします\nYouTubeの動画URLを何行でも貼り付けてください\nYouTubeのURLになっていない行や文字列は全て読み飛ばされ、重複した動画は削除されます\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`)
    if (inp) {
      var urlcap = [];
      inp.split(/\s/).forEach(v => { urlcap = urlcap.concat(...[...v?.matchAll(/ttps?:\/\/www\.youtube\.com\/watch\?v=([a-zA-Z0-9_\-]{11})|ttps?:\/\/youtu\.be\/([a-zA-Z0-9_\-]{11})|ttps?:\/\/www\.youtube\.com\/shorts\/([a-zA-Z0-9_\-]{11})|ttps?:\/\/www\.youtube\.com\/watch_videos\?video_ids=([a-zA-Z0-9_\-,]{11,600})/gmi)].map(c => c.slice(1, 999))).filter(w => w) }) // 書式が混在していても登場順に収納する
      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 = ``
          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`
          }
          let con = CONFIRM_AT_CREATE_FROM_URLS ? 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 href=${url}>${title}</a></div>`).hide(0).insertAfter($('#logo')).show(150).delay(9999).hide(250, function() { $(this).remove() })
            GM.setClipboard(enumText)
            popup(enumText, "", "right:0em; top:0em;max-width:40%;")
          }
        }
        //  }
      }
    }
  }

  function hideSuggest() {
    if (HIDE_SUGGEST && location.href.indexOf('www.youtube.com/results?') !== -1) {
      [ //'//div/div/span[@id="title"  and (text()="Learn while you\'re at home" or text()="For you" or text()="People also watched" or text()="家にいながら学ぶ" or text()="あなたへのおすすめ" or text()="他の人はこちらも視聴しています")]/../../../../..', // 縦横
        //'//div[@class="style-scope ytd-shelf-renderer"]/h2[@class="style-scope ytd-shelf-renderer"]/span[@id="title" and contains(@class,"style-scope ytd-shelf-renderer") and (text()="Learn while you\'re at home" or text()="For you" or text()="People also watched" or text()="家にいながら学ぶ" or text()="あなたへのおすすめ" or text()="他の人はこちらも視聴しています")]/../../../..', // 縦1列
        'ytd-shelf-renderer,ytd-horizontal-card-list-renderer', // 縦1列「○○の最新の動画をお見逃しなく」「他の人はこちらも検索」
      ].forEach(xp => {
        $(elegeta(xp)).hide(HIDE_SUGGEST, function() { $(this).remove() }); // 検索結果に割り込むサジェストを隠す
      });
    }
  }

  function run(node = document) {
    if (location.href == "https://www.youtube.com/" ||
      location.href.match(/https:\/\/www\.youtube\.com\/results\?.*(q=|search_query=)/) ||
      location.href.match("//www.youtube.com/channel/.*/search|//www.youtube.com/user/.*/search") ||
      (location.href.match("//www.youtube.com/channel/|//www.youtube.com/c/|//www.youtube.com/user/") && !(location.href.match("/community|/channels|/about|/playlists"))) ||
      location.href.match("//www.youtube.com/playlist") ||
      location.href.match("//www.youtube.com/watch")) {
      var place = eleget0('//div[@id="center" and @class="style-scope ytd-masthead"]');
    } else return;

    if (place) {
      if (USE_IMMEDIATE_PLAYLIST) {
        $('#playAllButton2,#extractAndConcatButton').remove();

        //$('ytd-logo yt-icon#logo-icon:not([data-rcli])').on("contextmenu", () => { makeContPlay(); return false; }); // ytアイコン右クリック
        $('ytd-topbar-logo-renderer#logo:not([data-rcli])').on("contextmenu", () => { makeContPlay(); return false; }); // ytアイコン右クリック
        var lmd = []; // ytアイコン右長押し
        var e = eleget0('ytd-topbar-logo-renderer#logo:not([data-rcli])')
        if (e) {
          e.dataset.rcli = 1;
          //e.style.boxShadow = "1px 1px 1px 0px #0004"
          e?.addEventListener("mouseup", e => e?.button && clearTimeout(lmd[e?.button]))
          e?.addEventListener("mousedown", e => {
            lmd[e.button] = setTimeout(e => {
              if (e.button == 2) { makeContPlay("shuffle"); return false; }
            }, 300, e)
          })
        }

        var playAllButton2 = $(`<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 immediately (right-click to shuffle)" id="playAllButton2">Immediate<br>Playlist${equeue.length?" ("+equeue.length+"+)":""}</span>`)
        playAllButton2.insertAfter(place);
        playAllButton2.on("contextmenu", () => { playAllG("shuffle"); return false; });
        playAllButton2.on("click", () => { playAllG(); return false; });
        playAllButton2.on("mousemove", () => {
          var [url, len] = getUrla();
          $('#playAllButton2').attr("title", `クリックで画面に出ている動画を限定公開プレイリストにして再生(右クリックだとシャッフル) (${len})\nGenerate playlist from all displayed videos and open immediately (right-click to shuffle)\n\n${url}`);
          return false;
        });
      }
      $('#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)" id="playAllButton">Play All</span>')
      playAllButton.insertAfter(place);
      playAllButton.on("contextmenu", () => { playAll("shuffle"); return false; });
      playAllButton.on("click", () => { playAll(); return false; });
      if (!playAllCount) {
        playAllCount = setInterval(() => {
          let currentLength = elegeta('//yt-icon[@class="style-scope ytd-menu-renderer"]').length;
          if (lastLength != currentLength) $('#playAllButton').html("Play All (" + currentLength + ")" + (DEBUG ? "<br>wait:" + wait : ""));
          lastLength = currentLength;
        }, 1000);
      }
    }
  }

  function playAllG(option = false) {
    setTimeout(pauseVideo, 17);
    var [url, len] = getUrla(option)
    if (len > 0)
      if (!CONFIRM_AT_CREATE_FROM_URLS || (CONFIRM_AT_CREATE_FROM_URLS && confirm(`下記を開きます。よろしいですか?(${len})\n\n${url}`))) location.href = url
  }

  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/"]:visible');
    if (videoEle.length) {
      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))]
      if (videoIDa.length) {
        videoIDa = ((option === "shuffle") ? shuffle(videoIDa) : videoIDa)
        var videoIDa = [...new Set([...equeue, ...videoIDa])].slice(0, 50)
        let url = `https://www.youtube.com/watch_videos?video_ids=${videoIDa.join(",")}`
        return [url, videoIDa.length]
      }
    } else return ["", 0];
  }

  function pauseVideo() {
    let e = eleget0('//video');
    if (e) { e.pause(); } else { setTimeout(pauseVideo, 17) }
  }

  function playAll(option = false) {
    setTimeout(pauseVideo, 17);
    let videoLength = elegeta('//yt-icon[@class="style-scope ytd-menu-renderer"]', document, 0).length;
    elegeta('//ytd-rich-item-renderer|//div[@id="dismissible"]', document.body, 0).forEach(e => { e.remove(); });
    let d = 0;
    let videoEle = elegeta('//yt-icon[@class="style-scope ytd-menu-renderer"]');
    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 = eleget0('//yt-formatted-string[text()="キューに追加"]|//yt-formatted-string[text()="Add to queue"]');
        if (queue) queue.click();
      }, d + wait / 2);
      d += wait + (videoLength / 5);
      if ((i++) > 201) break; // キューは200件までしか入らないので時間節約
    }
    d += wait * Math.min(7000, Math.max(2000, waitLast)) / 1000 + videoLength / 3;
    cli('//div[contains(@class,\"ytp-miniplayer-play-button-container\")]/button[@aria-label=\"再生(k)\"]|//button[@class="ytp-play-button ytp-button" and @aria-label="Play (k)"]', d);
    d += wait * Math.min(7000, Math.max(2000, waitLast)) / 1000 + videoLength / 3;
    if (!(location.href.match(/\/watch\?v=/))) cli('//div[@class="ytp-miniplayer-scrim"]/button[@aria-label="拡大(i)"]|//div[@class="ytp-miniplayer-scrim"]/button[@aria-label="Expand (i)"]', d, "infinity");
    d += wait;
    setTimeout(() => { let e = eleget0('//video'); if (e) { e.play(); } }, d);
  }

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

  function cli(xpath, wait, mode = "") { // mode: infinity:押せるまで監視し続ける
    setTimeout(() => {
      let ele = eleget0(xpath);
      if (ele) { ele.click(); } else if (mode === "infinity") { cli(xpath, 200, mode) }
    }, wait);
    if (eleget0(xpath)) { return true } else { return false }
  }

  function elegeta(xpath, node = document, onlyVisible = 1) {
    if (!xpath) return [];
    if (!/^\.?\//.test(xpath)) return /:visible$/.test(xpath) ? [...node.querySelectorAll(xpath.replace(/:visible$/, ""))].filter(e => e.offsetHeight) : [...node.querySelectorAll(xpath)]
    try {
      var array = [];
      var ele = document.evaluate("." + xpath, node, null, XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null);
      let j = 0;
      for (var i = 0; i < ele.snapshotLength; i++) {
        let ei = ele.snapshotItem(i);
        if (ei.offsetHeight) { if (onlyVisible) { array[j++] = ei; } } else { if (!onlyVisible) { array[j++] = ei; } }
      }
      return array;
    } catch (e) { return []; }
  }

  function eleget0(xpath, node = document) {
    if (!xpath || !node) return null;
    if (!/^\.?\//.test(xpath)) return /:visible$/.test(xpath) ? [...node.querySelectorAll(xpath.replace(/:visible$/, ""))].filter(e => e.offsetHeight)[0] ?? null : node.querySelector(xpath.replace(/:visible$/, ""));
    try {
      var ele = document.evaluate("." + xpath.replace(/:visible$/, ""), 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; 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('')
  }
})();