NodeLoc上现在是奖券抽奖的,默认会显示个人中奖概率,但实际上抽奖的时候奖品可能不唯一,比如抽奖奖品数量是5,你投入的奖券是10,占总票数的100的10%,那么你实际显示的中奖概率是10%,但是实际上你的第一名投入的奖券比如是50,其余的40人投入的奖券数量的40,那么你投入的10个券其实是排名第二的,你中奖概率实际上是100%。

// ==UserScript==
// @name NodeLoc抽奖参与者排序插件
// @namespace http://tampermonkey.net/
// @version 1.01
// @description 在NodeLoc抽奖页面显示按奖券数量排序的参与者列表
// @author You
// @match https://www.nodeloc.com/t/topic/*
// @grant none
// ==/UserScript==
(function() {
'use strict';
// 获取当前登录用户名
function getCurrentUsername() {
// 方法1: 从 Discourse 全局对象获取
if (typeof Discourse !== 'undefined' && Discourse.User!=='undefined') {
return Discourse.User.current().name;
}
// 方法2: 从页面元素获取
const userMenuTrigger = document.querySelector('.header-dropdown-toggle.current-user');
if (userMenuTrigger) {
const img = userMenuTrigger.querySelector('img');
if (img && img.alt) {
return img.alt;
}
}
// 方法3: 从用户菜单链接获取
const userLink = document.querySelector('a[href^="/u/"].current-user');
if (userLink) {
const href = userLink.getAttribute('href');
const match = href.match(/\/u\/([^\/]+)/);
if (match) {
return match[1];
}
}
// 方法4: 从页面头部用户信息获取
const currentUserElement = document.querySelector('.current-user .username');
if (currentUserElement) {
return currentUserElement.textContent.trim();
}
// 方法5: 从 meta 标签获取
const currentUserMeta = document.querySelector('meta[name="discourse-current-user"]');
if (currentUserMeta) {
try {
const userInfo = JSON.parse(currentUserMeta.content);
return userInfo.username;
} catch (e) {
console.log('Failed to parse current user meta:', e);
}
}
return null;
}
// 等待参与者列表加载完成
function waitForParticipants(participantList, maxRetries = 20, retryDelay = 500) {
return new Promise((resolve) => {
let retryCount = 0;
function checkParticipants() {
const participantLinks = participantList.querySelectorAll('a[title]');
console.log(`尝试 ${retryCount + 1}: 找到 ${participantLinks.length} 个参与者链接`);
if (participantLinks.length > 0) {
// 检查是否有有效的title属性
const validLinks = Array.from(participantLinks).filter(link => {
const title = link.getAttribute('title');
return title && title.includes('奖券');
});
console.log(`有效的参与者链接: ${validLinks.length}`);
if (validLinks.length > 0) {
resolve(participantLinks);
return;
}
}
retryCount++;
if (retryCount < maxRetries) {
setTimeout(checkParticipants, retryDelay);
} else {
console.log('达到最大重试次数,继续执行...');
resolve(participantLinks);
}
}
checkParticipants();
});
}
// 检查页面是否包含抽奖参与者列表
async function checkAndAddSortedList() {
// 首先检查是否是抽奖页面
const lotteryContainer = document.querySelector('.lottery-container');
if (!lotteryContainer) {
console.log('不是抽奖页面,跳过插件逻辑');
return;
}
const participantList = document.querySelector('.lottery-participant-list');
if (!participantList) {
console.log('未找到参与者列表');
return;
}
// 检查是否已经添加过排序列表
if (document.querySelector('.sorted-participant-list')) {
return;
}
console.log('发现抽奖页面,等待参与者内容加载...');
// 等待参与者链接加载完成
const participantLinks = await waitForParticipants(participantList);
// 获取当前登录用户名
const currentUsername = getCurrentUsername();
const participants = [];
// 解析参与者信息
participantLinks.forEach(link => {
const title = link.getAttribute('title');
console.log('解析title:', title); // 调试用
// 使用正则表达式匹配用户名和奖券数量
// 支持中文括号()和英文括号()
const match = title.match(/^(.+?)[((](\d+)\s*奖券[))]$/);
if (match) {
const username = match[1].trim();
const ticketCount = parseInt(match[2]);
console.log('解析结果:', username, ticketCount); // 调试用
participants.push({
username: username,
tickets: ticketCount,
href: link.getAttribute('href')
});
} else {
console.log('未能解析的title:', title); // 调试用
}
});
// 如果没有解析到参与者,显示错误信息
if (participants.length === 0) {
console.log('未解析到任何参与者');
const errorDiv = document.createElement('div');
errorDiv.className = 'sorted-participant-list';
errorDiv.style.cssText = `
margin-top: 15px;
padding: 15px;
background-color: #fff3cd;
border: 1px solid #ffeaa7;
border-radius: 5px;
font-family: Arial, sans-serif;
text-align: center;
color: #856404;
`;
errorDiv.innerHTML = `
<h4 style="margin: 0 0 10px 0;">参与者排行榜</h4>
<p style="margin: 0;">正在等待抽奖数据加载,请稍后刷新页面重试...</p>
`;
participantList.parentNode.insertBefore(errorDiv, participantList.nextSibling);
return;
}
// 按奖券数量从高到低排序
participants.sort((a, b) => b.tickets - a.tickets);
// 查找当前用户排名
let currentUserRank = -1;
let currentUserTickets = 0;
if (currentUsername) {
const userIndex = participants.findIndex(p => p.username.toLowerCase() === currentUsername.toLowerCase());
if (userIndex !== -1) {
currentUserRank = userIndex + 1;
currentUserTickets = participants[userIndex].tickets;
}
}
// 创建排序后的列表容器
const sortedDiv = document.createElement('div');
sortedDiv.className = 'sorted-participant-list';
sortedDiv.style.cssText = `
margin-top: 15px;
padding: 15px;
background-color: #f8f9fa;
border: 1px solid #dee2e6;
border-radius: 5px;
font-family: Arial, sans-serif;
`;
// 添加标题
const titleContainer = document.createElement('div');
titleContainer.style.cssText = `
margin-bottom: 10px;
`;
const title = document.createElement('h4');
title.textContent = '参与者排行榜(按奖券数量排序)';
title.style.cssText = `
margin: 0 0 10px 0;
color: #495057;
font-size: 16px;
font-weight: bold;
`;
titleContainer.appendChild(title);
// 如果找到当前用户,在标题下方显示排名信息
if (currentUserRank > 0) {
const userRankInfo = document.createElement('div');
userRankInfo.style.cssText = `
background-color: #007bff;
color: white;
padding: 8px 15px;
border-radius: 20px;
font-size: 14px;
font-weight: bold;
display: inline-block;
margin-bottom: 10px;
box-shadow: 0 2px 4px rgba(0,123,255,0.3);
`;
userRankInfo.textContent = `我当前排第${currentUserRank}名 (${currentUserTickets}张奖券)`;
titleContainer.appendChild(userRankInfo);
} else if (currentUsername) {
// 如果有用户名但没找到排名,显示未参与信息
const notParticipatingInfo = document.createElement('div');
notParticipatingInfo.style.cssText = `
background-color: #6c757d;
color: white;
padding: 8px 15px;
border-radius: 20px;
font-size: 14px;
font-weight: bold;
display: inline-block;
margin-bottom: 10px;
box-shadow: 0 2px 4px rgba(108,117,125,0.3);
`;
notParticipatingInfo.textContent = `我未参与此次抽奖`;
titleContainer.appendChild(notParticipatingInfo);
}
sortedDiv.appendChild(titleContainer);
// 创建列表容器
const listContainer = document.createElement('div');
listContainer.style.cssText = `
max-height: 400px;
overflow-y: auto;
border: 1px solid #ddd;
border-radius: 3px;
background-color: white;
`;
// 添加参与者列表项
participants.forEach((participant, index) => {
const isCurrentUser = currentUsername && participant.username.toLowerCase() === currentUsername.toLowerCase();
const item = document.createElement('div');
item.style.cssText = `
padding: 8px 12px;
border-bottom: 1px solid #eee;
display: flex;
justify-content: space-between;
align-items: center;
transition: background-color 0.2s;
${isCurrentUser ? 'border-left: 4px solid #007bff; box-shadow: 0 2px 4px rgba(0,123,255,0.2);' : ''}
`;
// 设置背景色
let backgroundColor = 'white';
if (isCurrentUser) {
backgroundColor = '#e3f2fd';
} else if (index === 0) {
backgroundColor = '#fff3cd';
} else if (index === 1) {
backgroundColor = '#f8f9fa';
} else if (index === 2) {
backgroundColor = '#f1f3f4';
}
item.style.backgroundColor = backgroundColor;
// 鼠标悬停效果
item.addEventListener('mouseenter', () => {
item.style.backgroundColor = isCurrentUser ? '#bbdefb' : '#e9ecef';
});
item.addEventListener('mouseleave', () => {
item.style.backgroundColor = backgroundColor;
});
// 创建用户名链接
const nameLink = document.createElement('a');
nameLink.href = participant.href;
nameLink.textContent = participant.username;
nameLink.style.cssText = `
color: ${isCurrentUser ? '#1976d2' : '#007bff'};
text-decoration: none;
font-weight: ${isCurrentUser ? 'bold' : '500'};
`;
nameLink.addEventListener('mouseenter', () => {
nameLink.style.textDecoration = 'underline';
});
nameLink.addEventListener('mouseleave', () => {
nameLink.style.textDecoration = 'none';
});
// 创建奖券数量显示
const ticketCount = document.createElement('span');
ticketCount.textContent = `${participant.tickets} 奖券`;
ticketCount.style.cssText = `
color: ${isCurrentUser ? '#1976d2' : '#28a745'};
font-weight: bold;
background-color: ${isCurrentUser ? '#e3f2fd' : '#d4edda'};
padding: 2px 8px;
border-radius: 12px;
font-size: 12px;
${isCurrentUser ? 'border: 1px solid #1976d2;' : ''}
`;
// 添加排名
const rank = document.createElement('span');
rank.textContent = `#${index + 1}`;
rank.style.cssText = `
color: ${isCurrentUser ? '#1976d2' : '#6c757d'};
font-size: 12px;
font-weight: bold;
margin-right: 10px;
min-width: 30px;
`;
// 如果是当前用户,添加标识
const leftContainer = document.createElement('div');
leftContainer.style.display = 'flex';
leftContainer.style.alignItems = 'center';
leftContainer.appendChild(rank);
leftContainer.appendChild(nameLink);
if (isCurrentUser) {
const meLabel = document.createElement('span');
meLabel.textContent = ' (我)';
meLabel.style.cssText = `
color: #1976d2;
font-size: 11px;
font-weight: bold;
background-color: #e3f2fd;
padding: 1px 5px;
border-radius: 8px;
margin-left: 5px;
border: 1px solid #1976d2;
`;
leftContainer.appendChild(meLabel);
}
item.appendChild(leftContainer);
item.appendChild(ticketCount);
listContainer.appendChild(item);
});
sortedDiv.appendChild(listContainer);
// 添加统计信息
const stats = document.createElement('div');
stats.style.cssText = `
margin-top: 10px;
font-size: 12px;
color: #6c757d;
text-align: center;
`;
const totalTickets = participants.reduce((sum, p) => sum + p.tickets, 0);
stats.textContent = `总计 ${participants.length} 人参与,共 ${totalTickets} 张奖券`;
sortedDiv.appendChild(stats);
// 将排序列表插入到原列表下方
participantList.parentNode.insertBefore(sortedDiv, participantList.nextSibling);
}
// 页面加载完成后执行
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', () => {
setTimeout(checkAndAddSortedList, 1000); // 延迟1秒执行
});
} else {
setTimeout(checkAndAddSortedList, 1000); // 延迟1秒执行
}
// 监听页面变化(处理AJAX加载的情况)
const observer = new MutationObserver((mutations) => {
let shouldCheck = false;
mutations.forEach((mutation) => {
if (mutation.type === 'childList') {
mutation.addedNodes.forEach((node) => {
if (node.nodeType === 1) {
// 检查是否添加了抽奖容器
if (node.classList && node.classList.contains('lottery-container')) {
shouldCheck = true;
} else if (node.querySelector && node.querySelector('.lottery-container')) {
shouldCheck = true;
} else if (node.classList && node.classList.contains('lottery-participant-list')) {
shouldCheck = true;
} else if (node.querySelector && node.querySelector('.lottery-participant-list')) {
shouldCheck = true;
} else if (node.querySelector && node.querySelector('a[title*="奖券"]')) {
// 检测到包含"奖券"的链接被添加
shouldCheck = true;
}
}
});
}
});
if (shouldCheck) {
console.log('检测到抽奖相关页面变化,延迟执行检查...');
setTimeout(checkAndAddSortedList, 500);
}
});
observer.observe(document.body, {
childList: true,
subtree: true
});
})();
文章评论