ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Tampermonkey脚本实现春节批量拜年祝福自动化

Tampermonkey脚本实现春节批量拜年祝福自动化

1. 项目背景与需求分析

春节作为中国人最重要的传统节日,拜年祝福是必不可少的社交礼仪。但随着社交网络的发展,手动逐个发送祝福的方式已经显得效率低下。作为一名前端开发者,我决定用技术手段解决这个痛点——开发一个半自动批量拜年工具。

这个Tampermonkey脚本需要解决几个核心问题:

  • 如何在社交平台网页版上实现自动化操作
  • 如何避免因操作频率过高被平台限制
  • 如何确保只给真正的"好友"发送祝福
  • 如何让整个过程可控可监控

2. 技术选型与方案设计

2.1 为什么选择Tampermonkey

Tampermonkey是目前最流行的用户脚本管理器,具有以下优势:

  • 跨浏览器支持(Chrome/Firefox/Edge等)
  • 无需复杂部署,安装即可使用
  • 可以直接操作DOM,与网页交互
  • 支持GM_* API,提供丰富的扩展功能

2.2 核心功能模块设计

整个工具包含以下功能模块:

  1. UI控制面板:提供开始/暂停按钮、速度调节、日志显示
  2. 好友检测模块:识别有效好友关系
  3. 限速控制器:防止操作过于频繁
  4. 日志系统:记录发送状态和异常情况

3. 详细实现步骤

3.1 基础框架搭建

首先创建基本的Tampermonkey脚本框架:

// ==UserScript== // @name 春节半自动拜年工具 // @namespace http://tampermonkey.net/ // @version 1.0 // @description 半自动批量发送春节祝福 // @author YourName // @match https://*.example.com/* // @grant GM_setValue // @grant GM_getValue // @grant GM_log // ==/UserScript== (function() { 'use strict'; // 主逻辑将在这里实现 })();

3.2 UI界面开发

在页面右下角添加浮动控制面板:

