您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
祝你好运
- // ==UserScript==
- // @name V2ex Random Floor
- // @namespace http://tampermonkey.net/
- // @version 2025-05-28
- // @description 祝你好运
- // @author You
- // @match https://www.v2ex.com/t/*
- // @icon https://www.google.com/s2/favicons?sz=64&domain=v2ex.com
- // @grant GM_registerMenuCommand
- // @require https://cdnjs.cloudflare.com/ajax/libs/seedrandom/3.0.5/seedrandom.js
- // @license MIT
- // ==/UserScript==
- (function () {
- 'use strict';
- const V2exMaxPageSize = 20
- // ==UserScript菜单==
- if (typeof GM_registerMenuCommand !== 'undefined') {
- GM_registerMenuCommand('启动抽奖', async function() {
- const urlTopicId = window.location.pathname.match(/\/t\/(\d+)/)[1];
- const topicId = prompt('请输入主题ID:', urlTopicId || '');
- if (!topicId) return alert('主题ID不能为空');
- const defaultToken = window.localStorage.getItem('v2ex-random-floor-token') || '';
- const userToken = prompt('请输入用户Token,在设置 - Tokens 中生成:', defaultToken);
- if (!userToken) return alert('用户Token不能为空');
- window.localStorage.setItem('v2ex-random-floor-token', userToken);
- const luckyCount = parseInt(prompt('请输入抽奖人数:', '1'), 10) || 1;
- if (isNaN(luckyCount) || luckyCount < 1) return alert('抽奖人数必须大于0');
- const isUnique = confirm('是否用户去重? (确定=是, 取消=否)');
- const today = new Date();
- const endTime = new Date(today.getFullYear(), today.getMonth(), today.getDate() + 1, 0, 0, 0);
- const deadline = prompt('请输入截止时间(可留空, 格式: yyyy-mm-dd HH:MM):', `${endTime.toISOString().slice(0, 10)} 24:00`);
- if (new Date(deadline).getTime() < 0) return alert('截止时间格式不正确');
- const maxUserCount = parseInt(prompt('请输入最大参与人次(可留空):', '0'), 10) || 0;
- if (isNaN(maxUserCount) || maxUserCount < 0) return alert('最大参与人次必须大于等于0');
- if (maxUserCount && maxUserCount < luckyCount) return alert('最大参与人次必须大于等于抽奖人数');
- const randomSeed = prompt('请输入随机种子,如总楼层数,可引入外部随机变量保证公平性:', '');
- const options = { topicId, luckyCount, isUnique, deadline, randomSeed, userToken, maxUserCount };
- const rf = new RandomFloor(options);
- try {
- console.log(`开始抽奖: 主题ID=${rf.topicId}, 抽奖人数=${rf.luckyCount}, 用户去重=${rf.isUnique}, 截止时间=${rf.deadline}, 最大参与人次=${rf.maxUserCount}`);
- await rf.run();
- alert('抽奖执行完毕, 结果请查看控制台日志');
- } catch (e) {
- alert('抽奖出错: ' + e.message);
- }
- });
- }
- class RandomFloor {
- constructor(options) {
- // 主题 ID
- this.topicId = options.topicId
- // 随机种子
- this.nextRandomSeed = new Math.seedrandom(options.randomSeed || Math.random())
- // 抽奖人数
- this.luckyCount = options.luckyCount || 1
- // 是否用户去重
- this.isUnique = options.isUnique || false
- // 截止时间
- this.deadline = options.deadline || 0
- // 最多参与人次
- this.maxUserCount = options.maxUserCount || 0
- // 回帖列表
- this.replies = []
- // token
- this.token = options.userToken || ''
- }
- async run() {
- const replies = await this.getReplyList(this.topicId)
- const validateList = this.filterReplies(replies)
- this.addLog(`累计 ${replies.length} 条回帖,去重后 ${validateList.length} 条`)
- if (validateList.length < this.luckyCount) {
- this.addLog(`回帖数量不足,无法抽奖`)
- return
- }
- this.addLog(`开始抽奖...`)
- const nextId = this.nextRandomSeed
- const luckyList = []
- const luckySet = new Set()
- while (luckyList.length < this.luckyCount) {
- const index = Math.floor(nextId() * validateList.length)
- const item = validateList[index]
- if (this.isUnique && luckySet.has(item.userId)) {
- continue
- }
- luckySet.add(item.userId)
- luckyList.push(item)
- }
- this.addLog(`抽奖完成,中奖名单如下:`)
- let messageText = `抽奖完成,中奖名单如下:\n`
- luckyList.forEach(item => {
- messageText += `第${item.index}楼 @${item.userName} (${item.userId})\n`
- })
- this.addLog(messageText)
- this.addLog(`抽奖结束`)
- }
- get deadlineStamp() {
- if (typeof this.deadline === 'number') {
- return this.deadline
- }
- if (typeof this.deadline === 'string') {
- return new Date(this.deadline).getTime()
- }
- if (this.deadline instanceof Date) {
- return this.deadline.getTime()
- }
- return 0
- }
- filterReplies(replies) {
- const { isUnique, deadlineStamp, maxUserCount } = this
- const userIds = new Set()
- const items = replies.filter(item => {
- if (isUnique && userIds.has(item.userId)) {
- return false
- }
- if (deadlineStamp && item.created > deadlineStamp) {
- return false
- }
- userIds.add(item.userId)
- return true
- })
- return maxUserCount > 0 ? items.slice(0, maxUserCount) : items
- }
- async getReplyList(topicId, page = 1) {
- this.addLog(`获取楼层列表: 第${page}页 获取中...`)
- const replies = await fetch(`/api/v2/topics/${topicId}/replies?p=${page}`, {
- method: 'GET',
- headers: { Authorization: 'Bearer ' + this.token }
- }).then(res => {
- if (res.status !== 200) {
- throw new Error(`获取楼层列表失败: ${res.status} ${res.statusText}`)
- }
- return res.json()
- })
- if (!replies.success) {
- throw new Error(`获取楼层列表失败: ${replies.message}`)
- }
- if (!replies.result.length) {
- return this.replies
- }
- const dataList = replies.result.map((item, index) => {
- return {
- index: this.replies.length + index + 1,
- userId: item.member.id,
- userName: item.member.username,
- created: item.created * 1000,
- }
- })
- this.replies.push(...dataList)
- this.addLog(`获取楼层列表: 第${page}页 获取到 ${dataList.length} 条`)
- if (dataList.length < V2exMaxPageSize) {
- this.addLog(`获取楼层列表: 第${page}页 获取完毕`)
- return this.replies
- }
- return this.getReplyList(topicId, page + 1)
- }
- addLog(message) {
- console.info(`[v2ex RandomFloor] ${message}`)
- }
- }
- })();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址