Curator_Tools

添加删除按钮

Aby zainstalować ten skrypt, wymagana jest instalacje jednego z następujących rozszerzeń: Tampermonkey, Greasemonkey lub Violentmonkey.

You will need to install an extension such as Tampermonkey to install this script.

Aby zainstalować ten skrypt, wymagana jest instalacje jednego z następujących rozszerzeń: Tampermonkey, Violentmonkey.

Aby zainstalować ten skrypt, wymagana będzie instalacja rozszerzenia Tampermonkey lub Userscripts.

You will need to install an extension such as Tampermonkey to install this script.

Aby zainstalować ten skrypt, musisz zainstalować rozszerzenie menedżera skryptów użytkownika.

(Mam już menedżera skryptów użytkownika, pozwól mi to zainstalować!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

Będziesz musiał zainstalować rozszerzenie menedżera stylów użytkownika, aby zainstalować ten styl.

Będziesz musiał zainstalować rozszerzenie menedżera stylów użytkownika, aby zainstalować ten styl.

Musisz zainstalować rozszerzenie menedżera stylów użytkownika, aby zainstalować ten styl.

(Mam już menedżera stylów użytkownika, pozwól mi to zainstalować!)

// ==UserScript==
// @name:zh-CN      鉴赏家小工具
// @name            Curator_Tools
// @namespace       https://blog.chrxw.com
// @supportURL      https://blog.chrxw.com/scripts.html
// @contributionURL https://afdian.com/@chr233
// @version         1.17
// @description     添加删除按钮
// @description:zh-CN  添加删除按钮
// @author          Chr_
// @include         https://store.steampowered.com/curator/*
// @license         AGPL-3.0
// @icon            https://blog.chrxw.com/favicon.ico
// @grant           GM_addStyle
// ==/UserScript==

// 初始化
(() => {
  "use strict";

  const eleContainer = document.getElementById("subpage_container");
  if (eleContainer) {
    const observer = new MutationObserver(onPageLoad);
    observer.observe(eleContainer, { childList: true, subtree: true });
  }

  let lastPathname = "";
  let lastCount = 0;
  let t = 0;

  function onPageLoad() {
    if (lastPathname == location.pathname) {
      return;
    }
    lastPathname = location.pathname;

    if (t !== 0) {
      clearInterval(t);
      t = 0;
    }

    if (location.pathname.includes("admin/review_create")) {
      injectReviewCreate();
    } else if (location.pathname.includes("admin/reviews_manage")) {
      injectReviewManage();
    } else if (location.pathname.includes("admin/stats")) {
      injectStats();
    } else if (g_strCuratorAdminURL) {
      injectStoreList();
    }
  }

  onPageLoad();

  function genBtn(text, cls, foo) {
    const btn = document.createElement("button");
    btn.textContent = text;
    btn.className = cls;
    btn.addEventListener("click", foo);
    return btn;
  }
  function genA(url) {
    const a = document.createElement("a");
    a.href = url;
    a.target = "_blank";
    return a;
  }
  function genDiv(cls) {
    const div = document.createElement("div");
    div.className = cls;
    return div;
  }
  function genSpan(name) {
    const span = document.createElement("span");
    span.textContent = name;
    return span;
  }
  function genCheck(name, checked, foo) {
    const l = document.createElement('label');
    const i = document.createElement('input');
    const s = genSpan(name);
    s.className = "ct_option";
    i.textContent = name;
    i.title = name;
    i.type = 'checkbox';
    i.checked = !!checked;
    i.addEventListener('change', foo);
    l.appendChild(i);
    l.appendChild(s);
    return [l, i];
  }

  function injectReviewCreate() {
    const [_, curator, appid] = lastPathname.match(
      /\/curator\/([^\/]+)\/admin\/review_create\/?(\d+)?/
    ) ?? [null, null, null];

    if (curator) {
      const btnArea = document.querySelector("div.titleframe");

      if (appid) {
        const btn = genBtn(
          "删除该评测",
          "ct_btn btnv6_blue_hoverfade",
          async () => await deleteReview(curator, appid)
        );
        btnArea.appendChild(btn);
        const link = genA(`https://store.steampowered.com/app/${appid}`);
        const btn2 = genBtn("商店页", "ct_btn btnv6_blue_hoverfade");
        link.appendChild(btn2);
        btnArea.appendChild(link);
      } else {
        const appSuggest = document.querySelector("#app_suggest_id");
        const reviewType = document.querySelector('textarea[name="blurb"]');
        if (appSuggest && reviewType) {
          let suggestAppId = null;

          const btn = genBtn("编辑原先的评测", "ct_btn btnv6_blue_hoverfade");
          const link = genA("#");
          link.appendChild(btn);
          btnArea.appendChild(link);

          const btn2 = genBtn(
            "删除原先的评测",
            "ct_btn btnv6_blue_hoverfade",
            async () => await deleteReview(curator, suggestAppId)
          );
          btnArea.appendChild(btn2);

          const link3 = genA("#");
          const btn3 = genBtn("商店页", "ct_btn");
          link3.appendChild(btn3);
          btnArea.appendChild(link3);

          const spStatus = genSpan("test");
          btnArea.appendChild(spStatus);

          t = setInterval(() => {
            if (appSuggest.value !== suggestAppId) {
              suggestAppId = appSuggest.value;
              btn.disabled = true;
              btn2.disabled = true;
              link.href = "#";

              if (suggestAppId) {
                spStatus.textContent = "正在获取评测内容";

                getReviewText(curator, suggestAppId)
                  .then((text) => {
                    if (text) {
                      spStatus.textContent = "读取完成";
                      reviewType.value = text;
                      btn.disabled = false;
                      btn2.disabled = false;
                      link.href = `https://store.steampowered.com/curator/${curator}/admin/review_create/${suggestAppId}`;
                    } else {
                      spStatus.textContent = "未写过评测";
                    }
                  })
                  .catch((e) => {
                    spStatus.textContent = "读取失败";
                    console.error(e);
                  });

                link3.href = `https://store.steampowered.com/app/${suggestAppId}`;
                btn3.disabled = false;
              } else {
                spStatus.textContent = "未选择游戏";
                link3.href = "#";
                btn3.disabled = true;
              }
            }
          }, 500);
        }
      }
    }
  }

  function injectReviewManage() {
    const [_, curator] = lastPathname.match(
      /\/curator\/([^\/]+)\/admin\/reviews_manage\/?/
    ) ?? [null, null];

    if (curator) {
      setupManageHook();

      const pageBtns = document.querySelectorAll("#ReviewsManage_controls>span.pagebtn,#ReviewsManage_controls>span>span.ReviewsManage_paging_pagelink");
      pageBtns.forEach((btn) => {
        btn.addEventListener("click", setupManageHook);
      });
    }
  }

  function setupManageHook() {
    const t = setInterval(() => {
      const controlBtns = document.querySelectorAll("#reviews_container>label>div.controls>a.edit_list_icon");
      if (controlBtns.length === 0) {
        return;
      }

      clearInterval(t);

      controlBtns.forEach((btn) => {
        btn.addEventListener("click", (e) => {
          e.preventDefault();

          location.href = btn.href;
        });
      });
    }, 500);
  }

  function injectStats() {
    injectBtn();
    injectGotoBtn();

    lastCount = document.querySelectorAll(
      "#RecentReferralsRows td>.ct_div,#TopReferralsRows td>.ct_div"
    ).length;

    const spanList = document.querySelectorAll(
      "#RecentReferrals_controls>span,#RecentReferrals_controls>span>span,#TopReferrals_controls>span,#TopReferrals_controls>span>span"
    );
    for (let span of spanList) {
      span.addEventListener("click", updateInjectBtn);
    }
  }

  /**
   *  注入商店页测评列表
   */
  function injectStoreList() {
    const [_, curator] = lastPathname.match(
      /\/curator\/([^\/]+)\/?/
    ) ?? [null, null];

    if (curator) {
      const optionArea = document.querySelector(".browse_tabs");
      if (!optionArea) {
        return;
      }

      injectStoreBtn(curator);

      const isAdmin = document.querySelector(".navigation_bar").childElementCount >= 5;
      if (isAdmin) {
        const isGoToAdmin = localStorage.getItem("ct_goto_admin");
        const [lblEnable, chkEnable] = genCheck("跳转到编辑页", isGoToAdmin, (e) => {
          e.preventDefault();

          localStorage.setItem("ct_goto_admin", chkEnable.checked ? "true" : "");
        });
        optionArea.appendChild(lblEnable);
      }

      const isNewWindow = localStorage.getItem("ct_new_window");
      const [lblWindow, chkWindow] = genCheck("新窗口打开", isNewWindow, (e) => {
        e.preventDefault();

        localStorage.setItem("ct_new_window", chkWindow.checked ? "true" : "");
      });
      optionArea.appendChild(lblWindow);
    }
  }

  /**
   * 删除评测
   */
  async function deleteReview(curator, appid, ele = null) {
    ShowConfirmDialog("", "真的要删除这篇评测吗", "给我删", "手滑了").done(
      () => {
        fetch(
          `https://store.steampowered.com/curator/${curator}/admin/ajaxdeletereview/`,
          {
            method: "POST",
            credentials: "include",
            body: `appid=${appid}&sessionid=${g_sessionID}`,
            headers: {
              "content-type":
                "application/x-www-form-urlencoded; charset=UTF-8",
            },
          }
        )
          .then(async (response) => {
            if (response.ok) {
              showAlert("删除成功", true);

              setTimeout(() => {
                if (location.pathname.includes("review_create")) {
                  if (location.pathname.includes(appid)) {
                    location.pathname = `/curator/${curator}/admin/reviews_manage`;
                  }
                } else {
                  if (ele) {
                    ele.style.opacity = "0.5";
                  }
                }
              }, 500);
            } else {
              showAlert("删除失败", false);
            }
          })
          .catch((err) => {
            console.error(err);
            showAlert(`删除出错 ${err}`, false);
          });
      }
    );
  }

  function updateInjectBtn() {
    const t = setInterval(() => {
      const count = document.querySelectorAll(
        "#RecentReferralsRows td>.ct_div,#TopReferralsRows td>.ct_div"
      ).length;
      if (count != lastCount) {
        clearInterval(t);
        injectBtn();
        lastCount = document.querySelectorAll(
          "#RecentReferralsRows td>.ct_div,#TopReferralsRows td>.ct_div"
        ).length;
      }
    }, 500);
  }

  function injectBtn() {
    const tdList = document.querySelectorAll(
      "#RecentReferralsRows>table>tbody>tr>td:last-child,#TopReferralsRows>table>tbody>tr>td:last-child"
    );
    for (let td of tdList) {
      const a = td.childNodes[0];

      if (a.nodeName !== "A") {
        continue;
      }

      const div = genDiv("ct_div");
      div.appendChild(a);
      td.appendChild(div);

      const [_, curator, appid] = a.href.match(
        /\/curator\/([^\/]+)\/admin\/review_create\/(\d+)/
      ) ?? [null, null, null];

      if (curator !== null && appid !== null) {
        const btn = genBtn("删", "ct_btn btnv6_blue_hoverfade", async () =>
          deleteReview(curator, appid, td.parentNode)
        );
        div.appendChild(btn);

        getReviewType(curator, appid).then((type) => {
          let text = "";
          let color = "#fff";
          switch (type) {
            case 0:
              text = "推荐";
              color = "#a9be7b";
              break;
            case 1:
              text = "不推荐";
              color = "#9e2a22";
              break;
            case 2:
              text = "情报";
              color = "#ecd452";
              break;
            default:
              text = "错误";
              color = "#d3ccd6";
              break;
          }
          const span = genSpan(text);
          span.style.color = color;
          td.insertBefore(span, td.childNodes[0]);
        });
      }
    }
  }

  function injectGotoBtn() {
    const recentController = new CAjaxPagingControls(
      g_RecentReferralsPagingData,
      g_RecentReferralsPagingData["url"]
    );
    const recentCtn = document.querySelector(
      "#RecentReferrals_ctn > div:nth-child(2)"
    );
    recentCtn.appendChild(
      genBtn("跳转到...", "ct_btn2 btnv6_blue_hoverfade", () => {
        gotoPage(recentController);
      })
    );

    const topController = new CAjaxPagingControls(
      g_TopReferralsPagingData,
      g_TopReferralsPagingData["url"]
    );
    const topCtn = document.querySelector(
      "#TopReferrals_ctn > div:nth-child(2)"
    );
    topCtn.appendChild(
      genBtn("跳转到...", "ct_btn2 btnv6_blue_hoverfade", () => {
        gotoPage(topController);
      })
    );
  }

  function injectStoreBtn(curator) {
    const container = document.querySelector("#RecommendationsRows");
    if (!container) return;

    // 处理单个元素
    function processDiv(eleDiv) {
      if (eleDiv.dataset.ctInjected) return; // 避免重复注入
      const eleA = eleDiv.querySelector("a");
      if (!eleA) return;

      eleDiv.dataset.ctInjected = "1";

      eleDiv.addEventListener("click", (e) => {
        const isGoToAdmin = localStorage.getItem("ct_goto_admin");
        const isNewWindow = localStorage.getItem("ct_new_window");

        if (isGoToAdmin || isNewWindow) {
          e.preventDefault();
        } else {
          return;
        }

        if (isGoToAdmin) {
          const href = eleA.href;
          const [_, appId] = href.match(/\/app\/(\d+)/) ?? [null, null];

          const url = `https://store.steampowered.com/curator/${curator}/admin/review_create/${appId}`;
          if (isNewWindow) {
            window.open(url, "_blank");
          } else {
            location.href = url;
          }
        }
        else if (isNewWindow) {
          window.open(eleA.href, "_blank");
        }

      });

    }

    // 处理现有元素
    container.querySelectorAll("div>div[role]").forEach(processDiv);

    // 监听新增元素
    const observer = new MutationObserver((mutations) => {
      for (const mutation of mutations) {
        for (const node of mutation.addedNodes) {
          if (node.nodeType !== Node.ELEMENT_NODE) continue;
          // 新增的是父 div,查找其中的 div[role]
          node.querySelectorAll("div[role]").forEach(processDiv);
          // 也检查 node 本身
          if (node.matches("div[role]")) processDiv(node);
        }
      }
    });

    observer.observe(container, { childList: true, subtree: true });
  }

  function gotoPage(controller) {
    const dialog = ShowPromptDialog("请输入页码", "", "跳转", "取消");

    dialog.done((txt) => {
      const page = parseInt(txt);
      if (page !== page || page < 1) {
        showAlert("请输入有效数字", false);
        return;
      }

      controller.GoToPage(page - 1, true);
      updateInjectBtn();
    });

    dialog.fail(() => {
      dialog.Dismiss();
    });
  }

  function showAlert(text, succ = true) {
    return ShowAlertDialog(`${succ ? "✅" : "❌"}`, text);
  }

  //获取评测类型
  function getReviewType(curatorId, appId) {
    return new Promise((resolve, reject) => {
      fetch(
        `https://store.steampowered.com/curator/${curatorId}/admin/review_create/${appId}`,
        {
          method: "GET",
          credentials: "include",
        }
      )
        .then((response) => {
          if (response.ok) {
            return response.text();
          } else {
            resolve(-2);
          }
        })
        .then((data) => {
          const match = data.match(
            /"recommendation_state" value="(\d)" checked/
          );
          if (match) {
            resolve(parseInt(match[1]));
          } else {
            resolve(-1);
          }
        })
        .catch((err) => {
          console.error(err);
          resolve(-3);
        });
    });
  }

  //获取写过的评测
  function getReviewText(curatorId, appId) {
    return new Promise((resolve, reject) => {
      fetch(
        `https://store.steampowered.com/curator/${curatorId}/admin/review_create/${appId}`,
        {
          method: "GET",
          credentials: "include",
        }
      )
        .then((response) => {
          if (response.ok) {
            return response.text();
          } else {
            resolve(-2);
          }
        })
        .then((data) => {
          const parser = new DOMParser();
          const xmlDoc = parser.parseFromString(data, "text/html");
          const match = xmlDoc.querySelector('textarea[name="blurb"]');
          if (match) {
            resolve(match.value);
          } else {
            resolve(-1);
          }
        })
        .catch((err) => {
          console.error(err);
          resolve(-3);
        });
    });
  }
})();

GM_addStyle(`
.ct_btn {
  padding: 5px;
  margin-right: 10px;
}
.ct_btn2 {
  padding: 5px;
  margin-right: 10px;
}
td {
  height: 100%;
}
.ct_div {
  display: flex;
  align-content: center;
  align-items: center;
  height: 25px;
  width: 40px;
}
tr > td > .ct_div > .ct_btn {
  display: none;
}
tr:hover > td > .ct_div > .ct_btn {
  display: block;
}
span.ct_option {
  margin: 5px;
}

`);