群友要送黑神话悟空了,写一个Yunzai plugin来帮忙抽一下奖,公开源码了:

import plugin from '../../lib/plugins/plugin.js';

export class LuckyDraw extends plugin {
  constructor() {
    super({
      name: '抽奖',
      dsc: '自动抽奖功能',
      event: 'message',
      priority: 5000,
      rule: [
        {
          reg: '^#开始报名',
          fnc: 'startSignup'
        },
        {
          reg: '^报名\\s*(.+)$',
          fnc: 'signup'
        },
        {
          reg: '^#本群中奖记录$', 
          fnc: 'getDrawRecords'
        },
        {
          reg: '^#重新挂载抽奖程序$',
          fnc: 'resumePendingDraws'
        }
      ]
    });
  }

  async resumePendingDraws() {
    const keys = await redis.keys('luckyDraw:*:endTime');
    const now = Date.now();

    for (const key of keys) {
      const groupId = key.split(':')[1];
      const theme = key.split(':')[2];
      const endTime = parseInt(await redis.get(key), 10);

      if (now >= endTime) {
        await this.startDraw(groupId, theme);
      } else {
        // 恢复定时器
        this.reply(`恢复定时器:${theme}, 倒计时:${(endTime - now) / 1000}秒`, false);
        setTimeout(() => this.startDraw(groupId, theme), endTime - now);
      }
    }
  }

  async cleanKeys(groupId, theme) {
    await redis.del(`luckyDraw:${groupId}:${theme}:theme`);
    await redis.del(`luckyDraw:${groupId}:${theme}:numWinners`);
    await redis.del(`luckyDraw:${groupId}:${theme}:endTime`);
    await redis.del(`luckyDraw:${groupId}:${theme}:participants`);
  }

