塬友に土源プラグインをおすすめする

「水源助手」という名前だが、以下の一行を追加すると、より良く機能します。
@match https://xjtu.app/*

:evergreen_tree:水源銭がダウンしてしまったため、元の投稿のリンクがなくなりました。このツールを利用している方がいらっしゃいましたら、ぜひ共有していただき、多くの便利機能(例えば、表情は誰が貼ったのかを確認できる機能など)を追加していますので、ご活用ください。

「いいね!」 5

:evergreen_tree: :plus: :hand_with_fingers_splayed: 復活できるの?
このプラットフォームで水源リンクを移動させたり開いたりできるの?

「いいね!」 4

直接主棟のデータをコピーして送ってもいいですか?:cowboy_hat_face:

「いいね!」 3

長すぎる(怠け者)

「いいね!」 2

[details=“まとめ”]

2022年4月12日更新:

現在、リメイク版の水源アシスタントは元のバージョンと統合され、現在は単一のバージョンとなっています。皆さんは元のスクリプトのアドレスでそのまま使用することができます:

https://greasyfork.org/zh-CN/scripts/429382-水源助手

以前、リメイク版のアドレスからスクリプトをインストールしていたユーザーは、Greasy Forkによって自動的に統合された元のバージョンのアドレスにリダイレクトされるはずです。ご自身で確認してみてください。


以下は2022年3月25日の元の投稿内容です:

リメイクの理由

「水源アシスタント」スクリプトは、無私奉仕で作成された@bluecatさんの作品です。当初はQQ表情を投稿時に使用するためのスクリプトとして作成され、その後、投稿下部に任意の表情を貼り付けるデフォルトで賛同者と表情を貼り付けたユーザーを表示反和諧語曖昧な要素を除去などの機能が追加されました。私は長い間「水源アシスタント」スクリプトを使用しており、このスクリプトは水源を閲覧する体験を大幅に向上させてくれました。私はさらに手を加え、モバイルブラウザでこのスクリプトを使用する方法を実現しました。投稿時現在、「水源アシスタント」スクリプトのインストール総数は113回に達しています。

しかしながら、長い間、スクリプトの表情を貼り付ける機能にはいくつかの小さな欠陥があり、時に不満を感じていました。例えば:

  • 表情を貼り付けたユーザーの領域がリアルタイムに更新されず、表情をクリックすると不協和感があります。

shuiyuan_retort_non_realtime

  • 一つの投稿に対する返信をクリックし、戻ってくると、以前の投稿下部の表情を貼り付けたユーザーの領域が展開されません。

shuiyuan_retort_disappear_after_click_reply

  • スクリプトが各投稿の表情を取得する際、毎回サーバーにリクエストを送信するため、サーバーからの制限がかかり、ロードできなくなることがあります。

先週、これらの問題を解決したくなり、開発者としての貢献の観点から、変更を元のコードに統合するのが最善であると考えました。しかし、元のスクリプトを修正して望みの効果を得ることは困難であると判明し、結局は新たに実装し直すことにしました。これにより、さらなる改良も実現しやすくなります。私はbluecatの「江湖の地位」を奪う意図は全くありません。スクリプトの創始者および主要貢献者は依然としてbluecatです。私とbluecatが将来どちらがスクリプトのメンテナンスに時間を費やすかは未定です。将来のある日、私は水源を閲覧する時間がなくなり、徐々にコミュニティから離れていくかもしれません。しかし、今日現在、私はリメイク版のスクリプトを作成し、このスクリプトが他のユーザーの閲覧体験を向上させる可能性があると考えているため、これを公開し、皆さんにもう一つの選択肢を提供します。

インストールアドレス:

