Midjourney Autopic

Discord自动点击Midjourney的U1、U2、U3、U4,自动下载所有图片,满80张会自动下载zip包,不到80张点关闭即可自动下载。

// ==UserScript==
// @name         Midjourney Autopic
// @namespace    http://tampermonkey.net/
// @version      1.4.7
// @description  Discord自动点击Midjourney的U1、U2、U3、U4,自动下载所有图片,满80张会自动下载zip包,不到80张点关闭即可自动下载。
// @author       老陆(vx:laolu2045)
// @match        https://discord.com/channels/*/*
// @icon         https://www.midjourney.com/favicon.ico
// @grant        unsafeWindow
// @require      https://code.jquery.com/jquery-3.6.0.min.js
// @require      https://cdnjs.cloudflare.com/ajax/libs/jszip/3.5.0/jszip.min.js
// @require      https://cdnjs.cloudflare.com/ajax/libs/xlsx/0.16.8/xlsx.full.min.js
// @require      https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.21/lodash.min.js
// @license      MIT License
// ==/UserScript==


(function () {
  ("use strict");
  const sessionId = "ea8816d857ba9ae2f74c59ae1a953afe";
  function sleep(ms) {
    return new Promise((resolve) => setTimeout(resolve, ms));
  }
  async function clickButtons(lastMsg) {
    console.log(lastMsg);
    const buttonArr = Array.from(lastMsg.querySelectorAll("button")).filter(
      (button) =>
        ["U1", "U2", "U3", "U4"].some(
          (text) => button.textContent.trim() === text
        )
    );
    if (buttonArr.length >= 4) {
      for (let i = 0; i < 4; i++) {
        buttonArr[i].click();
        console.log("点击1下U按钮");
        await sleep(3000); // 在每次点击后等待3秒,1秒=1000
      }
    }
  }
  //midjourney机器人
  function sendMessage(token, channelId, guildId, sessionId, content) {
    const url = `https://discord.com/api/v9/interactions`;
    const payload = {
      type: 2,
      application_id: "936929561302675456",
      guild_id: guildId,
      channel_id: channelId,
      session_id: sessionId,
      data: {
        version: "1237876415471554623",
        // version: "1166847114203123795",
        id: "938956540159881230",
        name: "imagine",
        type: 1,
        options: [
          {
            type: 3,
            name: "prompt",
            value: content,
          },
        ],
        application_command: {
          id: "938956540159881230",
          application_id: "936929561302675456",
          version: "1237876415471554623",
          // version: "1166847114203123795",
          default_member_permissions: null,
          type: 1,
          nsfw: false,
          name: "imagine",
          description: "Create images with Midjourney",
          dm_permission: true,
          contexts: [0, 1, 2],
          options: [
            {
              type: 3,
              name: "prompt",
              description: "The prompt to imagine",
              required: true,
            },
          ],
        },
        attachments: [],
      },
      nonce: Date.now().toString(),
    };
    console.log(payload);
    const request = new XMLHttpRequest();
    request.open("POST", url, true);
    request.setRequestHeader("Content-Type", "application/json");
    request.setRequestHeader("Authorization", token);
    request.send(JSON.stringify(payload));
  }

  //niji机器人
  function sendMessageNiji(token, channelId, guildId, sessionId, content) {
    const url = `https://discord.com/api/v9/interactions`;
    const payload = {
      type: 2,
      application_id: "1022952195194359889",
      guild_id: guildId,
      channel_id: channelId,
      session_id: sessionId,
      data: {
        version: "1248805223892254774",
        id: "1023054140580057099",
        name: "imagine",
        type: 1,
        options: [
          {
            type: 3,
            name: "prompt",
            value: content,
          },
        ],
        application_command: {
          id: "1023054140580057099",
          application_id: "1022952195194359889",
          version: "1248805223892254774",
          default_member_permissions: null,
          type: 1,
          nsfw: false,
          name: "imagine",
          description: "Create images with Niji journey",
          dm_permission: true,
          contexts: [0, 1, 2],
          options: [
            {
              type: 3,
              name: "prompt",
              description: "The prompt to imagine",
              required: true,
            },
          ],
        },
        attachments: [],
      },
      nonce: Date.now().toString(),
    };
    console.log(payload);
    const request = new XMLHttpRequest();
    request.open("POST", url, true);
    request.setRequestHeader("Content-Type", "application/json");
    request.setRequestHeader("Authorization", token);
    request.send(JSON.stringify(payload));
  }


function cleanToken(str) {
  // 去掉最外层可能存在的引号、空格、换行
  return str ? str.replace(/^"+|"+$/g, "").trim() : "";
}

function getToken() {
  /* ---------- 方法 1:webpack 内部 API ---------- */
  try {
    const chunk = window.webpackChunkdiscord_app;
    if (chunk) {
      const mods = [];
      chunk.push([
        [""],
        {},
        (e) => {
          if (e?.c) mods.push(...Object.values(e.c));
        }
      ]);
      const tokenModule = mods.find(
        (m) => m?.exports?.default?.getToken !== undefined
      );
      if (tokenModule) {
        return cleanToken(tokenModule.exports.default.getToken());
      }
    }
  } catch (err) {
    console.warn("webpack 获取 token 失败:", err);
  }

  /* ---------- 方法 2:localStorage 直接键 ---------- */
  try {
    const raw =
      localStorage.getItem("token") ||
      localStorage.getItem("discord_token");
    if (raw) {
      // localStorage.token 的值往往是 "\"mfa.xxxx\"" 这一类,需要 JSON.parse 去掉包裹引号
      const parsed = raw.startsWith('"') ? JSON.parse(raw) : raw;
      return cleanToken(parsed);
    }
  } catch (err) {
    // ignore
  }

  /* ---------- 方法 3:遍历 localStorage 兜底 ---------- */
  try {
    for (let i = 0; i < localStorage.length; i++) {
      const val = localStorage.getItem(localStorage.key(i));
      if (!val) continue;

      // (3-1) 尝试解析 JSON,常见结构 {token: "..."}
      try {
        const obj = JSON.parse(val);
        if (obj?.token) return cleanToken(obj.token);
      } catch {
        /* 不是 JSON,继续正则判断 */
      }

      // (3-2) 直接正则匹配用户 token 形态
      if (
        /^(mfa\.[\w-]{80,})|([\w-]{24}\.[\w-]{6}\.[\w-]{27})$/.test(val.trim())
      ) {
        return cleanToken(val);
      }
    }
  } catch (err) {
    // ignore
  }

  console.error("getToken: 未找到有效的 Discord token");
  return null;
}



  const token = getToken();
  if (token) {
    console.log("成功获取 token!长度:", token.length);
    // 不要打印完整 token,这可能有安全风险
    console.log("Token 前10位:", token.substring(0, 10) + "...");
  } else {
    console.log("获取 token 失败");
  }

  let messages = [];
  let urls = [];
  let prompt = [];
  let promptUrl = [];
  let commandInterval = [];
  let roundInterval = [];
  let commandsPerRound = 0;

  async function paotu() {
    createModal();
  }
  let downloads = "cut";
  let bot = "mj";
  let paotuisRunning = false;
  // 此变量用于控制下载模式,可为 'zip' 或 'direct'
  let downloadMode = "direct";

  async function createModal() {
    // 创建一个半透明的背景
    const overlay = document.createElement("div");
    overlay.style = `
        position: fixed;
        top: 0;
        left: 0;
        width: 100%;
        height: 100%;
        display: flex;
        justify-content: center;
        align-items: center;
        background: rgba(0, 0, 0, 0.5);
        backdrop-filter: blur(5px);
    `;

    // 创建一个带有毛玻璃效果的窗口
    const modal = document.createElement("div");
    modal.style = `
        width: 50%;
        padding: 40px;
        background: rgba(225, 225, 225, 0.7);
        border-radius: 10px;
        backdrop-filter: blur(10px);
        box-shadow: 0 8px 32px 0 rgba(31, 38, 135, 0.37);
        border: 1px solid rgba(255, 255, 255, 0.18);
        text-align: center;
    `;
    const label1 = document.createElement("label");
    label1.textContent = "顺序读取表格第1列的数据";
    label1.style = `margin-right: 10px;`;
    // 创建选择命令表格的按钮
    const commandTableButton = document.createElement("button");
    commandTableButton.textContent = "选择MJ命令xlsx表格";
    commandTableButton.style = `
        margin: 10px;
        padding: 10px;
        font-size: 18px;
        border-radius: 5px;
        background: linear-gradient(90deg, rgba(0, 121, 191, 0.8) 0%, rgba(0, 212, 255, 0.8) 100%);
        color: white;
        cursor: pointer;
        border: none;
        transition: background 0.5s;
    `;

    // 创建单选框组
    const radioGroupDiv = document.createElement("div");

    // MJ机器人选项
    const mjRobotRadio = document.createElement("input");
    mjRobotRadio.type = "radio";
    mjRobotRadio.name = "robotSelection";
    mjRobotRadio.id = "mjRobot";
    mjRobotRadio.value = "MJ机器人";
    mjRobotRadio.checked = true; // 默认选择MJ机器人
    const mjRobotLabel = document.createElement("label");
    mjRobotLabel.htmlFor = "mjRobot";
    mjRobotLabel.textContent = "MJ机器人";
    mjRobotRadio.addEventListener("change", function () {
      if (this.checked) {
        bot = "mj";
      }
    });

    // Niji机器人选项
    const nijiRobotRadio = document.createElement("input");
    nijiRobotRadio.type = "radio";
    nijiRobotRadio.name = "robotSelection";
    nijiRobotRadio.id = "nijiRobot";
    nijiRobotRadio.value = "Niji机器人";
    const nijiRobotLabel = document.createElement("label");
    nijiRobotLabel.htmlFor = "nijiRobot";
    nijiRobotLabel.textContent = "Niji机器人";
    nijiRobotRadio.addEventListener("change", function () {
      if (this.checked) {
        bot = "niji";
      }
    });

    if (bot === "mj") {
      mjRobotRadio.checked = true;
    } else if (bot === "niji") {
      nijiRobotRadio.checked = true;
    }
    // 将单选框和对应的标签添加到单选框组中
    radioGroupDiv.appendChild(mjRobotRadio);
    radioGroupDiv.appendChild(mjRobotLabel);
    radioGroupDiv.appendChild(nijiRobotRadio);
    radioGroupDiv.appendChild(nijiRobotLabel);

    // 创建下载单选框组
    const downloadGroupDiv = document.createElement("div");

    // 全部下载
    const downloadAllRadio = document.createElement("input");
    downloadAllRadio.type = "radio";
    downloadAllRadio.name = "downloadSelection";
    downloadAllRadio.id = "downloadAll";
    downloadAllRadio.value = "全部下载";
    downloadAllRadio.checked = true; // 默认选择
    const downloadAllLabel = document.createElement("label");
    downloadAllLabel.htmlFor = "downloadAll";
    downloadAllLabel.textContent = "全部下载";
    downloadAllRadio.addEventListener("change", function () {
      if (this.checked) {
        downloads = "all";
        console.log("downloads", downloads);
      }
    });

    // 跳过4宫格下载
    const downloadSkipRadio = document.createElement("input");
    downloadSkipRadio.type = "radio";
    downloadSkipRadio.name = "downloadSelection";
    downloadSkipRadio.id = "downloadSkip";
    downloadSkipRadio.value = "跳过4宫格下载";
    const downloadSkipLabel = document.createElement("label");
    downloadSkipLabel.htmlFor = "downloadSkip";
    downloadSkipLabel.textContent = "跳过4宫格下载";
    downloadSkipRadio.addEventListener("change", function () {
      if (this.checked) {
        downloads = "skip";
        console.log("downloads", downloads);
      }
    });

    // 自动切割并下载
    const downloadCutRadio = document.createElement("input");
    downloadCutRadio.type = "radio";
    downloadCutRadio.name = "downloadSelection";
    downloadCutRadio.id = "downloadCut";
    downloadCutRadio.value = "自动切割并下载大图";
    const downloadCutLabel = document.createElement("label");
    downloadCutLabel.htmlFor = "downloadCut";
    downloadCutLabel.textContent = "自动切割并下载大图";
    downloadCutRadio.addEventListener("change", function () {
      if (this.checked) {
        downloads = "cut";
        console.log("downloads", downloads);
      }
    });

    if (downloads === "all") {
      downloadAllRadio.checked = true;
    } else if (downloads === "skip") {
      downloadSkipRadio.checked = true;
    } else if (downloads === "cut") {
      downloadCutRadio.checked = true;
    }

    // 将单选框和对应的标签添加到单选框组中
    downloadGroupDiv.appendChild(downloadAllRadio);
    downloadGroupDiv.appendChild(downloadAllLabel);
    downloadGroupDiv.appendChild(downloadSkipRadio);
    downloadGroupDiv.appendChild(downloadSkipLabel);
    downloadGroupDiv.appendChild(downloadCutRadio);
    downloadGroupDiv.appendChild(downloadCutLabel);

    // 创建下载模式单选框组
    const downloadModeGroupDiv = document.createElement("div");

    // zip打包图片下载
    const downloadZipRadio = document.createElement("input");
    downloadZipRadio.type = "radio";
    downloadZipRadio.name = "downloadModeSelection";
    downloadZipRadio.id = "downloadZip";
    downloadZipRadio.value = "下载图片zip包(80张)";
    downloadZipRadio.checked = true; // 默认选择
    const downloadZipLabel = document.createElement("label");
    downloadZipLabel.htmlFor = "downloadZip";
    downloadZipLabel.textContent = "下载图片zip包(80张)";
    downloadZipRadio.addEventListener("change", function () {
      if (this.checked) {
        downloadMode = "zip";
        console.log("downloadMode", downloadMode);
      }
    });

    // 单张图片下载
    const downloadDirectRadio = document.createElement("input");
    downloadDirectRadio.type = "radio";
    downloadDirectRadio.name = "downloadModeSelection";
    downloadDirectRadio.id = "downloadDirect";
    downloadDirectRadio.value = "下载单张图片";
    const downloadDirectLabel = document.createElement("label");
    downloadDirectLabel.htmlFor = "downloadDirect";
    downloadDirectLabel.textContent = "下载单张图片";
    downloadDirectRadio.addEventListener("change", function () {
      if (this.checked) {
        downloadMode = "direct";
        console.log("downloadMode", downloadMode);
      }
    });

    if (downloadMode === "zip") {
      downloadZipRadio.checked = true;
    } else if (downloadMode === "direct") {
      downloadDirectRadio.checked = true;
    }
    // 将单选框和对应的标签添加到单选框组中
    downloadModeGroupDiv.appendChild(downloadZipRadio);
    downloadModeGroupDiv.appendChild(downloadZipLabel);
    downloadModeGroupDiv.appendChild(downloadDirectRadio);
    downloadModeGroupDiv.appendChild(downloadDirectLabel);

    const brElement = document.createElement("br");
    modal.appendChild(radioGroupDiv);
    modal.appendChild(downloadGroupDiv);
    modal.appendChild(downloadModeGroupDiv);
    modal.appendChild(label1);
    modal.appendChild(commandTableButton);

    // 创建输入字段
    const fields = [
      {
        label: "命令发送间隔时间(秒)【推荐6-10】",
        id: "command-interval",
        isRange: true,
      },
      {
        label: "每轮间隔时间(秒)【推荐800-1000】",
        id: "round-interval",
        isRange: true,
      },
      {
        label: "每轮命令数(个)【推荐5-8】",
        id: "round-command-count",
        isRange: true,
      },
      {
        label: "每条命令输出次数(默认1次)",
        id: "command-num",
        isRange: false,
      },
    ];
    fields.forEach((field) => {
      const fieldContainer = document.createElement("div");
      fieldContainer.style = `display: flex; justify-content: center; align-items: center; margin: 20px;`;

      const label = document.createElement("label");
      label.textContent = field.label;
      label.style = `margin-right: 10px;`;

      if (field.isRange) {
        const labelDash = document.createElement("label");
        labelDash.textContent = "-";
        labelDash.style = `margin: 0 10px;`;
        const input = document.createElement("input");
        input.id = `${field.id}-1`; // 添加 ID
        input.style = `
            padding: 10px;
            font-size: 16px;
            border-radius: 5px;
            border: 1px solid rgba(0, 0, 0, 0.3);
        `;
        const input2 = document.createElement("input");
        input2.id = `${field.id}-2`; // 添加 ID
        input2.style = `
                padding: 10px;
                font-size: 16px;
                border-radius: 5px;
                border: 1px solid rgba(0, 0, 0, 0.3);
            `;
        fieldContainer.appendChild(label);
        fieldContainer.appendChild(input);
        fieldContainer.appendChild(labelDash);
        fieldContainer.appendChild(input2);
      } else {
        const input = document.createElement("input");
        input.style = `
            padding: 10px;
            font-size: 16px;
            border-radius: 5px;
            border: 1px solid rgba(0, 0, 0, 0.3);
        `;
        input.id = field.id;
        if (field.id === "command-num") {
          input.type = "number";
          input.min = "1";
          input.max = "100";
          input.value = "1"; // 设置默认值
          input.style.width = "50px"; // 固定宽度以适应按钮
          const incrementButton = document.createElement("button");
          incrementButton.textContent = "+";
          incrementButton.onclick = function () {
            if (input.value < 100) input.value++;
          };

          const decrementButton = document.createElement("button");
          decrementButton.textContent = "-";
          decrementButton.onclick = function () {
            if (input.value > 1) input.value--;
          };

          fieldContainer.appendChild(label);
          fieldContainer.appendChild(decrementButton);
          fieldContainer.appendChild(input);
          fieldContainer.appendChild(incrementButton);
        } else {
          // 非 command-num 输入框的处理
          fieldContainer.appendChild(label);
          fieldContainer.appendChild(input);
        }
      }

      modal.appendChild(fieldContainer);
    });

    // 创建开始按钮
    const startButton = document.createElement("button");
    if (paotuisRunning) {
      startButton.textContent = "停止跑图";
      startButton.style = `
        display: block;
        margin-top: 20px;
        margin-left: auto;
        margin-right: auto;
        padding: 10px 20px;
        font-size: 18px;
        border: none;
        border-radius: 5px;
        background: linear-gradient(90deg, rgba(235, 52, 52, 1) 0%, rgba(236, 116, 116, 1) 100%);
        box-shadow: 0px 3px 15px rgba(0,0,0,0.2);
        color: white;
        cursor: pointer;
        transition: background 0.5s;
    `;
    } else {
      startButton.textContent = "开始跑图";
      startButton.style = `
        display: block;
        margin-top: 20px;
        margin-left: auto;
        margin-right: auto;
        padding: 10px 20px;
        font-size: 18px;
        border: none;
        border-radius: 5px;
        background: linear-gradient(90deg, rgba(39, 174, 96, 0.8) 0%, rgba(0, 230, 64, 0.8) 100%);
        box-shadow: 0px 3px 15px rgba(0,0,0,0.2);
        color: white;
        cursor: pointer;
        transition: background 0.5s;
    `;
    }
    modal.appendChild(startButton);

    // 创建关闭按钮
    const closeButton = document.createElement("button");
    closeButton.textContent = "关闭";
    closeButton.style = `
        display: block;
        margin-top: 20px;
        margin-left: auto;
        margin-right: auto;
        padding: 10px 20px;
        border: none;
        border-radius: 5px;
        background: linear-gradient(90deg, rgba(235, 52, 52, 1) 0%, rgba(236, 116, 116, 1) 100%);
        box-shadow: 0px 3px 15px rgba(0,0,0,0.2);
        color: white;
        cursor: pointer;
        transition: background 0.5s;
    `;

    closeButton.addEventListener("mouseover", () => {
      closeButton.style.background = "rgba(236, 116, 116, 1)";
    });

    closeButton.addEventListener("mouseout", () => {
      closeButton.style.background =
        "linear-gradient(90deg, rgba(235, 52, 52, 1) 0%, rgba(236, 116, 116, 1) 100%)";
    });

    closeButton.addEventListener("click", () => {
      document.body.removeChild(overlay);
    });

    modal.appendChild(closeButton);

    // 将窗口添加到背景上,然后将背景添加到文档上
    overlay.appendChild(modal);
    document.body.appendChild(overlay);

    const fileInput = document.createElement("input");
    fileInput.type = "file";
    fileInput.accept = ".xlsx";
    fileInput.style.display = "none";
    modal.appendChild(fileInput);

    commandTableButton.addEventListener("click", () => {
      fileInput.click();
    });

    fileInput.addEventListener("change", handleFileSelect, false);

    function handleFileSelect(evt) {
      var files = evt.target.files;
      var f = files[0];
      var reader = new FileReader();

      reader.onload = function (e) {
        var data = new Uint8Array(e.target.result);
        var workbook = XLSX.read(data, { type: "array" });
        var worksheet = workbook.Sheets[workbook.SheetNames[0]];
        var jsonData = XLSX.utils.sheet_to_json(worksheet, { header: 1 });
        messages = jsonData.map((row) => row[0]);
      };

      reader.readAsArrayBuffer(f);
    }

    startButton.addEventListener("click", () => {
      const { commandInterval, roundInterval, roundCommandCount, commandNum } =
        getFieldValues();

      if (!paotuisRunning) {
        // Start running
        paotuisRunning = true;
        urls = [];
        prompt = [];
        promptUrl = [];
        startButton.textContent = "停止跑图";
        startButton.style = `
        display: block;
        margin-top: 20px;
        margin-left: auto;
        margin-right: auto;
        padding: 10px 20px;
        font-size: 18px;
        border: none;
        border-radius: 5px;
        background: linear-gradient(90deg, rgba(235, 52, 52, 1) 0%, rgba(236, 116, 116, 1) 100%);
        box-shadow: 0px 3px 15px rgba(0,0,0,0.2);
        color: white;
        cursor: pointer;
        transition: background 0.5s;
    `;
        sendCommands(
          commandInterval,
          roundInterval,
          roundCommandCount,
          commandNum
        );
      } else {
        // Stop running
        paotuisRunning = false;
        startButton.textContent = "开始跑图";
        startButton.style = `
        display: block;
        margin-top: 20px;
        margin-left: auto;
        margin-right: auto;
        padding: 10px 20px;
        font-size: 18px;
        border: none;
        border-radius: 5px;
        background: linear-gradient(90deg, rgba(39, 174, 96, 0.8) 0%, rgba(0, 230, 64, 0.8) 100%);
        box-shadow: 0px 3px 15px rgba(0,0,0,0.2);
        color: white;
        cursor: pointer;
        transition: background 0.5s;
    `;
      }
    });

    async function sendCommands(
      commandInterval,
      roundInterval,
      roundCommandCount,
      commandNum
    ) {
      let counter = 0;
      let channelId = window.location.href.substring(
        window.location.href.lastIndexOf("/") + 1,
        window.location.href.length
      );
      let urlParts = window.location.href.split("/");
      let guildId = urlParts[urlParts.length - 2];
      for (let i = 0; i < messages.length; i++) {
        if (!paotuisRunning) {
          // 停止发送命令,如果运行状态被设置为 false
          break;
        }
        // 在每轮开始时,随机选择本轮的命令数
        let currentRoundCommandCount = Math.floor(
          Math.random() * (roundCommandCount[1] - roundCommandCount[0] + 1) +
            roundCommandCount[0]
        );
        for (let j = 0; j < commandNum; j++) {
          if (bot === "mj") {
            sendMessage(token, channelId, guildId, sessionId, messages[i]);
          } else if (bot === "niji") {
            sendMessageNiji(token, channelId, guildId, sessionId, messages[i]);
          }

          counter++;

          // 每次发送后检查是否达到每轮命令数
          if (counter >= currentRoundCommandCount) {
            counter = 0;
            await sleeps(getRandom(roundInterval[0], roundInterval[1]));
          } else if (j < commandNum - 1) {
            // 如果还未到达每轮命令数但命令需要重复发送,则等待命令间隔时间
            await sleeps(getRandom(commandInterval[0], commandInterval[1]));
          }
        }

        // 如果最后一条命令后未触发等待,这里需要额外的等待
        if (counter !== 0) {
          await sleeps(getRandom(commandInterval[0], commandInterval[1]));
        }
      }
      paotuisRunning = false;
      startButton.textContent = "开始跑图";
      startButton.style = `
        display: block;
        margin-top: 20px;
        margin-left: auto;
        margin-right: auto;
        padding: 10px 20px;
        font-size: 18px;
        border: none;
        border-radius: 5px;
        background: linear-gradient(90deg, rgba(39, 174, 96, 0.8) 0%, rgba(0, 230, 64, 0.8) 100%);
        box-shadow: 0px 3px 15px rgba(0,0,0,0.2);
        color: white;
        cursor: pointer;
        transition: background 0.5s;
    `;
    }

    function sleeps(ms) {
      return new Promise((resolve) => setTimeout(resolve, ms * 1000));
    }

    function getRandom(min, max) {
      return Math.random() * (max - min) + min;
    }

    function getFieldValues() {
      let commandInterval = [
        Number(document.querySelector("#command-interval-1").value),
        Number(document.querySelector("#command-interval-2").value),
      ];
      let roundInterval = [
        Number(document.querySelector("#round-interval-1").value),
        Number(document.querySelector("#round-interval-2").value),
      ];
      let roundCommandCount = [
        Number(document.querySelector("#round-command-count-1").value),
        Number(document.querySelector("#round-command-count-2").value),
      ];
      let commandNum = Number(document.querySelector("#command-num").value);
      return { commandInterval, roundInterval, roundCommandCount, commandNum };
    }
  }

  //自动下载
  // utils.ts 的内容
  function saveFile(content, fileName) {
    const link = document.createElement("a");
    link.style.display = "none";
    link.download = fileName;
    link.href = URL.createObjectURL(content);
    document.body.appendChild(link);
    link.click();
    document.body.removeChild(link);
  }

  async function getUrlBlob(url) {
    try {
      const res = await fetch(url);
      return res.blob();
    } catch {
      return null;
    }
  }

  // 主要的代码
  function checkIsMidjourneyBot(li) {
    if (!li) {
      return false;
    }
    var userName = li.querySelector(
      "h3[class^=header] span[class^=username]"
    )?.textContent;
    if (userName === "Midjourney Bot" || userName === "niji・journey Bot") {
      return true;
    }
    if (!userName) {
      return checkIsMidjourneyBot(li.previousElementSibling);
    }
  }

  function matchMidjourneyLis(li) {
    if (checkIsMidjourneyBot(li)) {
      const id = li.id.substring(14);
      let bs = 0;
      const prompts = Array.from(
        li.querySelector("div[id^=message-content-]>strong")?.childNodes ?? []
      )
        .map((node) => node.textContent)
        .join(" ");
      //修改 div[class^=messageAttachment]
      const url = li.querySelector(
        "div[id^=message-accessories-] div[class^=imageWrapper] a[data-role=img]"
      )?.href;
      const isFourGrid =
        Array.from(li.querySelectorAll("button"))
          .map((_) => _.textContent.trim())
          .filter((text) => text !== "")
          .join("")
          .replace(/[\u200B-\u200D\uFEFF]/g, "") === "U1U2U3U4V1V2V3V4";
      if (isFourGrid) {
        bs = 0;
      } else {
        bs = 1;
      }
      if (downloads === "all") {
        return {
          id,
          prompts,
          url,
          bs,
        };
      } else if (downloads === "skip") {
        if (!isFourGrid) {
          return {
            id,
            prompts,
            url,
            bs,
          };
        }
      } else if (downloads === "cut") {
        if (isFourGrid) {
          return {
            id,
            prompts,
            url,
            bs,
          };
        }
      }
    }
  }

  let currentZip = new JSZip();
  let zipCount = 0;
  let zipBatch = new Set();
  //图片打包数量
  const flushThreshold = 80;
  let mutationObserver;

  //分割图片
  async function splitImageIntoFour(url) {
    const image = new Image();
    image.crossOrigin = "Anonymous";
    const loadPromise = new Promise((resolve, reject) => {
      image.onload = () => resolve();
      image.onerror = reject;
    });
    image.src = url;
    await loadPromise;

    const canvas = document.createElement("canvas");
    const ctx = canvas.getContext("2d");
    const width = image.width;
    const height = image.height;
    const midWidth = Math.floor(width / 2);
    const midHeight = Math.floor(height / 2);

    canvas.width = midWidth;
    canvas.height = midHeight;

    const blobs = [];
    const coordinates = [
      [0, 0, midWidth - 1, midHeight - 1], // Upper Left
      [midWidth, 0, width, midHeight - 1], // Upper Right
      [0, midHeight, midWidth - 1, height], // Lower Left
      [midWidth, midHeight, width, height], // Lower Right
    ];

    for (let i = 0; i < 4; i++) {
      ctx.clearRect(0, 0, midWidth, midHeight);
      const [sourceX1, sourceY1, sourceX2, sourceY2] = coordinates[i];
      ctx.drawImage(
        image,
        sourceX1,
        sourceY1,
        sourceX2 - sourceX1,
        sourceY2 - sourceY1,
        0,
        0,
        midWidth,
        midHeight
      );

      const blob = await new Promise((resolve) => canvas.toBlob(resolve));
      blobs.push(blob);
    }
    return blobs;
  }

  function processImage(data) {
    const regexFilename = /^.+\/(.+?)\.png.*$/;
    const matchResult = data.url.match(regexFilename);
    const fileName = matchResult ? matchResult[1] : null;
    const regexDigits = /_(\d{4})_/;

    if (fileName) {
      splitImageIntoFour(data.url).then((blobs) => {
        blobs.forEach((blob, index) => {
          let newFileName;
          if (regexDigits.test(fileName)) {
            newFileName = fileName.replace(
              regexDigits,
              `_${RegExp.$1}_U${index + 1}_`
            );
          } else {
            newFileName = fileName + "_" + "U" + (index + 1);
          }
          if (downloadMode === "zip") {
            currentZip.file(`${newFileName}.png`, blob, { binary: true });
            console.log(`[添加到ZIP] ${newFileName}`);
          } else if (downloadMode === "direct") {
            downloadImage(blob, `${newFileName}.png`);
            console.log(`[直接下载] ${newFileName}`);
          }
          zipCount++;
          // 添加对应的url
          if (data.bs === 0) {
            // 修改的部分开始
            const matchResults = data.prompts.match(/\d{4}/);
            const numberInPrompt = matchResults ? matchResults[0] : null;
            if (numberInPrompt) {
              for (let i = 0; i < messages.length; i++) {
                if (messages[i].includes(numberInPrompt)) {
                  const indexInUrls = urls.findIndex(
                    (entry) => entry.index === i
                  );

                  if (indexInUrls !== -1) {
                    urls.splice(indexInUrls, 1);
                  }
                  urls.push({ url: data.url, index: i });
                  console.log(urls);
                  break;
                }
              }
            }
            // 修改的部分结束
          }

          console.log(
            `[新的图片] ${newFileName}`,
            `[ZIP 计数] ${zipCount} / ${flushThreshold}`
          );
          if (zipCount >= flushThreshold && downloadMode === "zip") {
            flush();
          }
        });
      });
    }
  }

  // 直接下载图片的函数
  function downloadImage(blob, filename) {
    const url = window.URL.createObjectURL(blob);
    const a = document.createElement("a");
    a.href = url;
    a.download = filename;
    document.body.appendChild(a);
    a.click();
    window.URL.revokeObjectURL(url);
    a.remove();
  }

  function parseLiNode(node) {
    const data = matchMidjourneyLis(node);
    if (data?.url) {
      const matchResult = data.url.match(/^.+\/(.+?)\.png.*$/);
      const fileName = matchResult ? matchResult[1] : null;
      if (fileName && !zipBatch.has(fileName)) {
        zipBatch.add(fileName);
        // 如果 downloads 为 "cut",则处理和切割图片
        if (downloads === "cut") {
          processImage(data);
        } else {
          getUrlBlob(data.url).then((binaryBlob) => {
            if (binaryBlob && currentZip && downloadMode === "zip") {
              currentZip.file(`${fileName}.png`, binaryBlob, {
                binary: true,
              });
            } else if (downloadMode === "direct") {
              downloadImage(binaryBlob, `${fileName}.png`);
            }
            zipCount++;
            if (downloads === "skip") {
              prompt.push(data.prompts);
              promptUrl.push(data.url);
            }
            // 添加对应的url
            if (data.bs === 0) {
              // 修改的部分开始
              const matchResults = data.prompts.match(/\d{4}/);
              const numberInPrompt = matchResults ? matchResults[0] : null;
              if (numberInPrompt) {
                for (let i = 0; i < messages.length; i++) {
                  if (messages[i].includes(numberInPrompt)) {
                    const indexInUrls = urls.findIndex(
                      (entry) => entry.index === i
                    );

                    if (indexInUrls !== -1) {
                      urls.splice(indexInUrls, 1);
                    }
                    urls.push({ url: data.url, index: i });
                    console.log(urls);
                    break;
                  }
                }
              }
              // 修改的部分结束
            }
            console.log(
              `[新的图片] ${data.url}`,
              `[ZIP 计数] ${zipCount} / ${flushThreshold}`
            );
            if (zipCount >= flushThreshold && downloadMode === "zip") {
              flush();
            }
          });
        }
      }
    }
  }

  function startObserve() {
    mutationObserver = new MutationObserver((records) => {
      records.forEach((record) => {
        const addNodes = record.addedNodes;
        if (
          record.type === "childList" &&
          addNodes &&
          addNodes?.length &&
          record.target.tagName === "OL"
        ) {
          addNodes.forEach((node) => {
            const li = node;
            if (li.tagName === "LI" && li.id.startsWith("chat-messages-")) {
              parseLiNode(li);
            }
          });
        } else if (
          record.type === "attributes" &&
          record.attributeName === "class" &&
          record.target.tagName === "DIV" &&
          record.target.className.includes("imageWrapper")
        ) {
          let li = record.target;
          while (li && li.tagName !== "LI") {
            li = li.parentElement;
          }
          if (li) {
            parseLiNode(li);
          }
        }
      });
    });
    const contentList = document.querySelector('ol[class^="scrollerInner"]');
    if (contentList) {
      mutationObserver.observe(contentList, {
        attributes: true,
        childList: true,
        subtree: true,
      });
    }
  }

  async function flush() {
    if (zipCount) {
      const prevZip = currentZip;
      currentZip = new JSZip();
      zipCount = 0;
      zipBatch.clear();
      const zipContent = await prevZip.generateAsync({ type: "blob" });
      saveFile(zipContent, `midjourney-${Date.now()}.zip`);
    }
  }

  //分割功能导出excel
  function exportToExcel() {
    // 1. 创建数据数组
    let data = [];
    for (let i = 0; i < messages.length; i++) {
      const message = messages[i];
      const urlObj = urls.find((u) => u.index === i);
      const url = urlObj ? urlObj.url : ""; // 如果找到与索引匹配的URL,则使用它,否则为空字符串
      data.push([message, url]);
    }

    // 2. 使用xlsx库生成工作表
    const ws = XLSX.utils.aoa_to_sheet(data);

    // 3. 创建工作簿并将工作表添加到工作簿中
    const wb = XLSX.utils.book_new();
    XLSX.utils.book_append_sheet(wb, ws, "Sheet1");

    // 4. 导出工作簿到Excel文件
    XLSX.writeFile(wb, "data.xlsx");
  }

  //跳过4宫格图导出excel
  function exportToExcelSkip() {
    // 1. 创建数据数组
    let data = [];
    for (let i = 0; i < prompt.length; i++) {
      // 直接使用prompt和promptUrl数组的内容
      data.push([prompt[i], promptUrl[i]]);
    }

    // 2. 使用xlsx库生成工作表
    const ws = XLSX.utils.aoa_to_sheet(data);

    // 3. 创建工作簿并将工作表添加到工作簿中
    const wb = XLSX.utils.book_new();
    XLSX.utils.book_append_sheet(wb, ws, "Sheet1");

    // 4. 导出工作簿到Excel文件
    XLSX.writeFile(wb, "data.xlsx");
  }

  unsafeWindow.xiazai = flush;
  unsafeWindow.stopObserveAndDownload = function () {
    if (mutationObserver) {
      mutationObserver.disconnect();
    }
    currentZip = new JSZip();
    zipBatch = new Set();
    if (downloads === "cut" && messages.length > 0 && urls.length > 0) {
      exportToExcel();
    } else if (
      downloads === "skip" &&
      prompt.length > 0 &&
      promptUrl.length > 0
    ) {
      exportToExcelSkip();
    }
  };

  function addButtons() {
    let style = document.createElement("style");
    let style_on = document.createElement("style");
    style.innerText = `.Btn{display:flex;width:90%;height:35px;margin:10px;justify-content: center;align-items: center;background-color: #2b2d31;color: white;font-weight: bolder;border-radius: 20px;box-shadow: 1px 1px 8px #e7ec1acf;}`;
    style_on.innerText = `.Btn_on{display:flex;width:90%;height:35px;margin:10px;justify-content: center;align-items: center;background-color: #2b2d31;color: white;font-weight: bolder;border-radius: 20px;box-shadow: 1px 1px 8px #1aec3fcf;}`;

    document.head.appendChild(style);
    document.head.appendChild(style_on);

    let btn2 = document.createElement("button");
    btn2.innerText = "自动下载图片";
    btn2.setAttribute("class", "Btn");
    document.querySelector(".content__99f8c").prepend(btn2);

    let btn2isRunning = false; // 初始状态为未运行

    document.querySelector(".Btn").addEventListener("click", function () {
      if (btn2isRunning) {
        // 如果已经在运行,那么停止它
        if (downloadMode === "zip") {
          xiazai();
        }
        sleep(500);
        stopObserveAndDownload();
        btn2.innerText = "自动下载图片"; // 按钮文字变回原样
        btn2.setAttribute("class", "Btn");
      } else {
        // 否则,开始运行
        startObserve();
        btn2.innerText = "停止(下载剩余图片)"; // 按钮文字变成:停止
        btn2.setAttribute("class", "Btn_on");
      }
      btn2isRunning = !btn2isRunning; // 切换运行状态
    });

    let btn3 = document.createElement("button");
    btn3.innerText = "自动选大图";
    btn3.setAttribute("class", "Btn");
    document.querySelector(".content__99f8c").prepend(btn3);

    let isRunning = false; // 初始状态为未运行
    let intervalId = null; // 保存setInterval的ID

    document.querySelector(".Btn").addEventListener("click", function () {
      if (isRunning) {
        // 如果已经在运行,那么停止它
        clearInterval(intervalId);
        btn3.innerText = "自动选大图"; // 按钮文字变回原样
        btn3.setAttribute("class", "Btn");
      } else {
        // 否则,开始运行
        let lastCheckedMessage = null;
        intervalId = setInterval(async function () {
          const allMsg = document.getElementsByClassName(
            "messageListItem_d5deea"
          ); //获取消息列表
          const lastMsg = allMsg[allMsg.length - 1]; //获取最后一条消息的元素
          if (lastMsg !== lastCheckedMessage) {
            lastCheckedMessage = lastMsg;
            await clickButtons(lastMsg);
          }
        }, 5); // 每5毫秒检查一次
        btn3.innerText = "自动选大图(已开启)"; // 按钮文字变成:已开启
        btn3.setAttribute("class", "Btn_on");
      }
      isRunning = !isRunning; // 切换运行状态
    });
    let btn4isRunning = false; // 初始状态为未运行
    let btn4 = document.createElement("button");
    btn4.innerText = "开始跑图";
    btn4.setAttribute("class", "Btn");
    document.querySelector(".content__99f8c").prepend(btn4);
    document.querySelector(".Btn").addEventListener("click", function () {
      console.log("开始跑图");
      paotu();
    });
  }

  function setupObserver() {
    const targetNode = document.getElementById("app-mount");
    const config = { childList: true, subtree: true };
    const callback = function (mutationsList, observer) {
      for (let mutation of mutationsList) {
        if (mutation.type === "childList") {
          if (
            document.querySelector(".content__99f8c") &&
            !document.querySelector(".Btn")
          ) {
            console.log("Adding buttons...");
            addButtons();
          }
        }
      }
    };
    const observer = new MutationObserver(callback);
    observer.observe(targetNode, config);
  }
  setupObserver();
})();
长期地址
遇到问题?请前往 GitHub 提 Issues。