  async startSignup() {
    const groupId = this.e.group_id;

    if (this.e.sender.role !== 'admin' && this.e.sender.role !== 'owner') {
      return this.reply('只有管理员或群主可以开始新的报名。', false, { at: true });
    }

    const match = this.e.msg.match(/^#开始报名\s*(.+)\s*,中奖人数(\d+)个,限时((\d+)(分钟|小时|天)|(\d{4}年\d{1,2}月\d{1,2}日\d{1,2}:\d{2}))$/);
    if (!match) {
      return this.reply('指令格式错误,请使用格式:#开始报名xxx,中奖人数n个,限时时间。', false, { at: true });
    }

    const theme = match[1];
    const numWinners = parseInt(match[2], 10);

    const existingKeywordLuckyDraw = await redis.get(`luckyDraw:${groupId}:${theme}:theme`);
    const existingKeywordResult = await redis.get(`luckyDrawResult:${groupId}:${theme}`);

    if (existingKeywordLuckyDraw || existingKeywordResult) {
      return this.reply(`关键词 "${theme}" 已被占用,请选择其他关键词。`, false, { at: true });
    }

    let timeLimit;
    if (match[4]) {
      const timeValue = parseInt(match[4], 10);
      const timeUnit = match[5];

      if (timeUnit === '分钟') {
        timeLimit = timeValue * 60;
      } else if (timeUnit === '小时') {
        timeLimit = timeValue * 3600;
      } else if (timeUnit === '天') {
        timeLimit = timeValue * 86400;
      }
    } else if (match[6]) {
      const dateTimeStr = match[6].replace('年', '-').replace('月', '-').replace('日', ' ');
      const endDate = new Date(dateTimeStr);
      const now = new Date();
      timeLimit = Math.floor((endDate.getTime() - now.getTime()) / 1000);

      if (timeLimit <= 0) {
        return this.reply('输入的时间已经过去,请重新设置。', false, { at: true });
      }
    }

    if (timeLimit > 86400 * 30) {
      return this.reply('时间过长,请重新发布指令,限时时间不能超过30天。', false, { at: true });
    }

    const startTime = Date.now();
    const endTime = startTime + timeLimit * 1000;
    console.log(endTime);
    

    await redis.set(`luckyDraw:${groupId}:${theme}:theme`, theme);
    await redis.set(`luckyDraw:${groupId}:${theme}:numWinners`, String(numWinners));
    await redis.set(`luckyDraw:${groupId}:${theme}:endTime`, String(endTime));
    await redis.set(`luckyDraw:${groupId}:${theme}:participants`, JSON.stringify([]));

    await this.reply([`开始报名${theme}活动!请发送“报名${theme}”参加!`, `中奖人数:${numWinners}个,`, `报名截止时间:${new Date(endTime).toLocaleString()}`], false);
    await this.reply([segment.at("all")]);

    setTimeout(() => this.startDraw(groupId, theme), timeLimit * 1000);
  }

  async signup() {
    const match = this.e.msg.match(/^报名\s*(.+)$/);
    if (!match) {
      return this.reply('报名格式错误,请使用 “报名+关键词” 进行报名。', false, { at: true });
    }

    const keyword = match[1].trim();
    const groupId = this.e.group_id;
    console.log(`luckyDraw:${groupId}:${keyword}:theme`);
    
    const theme = await redis.get(`luckyDraw:${groupId}:${keyword}:theme`);

    console.log(theme,keyword);
    
    if (!theme) {
      return this.reply(`报名关键词错误,请使用正确的关键词。`, false, { at: true });
    }

    await redis.watch(`luckyDraw:${groupId}:${keyword}:participants`);

    let participants = JSON.parse(await redis.get(`luckyDraw:${groupId}:${keyword}:participants`)) || [];

    if (participants.includes(this.e.user_id)) {
      return this.reply('你已经报名了!', false, { at: true });
    }

    participants.push(this.e.user_id);

    await redis.multi()
      .set(`luckyDraw:${groupId}:${keyword}:participants`, JSON.stringify(participants))
      .exec();

    await this.reply('报名成功!', false, { at: true });
  }

  async startDraw(groupId, theme) {
    const participants = JSON.parse(await redis.get(`luckyDraw:${groupId}:${theme}:participants`)) || [];
    const numWinners = parseInt(await redis.get(`luckyDraw:${groupId}:${theme}:numWinners`), 10);

    if (participants.length === 0) {
      await this.cleanKeys(groupId, theme);
      return this.reply(`活动${theme}:没有人报名,无法抽奖。`, false);
    }

    if (numWinners > participants.length) {
      await this.cleanKeys(groupId, theme);
      return this.reply(`活动${theme}:报名人数少于抽取人数,请重新设置抽取人数。`, false);
    }

    const winners = [];
    const participantsCopy = [...participants];
    for (let i = 0; i < numWinners; i++) {
      const winnerIndex = Math.floor(Math.random() * participantsCopy.length);
      winners.push(participantsCopy.splice(winnerIndex, 1)[0]);
    }

    await this.reply([`抽奖结束!${theme}的中奖者为以下人员`]);
    for (const winner of winners) {
      await this.reply([segment.at(winner)]);
    }

    await redis.set(`luckyDrawResult:${groupId}:${theme}`, JSON.stringify(winners));

    await this.cleanKeys(groupId, theme);
  }

  async getDrawRecords() {
    const groupId = this.e.group_id;
    const keys = await redis.keys(`luckyDrawResult:${groupId}:*`);

    if (keys.length === 0) {
      return this.reply('本群还没有任何抽奖记录。', false, { at: true });
    }

    const records = [];
    for (const key of keys) {
      const theme = key.split(':')[2];
      const winners = JSON.parse(await redis.get(key));
      const winnerNames = winners.map(winner => segment.at(winner));
      await this.reply([`活动${theme}的中奖者为:`, ...winnerNames]);
    }

    await this.reply(records.join('\n'), false);
  }
}

其中服务器上出了redis的不少岔子,一一记录一下:

  • 为了让Navicat连接上redis服务器,除了打开ufw的6379端口之外,还要使用 iptables -I 命令在指定位置插入规则。例如,将一个允许规则插入到链的顶部::
sudo iptables -I INPUT 1 -p tcp --dport 6379 -j ACCEPT
  • ReadWriteDirectories=-/etc/redis 这一行配置允许 Redis 服务对 /etc/redis 目录进行写操作,这可能导致 Redis 动态修改配置文件并覆盖 dir 配置项。
sudo vim /lib/systemd/system/redis-server.service
  • 可以尝试将这行配置注释掉或删除,然后重新加载 systemd 配置并重启 Redis 服务:
sudo systemctl daemon-reload
sudo systemctl restart redis
最后修改:2024 年 08 月 19 日
收款不要了,给孩子补充点点赞数吧