function createControlPanel() { const panel = document.createElement('div'); panel.id = 'newYearPanel'; panel.style.position = 'fixed'; panel.style.right = '20px'; panel.style.bottom = '20px'; panel.style.width = '300px'; panel.style.backgroundColor = '#fff'; panel.style.border = '1px solid #ddd'; panel.style.borderRadius = '5px'; panel.style.padding = '15px'; panel.style.boxShadow = '0 0 10px rgba(0,0,0,0.1)'; panel.style.zIndex = '9999'; // 添加标题 const title = document.createElement('h3'); title.textContent = '春节拜年助手'; title.style.marginTop = '0'; panel.appendChild(title); // 添加控制按钮 const startBtn = document.createElement('button'); startBtn.textContent = '开始发送'; startBtn.style.marginRight = '10px'; panel.appendChild(startBtn); const pauseBtn = document.createElement('button'); pauseBtn.textContent = '暂停'; panel.appendChild(pauseBtn); // 添加速度控制滑块 const speedLabel = document.createElement('label'); speedLabel.textContent = '发送速度:'; speedLabel.style.display = 'block'; speedLabel.style.marginTop = '15px'; panel.appendChild(speedLabel); const speedSlider = document.createElement('input'); speedSlider.type = 'range'; speedSlider.min = '1'; speedSlider.max = '10'; speedSlider.value = '5'; speedSlider.style.width = '100%'; panel.appendChild(speedSlider); // 添加日志区域 const logLabel = document.createElement('label'); logLabel.textContent = '操作日志:'; logLabel.style.display = 'block'; logLabel.style.marginTop = '15px'; panel.appendChild(logLabel); const logArea = document.createElement('div'); logArea.id = 'newYearLog'; logArea.style.height = '150px'; logArea.style.overflowY = 'auto'; logArea.style.border = '1px solid #eee'; logArea.style.padding = '5px'; logArea.style.fontFamily = 'monospace'; logArea.style.fontSize = '12px'; panel.appendChild(logArea); document.body.appendChild(panel); return { startBtn, pauseBtn, speedSlider, logArea }; }

3.3 好友检测实现

检测有效好友的几种方法:

  1. 通过共同群组判断:
function isRealFriend(userElement) { // 检查是否有"共同群组"提示 const commonGroups = userElement.querySelector('.common-groups'); if (commonGroups && commonGroups.textContent.includes('个共同群聊')) { return true; } // 检查是否有"好友"标签 const friendTag = userElement.querySelector('.friend-tag'); if (friendTag && friendTag.textContent === '好友') { return true; } // 检查最近互动时间 const lastInteraction = userElement.querySelector('.last-interaction'); if (lastInteraction) { const timeText = lastInteraction.textContent; if (timeText.includes('天前') && parseInt(timeText) > 30) { return false; } } return false; }
  1. 通过互动频率判断:
function checkInteractionFrequency(userId) { // 从本地存储获取互动记录 const interactionHistory = GM_getValue('interactionHistory', {}); if (interactionHistory[userId]) { const {count, lastTime} = interactionHistory[userId]; const now = Date.now(); const daysPassed = (now - lastTime) / (1000 * 60 * 60 * 24); // 过去30天内互动超过3次视为有效好友 if (daysPassed < 30 && count >= 3) { return true; } } return false; }

3.4 限速控制实现

使用队列和定时器实现速率控制:

class RateLimiter { constructor(maxRequests, timeWindow) { this.queue = []; this.maxRequests = maxRequests; this.timeWindow = timeWindow; this.currentRequests = 0; this.timer = null; } addTask(task) { return new Promise((resolve) => { this.queue.push({task, resolve}); this.processQueue(); }); } processQueue() { if (this.timer === null) { this.timer = setTimeout(() => { this.currentRequests = 0; this.timer = null; this.processQueue(); }, this.timeWindow); } while (this.queue.length > 0 && this.currentRequests < this.maxRequests) { this.currentRequests++; const {task, resolve} = this.queue.shift(); task().then(resolve); } } } // 使用示例 const limiter = new RateLimiter(3, 10000); // 10秒内最多3次

3.5 日志系统实现

class Logger { constructor(logElement) { this.logElement = logElement; this.maxEntries = 50; this.entries = []; } log(message, type = 'info') { const now = new Date(); const timeStr = now.toLocaleTimeString(); const entry = document.createElement('div'); entry.textContent = `[${timeStr}] ${message}`; switch (type) { case 'error': entry.style.color = 'red'; break; case 'warning': entry.style.color = 'orange'; break; case 'success': entry.style.color = 'green'; break; default: entry.style.color = 'black'; } this.entries.push(entry); if (this.entries.length > this.maxEntries) { this.entries.shift(); } this.logElement.innerHTML = ''; this.entries.forEach(e => this.logElement.appendChild(e)); this.logElement.scrollTop = this.logElement.scrollHeight; } }

4. 完整实现与集成

4.1 主流程实现

(function() { 'use strict'; // 初始化UI const {startBtn, pauseBtn, speedSlider, logArea} = createControlPanel(); const logger = new Logger(logArea); // 初始化限速器 let limiter = new RateLimiter(5, 10000); // 更新限速器配置 speedSlider.addEventListener('input', () => { const speed = parseInt(speedSlider.value); const maxRequests = Math.max(1, Math.floor(speed / 2)); const timeWindow = 10000 - (speed * 500); limiter = new RateLimiter(maxRequests, timeWindow); logger.log(`调整发送速度为: ${speed} (${maxRequests}次/${timeWindow/1000}秒)`); }); // 好友列表处理 async function processFriendList() { const friendElements = document.querySelectorAll('.friend-item'); logger.log(`找到 ${friendElements.length} 个好友`); for (const element of friendElements) { try { if (!isRealFriend(element)) { logger.log(`跳过非好友: ${element.querySelector('.name').textContent}`, 'warning'); continue; } await limiter.addTask(async () => { // 模拟点击发送按钮 const sendBtn = element.querySelector('.send-btn'); if (sendBtn) { sendBtn.click(); // 等待对话框出现 await new Promise(resolve => setTimeout(resolve, 500)); // 输入祝福语 const input = document.querySelector('.message-input'); if (input) { input.value = '新春快乐,万事如意!🎉'; // 模拟点击发送 const realSendBtn = document.querySelector('.real-send-btn'); if (realSendBtn) { realSendBtn.click(); logger.log(`已发送给: ${element.querySelector('.name').textContent}`, 'success'); // 记录发送状态 GM_setValue('lastSend', Date.now()); } } } }); } catch (error) { logger.log(`发送失败: ${error.message}`, 'error'); } } } // 按钮事件绑定 let isRunning = false; let processInterval = null; startBtn.addEventListener('click', () => { if (isRunning) return; isRunning = true; logger.log('开始批量发送祝福'); // 每30秒检查一次新好友 processFriendList(); processInterval = setInterval(processFriendList, 30000); }); pauseBtn.addEventListener('click', () => { if (!isRunning) return; isRunning = false; clearInterval(processInterval); logger.log('已暂停发送'); }); })();

4.2 异常处理与恢复

// 在processFriendList函数中添加错误恢复逻辑 async function processFriendList() { try { // ...原有代码... } catch (error) { logger.log(`处理好友列表时出错: ${error.message}`, 'error'); // 检查是否被限制 const warning = document.querySelector('.warning-message'); if (warning && warning.textContent.includes('操作频繁')) { logger.log('检测到操作频繁限制,暂停30分钟', 'error'); isRunning = false; clearInterval(processInterval); // 30分钟后自动恢复 setTimeout(() => { logger.log('30分钟冷却结束,可以重新开始'); }, 30 * 60 * 1000); } } }

5. 使用技巧与注意事项

5.1 实际使用建议

  1. 最佳发送时间:

    • 建议在除夕晚上8点到10点之间使用
    • 避免在凌晨时段发送,容易被误认为垃圾信息
    • 分批次发送,不要一次性发送给所有好友
  2. 祝福语个性化:

    const greetings = [ '新春快乐,{name}!祝你新的一年万事如意!', '{name},新年好!祝你阖家幸福,心想事成!', '给{name}拜年啦!兔年大吉,好运连连!' ]; function getRandomGreeting(name) { const template = greetings[Math.floor(Math.random() * greetings.length)]; return template.replace('{name}', name); }
  3. 发送频率控制:

    • 初始设置建议3次/10秒
    • 如果账号权重较低,建议降低到2次/10秒
    • 观察平台反应,如果出现警告应立即暂停

5.2 常见问题排查

  1. 脚本不生效:

    • 检查Tampermonkey是否已启用脚本
    • 确认@match规则与当前网址匹配
    • 查看Tampermonkey控制台是否有错误
  2. 无法检测好友:

    • 检查选择器是否与当前页面结构匹配
    • 可能需要根据平台更新调整isRealFriend函数
  3. 发送失败:

    • 检查网络连接
    • 确认账号没有被临时限制
    • 查看页面结构是否发生变化

5.3 进阶优化方向

  1. 多平台适配:

    // 检测当前平台 function detectPlatform() { if (window.location.host.includes('wechat')) { return 'wechat'; } else if (window.location.host.includes('qq')) { return 'qq'; } else { return 'unknown'; } } // 根据平台调整选择器 function getPlatformSelectors(platform) { const selectors = { wechat: { friendItem: '.wechat-friend', sendBtn: '.wechat-send-btn' }, qq: { friendItem: '.qq-friend', sendBtn: '.qq-send-btn' } }; return selectors[platform] || selectors.wechat; }
  2. 发送统计功能:

    class Statistics { constructor() { this.total = 0; this.success = 0; this.failed = 0; this.startTime = null; } start() { this.startTime = Date.now(); } recordSuccess() { this.success++; this.total++; } recordFailed() { this.failed++; this.total++; } getSummary() { const duration = (Date.now() - this.startTime) / 1000; const speed = this.total / duration; return { total: this.total, success: this.success, failed: this.failed, duration: `${duration.toFixed(1)}秒`, speed: `${speed.toFixed(2)}次/秒` }; } }
  3. 祝福语模板管理:

    function initGreetingTemplates() { const templates = GM_getValue('greetingTemplates', [ '新春快乐,{name}!祝你新的一年万事如意!' ]); // 添加模板管理UI const templateContainer = document.createElement('div'); templateContainer.style.marginTop = '10px'; const templateLabel = document.createElement('label'); templateLabel.textContent = '祝福语模板:'; templateLabel.style.display = 'block'; templateContainer.appendChild(templateLabel); const templateTextarea = document.createElement('textarea'); templateTextarea.style.width = '100%'; templateTextarea.style.height = '60px'; templateTextarea.value = templates.join('\n---\n'); templateContainer.appendChild(templateTextarea); const saveBtn = document.createElement('button'); saveBtn.textContent = '保存模板'; saveBtn.style.marginTop = '5px'; saveBtn.addEventListener('click', () => { const newTemplates = templateTextarea.value.split('\n---\n'); GM_setValue('greetingTemplates', newTemplates); logger.log('祝福语模板已更新'); }); templateContainer.appendChild(saveBtn); document.querySelector('#newYearPanel').appendChild(templateContainer); return { getRandomTemplate: function(name) { const tmpls = GM_getValue('greetingTemplates', templates); const template = tmpls[Math.floor(Math.random() * tmpls.length)]; return template.replace('{name}', name); } }; }

6. 安全与合规建议

  1. 尊重平台规则:

    • 不要设置过高的发送频率
    • 避免在短时间内发送大量相同内容
    • 建议添加随机延迟,模拟人工操作
  2. 用户隐私保护:

    • 不要收集或存储好友的个人信息
    • 所有数据应只在本地处理
    • 脚本不应请求不必要的权限
  3. 可随时停止:

    • 确保暂停按钮能立即生效
    • 提供紧急停止快捷键
    document.addEventListener('keydown', (e) => { if (e.key === 'Escape') { isRunning = false; clearInterval(processInterval); logger.log('已紧急停止', 'error'); } });
  4. 明确告知:

    • 在脚本描述中明确说明功能
    • 提醒用户合理使用
    • 提供关闭UI的选项
    const closeBtn = document.createElement('button'); closeBtn.textContent = '×'; closeBtn.style.position = 'absolute'; closeBtn.style.right = '5px'; closeBtn.style.top = '5px'; closeBtn.style.background = 'none'; closeBtn.style.border = 'none'; closeBtn.style.fontSize = '16px'; closeBtn.style.cursor = 'pointer'; closeBtn.addEventListener('click', () => { document.querySelector('#newYearPanel').style.display = 'none'; }); document.querySelector('#newYearPanel').appendChild(closeBtn);
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进