Greasy Fork

Greasy Fork is available in English.

5chサムネイル表示他

r(文字列未選択時):ホバー中のレスへのアンカーを記入(R:追記) r(文字列選択時):選択文字列を引用(R:追記) m:ホバー中のレスの10番前からを表示 ,:ホバー中のレス以降を表示 .:ホバー中のレス以前を表示 d:書き込み欄にスクロール y:ホバー中の画像をyandexで画像検索 l:ホバー中のレスへのリンクをコピー

当前为 2022-03-22 提交的版本,查看 最新版本

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         5chサムネイル表示他
// @description  r(文字列未選択時):ホバー中のレスへのアンカーを記入(R:追記) r(文字列選択時):選択文字列を引用(R:追記) m:ホバー中のレスの10番前からを表示 ,:ホバー中のレス以降を表示 .:ホバー中のレス以前を表示 d:書き込み欄にスクロール y:ホバー中の画像をyandexで画像検索 l:ホバー中のレスへのリンクをコピー
// @version      0.1.19
// @run-at       document-idle
// @match        *://*.5ch.net/test/read.cgi/*
// @match        *://*.5ch.net/*/
// @match        *://*.5ch.net/*/SETTING.TXT
// @match        *://*.shitaraba.net/bbs/read.cgi/*
// @match        *://*.shitaraba.net/bbs/read_archive.cgi/*
// @match        *://*.2chan.net/*
// @grant        GM_addStyle
// @grant        GM.setClipboard
// @grant       GM_setValue
// @grant       GM_getValue
// @grant       GM_deleteValue
// @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 enableHoverZoom = 1; // 1:画像サムネイルのホバーズームを有効
  const DEFAULT_MAIL_ADDRESS = ""; // メールアドレス初期値 ""、"sage"、その他に変更可
  const inlineImageThumbnailHeight = 65; // 画像サムネイルの縦サイズ(px)
  const inlineImageThumbnailBokashi = 0; // 画像サムネイルのぼかしの強さ(0~10くらい)
  const WAIT = performance.now() * 0.5; // ページ開始後のウエイト 不安定なときは大きくする
  const WAIT_IMAGE_EMBED_INTERVAL = 500; // 画像埋め込みの頻度(ミリ秒)
  const WAIT_VIDEO_EMBED_INTERVAL = 500; // 動画埋め込みの頻度(ミリ秒)
  const NUMBER_IMAGE_EMBED_AT_ONCE = 3; // 画像埋め込みの速度(枚数)
  const inlineImageThumbnailPreloadRadius = window.innerHeight * 1; // 画面外の上下何画面分までを「画面内」とするか
  const ALTERNATIVE_THUMBNAIL = 0; // 1:別方式のサムネイル
  const CONFIRM_FOR_Y = 1; // 1:yキーでyandex検索をする時URLの確認を求める 0:求めない
  const CONFIRM_FOR_MCP = 0; // 1:M,.キーでURLの確認を求める 0:求めない
  const QUOTE_STYLE = "color:#008080;"; // 引用文のスタイル "";なら無効
  const DEBUG_TIMER = 0; // 1ならかかった時間を計測

  const CH5_QUOTE_POPUP_SCALING_LOWER_LIMIT = 0.9; // 引用ポップアップが画面下に収まらない時縮小する下限
  const marginH = 200; // 元絵と拡大画像の横の余白px
  const POPUP_Z_INDEX = 10000; // ポップアップ画像がどれくらい手前に来るか
  const FORCE_USE_HALF_WIDTH = 0; // 1:ホバーズームで必ず画面の横半分のサイズ
  const marginPe = 8;
  const verbose = 0; // 1:debug
  const isfvw = eleget0('#fvw_menu')
  const MINIMUM_ROWS = (window.navigator.userAgent.toLowerCase().indexOf('firefox') != -1) ? 2 : 3;

  var poppedUrl = "";
  var yandexUrl = "";
  var mousex = 0;
  var mousey = 0;
  var lastEle = "";
  var resFloat = false;
  var resListen = false;
  var maet = 0;

  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] }
  $.fn.animate2 = function(properties, duration, ease) {
    ease = ease || 'ease';
    var $this = this;
    var cssOrig = { transition: $this.css('transition') };
    return $this.queue(next => {
      properties['transition'] = 'all ' + duration + 'ms ' + ease;
      $this.css(properties);
      setTimeout(function() {
        $this.css(cssOrig);
        next();
      }, duration);
    });
  };

  if (/\.2chan\.net/.test(new URL(location.href).hostname)) { // ふたば
    $('#ftxa').css({ "padding": "0.5em", "line-height": "1.2", "font-family": "sans-serif" })
    setFloatKakikomi2({ textareaXP: '#ftxa', submitbuttonXP: '//input[@type="submit" and @value="返信する"]', wid: '55%', minwid: 700 })
    $('#ftxa').on("change click", () => { floatKakikomi2({ textareaXP: '//textarea[@id="ftxa"]', submitbuttonXP: '//input[@type="submit" and @value="返信する"]', wid: '55%', minwid: 700 }) })
    // レスにホバーしてキー入力
    $(document).on("keypress", e => {
      if (e.target.tagName == 'INPUT' || e.target.tagName == 'TEXTAREA' || e.target.getAttribute('contenteditable') == 'true') return;
      var key = (e.shiftKey ? "Shift+" : "") + (e.altKey ? "Alt+" : "") + (e.ctrlKey ? "Ctrl+" : "") + e.key;
      var selectedStr = window.getSelection().toString() ? ">" + window.getSelection().toString().replace(/^\r\n/, "").replace(/\r?\n/g, "\r\n>").replace(/^\r?\n|\r?\n$/, "\r\n").replace(/([^\n])$/, "$1\n") : null;
      if ($('#ftxa').length) {
        if ((selectedStr && (key === "r" || key === "Shift+R"))) { // r::(文字列選択中)ホバー中のレスを引用(R::追記)
          $("#ftxa").attr("rows", MINIMUM_ROWS)
          $("#ftxa").val((key === "Shift+R" ? $("#ftxa").val() : "") + selectedStr)
          floatKakikomi2({ textareaXP: '//textarea[@id="ftxa"]', submitbuttonXP: '//input[@type="submit" and @value="返信する"]', wid: '55%', minwid: 700 })
          return false;
        }
      }
    });
    $('body').on('mouseup', function(e) {
      if (window.getSelection) {
        var selectedStr = window.getSelection().toString();
        if (selectedStr != '' && selectedStr != '\n') {
          popup2(`r:引用\nR:引用(追記)`, 5); //popup2(`『${selectedStr}』を\nr:引用\nR:引用(追記)`, 5);
        }
      }
    });
  }

  if (/shitaraba/.test(location.href)) { // したらば
    quote();
    setTimeout(hNukiURLHokan, 0);
    setInterval(videoUmekomi, WAIT_VIDEO_EMBED_INTERVAL);
    setInterval(imageUmekomi, WAIT_IMAGE_EMBED_INTERVAL);
    document.addEventListener("mousemove", function(e) {
      mousex = e.clientX;
      mousey = e.clientY;
    }, false);
    if (enableHoverZoom) setInterval(onmove, 16.667);
    setFloatKakikomi2({ textareaXP: '//textarea[@name="MESSAGE"]', submitbuttonXP: '//div[@id="form_write"]/form[@method="POST"]/input[@value="書き込む"]' })
    return;
  }

  if (location.href.match(/SETTING.TXT$/)) { // SETTING
    var e = document.body.innerText.match(/BBS_LINE_NUMBER=(\d*)/);
    if (e) { pref(getIta() + " : line_number", e[1]); }
    var e = document.body.innerText.match(/BBS_MESSAGE_COUNT=(\d*)/);
    if (e) { pref(getIta() + " : message_count", e[1]); }
    return;
  }

  function getIta() {
    let name = location.href.match(/^https?:\/\/.+\.5ch\.net\/test\/read.cgi\/([^\/]+)/) || location.href.match(/^https?:\/\/.+\.5ch\.net\/([^\/]+)/);
    if (name) name = name[1]
    return name;
  }
  var line_number = pref(getIta() + " : line_number") || null;
  var message_count = pref(getIta() + " : message_count") || null;

  if (location.href.match(/^https?:\/\/.+\.5ch\.net\/[^/]+\/$/) && eleget0('//div[last()]/form[@method="POST"]/p/input[@value="新規スレッド作成"]')) { // 板トップ
    elegeta('//div[@class="NEW_THREAD"]/form[@method="POST"]/p/textarea').forEach(mes => {
      mes.setAttribute("stretchabletextarea", "1");
      mes.setAttribute("wrap", "on");
      mes.style.width = "90%";
      mes.addEventListener("input", () => kakikomiStretch(mes));
      mes.addEventListener("focus", () => kakikomiStretch(mes));
    });
    elegeta('//textarea[not(@stretchabletextarea)]').forEach(mes => {
      mes.setAttribute("stretchabletextarea", "1");
      mes.setAttribute("wrap", "on");
      mes.style.width = "90%";
      mes.addEventListener("input", () => kakikomiStretch(mes, "nearest"));
      mes.addEventListener("focus", () => kakikomiStretch(mes, "nearest"));
    });
    return;

    function kakikomiStretch(target, scrollBlock = "center") {
      if (target.value == "") target.style.height = "100px";
      let lineHeight = target.style.height.match("px") ? Number((target.style.height || "0px").replace("px", "")) : 0; //getAttribute("rows"));
      let height = target.scrollHeight; //+12;
      let clientHeight = Math.min(document.documentElement.clientHeight, window.innerHeight) - 165;
      for (let i = 0; i < 200 && (height >= target.offsetHeight) && target.offsetHeight < clientHeight; i++) {
        lineHeight += 10;
        target.style.height = lineHeight + "px";
      }
      setTimeout(() => { target.scrollIntoView({ behavior: "smooth", block: scrollBlock, inline: "center" }); }, 17);
      displayLineLimit(target);
    }
  }

  if (/^https?:\/\/.+\.5ch\.net\/test\/read\.cgi\/.+/.test(location.href) == false) { return; }

  // httpならhttpsに
  if (location.href.indexOf("http://") != -1) {
    location.href = location.href.replace(/^http:\/\//, "https://");
    return;
  }
  if (location.href.indexOf("subback.html") != -1) { return; }
  if (location.href.match(/\/\/.*\.5ch\.net\/\w*\/$/)) { return; }

  $(".mascot").attr("style", "");
  let serverColor = colorFromText(new URL(location.href).hostname, 50, 30);

  $(document.body).append(`<span id="5chVerticalThreadTitle" style="writing-mode: vertical-rl; top:2em; right:0.3em;position:fixed;z-index:-111; font-size:3em; opacity:0.5; color:${serverColor};">${document.title}</span>`);
  $(eleget0('//ul/li[@class="menubottomnav"]/a[@class="menuitem" and text()="全部"]/../../..')).before(`<span style=" margin:0 0 0 0.2em;font-size:1.5em; opacity:0.9; color:${serverColor};">${document.title}</span>`);

  function colorFromText(txt, s, l) {
    return !txt ? "hsl(0,50%,50%)" : `hsl(${Array.from(txt).map(ch => ch.charCodeAt(0)).reduce((a, b) => a+b)**3%360}, ${s}%, ${l}%)`;
  }

  // レスにホバーしてキー入力
  $(document).on("keypress", e => {
    if (e.target.tagName == 'INPUT' || e.target.tagName == 'TEXTAREA' || e.target.getAttribute('contenteditable') == 'true') return;
    var key = (e.shiftKey ? "Shift+" : "") + (e.altKey ? "Alt+" : "") + (e.ctrlKey ? "Ctrl+" : "") + e.key;
    var selectedStr = window.getSelection().toString() ? ">" + window.getSelection().toString().replace(/^\r\n/, "").replace(/\r\n/g, "\r\n>").replace(/^\r\n|\r\n$/, "\r\n") : null;
    let num = getNearest('.post');
    let current = location.href + ((location.href.match(/^https?:\/\/.+\.5ch\.net\/test\/read\.cgi\/\w+\/\d+$/)) ? "/" : ""); // /18246213874 で終わるとバグる
    if ($('form>p>textarea').length) {
      if ((selectedStr && (key === "r" || key === "Shift+R"))) { // r::(文字列選択中)ホバー中のレスを引用(R::追記)
        let res = getNearest('div.message');
        if (num.id >= 2 || (num.id == 1 && $(window).scrollTop() < 500)) {
          $(getNearest('div.post')).effect("highlight", 500);
          floatKakikomi2({
            string: ">>" + num.id + "\r\n" +
              //            (selectedStr ? selectedStr : (res.innerText.replace(/^>.*$\n/gm, "").replace(/^(.+)$/gm, ">$1"))) + "\r\n",(e.key == "R"||e.key == "C") ); // 引用の引用をしない
              (selectedStr ? selectedStr : (res.innerText.replace(/^(.+)$/gm, ">$1"))) + "\r\n",
            //string: (selectedStr ? selectedStr : (res.innerText.replace(/^(.+)$/gm, ">$1"))) + "\r\n",
            addMode: (e.key == "R" || e.key == "C"),
            command: e.key == "r" ? "resetHeight" : null
          });
          //$('form>p>textarea').effect("highlight", 500);
        }
        return false;
      }
      if (key == "r" || key == "Shift+R") { // r::ホバー中のレスにアンカー R::レスにアンカー(追記)
        if (num.id >= 2 || (num.id == 1 && $(window).scrollTop() < 500)) {
          $(getNearest('div.post')).effect("highlight", 500);
          //          floatKakikomi2(">>" + num.id + "\n", e.key == "R", e.key == "r" ? "resetHeight" : null);
          floatKakikomi2({ string: ">>" + num.id + "\n", addMode: e.key == "R", command: e.key == "r" ? "resetHeight" : null });
          //$('form>p>textarea').effect("highlight", 500);
        }
        return false;
      }
    }
    if (key === "m" || key === "," || key === "Shift+M" || key === "Shift+<") { // ,::そのレス以降を表示 m::そのレスの10個前以降を表示 (Shiftを押しながらだと新しいタブで開く)
      let num = getNearest('.post');
      if (num) {
        let num2 = (num.id) - (/m/i.test(key) ? 10 : 0);
        $(getNearest('div.post')).effect("highlight");
        let last = current.replace(/\/l\d+$/, "/").replace(/^.*\/([0-9]{0,4})?-?([0-9n]{0,4}?$)/gm, "$2");
        let url = current.replace(/\/l\d+$/, "/").replace(/\/[0-9\-]{0,4}-?[0-9n]{0,4}?$/gm, "/").replace(/(\d)$/, "$1/") + Math.max(1, num2) + "-" + last;
        if (CONFIRM_FOR_MCP && !confirm(url)) return;
        if (/Shift\+/i.test(key)) { window.open(url) } else { location.href = url; }
      }
      return false;
    }
    if (key === "." || key === "Shift+>") { // .::そのレス以前を表示 (Shiftを押しながらだと新しいタブで開く)
      let num = getNearest('.post');
      if (num) {
        let num2 = (num.id) - (e.key === "m" ? 10 : 0);
        $(getNearest('div.post')).effect("highlight");
        if (num2 > 1) {
          let url = current.replace(/\/l\d+$/, "/").replace(/(\/[0-9]{0,4})-?[0-9n]{0,4}?$/gm, "$1") + "-" + Math.max(1, num2);
          if (CONFIRM_FOR_MCP && !confirm(url)) return;
          //          location.href = url;
          if (/Shift\+/i.test(key)) { window.open(url) } else { location.href = url; }
        }
      }
      return false;
    }
    if (key === "l") { // l::ホバー中のレスへのリンクをコピー
      if (num) {
        $(getNearest('div.post')).effect("highlight", 500);
        GM.setClipboard(document.title + "\r\n" + location.href.replace(/\/l\d+$/, "").replace(/\/[0-9\-]{0,4}-?[0-9n]{0,4}?$/gm, "/").replace(/(\d)$/, "$1/") + num.id + "\r\n");
      }
      return false;
    }
    /*    if (key === "Shift+F") { // F::re.Find2chで検索
          let query = prompt("re.Find2chでキーワード検索します\n\nhttps://refind2ch.org/search?q=${キーワード}&sort=rate\n\n");
          if (query) window.open(`https://refind2ch.org/search?q=${query}&sort=rate`);
          return false;
        }*/
    if (key === "y" && yandexUrl) { // y::ホバー中の画像をyandex画像検索で検索
      if (!CONFIRM_FOR_Y || window.confirm(yandexUrl + "\n\nを開きます。よろしいですか?")) window.open(yandexUrl);
      return false;
    }
  });

  $(document).on("keydown", e => {
    if (e.target.tagName == 'INPUT' || e.target.tagName == 'TEXTAREA' || e.target.getAttribute('contenteditable') == 'true') return;
    var key = (e.shiftKey ? "Shift+" : "") + (e.altKey ? "Alt+" : "") + (e.ctrlKey ? "Ctrl+" : "") + e.key;
    if ($('form>p>textarea').length) {
      if (key === "d") { // d::書き込み欄にスクロール
        scrollKakikomi(false);
      }
    }
  });

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

  if (enableHoverZoom) setInterval(onmove, 16.667);

  function onmove() {
    let ele = document.elementFromPoint(mousex, mousey);
    if (lastEle !== ele) {
      $('img.hzP').remove();
      poppedUrl = "";
    }
    if (ele)
      if (ele.tagName === "IMG" && lastEle !== ele) {
        poppedUrl = pe(ele);
        yandexUrl = poppedUrl.match(/\;base64\,/i) ? null : "https://yandex.com/images/search?rpt=imageview&url=" + poppedUrl;
      }
    lastEle = ele;
  }

  function pe(a) {
    var panel = document.createElement("img")
    if (!a) return;
    var src = ""
    if (a.parentNode && a.parentNode.tagName == "A" && a.parentNode.href.match(/\.png|\.jpg|\.jpeg|\.gif|\.bmp/i)) {
      src = decodeURIComponent(a.parentNode.href.replace(/https?:\/\/jump\.5ch\.net\/\?|https?:\/\/jbbs\.shitaraba\.net\/bbs\/link\.cgi\?url=/, ""));
    } else if (a.parentNode && a.parentNode.parentNode && a.parentNode.parentNode.tagName == "A" && a.parentNode.parentNode.href.match(/\.png|\.jpg|\.jpeg|\.gif|\.bmp/i)) {
      src = decodeURIComponent(a.parentNode.parentNode.href.replace(/https?:\/\/jump\.5ch\.net\/\?|https?:\/\/jbbs\.shitaraba\.net\/bbs\/link\.cgi\?url=/, ""));
    }
    panel.className = "hzP"; //notifyMe(panel.src)
    panel.src = src;
    setSize(a, panel, a);
    document.body.appendChild(panel);
    panel.addEventListener('load', e => {
      setSize(e.target, e.target, a);
    });
    return panel.src;
  }

  function setSize(a, b, s) {
    var panel = b;
    let imgAspect = a.naturalWidth / a.naturalHeight; // svg等だとNaN 要.onload
    let clientAspect = window.innerWidth / 2 / window.innerHeight;

    let peStyle = 'margin:2px; border-radius:3px; color:#ffffff;  box-shadow:3px 3px 8px #0008; border:2px solid #fff;';
    let boxPos = (mousey < (window.innerHeight / 2) ? "bottom:0px;" : "top:0px;") + ((mousex < document.documentElement.clientWidth / 2) ? "right:0px; " : "left:0px;");
    panel.className = "ignoreMe hzP";
    let amariWidth = (mousex < document.documentElement.clientWidth / 2) ? (document.documentElement.clientWidth - (s.getBoundingClientRect()).right) : (s.getBoundingClientRect().left);

    if (imgAspect && ((window.innerHeight * imgAspect - marginPe) < amariWidth - marginH)) {
      panel.setAttribute("style", `all:initial;float:none; width:${(window.innerHeight)*imgAspect-marginPe}px; height:${((window.innerHeight))-marginPe}px;${boxPos} z-index:${POPUP_Z_INDEX}; position:fixed; ${peStyle}`); // 縦目いっぱい
      if (verbose) popup(`余り左右:${amariWidth} naturalWidth:${a.naturalWidth} naturalHeight:${a.naturalHeight} a:${Math.round(imgAspect*100)/100} 縦目いっぱい`)
    } else if (imgAspect && (((amariWidth - marginH) / imgAspect) <= window.innerHeight)) { //&& (amariWidth - marginH - marginPe) > a.width * 2.5)) {
      panel.setAttribute("style", `all:initial;float:none; width:${amariWidth-marginH-marginPe}px; height:${(amariWidth-marginH)/imgAspect-marginPe}px;${boxPos} z-index:${POPUP_Z_INDEX}; position:fixed; ${peStyle}`); // 元絵の左右にくっつき最大
      if (verbose) popup(`余り左右:${amariWidth} naturalWidth:${a.naturalWidth} naturalHeight:${a.naturalHeight} a:${Math.round(imgAspect*100)/100} くっつき`)
    } else if (!imgAspect || window.innerWidth * 0.48 / imgAspect - marginPe <= window.innerHeight) {
      panel.setAttribute("style", `all:initial;float:none; width:${window.innerWidth*0.48-marginPe}px; height:${window.innerWidth*0.48/imgAspect-marginPe}px; ${boxPos} z-index:${POPUP_Z_INDEX}; position:fixed; ${peStyle}`); // 横48%
      if (verbose) popup(`余り左右:${amariWidth} naturalWidth:${a.naturalWidth} naturalHeight:${a.naturalHeight} a:${Math.round(imgAspect*100)/100} width:${window.innerWidth*0.48-marginPe}px; height:${window.innerWidth*0.48/imgAspect-marginPe}px; 48%`)
    } else {
      panel.setAttribute("style", `all:initial;float:none; width:${(window.innerHeight)*imgAspect-marginPe}px; height:${((window.innerHeight))-marginPe}px;${boxPos} z-index:${POPUP_Z_INDEX}; position:fixed; ${peStyle}`); // 縦目いっぱい
      if (verbose) popup(`余り左右:${amariWidth} naturalWidth:${a.naturalWidth} naturalHeight:${a.naturalHeight} a:${Math.round(imgAspect*100)/100} 縦目いっぱい2`)
    }
  }

  function getNearest(xpath) {
    let e = document.elementFromPoint(mousex, mousey);
    return e.closest(xpath)
  }

  // 書き込み欄までスクロール
  function scrollKakikomi(loop = true) {
    if (resFloat) return;
    var ele = eleget0('//div[@class="formbody"]/form/p/textarea');
    if (ele) {
      var $target = $('p>input.submitbtn'),
        offset = $target.offset() || { top: 0, left: 0 },
        outerHeight = $target.outerHeight();
      $("html,body").animate({ scrollTop: (offset.top - window.innerHeight + outerHeight) });
    } else if (loop) setTimeout(scrollKakikomi, 500);
  }

  //  setTimeout(hNukiURLHokan, WAIT);
  setTimeout(hNukiURLHokan, 0);
  setTimeout(scrollKakikomi, 1);
  setTimeout(scrollKakikomi, WAIT);

  setTimeout(() => {
    setInterval(videoUmekomi, WAIT_VIDEO_EMBED_INTERVAL);
    setInterval(imageUmekomi, WAIT_IMAGE_EMBED_INTERVAL);

    let ma = $(xa('//input[@placeholder="メールアドレス(省略可)"]'));
    ma.dblclick(() => {
      ma.val(ma.val() == "sage" ? "" : "sage");
      floatKakikomi2();
    });

    // 細かい調整
    $("div.formbox").css("margin", "0");
    $('input[placeholder="メールアドレス(省略可)"]').val(DEFAULT_MAIL_ADDRESS);
    //$(eleget0('//p/input[@name="mail"]')).css("ime-mode", "inactive");
    $("form>p>textarea").click(() => {
      floatKakikomi2();
    });

    quote();

  }, WAIT);

  return;

  function quote() {
    if (QUOTE_STYLE != "") {
      elegeta('//div[@class="message"]|//dd').forEach(e => {
        if (/^〉|》|>|>[^>\d]+/m.test(e.textContent)) {
          e.outerHTML = e.outerHTML.replace(/<span class="escaped">\s*(((〉|》|>|&gt;)[^>]+))(?!([^<]+)?>)/gmi, `<span class="t5quote" style="${QUOTE_STYLE}">$1</span>`).replace(/<dd>\s*(((〉|》|>|&gt;)[^>]+))(?!([^<]+)?>)/gmi, `<dd> <span class="t5quote" style="${QUOTE_STYLE}">$1</span>`).replace(/<br>\s*(((〉|》|>|&gt;)[^>]+))(?!([^<]+)?>)/gmi, `<br> <span class="t5quote" style="${QUOTE_STYLE}">$1</span>`)
        }
      }); // 引用に着色,タグの外側だけ置き換え // これをやるとシステムのサムネ添付が終わる

      setTimeout(() => { popuponquote() }, location.href.match0(/shitaraba/) ? 2000 : 0)
    }
  }

  // h抜きのURLをリンクにする
  function hNukiURLHokan() {
    document.querySelectorAll("div.post,dd").forEach(function(obj) {
      var html = obj.innerHTML;
      if (obj.innerText.match(/^ttps?:\/\//gm)) {
        var newhtml = html.replace(/[^h](ttps?:\/\/[^<]+)/gm, "<a referrerpolicy='no-referrer' rel='nofollow external noopener noreferrer' href=\"h$1\">$1</a>");
        obj.innerHTML = newhtml;
      }
    });
  }

  function setFloatKakikomi2({ textareaXP = '//form/p/textarea[@name="MESSAGE"]', submitbuttonXP = '//input[@class="submitbtn btn"]', wid = "70%", minwid = 900 } = {}) {
    var mes = eleget0(textareaXP);
    if (mes) {
      mes.addEventListener("input", () => floatKakikomi2({ textareaXP: textareaXP, submitbuttonXP: submitbuttonXP, wid: wid, minwid: minwid }));
      mes.addEventListener("focus", () => floatKakikomi2({ textareaXP: textareaXP, submitbuttonXP: submitbuttonXP, wid: wid, minwid: minwid }));
      resListen = 1;
    }
  }

  // 書き込み欄調整、クリックでフロート化
  function floatKakikomi2({ string = "", addMode = 0, command = "", textareaXP = '//form/p/textarea[@name="MESSAGE"]', submitbuttonXP = '//input[@class="submitbtn btn"]', wid = '70%', minwid = 900 } = {}) {
    var textarea = eleget0(textareaXP);
    var submitbutton = eleget0(submitbuttonXP)
    if (!resFloat) {
      $(window).resize(() => {
        $(textarea).css({ "width": wid, "min-width": Math.min(minwid, (window.innerWidth - 100)) + "px", "max-width": (window.innerWidth - 100) + "px" }).attr("wrap", "on").attr("tabIndex", "1");
        kakikomiStretch2(textareaXP, "resetHeight")
      });
    }
    resListen || setFloatKakikomi2()

    resFloat = true;
    let curRes = $(textarea).val();
    $(textarea).css({ "z-index": "999", "position": "fixed", "right": "1em", "bottom": "3em", "height": "auto" }).attr("tabIndex", "1");
    $(textarea).css({ "width": wid, "min-width": Math.min(minwid, (window.innerWidth - 100)) + "px", "max-width": (window.innerWidth - 100) + "px" }).attr("wrap", "on").attr("tabIndex", "1");
    $(submitbutton).css({ "z-index": "999", "position": "fixed", "right": "1em", "bottom": "0em" }).attr("tabIndex", "2");
    string && $(textarea).val((addMode ? curRes + ((curRes == "" || curRes.slice(-1) == "\n") ? "" : "\n") : "") + string);
    $(textarea).focus().attr("stretchabletextarea", "1");
    if ((textarea).value == "" || command === "resetHeight") textarea.rows = MINIMUM_ROWS;
    kakikomiStretch2(textareaXP);
  }

  function kakikomiStretch2(xp, command = "") {
    let target = eleget0(xp);
    if (target.value == "" || command === "resetHeight") target.rows = MINIMUM_ROWS;
    let lineHeight = Number(target.getAttribute("rows"));
    let height = target.scrollHeight; //+12;
    let clientHeight = Math.min(document.documentElement.clientHeight, window.innerHeight) - 165;
    for (let i = 0; i < 90 && (height >= target.offsetHeight) && target.offsetHeight < clientHeight; i++) {
      lineHeight++;
      target.setAttribute("rows", lineHeight);
    }
    displayLineLimit(target);
    return target;
  }


  function displayLineLimit(target) {
    let line = target.value.split(/\r\n|\r|\n/).length;
    if (line_number && message_count) {
      target.style.backgroundColor = line > line_number * 2 ? "#fff0f0" : (new Blob([target.value]).size) > message_count ? "#fffff0" : "#ffffff";
    }
  }

  function sortDescendMiddle(array) {
    return array.sort((a, b) => {
      return Math.abs(a.getBoundingClientRect().top - document.documentElement.clientHeight / 2) > Math.abs(b.getBoundingClientRect().top - document.documentElement.clientHeight / 2) ? 1 : -1
    });
  }

  // 画像と動画をインライン埋め込み
  function imageUmekomi() {

    // 画像埋め込み
    var i = 0;

    sw1("removeSysThumbs");
    for (let a of sortDescendMiddle(elegeta('//a[@imge="af"]/div[@div="thumb5ch"]'))) { if (isinscreen(a)) setTimeout((function(a) { return function() { { a.remove(); } } })(a), WAIT_IMAGE_EMBED_INTERVAL * 3 / NUMBER_IMAGE_EMBED_AT_ONCE) }
    sw2("removeSysThumbs");

    sw1("umegazo")
    for (let ele of sortDescendMiddle(elegeta('//a[not(@imge)]'))) {
      let isImg = 0;
      var url = ele.href || ($(ele).text());
      url = url.replace(/^(ttps?:\/\/)/m, "h$1");
      try {
        var urlImg = decodeURIComponent(url.replace(/https?:\/\/jump\.5ch\.net\/\?|https?:\/\/jbbs\.shitaraba\.net\/bbs\/link\.cgi\?url=/, "")) //.replace(/https?:\/\/jbbs\.shitaraba\.net\/bbs\/link\.cgi\?url=/,"");
      } catch (e) { var urlImg = url.replace(/https?:\/\/jump\.5ch\.net\/\?|https?:\/\/jbbs\.shitaraba\.net\/bbs\/link\.cgi\?url=/, "") }
      if ($(ele).text().match(/\.jpg|\.jpeg|\.png|\.gif|\.bmp/)) { isImg = 1; } else { isImg = 0; }
      if (!isImg) ele.setAttribute("imge", "!i");
      if ((!isinscreen(ele))) continue; // 画面内に無い
      if (isImg) { //notifyMe(url)//notifyMe(decodeURIComponent(urlImg))
        let next = ele.children ? ele.children[0] : null;
        if (next && next.tagName === "DIV") { // システムのサムネイルあり
          if (ALTERNATIVE_THUMBNAIL) next.outerHTML = '<br><a class="ignoreMe" referrerpolicy="no-referrer" rel="nofollow external noopener noreferrer" href=' + url + ' target="_blank"><img referrerpolicy="no-referrer" src=' + urlImg + ' height="' + inlineImageThumbnailHeight + '" ' + (inlineImageThumbnailBokashi ? 'style="filter: blur(' + inlineImageThumbnailBokashi + 'px);"' : '') + '/></a>';
          ele.setAttribute("imge", "rp");
        } else { // if ( /^ttp/.test(ele.textContent)){ // システムのサムネ添付はhtmlに変更があると諦めて中断される
          $(ele).after($('<br><a class="ignoreMe" referrerpolicy="no-referrer" rel="nofollow external noopener noreferrer" href=' + url + ' target="_blank"><img class="ignoreMe" referrerpolicy="no-referrer" src=' + urlImg + ' height="' + inlineImageThumbnailHeight + '" ' + (inlineImageThumbnailBokashi ? 'style="filter: blur(' + inlineImageThumbnailBokashi + 'px);"' : '') + '/></a>'));
          ele.setAttribute("imge", "af");
        }
        if (++i >= NUMBER_IMAGE_EMBED_AT_ONCE) break; // 一度に設定枚数ずつしかやらない
      }
    }
    sw2("umegazo")

  };

  function videoUmekomi() {
    sw1("umedouga")
    // ニコ動埋め込み(PrivacyBadger等は要Disable)
    for (let ele of sortDescendMiddle(elegeta('//a[contains(@href,"nicovideo")][not(@nde)]'))) {
      if ((!isinscreen(ele))) continue; // 画面内に無い
      let url = ele.innerText.replace(/^ttp/i, "http");
      ele.setAttribute("nde", "nde");
      let nico = url.match(/h?ttps?:\/\/www.nicovideo.jp\/watch\/(.*)/i);
      if (!nico) continue
      $(ele).after(`<br><iframe class="ignoreMe" referrerpolicy="no-referrer" rel="nofollow external noopener noreferrer" allowfullscreen="allowfullscreen" allow="autoplay" src="https://embed.nicovideo.jp/watch/${nico[1]}${nico[1].match0(/\?/)?"&":"?"}persistence=1&amp;oldScript=1&amp;allowProgrammaticFullScreen=1" style="max-width: 100%;" width="312" height="176" frameborder="0"></iframe>`) // 埋め込み外部プレイヤー版
      break; // 一度に1つずつしかやらない
    }
    sw2("umedouga")
    sw1("umeYT")

    // youtube埋め込み
    for (let ele of sortDescendMiddle(elegeta('//a[contains(@href,"youtube.com")][not(@yte)]|//a[contains(@href,"youtu.be")][not(@yte)]'))) {
      if ((!isinscreen(ele))) continue; // 画面内に無い
      let url = ele.innerText;
      ele.setAttribute("yte", "yte");
      var sm = (url.match(/h?ttps?:\/\/youtu\.be\/([^&?]+.*)[\?\&]t=(\d*).*$/i) || url.match(/h?ttps?:\/\/w?w?w?m?\.youtube\.com\/watch\?v=([^&]+.*)&t=(\d*).*$/i)) || url.match(/h?ttps?:\/\/youtu\.be\/([^&?]+)/i) || url.match(/h?ttps?:\/\/w?w?w?m?\.youtube\.com\/watch\?v=([^&]+)/i);
      if (!sm) continue
      $(ele).after('<p class="ignoreMe" style="margin:0 0 0px;"><iframe class="ignoreMe" referrerpolicy="no-referrer" src="https://www.youtube.com/embed/' + sm[1] + (sm[2] ? "?start=" + sm[2] : "") + '" id="ytplayer" type="text/html" width=320 height=180 frameborder=0 allowfullscreen allow="picture-in-picture"></p>');
      break; // 一度に1つずつしかやらない
    };
    sw2("umeYT")

    // video.twimg埋め込み
    for (let ele of sortDescendMiddle(elegeta('//a[contains(@href,"ttps://video.twimg.com/ext_tw_video/")][not(@yte)]|.//a[contains(@href,"ttps://i.imgur.com/") and contains(@href,".mp4")][not(@yte)]'))) {
      if ((!isinscreen(ele))) continue; // 画面内に無い
      let url = ele.innerText;
      url = url.replace(/^ttp/i, "http");
      ele.setAttribute("yte", "yte");
      var poster = url.match0('https://i.imgur.com/') ? ` poster="${url.replace(/\.mp4/,".jpg")}" ` : "";
      $(ele).after(`<p class="ignoreMe"><video class="ignoreMe" referrerpolicy="no-referrer" ${poster} rel="nofollow external noopener noreferrer" width="312" height="176" allowfullscreen="allowfullscreen"  preload="none" src="${ url }" controls="" loop="" > <source src="${url}" type="video/mp4"> </video></p>`)
      break; // 一度に1つずつしかやらない
    };
    sw2("umeVideoTwimg")
  }

  // eleはスクロール画面内に入ってる?
  function isinscreen(ele) {
    if (!ele) return;
    var eler = ele.getBoundingClientRect();
    return (eler.top > 0 - inlineImageThumbnailPreloadRadius && eler.left > 0 && eler.left < document.documentElement.clientWidth && eler.top < Math.min(window.innerHeight, document.documentElement.clientHeight) + inlineImageThumbnailPreloadRadius);
  }

  function elegeta(xpath, node = document) {
    if (!xpath) return [];
    if (!/^\.?\//.test(xpath)) return [...document.querySelectorAll(xpath)];
    try {
      var array = [];
      var ele = document.evaluate("." + xpath, node, null, XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null);
      let l = ele.snapshotLength;
      for (var i = 0; i < l; i++) array[i] = ele.snapshotItem(i);
      return array;
    } catch (e) { popup(e + "\n" + xpath); return []; }
  }

  function eleget0(xpath, node = document) {
    if (!xpath) return null;
    if (!/^\.?\//.test(xpath)) return document.querySelector(xpath);
    try {
      var ele = document.evaluate(xpath, node, null, XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null);
      return ele.snapshotLength > 0 ? ele.snapshotItem(0) : "";
    } catch (e) { popup(e + "\n" + xpath); return null; }
  }

  function xa(xpath, node = document) {
    if (!xpath) return [];
    if (xpath.match(/^\//)) {
      try {
        var array = [];
        var ele = document.evaluate("." + xpath, node, null, XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null);
        let l = ele.snapshotLength;
        for (var i = 0; i < l; i++) array[i] = ele.snapshotItem(i);
        return array;
      } catch (e) { return []; }
    } else {
      return $(xpath);
    }
  }

  function sw1(s) {
    if (DEBUG_TIMER) console.time(s);
  }

  function sw2(s) {
    if (DEBUG_TIMER) console.timeEnd(s);
  }

  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) {
          alert("データベースがバグってるのでクリアします\n" + e);
          pref(name, []);
          return;
        }
      } else return data;
    }
    if (store === "" || store === []) { // 書き込み、削除
      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) {
        alert("データベースがバグってるのでクリアします\n" + e);
        pref(name, "");
      }
      return store;
    }
  }

  function popup(text, color = "#6080ff") {
    text = String(text);
    text = text.replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/'/g, "&#39;").replace(/`/g, '&#x60;').replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/\n/gm, "<br>")
    var e = document.getElementById("hzbox");
    if (e) { e.remove(); }
    var e = document.body.appendChild(document.createElement("span"));
    e.innerHTML = '<span id="hzbox" style="all:initial; position: fixed; right:1em; top: 1em; z-index:1000000; opacity:1; font-size:15px; font-weight:bold; margin:0px 1px; text-decoration:none !important; text-align:left; padding:1px 6px 1px 6px; border-radius:12px; background-color:' + color + '; color:white; white-space: nowrap;" onclick=\'var a = document.createElement(\"textarea\"); a.value = \"' + text.replace(/<br>/gm, "\\n") + '\"; document.body.appendChild(a); a.select(); document.execCommand(\"copy\"); a.parentElement.removeChild(a);\'">' + text + '</span>';
    setTimeout((function(e) { return function() { e.remove(); } })(e), 5000);
  }

  function popup2(text, i = 0) {
    text = text.replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/'/g, "&#39;").replace(/`/g, '&#x60;').replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/\n/gm, "<br>")
    var mae = eleget0('//span[@id="yhmbox"]');
    if (maet && mae) {
      mae.remove();
      clearTimeout(maet);
    }
    let bgcol = (pref("translucent") != "on") ? "#6080ff" : "#909090";
    var ele = $('<span id="yhmbox" class="ignoreMe" style="all:initial; font-family:sans-serif; cursor:pointer; position: fixed; right:0em; bottom: ' + (i * 2 + 2) + 'em; z-index:2147483647; opacity:1; font-size:15px; margin:0px 1px; text-decoration:none !important; padding:1px 6px 1px 6px; word-break: break-all !important; border-radius:12px; background-color:' + bgcol + '; color:white; ">' + text + '</span>').appendTo('body');
    maet = setTimeout(function() {
      var mae = eleget0('//span[@id="yhmbox"]');
      if (mae) { mae.remove(); }
    }, 5000);
    $(ele).attr("title", "クリックでこのガイドを一時的に消す").click(function() {
      $(this).fadeOut(200).queue(function() {
        $(this).remove();
        clearTimeout(maet);
      })
    });
  }

  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 popuponquote() {
    // 引用ホバー
    GM_addStyle(".allpopup{color:#00f}")
    if (/\.shitaraba\.net\/bbs\/read_archive.cgi\//.test(location.href) && eleget0('//HTML/BODY/DL[1]')) { eleget0('//HTML/BODY/DL[1]').id = "thread-body" }
    if (eleget0('//div[@class="post" and not(@id)]')) elegeta('.date').forEach(e => { e.closest('.post').id = e.textContent.trim().match0(/^(\d+)\s:/) || ""; })

    var latestHover;
    var mesEleA = elegeta('.thread .message,#thread-body .message').slice(0, 1000); // したらばで1000以上のスレは重すぎて実質固まるので1000までしか処理しない
    elegeta('//div[@class="meta"]').slice(0, 1000).forEach(e => {
      let ep = e.closest(".post");
      if (ep) {
        let eID = ep.id
        let quoted = gatherRes(new RegExp(`>>${eID}[^0-9]`), ep, 1)
        if (quoted) { $(e).append(` <span class="allpopup" id="${eID}">\>\>ALL</span>`) }
      }
    })

    function gatherRes(eWord = "", ele = null, find = 0) {
      if (find) { // 高速化のため1つでも見つけたら帰る版
        let findEle = (mesEleA || []).find(e => e.textContent.match(eWord))
        if (findEle) return findEle.closest(".post")
        if (!ele) return false;
        let mesA = mesEleA
        return elegeta(".t5quote").slice(0, 1000).find(e => {
          var eWord0 = (e.textContent.match0(/^[>>]+(.+)$/m) || "").trim()
          if (eWord0 && e.closest(".post") != ele && ele.textContent.indexOf(eWord0) !== -1) { return e; } else { return null; }
        })
      }
      let gathered = mesEleA.filter(e => eWord ? e.textContent.match(eWord) : []) || []
      let mesA = mesEleA
      if (ele) { // eleが指定されているならそのレスにある文字列が引用されているものも列挙して連結
        gathered = gathered.concat(elegeta(".t5quote").slice(0, 1000).filter(e => {
          var eWord = (e.textContent.match0(/^[>>]+(.+)$/m) || "").trim()
          if (eWord && e.closest(".post") != ele && ele.textContent.indexOf(eWord) !== -1) { return e; } else { return null; }
        }))
      }
      let resNumA = gathered.map(e => e.closest(".post").id).sort((a, b) => a - b)
      resNumA = [...new Set(resNumA)]

      let gathered2 = []
      resNumA.forEach(e => {
        let b = eleget0(`//div[@id="${e}"]`).cloneNode(true);
        $(b).css({ "display": "block", "padding": "1em 2em 0.3em 2em", "border-bottom": "1px solid gray", "margin": "0", "display": "block" }) //.removeClass("rtdAttract");
        gathered2.push(b)
      })
      var quoteDesEle0 = gathered2.length ? gathered2[0] : null;
      return [gathered2, quoteDesEle0];
    }

    document.addEventListener("mousemove", function(e) {
      //    setInterval(() => {
      var hoverEle = (document.elementFromPoint(mousex, mousey))
      if (!hoverEle) return
      mesEleA = elegeta('.thread .message,#thread-body .message').slice(0, 1000)
      if (hoverEle.className == "allpopup") {
        if (!hoverEle.closest(".post")) return;
        var [gathered, quoteDesEle0] = gatherRes(new RegExp(`>>${hoverEle.closest(".post").id}[^0-9]`), hoverEle.closest(".post"))
      } else
      if (hoverEle.className == "t5quote") {
        var eWord = (hoverEle.textContent.match0(/^[>>]+(.+)$/m) || "").trim();
        if (!eWord) return;
        var [gathered, quoteDesEle0] = gatherRes(new RegExp(eWord))
        if (gathered.length <= 1) return;
      }

      if (quoteDesEle0 && latestHover != hoverEle && !eleget0(`//span[contains(@class,"ch5pu")][@data-anker="${quoteDesEle0.id}"]`)) { // 新しく黄土色文字か>>ALLの上に入った
        let xr = mousex > window.innerWidth * 2 / 3
        let x = xr ? "right:0px;" : `left:${($(hoverEle).offset().left + $(hoverEle).outerWidth()*1/3)}px;`
        var hitpostsEle = $(`<span class="ch5pu ignoreFilter" id="ch5pu${quoteDesEle0.id}" data-anker="${quoteDesEle0.id}" style="z-index:0; background-color:#efefef; margin:3px 1em 3px 0em; box-shadow: 2px 2px 15px gray; border-bottom:0; outline-offset: -1px; position:absolute; ${x} top:${$(hoverEle).offset().top+$(hoverEle).outerHeight()-3}px;" ondblclick="this.remove();"></span>`)
        $(hitpostsEle).append(gathered)
        $("body").append(hitpostsEle);
        let eley = $(hoverEle).offset().top - $(window).scrollTop() + $(hoverEle).outerHeight() + 3;
        let marginheight = Math.min(window.innerHeight, document.documentElement.clientHeight) - eley
        let eleheight = $(".ch5pu").height()
        if (marginheight / eleheight < 1) $(".ch5pu").css({ "transform-origin": xr ? "top right" : "top left" }).delay(100).animate2({ "transform": `scale(${Math.max(CH5_QUOTE_POPUP_SCALING_LOWER_LIMIT,marginheight/eleheight)})`, "opacity": "1" }, 100)
      } else {
        //        if (latestHover != hoverEle && !hoverEle.closest(".ftbpu") && !hoverEle.closest(".ch5pu") && !hoverEle.closest(".post_hover") && !quoteDesEle0) { //&& !eWord) {
        if (latestHover != hoverEle && !hoverEle.closest(".ftbpu,.ch5pu,.post_hover") && !quoteDesEle0) { //&& !eWord) {
          $(".ch5pu").remove();
        }
      }
      latestHover = hoverEle
    }, false);
    //    }, 17 * 3)
  }

  function ct(callback, name = "test", time = 400) { console.time(name); for (let i = time; i--;) { callback() } console.timeEnd(name) } // 速度測定
})();