// ==UserScript==
// @name 花瓣"去"水印-pro 1.1.13
// @version 1.1.13
// @description 主要功能:1.显示花瓣真假PNG(原理:脚本通过给花瓣图片添加背景色,显示出透明PNG图片,透出背景色的即为透明PNG,非透明PNG就会被过滤掉) 2.通过自定义修改背景色,区分VIP素材和免费素材。3.121.40.25.9 自动添加邀请码. 更多描述可安装后查看
// @author 小张 | 个人博客:https://blog.z-l.top | 公众号“爱吃馍” | 设计导航站 :https://dh.z-l.top | quicker账号昵称:星河城野❤
// @license GPL-3.0
// @namespace https://getquicker.net/User/Actions/388875-%E6%98%9F%E6%B2%B3%E5%9F%8E%E9%87%8E%E2%9D%A4
// @match https://huaban.com/*
// @match http://121.40.25.9:8080/register.html
// @connect gd.huaban.com
// @grant GM_addStyle
// @grant GM_getValue
// @grant GM_setValue
// @grant GM_setClipboard
// @grant GM_registerMenuCommand
// @grant GM_xmlhttpRequest
// @connect *
// @grant GM_info
// @grant GM_download
// @run-at document-end
// @connect scriptcat.org
// @icon https://st0.dancf.com/static/02/202306090204-51f4.png
// @require https://cdn.tailwindcss.com
// @require https://cdn.jsdelivr.net/gh/xiaolongmr/tampermonkey-scripts@bbea58f/%E8%8A%B1%E7%93%A3%E5%8E%BB%E6%B0%B4%E5%8D%B0/%E8%8A%B1%E7%93%A3%E2%80%9C%E5%8E%BB%E2%80%9D%E6%B0%B4%E5%8D%B0Pro%E6%9B%B4%E6%96%B0%E6%8F%90%E7%A4%BA%E8%84%9A%E6%9C%AC.js
// @require https://cdn.jsdelivr.net/gh/xiaolongmr/tampermonkey-scripts@09ee56b513ba1a42a4d0257c69a332d0a91aba77/%E7%BD%91%E7%AB%99%E6%B3%A8%E5%86%8C%E8%87%AA%E5%8A%A8%E5%A1%AB%E5%86%99%E8%A1%A8%E5%8D%95%E4%BF%A1%E6%81%AF/%E7%BD%91%E7%AB%99%E6%B3%A8%E5%86%8C%E8%87%AA%E5%8A%A8%E5%A1%AB%E5%86%99%E8%A1%A8%E5%8D%95%E4%BF%A1%E6%81%AF.js
// ==/UserScript==
(function () {
"use strict";
// 先于页面初始化接管花瓣新版图片右键菜单,避免官方菜单抢占脚本右键下载。
const HUABAN_CONTEXT_MENU_SELECTOR = '[data-pin-context-menu="true"]';
function installHuabanContextMenuGuard() {
const styleId = "huaban-context-menu-guard-style";
let contextMenuObserver = null;
const isGuardEnabled = () => {
try {
return typeof GM_getValue !== "function" ||
GM_getValue("enableRightClickDownload", true);
} catch {
return true;
}
};
const ensureStyle = () => {
if (document.getElementById(styleId)) return;
const style = document.createElement("style");
style.id = styleId;
style.textContent = `
${HUABAN_CONTEXT_MENU_SELECTOR} {
display: none !important;
visibility: hidden !important;
opacity: 0 !important;
pointer-events: none !important;
}
`;
(document.head || document.documentElement).appendChild(style);
};
const removeStyle = () => {
document.getElementById(styleId)?.remove();
};
const removeOfficialMenu = () => {
if (!isGuardEnabled()) return;
document.querySelectorAll(HUABAN_CONTEXT_MENU_SELECTOR).forEach((menu) => {
menu.remove();
});
};
const syncGuardState = () => {
if (isGuardEnabled()) {
ensureStyle();
removeOfficialMenu();
} else {
removeStyle();
}
};
const startObserver = () => {
if (!document.body || document.body.dataset.huabanContextMenuGuard) return;
document.body.dataset.huabanContextMenuGuard = "true";
contextMenuObserver = new MutationObserver(removeOfficialMenu);
contextMenuObserver.observe(document.body, {
childList: true,
subtree: true,
});
syncGuardState();
};
window.__huabanSyncContextMenuGuard = syncGuardState;
startObserver();
document.addEventListener("DOMContentLoaded", startObserver, { once: true });
window.addEventListener("beforeunload", () => {
contextMenuObserver?.disconnect();
});
}
installHuabanContextMenuGuard();
// ==================== 弹窗通用函数 ====================
function showToast(text, isHtml = false) {
const toast = document.createElement("div");
if (isHtml) {
toast.innerHTML = text;
} else {
toast.textContent = text;
}
toast.style.cssText = `position:fixed; top:-50px; left:50%; transform:translateX(-50%) translateY(0); background:#00c853; color:#fff; padding:8px 20px; border-radius:50px; z-index:2147483647; font-size:13px; font-weight:bold; border:1px solid rgba(255,255,255,0.3); box-shadow:0 4px 15px rgba(0,200,83,0.4);`;
const style = document.createElement("style");
style.textContent = `
@keyframes toastIn { 0%{top:-50px;transform:translateX(-50%) scale(0.8);opacity:0} 60%{top:25px;transform:translateX(-50%) scale(1.05)} 80%{top:15px;transform:translateX(-50%) scale(0.98)} 100%{top:20px;transform:translateX(-50%) scale(1);opacity:1} }
@keyframes toastOut { 0%{top:20px;transform:translateX(-50%) scale(1);opacity:1} 100%{top:-50px;transform:translateX(-50%) scale(0.8);opacity:0} }
`;
document.head.appendChild(style);
toast.style.animation = "toastIn 0.5s cubic-bezier(0.34,1.56,0.64,1) forwards";
if (isHtml) {
toast.style.whiteSpace = "nowrap";
toast.style.pointerEvents = "auto";
toast.style.cursor = "pointer";
}
document.body.appendChild(toast);
const duration = isHtml ? 4000 : 1200;
setTimeout(() => {
toast.style.animation = "toastOut 0.3s ease-in forwards";
setTimeout(() => { toast.remove(); style.remove(); }, 300);
}, duration);
}
function loadImageForClipboard(url) {
return new Promise((resolve, reject) => {
const img = new Image();
img.crossOrigin = "anonymous";
img.onload = () => resolve(img);
img.onerror = () => reject(new Error("图片加载失败"));
img.src = url;
});
}
function canvasToBlob(canvas) {
return new Promise((resolve, reject) => {
canvas.toBlob((blob) => {
if (blob) {
resolve(blob);
} else {
reject(new Error("图片转换失败"));
}
}, "image/png");
});
}
async function copyImageOrUrl(url) {
if (!url) {
showToast("图片地址为空");
return;
}
try {
if (!navigator.clipboard?.write || typeof ClipboardItem === "undefined") {
throw new Error("当前浏览器不支持复制图片");
}
const img = await loadImageForClipboard(url);
const canvas = document.createElement("canvas");
canvas.width = img.naturalWidth || img.width;
canvas.height = img.naturalHeight || img.height;
if (!canvas.width || !canvas.height) throw new Error("图片尺寸无效");
const ctx = canvas.getContext("2d");
if (!ctx) throw new Error("无法创建画布");
ctx.drawImage(img, 0, 0);
const blob = await canvasToBlob(canvas);
await navigator.clipboard.write([new ClipboardItem({ [blob.type]: blob })]);
showToast("图片已复制");
} catch (error) {
console.warn("复制图片失败,已尝试复制图片URL:", error);
try {
await navigator.clipboard.writeText(url);
showToast("图片URL已复制");
} catch (fallbackError) {
console.warn("复制图片URL失败:", fallbackError);
showToast("复制失败,请检查浏览器剪贴板权限");
}
}
}
// ==================== 常量定义 ====================
// 时间配置(毫秒)
const TIMING = {
debounceWatermark: 200, // 去水印操作的防抖延迟
watermarkCheckInterval: 2000, // 水印检测间隔
minProcessInterval: 500, // 最小处理间隔
switchTransition: 1000, // 开关状态变化反馈时长
};
// 设置面板背景图片
const SETTINGS_BG_IMAGES = {
left: 'https://cdn.h5ds.com/space/files/600972551685382144/20260405/965244304316362752.png',
right: 'https://cdn.h5ds.com/space/files/600972551685382144/20260405/965244228593389568.png',
};
// DOM 选择器
const SELECTORS = {
// 花瓣网中的"查看大图"按钮图片
imageButton:
'img[data-button-name="查看大图"]',
// 图片查看器中的大图元素
imageViewer:
'img.vYzIMzy2[alt="查看图片"]',
// 图片查看器容器内的大图元素
imageViewerContainer:
'#imageViewerWrapper img.vYzIMzy2[alt="查看图片"]',
// 简单图片查看器中的大图元素
imageViewerSimple: 'img.vYzIMzy2[alt="查看图片"]',
};
// 默认配置
const DEFAULT_CONFIG = {
materialColor: "#ffe0e0",
// 花瓣官方素材:淡红色
userColor: "#ebffff",
// 用户上传:粉蓝色
enableCustom: true,
// 启用自定义背景色
enableDragDownload: true,
// 启用拖拽下载功能
enableRightClickDownload: true,
// 启用右键下载功能
enableRemoveWatermark: true,
// 启用去水印功能
enableHideSettingsButton: false,
// 隐藏蜡笔小新设置按钮
};
// 配置字段映射(简化 getConfig/saveConfig)
const CONFIG_KEYS = [
"materialColor",
"userColor",
"enableCustom",
"enableDragDownload",
"enableRightClickDownload",
"enableRemoveWatermark",
"enableHideSettingsButton",
];
// ==================== 状态变量 ====================
// 状态变量:跟踪Ctrl+V/Cmd+V的使用状态
let isImageSearchMode = false;
// ==================== 工具函数 ====================
function debugLog() {
/* no-op */
}
// 颜色验证
function isValidColor(color) {
const hexRegex = /^#([0-9A-F]{3}|[0-9A-F]{6})$/i;
const rgbRegex =
/^rgb\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*\)$/i;
return hexRegex.test(color) || rgbRegex.test(color);
}
// 获取清理后的文件名(移除后缀参数,使用PNG扩展名)
// 根据alt属性或span标签生成文件名,如果没有则使用默认文件名
function getFileNameFromAlt(img) {
// 仅使用alt属性生成文件名
const altText = img.alt || "";
// 如果alt属性有内容且不是默认的"查看图片",则使用alt作为文件名
if (altText && altText.trim() && altText !== "查看图片") {
// 清理alt文本,移除特殊字符,只保留字母、数字、中文和空格
let cleanAlt = altText.replace(/[^\w\u4e00-\u9fa5\s]/g, "").trim();
// 如果清理后的文本不为空,则使用alt作为文件名
if (cleanAlt) {
// 限制文件名长度,避免过长
if (cleanAlt.length > 40) {
cleanAlt = cleanAlt.substring(0, 40);
}
return cleanAlt;
}
}
// 如果alt属性无效,返回默认名称
return "花瓣图片";
}
// 检查快捷键是否匹配
const isHotkeyMatch = (e, hotkeyConfig) => {
if (!hotkeyConfig) return false;
const ctrlCmd = e.ctrlKey || e.metaKey;
const shift = e.shiftKey;
const alt = e.altKey;
const key = e.key.toLowerCase();
return (
ctrlCmd === hotkeyConfig.ctrlCmd &&
shift === hotkeyConfig.shift &&
alt === hotkeyConfig.alt &&
key === hotkeyConfig.key
);
};
// ==================== 配置相关 ====================
// 获取配置 - 使用配置字段映射简化代码
function getConfig() {
const result = {};
CONFIG_KEYS.forEach((key) => {
result[key] = GM_getValue(key, DEFAULT_CONFIG[key]);
});
return result;
}
// 保存配置 - 使用配置字段映射简化代码
function saveConfig(config) {
CONFIG_KEYS.forEach((key) => {
if (key in config) {
GM_setValue(key, config[key]);
}
});
}
// 默认快捷键配置
const DEFAULT_HOTKEYS = {
searchFocus: {
ctrlCmd: true,
shift: false,
alt: false,
key: "k",
description: "定位到搜索框",
},
imageSearch: {
ctrlCmd: true,
shift: false,
alt: false,
key: "p",
description: "以图搜索功能",
},
openSettings: {
ctrlCmd: true,
shift: false,
alt: false,
key: ",",
description: "打开设置界面",
},
};
// 获取快捷键配置
const getHotkeysConfig = () => {
return typeof GM_getValue === "function"
? GM_getValue("hotkeysConfig", DEFAULT_HOTKEYS)
: DEFAULT_HOTKEYS;
};
// ==================== 样式应用 ====================
// 应用样式
function applyStyles() {
const config = getConfig();
// 移除旧样式
const oldStyle = document.getElementById("huaban-bg-style");
if (oldStyle) oldStyle.remove();
// 添加动画效果CSS
const style = document.createElement("style");
style.id = "huaban-bg-style";
style.textContent = `
/* 花瓣素材 背景色 */
[data-material-id] img.transparent-img-bg,
[data-material-id] img.transparent-img-black-bg {
background-color: ${config.enableCustom ? config.materialColor : "transparent"} !important;
${config.enableCustom ? "background-image:none!important;" : ""}
}
/* 用户上传背景色 */
.KKIUywzb:not([data-material-id]) img.transparent-img-bg,
.KKIUywzb:not([data-material-id]) img.transparent-img-black-bg {
background-color: ${config.enableCustom ? config.userColor : "transparent"} !important;
${config.enableCustom ? "background-image:none!important;" : ""}
}
/* 搜索框聚焦时的样式 - 仅在使用快捷键时触发 */
[data-button-name="搜索框"].hb-search-focused:before {
background: rgba(255, 40, 75, 0.3) !important;
}
${config.enableRightClickDownload ? `
/* 右键下载启用时屏蔽花瓣官方图片右键菜单 */
[data-pin-context-menu="true"] {
display: none !important;
}
` : ""}
/* antd弹出层样式宽度,花瓣采集框 */
.ant-popover {
min-width: 540px!important;
}
/* 下面是:花瓣添加到花瓣,画板列表元素 */
.z8_k0U12 .JYXx0SF7 .__0nq08tOH {
display: grid;
grid-template-columns: repeat(2, minmax(0px, 1fr));
height: auto!important;
max-height: 300px;
}
/* 个人信息面板样式 */
.user-profile { max-width: 800px; margin: 0 auto; }
.profile-header { display: flex; align-items: center; gap: 20px; margin-bottom: 30px; padding-bottom: 20px; border-bottom: 1px solid #eee; }
.avatar { width: 100px; height: 100px; border-radius: 50%; object-fit: cover; border: 3px solid #fff; box-shadow: 0 2px 10px rgba(0,0,0,0.1); }
.profile-info h3 { margin: 0 0 10px; font-size: 24px; color: #333; }
.profile-info .job { color: #666; margin: 0 0 5px; }
.profile-info .joined { color: #999; font-size: 14px; }
.profile-stats { display: grid; grid-template-columns: repeat(auto-fit, minmax(120px, 1fr)); gap: 15px; }
.stat-item { background: #f8f9fa; padding: 15px; border-radius: 8px; text-align: center; }
.stat-value { display: block; font-size: 28px; font-weight: bold; color: #333; margin-bottom: 5px; }
.stat-label { color: #666; font-size: 14px; }
.loading { text-align: center; padding: 40px 0; color: #666; }
.error-message { color: #dc3545; padding: 20px; text-align: center; background: #f8d7da; border-radius: 4px; }
`;
document.head.appendChild(style);
}
// ==================== 图片处理 ====================
// ==================== 页面元素处理 ====================
// 加载素材网站列表数据(异步)
// 实现CDN加载和缓存机制
async function loadMaterialSites() {
// 1. 检查缓存
const cacheKey = 'materialSitesCache';
const cacheExpiryKey = 'materialSitesCacheExpiry';
const cachedData = GM_getValue(cacheKey, null);
const cacheExpiry = GM_getValue(cacheExpiryKey, 0);
const now = Date.now();
const cacheDuration = 24 * 60 * 60 * 1000; // 24小时
// 2. 缓存有效直接返回
if (cachedData && now < cacheExpiry) {
debugLog('使用缓存的素材网站数据');
return cachedData;
}
// 3. 缓存失效,从CDN加载
try {
debugLog('从CDN加载素材网站数据');
const response = await fetch('https://cdn.jsdelivr.net/gh/xiaolongmr/tampermonkey-scripts/花瓣去水印/素材网.json');
const data = await response.json();
// 4. 更新缓存
GM_setValue(cacheKey, data);
GM_setValue(cacheExpiryKey, now + cacheDuration);
debugLog('素材网站数据已缓存');
return data;
} catch (error) {
console.error('加载素材网站列表失败:', error);
throw error;
}
}
// 创建单个素材网站项
function createSiteItem(site) {
const siteItem = document.createElement("a");
siteItem.href = site.href;
siteItem.target = "_blank";
siteItem.rel = "noopener noreferrer";
siteItem.className = "flex items-center gap-2 p-3 border rounded-md hover:bg-slate-50 transition-colors text-sm";
// 网站Logo
const siteLogo = document.createElement("img");
siteLogo.src = site.logoSrc;
siteLogo.alt = site.alt;
siteLogo.className = "w-6 h-6 object-contain";
siteLogo.referrerPolicy = "no-referrer";
// 网站信息容器
const siteInfo = document.createElement("div");
siteInfo.className = "flex-1 min-w-0";
// 网站标题
const siteTitle = document.createElement("div");
siteTitle.className = "font-medium text-slate-700 truncate";
siteTitle.textContent = site.title;
// 网站描述
const siteTip = document.createElement("div");
siteTip.className = "text-xs text-slate-500 truncate";
siteTip.textContent = site.tip;
// 积分提示
const sitePoints = document.createElement("div");
sitePoints.className = "text-xs text-amber-600";
sitePoints.textContent = site.jifen_tip;
// 组装网站信息
siteInfo.appendChild(siteTitle);
siteInfo.appendChild(siteTip);
// 组装网站项
siteItem.appendChild(siteLogo);
siteItem.appendChild(siteInfo);
siteItem.appendChild(sitePoints);
return siteItem;
}
// 创建素材网站列表容器
function createMaterialSitesContainer() {
const container = document.createElement("div");
container.id = "material-sites-container";
container.className = "bg-white rounded-lg p-4 mb-4";
// 创建标题
const title = document.createElement("h3");
title.className = "text-lg font-medium text-slate-700 mb-3";
title.textContent = "素材网站推荐";
container.appendChild(title);
return container;
}
// 添加文案信息
function addInfoText(container) {
const infoText = document.createElement("div");
infoText.className = "mt-4 p-4 bg-slate-50 rounded-lg text-sm text-slate-700 border border-slate-200";
infoText.innerHTML = '以上网站使用 http://www.sucaifeng.com 网站 购买积分进行下载,你也可以注册一下,邀请码:1474728874 使用邀请码注册,我们都能得到1000积分,就可以给更多朋友下载素材,
如果可以的话,求求给公众号的文章点点广告,每天都可以点的,这样我会有更新的动力,也可以购买一些AI作图的接口,比如nano banana gemini等绘画的功能加入这个脚本供大家免费使用';
container.appendChild(infoText);
}
// 在素材页面渲染素材网站列表
async function renderMaterialSitesOnSucaiPage() {
// 检查当前是否在指定页面
if (window.location.href !== "https://huaban.com/pages/sucai") {
return;
}
const layoutContent = document.getElementById("layout-content");
if (!layoutContent) {
return;
}
// 检查是否已经渲染过,避免重复创建
if (document.getElementById("material-sites-container")) {
return;
}
// 隐藏原有内容
Array.from(layoutContent.children).forEach((child) => {
child.style.display = "none";
});
// 创建容器和列表
const materialSitesContainer = createMaterialSitesContainer();
const sitesList = document.createElement("div");
sitesList.className = "grid grid-cols-5 gap-3 overflow-auto";
// 渲染素材网站列表
try {
// 从CDN或缓存加载数据
const materialSites = await loadMaterialSites();
// 创建并添加所有网站项
materialSites.forEach((site) => {
const siteItem = createSiteItem(site);
sitesList.appendChild(siteItem);
});
} catch (error) {
console.error("渲染素材网站列表失败:", error);
sitesList.innerHTML = `
无法加载素材网站列表
`;
}
// 组装并添加到页面
materialSitesContainer.appendChild(sitesList);
addInfoText(materialSitesContainer);
layoutContent.insertBefore(materialSitesContainer, layoutContent.firstChild);
}
// ==================== 事件监听 ====================
// 增强的页面变化监听,支持AJAX动态加载
function observePageChanges() {
const observer = new MutationObserver((mutations) => {
// 检查是否有新的内容被添加
mutations.forEach((mutation) => {
if (mutation.type === "childList" && mutation.addedNodes.length) {
mutation.addedNodes.forEach((node) => {
if (node.nodeType === 1) {
// 元素节点
// 可以在这里添加其他需要的DOM变化处理逻辑
}
});
}
});
});
// 观察选项
observer.observe(document.body, {
childList: true,
subtree: true,
});
debugLog("页面变化监听器已启动");
return observer;
}
// ==================== Hover操作面板功能 ====================
const HOVER_PANEL_STYLE = `
.hover-img-container {
position: relative !important;
display: inline-block !important;
}
.hover-action-panel,
.img-size-text {
opacity: 0;
pointer-events: none;
transition: opacity 0.2s ease;
}
.hover-action-panel.show,
.img-size-text.show {
opacity: 1;
pointer-events: auto;
}
.hover-action-panel {
position: absolute;
bottom: 6px;
right: 6px;
display: flex;
flex-direction: row;
gap: 5px;
z-index: 2147483647;
}
.hover-action-panel button {
border: none;
border-radius: 8px;
font-size: 16px;
cursor: pointer;
background: rgba(255, 255, 255, 0.85);
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
color: black;
align-items: center;
padding: 7px;
transition: transform 0.15s, background 0.15s, box-shadow 0.15s;
}
.hover-action-panel button:hover {
background: rgba(255, 255, 255, 1);
transform: scale(1.05);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.25);
}
.hover-action-panel button:active {
transform: scale(0.95);
}
.hover-action-panel .ps-btn {
background: transparent;
padding: 0;
box-shadow: none;
}
.img-size-text {
position: absolute;
bottom: 6px;
left: 6px;
padding: 6px 12px;
z-index: 9999;
border-radius: 16px;
background: rgba(255, 255, 255, 0.85);
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
backdrop-filter: blur(5px);
font-size: 12px;
color: black;
}
`;
function createHoverPanel(imgElement) {
const parent = imgElement.parentElement;
if (!parent || parent.querySelector(".hover-action-panel")) return;
let hoverImageInfoPromise = null;
let hoverImageInfoMaterialId = "";
let hoverImageInfoIsFallback = false;
const getCurrentMaterialId = () => imgElement.closest("[data-material-id]")?.dataset.materialId || "";
const getFallbackHoverImageInfo = () => {
const url = processImageUrl(imgElement.dataset.originalSrc || imgElement.currentSrc || imgElement.src);
return {
url,
width: imgElement.naturalWidth,
height: imgElement.naturalHeight,
title: imgElement.alt || imgElement.title || "",
file_format: getFileExtension(url).replace(".", ""),
};
};
const getHoverImageInfo = () => {
const materialId = getCurrentMaterialId();
const shouldRetryMaterialDetail = materialId && hoverImageInfoMaterialId === materialId && hoverImageInfoIsFallback;
if (!hoverImageInfoPromise || hoverImageInfoMaterialId !== materialId || shouldRetryMaterialDetail) {
hoverImageInfoMaterialId = materialId;
hoverImageInfoIsFallback = false;
hoverImageInfoPromise = (async () => {
if (materialId) {
try {
const detail = await fetchMaterialDetailById(materialId);
if (detail?.url) return detail;
} catch (error) {
debugLog("获取缩略图素材高清信息失败:", materialId, error);
}
}
hoverImageInfoIsFallback = !!materialId;
return { ...getFallbackHoverImageInfo(), __fromFallback: true };
})();
}
return hoverImageInfoPromise;
};
const getHoverFileName = (info) => {
const extension = info?.file_format
? `.${String(info.file_format).replace(/^\./, "").toLowerCase()}`
: getFileExtension(info?.url || imgElement.src);
return `${getFileNameFromAlt(imgElement)}${extension || ".png"}`;
};
parent.classList.add("hover-img-container");
const panel = document.createElement("div");
panel.className = "hover-action-panel";
panel.innerHTML = ``;
parent.after(panel);
// 获取原图尺寸
let sizeText = null;
let sizeInfoMaterialId = null;
let sizeInfoIsFallback = false;
const updateSize = async () => {
if (!sizeText) {
sizeText = document.createElement("span");
sizeText.className = "img-size-text";
parent.after(sizeText);
}
const setSizeText = (width, height) => {
if (!width || !height) return false;
sizeText.textContent = `${width} x ${height}px`;
return true;
};
const materialId = getCurrentMaterialId();
const shouldRetrySize = materialId && sizeInfoMaterialId === materialId && sizeInfoIsFallback;
if (sizeInfoMaterialId === materialId && !shouldRetrySize) return;
sizeInfoMaterialId = materialId;
sizeInfoIsFallback = false;
const fallbackInfo = getFallbackHoverImageInfo();
if (!sizeText.textContent) setSizeText(fallbackInfo.width, fallbackInfo.height);
const detail = await getHoverImageInfo();
if (setSizeText(detail?.width, detail?.height)) {
sizeInfoIsFallback = !!detail?.__fromFallback;
return;
}
const img = new Image();
img.crossOrigin = 'anonymous';
img.onload = () => {
if (img.naturalWidth > 0) {
setSizeText(img.naturalWidth, img.naturalHeight);
sizeInfoIsFallback = true;
}
};
img.src = detail?.url || fallbackInfo.url;
};
imgElement.complete ? updateSize() : imgElement.addEventListener("load", updateSize);
// hover事件
let hoverTimeout;
const show = () => {
clearTimeout(hoverTimeout);
updateSize();
panel.classList.add("show");
const sizeText = parent.nextElementSibling;
sizeText?.classList.add("show");
};
const hide = () => {
hoverTimeout = setTimeout(() => {
panel.classList.remove("show");
const sizeText = parent.nextElementSibling;
sizeText?.classList.remove("show");
}, 100);
};
parent.addEventListener("mouseenter", show);
parent.addEventListener("mouseleave", hide);
panel.addEventListener("mouseenter", show);
panel.addEventListener("mouseleave", hide);
// 复制按钮 - Canvas方式复制图片
panel.querySelector(".copy-btn").addEventListener("click", async e => {
e.stopPropagation();
const detail = await getHoverImageInfo();
const url = detail.url;
await copyImageOrUrl(url);
});
// 下载按钮
panel.querySelector(".download-btn").addEventListener("click", async e => {
e.stopPropagation();
const detail = await getHoverImageInfo();
const url = detail.url;
const fileName = getHoverFileName(detail);
if (typeof GM_download === "function") {
GM_download({ url, name: fileName });
} else {
const a = document.createElement("a");
a.href = url;
a.download = fileName;
a.click();
}
});
// PS导入按钮 - Alt点击新建文件导入
panel.querySelector(".ps-btn").addEventListener("click", async e => {
e.stopPropagation();
const detail = await getHoverImageInfo();
const url = detail.url;
const name = getFileNameFromAlt(imgElement);
const isNewDoc = e.altKey;
const clipboardStr = `PS_IMPORTER:${url}|||${name}${isNewDoc ? "|||NEW_DOC" : ""}`;
try { GM_setClipboard(clipboardStr); } catch { await navigator.clipboard.writeText(clipboardStr); }
showToast(isNewDoc ? "已发送至PS (新建文件)" : '已发送至PS (需安装图片导导插件)', isNewDoc ? false : true);
});
}
function initImageHover() {
document.head.insertAdjacentHTML("beforeend", ``);
const processImage = img => {
if (img.dataset.hoverPanelBound) return;
const parent = img.closest("[data-material-id]") || img.closest(".KKIUywzb");
if (parent && !parent.querySelector(".hover-action-panel")) {
img.dataset.hoverPanelBound = "true";
createHoverPanel(img);
}
};
new MutationObserver(m => m.forEach(n => n.addedNodes.forEach(node => {
if (node.nodeType !== 1) return;
if (node.tagName === "IMG") processImage(node);
node.querySelectorAll?.("img").forEach(processImage);
}))).observe(document.body, { childList: true, subtree: true });
document.querySelectorAll("img").forEach(processImage);
}
// 处理图片URL,删除_fwXXXwebp部分
function processImageUrl(url) {
let cleanUrl = url;
// 匹配_fw后跟数字和webp的模式
const fwRegex = /_fw\d+webp/;
if (fwRegex.test(cleanUrl)) {
cleanUrl = cleanUrl.replace(fwRegex, "");
debugLog("处理后的下载URL:", url, "→", cleanUrl);
}
return cleanUrl;
}
// 去水印相关功能
// 缓存高清URL,避免重复请求
const hdUrlCache = new Map();
const MATERIAL_DETAIL_TIMEOUT = 10000;
function requestMaterialDetailById(id) {
return new Promise((resolve, reject) => {
GM_xmlhttpRequest({
method: "GET",
url: `https://gd.huaban.com/editor/design?id=${id}`,
timeout: MATERIAL_DETAIL_TIMEOUT,
onload: (res) => {
const scriptMatch = res.responseText.match(/window\.__SSR_TEMPLATE\s*=\s*(\{[\s\S]*?\})(?:;|\s*<\/script>)/);
if (!scriptMatch) {
reject(new Error("未找到素材详情数据"));
return;
}
try {
const ssrData = JSON.parse(scriptMatch[1]);
if (!ssrData?.preview?.url) {
reject(new Error("素材详情缺少预览图地址"));
return;
}
resolve({
url: ssrData.preview.url,
width: ssrData.preview.width,
height: ssrData.preview.height,
dpi: ssrData.dpi,
file_format: ssrData.files?.[0]?.file_format || "",
content_url: ssrData.content_url,
type: ssrData.type,
title: ssrData.title,
video: ssrData.preview.video,
});
} catch (error) {
reject(error);
}
},
onerror: () => reject(new Error("素材详情请求失败")),
ontimeout: () => reject(new Error("素材详情请求超时")),
});
});
}
async function fetchMaterialDetailById(id) {
const cached = hdUrlCache.get(id);
if (cached && cached !== "loading") return cached;
if (cached === "loading") {
return new Promise((resolve, reject) => {
const startedAt = Date.now();
const timer = setInterval(() => {
const current = hdUrlCache.get(id);
if (current && current !== "loading") {
clearInterval(timer);
resolve(current);
} else if (!current || Date.now() - startedAt > MATERIAL_DETAIL_TIMEOUT) {
clearInterval(timer);
if (hdUrlCache.get(id) === "loading") hdUrlCache.delete(id);
reject(new Error("等待素材详情超时"));
}
}, 100);
});
}
hdUrlCache.set(id, "loading");
try {
const detail = await requestMaterialDetailById(id);
hdUrlCache.set(id, detail);
return detail;
} catch (error) {
if (hdUrlCache.get(id) === "loading") hdUrlCache.delete(id);
throw error;
}
}
// 目标图片/视频选择器(主展示区和弹出层)
const TARGET_SELECTORS = ['.OPWXbLYw img', '.Wa6mMsQV img', '.vYzIMzy2 img', '.VFtkdxbR img', '.ujZSLFrU video', '.tYymeMNB video','.PBVOckbr img'];
// 处理页面更新:提取ID,请求或替换高清图片
function handleUpdate() {
const config = getConfig();
if (!config.enableRemoveWatermark) return;
// 查找包含ID的父级div元素,支持新旧版本选择器
const sourceDiv = document.querySelector('.__2p__B98x, .QzLweiwl');
if (!sourceDiv) {
// 没有ID信息(免费素材),直接从目标图片获取URL
handleFreeImage();
return;
}
// 提取ID
const match = sourceDiv.innerText.match(/ID[::]\s*(\d+)/i);
if (!match) {
// 没有提取到ID,当作免费素材处理
handleFreeImage();
return;
}
const id = match[1];
const applyMaterialDetail = (cachedUrl) => {
// 只有type为image时才替换高清图片
if (cachedUrl.type === 'image') {
executeReplacement(cachedUrl.url);
}
// 尺寸信息和下载按钮不受type影响,始终显示
if (cachedUrl.width && cachedUrl.height) {
showSizeInfo(cachedUrl.width, cachedUrl.height, cachedUrl.dpi, cachedUrl.url, cachedUrl.file_format, cachedUrl.content_url, cachedUrl.type, cachedUrl.title);
}
};
const cachedUrl = hdUrlCache.get(id);
if (cachedUrl && cachedUrl !== "loading") {
applyMaterialDetail(cachedUrl);
return;
}
if (cachedUrl === "loading") return;
fetchMaterialDetailById(id)
.then(applyMaterialDetail)
.catch((error) => debugLog("获取详情页素材高清信息失败:", id, error));
}
// 处理免费素材(没有ID)
function handleFreeImage() {
const targets = TARGET_SELECTORS.map(sel => document.querySelector(sel)).filter(img => img);
targets.forEach(img => {
const parent = img.parentElement;
if (!parent || img.dataset.freeProcessed) return; // 已有标记则跳过
img.dataset.freeProcessed = 'true'; // 标记已处理
const url = processImageUrl(img.src);
const imgEl = new Image();
imgEl.crossOrigin = 'anonymous';
imgEl.onload = () => {
if (imgEl.naturalWidth > 0) {
if (parent.querySelector('.size-action-panel')) return; // 再次检查
// 创建尺寸信息
const sizeOverlay = document.createElement('div');
sizeOverlay.className = 'size-info-overlay';
sizeOverlay.textContent = `${imgEl.naturalWidth} x ${imgEl.naturalHeight}px`;
Object.assign(sizeOverlay.style, {
position: 'absolute', bottom: '8px', left: '8px', padding: '6px 12px', zIndex: '9999',
borderRadius: '99px', background: 'rgba(255, 255, 255, 0.7)',
boxShadow: '0 2px 8px rgba(0,0,0,0.15)', fontSize: '12px', color: '#000'
});
parent.appendChild(sizeOverlay);
// 创建操作按钮组
const actionPanel = document.createElement('div');
actionPanel.className = 'size-action-panel';
actionPanel.style.cssText = 'position: absolute;bottom: 60px;right: 8px;display: flex;flex-direction: column;gap: 6px;z-index: 10000;';
// 复制按钮
const copyBtn = document.createElement('div');
copyBtn.className = 'size-copy-btn';
copyBtn.title = '复制图片';
copyBtn.style.cssText = 'display: flex; align-items: center; justify-content: center; width: 43.9px; height: 43.9px; border-radius: 99px; background: rgba(255, 255, 255, 0.9); cursor: pointer; transition: 0.15s; transform: scale(1); box-shadow: rgba(0, 0, 0, 0.15) 0px 2px 8px;';
copyBtn.innerHTML = '';
copyBtn.addEventListener('mouseenter', () => { copyBtn.style.transform = 'scale(1.1)'; });
copyBtn.addEventListener('mouseleave', () => { copyBtn.style.transform = 'scale(1)'; });
copyBtn.addEventListener('click', async (e) => {
e.stopPropagation();
await copyImageOrUrl(url);
});
actionPanel.appendChild(copyBtn);
// 下载按钮
const downloadBtn = document.createElement('div');
downloadBtn.className = 'size-download-btn';
downloadBtn.title = '下载图片';
downloadBtn.style.cssText = 'display: flex; align-items: center; justify-content: center; width: 43.9px; height: 43.9px; border-radius: 99px; background: rgba(255, 255, 255, 0.9); cursor: pointer; transition: 0.15s; transform: scale(1); box-shadow: rgba(0, 0, 0, 0.15) 0px 2px 8px;';
downloadBtn.innerHTML = '';
downloadBtn.addEventListener('mouseenter', () => { downloadBtn.style.transform = 'scale(1.1)'; });
downloadBtn.addEventListener('mouseleave', () => { downloadBtn.style.transform = 'scale(1)'; });
downloadBtn.addEventListener('click', (e) => {
e.stopPropagation();
const fileName = (img.alt || '花瓣图片') + '.png';
if (typeof GM_download === "function") {
GM_download({ url, name: fileName });
} else {
const a = document.createElement('a');
a.href = url;
a.download = fileName;
a.click();
}
});
actionPanel.appendChild(downloadBtn);
// PS导入按钮
const psBtn = document.createElement('div');
psBtn.className = 'size-ps-btn';
psBtn.title = '导入PS (Alt+点击新建文件)';
psBtn.style.cssText = 'display: flex; align-items: center; justify-content: center; width: 43.9px; height: 43.9px; cursor: pointer; border-radius: 99px; transition: 0.15s; transform: scale(1); background: rgba(255, 255, 255, 0.9); box-shadow: rgba(0, 0, 0, 0.15) 0px 2px 8px;';
psBtn.innerHTML = '
';
psBtn.addEventListener('mouseenter', () => { psBtn.style.transform = 'scale(1.1)'; });
psBtn.addEventListener('mouseleave', () => { psBtn.style.transform = 'scale(1)'; });
psBtn.addEventListener('click', async (e) => {
e.stopPropagation();
const isNewDoc = e.altKey;
const name = img.alt || '花瓣图片';
const clipboardStr = `PS_IMPORTER:${url}|||${name}${isNewDoc ? '|||NEW_DOC' : ''}`;
try { GM_setClipboard(clipboardStr); } catch { await navigator.clipboard.writeText(clipboardStr); }
showToast(isNewDoc ? '已发送至PS (新建文件)' : '已发送至PS (需安装图片导导插件)', isNewDoc ? false : true);
});
actionPanel.appendChild(psBtn);
parent.appendChild(actionPanel);
}
};
imgEl.src = url;
});
}
// 显示尺寸信息和下载按钮
function showSizeInfo(width, height, dpi, url, fileFormat, contentUrl, type, title) {
const targets = TARGET_SELECTORS.map(sel => document.querySelector(sel)).filter(img => img);
targets.forEach(img => {
const parent = img.parentElement;
if (parent.querySelector('.size-info-overlay') || parent.querySelector('.download-btn-overlay')) return;
// 创建尺寸信息覆盖层
const sizeOverlay = document.createElement('div');
sizeOverlay.className = 'size-info-overlay';
sizeOverlay.textContent = `${width} 像素 x ${height} 像素 (${dpi} dpi)`;
const sizeStyle = {
position: 'absolute',
bottom: '8px',
left: '8px',
color: '#000000ff',
padding: '6px 12px',
zIndex: '9999',
transform: 'translateZ(0)',
borderRadius: '99px',
background: 'rgba(255, 255, 255, 0.7)',
boxShadow: '0 0 1px 0 var(--boxshadow-color-medium-100, rgba(0, 0, 0, .1)), 0 8px 40px -2px var(--boxshadow-color-medium-200, rgba(0, 0, 0, .1))',
transition: 'box-shadow .2s ease',
userSelect: 'none'
};
Object.assign(sizeOverlay.style, sizeStyle);
parent.appendChild(sizeOverlay);
// 在包含ID信息的元素外添加a标签包裹
if (contentUrl) {
const sourceDiv = document.querySelector('.__2p__B98x, .QzLweiwl');
if (sourceDiv && sourceDiv.parentElement && !sourceDiv.parentElement.classList.contains('content-url-wrapper')) {
const wrapper = document.createElement('a');
wrapper.className = 'content-url-wrapper';
wrapper.href = contentUrl;
wrapper.target = '_blank';
wrapper.style.textDecoration = 'none';
wrapper.style.color = 'inherit';
sourceDiv.parentElement.insertBefore(wrapper, sourceDiv);
wrapper.appendChild(sourceDiv);
}
}
// 创建操作按钮组容器
const actionPanel = document.createElement('div');
actionPanel.className = 'size-action-panel';
actionPanel.style.cssText = 'position: absolute;bottom: 60px;right: 8px;display: flex;flex-direction: column;gap: 6px;z-index: 10000;';
// 复制按钮
const copyBtn = document.createElement('div');
copyBtn.className = 'size-copy-btn rtaVfkLT';
copyBtn.title = '复制图片';
copyBtn.style.cssText = 'transition: 0.15s; transform: scale(1); box-shadow: rgba(0, 0, 0, 0.15) 0px 2px 8px;';
copyBtn.innerHTML = '';
copyBtn.addEventListener('mouseenter', () => { copyBtn.style.transform = 'scale(1.05)'; });
copyBtn.addEventListener('mouseleave', () => { copyBtn.style.transform = 'scale(1)'; });
copyBtn.addEventListener('click', async (e) => {
e.stopPropagation();
await copyImageOrUrl(url);
});
actionPanel.appendChild(copyBtn);
// PS导入按钮
const psBtn = document.createElement('div');
psBtn.className = 'size-ps-btn rtaVfkLT';
psBtn.title = '导入PS (Alt+点击新建文件)';
psBtn.style.cssText = 'transition: 0.15s; transform: scale(1); background: rgb(255, 255, 255); box-shadow: rgba(0, 0, 0, 0.15) 0px 2px 8px;';
psBtn.innerHTML = '
';
psBtn.addEventListener('mouseenter', () => { psBtn.style.transform = 'scale(1.05)'; });
psBtn.addEventListener('mouseleave', () => { psBtn.style.transform = 'scale(1)'; });
psBtn.addEventListener('click', async (e) => {
e.stopPropagation();
const isNewDoc = e.altKey;
const name = title || img.alt || '花瓣图片';
const clipboardStr = `PS_IMPORTER:${url}|||${name}${isNewDoc ? '|||NEW_DOC' : ''}`;
try { GM_setClipboard(clipboardStr); } catch { await navigator.clipboard.writeText(clipboardStr); }
showToast(isNewDoc ? '已发送至PS (新建文件)' : '已发送至PS (需安装图片导导插件)', isNewDoc ? false : true);
});
actionPanel.appendChild(psBtn);
parent.appendChild(actionPanel);
// 创建下载按钮(可能返回多个按钮)
const extension = getFileExtension(url);
createDownloadButton(url, img, extension, fileFormat, contentUrl, type, title, parent);
});
}
// 获取文件扩展名
function getFileExtension(url) {
const match = url.match(/\.([^.?]+)(?:\?|$)/);
return match ? '.' + match[1] : '.jpg';
}
// 创建下载按钮的公共函数
function createDownloadButton(url, imgElement, extension, fileFormat, contentUrl, type, title, parent) {
// 不论什么情况,始终显示下载PNG/JPG按钮(下载hdUrl)
const pngButton = createSingleButton('下载 ' + extension.toUpperCase(), url, imgElement, 'image', title);
pngButton.style.top = '8px';
parent.appendChild(pngButton);
let buttonIndex = 1;
// 根据类型和文件格式添加额外的下载按钮
if (type === 'poster' && fileFormat === 'psd') {
// PSD素材:额外显示下载PSD按钮
const psdButton = createSingleButton('下载 PSD', null, imgElement, 'psd', title);
psdButton.style.top = (8 + buttonIndex * 40) + 'px';
parent.appendChild(psdButton);
buttonIndex++;
} else if (type === 'image' && fileFormat === 'eps') {
// EPS素材:需要先获取 originUrl,如果有才显示下载EPS按钮
if (contentUrl) {
fetch(contentUrl)
.then(response => response.json())
.then(data => {
if (data.originUrl) {
const epsButton = createSingleButton('下载 EPS', data.originUrl, imgElement, 'eps', title);
epsButton.style.top = (8 + buttonIndex * 40) + 'px';
parent.appendChild(epsButton);
}
})
.catch(err => {
console.error('获取EPS originUrl失败:', err);
});
}
} else if ((type === 'image' || type === 'element') && fileFormat === 'ai') {
// AI素材:先检测 contentUrl 返回的 model.type,只有是 svg 才显示 AI 下载按钮
const aiButton = createSingleButton('下载 AI', contentUrl, imgElement, 'ai', title);
aiButton.style.top = (8 + buttonIndex * 40) + 'px';
aiButton.style.display = 'none'; // 先隐藏,等检测到是 svg 再显示
parent.appendChild(aiButton);
buttonIndex++;
if (contentUrl) {
fetch(contentUrl)
.then(response => response.json())
.then(data => {
if (data.model && data.model.type === 'svg') {
aiButton.style.display = '';
} else {
aiButton.remove(); // 不是 svg 就删除按钮
}
})
.catch(err => {
console.error('检测AI类型失败:', err);
aiButton.remove();
});
} else {
aiButton.remove();
}
} else if (type === 'element' && fileFormat === 'svg') {
// SVG元素素材:额外显示下载SVG按钮
const svgButton = createSingleButton('下载 SVG', contentUrl, imgElement, 'svg', title);
svgButton.style.top = (8 + buttonIndex * 40) + 'px';
parent.appendChild(svgButton);
buttonIndex++;
} else if (type === 'movie' && fileFormat === 'zip') {
// 视频素材:额外显示下载ZIP、MP3、MP4按钮
const zipButton = createSingleButton('下载 ZIP', contentUrl, imgElement, 'zip', title);
zipButton.style.top = (8 + buttonIndex * 40) + 'px';
parent.appendChild(zipButton);
buttonIndex++;
const mp3Button = createSingleButton('下载 MP3', null, imgElement, 'mp3', title);
mp3Button.style.top = (8 + buttonIndex * 40) + 'px';
parent.appendChild(mp3Button);
buttonIndex++;
const mp4Button = createSingleButton('下载 MP4', null, imgElement, 'mp4', title);
mp4Button.style.top = (8 + buttonIndex * 40) + 'px';
parent.appendChild(mp4Button);
} else if (fileFormat === 'zip' && type !== 'image') {
// 普通ZIP素材(排除type='image'的情况):额外显示下载ZIP按钮
const zipButton = createSingleButton('下载 ZIP', contentUrl, imgElement, 'zip', title);
zipButton.style.top = (8 + buttonIndex * 40) + 'px';
parent.appendChild(zipButton);
}
}
// 创建单个下载按钮
function createSingleButton(text, url, imgElement, downloadType, title) {
const downloadBtn = document.createElement('div');
downloadBtn.className = 'download-btn-overlay';
downloadBtn.textContent = text;
// 判断是否为待实现的功能
const isImplemented = downloadType === 'image' || downloadType === 'zip' || downloadType === 'eps' || downloadType === 'ai' || downloadType === 'svg';
// 统一的按钮样式
const btnStyle = {
position: 'absolute',
top: '8px',
right: '8px',
color: '#ffffff',
padding: '6px 12px',
zIndex: '9999',
transform: 'translateZ(0) scale(1)',
borderRadius: '99px',
background: 'rgba(0, 153, 255, 0.94)',
boxShadow: '0 0 1px 0 var(--boxshadow-color-medium-100, rgba(0, 0, 0, .1)), 0 8px 40px -2px var(--boxshadow-color-medium-200, rgba(0, 0, 0, .1))',
transition: 'transform 0.15s, box-shadow 0.15s',
userSelect: 'none'
};
// 待实现的功能按钮样式
if (!isImplemented) {
btnStyle.cursor = 'not-allowed';
btnStyle.opacity = '0.5';
btnStyle.pointerEvents = 'none';
btnStyle.background = 'rgba(128, 128, 128, 0.7)';
} else {
btnStyle.cursor = 'pointer';
}
Object.assign(downloadBtn.style, btnStyle);
// 添加hover效果
if (isImplemented) {
downloadBtn.addEventListener('mouseenter', () => {
downloadBtn.style.transform = 'translateZ(0) scale(1.05)';
});
downloadBtn.addEventListener('mouseleave', () => {
downloadBtn.style.transform = 'translateZ(0) scale(1)';
});
}
// 根据下载类型绑定不同的点击事件
downloadBtn.addEventListener('click', () => {
console.log(`点击了 ${text} 按钮,下载类型: ${downloadType}`);
switch (downloadType) {
case 'image':
if (url) {
downloadImage(url, imgElement, title);
}
break;
case 'zip':
if (url) {
downloadZipFile(url, imgElement, title);
}
break;
case 'eps':
if (url) {
downloadImage(url, imgElement, title);
}
break;
case 'ai':
if (url) {
downloadAiFile(url, imgElement, title);
}
break;
case 'svg':
if (url) {
downloadSvgFile(url, imgElement, title);
}
break;
case 'psd':
// TODO: 实现PSD下载
console.log('PSD下载功能待实现');
break;
case 'mp3':
// TODO: 实现MP3下载
console.log('MP3下载功能待实现');
break;
case 'mp4':
// TODO: 实现MP4下载
console.log('MP4下载功能待实现');
break;
}
});
return downloadBtn;
}
// 下载图片功能
function downloadImage(url, imgElement, title) {
const filename = title || imgElement?.alt || 'huaban_image';
const extension = getFileExtension(url);
const fullFilename = filename + extension;
fetch(url)
.then(response => {
if (!response.ok) throw new Error('Network response was not ok');
return response.blob();
})
.then(blob => {
const blobUrl = URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = blobUrl;
link.download = fullFilename;
link.style.display = 'none';
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
URL.revokeObjectURL(blobUrl);
})
.catch(() => {
const link = document.createElement('a');
link.href = url;
link.download = fullFilename;
link.style.display = 'none';
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
});
}
// 下载ZIP文件功能
function downloadZipFile(contentUrl, imgElement, title) {
const filename = title || imgElement?.alt || 'huaban_素材';
fetch(contentUrl)
.then(response => response.json())
.then(data => {
if (data.url) {
const link = document.createElement('a');
link.href = data.url;
link.download = `${filename}.zip`;
link.style.display = 'none';
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
} else {
throw new Error('ZIP URL not found');
}
})
.catch(err => {
console.error('ZIP下载失败:', err);
alert('ZIP文件下载失败,请稍后重试');
});
}
// 下载SVG文件功能
function downloadSvgFile(url, imgElement, title) {
const filename = title || imgElement?.alt || 'huaban_素材';
fetch(url)
.then(response => {
if (!response.ok) throw new Error('Network response was not ok');
return response.blob();
})
.then(blob => {
const blobUrl = URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = blobUrl;
link.download = `${filename}.svg`;
link.style.display = 'none';
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
URL.revokeObjectURL(blobUrl);
})
.catch(() => {
const link = document.createElement('a');
link.href = url;
link.download = `${filename}.svg`;
link.style.display = 'none';
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
});
}
// 下载AI文件功能
function downloadAiFile(contentUrl, imgElement, title) {
const filename = title || imgElement?.alt || 'huaban_素材';
fetch(contentUrl)
.then(response => response.json())
.then(data => {
if (data.model && data.model.url) {
const svgUrl = data.model.url;
const colors = data.model.colors || [];
fetch(svgUrl)
.then(response => response.text())
.then(svgContent => {
let processedSvg = svgContent;
if (colors.length > 0) {
processedSvg = svgContent.replace(/\{\{colors\[(\d+)\]\}\}/g, (match, index) => {
const colorIndex = parseInt(index);
return colors[colorIndex] || '#000000';
});
}
const blob = new Blob([processedSvg], { type: 'image/svg+xml' });
const blobUrl = URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = blobUrl;
link.download = `${filename}.svg`;
link.style.display = 'none';
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
URL.revokeObjectURL(blobUrl);
})
.catch(err => {
console.error('SVG下载失败:', err);
alert('SVG文件下载失败,请稍后重试');
});
} else {
throw new Error('AI data not found');
}
})
.catch(err => {
console.error('AI下载失败:', err);
alert('AI文件下载失败,请稍后重试');
});
}
// 替换图片/视频并显示加载指示器
function executeReplacement(url) {
const targets = TARGET_SELECTORS.map(sel => document.querySelector(sel)).filter(img => img && img.src !== url);
targets.forEach(img => {
const parent = img.parentElement;
// 避免重复添加覆盖层
if (parent.querySelector('.loading-overlay')) return;
// 创建加载覆盖层
const overlay = document.createElement('div');
overlay.className = 'loading-overlay';
overlay.textContent = '正在加载高清图片...';
Object.assign(overlay.style, {
position: 'absolute',
top: '0',
left: '0',
width: '100%',
height: '100%',
backgroundColor: 'rgba(0, 0, 0, 0.7)',
color: 'white',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
fontSize: '16px',
fontWeight: 'bold',
zIndex: '9999',
pointerEvents: 'none'
});
// 确保父元素相对定位
if (getComputedStyle(parent).position === 'static') parent.style.position = 'relative';
parent.appendChild(overlay);
// 图片加载完成后移除覆盖层
img.onload = () => {
overlay.remove();
};
img.onerror = () => {
overlay.remove();
console.log('高清图片加载失败');
};
// 设置高清URL并移除srcset
img.src = url;
img.removeAttribute('srcset');
img.style.boxSizing = 'border-box';
});
}
// 拦截拖拽和右键下载事件
function interceptDragAndDownload() {
// 监听拖拽开始事件 - 支持传统拖拽(拖拽到桌面/资源管理器)
document.addEventListener("dragstart", function (e) {
// 确保是图片元素
const img = e.target;
if (img.tagName !== "IMG") {
return;
}
// 只处理图片
if (!img.src.includes("http") && !img.src.includes("data:image") && !img.dataset.originalSrc) {
return;
}
// 检查拖拽下载功能是否启用
const config = getConfig();
if (!config.enableDragDownload) {
debugLog("拖拽下载功能已禁用,跳过处理");
return;
}
debugLog("检测到图片拖拽开始:", img.src);
try {
// 处理URL,删除_fwXXXwebp部分
let cleanUrl;
let isBase64 = img.src.includes("data:image");
if (isBase64) {
// 如果是base64格式,直接使用base64
cleanUrl = img.src;
} else {
// 如果不是base64格式,使用原始URL并处理
const originalSrc = img.dataset.originalSrc || img.src;
cleanUrl = processImageUrl(originalSrc);
}
// 设置拖拽数据 - 支持多种拖拽场景
e.dataTransfer.effectAllowed = "copy";
// 设置URI列表(支持大多数文件管理器)
e.dataTransfer.setData("text/uri-list", cleanUrl);
// 设置纯文本URL(备用)
e.dataTransfer.setData("text/plain", cleanUrl);
// 设置DownloadURL(支持某些浏览器和工具)
const fileName = getFileNameFromAlt(img) + ".png";
e.dataTransfer.setData(
"DownloadURL",
`image/png:${fileName}:${cleanUrl}`
);
debugLog("已设置拖拽数据,文件名将保存为:", fileName);
debugLog("拖拽数据设置完成");
} catch (error) {
console.error("拖拽处理失败:", error);
// 失败时不阻止默认行为,让浏览器使用原始URL
}
});
const HUABAN_CONTEXT_MENU_SELECTOR = '[data-pin-context-menu="true"]';
const RIGHT_CLICK_CONTAINER_SELECTOR =
"#imageViewerWrapper, [data-pin-id], [data-file-id], [data-material-id], [data-content-id], [data-check-cover], [data-content-type], .KKIUywzb, .OPWXbLYw, .Wa6mMsQV, .VFtkdxbR, .PBVOckbr, .ujZSLFrU";
let blockHuabanContextMenuUntil = 0;
let contextMenuCleanupTimer = null;
const shouldRemoveHuabanContextMenu = () => {
const config = getConfig();
return config.enableRightClickDownload;
};
const removeHuabanContextMenu = () => {
document.querySelectorAll(HUABAN_CONTEXT_MENU_SELECTOR).forEach((menu) => {
menu.remove();
});
};
const suppressHuabanContextMenu = (duration = 2500) => {
blockHuabanContextMenuUntil = Date.now() + duration;
removeHuabanContextMenu();
requestAnimationFrame(removeHuabanContextMenu);
setTimeout(removeHuabanContextMenu, 0);
setTimeout(removeHuabanContextMenu, 80);
setTimeout(removeHuabanContextMenu, 250);
if (contextMenuCleanupTimer) clearInterval(contextMenuCleanupTimer);
contextMenuCleanupTimer = setInterval(() => {
removeHuabanContextMenu();
if (Date.now() > blockHuabanContextMenuUntil) {
clearInterval(contextMenuCleanupTimer);
contextMenuCleanupTimer = null;
}
}, 60);
};
new MutationObserver((mutations) => {
if (!shouldRemoveHuabanContextMenu()) return;
mutations.forEach((mutation) => {
mutation.addedNodes.forEach((node) => {
if (node.nodeType !== Node.ELEMENT_NODE) return;
if (
node.matches?.(HUABAN_CONTEXT_MENU_SELECTOR) ||
node.querySelector?.(HUABAN_CONTEXT_MENU_SELECTOR)
) {
removeHuabanContextMenu();
}
});
});
}).observe(document.body, { childList: true, subtree: true });
const getImageUrl = (img) => {
if (!img) return "";
const srcsetUrl = img.srcset?.split(",")?.[0]?.trim()?.split(/\s+/)?.[0] || "";
return img.dataset?.originalSrc || img.currentSrc || img.src || srcsetUrl || "";
};
const isUsableImage = (img) => {
const imageUrl = getImageUrl(img);
return Boolean(img && (imageUrl.includes("http") || imageUrl.includes("data:image") || imageUrl.includes("blob:")));
};
const isSupportedRightClickImage = (img) => {
if (!isUsableImage(img)) return false;
const imageUrl = getImageUrl(img);
const anchor = img.closest("a");
const isHuabanImage = /(?:gd-)?hbimg|huaban\.com/.test(imageUrl);
const isInSupportedContainer = Boolean(img.closest(RIGHT_CLICK_CONTAINER_SELECTOR));
return Boolean(
img.matches(SELECTORS.imageButton) ||
img.closest("#imageViewerWrapper") ||
img.matches(SELECTORS.imageViewerSimple) ||
(anchor && anchor.querySelector('span[style*="display: none"]')) ||
(isHuabanImage && isInSupportedContainer)
);
};
const getCandidateImageFromElement = (element) => {
if (!element) return null;
const img = element?.closest?.("img");
return isSupportedRightClickImage(img) ? img : null;
};
const getImageFromPoint = (e) => {
if (typeof document.elementsFromPoint === "function") {
const elements = document.elementsFromPoint(e.clientX, e.clientY);
for (const element of elements) {
const img = getCandidateImageFromElement(element);
if (img) return img;
}
}
const images = document.querySelectorAll("img");
for (const img of images) {
if (!isSupportedRightClickImage(img)) continue;
const rect = img.getBoundingClientRect();
const isInside =
e.clientX >= rect.left &&
e.clientX <= rect.right &&
e.clientY >= rect.top &&
e.clientY <= rect.bottom;
if (isInside) return img;
}
return null;
};
const isLikelyHuabanImageContext = (e) => {
const element = e.target?.nodeType === Node.ELEMENT_NODE ? e.target : e.target?.parentElement;
if (element?.closest?.(RIGHT_CLICK_CONTAINER_SELECTOR)) return true;
if (typeof document.elementsFromPoint !== "function") return false;
return document.elementsFromPoint(e.clientX, e.clientY).some((pointElement) =>
pointElement.closest?.(RIGHT_CLICK_CONTAINER_SELECTOR)
);
};
const getRightClickDownloadImage = (e) => {
const element = e.target?.nodeType === Node.ELEMENT_NODE ? e.target : e.target?.parentElement;
const img =
getCandidateImageFromElement(element) ||
getImageFromPoint(e);
return img || null;
};
const stopHuabanRightClickMenu = (e) => {
if (e.button !== 2) return;
const config = getConfig();
if (!config.enableRightClickDownload) return;
const img = getRightClickDownloadImage(e);
if (!img && !isLikelyHuabanImageContext(e)) return;
// 花瓣官方菜单会抢右键事件,捕获阶段先截断它的初始化事件。
suppressHuabanContextMenu();
e.preventDefault();
e.stopPropagation();
e.stopImmediatePropagation();
};
const handleRightClickDownload = (e) => {
const img = getRightClickDownloadImage(e);
const config = getConfig();
if (!config.enableRightClickDownload) {
debugLog("右键下载功能已禁用,跳过处理");
return;
}
if (!img) {
if (!isLikelyHuabanImageContext(e)) return;
suppressHuabanContextMenu();
e.preventDefault();
e.stopPropagation();
e.stopImmediatePropagation();
return;
}
// 捕获阶段阻止花瓣官方菜单和浏览器默认菜单。
e.preventDefault();
suppressHuabanContextMenu();
e.stopPropagation();
e.stopImmediatePropagation();
debugLog("检测到图片右键菜单,使用GM_download下载:", img.src);
// 处理URL,删除_fwXXXwebp部分
// 如果是base64格式,使用原始URL
const originalSrc = getImageUrl(img);
const cleanUrl = processImageUrl(originalSrc);
// 使用GM_download API直接下载图片
// 注意:GM_download需要用户确认,所以这里使用异步方式
setTimeout(() => {
try {
// 使用alt属性作为文件名,如果没有alt则使用默认文件名
const fileName = getFileNameFromAlt(img) + ".png";
// 使用GM_download下载图片
// 注意:GM_download会弹出下载确认对话框
GM_download({
url: cleanUrl,
name: fileName,
onload: function () {
console.log("图片下载成功:", fileName, "URL:", cleanUrl);
},
onerror: function (error) {
console.error("图片下载失败:", error);
},
});
} catch (error) {
console.error("GM_download调用失败:", error);
}
}, 100);
};
// 使用捕获阶段抢在花瓣官方右键菜单前处理。
window.addEventListener("pointerdown", stopHuabanRightClickMenu, true);
window.addEventListener("mousedown", stopHuabanRightClickMenu, true);
window.addEventListener("pointerup", stopHuabanRightClickMenu, true);
window.addEventListener("mouseup", stopHuabanRightClickMenu, true);
window.addEventListener("contextmenu", handleRightClickDownload, true);
debugLog("拖拽和右键下载拦截器已启动");
}
// ==================== UI组件 ====================
// 创建配置界面(左侧导航 / 右侧内容)
function createConfigUI() {
const config = getConfig();
// 检查是否已存在配置面板
const existingPanel = document.getElementById("huabanConfig");
if (existingPanel) {
existingPanel.__huabanCleanup?.();
existingPanel.remove();
document.body.style.overflow = "auto";
return;
}
// 创建主容器
const container = document.createElement("div");
container.id = "huabanConfig";
container.className =
"fixed inset-0 bg-black/30 flex items-center justify-center z-[999] backdrop-blur-sm";
// 创建卡片(更宽以容纳侧边栏)
const card = document.createElement("div");
// 固定高度布局,确保左侧导航与右侧内容高度一致
card.className =
"bg-white rounded-[20px] shadow-[0_8px_25px_rgba(0,0,0,0.15)] w-[900px] h-[680px] max-w-[96vw] flex flex-col overflow-hidden";
card.style.fontFamily =
'-apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif';
// 侧边栏与主内容容器
const bodyWrap = document.createElement("div");
bodyWrap.className = "flex flex-1 min-h-0";
const sidebar = document.createElement("div");
// 侧栏采用纵向布局,底部显示版本号
sidebar.className =
"w-[160px] p-3 bg-slate-50 box-border flex flex-col justify-between overflow-hidden";
// 左侧导航按钮
const makeNavBtn = (id, text) => {
const b = document.createElement("button");
b.id = id;
b.textContent = text;
// 简约按钮样式:基础布局 + 简单过渡
b.className =
"block w-full text-left p-3 mb-2 rounded-lg bg-transparent cursor-pointer text-slate-700 text-sm transition-all duration-150 border-none";
// 简约hover效果
b.addEventListener("mouseenter", () => {
if (!b.dataset.active) b.style.backgroundColor = "#e9edf3ff";
});
b.addEventListener("mouseleave", () => {
if (!b.dataset.active) b.style.backgroundColor = "transparent";
});
return b;
};
// 切换激活态样式
function setActive(activeId) {
const btns = sidebar.querySelectorAll('button[id^="cfg-tab-"]');
btns.forEach((b) => {
if (b.id === activeId) {
b.dataset.active = "1";
// 极简选中状态:仅保留背景色和文字颜色
b.style.backgroundColor = "rgb(255, 255, 255)";
b.style.color = "rgb(255, 40, 75)";
} else {
delete b.dataset.active;
b.style.backgroundColor = "transparent";
b.style.color = "rgb(51, 65, 85)";
}
});
}
const navSettings = makeNavBtn("cfg-tab-settings", "⚙️ 设置选项");
const navUsage = makeNavBtn("cfg-tab-usage", "📖 使用说明");
const navUpdate = makeNavBtn("cfg-tab-update", "📝 更新记录");
const navTwikoo = makeNavBtn("cfg-tab-twikoo", "🤝 网友互助");
const navThanks = makeNavBtn("cfg-tab-thanks", "🙏 致谢名单");
const navUserProfile = makeNavBtn("cfg-tab-user", "👤 个人信息");
const navTop = document.createElement("div");
navTop.className = "flex flex-col gap-2";
navTop.appendChild(navSettings);
navTop.appendChild(navUsage);
navTop.appendChild(navUpdate);
navTop.appendChild(navTwikoo);
navTop.appendChild(navThanks);
navTop.appendChild(navUserProfile);
sidebar.appendChild(navTop);
// 版本信息放在侧栏底部,参考示例布局
const versionEl = document.createElement("div");
versionEl.className = "text-xs text-slate-400 p-3 flex items-center justify-between";
const versionText = document.createElement("span");
versionText.textContent = `Pro版 v${GM_info.script.version}`;
versionEl.appendChild(versionText);
const donateLink = document.createElement("a");
donateLink.href = "https://getquicker.net/DonateAuthor?serial=388875&nickname=%E7%88%B1%E5%90%83%E9%A6%8D%E7%9A%84%E5%B0%8F%E5%BC%A0";
donateLink.className = "text-xs text-red-400 hover:text-red-300 transition-colors";
donateLink.textContent = "捐助";
donateLink.target = "_blank";
donateLink.rel = "noopener noreferrer";
versionEl.appendChild(donateLink);
sidebar.appendChild(versionEl);
const main = document.createElement("div");
main.id = "hb-config-main-settings"; // 默认显示设置面板,所以默认id为settings
// 主区使用滚动容器以适配内嵌大型面板(如历史、聊天)
main.className = "flex-1 m-4 overflow-auto min-h-0 box-border";
bodyWrap.appendChild(sidebar);
bodyWrap.appendChild(main);
// 添加到卡片(不再渲染顶部 header)
card.appendChild(bodyWrap);
container.appendChild(card);
// 添加到页面
document.body.appendChild(container);
// 导航交互:渲染不同的面板
function renderSettings() {
// 设置主容器的 id,包含所属分类
main.id = "hb-config-main-settings";
// 重置所有可能受致谢名单影响的样式
main.style.padding = "16px";
main.style.margin = "0";
main.style.background = "none";
main.style.backgroundImage = `url(${SETTINGS_BG_IMAGES.left}), url(${SETTINGS_BG_IMAGES.right})`;
main.style.backgroundPosition = '0 100%, 100% 100%';
main.style.backgroundRepeat = 'no-repeat, no-repeat';
main.style.backgroundSize = '18%';
main.style.position = 'relative';
// 清理旧内容
main.innerHTML = "";
// switchesSection, colorSettings, actions 会被插入后
main.appendChild(switchesSection);
main.appendChild(colorSettingsSection);
main.appendChild(hotkeysSettings);
main.appendChild(actions);
// 添加免责提示
const disclaimer = document.createElement('div');
disclaimer.className = 'text-sm text-center absolute w-full bottom-0 left-1/2 -translate-x-1/2 text-gray-500 py-4';
disclaimer.textContent = '免责声明:本脚本仅用于学习研究,请勿用于商业用途';
main.appendChild(disclaimer);
// 初始化时根据开关状态显示或隐藏颜色选择器
const colorSettingsContainer = document.getElementById("colorSettingsContainer");
const enableCustomSwitch = document.getElementById("enableCustomSwitch");
if (colorSettingsContainer && enableCustomSwitch) {
colorSettingsContainer.style.display = enableCustomSwitch.checked ? "block" : "none";
}
}
// 更新记录在主区域嵌入 Feishu(iframe),若无法显示提供外链
function renderUpdate() {
// 设置主容器的 id,包含所属分类
main.id = "hb-config-main-update";
main.innerHTML = "";
// 重置所有可能受致谢名单影响的样式
main.style.padding = "0";
main.style.margin = "0";
main.style.background = "none";
main.style.borderRadius = "0";
main.style.position = "relative";
const feishuUrl =
"https://ai-chimo.feishu.cn/wiki/EcTAwKw2bifqGjku9pzccaVcnId";
const iframe = document.createElement("iframe");
iframe.src = feishuUrl;
iframe.className = "w-full h-full min-h-[480px] border-0 rounded-lg";
iframe.allow = "fullscreen; clipboard-write";
const fallback = document.createElement("div");
fallback.className =
"text-sm text-center absolute w-full bottom-0 left-1/2 -translate-x-1/2 no-underline bg-white px-4 py-2 rounded shadow-md";
fallback.innerHTML = `若内容无法显示,请 点我在新标签页打开更新记录(飞书文档)`;
main.appendChild(iframe);
main.appendChild(fallback);
}
// 使用说明在主区域嵌入 Feishu(iframe),若无法显示提供外链
function renderUsage() {
// 设置主容器的 id,包含所属分类
main.id = "hb-config-main-usage";
main.innerHTML = "";
// 重置所有可能受致谢名单影响的样式
main.style.padding = "0";
main.style.margin = "0";
main.style.background = "none";
main.style.borderRadius = "0";
main.style.position = "relative";
const feishuUrl =
"https://ai-chimo.feishu.cn/wiki/E9SEwhoMmiv2CkkC1VgcAbRTnW3";
const iframe = document.createElement("iframe");
iframe.src = feishuUrl;
iframe.className = "w-full h-full min-h-[480px] border-0 rounded-lg";
iframe.allow = "fullscreen; clipboard-write";
const fallback = document.createElement("div");
fallback.className =
"text-sm text-center absolute w-full bottom-0 left-1/2 -translate-x-1/2 no-underline bg-white px-4 py-2 rounded shadow-md";
fallback.innerHTML = `若内容无法显示,请 点我在新标签页打开使用说明(飞书文档)`;
main.appendChild(iframe);
main.appendChild(fallback);
}
// 导航按钮事件(同时设置激活态)
navSettings.addEventListener("click", () => {
setActive("cfg-tab-settings");
renderSettings();
});
navUsage.addEventListener("click", () => {
setActive("cfg-tab-usage");
renderUsage();
});
navUpdate.addEventListener("click", () => {
setActive("cfg-tab-update");
renderUpdate();
});
//个人信息
function renderUserProfile() {
// 使用外部作用域中已经定义好的main变量
if (!main) return;
// 设置主容器的 id,包含所属分类
main.id = "hb-config-main-user";
main.innerHTML =
' 加载个人信息中...
';
// 重置所有可能受致谢名单影响的样式
main.style.padding = "16px";
main.style.margin = "0";
main.style.background = "none";
main.style.borderRadius = "0";
main.style.position = "relative";
fetch("https://huaban.com/v3/users/me")
.then((response) => response.json())
.then((data) => {
const createdAt = new Date(
data.created_at * 1000
).toLocaleDateString();
main.innerHTML = `
${data.board_count}
画板
${data.pin_count}
采集
${data.follower_count
}
粉丝
${data.following_count
}
关注
`;
})
.catch((error) => {
main.innerHTML =
' 获取个人信息失败,请稍后重试
';
console.error("获取花瓣用户信息失败:可能未登录", error);
});
}
// 在主区域渲染致谢名单(iframe)
function renderThanksPanel() {
// 设置主容器的 id,包含所属分类
main.id = "hb-config-main-thanks";
main.style.padding = "0px";
main.style.margin = "16px";
main.style.background =
"linear-gradient(rgb(255, 198, 196), rgba(255, 198, 196, 0.95) 50%, rgb(255, 255, 255) 90%)";
main.style.borderRadius = "6px";
// 清理旧内容
main.innerHTML = "";
const iframe = document.createElement("iframe");
iframe.src =
"https://xiaolongmr.github.io/tampermonkey-scripts/%E8%8A%B1%E7%93%A3%E5%8E%BB%E6%B0%B4%E5%8D%B0/%E8%87%B4%E8%B0%A2%E5%90%8D%E5%8D%95.html";
iframe.className =
"block mx-auto w-[420px] h-[585px] border-0 rounded-lg";
main.appendChild(iframe);
}
// 重新创建修改后的renderTwikooPanel函数
function renderTwikooPanel() {
// 设置主容器的 id,包含所属分类
main.id = "hb-config-main-twikoo";
// 重置所有可能受致谢名单影响的样式
main.style.padding = "16px";
main.style.margin = "0";
main.style.background = "none";
main.style.borderRadius = "0";
main.innerHTML = "";
const title = document.createElement("div");
title.className = "flex items-center justify-between mb-3";
title.innerHTML = `网友互助区
通过 Twikoo 聊天与其他用户交流
`;
const wrapper = document.createElement("div");
wrapper.className = "flex-1 flex flex-col gap-3 h-full min-h-0";
// 创建说明文字区域
const materialSitesSection = document.createElement("div");
materialSitesSection.className = "bg-white rounded-lg p-3 box-border";
const materialSitesTitle = document.createElement("div");
const materialSitesList = document.createElement("div");
materialSitesList.className = "text-sm text-slate-600 leading-relaxed";
materialSitesList.innerHTML =
"公众号文章开了广告,朋友们有空的话每天可点点广告,收益用于购买素材解析网站的积分,帮使用脚本的朋友免费下载素材,可下载的素材点我进入查看,复制你要下的素材网址,在下方或公众号任意文章下评论,我看到会帮忙下载的,积分用完为止!";
materialSitesSection.appendChild(materialSitesTitle);
materialSitesSection.appendChild(materialSitesList);
const commentWrap = document.createElement("div");
commentWrap.id = "tcomment";
commentWrap.className =
"flex-1 min-h-0 overflow-auto bg-white rounded-lg p-3 box-border";
wrapper.appendChild(materialSitesSection);
wrapper.appendChild(commentWrap);
main.appendChild(title);
main.appendChild(wrapper);
// 动态加载Twikoo并初始化(若未加载)
try {
if (!document.querySelector('link[href*="twikoo"]')) {
const twikooCss = document.createElement("link");
twikooCss.rel = "stylesheet";
twikooCss.href =
"https://cdn.jsdelivr.net/npm/twikoo@1.6.44/dist/twikoo.css";
document.head.appendChild(twikooCss);
}
if (typeof twikoo === "undefined") {
const twikooScript = document.createElement("script");
twikooScript.src =
"https://cdn.jsdelivr.net/npm/twikoo@1.6.44/dist/twikoo.nocss.js";
twikooScript.onload = function () {
try {
if (typeof twikoo !== "undefined") {
twikoo.init({
envId: "https://twikookaishu.z-l.top",
el: "#tcomment",
path: "/huaban-helper-all",
});
}
} catch (e) {
console.error(e);
}
};
document.head.appendChild(twikooScript);
} else {
try {
twikoo.init({
envId: "https://twikookaishu.z-l.top",
el: "#tcomment",
path: "/huaban-helper-all",
});
} catch (e) { }
}
} catch (e) {
console.error("初始化 Twikoo 失败", e);
}
}
navTwikoo.addEventListener("click", (e) => {
e.preventDefault();
setActive("cfg-tab-twikoo");
renderTwikooPanel();
});
navThanks.addEventListener("click", (e) => {
e.preventDefault();
setActive("cfg-tab-thanks");
renderThanksPanel();
});
navUserProfile.addEventListener("click", (e) => {
e.preventDefault();
setActive("cfg-tab-user");
renderUserProfile();
});
// 初始显示设置面板并设置激活态
// NOTE: moved below after switchesSection/colorSettings/actions are created
const showThanksList = () => {
try {
// 创建模态框
const modal = document.createElement("div");
modal.className =
"fixed inset-0 bg-black/30 flex items-center justify-center z-[9999] backdrop-blur-sm";
// 禁止页面滚动
document.body.style.overflow = "hidden";
// 创建容器
const container = document.createElement("div");
container.className =
"relative w-[420px] h-[585px] max-w-[95vw] max-h-[90vh] overflow-hidden";
// 创建iframe嵌套致谢名单HTML文件
const iframe = document.createElement("iframe");
iframe.src =
"https://xiaolongmr.github.io/tampermonkey-scripts/%E8%8A%B1%E7%93%A3%E5%8E%BB%E6%B0%B4%E5%8D%B0/%E8%87%B4%E8%B0%A2%E5%90%8D%E5%8D%95.html";
iframe.className =
"absolute inset-0 w-full h-full border-0 outline-none";
iframe.allow = "autoplay; clipboard-write";
// 创建关闭按钮
const closeButton = document.createElement("div");
closeButton.className =
"absolute right-2.5 top-2.5 w-7 h-7 bg-black/10 rounded-full flex items-center justify-center select-none z-10 cursor-pointer";
// 创建SVG关闭图标
const closeIcon = document.createElementNS(
"http://www.w3.org/2000/svg",
"svg"
);
closeIcon.setAttribute("width", "16");
closeIcon.setAttribute("height", "16");
closeIcon.setAttribute("xmlns", "http://www.w3.org/2000/svg");
closeIcon.setAttribute("viewBox", "0 0 1024 1024");
closeIcon.setAttribute("fill", "white");
// 创建路径
const path = document.createElementNS(
"http://www.w3.org/2000/svg",
"path"
);
path.setAttribute(
"d",
"M198.1 267.7l75.4-75.4 576.3 576.3-75.4 75.4-576.3-576.3zm576.4-69.3l75.4 75.4-580.7 580.8-75.4-75.4 580.7-580.8z"
);
// 组装SVG图标
closeIcon.appendChild(path);
closeButton.appendChild(closeIcon);
closeButton.addEventListener("click", () => {
document.body.removeChild(modal);
// 恢复页面滚动
document.body.style.overflow = "auto";
});
closeButton.addEventListener("mouseenter", () => {
closeButton.style.backgroundColor = "rgba(0, 0, 0, 0.2)";
});
closeButton.addEventListener("mouseleave", () => {
closeButton.style.backgroundColor = "rgba(0, 0, 0, 0.1)";
});
// 组装模态框
container.appendChild(iframe);
container.appendChild(closeButton);
modal.appendChild(container);
// 点击模态框背景关闭
modal.addEventListener("click", (e) => {
if (e.target === modal) {
document.body.removeChild(modal);
// 恢复页面滚动
document.body.style.overflow = "auto";
}
});
// 添加到文档
document.body.appendChild(modal);
} catch (error) {
console.error("显示致谢名单失败:", error);
alert("无法加载致谢名单,请稍后再试");
}
};
// 延迟添加事件监听器,确保DOM已渲染
setTimeout(() => {
const thanksListLink = document.getElementById("thanksListLink");
if (thanksListLink) {
thanksListLink.addEventListener("click", (e) => {
e.preventDefault();
showThanksList();
});
}
}, 0);
// 卡片内容
const content = main;
// 启用开关区域 - 使用网格布局
const switchesSection = document.createElement("div");
switchesSection.className = "mb-4 grid grid-cols-2 gap-3";
// 自定义背景色开关
const enableCustomSection = document.createElement("div");
enableCustomSection.className =
"flex items-center justify-between p-3 bg-slate-50 rounded-lg border border-slate-200";
const enableCustomHTML = `
开启图片自定义背景色
`;
enableCustomSection.innerHTML = enableCustomHTML;
// 去水印功能开关
const enableWatermarkSection = document.createElement("div");
enableWatermarkSection.className =
"flex items-center justify-between p-3 bg-slate-50 rounded-lg border border-slate-200";
const enableWatermarkHTML = `
花瓣去水印(仅支持商用无忧素材)
`;
enableWatermarkSection.innerHTML = enableWatermarkHTML;
// 拖拽下载功能开关
const enableDragSection = document.createElement("div");
enableDragSection.className =
"flex items-center justify-between p-3 bg-slate-50 rounded-lg border border-slate-200";
const enableDragHTML = `
拖拽下载图片(适配资源管理器/PureRef)
`;
enableDragSection.innerHTML = enableDragHTML;
// 右键下载功能开关
const enableRightClickSection = document.createElement("div");
enableRightClickSection.className =
"flex items-center justify-between p-3 bg-slate-50 rounded-lg border border-slate-200";
const enableRightClickHTML = `
右键下载图片(修正乱码名称)
`;
enableRightClickSection.innerHTML = enableRightClickHTML;
// 隐藏蜡笔小新设置按钮开关
const enableHideSettingsButtonSection = document.createElement("div");
enableHideSettingsButtonSection.className =
"flex items-center justify-between p-3 bg-slate-50 rounded-lg border border-slate-200";
const enableHideSettingsButtonHTML = `
隐藏蜡笔小新(进入此页面的按钮)
`;
enableHideSettingsButtonSection.innerHTML = enableHideSettingsButtonHTML;
// 组装开关区域
switchesSection.appendChild(enableCustomSection);
switchesSection.appendChild(enableWatermarkSection);
switchesSection.appendChild(enableDragSection);
switchesSection.appendChild(enableRightClickSection);
switchesSection.appendChild(enableHideSettingsButtonSection);
const colorSettings = `
🎨 背景颜色
`;
const colorSettingsWrapper = document.createElement("div");
colorSettingsWrapper.innerHTML = colorSettings.trim();
const colorSettingsSection = colorSettingsWrapper.firstElementChild;
// 快捷键设置区域
const hotkeysSettings = document.createElement("div");
hotkeysSettings.className = "mb-4";
hotkeysSettings.innerHTML = `
`;
// 获取当前快捷键配置
const hotkeysConfig = getHotkeysConfig();
// 快捷键项目列表
const hotkeyItems = [
{ id: "searchFocus", label: "定位到搜索框", defaultKey: "k" },
{ id: "imageSearch", label: "以图搜索功能", defaultKey: "p" },
{ id: "openSettings", label: "打开设置界面", defaultKey: "," },
];
// 创建网格容器
const hotkeysGrid = document.createElement("div");
hotkeysGrid.className = "grid grid-cols-2 gap-3";
// 创建每个快捷键设置项
hotkeyItems.forEach((item) => {
const hotkeyItem = document.createElement("div");
hotkeyItem.className =
"p-2 bg-slate-50 rounded-lg border border-slate-200";
const hotkeyConfig = hotkeysConfig[item.id] || {
ctrlCmd: true,
shift: false,
alt: false,
key: item.defaultKey,
};
hotkeyItem.innerHTML = `
${item.label
}
`;
hotkeysGrid.appendChild(hotkeyItem);
});
// 将网格容器添加到快捷键设置区域
hotkeysSettings.appendChild(hotkeysGrid);
// 操作按钮
const actions = document.createElement("div");
actions.className = "flex gap-2";
actions.innerHTML = `
`;
// 组装内容
content.appendChild(switchesSection);
content.appendChild(colorSettingsSection);
content.appendChild(hotkeysSettings);
content.appendChild(actions);
// 初始显示设置面板并设置激活态(放在这里以确保所有元素已创建)
setActive("cfg-tab-settings");
renderSettings();
// 卡片已在前面组装并添加到页面,后续只需填充 `content`(即 main)
// 获取元素
const enableCustomSwitch = document.getElementById("enableCustomSwitch");
const enableCustomThumb = document.getElementById("enableCustomThumb");
const enableCustomContainer = document.getElementById(
"enableCustomContainer"
);
const enableDragSwitch = document.getElementById("enableDragSwitch");
const enableDragThumb = document.getElementById("enableDragThumb");
const enableDragContainer = document.getElementById("enableDragContainer");
const enableRightClickSwitch = document.getElementById("enableRightClickSwitch");
const enableRightClickThumb = document.getElementById("enableRightClickThumb");
const enableRightClickContainer = document.getElementById(
"enableRightClickContainer"
);
const enableWatermarkSwitch = document.getElementById("enableWatermarkSwitch");
const enableWatermarkThumb = document.getElementById("enableWatermarkThumb");
const enableWatermarkContainer = document.getElementById(
"enableWatermarkContainer"
);
const enableHideSettingsButtonSwitch = document.getElementById("enableHideSettingsButtonSwitch");
const enableHideSettingsButtonThumb = document.getElementById("enableHideSettingsButtonThumb");
const enableHideSettingsButtonContainer = document.getElementById(
"enableHideSettingsButtonContainer"
);
const materialPreview = document.getElementById("materialPreview");
const materialPicker = document.getElementById("materialPicker");
const userPreview = document.getElementById("userPreview");
const userPicker = document.getElementById("userPicker");
const saveBtn = document.getElementById("saveBtn");
const resetBtn = document.getElementById("resetBtn");
// 开关事件处理器工厂函数 - 消除重复代码
const createSwitchHandler = (
thumbElement,
containerElement,
callback
) => {
return function () {
const isChecked = this.checked;
const switchBg = containerElement.querySelector("span:nth-child(2)");
switchBg.style.backgroundColor = isChecked ? "#3b82f6" : "#e2e8f0";
thumbElement.style.left = isChecked ? "22px" : "2px";
if (typeof callback === "function") callback(isChecked);
};
};
// 修复自定义背景色开关功能
enableCustomSwitch.addEventListener(
"change",
createSwitchHandler(
enableCustomThumb,
enableCustomContainer,
(isChecked) => {
applyStyles();
// 动态显示或隐藏颜色选择器
const colorSettingsContainer = document.getElementById(
"colorSettingsContainer"
);
if (colorSettingsContainer) {
colorSettingsContainer.style.display = isChecked ? "block" : "none";
}
}
)
);
// 拖拽下载开关功能
enableDragSwitch.addEventListener(
"change",
createSwitchHandler(
enableDragThumb,
enableDragContainer,
(isChecked) => {
debugLog("拖拽下载开关状态变化:", isChecked);
}
)
);
// 右键下载功能
enableRightClickSwitch.addEventListener(
"change",
createSwitchHandler(
enableRightClickThumb,
enableRightClickContainer,
(isChecked) => {
debugLog("右键下载开关状态变化:", isChecked);
}
)
);
// 去水印功能开关
enableWatermarkSwitch.addEventListener(
"change",
createSwitchHandler(
enableWatermarkThumb,
enableWatermarkContainer,
(isChecked) => {
debugLog("去水印开关状态变化:", isChecked);
}
)
);
// 隐藏蜡笔小新设置按钮开关
enableHideSettingsButtonSwitch.addEventListener(
"change",
createSwitchHandler(
enableHideSettingsButtonThumb,
enableHideSettingsButtonContainer,
(isChecked) => {
debugLog("隐藏蜡笔小新开关状态变化:", isChecked);
// 立即更新设置按钮的显示状态
const settingsButton = document.getElementById('hb-settings-button');
if (settingsButton) {
settingsButton.style.display = isChecked ? 'none' : 'block';
}
}
)
);
// 颜色选择器工厂函数 - 简化版本,只保留拾色器功能
const createColorPickerHandler = (
pickerElement,
previewElement
) => {
// 预览元素点击打开拾色器
previewElement.addEventListener("click", () => pickerElement.click());
// 拾色器输入事件
pickerElement.addEventListener("input", (e) => {
previewElement.style.backgroundColor = e.target.value;
});
};
// 绑定材料水印颜色选择器
createColorPickerHandler(materialPicker, materialPreview);
// 绑定用户水印颜色选择器
createColorPickerHandler(userPicker, userPreview);
// 保存配置
// 初始化快捷键设置功能
function initHotkeySettings() {
// 快捷键配置变量
let currentHotkeyInput = null;
let currentHotkeyConfig = getHotkeysConfig();
let originalHotkeyValue = null;
// 默认快捷键配置
const defaultHotkeys = DEFAULT_HOTKEYS;
// 格式化快捷键显示文本
const formatHotkeyText = (config) => {
return `${config.ctrlCmd ? "Ctrl+" : ""}${config.shift ? "Shift+" : ""
}${config.alt ? "Alt+" : ""}${config.key.toUpperCase()}`;
};
// 处理输入框激活
const handleInputFocus = (input) => {
// 移除其他输入框的激活状态
document.querySelectorAll('input[id^="hotkey-"]').forEach((i) => {
i.style.borderColor = "#e2e8f0";
// 恢复其他输入框的原始值
if (i !== input && i.dataset.originalValue) {
i.value = i.dataset.originalValue;
}
});
// 保存原始值
originalHotkeyValue = input.value;
input.dataset.originalValue = originalHotkeyValue;
// 设置当前激活的输入框
currentHotkeyInput = input;
currentHotkeyInput.style.borderColor = "#ff284b";
currentHotkeyInput.value = "请按下新的快捷键组合...";
};
// 处理输入框失焦
const handleInputBlur = (input) => {
if (currentHotkeyInput === input) {
// 如果没有完成快捷键设置,恢复原始值
input.value = input.dataset.originalValue || "";
input.style.borderColor = "#e2e8f0";
currentHotkeyInput = null;
originalHotkeyValue = null;
}
};
// 处理键盘事件
const handleKeydown = (e) => {
if (currentHotkeyInput) {
e.preventDefault();
// 获取按键信息
const ctrlCmd = e.ctrlKey || e.metaKey;
const shift = e.shiftKey;
const alt = e.altKey;
const key = e.key.toLowerCase();
// 只允许字母、数字和部分符号
if (key && key.length === 1 && !e.code.includes("F") && key !== " ") {
// 构建快捷键配置
const hotkeyConfig = {
ctrlCmd,
shift,
alt,
key,
description: currentHotkeyConfig[currentHotkeyInput.dataset.hotkeyId].description,
};
// 更新输入框显示
currentHotkeyInput.value = formatHotkeyText(hotkeyConfig);
// 保存到临时配置
currentHotkeyConfig[currentHotkeyInput.dataset.hotkeyId] = hotkeyConfig;
// 移除激活状态和原始值标记
currentHotkeyInput.style.borderColor = "#e2e8f0";
delete currentHotkeyInput.dataset.originalValue;
currentHotkeyInput = null;
originalHotkeyValue = null;
}
}
};
// 处理重置按钮点击
const handleResetClick = (btn) => {
const hotkeyId = btn.id.replace("reset-hotkey-", "");
const input = document.getElementById(`hotkey-${hotkeyId}`);
// 获取默认配置
const defaultConfig = defaultHotkeys[hotkeyId];
// 更新输入框显示
input.value = formatHotkeyText(defaultConfig);
// 更新临时配置
currentHotkeyConfig[hotkeyId] = defaultConfig;
};
// 保存快捷键配置
const saveHotkeyConfig = () => {
if (typeof GM_setValue === "function") {
GM_setValue("hotkeysConfig", currentHotkeyConfig);
}
};
// 绑定事件监听器
// 输入框点击事件
document.querySelectorAll('input[id^="hotkey-"]').forEach((input) => {
input.addEventListener("click", () => handleInputFocus(input));
input.addEventListener("blur", () => handleInputBlur(input));
});
// 键盘事件
document.addEventListener("keydown", handleKeydown);
// 重置按钮事件
document.querySelectorAll('button[id^="reset-hotkey-"]').forEach((btn) => {
btn.addEventListener("click", () => handleResetClick(btn));
});
// 返回保存配置和清理监听的方法
return {
saveHotkeyConfig,
cleanup() {
document.removeEventListener("keydown", handleKeydown);
},
};
}
// 初始化快捷键设置
const hotkeyManager = initHotkeySettings();
saveBtn.addEventListener("click", () => {
// 从颜色选择器获取颜色值
const materialColor = materialPicker.value;
const userColor = userPicker.value;
// 由于使用了颜色选择器,颜色值一定是有效的HEX格式,所以可以跳过验证
if (!isValidColor(materialColor) || !isValidColor(userColor)) {
alert("请选择有效的颜色");
return;
}
const newConfig = {
enableCustom: enableCustomSwitch.checked,
enableDragDownload: enableDragSwitch.checked,
enableRightClickDownload: enableRightClickSwitch.checked,
enableRemoveWatermark: enableWatermarkSwitch.checked,
enableHideSettingsButton: enableHideSettingsButtonSwitch.checked,
materialColor: materialColor,
userColor: userColor,
};
saveConfig(newConfig);
window.__huabanSyncContextMenuGuard?.();
// 保存快捷键配置
hotkeyManager.saveHotkeyConfig();
applyStyles();
const originalText = saveBtn.textContent;
saveBtn.textContent = "已保存!";
setTimeout(
() => (saveBtn.textContent = originalText),
TIMING.switchTransition
);
setTimeout(closeConfig, TIMING.switchTransition + 200);
});
// 恢复默认
resetBtn.addEventListener("click", () => {
if (confirm("确定恢复默认设置吗?")) {
saveConfig(DEFAULT_CONFIG);
window.__huabanSyncContextMenuGuard?.();
// 恢复所有开关状态的工厂函数
const restoreSwitchState = (
switchEl,
thumbEl,
containerEl,
isEnabled
) => {
switchEl.checked = isEnabled;
const switchBg = containerEl.querySelector("span:nth-child(2)");
switchBg.style.backgroundColor = isEnabled
? "#3b82f6"
: "#e2e8f0";
thumbEl.style.left = isEnabled ? "22px" : "2px";
};
// 恢复所有开关状态
restoreSwitchState(
enableCustomSwitch,
enableCustomThumb,
enableCustomContainer,
DEFAULT_CONFIG.enableCustom
);
restoreSwitchState(
enableDragSwitch,
enableDragThumb,
enableDragContainer,
DEFAULT_CONFIG.enableDragDownload
);
restoreSwitchState(
enableRightClickSwitch,
enableRightClickThumb,
enableRightClickContainer,
DEFAULT_CONFIG.enableRightClickDownload
);
restoreSwitchState(
enableWatermarkSwitch,
enableWatermarkThumb,
enableWatermarkContainer,
DEFAULT_CONFIG.enableRemoveWatermark
);
restoreSwitchState(
enableHideSettingsButtonSwitch,
enableHideSettingsButtonThumb,
enableHideSettingsButtonContainer,
DEFAULT_CONFIG.enableHideSettingsButton
);
// 恢复颜色设置
materialPreview.style.backgroundColor = DEFAULT_CONFIG.materialColor;
materialPicker.value = DEFAULT_CONFIG.materialColor;
userPreview.style.backgroundColor = DEFAULT_CONFIG.userColor;
userPicker.value = DEFAULT_CONFIG.userColor;
// 应用设置
applyStyles();
const originalText = resetBtn.textContent;
resetBtn.textContent = "已恢复!";
setTimeout(
() => (resetBtn.textContent = originalText),
TIMING.switchTransition
);
}
});
let isConfigClosing = false;
const handleConfigEsc = (e) => {
if (e.key === "Escape") closeConfig();
};
const cleanupConfigListeners = () => {
document.removeEventListener("keydown", handleConfigEsc);
hotkeyManager.cleanup?.();
};
container.__huabanCleanup = cleanupConfigListeners;
// 关闭配置
function closeConfig() {
if (isConfigClosing) return;
isConfigClosing = true;
cleanupConfigListeners();
container.style.opacity = "0";
setTimeout(() => {
container.remove();
// 恢复页面滚动
document.body.style.overflow = "auto";
}, 200);
}
// 点击外部关闭
container.addEventListener("click", (e) => {
if (e.target === container) closeConfig();
});
// ESC键关闭
document.addEventListener("keydown", handleConfigEsc);
}
// ==================== 初始化 ====================
// 初始化
function init() {
// 注册油猴菜单命令
GM_registerMenuCommand("⚙️ 设置首选项", createConfigUI);
// 应用样式(包含动画效果)
applyStyles();
// 立即尝试渲染素材网站列表(不等待事件)
renderMaterialSitesOnSucaiPage();
// 使用MutationObserver监听DOM变化,确保元素出现后立即渲染
const materialSitesObserver = new MutationObserver(() => {
if (window.location.href === "https://huaban.com/pages/sucai") {
const layoutContent = document.getElementById("layout-content");
if (layoutContent) {
// 检查容器是否存在,如果不存在则渲染
if (!document.getElementById("material-sites-container")) {
renderMaterialSitesOnSucaiPage();
}
}
}
});
// 观察body的子元素变化,持续监听
materialSitesObserver.observe(document.body, {
childList: true,
subtree: true,
});
// 监听URL变化,处理单页应用导航
let lastUrl = window.location.href;
new MutationObserver(() => {
const currentUrl = window.location.href;
if (currentUrl !== lastUrl) {
lastUrl = currentUrl;
if (currentUrl === "https://huaban.com/pages/sucai") {
// 延迟执行,确保DOM已更新
setTimeout(() => {
renderMaterialSitesOnSucaiPage();
}, 500);
}
}
}).observe(document, {
subtree: true,
childList: true
});
// 页面加载完成后执行初始化
window.addEventListener("load", () => {
debugLog("页面加载完成,执行初始化");
setTimeout(() => {
applyStyles();
}, 500); // 延迟一点时间,确保页面完全渲染
});
// 监听页面变化
const observer = observePageChanges();
// 拦截拖拽和右键下载事件
interceptDragAndDownload();
// 在页面左下角添加设置按钮
addSettingsButtonToPage();
// 初始化图片hover面板
initImageHover();
// 去水印功能初始化
// 监听DOM变化,处理异步加载内容
const watermarkObserver = new MutationObserver(handleUpdate);
watermarkObserver.observe(document.body, { childList: true, subtree: true });
// 定期检查更新(处理URL变化等情况)
setInterval(handleUpdate, 1000);
// 清理函数
window.addEventListener("beforeunload", () => {
observer.disconnect();
watermarkObserver.disconnect();
});
// 在页面左下角添加设置按钮
function addSettingsButtonToPage() {
try {
// 检查是否已经添加过
if (document.getElementById('hb-settings-button')) return;
// 获取配置,检查是否需要隐藏设置按钮
const config = getConfig();
if (config.enableHideSettingsButton) {
debugLog("设置按钮已隐藏");
return;
}
// 创建设置按钮
const settingsButton = document.createElement('div');
settingsButton.id = 'hb-settings-button';
settingsButton.innerHTML = '

';
// 设置固定定位,左下角,距离顶部30%
settingsButton.style.cssText = `
position: fixed;
left: 1px;
top: 50%;
z-index: 9999;
cursor: pointer;
transition: transform 0.2s ease;
`;
// 添加悬停效果
settingsButton.addEventListener('mouseenter', () => {
settingsButton.style.transform = 'scale(1.05)';
});
settingsButton.addEventListener('mouseleave', () => {
settingsButton.style.transform = 'scale(1)';
});
// 添加点击事件,打开脚本设置
settingsButton.addEventListener('click', () => {
createConfigUI();
});
// 添加到页面
document.body.appendChild(settingsButton);
debugLog("设置按钮已添加到页面左下角");
} catch (error) {
console.error("添加设置按钮失败:", error);
}
}
// 添加快捷键处理
document.addEventListener("keydown", (e) => {
// 获取当前快捷键配置
const hotkeysConfig = getHotkeysConfig();
// 快速定位到搜索框
if (isHotkeyMatch(e, hotkeysConfig.searchFocus)) {
// 阻止默认行为
e.preventDefault();
// 查找搜索框并聚焦
const searchInput = document.getElementById("hb_search_input");
if (searchInput) {
searchInput.focus();
// 选中搜索框内容,方便直接输入新内容
searchInput.select();
// 查找data-button-name="搜索框"的元素并修改其:before伪元素背景色
const searchButton = document.querySelector(
'[data-button-name="搜索框"]'
);
if (searchButton) {
// 添加类名以便修改伪元素样式
searchButton.classList.add("hb-search-focused");
// 8秒后移除类名,恢复原来的样式
setTimeout(() => {
searchButton.classList.remove("hb-search-focused");
}, 8000);
}
}
}
// 以图搜索功能
if (isHotkeyMatch(e, hotkeysConfig.imageSearch)) {
// 查找以图搜索按钮
const imageSearchButton = document.querySelector(
'[data-button-name="以图搜索按钮"]'
);
if (imageSearchButton) {
// 如果是第一次按下Ctrl+V/Cmd+V
if (!isImageSearchMode) {
// 阻止默认的粘贴行为
e.preventDefault();
// 模拟点击以图搜索按钮
imageSearchButton.click();
// 设置状态为true,表示已进入图片搜索模式
isImageSearchMode = true;
// 3秒后自动重置状态(如果用户没有进行第二次操作)
setTimeout(() => {
isImageSearchMode = false;
}, 3000);
} else {
// 如果是第二次按下Ctrl+V/Cmd+V,恢复正常粘贴功能
// 不阻止默认行为,让用户可以粘贴图片
// 重置状态,以便下次使用
isImageSearchMode = false;
}
}
}
// 打开设置界面
if (isHotkeyMatch(e, hotkeysConfig.openSettings)) {
// 阻止默认行为
e.preventDefault();
// 调用设置首选项函数
createConfigUI();
}
});
}
init();
})();