在discord.py中如何记录用户进出语音频道时间及使用on_voice_state_update()
Discord机器人语音频道进出时间记录实现思路
核心方法on_voice_state_update基础说明
该事件会在任意用户的语音状态发生变化时触发,自带三个固定参数:
member:触发状态变化的用户对象before:状态变化前的用户语音状态对象,可通过before.channel获取变化前的语音频道after:状态变化后的用户语音状态对象,可通过after.channel获取变化后的语音频道
实现核心思路
1. 状态变化场景判断
你只需要通过before.channel和after.channel的对比,就能覆盖所有需要统计的场景:
- 加入语音频道:
before.channel is None且after.channel is not None,此时记录用户的加入时间和对应频道ID到临时存储 - 离开语音频道:
before.channel is not None且after.channel is None,此时取出临时存储的加入时间,计算停留时长后写入持久化存储,同时删除临时存储的对应记录 - 切换语音频道:
before.channel is not None且after.channel is not None且before.channel.id != after.channel.id,先结算用户在前一个频道的停留时长并存入持久化存储,再记录用户在新频道的加入时间
2. 存储方案选择
- 临时存储:优先使用Python字典实现,结构示例:
voice_join_records = {用户ID: (加入时间戳, 频道ID)},不需要额外依赖,适合中小体量机器人;如果需要避免机器人重启丢失临时记录,可以使用Redis做持久化缓存 - 持久化存储:数据量小可以直接用SQLite,数据量大可以用MySQL、PostgreSQL等关系型数据库,表结构建议包含字段:
user_id、channel_id、join_time、leave_time、duration_seconds
3. 边界情况处理
- 方法开头先添加
if member.bot: return逻辑,过滤掉机器人自身的语音状态变化,避免生成无效记录 - 机器人重启后,先主动拉取当前所有语音频道的在线用户,为这些用户预写入当前时间戳作为加入时间,避免重启后用户离开时无法匹配加入记录
- 必须在Discord开发者后台的机器人设置页开启Voice State Intent,同时代码中初始化Intents时显式开启
voice_states权限,否则on_voice_state_update事件不会被触发
最小可运行代码示例
import datetime import discord from discord.ext import commands # 临时存储用户加入语音的记录,key为用户ID,value为(加入时间戳, 频道ID) voice_join_records = {} # 注意必须开启voice_states intent intents = discord.Intents.default() intents.voice_states = True intents.members = True bot = commands.Bot(command_prefix="!", intents=intents) @bot.event async def on_voice_state_update(member, before, after): # 过滤机器人自身的事件 if member.bot: return # 场景1:用户加入语音频道 if before.channel is None and after.channel is not None: join_time = datetime.datetime.now().timestamp() voice_join_records[member.id] = (join_time, after.channel.id) return # 场景2:用户离开语音频道 if before.channel is not None and after.channel is None: # 无加入记录则跳过(多为机器人重启前就已在频道的用户) if member.id not in voice_join_records: return join_time, channel_id = voice_join_records.pop(member.id) leave_time = datetime.datetime.now().timestamp() duration = round(leave_time - join_time, 2) # 此处可替换为写入数据库/本地文件的逻辑 print(f"用户{member.name}({member.id}) 在频道 {before.channel.name}({channel_id}) 停留了 {duration} 秒") return # 场景3:用户切换语音频道 if before.channel is not None and after.channel is not None and before.channel.id != after.channel.id: # 先结算旧频道的停留时长 if member.id in voice_join_records: join_time, old_channel_id = voice_join_records.pop(member.id) leave_time = datetime.datetime.now().timestamp() duration = round(leave_time - join_time, 2) print(f"用户{member.name}({member.id}) 在频道 {before.channel.name}({old_channel_id}) 停留了 {duration} 秒") # 记录新频道的加入时间 voice_join_records[member.id] = (datetime.datetime.now().timestamp(), after.channel.id) return bot.run("你的机器人Token")
内容的提问来源于stack exchange,提问作者dcnaber
相关产品推荐
相关产品推荐

