闲管家一键上架助手

在商品页面复制商品信息并在闲鱼管家后台上架页面插入“一键填充”按钮,添加下载商品详情图片的功能。包括选品功能,可在闲鱼商详页导出猜你喜欢的数据到Excel,支持动态数据

// ==UserScript==
// @name         闲管家一键上架助手
// @namespace    http://tampermonkey.net/
// @version      1.31
// @description  在商品页面复制商品信息并在闲鱼管家后台上架页面插入“一键填充”按钮,添加下载商品详情图片的功能。包括选品功能,可在闲鱼商详页导出猜你喜欢的数据到Excel,支持动态数据
// @author       九旬 wx:728396024    mail:[email protected]
// @match        https://h5.m.goofish.com/item?id=*
// @match        https://www.goofish.com/item*
// @match        https://goofish.pro/*
// @match        https://www.goofish.pro/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=goofish.pro
// @grant        GM_setValue
// @grant        GM_getValue
// @require      https://cdn.jsdelivr.net/npm/sweetalert2@11/dist/sweetalert2.all.min.js
// @require      https://cdnjs.cloudflare.com/ajax/libs/xlsx/0.18.5/xlsx.full.min.js
// @license      MIT
// ==/UserScript==

(function () {
  "use strict";

  // 使用 unsafeWindow 访问原始 window 对象
  const unsafeWindow = window.unsafeWindow || window;

  interceptRequests();

  function showToast(message, type = "success") {
    Swal.fire({
      toast: true,
      position: "top-end",
      icon: type,
      title: message,
      showConfirmButton: false,
      timer: 1500,
    });
  }

  function downloadImage(url, fileName) {
    fetch(url)
      .then(response => response.blob())
      .then(blob => {
        const a = document.createElement("a");
        a.href = URL.createObjectURL(blob);
        a.download = `${fileName}.jpg`;
        a.click();
        URL.revokeObjectURL(a.href);
      })
      .catch(error => console.error("图片下载失败:", error));
  }

  // 生成随机数
  function getRandomNumber(min, max) {
    const randomInt = Math.floor(Math.random() * (max / 10 - min / 10 + 1)) + min / 10;
    return randomInt * 10;
  }

  // 获取价格,适配不同格式,比如 1~10、10
  function getPrice(priceStr) {
    // 移除特殊字符 "¥" 和 "\n"
    const cleanedStr = priceStr.replace(/[¥\n]/g, "");

    // 使用正则表达式提取所有数字
    const numbers = cleanedStr.match(/\d+(\.\d+)?/g);

    if (numbers === null) {
      return null; // 如果没有匹配到数字,返回null
    }

    // 将提取出的字符串数字转换为浮点数
    const prices = numbers.map(Number);

    // 找出最大值
    const maxPrice = Math.max(...prices);

    return maxPrice;
  }

  // 商品页面逻辑
  if (window.location.href.includes("https://www.goofish.com/item")) {
    setTimeout(() => {
      const button = document.createElement("button");
      let goofishImages = document.querySelectorAll("div.slick-slide:not(.slick-cloned)").length;

      button.innerHTML = `复制商品信息 & 下载图片( <b> ${goofishImages} </b>)`;
      button.style.position = "fixed";
      button.style.bottom = "20px";
      button.style.left = "20px";
      button.style.zIndex = 1000;
      button.style.padding = "10px 20px";
      button.style.backgroundColor = "#28a745";
      button.style.color = "white";
      button.style.border = "none";
      button.style.borderRadius = "5px";
      button.style.cursor = "pointer";

      document.body.appendChild(button);

      button.addEventListener("click", () => {
        copyGoods();
        downloadImages();
      });
    }, 1500);

    // 复制商品信息
    function copyGoods() {
      const detailTextElements = document.querySelectorAll("[class*='desc']")[1];
      const priceElements = document.querySelectorAll("[class*='price']")[0];

      if (detailTextElements && priceElements) {
        const detailText = detailTextElements.innerText;
        const indexOfFirstNewLine = detailText.indexOf(" ");
        let productName, productDescription;

        if (indexOfFirstNewLine !== -1) {
          productName = detailText.slice(0, indexOfFirstNewLine).trim();
          productDescription = detailText.trim();
        } else {
          productName = detailText.trim();
          productDescription = "";
        }

        // 裁剪商品标题到30个中文字以内
        const maxTitleLength = 30;
        productName = productName.slice(0, maxTitleLength);

        const productPrice = getPrice(priceElements.innerText.trim());

        GM_setValue("productName", productName);
        // GM_setValue("productDescription", productDescription);
        GM_setValue("productPrice", productPrice);

        console.log("商品信息已存储");
        console.log("商品名:", productName);
        // console.log("商品详情:", productDescription);
        console.log("商品价格:", productPrice);

        showToast("商品信息已复制");
      } else {
        console.error("未找到商品详情或价格元素");
        showToast("未找到商品详情或价格元素", "error");
      }
    }

    // 下载商品图片
    function downloadImages() {
      // 获取所有 class 为 slick-slide 且不包含 slick-cloned 的 div 元素
      const goofishImageDivs = document.querySelectorAll("div.slick-slide:not(.slick-cloned)");

      // 遍历这些 div 元素,获取其中的 img 标签
      const goofishImages = Array.from(goofishImageDivs).map(div => div.querySelector("img"));

      // 过滤掉可能不存在 img 标签的 div
      const filteredGoofishImages = goofishImages.filter(img => img !== null);

      filteredGoofishImages.forEach((img, index) => {
        setTimeout(() => {
          let src = img.src.replace(/_webp$/, ""); // 去掉_webp并下载jpg格式
          downloadImage(src, `${index + 1}`);
        }, index * 100); // 每个下载任务之间延迟100毫秒
      });
      showToast("图片正在开始");
    }
  }

  // 闲鱼管家上架页面逻辑
  if (window.location.hostname.includes("goofish.pro")) {
    const button = document.createElement("button");
    button.innerText = "一键填充";
    button.style.position = "fixed";
    button.style.bottom = "10px";
    button.style.left = "150px";
    button.style.zIndex = 1000;
    button.style.padding = "10px 20px";
    button.style.backgroundColor = "#28a745";
    button.style.color = "white";
    button.style.border = "none";
    button.style.borderRadius = "5px";
    button.style.cursor = "pointer";

    document.body.appendChild(button);

    button.addEventListener("click", () => {
      // 检查当前页面是否是商品添加页面
      if (!window.location.href.includes("/sale/product/add")) {
        window.location.href = "https://goofish.pro/sale/product/add?from=%2Fon-sale";
        setTimeout(() => fillProductInfo(), 1500);
        return;
      } else {
        fillProductInfo();
      }
    });
  }

  function fillProductInfo() {
    // 从 Tampermonkey 存储中获取商品信息
    const productName = GM_getValue("productName", "");
    const productDescription = GM_getValue("productDescription", "") || productName;
    let prict = GM_getValue("productPrice");
    let productPrice = 100;
    if (prict < 2) {
      productPrice = 1.9;
    } else {
      productPrice = parseFloat(GM_getValue("productPrice", "100")) - 0.1;
    }

    if (!productName || !productDescription || isNaN(productPrice)) {
      showToast("请先去闲鱼详情页复制商品信息", "warning");
      return;
    }

    console.log("读取到的商品名:", productName);
    console.log("读取到的商品详情:", productDescription);
    console.log("读取到的商品价格:", productPrice);

    // 选择类目
    const categoryElements = document.querySelectorAll(".release-history");
    if (categoryElements.length > 0) {
      categoryElements[0].click();
    } else {
      console.error("未找到类目选择元素");
      showToast("请选择商品分类", "error");
    }

    setTimeout(() => {
      // 选择店铺
      const shopList = document.querySelectorAll("ul.auth-list li");
      if (shopList.length > 1) {
        // 默认选择第一个店铺
        const unSelectedIconElements = document.querySelector(".un-selected-icon");
        if (unSelectedIconElements) {
          unSelectedIconElements.click();
        }
      } else {
        console.error("请先创建闲鱼店铺");
        showToast("请先创建闲鱼店铺", "error");
      }

      setTimeout(() => {
        // 填充商品名
        const inputElement = document.querySelector('input[placeholder="请输入商品标题,最多允许输入30个汉字"]');
        if (inputElement) {
          inputElement.value = productName;
          const event = new Event("input", { bubbles: true });
          inputElement.dispatchEvent(event);
        } else {
          console.error("未找到商品标题输入框");
          showToast("未找到商品标题输入框", "error");
        }

        // 填充商品描述
        const descriptionElements = document.querySelector('textarea[placeholder="请输入商品描述"]');
        if (descriptionElements) {
          descriptionElements.value = productDescription;
          const event = new Event("input", { bubbles: true });
          descriptionElements.dispatchEvent(event);
        } else {
          console.error("未找到商品描述输入框");
          showToast("未找到商品描述输入框", "error");
        }

        // 搜索一个价格 0=原价 1=售价
        const inputElements = document.querySelectorAll('input[placeholder="¥ 0.00"]');
        // 填充原价
        if (inputElements[0]) {
          inputElements[0].value = getRandomNumber(200, 500);
          const event = new Event("input", { bubbles: true });
          inputElements[0].dispatchEvent(event);
        } else {
          console.error("未找到价格输入框");
          showToast("未找到价格输入框", "error");
        }
        // 填充价格
        if (inputElements[1]) {
          inputElements[1].value = productPrice.toFixed(2);
          const event = new Event("input", { bubbles: true });
          inputElements[1].dispatchEvent(event);
        } else {
          console.error("未找到价格输入框");
          showToast("未找到价格输入框", "error");
        }

        // // 填充库存
        // const productStore = 1;
        // if (inputElements.length > 5) {
        //   inputElements[6].value = productStore;
        //   const event = new Event("input", { bubbles: true });
        //   inputElements[6].dispatchEvent(event);
        // } else {
        //   console.error("未找到库存输入框");
        //   showToast("未找到库存输入框", "error");
        // }

        // const expressFeeItem = document.querySelector(".express_fee-item");
        // const radioElements = expressFeeItem.querySelectorAll(".el-radio")[0];
        // const isSelet = radioElements.querySelector('input[type="radio"]').value;
        // console.log(isSelet);
        // if (radioElements.length) {
        //   radioElements[0].click(); // 包邮
        // } else {
        //   console.error("未找到发布商品时机的单选框");
        //   showToast("未找到发布商品时机的单选框", "error");
        // }

        showToast("商品信息已填充");
      }, 500);
    }, 500);
  }

  // 闲鱼详情页:提取并显示想要人数、浏览量和转化率
  if (window.location.href.includes("https://www.goofish.com/item")) {
    setTimeout(() => {
      // const spanElement = document.querySelector("#ice-container > div.content-container--gIWgkNkm > div.item-container--yLJD5VZj > div.item-main-container--jhpFKlaS > div.item-main-info--rA5Bmpa5 > div.tips--JYdXhSNh > div.want--mVAXJTGv");
      const spanElement = document.querySelectorAll("[class*='want']")[0];
      if (spanElement) {
        const textContent = spanElement.textContent.trim();

        // 初始化变量
        let wantText = "0人想要";
        let viewText = "0浏览";

        // 检查字符串内容并进行拆分和处理
        if (textContent.includes("人想要") && textContent.includes("浏览")) {
          // 如果同时包含 "人想要" 和 "浏览"
          [wantText, viewText] = textContent.split(" ");
        } else if (textContent.includes("浏览")) {
          // 只有 "浏览"
          viewText = textContent;
        }

        // 提取数字部分并转换为整数
        const wantNumber = parseInt(wantText.replace("人想要", "").trim(), 10) || 0;
        const viewNumber = parseInt(viewText.replace("浏览", "").trim(), 10) || 0;

        let rate = 0;
        if (wantNumber != 0 || viewNumber != 0) {
          rate = wantNumber / viewNumber;
        }
        const conversionRate = (rate * 100).toFixed(0);
        const conversionRateText = conversionRate + "%";

        const statsDiv = document.createElement("div");
        statsDiv.style.position = "fixed";
        statsDiv.style.bottom = "63px";
        statsDiv.style.left = "20px";
        statsDiv.style.backgroundColor = "#93ab9b";
        statsDiv.style.borderRadius = "6px";
        statsDiv.style.color = "white";
        statsDiv.style.padding = "10px";
        statsDiv.style.zIndex = "1000";
        statsDiv.style.fontSize = "14px";

        const conversionRateSpan = document.createElement("b");
        conversionRateSpan.textContent = conversionRateText;
        conversionRateSpan.style.backgroundColor = conversionRate > 7 ? "green" : "red";
        statsDiv.style.backgroundColor = conversionRate > 7 ? "#93ab9b" : "rgb(211 131 131)";

        conversionRateSpan.style.padding = "2px 4px";
        conversionRateSpan.setAttribute("id", "conversion-rate");

        statsDiv.innerHTML = `
想要数 : <b id="want-num">${wantNumber}</b><br>
浏览量 : <b id="view-num">${viewNumber}</b><br>
转化率 : `;
        statsDiv.appendChild(conversionRateSpan);

        document.body.appendChild(statsDiv);

        if (conversionRate < 7) {
          console.log(`转化率太低了 ${conversionRate}%`);
        }
      } else {
        console.error("无法找到目标 span 元素");
      }
    }, 1000);
  }

  // 拦截并处理接口请求
  function interceptRequests() {
    // 覆盖原生的 XMLHttpRequest
    const originalXHR = unsafeWindow.XMLHttpRequest;
    unsafeWindow.XMLHttpRequest = function () {
      const xhr = new originalXHR();

      // 保存原始的 open 方法
      const originalOpen = xhr.open;
      xhr.open = function (method, url, ...rest) {
        this._url = url; // 保存请求 URL
        return originalOpen.apply(this, [method, url, ...rest]);
      };

      // 保存原始的 send 方法
      const originalSend = xhr.send;
      xhr.send = function (...args) {
        this.addEventListener("load", function () {
          // 拦截商品详情接口,获取详情文本内容
          if (this._url.includes("h5api.m.goofish.com/h5/mtop.taobao.idle.pc.detail")) {
            try {
              const data = JSON.parse(this.responseText);
              console.log("接收到的数据:", data);
              if (data?.data?.itemDO?.desc) {
                let productDescription = data.data.itemDO.desc;
                const goodsId = new URL(location.href).searchParams.get("id");
                //                 productDescription += `\n \n[钉子]发的是百 度 网 盘 链 接,永不失效,售出不退。
                // [钉子]任何情况,不要申请退款,私信沟通给你处理,小店经营不易。
                // [钉子]所有文件均获取自网络公开渠道,仅供学习和交流使用,所有版权归版权人所有,如版权方认为侵犯了您的版权,请及时联系小店删除。`;
                //                 productDescription += `\n${goodsId}`;
                GM_setValue("productDescription", productDescription);
                console.log("商品详情:", productDescription);
              }
            } catch (error) {
              console.error("解析 XHR 响应时发生错误:", error);
            }
          }

          // 拦截商品详情接口,获取详情文本内容
          if (this._url.includes("h5api.m.goofish.com/h5/mtop.taobao.idle.item.web.recommend.list")) {
            try {
              const data = JSON.parse(this.responseText);
              console.log("接收到的数据:", data);

              // 检查 data.data.cardList 是否为数组
              if (data && Array.isArray(data.data?.cardList)) {
                // 提取有效的数据
                const tempData = data.data.cardList.filter(item => {
                  if (item && item.cardData && item.cardData.itemId) {
                    const price = parseInt(item.cardData.price);
                    return price > 0;
                  }
                  return false;
                });

                // 合并到全局数组 window.collectedData
                window.collectedData.push(...tempData);
                console.log("新数据已追加:", data.data.cardList);

                // 去重:使用一个 Set 来追踪已经存在的 itemId
                const uniqueItems = [];
                const itemIdSet = new Set();

                // 遍历 window.collectedData,添加未重复的 item
                for (const item of window.collectedData) {
                  const itemId = item.cardData?.itemId;
                  if (itemId && !itemIdSet.has(itemId)) {
                    uniqueItems.push(item);
                    itemIdSet.add(itemId);
                  }
                }

                // 更新 window.collectedData 为去重后的结果
                window.collectedData = uniqueItems;

                console.log("去重后的数据:", window.collectedData);

                // 更新数据计数
                updateDataCount();
              }
            } catch (error) {
              console.error("解析 XHR 响应时发生错误:", error);
            }
          }
        });
        return originalSend.apply(this, args);
      };

      return xhr;
    };
  }

  // 自动写入类目(暂定为电子资料)
  // if (window.location.hostname.includes("goofish.pro")) {
  //   // 要写入的键
  //   const key = "goods_select";

  //   // 要写入的值(注意是一个字符串)
  //   const value = JSON.stringify([
  //     {
  //       name: "电子资料",
  //       id: [99, "eebfcb1cd9bfce8e212e21d79c0262e7", "eebfcb1cd9bfce8e212e21d79c0262e7", "3cdbae6d47df9251a7f7e02f36b0b49a"],
  //       item_biz_type: 2,
  //     },
  //   ]);

  //   // 检查localStorage中是否已经存在该键
  //   if (!localStorage.getItem(key)) {
  //     // 将键值对写入localStorage
  //     // localStorage.setItem(key, value);
  //     // console.log(`已写入localStorage: ${key} = ${value}`);
  //   } else {
  //     console.log(`localStorage中已存在: ${key} = ${localStorage.getItem(key)}`);
  //   }
  // }

  repleaceUrl();
  // 避免闲管家的域名混用,带www和不带www的,因为两者的cookie不同,导致登录状态是不共享的
  function repleaceUrl() {
    // 获取当前页面的 URL
    const currentUrl = window.location.href;

    // 判断是否是以 'https://www.goofish.pro/' 开头
    if (currentUrl.startsWith("https://www.goofish.pro/")) {
      // 使用正则表达式替换 'www.' 为 ''
      const newUrl = currentUrl.replace("https://www.", "https://");

      // 跳转到新的 URL
      window.location.replace(newUrl);
    }
  }

  // 闲鱼商详页导出猜你喜欢的数据到Excel,支持动态数据

  if (window.location.href.includes("https://www.goofish.com/item?")) {
    exportGoodsList();
  }

  // 更新商品数量显示
  function updateDataCount() {
    const countElement = document.getElementById("data-count");
    if (countElement) {
      countElement.innerText = window.collectedData.length;
    }
  }

  function exportGoodsList() {
    setTimeout(() => {
      insertDownloadButton();
    }, 0);

    // 全局数组,用于存储商品数据,确保全局数据存储在 window 对象上
    if (!window.collectedData) {
      window.collectedData = [];
    }

    interceptRequests();

    // 插入下载按钮到页面
    function insertDownloadButton() {
      const button = document.createElement("button");
      button.innerHTML = `导出 [猜你喜欢] 商品 (<b id="data-count">0</b>)`;
      button.style.position = "fixed";
      button.style.width = "240px";
      button.style.left = "50%";
      button.style.marginLeft = "-120px";
      button.style.bottom = "20px";
      button.style.zIndex = 9999;
      button.style.padding = "10px 20px";
      button.style.backgroundColor = "rgb(40, 167, 69)";
      button.style.color = "#FFFFFF";
      button.style.border = "none";
      button.style.borderRadius = "5px";
      button.style.cursor = "pointer";
      button.addEventListener("click", downloadExcel);
      document.body.appendChild(button);
    }

    // 时间戳转换成日期格式
    function timestampToFormattedDate(timestamp) {
      if (!timestamp) return "";
      var date = new Date(parseInt(timestamp));
      var year = date.getFullYear(); // 获取年份
      var month = date.getMonth() + 1; // 获取月份,月份是从0开始的,所以需要加1
      var day = date.getDate(); // 获取日期

      // 月份和日期如果是单数,需要在前面补0
      month = month < 10 ? "0" + month : month;
      day = day < 10 ? "0" + day : day;

      return `${year}-${month}-${day}`;
    }

    // 下载Excel文件
    function downloadExcel() {
      if (window.collectedData.length === 0) {
        alert("没有数据可导出");
        return;
      }

      // 创建Excel工作簿
      const workbook = XLSX.utils.book_new();
      const worksheetData = window.collectedData
        .map(item => {
          console.log(111, item.cardData?.trackEventParamInfo?.args?.publishTime);
          return {
            //商品ID: item.cardData.itemId || '',
            //链接: `https://www.goofish.com/item?id=` + item.cardData.itemId,
            链接: {
              f: `HYPERLINK("https://www.goofish.com/item?id=${item.cardData.itemId}", "https://www.goofish.com/item?id=${item.cardData.itemId}")`,
            },
            标题: {
              f: `HYPERLINK("https://www.goofish.com/item?id=${item.cardData.itemId}", "${item.cardData.title}")`,
            },
            想要数: parseInt(item.cardData.clickParam.args.wantNum, 10) || "",
            价格: parseInt(item.cardData.price) - 0.1 || "",
            城市: item.cardData.area || "",
            发布日期: timestampToFormattedDate(item.cardData?.clickParam?.args?.publishTime),
          };
        })
        .sort((a, b) => b.想要数 - a.想要数);

      // 将数据转化为工作表
      const worksheet = XLSX.utils.json_to_sheet(worksheetData);
      XLSX.utils.book_append_sheet(workbook, worksheet, "猜你喜欢");

      // 生成Excel并触发下载
      const workbookOut = XLSX.write(workbook, { bookType: "xlsx", type: "array" });
      const blob = new Blob([workbookOut], { type: "application/octet-stream" });
      const url = URL.createObjectURL(blob);
      const a = document.createElement("a");
      a.href = url;
      a.download = "猜你喜欢商品.xlsx";
      document.body.appendChild(a);
      a.click();
      document.body.removeChild(a);
      URL.revokeObjectURL(url);
    }
  }
})();