// ==UserScript==
// @name QQ空间自动点赞 (全能GUI版)
// @namespace https://scriptcat.org/
// @version 3.0
// @description 支持个人中心 + 好友主页自动点赞。内置GUI设置界面,支持白名单/黑名单配置。
// @author AI Assistant
// @match https://user.qzone.qq.com/*
// @match https://qzone.qq.com/*
// @grant GM_getValue
// @grant GM_setValue
// @grant GM_addStyle
// @run-at document-idle
// ==/UserScript==
(function() {
'use strict';
// --- 默认配置 ---
const DEFAULT_CONFIG = {
mode: 'all', // all | whitelist | blacklist
names: '', // 逗号或换行分隔
minDelay: 800,
maxDelay: 2000,
enable: true // 总开关
};
// --- 样式定义 ---
const STYLES = `
#qz-helper-btn {
position: fixed;
bottom: 50px;
right: 20px;
z-index: 9999;
background: linear-gradient(135deg, #FF9800, #F44336);
color: #fff;
border: none;
border-radius: 50px;
padding: 10px 20px;
font-size: 14px;
font-weight: bold;
box-shadow: 0 4px 15px rgba(244, 67, 54, 0.4);
cursor: pointer;
transition: transform 0.2s;
}
#qz-helper-btn:hover { transform: scale(1.05); }
#qz-settings-panel {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 400px;
background: #fff;
z-index: 10000;
border-radius: 12px;
box-shadow: 0 10px 30px rgba(0,0,0,0.3);
padding: 20px;
font-family: "Microsoft YaHei", sans-serif;
display: none;
border: 1px solid #eee;
}
.qz-header { display: flex; justify-content: space-between; align-items: center; border-bottom: 1px solid #eee; padding-bottom: 10px; margin-bottom: 15px; }
.qz-header h3 { margin: 0; font-size: 18px; color: #333; }
.qz-close { cursor: pointer; font-size: 24px; color: #999; line-height: 1; }
.qz-form-item { margin-bottom: 15px; }
.qz-form-item label { display: block; margin-bottom: 5px; font-weight: bold; font-size: 13px; color: #555; }
.qz-form-item select, .qz-form-item input, .qz-form-item textarea {
width: 100%; box-sizing: border-box; padding: 8px; border: 1px solid #ddd; border-radius: 6px; font-size: 13px; outline: none;
}
.qz-form-item select:focus, .qz-form-item input:focus, .qz-form-item textarea:focus { border-color: #FF9800; }
.qz-form-item textarea { height: 80px; resize: vertical; }
.qz-delay-group { display: flex; gap: 10px; align-items: center; }
.qz-footer { margin-top: 20px; text-align: right; }
.qz-btn { padding: 8px 25px; border-radius: 6px; border: none; cursor: pointer; font-size: 13px; font-weight: bold; transition: background 0.2s;}
.qz-btn-primary { background: #FF9800; color: white; }
.qz-btn-primary:hover { background: #F57C00; }
.qz-status-bar { margin-top: 15px; font-size: 12px; color: #666; border-top: 1px solid #eee; padding-top: 10px; background: #f9f9f9; padding: 10px; border-radius: 4px;}
`;
GM_addStyle(STYLES);
// --- 状态管理 ---
let config = GM_getValue('qz_config', DEFAULT_CONFIG);
let isRunning = false;
// --- GUI 构建函数 (保持不变,略微美化) ---
function createGUI() {
if (document.getElementById('qz-helper-btn')) return;
const btn = document.createElement('button');
btn.id = 'qz-helper-btn';
btn.innerHTML = '❤️ 点赞设置';
btn.onclick = togglePanel;
document.body.appendChild(btn);
const panel = document.createElement('div');
panel.id = 'qz-settings-panel';
panel.innerHTML = `
状态: 准备就绪
`;
document.body.appendChild(panel);
panel.querySelector('.qz-close').onclick = togglePanel;
panel.querySelector('#qz-save').onclick = saveConfig;
}
function togglePanel() {
const panel = document.getElementById('qz-settings-panel');
if (panel.style.display === 'block') {
panel.style.display = 'none';
} else {
loadConfigToUI();
panel.style.display = 'block';
}
}
function loadConfigToUI() {
config = GM_getValue('qz_config', DEFAULT_CONFIG);
document.getElementById('qz-enable').value = config.enable.toString();
document.getElementById('qz-mode').value = config.mode;
document.getElementById('qz-names').value = config.names;
document.getElementById('qz-min-delay').value = config.minDelay;
document.getElementById('qz-max-delay').value = config.maxDelay;
}
function saveConfig() {
const newConfig = {
enable: document.getElementById('qz-enable').value === 'true',
mode: document.getElementById('qz-mode').value,
names: document.getElementById('qz-names').value,
minDelay: parseInt(document.getElementById('qz-min-delay').value) || 800,
maxDelay: parseInt(document.getElementById('qz-max-delay').value) || 2000
};
GM_setValue('qz_config', newConfig);
config = newConfig;
const btn = document.getElementById('qz-save');
btn.innerText = "已保存!";
setTimeout(() => {
btn.innerText = "保存并应用";
document.getElementById('qz-settings-panel').style.display = 'none';
}, 800);
updateStatus("配置已更新,继续扫描...");
}
function updateStatus(text) {
const el = document.getElementById('qz-status');
if (el) el.innerText = `状态: ${text}`;
}
// --- 核心逻辑增强版 ---
const sleep = (ms) => new Promise(r => setTimeout(r, ms));
const randomSleep = (min, max) => sleep(Math.floor(Math.random() * (max - min + 1) + min));
const getNameList = () => {
if (!config.names) return [];
return config.names.split(/[,,\n]/).map(s => s.trim()).filter(s => s);
};
const checkPermission = (author) => {
const list = getNameList();
if (config.mode === 'all') return true;
if (config.mode === 'whitelist') return list.includes(author);
if (config.mode === 'blacklist') return !list.includes(author);
return false;
};
/**
* 获取页面上所有的动态元素
* 关键修改:同时查找主文档和 app_canvas_frame 框架内的元素
*/
const getAllFeeds = () => {
let feeds = [];
// 1. 扫描主文档
try {
const mainFeeds = document.querySelectorAll('.f-single');
if(mainFeeds.length > 0) feeds = feeds.concat(Array.from(mainFeeds));
} catch(e) {}
// 2. 扫描 Iframe (针对个人主页)
try {
const iframe = document.getElementById('app_canvas_frame');
if (iframe && iframe.contentDocument) {
const frameFeeds = iframe.contentDocument.querySelectorAll('.f-single');
if(frameFeeds.length > 0) feeds = feeds.concat(Array.from(frameFeeds));
}
} catch (e) {
// 可能会遇到跨域保护,但在QQ空间同域下通常没事
console.warn("访问Iframe受限:", e);
}
return feeds;
};
const mainTask = async () => {
if (isRunning) return;
config = GM_getValue('qz_config', DEFAULT_CONFIG);
if (!config.enable) {
updateStatus("脚本暂停中");
return;
}
isRunning = true;
updateStatus("正在扫描动态 (含好友主页)...");
try {
const feeds = getAllFeeds();
let hasAction = false;
for (const feed of feeds) {
if (!config.enable) break;
// 1. 查找点赞按钮 (v3通用版)
const likeBtn = feed.querySelector('.qz_like_btn_v3');
if (!likeBtn) continue;
// 2. 检查是否已赞
// item-on 类代表已赞,或者 title/aria-label 包含取消
if (likeBtn.classList.contains('item-on')) continue;
// 3. 获取作者昵称
// 个人主页的结构略有不同,有时候昵称在 .user-info 里
let nickEl = feed.querySelector('.f-nick') || feed.querySelector('.user-info .nickname');
// 如果在个人主页找不到昵称元素,可能需要从页面标题或者顶部获取(视作该页面的主人)
// 这里做个兜底:如果找不到昵称,但在个人主页Iframe里,通常意味着是号主发的
let author = "";
if (nickEl) {
author = nickEl.innerText.trim();
} else {
// 尝试获取动态所有者备选方案
const ownerEl = document.querySelector('.head-detail .user-name') ||
(document.getElementById('app_canvas_frame') &&
document.getElementById('app_canvas_frame').contentDocument.querySelector('.user-name'));
if(ownerEl) author = ownerEl.innerText.trim();
}
if (!author) continue; // 如果实在找不到名字,为了安全起见跳过
// 4. 权限校验
if (checkPermission(author)) {
updateStatus(`正在点赞: ${author}`);
likeBtn.click();
hasAction = true;
await randomSleep(config.minDelay, config.maxDelay);
}
}
if (!hasAction) {
// 判断当前是在哪个环境
const count = feeds.length;
updateStatus(`扫描了 ${count} 条动态,暂无新内容。`);
}
} catch (e) {
console.error("点赞出错:", e);
updateStatus("发生错误,重试中...");
} finally {
isRunning = false;
}
};
// --- 启动 ---
window.addEventListener('load', () => {
createGUI();
// 首次延迟启动
setTimeout(mainTask, 3000);
// 循环扫描 (4秒一次)
setInterval(mainTask, 4000);
console.log("QQ空间全能点赞助手 v3.0 已加载");
});
})();