// ==UserScript==
// @name 闲鱼管家上货助手
// @namespace http://tampermonkey.net/
// @version 1.21
// @description 在商品页面复制商品信息并在闲鱼管家后台上架页面插入“一键填充”按钮,添加下载商品详情图片的功能。包括选品功能,可在闲鱼商详页导出猜你喜欢的数据到Excel,支持动态数据
// @author 阿阅 wx:pangyue2 mail:pang-yue@qq.com
// @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(".desc--WLgQcGKD");
const priceElements = document.querySelectorAll(".price--Ls68DZ8a ");
let index = 0
if (detailTextElements.length > 1) index = 1 // 避免获取错元素
if (detailTextElements.length > 0 && priceElements.length > 0) {
const detailText = detailTextElements[index].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[0].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 > 0) {
shopList[0].click();
} else {
console.error("未找到目标店铺元素");
showToast("未找到目标店铺元素", "error");
}
setTimeout(() => {
// 填充商品名
const inputElements = document.querySelectorAll(".el-input__inner");
if (inputElements.length > 2) {
inputElements[2].value = productName;
const event = new Event("input", { bubbles: true });
inputElements[2].dispatchEvent(event);
} else {
console.error("未找到商品标题输入框");
showToast("未找到商品标题输入框", "error");
}
// 填充商品详情
const descriptionElements = document.querySelectorAll(
".el-textarea__inner"
);
if (descriptionElements.length > 0) {
descriptionElements[0].value = productDescription;
const event = new Event("input", { bubbles: true });
descriptionElements[0].dispatchEvent(event);
} else {
console.error("未找到商品描述输入框");
showToast("未找到商品描述输入框", "error");
}
// 填充价格
if (inputElements.length > 4) {
inputElements[4].value = productPrice.toFixed(2);
const event = new Event("input", { bubbles: true });
inputElements[4].dispatchEvent(event);
} else {
console.error("未找到价格输入框");
showToast("未找到价格输入框", "error");
}
// // 填充原价
// if (inputElements.length > 5) {
// inputElements[5].value = getRandomNumber(200, 500);
// const event = new Event('input', { bubbles: true });
// inputElements[5].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 radioElements = document.querySelectorAll(".el-radio");
if (radioElements.length > 11) {
radioElements[11].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"
);
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);
}
}
})();