// ==UserScript==
// @name 小红书粉丝检查抽奖器 v4.4(抽奖版)
// @namespace http://tampermonkey.net/
// @version 4.4
// @description 小红书自动检查粉丝关系,黑名单过滤,安全随机抽奖,CSV导出中奖名单!
// @author Suzhiworkshops
// @match https://www.xiaohongshu.com/explore/*
// @grant GM_xmlhttpRequest
// @connect xiaohongshu.com
// ==/UserScript==
(function () {
'use strict';
const CONFIG = {
maxConcurrent: 5,
delayBetweenBatches: 1000,
baseUrl: 'https://www.xiaohongshu.com'
};
let users = [], failedCount = 0, isProcessing = false;
function createButton() {
const button = document.createElement('button');
button.textContent = '📤 开始抽奖';
Object.assign(button.style, {
position: 'fixed', top: '20px', right: '20px', padding: '10px 20px',
backgroundColor: '#fe2c55', color: 'white', border: 'none',
borderRadius: '20px', cursor: 'pointer', zIndex: 9999, fontSize: '14px', fontWeight: 'bold'
});
button.onclick = async () => {
if (isProcessing) return alert('处理中,请稍候');
isProcessing = true;
button.disabled = true;
button.textContent = '处理中...';
try { await start(); }
catch (e) { addLog(`❌ 错误:${e.message}`); }
finally {
isProcessing = false;
button.disabled = false;
button.textContent = '📤 粉丝检测';
}
};
document.body.appendChild(button);
}
async function getBlacklist() {
const input = prompt('输入黑名单关键词(逗号分隔)', '广告,测试,抽奖');
return input ? input.split(',').map(k => k.trim()).filter(Boolean) : [];
}
async function start() {
createPanel();
const blacklist = await getBlacklist();
addLog(`🔍 黑名单关键词:${blacklist.join(', ') || '无'}`);
const nodes = document.querySelectorAll('.comment-item .name');
if (!nodes.length) return addLog('⚠️ 未找到用户,先加载评论');
const rawUsers = Array.from(nodes).map(el => ({
username: el.textContent.trim(),
userId: el.getAttribute('data-user-id') || '未知',
profileUrl: CONFIG.baseUrl + el.getAttribute('href'),
isFan: false, checked: false
})).filter(u => u.profileUrl);
const seen = new Set();
const deduped = rawUsers.filter(u => seen.has(u.userId) ? false : seen.add(u.userId));
users = deduped.filter(u => {
const hit = blacklist.find(k => u.username.includes(k));
if (hit) {
addLog(`🚫 跳过:${u.username}(关键词:“${hit}”)`);
return false;
}
return true;
});
addLog(`🎯 待检测用户:${users.length}人`);
await processBatches(users, CONFIG.maxConcurrent);
finalize();
}
async function processBatches(list, size) {
for (let i = 0; i < list.length; i += size) {
await Promise.all(list.slice(i, i + size).map(u => checkUser(u)));
addLog(`📦 已检测:${Math.min(i + size, list.length)} / ${list.length}`);
if (i + size < list.length) await sleep(CONFIG.delayBetweenBatches);
}
}
async function checkUser(user) {
return new Promise(resolve => {
GM_xmlhttpRequest({
method: 'GET', url: user.profileUrl,
headers: {'User-Agent': navigator.userAgent},
onload: res => {
user.checked = true;
user.isFan = /互相关注|回关/.test(res.responseText);
addLog(`${user.isFan ? '❤️ 是粉丝' : '💔 非粉丝'}:${user.username}`);
resolve();
},
onerror: err => {
failedCount++;
addLog(`❌ 失败:${user.username}`);
resolve();
}
});
});
}
function finalize() {
const fans = users.filter(u => u.checked && u.isFan);
addLog(`✅ 完成:成功检测${fans.length}个粉丝,失败${failedCount}`);
const count = Math.min(parseInt(prompt(`输入中奖人数(最多${fans.length}):`, '3')), fans.length);
if (!count) return;
const winners = secureShuffle(fans, count);
addLog(`🏆 🎉 抽奖结果(${count}人):`);
winners.forEach((u, i) => addLog(`第${i + 1}名 🎁 ${u.username}`));
exportCSV(winners, `中奖名单_${new Date().toISOString().slice(0, 10)}.csv`);
}
function secureShuffle(arr, count) {
const res = [], used = new Set();
while (res.length < count && used.size < arr.length) {
const r = crypto.getRandomValues(new Uint32Array(1))[0] % arr.length;
if (!used.has(r)) used.add(r) && res.push(arr[r]);
}
return res;
}
function exportCSV(data, filename) {
const csv = [
'\uFEFF用户名,用户ID,主页链接',
...data.map(u => `${u.username},${u.userId},${u.profileUrl}`)
].join('\n');
const blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' });
const url = URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url; link.download = filename;
link.textContent = '🎁 下载中奖名单';
Object.assign(link.style, {
display: 'block', marginTop: '8px', background: '#27ae60', color: '#fff',
textAlign: 'center', padding: '10px 0', borderRadius: '20px', textDecoration: 'none', fontSize: '14px'
});
document.getElementById('xh-result-content').appendChild(link);
}
function createPanel() {
if (document.getElementById('xh-result-panel')) return;
const panel = document.createElement('div');
panel.id = 'xh-result-panel';
panel.style = 'position:fixed;top:70px;right:20px;width:340px;max-height:70vh;background:#fff;border-radius:12px;box-shadow:0 4px 18px rgba(0,0,0,0.1);overflow:auto;z-index:9999;font-family:sans-serif;color:#000';
panel.innerHTML = `<div style='padding:12px;font-weight:bold;border-bottom:1px solid #eee;font-size:15px;color:#000'>📋 粉丝检测日志</div><div id='xh-result-content' style='padding:12px;font-size:14px;color:#000;'></div>`;
document.body.appendChild(panel);
}
function addLog(text) {
const area = document.getElementById('xh-result-content');
if (!area) return;
const p = document.createElement('p');
p.textContent = text; p.style.margin = '4px 0'; p.style.color = '#000';
if (/💔/.test(text)) p.style.color = '#888';
if (/❌|🚫/.test(text)) p.style.color = '#c0392b';
if (/✅|🎯|🏆/.test(text)) p.style.color = '#27ae60';
if (/⚠️/.test(text)) p.style.color = '#e67e22';
area.appendChild(p); area.scrollTop = area.scrollHeight;
}
function sleep(ms) { return new Promise(r => setTimeout(r, ms)); }
createButton();
})();