方便复制抽奖贴文案,填写抽奖主题即可
抽奖主题:
:trophy: 奖品详情:
:three_o_clock: 活动时间:
开始时间:
截止时间:
:memo: 参与方式:
在本帖下回复任意内容
:magnifying_glass_tilted_left: 抽奖规则:
每位用户仅允许参与一次。
使用官方抽奖工具随机抽取中奖者。
:warning: 注意事项:
本活动将在活动截止时间后关闭回帖,以确保公正性。
中奖者将在活动结束后24小时内在本帖公布,并通过私信通知领奖方式。
所有规则及抽奖结果由活动发起人和论坛管理团队最终解释。
期待您的积极参与,祝您好运!如有任何疑问,欢迎随时联系抽奖发起人。
油猴源码
// ==UserScript==
// @name LINUX DO 抽奖助手
// @namespace https://linux.do/
// @version 0.1.2
// @description 在 LINUX DO 论坛帖子页面添加抽奖功能
// @author linux.do
// @match https://linux.do/t/*
// @grant GM_xmlhttpRequest
// @connect connect.linux.do
// ==/UserScript==
(function() {
'use strict';
const VERSION = '0.1.2';
// 添加抽奖按钮样式
const style = document.createElement('style');
style.textContent = `
.d-header-icons .lottery-btn,
.header-icons .lottery-btn {
display: flex;
align-items: center;
justify-content: center;
width: 34px;
height: 34px;
border-radius: 50%;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
border: none;
cursor: pointer;
font-size: 16px;
box-shadow: 0 2px 6px rgba(102, 126, 234, 0.3);
transition: transform 0.2s, box-shadow 0.2s;
margin: 0 0 0 5px;
padding: 0;
vertical-align: middle;
}
.d-header-icons .lottery-btn:hover,
.header-icons .lottery-btn:hover {
transform: scale(1.08);
box-shadow: 0 3px 10px rgba(102, 126, 234, 0.5);
}
/* 备用固定定位样式 */
.lottery-btn-fixed {
position: fixed;
right: 70px;
top: 10px;
z-index: 9999;
width: 34px;
height: 34px;
border-radius: 50%;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
border: none;
cursor: pointer;
font-size: 16px;
box-shadow: 0 2px 6px rgba(102, 126, 234, 0.3);
}
.lottery-modal {
display: none;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0,0,0,0.5);
z-index: 10000;
justify-content: center;
align-items: center;
}
.lottery-modal.show { display: flex; }
.lottery-dialog {
background: var(--secondary, #fff);
color: var(--primary, #333);
border-radius: 12px;
padding: 24px;
width: 90%;
max-width: 500px;
max-height: 80vh;
overflow-y: auto;
box-shadow: 0 20px 60px rgba(0,0,0,0.3);
}
.lottery-dialog h2 {
margin: 0 0 20px;
font-size: 1.5rem;
display: flex;
align-items: center;
gap: 8px;
}
.lottery-dialog label {
display: block;
margin: 12px 0 6px;
font-weight: 600;
}
.lottery-dialog input {
width: 100%;
padding: 10px 12px;
border: 1px solid var(--primary-low, #ddd);
border-radius: 6px;
font-size: 1rem;
background: var(--secondary, #fff);
color: var(--primary, #333);
}
.lottery-dialog input:focus {
outline: none;
border-color: #667eea;
box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.2);
}
.lottery-actions {
display: flex;
gap: 12px;
margin-top: 20px;
}
.lottery-actions button {
flex: 1;
padding: 12px;
border: none;
border-radius: 6px;
font-size: 1rem;
font-weight: 600;
cursor: pointer;
transition: opacity 0.2s;
}
.lottery-actions button:hover { opacity: 0.9; }
.lottery-actions button:disabled { opacity: 0.5; cursor: not-allowed; }
.btn-primary {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
}
.btn-secondary {
background: var(--primary-low, #e5e5e5);
color: var(--primary, #333);
}
.lottery-result {
margin-top: 16px;
padding: 12px;
background: var(--primary-very-low, #f5f5f5);
border-radius: 8px;
font-family: monospace;
font-size: 0.85rem;
white-space: pre-wrap;
max-height: 300px;
overflow-y: auto;
}
.lottery-status {
margin-top: 12px;
padding: 8px 12px;
border-radius: 6px;
font-size: 0.9rem;
}
.lottery-status.info { background: #e0f2fe; color: #0369a1; }
.lottery-status.error { background: #fee2e2; color: #dc2626; }
.lottery-status.success { background: #d1fae5; color: #059669; }
`;
document.head.appendChild(style);
// 创建抽奖按钮
const btn = document.createElement('button');
btn.className = 'lottery-btn';
btn.innerHTML = '🎲';
btn.title = '抽奖';
// 插入到头部导航栏右侧(头像旁边)
function insertButton() {
const headerIcons = document.querySelector('.d-header-icons');
if (headerIcons) {
const wrapper = document.createElement('li');
wrapper.className = 'header-dropdown-toggle lottery-wrapper';
wrapper.style.cssText = 'display:flex;align-items:center;';
wrapper.appendChild(btn);
headerIcons.appendChild(wrapper);
} else {
btn.className = 'lottery-btn-fixed';
document.body.appendChild(btn);
}
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', insertButton);
} else {
setTimeout(insertButton, 500);
}
// 创建弹窗
const modal = document.createElement('div');
modal.className = 'lottery-modal';
modal.innerHTML = `
<div class="lottery-dialog">
<h2>🎲 LINUX DO 抽奖</h2>
<div id="lottery-topic-info"></div>
<div class="lottery-actions" style="margin-top: 10px;">
<button class="btn-secondary" id="lottery-copy-template">📝 复制文案模板</button>
</div>
<label for="lottery-count">中奖人数</label>
<input type="number" id="lottery-count" min="1" value="1" placeholder="请输入中奖人数">
<label for="lottery-last-floor">最后楼层 (可选)</label>
<input type="number" id="lottery-last-floor" min="1" placeholder="留空表示全部楼层参与">
<label for="lottery-required-text">回复必须包含的文本 (可选)</label>
<input type="text" id="lottery-required-text" placeholder="只抽取包含指定文本的回复">
<div id="lottery-status" class="lottery-status info" style="display:none"></div>
<div class="lottery-actions">
<button class="btn-secondary" id="lottery-cancel">取消</button>
<button class="btn-primary" id="lottery-start">开始抽奖</button>
</div>
<div id="lottery-result" class="lottery-result" style="display:none"></div>
<div class="lottery-actions" id="lottery-copy-wrap" style="display:none">
<button class="btn-primary" id="lottery-copy">📋 复制结果</button>
</div>
</div>
`;
document.body.appendChild(modal);
// 存储中奖用户信息,用于复制时生成 @ 列表
let winnersInfo = [];
let currentTopic = null; // 缓存当前 topic 信息(用于模板自动填标题)
// 事件绑定
btn.onclick = () => {
modal.classList.add('show');
checkTopic();
};
modal.onclick = (e) => { if (e.target === modal) modal.classList.remove('show'); };
document.getElementById('lottery-cancel').onclick = () => modal.classList.remove('show');
document.getElementById('lottery-start').onclick = startLottery;
document.getElementById('lottery-copy').onclick = copyResult;
document.getElementById('lottery-copy-template').onclick = copyTemplate;
function showStatus(msg, type = 'info') {
const el = document.getElementById('lottery-status');
el.textContent = msg;
el.className = `lottery-status ${type}`;
el.style.display = 'block';
}
function hideStatus() {
document.getElementById('lottery-status').style.display = 'none';
}
// 获取帖子ID
function getTopicId() {
const match = location.pathname.match(/\/t\/(?:topic\/)?(\d+)/);
return match ? match[1] : null;
}
// 检查帖子状态
async function checkTopic() {
const topicId = getTopicId();
if (!topicId) {
showStatus('无法识别帖子ID', 'error');
return;
}
try {
const resp = await fetch(`/t/${topicId}.json`);
const data = await resp.json();
currentTopic = data;
const info = document.getElementById('lottery-topic-info');
info.innerHTML = `
<p><strong>帖子:</strong>${data.title}</p>
<p><strong>作者:</strong>${data.details.created_by.username}</p>
<p><strong>状态:</strong>${data.closed ? '✅ 已关闭' : data.archived ? '✅ 已存档' : '❌ 未关闭'}</p>
`;
if (!data.closed && !data.archived) {
showStatus('帖子尚未关闭或存档,无法抽奖', 'error');
document.getElementById('lottery-start').disabled = true;
} else {
hideStatus();
document.getElementById('lottery-start').disabled = false;
}
} catch (e) {
showStatus('获取帖子信息失败: ' + e.message, 'error');
}
}
// MD5 实现
function md5(string) {
function md5cycle(x, k) {
var a = x[0], b = x[1], c = x[2], d = x[3];
a = ff(a, b, c, d, k[0], 7, -680876936); d = ff(d, a, b, c, k[1], 12, -389564586);
c = ff(c, d, a, b, k[2], 17, 606105819); b = ff(b, c, d, a, k[3], 22, -1044525330);
a = ff(a, b, c, d, k[4], 7, -176418897); d = ff(d, a, b, c, k[5], 12, 1200080426);
c = ff(c, d, a, b, k[6], 17, -1473231341); b = ff(b, c, d, a, k[7], 22, -45705983);
a = ff(a, b, c, d, k[8], 7, 1770035416); d = ff(d, a, b, c, k[9], 12, -1958414417);
c = ff(c, d, a, b, k[10], 17, -42063); b = ff(b, c, d, a, k[11], 22, -1990404162);
a = ff(a, b, c, d, k[12], 7, 1804603682); d = ff(d, a, b, c, k[13], 12, -40341101);
c = ff(c, d, a, b, k[14], 17, -1502002290); b = ff(b, c, d, a, k[15], 22, 1236535329);
a = gg(a, b, c, d, k[1], 5, -165796510); d = gg(d, a, b, c, k[6], 9, -1069501632);
c = gg(c, d, a, b, k[11], 14, 643717713); b = gg(b, c, d, a, k[0], 20, -373897302);
a = gg(a, b, c, d, k[5], 5, -701558691); d = gg(d, a, b, c, k[10], 9, 38016083);
c = gg(c, d, a, b, k[15], 14, -660478335); b = gg(b, c, d, a, k[4], 20, -405537848);
a = gg(a, b, c, d, k[9], 5, 568446438); d = gg(d, a, b, c, k[14], 9, -1019803690);
c = gg(c, d, a, b, k[3], 14, -187363961); b = gg(b, c, d, a, k[8], 20, 1163531501);
a = gg(a, b, c, d, k[13], 5, -1444681467); d = gg(d, a, b, c, k[2], 9, -51403784);
c = gg(c, d, a, b, k[7], 14, 1735328473); b = gg(b, c, d, a, k[12], 20, -1926607734);
a = hh(a, b, c, d, k[5], 4, -378558); d = hh(d, a, b, c, k[8], 11, -2022574463);
c = hh(c, d, a, b, k[11], 16, 1839030562); b = hh(b, c, d, a, k[14], 23, -35309556);
a = hh(a, b, c, d, k[1], 4, -1530992060); d = hh(d, a, b, c, k[4], 11, 1272893353);
c = hh(c, d, a, b, k[7], 16, -155497632); b = hh(b, c, d, a, k[10], 23, -1094730640);
a = hh(a, b, c, d, k[13], 4, 681279174); d = hh(d, a, b, c, k[0], 11, -358537222);
c = hh(c, d, a, b, k[3], 16, -722521979); b = hh(b, c, d, a, k[6], 23, 76029189);
a = hh(a, b, c, d, k[9], 4, -640364487); d = hh(d, a, b, c, k[12], 11, -421815835);
c = hh(c, d, a, b, k[15], 16, 530742520); b = hh(b, c, d, a, k[2], 23, -995338651);
a = ii(a, b, c, d, k[0], 6, -198630844); d = ii(d, a, b, c, k[7], 10, 1126891415);
c = ii(c, d, a, b, k[14], 15, -1416354905); b = ii(b, c, d, a, k[5], 21, -57434055);
a = ii(a, b, c, d, k[12], 6, 1700485571); d = ii(d, a, b, c, k[3], 10, -1894986606);
c = ii(c, d, a, b, k[10], 15, -1051523); b = ii(b, c, d, a, k[1], 21, -2054922799);
a = ii(a, b, c, d, k[8], 6, 1873313359); d = ii(d, a, b, c, k[15], 10, -30611744);
c = ii(c, d, a, b, k[6], 15, -1560198380); b = ii(b, c, d, a, k[13], 21, 1309151649);
a = ii(a, b, c, d, k[4], 6, -145523070); d = ii(d, a, b, c, k[11], 10, -1120210379);
c = ii(c, d, a, b, k[2], 15, 718787259); b = ii(b, c, d, a, k[9], 21, -343485551);
x[0] = add32(a, x[0]); x[1] = add32(b, x[1]); x[2] = add32(c, x[2]); x[3] = add32(d, x[3]);
}
function cmn(q, a, b, x, s, t) { a = add32(add32(a, q), add32(x, t)); return add32((a << s) | (a >>> (32 - s)), b); }
function ff(a, b, c, d, x, s, t) { return cmn((b & c) | ((~b) & d), a, b, x, s, t); }
function gg(a, b, c, d, x, s, t) { return cmn((b & d) | (c & (~d)), a, b, x, s, t); }
function hh(a, b, c, d, x, s, t) { return cmn(b ^ c ^ d, a, b, x, s, t); }
function ii(a, b, c, d, x, s, t) { return cmn(c ^ (b | (~d)), a, b, x, s, t); }
function md51(s) {
var n = s.length, state = [1732584193, -271733879, -1732584194, 271733878], i;
for (i = 64; i <= s.length; i += 64) md5cycle(state, md5blk(s.substring(i - 64, i)));
s = s.substring(i - 64);
var tail = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0];
for (i = 0; i < s.length; i++) tail[i >> 2] |= s.charCodeAt(i) << ((i % 4) << 3);
tail[i >> 2] |= 0x80 << ((i % 4) << 3);
if (i > 55) { md5cycle(state, tail); for (i = 0; i < 16; i++) tail[i] = 0; }
tail[14] = n * 8;
md5cycle(state, tail);
return state;
}
function md5blk(s) {
var md5blks = [], i;
for (i = 0; i < 64; i += 4) md5blks[i >> 2] = s.charCodeAt(i) + (s.charCodeAt(i + 1) << 8) + (s.charCodeAt(i + 2) << 16) + (s.charCodeAt(i + 3) << 24);
return md5blks;
}
var hex_chr = '0123456789abcdef'.split('');
function rhex(n) { var s = '', j = 0; for (; j < 4; j++) s += hex_chr[(n >> (j * 8 + 4)) & 0x0F] + hex_chr[(n >> (j * 8)) & 0x0F]; return s; }
function hex(x) { for (var i = 0; i < x.length; i++) x[i] = rhex(x[i]); return x.join(''); }
function add32(a, b) { return (a + b) & 0xFFFFFFFF; }
return hex(md51(string));
}
// SHA 哈希
async function sha(algo, data) {
const buf = await crypto.subtle.digest(algo, new TextEncoder().encode(data));
return Array.from(new Uint8Array(buf)).map(b => b.toString(16).padStart(2, '0')).join('');
}
// 伪随机数生成器
class SeededRandom {
constructor(seed) {
this.seed = 0;
for (let i = 0; i < seed.length; i++) {
this.seed = ((this.seed << 5) - this.seed) + seed.charCodeAt(i);
this.seed = this.seed & 0x7fffffff;
}
}
next() {
this.seed = (this.seed * 1103515245 + 12345) & 0x7fffffff;
return this.seed / 0x7fffffff;
}
choice(arr) {
return arr[Math.floor(this.next() * arr.length)];
}
}
// 格式化日期
function formatDate(isoString) {
const d = new Date(isoString);
const pad = n => n.toString().padStart(2, '0');
return `${d.getFullYear()}-${pad(d.getMonth()+1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}`;
}
// 获取帖子内容
async function fetchPostContent(postId) {
const resp = await fetch(`/posts/${postId}.json`);
if (!resp.ok) throw new Error(`获取帖子内容失败: ${resp.status}`);
const data = await resp.json();
return data.raw || '';
}
// 开始抽奖
async function startLottery() {
const topicId = getTopicId();
const winnersCount = parseInt(document.getElementById('lottery-count').value) || 1;
const lastFloor = document.getElementById('lottery-last-floor').value ? parseInt(document.getElementById('lottery-last-floor').value) : null;
const requiredText = document.getElementById('lottery-required-text').value.trim() || null;
const startBtn = document.getElementById('lottery-start');
startBtn.disabled = true;
startBtn.textContent = '抽奖中...';
try {
// 获取帖子信息
showStatus('正在获取帖子信息...', 'info');
const topicResp = await fetch(`/t/${topicId}.json`);
const topic = await topicResp.json();
currentTopic = topic;
// 获取有效楼层
showStatus('正在获取有效楼层...', 'info');
const postsData = await new Promise((resolve, reject) => {
GM_xmlhttpRequest({
method: 'GET',
url: `https://connect.linux.do/api/topic/${topicId}/valid_post_number`,
onload: (resp) => {
if (resp.status === 200) {
try {
const data = JSON.parse(resp.responseText);
resolve(data);
} catch (e) {
reject(new Error('API返回数据格式错误'));
}
} else if (resp.status === 404) {
reject(new Error('该帖子不在抽奖支持的版块,或帖子不存在'));
} else if (resp.status === 403) {
reject(new Error('无权访问,请确保已登录'));
} else {
reject(new Error(`API请求失败: ${resp.status} - ${resp.responseText}`));
}
},
onerror: () => reject(new Error('网络请求失败,请检查网络连接'))
});
});
let numbers = postsData.rows || [];
let ids = postsData.ids || [];
let created = postsData.created || [];
if (!numbers.length) {
if (postsData.error) throw new Error(`API错误: ${postsData.error}`);
if (postsData.success === true) throw new Error('该帖子没有有效的参与楼层(可能原因:没有回复、回复已被删除、或回复者不符合条件)');
throw new Error('该帖不符合抽奖条件(可能原因:版块不支持抽奖、帖子未关闭)');
}
// 截取楼层
if (lastFloor) {
const cutIndex = numbers.findIndex(n => n > lastFloor);
if (cutIndex > 0) {
numbers = numbers.slice(0, cutIndex);
ids = ids.slice(0, cutIndex);
created = created.slice(0, cutIndex);
}
}
// 过滤包含指定文本的回复
if (requiredText) {
showStatus(`正在检查回复内容是否包含: ${requiredText} (0/${ids.length})`, 'info');
const filteredNumbers = [];
const filteredIds = [];
const filteredCreated = [];
for (let i = 0; i < ids.length; i++) {
showStatus(`正在检查回复内容是否包含: ${requiredText} (${i + 1}/${ids.length})`, 'info');
const content = await fetchPostContent(ids[i]);
if (content.includes(requiredText)) {
filteredNumbers.push(numbers[i]);
filteredIds.push(ids[i]);
filteredCreated.push(created[i]);
}
}
numbers = filteredNumbers;
ids = filteredIds;
created = filteredCreated;
if (!numbers.length) throw new Error(`没有找到包含 "${requiredText}" 的有效回复`);
}
const totalFloors = numbers.length;
const count = Math.min(winnersCount, totalFloors);
// 生成种子
showStatus('正在计算抽奖结果...', 'info');
const content = [
count, topicId, topic.details.created_by.username, topic.created_at,
ids.join(','), numbers.join(','), created.join(',')
].join('|');
const md5Hash = md5(content);
const sha1Hash = await sha('SHA-1', content);
const sha512Hash = await sha('SHA-512', content);
const seed = await sha('SHA-256', md5Hash + sha1Hash + sha512Hash);
// 抽奖
const rng = new SeededRandom(seed);
const available = [...numbers];
const winners = [];
for (let i = 0; i < count; i++) {
const winner = rng.choice(available);
available.splice(available.indexOf(winner), 1);
winners.push(winner);
}
// 获取中奖楼层对应的用户名
showStatus('正在获取中奖用户信息...', 'info');
winnersInfo = [];
for (const floor of winners) {
const floorIndex = numbers.indexOf(floor);
const postId = ids[floorIndex];
try {
const postResp = await fetch(`/posts/${postId}.json`);
const postData = await postResp.json();
winnersInfo.push({ floor, username: postData.username, postId });
} catch (e) {
winnersInfo.push({ floor, username: '未知用户', postId });
}
}
// 格式化结果
const divider = '='.repeat(80);
const divider2 = '-'.repeat(80);
const baseUrl = `https://linux.do/t/topic/${topicId}`;
let result = `${divider}
${'LINUX DO 抽奖结果 - ' + VERSION}
${divider}
帖子链接: ${baseUrl}
帖子标题: ${topic.title}
帖子作者: ${topic.details.created_by.username}
发帖时间: ${formatDate(topic.created_at)}
${divider2}
抽奖时间: ${formatDate(new Date().toISOString())}
参与楼层: ${numbers[0]} - ${numbers[numbers.length - 1]} 楼
有效楼层: ${totalFloors} 楼
中奖数量: ${count} 个
最终种子: ${seed}
${divider2}
恭喜以下楼层中奖:
${divider2}
`;
winners.forEach((floor, i) => {
const idx = (i + 1).toString().padStart(3, ' ');
result += `[${idx}] ${floor.toString().padStart(4, ' ')} 楼,楼层链接: ${baseUrl}/${floor}\n`;
});
result += `${divider}
注: 楼层顺序即为抽奖顺序
${divider}`;
// 显示结果
showStatus('抽奖完成!', 'success');
document.getElementById('lottery-result').textContent = result;
document.getElementById('lottery-result').style.display = 'block';
document.getElementById('lottery-copy-wrap').style.display = 'flex';
} catch (e) {
showStatus('抽奖失败: ' + e.message, 'error');
} finally {
startBtn.disabled = false;
startBtn.textContent = '开始抽奖';
}
}
// 复制结果(带 @ 中奖用户)
function copyResult() {
const result = document.getElementById('lottery-result').textContent;
const topicId = getTopicId();
const baseUrl = `https://linux.do/t/topic/${topicId}`;
let atList = '\n以下为中奖佬友及对应楼层:\n\n';
winnersInfo.forEach(info => {
atList += `* @${info.username} - [${info.floor} 楼](${baseUrl}/${info.floor})\n`;
});
const fullResult = '```text\n' + result + '\n```' + atList;
navigator.clipboard.writeText(fullResult).then(() => {
const b = document.getElementById('lottery-copy');
b.textContent = '✅ 已复制';
setTimeout(() => b.textContent = '📋 复制结果', 2000);
}).catch((e) => {
showStatus('复制结果失败: ' + (e?.message || e), 'error');
});
}
// 复制抽奖帖子文案模板(自动填充主题=当前帖子标题)
function copyTemplate() {
const title = (currentTopic && currentTopic.title) ? currentTopic.title : '';
const template =
`抽奖主题:
:trophy: 奖品详情:
:three_o_clock: 活动时间:
开始时间:
截止时间:
:memo: 参与方式:
在本帖下回复任意内容
:magnifying_glass_tilted_left: 抽奖规则:
每位用户仅允许参与一次。
使用官方抽奖工具随机抽取中奖者。
:warning: 注意事项:
本活动将在活动截止时间后关闭回帖,以确保公正性。
中奖者将在活动结束后24小时内在本帖公布,并通过私信通知领奖方式。
所有规则及抽奖结果由活动发起人和论坛管理团队最终解释。
期待您的积极参与,祝您好运!如有任何疑问,欢迎随时联系抽奖发起人。`;
navigator.clipboard.writeText(template).then(() => {
const b = document.getElementById('lottery-copy-template');
const old = b.textContent;
b.textContent = '✅ 已复制模板';
setTimeout(() => b.textContent = old, 2000);
}).catch((e) => {
showStatus('复制模板失败: ' + (e?.message || e), 'error');
});
}
})();

