- // ==UserScript==
- // @name mcbbs:将参与投票者的信息导出到csv
- // @namespace http://tampermonkey.net/
- // @version 0.3
- // @description https://www.mcbbs.net/thread-1464590-1-1.html
- // @author 破损的鞘翅
- // @match https://www.mcbbs.net/thread-*
- // @match https://www.mcbbs.net/forum.php?mod=viewthread*
- // @icon https://www.mcbbs.net/favicon.ico
- // @grant none
- // ==/UserScript==
- (function () {
- "use strict";
- // Your code here...
- class CSV {
- headers = [];
- datas = [];
- width = 0;
- constructor(...headers) {
- this.headers = headers.join(",");
- this.width = headers.length;
- }
- add(...data) {
- while (data.length < this.width) {
- data.push("");
- }
- this.datas.push(data.join(","));
- }
- saveAs(filename) {
- const a = document.createElement("a");
- a.href = URL.createObjectURL(new Blob([this.export], { type: "text/plain" }));
- a.download = filename + ".csv";
- document.body.append(a);
- a.click();
- a.remove();
- }
- get export() {
- const exports = [this.headers, ...this.datas];
- return exports.join("\r\n");
- }
- }
- async function fetchXml(url, init = {}) {
- const domParser = new DOMParser();
- const response = await fetch(url, init);
- const resText = (await response.text()) || "";
- const resXml = domParser.parseFromString(resText, "text/xml");
- return {
- html: domParser.parseFromString(resXml.documentElement.childNodes[0].data, "text/html"),
- raw: resText,
- };
- }
- async function getUserInfoByApi(uid) {
- const url = `https://mcbbs.wiki/rest.php/mbwutils/v0/credit/${uid}`;
- const {
- notfound,
- nickname = "",
- credits = {},
- activities: { currentGroupText },
- } = await fetch(url)
- .then((res) => res.json())
- .catch(() => {});
- if (notfound) {
- throw false;
- }
- return {
- uid,
- exp: credits.credit,
- nuggets: credits.nugget,
- emeralds: credits.gem,
- nickname,
- group: currentGroupText,
- };
- }
- async function getDingTie(uid) {
- console.log(uid);
- const result = await fetch(`https://auto.xmdhs.com/getforuid?uid=${uid}`).then((res) => res.json());
- return result.data?.length || 0;
- }
- async function getVoteResult(tid, page = 1, optionId = 0) {
- const result = []; //单个选项的投票结果
- const classifiedResult = {}; //投票结果的集合
- const url = new URL(`https://www.mcbbs.net/forum.php?mod=misc&action=viewvote&tid=${tid}&handlekey=viewvote&inajax=1`);
- url.searchParams.set("page", page);
- if (optionId > 0) {
- //optionId 大于0,为url添加参数,获取对应选项的投票结果。不加该参数则是获取第一个选项的结果。
- url.searchParams.set("polloptionid", optionId);
- }
- let { html, raw } = await fetchXml(url.href);
- while (html.querySelector("ul") == null) {
- //没获取到数据,1s后重试
- console.warn(`获取 ${url.href} 的数据失败:`, { raw });
- await new Promise((resolve) => setTimeout(resolve, 1000));
- ({ html, raw } = await fetchXml(url.href));
- }
- for (const li of html.querySelector("ul").children) {
- //将结果放入数组
- const uid = new URL(li.querySelector("a").href).searchParams.get("uid");
- result.push(uid);
- }
- if (html.querySelector(".nxt")) {
- //含有该元素,说明还有下一页,获取下一页的结果,放入数组
- const nextRes = await getVoteResult(tid, page + 1, optionId);
- result.push(...nextRes);
- }
- if (optionId > 0 || page > 1) {
- //optionId 大于 0 且 page 大于 1,说明该调用只获取单个选项的投票结果,返回之前获取的结果
- return result;
- } else {
- //反之,说明该调用是获取全部的投票结果。除了已经获取了的默认的结果,再获取其他选项的结果
- const options = html.querySelector("select").children;
- for (const option of options) {
- if (option.selected) {
- classifiedResult[option.innerText] = result;
- continue;
- }
- classifiedResult[option.innerText] = await getVoteResult(tid, 1, option.value);
- }
- return classifiedResult;
- }
- }
- function getButton() {
- const btn = document.createElement("a");
- btn.href = "javascript:;";
- btn.addEventListener("click", async () => {
- showPrompt(null, null, "<span>开始获取数据,在此期间请勿再次点击本按钮</span>", 5000);
- try {
- const csv = new CSV("Type", "TotalCredits", "Nuggets", "Emeralds", "DingTies", "Nickname", "UserGroupName", "UID");
- const result = await getVoteResult(window.tid);
- let taskQueueChunk = [];
- for (const option in result) {
- for (const uid of result[option]) {
- taskQueueChunk.push(
- (async () => {
- try {
- const { exp, nuggets, emeralds, nickname, group } = await getUserInfoByApi(uid);
- const dingTie = await getDingTie(uid);
- csv.add(option, exp, nuggets, emeralds, dingTie, nickname, group, uid);
- } catch {
- throw uid;
- }
- })()
- );
- if (taskQueueChunk.length >= 5) {
- //每5个promise一组
- await Promise.allSettled(taskQueueChunk);
- taskQueueChunk = [];
- }
- }
- }
- //等待没凑满5个的队列兑现
- await Promise.allSettled(taskQueueChunk);
- csv.saveAs(document.querySelector("#thread_subject").innerText);
- showPrompt(null, null, "<span>数据获取完毕,确认数据完整前请勿关闭网页,若数据有缺损,请打开控制台查看错误信息</span>", 5000);
- } catch (err) {
- showPrompt(null, null, "<span>数据获取出错,请打开控制台查看错误信息</span>", 5000);
- throw err;
- }
- });
- btn.innerText = "将数据导出至CSV";
- return btn;
- }
- if (document.querySelector("#poll>.pinf") && document.querySelector("#poll>.pinf").innerText.search("查看投票参与人") > 1) {
- document.querySelector("#poll>.pinf").append(getButton());
- }
- })();
复制代码
|