(リメイク版のアドレスは削除されました。皆さんは統合された元のバージョンのアドレスに直接アクセスしてください:https://greasyfork.org/zh-CN/scripts/429382-水源助手

(bluecatのGPL v3オープンソースライセンスを継承)

ブックマークレット版:```javascript
(async() => {
“use strict”;
const e = “shuiyuanHelperConfig”;
if (window.shuiyuanHelperLoaded) return void console.log(“既にShuiyuan Helperが読み込まれているため、スキップします。”);
window.shuiyuanHelperLoaded = true;

const a = document.documentElement.classList.contains(“mobile-device”),
o = () => JSON.parse(document.getElementById(“data-preloaded”).getAttribute(“data-preloaded”));

let n = null;
let t = null;
const i = () => {
if (t) return t;
try {
t = JSON.parse(o().site).watched_words_replace;
} catch (e) {
console.error(e);
console.warn(“ソースからの監視用語の解析に失敗、組み込みの監視用語にフォールバックします。”);
t = {
[(?:\W|^)(${decodeURIComponent("%E5%82%BB")}\W*逼)(?=\W|$)]: “大ばか者”,
[(?:\W|^)(${decodeURIComponent("%E7%A5%9E")}経病)(?=\W|$)]: “小変態”,
[(?:\W|^)(${decodeURIComponent("%E7%8E%8B")}八蛋)(?=\W|$)]: “小可愛い”,
[(?:\W|^)(${decodeURIComponent("%E5%86%9A")}家鋏)(?=\W|$)]: “萌萌だ”,
[(?:\W|^)(${decodeURIComponent("%E5%A6%88")}売り切れ)(?=\W|$)]: “不要”,
[(?:\W|^)(${decodeURIComponent("%E5%AD%A4")}児)(?=\W|$)]: “小宝石”
};
}
return t;
};

const r = e => new Promise((a) => setTimeout(a, e));

const s = (e, a) => a?.length ? […e.querySelectorAll(a.join(", “))] : ;
const l = (e, a) => a?.length ? […e.querySelectorAll(a.map((e) => .${e}).join(”, "))] : ;

const _ = async e => {
const a = await Promise.allSettled(e);
for (const e of a) “rejected” === e.status && console.error(e.reason);
};

const c = [
{
id: “expand-who-liked”,
description: “デフォルトで賛同者リストを開く”,
enabledByDefault: true,
matchClass: “like-count”,
onMatch: e => {
e.shuiyuanHelperHandled || (e.shuiyuanHelperHandled = true, e.click());
}
},
{
id: “show-liked-usernames”,
description: “賛同者のユーザー名を表示”,
enabledByDefault: true,
matchClass: “who-liked”,
onMatch: e => {
const a = e => {
if (e.children.length > 1) return;
const a = document.createElement(“span”);
a.style.margin = “0 5px”;
a.innerText = e.firstElementChild.getAttribute(“title”);
e.appendChild(a);
};
if (!e.shuiyuanHelperObserver) {
const o = new MutationObserver((e) => {
for (const o of e) if (“childList” === o.type)
for (const e of o.addedNodes)
e.matches?.(“a.trigger-user-card”) && a(e);
});
o.observe(e, { subtree: true, childList: true });
e.shuiyuanHelperObserver = o;
}
for (const o of e.querySelectorAll(“a.trigger-user-card”))
a(o);
}
},
{
id: “show-retort-users”,
description: “全てのリアクションユーザーを表示”,
enabledByDefault: true,
matchClass: “post-retort-container”,
onMatch: e => {
const a = e.closest(“.topic-area”),
o = parseInt(a.getAttribute(“data-topic-id”), 10);
a.shuiyuanHelperData || (a.shuiyuanHelperData = { retortFetchQueue: , retortFetchTimeoutID: null });
const n = a.shuiyuanHelperData,
t = e.closest(“article”),
i = parseInt(t.getAttribute(“data-post-id”), 10),
s = e => e.matches?.(“button.post-retort”),
l = (e, a, o) => {
const n = document.createElement(“div”);
n.shuiyuanHelperGenerated = true;
a.shuiyuanHelperGenerated = true;
n.appendChild(a);
const t = a.firstElementChild.alt.slice(1, -1),
i = o.get(t) || ;
for (const e of i) {
const o = document.createElement(“span”);
o.textContent = e;
n.appendChild(o);
}
e.appendChild(n);
};

    const _ = async () => {
      const e = n.retortFetchQueue;
      if (e.length === 0) return;

      const c = () => {
        const e = n.retortFetchQueue;
        e.includes(i) || e.push(i);
        null === n.retortFetchTimeoutID && (n.retortFetchTimeoutID = setTimeout(_, 300));
      };

      try {
        const e = await fetch(`/t/${o}/posts.json?${e.map((e) => `post_ids[]=${e}`).join("&")}`, {
          headers: {
            "Content-Type": "application/json",
            "Accept": "application/json",
          },
          credentials: "include"
        });
        if (!e.ok) throw new Error(`${e.status} ${e.statusText || ""}`);
        const i = await e.json();
        const r = i.post_stream?.posts || [];
        for (const e of r) {
          const o = new Map((e?.retorts || []).map((e) => [e.emoji, e.usernames]));
          const n = a.querySelector(`article[data-post-id="${e.id}"] .post-retort-container`);
          if (n) {
            for (const e of [...n.children]) {
              if (s(e)) {
                const t = e.querySelector("span");
                if (t) t.remove();
                const r = document.createElement("div");
                r.classList.add("emoji-retorts");
                for (const i of o.get(e.querySelector("span").alt.slice(1, -1)) || []) {
                  const a = document.createElement("span");
                  a.textContent = i;
                  r.appendChild(a);
                }
                e.replaceWith(r);
              } else if (e.matches("div") && e.shuiyuanHelperGenerated) {
                l(n, e.firstElementChild, o);
                e.remove();
              } else {
                e.remove();
              }
            }
          }
        }
      } catch (error) {
        console.error("リアクションユーザーの取得に失敗しました:", error);
      } finally {
        n.retortFetchQueue.length > 0 ? n.retortFetchTimeoutID = setTimeout(_, 300) : n.retortFetchTimeoutID = null;
      }
    };

    if (!e.shuiyuanHelperObserver) {
      const a = new MutationObserver((e) => {
        let a = false;
        for (const o of e) {
          if ("childList" === o.type) {
            for (const e of o.addedNodes) {
              if (s(e) && !e.shuiyuanHelperGenerated) {
                a = true;
              }
            }
          }
        }
        a && c();
      });
      a.observe(e, { subtree: true, childList: true });
      e.shuiyuanHelperObserver = a;
    }
    e.firstElementChild && c();
  }
},
{
  id: "retort-all-emojis",
  description: "投稿下に任意の絵文字を貼れるようにする",
  enabledByDefault: true,
  matchClass: "has-limited-set",
  onMatch: e => {
    e.classList.remove("has-limited-set");
    const a = document.createElement("div");
    a.classList.add("emojis-container");
    let n = null;
    try {
      const e = window.require("discourse/templates/components/emoji-group-sections").default(window.Discourse).parsedLayout.block.statements,
        t = window.require("I18n"),
        i = [];
      i.push({ section: "デフォルト", emojis: JSON.parse(o().siteSettings).retort_allowed_emojis.split("|") });
      let r = null,
        s = [];
      const l = () => {
        null !== r && (i.push({ section: r, emojis: s }), s = []);
      };
      for (const o of e) {
        const e = o[1];
        if (Array.isArray(e)) {
          if ("i18n" === e[1]) {
            l();
            r = t.t(e[2][0]);
          } else if ("replace-emoji" === e[1]) {
            s.push(e[2][0].slice(1, -1));
          }
        }
      }
      l();
      n = i;
    } catch (e) {
      console.error(e);
      console.warn("全ての絵文字をソースから解析できませんでした");
    }

    for (const { section: e, emojis: t } of n || []) {
      const o = document.createElement("div");
      o.classList.add("emoji-section");
      o.innerHTML = `<h3>${e}</h3>`;
      const r = document.createElement("div");
      r.classList.add("emoji-grid");
      for (const e of t) {
        const a = document.createElement("button");
        a.classList.add("emoji-button");
        a.innerHTML = e;
        a.onclick = () => {
          const o = document.createElement("span");
          o.className = "emoji";
          o.innerHTML = e;
          const n = e.closest("article").querySelector(".post-body");
          if (n) {
            const t = document.createElement("span");
            t.className = "emoji-retort";
            t.innerHTML = o.outerHTML;
            n.appendChild(t);
          }
        };
        r.appendChild(a);
      }
      o.appendChild(r);
      a.appendChild(o);
    }
    e.appendChild(a);
  }
}

];

「いいね!」 2

このスクリプトは慎重に使用してください。専門家によるオーディットを依頼することはおろか、少なくとも自分で一度は確認するようにしてください。

「いいね!」 7

以前 Discourse 用のブラウザ拡張機能のコード構造を見たことがありますが、問題ないと思います。

「いいね!」 3

:evergreen_tree:またクォタ制限に達してしまった:rofl::rofl::rofl:

「いいね!」 2