discord-dota-bot/discord_bot.py
OpenClaw Bot 8708c931c8 feat: 添加每周总结功能
- 新增 get_weekly_stats_for_discord() 获取过去7天统计数据
- 新增 generate_weekly_summary_for_discord() 生成周报
- 新增 generate_weekly_summary_image() 生成周报图片
- 新增 weekly_summary.html 模板
- 添加每周日21:00定时推送
- 添加 /weekly_summary 命令手动触发
2026-02-07 03:44:09 +00:00

241 lines
8.1 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import discord
from discord.ext import tasks
from loguru import logger
import dota
import utils
import sentry_sdk
import datetime
import asyncio
sentry_sdk.init(
dsn="https://272f1e4ecb3847ac8d24be796515e558@o4506942768021504.ingest.us.sentry.io/4506986058743808",
)
# formatter = logging.Formatter('%(levelname)s %(name)s %(asctime)s %(message)s', '%Y-%m-%d %H:%M:%S')
# log_handler = logging.FileHandler(utils.logger_file)
# log_handler.setFormatter(formatter)
# logger = logging.getLogger(__name__)
# logger.addHandler(log_handler)
# logger.propagate = False
logger.info('start bot')
# bot = discord.Bot(proxy='http://127.0.0.1:1235')
bot = discord.Bot()
channel_id = 1152167937852055552
@tasks.loop(minutes=1)
async def send_message(channel):
if utils.is_game_time():
send_message.change_interval(minutes=2)
else:
send_message.change_interval(minutes=15)
try:
matches = dota.get_friends_recent_matches()
# 获取连胜连败消息
streak_notifications = dota.check_streaks()
except Exception as e:
logger.error(f"Error in send_message task: {e}")
return
# 用于标记是否是第一场比赛
first_match = True
for match_ in matches:
data = await dota.serialize_match_for_discord(match_)
logger.info(f"sending match {match_['match_id']}, {data}")
try:
# 从embed中提取朋友信息并添加胜负状态
friends_info = ""
win_status = "✅ **胜利** " if data.get('win') else "❌ **失败** "
if data['embeds'] and len(data['embeds']) > 0:
embed = data['embeds'][0]
if 'fields' in embed and len(embed['fields']) > 2:
# 第三个field包含了朋友信息
field = embed['fields'][2]
if 'value' in field and field['value']:
friends_info = f"{win_status}**{field['value']}** 的比赛:\n\n"
# 将朋友信息放在内容开头,连胜连败消息只在第一场比赛时添加
content = data['content']
if first_match and streak_notifications:
streak_msg = '\n'.join(streak_notifications) + '\n\n'
content = friends_info + streak_msg + content
first_match = False # 标记已经处理过第一场比赛
else:
content = friends_info + content
# 发送比赛结果
await channel.send(content=content, embeds=[discord.Embed.from_dict(embed) for embed in data['embeds']])
except Exception as e:
logger.error(f"send match error {e}")
@bot.command(description="获取最近战绩", name='recent_matches')
async def get_friends_recent_matches(ctx, name, match_count=5):
await ctx.defer()
logger.info(f"get_friends_recent_matches {name} {match_count}")
friends = dota.Friend.filter(name=name)
match_count = int(match_count)
if friends.count() == 0:
await ctx.respond(content=f'找不到 {name} 的信息')
return
data = await dota.Friend.serialize_recent_matches_for_discord(friends, match_count)
if not data:
await ctx.respond(content=f'找不到 {name} 的战绩')
return
try:
await ctx.respond(content=data['content'], embeds=[discord.Embed.from_dict(embed) for embed in data['embeds']])
except Exception as e:
logger.error(f"send recent_matches error {e}")
@bot.command(description='获取朋友', name='list_friends')
async def get_friends(ctx):
logger.info(f'get_friends')
friends = dota.Friend.select()
msg = ''
if friends.count() == 0:
msg = '没有朋友'
else:
for friend in friends:
msg += f'{friend.name} {friend.steam_id} {friend.active}\n'
await ctx.respond(content=msg)
@bot.command(description='修改朋友信息', name='mod_friend')
async def mod_friend(ctx, steam_id, name):
logger.info(f'mod_friend {steam_id} {name}')
friend = dota.Friend.get_or_none(steam_id=steam_id)
if friend:
friend.name = name
friend.save()
await ctx.respond(content=f'修改成功 {steam_id} {name}')
else:
await ctx.respond(content=f'找不到 {steam_id}')
@bot.command(description='添加朋友', name='add_friend')
async def add_friend(ctx, steam_id, name):
logger.info(f'add_friend {steam_id} {name}')
friend = dota.Friend.get_or_none(steam_id=steam_id)
if friend:
await ctx.respond(content=f'已经存在 {steam_id} {name}')
else:
friend = dota.Friend.create(steam_id=steam_id, name=name, active=True)
await ctx.respond(content=f'添加成功 {steam_id} {name}')
@bot.command(description='禁用朋友', name='deactivate_friend')
async def deactivate_friend(ctx, steam_id):
logger.info(f'deactivate_friend {steam_id}')
friend = dota.Friend.get_or_none(steam_id=steam_id)
if friend:
friend.active = False
friend.save()
await ctx.respond(content=f'禁用成功 {steam_id}')
else:
await ctx.respond(content=f'找不到 {steam_id}')
@bot.command(description='获取本周统计总结', name='weekly_summary')
async def get_weekly_summary(ctx):
"""手动触发每周总结"""
await ctx.defer()
logger.info("Manual weekly summary requested")
try:
data = await dota.generate_weekly_summary_for_discord()
if data:
await ctx.respond(content=data['content'], embeds=[discord.Embed.from_dict(embed) for embed in data['embeds']])
else:
await ctx.respond(content='本周暂无数据')
except Exception as e:
logger.error(f"Error generating weekly summary: {e}")
await ctx.respond(content=f'生成周报失败: {str(e)}')
@bot.command(description='启用朋友', name='activate_friend')
async def activate_friend(ctx, steam_id):
logger.info(f'activate_friend {steam_id}')
friend = dota.Friend.get_or_none(steam_id=steam_id)
if friend:
friend.active = True
friend.save()
await ctx.respond(content=f'启用成功 {steam_id}')
else:
await ctx.respond(content=f'找不到 {steam_id}')
@tasks.loop(minutes=1)
async def heartbeat():
utils.heartbeat()
@tasks.loop(hours=24)
async def check_rank_changes(channel):
logger.info("Checking for rank changes")
try:
data = await dota.check_rank_changes_for_discord()
if data:
logger.info(f"Sending rank changes: {data}")
await channel.send(content=data['content'], embeds=[discord.Embed.from_dict(embed) for embed in data['embeds']])
except Exception as e:
logger.error(f"Error checking rank changes: {e}")
sentry_sdk.capture_exception(e)
@check_rank_changes.before_loop
async def before_check_rank_changes():
# 等待到晚上9点再开始第一次运行
now = datetime.datetime.now()
target_time = now.replace(hour=21, minute=0, second=0, microsecond=0)
if now >= target_time:
target_time = target_time + datetime.timedelta(days=1)
seconds_until_target = (target_time - now).total_seconds()
await asyncio.sleep(seconds_until_target)
@tasks.loop(hours=168) # 每7天运行一次
async def weekly_summary(channel):
"""每周总结任务"""
logger.info("Generating weekly summary")
try:
data = await dota.generate_weekly_summary_for_discord()
if data:
logger.info(f"Sending weekly summary")
await channel.send(content=data['content'], embeds=[discord.Embed.from_dict(embed) for embed in data['embeds']])
except Exception as e:
logger.error(f"Error generating weekly summary: {e}")
sentry_sdk.capture_exception(e)
@weekly_summary.before_loop
async def before_weekly_summary():
# 等待到周日晚上9点再开始第一次运行
now = datetime.datetime.now()
# 计算下一个周日的21:00
days_until_sunday = (6 - now.weekday()) % 7
if days_until_sunday == 0 and now.hour >= 21:
days_until_sunday = 7
target_time = (now + datetime.timedelta(days=days_until_sunday)).replace(hour=21, minute=0, second=0, microsecond=0)
seconds_until_target = (target_time - now).total_seconds()
logger.info(f"Weekly summary will start in {seconds_until_target} seconds")
await asyncio.sleep(seconds_until_target)
@bot.event
async def on_ready():
logger.info(f"We have logged in as {bot.user}")
channel = bot.get_channel(channel_id)
send_message.start(channel)
heartbeat.start()
# 启动天梯检查任务
check_rank_changes.start(channel)
# 启动每周总结任务
weekly_summary.start(channel)
bot.run('MTE1MjE2NTc3NDMwNDIyMzI2Mg.GEi-17.VvuIkRy_cFD9XF6wtTagY95LKEbTxKaxy-FxGw') # 这里替换成你自己的 token