Greasy Fork

Greasy Fork is available in English.

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

musictonicの代わり 右クリックだとシャッフル再生 e:カーソル下の動画をキューに入れる y:再生開始

当前为 2022-01-12 提交的版本,查看 最新版本

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name YouTube検索結果「全てキューに入れて再生」ボタンを追加
// @description musictonicの代わり 右クリックだとシャッフル再生 e:カーソル下の動画をキューに入れる y:再生開始
// @version      0.1.15
// @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
// @namespace http://greasyfork.icu/users/181558
// ==/UserScript==

(function() {
  const CLOSE_MINI_PLAYER_ALWAYS = 1; // 1:Escでミニプレイヤーを常に閉じる
  const AGREE_TO_CONTINUE_ALWAYS = 1; // 1:無操作一時停止を常に解除
  const HIDE_SUGGEST = 1000; // 1-:検索結果に割り込む「あなたへのおすすめ」「他の人はこちらも視聴しています」「家にいながら学ぶ」を隠す

  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 / 15)));
  const DEBUG = 0; // 1:wait値を表示

  var videoDisplayedLast = 0;
  var lastLength = 0;

  var playAllCount;

  //URLの変化を監視
  var href = location.href;
  var observer = new MutationObserver(function(mutations) {
    if (href !== location.href) {
      href = location.href;
      $('#playAllButton').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);
  }

  if (CLOSE_MINI_PLAYER_ALWAYS) setInterval(() => { // ミニプレイヤーを常に閉じる
    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();
  }, 701);

  var mousex = 0;
  var mousey = 0;

  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") { // e::enqueue
        e.preventDefault();
        var ele = document.elementFromPoint(mousex, mousey);
        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(() => { ancestorEle.style.opacity = 0.5 }, 0)
          setTimeout(() => { ancestorEle.style.opacity = 0.5 }, 17 * 2)
          setTimeout(() => { ancestorEle.style.opacity = 1 }, 17 * 4)
          setTimeout(() => { menuButton[0].click() }, 0);
          //alert("1 " + ancestorEle.outerHTML)
          setTimeout(() => {
            let queue = eleget0('//yt-formatted-string[text()="キューに追加"]|//yt-formatted-string[text()="Add to queue"]');
            //alert("2 " + queue.outerHTML)
            if (queue) {
              queue.click();
            }
          }, 100)
        }
        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;
      }
    }
  }, false)

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


  return;

  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列
      ].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) {
      $('#playAllButton').remove();
      var playAllButton = $('<span class="ignoreMe" style="cursor:pointer;color: #000; text-shadow: -1px -1px 0 #fff;" 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 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;
    //notifyMe(videoLength * 2)
    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"]');
    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);
    }
    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 [];
    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) return null;
    try {
      var ele = document.evaluate(xpath, node, null, XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null);
      if (ele.snapshotLength < 1) return "";
      let ei = ele.snapshotItem(0);
      if (ei.offsetHeight) return ei;
      return "";
    } catch (e) { 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;
  }
})();