Discord Bot命令修改后无法同步的技术求助
问题描述
我的Discord Bot已稳定运行10个月,此前所有带选项的命令同步均正常。昨日参照Apex命令的实现方式,为Fortnite命令添加选项后,本地启动Bot时控制台显示“Commands Synced”,但刷新Discord后命令未更新,新增测试命令也不显示。Bot的状态更新功能正常,已尝试本地/部署运行、重启刷新Discord等操作,期望Fortnite命令能像Apex命令一样显示对应选项。
相关代码
Fortnite.py
import discord from discord.ext import commands import datetime import requests from typing import Literal import os TIME_MAPPING = { 'Season': 'season', 'Lifetime': 'lifetime', } async def fortnite(interaction: discord.Interaction, time: Literal['Season', 'Lifetime'], name: str = None): try: if name is None: raise ValueError("Please provide a username.") if time is None: raise ValueError("Please provide a platform (Season, Lifetime).") time_window = TIME_MAPPING.get(time) if not api_platform: raise ValueError("Invalid time range. Please use Season or Lifetime") response = requests.get( f"https://fortnite-api.com/v2/stats/br/v2?timeWindow={time_window}&name={name}", headers={"Authorization": os.getenv('FORTNITE_API_KEY')} ) response.raise_for_status() data = response.json() if 'data' not in data: raise ValueError("Invalid data structure in API response.") stats = data['data'] account = stats['account'] battle_pass = stats['battlePass'] # Correct win rate calculation wins = stats['stats']['all']['overall']['wins'] matches = stats['stats']['all']['overall']['matches'] if matches > 0: calculated_win_rate = wins / matches else: calculated_win_rate = 0 embed = discord.Embed(title=f"Fortnite - {name}", color=0xdd4f7a, url=f"https://fortnitetracker.com/profile/all/{name}") embed.set_thumbnail(url="https://seeklogo.com/images/F/fortnite-logo-1F7897BD1E-seeklogo.com.png") embed.add_field(name="Account", value=f"Name: {account['name']}\nLevel: {battle_pass['level']}", inline=True) embed.add_field(name="Match Placements", value=f"Victory Royales: {wins} \nTop 5: {stats['stats']['all']['overall']['top5']}\nTop 12: {stats['stats']['all']['overall']['top12']}", inline=True) # Additional Stats embed.add_field(name="Kill Stats", value=f"Kills/Deaths: {stats['stats']['all']['overall']['kills']:,}/{stats['stats']['all']['overall']['deaths']:,}\n" f"KD Ratio: {stats['stats']['all']['overall']['kd']:.2f}\n" f"Kills Per Minute: {stats['stats']['all']['overall']['killsPerMin']:.2f}\n" f"Kills Per Match: {stats['stats']['all']['overall']['killsPerMatch']:.2f}\n" f"Players Outlived: {stats['stats']['all']['overall']['playersOutlived']:,}", inline=False) embed.add_field(name="Match Stats", value=f"Total Matches Played: {matches:,}\n" f"Win Rate: {calculated_win_rate:.2%}\n" f"Total Score: {stats['stats']['all']['overall']['score']:,}\n" f"Score Per Minute: {stats['stats']['all']['overall']['scorePerMin']:.0f}\n" f"Score Per Match: {stats['stats']['all']['overall']['scorePerMatch']:.0f}\n" f"Total Minutes Played: {stats['stats']['all']['overall']['minutesPlayed']:,}", inline=True) embed.timestamp = datetime.datetime.now(datetime.UTC) embed.set_footer(text="Built By Goldiez ❤️") await interaction.response.send_message(embed=embed) except requests.exceptions.RequestException as e: print(f"Error: {e}") await interaction.response.send_message( "Sorry, I couldn't retrieve Fortnite stats at the moment. Please try again later.") except (KeyError, ValueError) as e: print(f"Error: {e}") await interaction.response.send_message( "Failed to retrieve Fortnite stats. The Fortnite API is Currently Unavailable") except Exception as e: print(f"Error: {e}") await interaction.response.send_message( "Oops! An unexpected error occurred while processing your request. Please try again later.") def setup(client): client.tree.command( name="fortnite", description="Check your Fortnite Player Stats" )(fortnite)
Apex.py
import discord import datetime import requests from typing import Literal import os PLATFORM_MAPPING = { 'Xbox': 'xbl', 'Playstation': 'psn', 'Origin (PC)': 'origin', } async def apex(interaction: discord.Interaction, platform: Literal['Xbox', 'Playstation', 'Origin (PC)'], name: str = None): try: if name is None: raise ValueError("Please provide a username.") if platform is None: raise ValueError("Please provide a platform (Xbox, Playstation, Origin).") api_platform = PLATFORM_MAPPING.get(platform) if not api_platform: raise ValueError("Invalid platform. Please use Xbox, Playstation, or Origin.") response = requests.get(f"https://public-api.tracker.gg/v2/apex/standard/profile/{api_platform}/{name}", headers={"TRN-Api-Key": os.getenv('TRN-Api-Key')}) response.raise_for_status() data = response.json() if 'data' not in data or 'segments' not in data['data']: error_message = f"Invalid data structure in API response. Response: {data}" print(f"Error: {error_message}") raise ValueError(error_message) segments = data['data']['segments'] if not segments: await interaction.response.send_message(f"No segments found for the given username: {name}") return lifetime = segments[0]['stats'] ranked = segments[0]['stats']['rankScore'] peak_rank = segments[0]['stats']['lifetimePeakRankScore'] active_legend_name = data['data'].get('metadata', {}).get('activeLegendName', 'Unknown') active_legend_data = next( (legend for legend in segments if legend['metadata']['name'] == active_legend_name), None) # Helper function to get the percentile label def get_percentile_label(percentile): if percentile is not None: if percentile >= 90: return '🌟 Top' else: return 'Top' if percentile >= 50 else 'Bottom' else: return 'N/A' def format_stat_value(stat_data): stat_value = stat_data.get('value') if stat_value is not None: percentile_label = get_percentile_label(stat_data.get('percentile', 0)) percentile_value = int(stat_data.get('percentile', 0)) if percentile_label != 'N/A' else 0 return f"{int(stat_value):,} ({percentile_label} {percentile_value}%)" else: return 'N/A' legend_color = active_legend_data.get('metadata', {}).get('legendColor', '#9B8651') # Create the embed at the beginning embed = discord.Embed(title=f"Apex Legends - {name}", url=f"https://apex.tracker.gg/apex/profile/{api_platform}/{name}/overview", color=int(legend_color[1:], 16)) # Main lifetime stats level_data = lifetime.get('level', {}) kills_data = lifetime.get('kills', {}) damage_data = lifetime.get('damage', {}) matches_played_data = lifetime.get('matchesPlayed', {}) arena_winstreak_data = lifetime.get('arenaWinStreak', {}) formatted_level = format_stat_value(level_data) formatted_kills = format_stat_value(kills_data) formatted_damage = format_stat_value(damage_data) formatted_matches_played = format_stat_value(matches_played_data) formatted_arena_winstreak = format_stat_value(arena_winstreak_data) # Legend stats if active_legend_data and 'stats' in active_legend_data: # Embed setup embed.set_thumbnail(url=f"{active_legend_data['metadata']['portraitImageUrl']}") # Add fields to the embed only if the corresponding stats are available embed.add_field(name="Lifetime", value=f"Level: **{formatted_level}**" f"\nKills: **{formatted_kills}**" f"\nDamage: **{formatted_damage}**" f"\nMatches Played: **{formatted_matches_played}**" f"\nArena Winstreak: **{formatted_arena_winstreak}**", inline=True) # Add the dynamic fields to the embed embed_fields = [] for stat_key, stat_data in active_legend_data['stats'].items(): stat_name = stat_data.get('displayName', stat_key.capitalize()) stat_value = int(stat_data.get('value', 0)) if stat_data.get('value') is not None else 0 stat_percentile = stat_data.get('percentile', 0) field_str = f"{stat_name}: **{stat_value} ({get_percentile_label(stat_percentile)} {int(stat_percentile) if stat_percentile is not None else 0}%)**" embed_fields.append(field_str) # Add other fields to the embed embed.add_field(name="Current Rank", value=f"**_Battle Royale Rank_**\n{ranked.get('metadata', {}).get('rankName', 0)}: **{int(ranked.get('value', 0)):,}**" f"\n# {int(ranked.get('rank', 0) or 0):,} • {int(ranked.get('percentile', 0) or 0)}%", inline=True) embed.add_field(name="Peak Rank", value=f"**_Battle Royale Rank_**\n{peak_rank.get('metadata', {}).get('rankName', 0)}: **{int(peak_rank.get('value', 0)):,}**", inline=False) # Add the dynamic fields to the embed embed.add_field(name=f"{active_legend_name} - Currently Selected", value='\n'.join(embed_fields), inline=False) embed.timestamp = datetime.datetime.now(datetime.UTC) embed.set_footer(text="Built By Goldiez ❤️") await interaction.response.send_message(embed=embed) if not interaction.response.is_done(): await interaction.response.send_message(embed=embed) except ValueError as e: print(f"Error: {e}") if not interaction.response.is_done(): await interaction.response.send_message(f"Oops! Something went wrong. {e}") except requests.exceptions.RequestException as e: print(f"Error: {e}") if not interaction.response.is_done(): await interaction.response.send_message( "Sorry, I couldn't retrieve Apex Legends stats at the moment. Please try again later.") except Exception as e: print(f"Error: {e}") if not interaction.response.is_done(): await interaction.response.send_message( "Oops! An unexpected error occurred while processing your request. Please try again later.") def setup(client): client.tree.command( name="apex", description="Check your Apex Player Stats" )(apex)
bot.py
import discord from discord.ext import commands import os from dotenv import load_dotenv import asyncio import logging from commands import apex, league, fortnite, horoscope, help, review, tft, servers, kick import re # Load environment variables load_dotenv() # Get the blacklisted guild IDs from the environment variable blacklisted_guilds = set(map(int, os.getenv('BLACKLISTED_GUILDS', None).split(','))) if os.getenv( 'BLACKLISTED_GUILDS') else set() logger = logging.getLogger('discord.gateway') logger.setLevel(logging.ERROR) # Maybe fix as server grows # Create the bot instance intents = discord.Intents.all() client = commands.Bot(command_prefix="/", help_command=None, intents=intents) # Setup command modules apex.setup(client) league.setup(client) fortnite.setup(client) horoscope.setup(client) tft.setup(client) # servers.setup(client) # kick.setup(client) help.setup(client) review.setup(client) # Regex pattern to match Discord invite links invite_link_pattern = re.compile(r"(https?://)?(www\.)?(discord\.gg|discordapp\.com/invite)/[A-Za-z0-9]+") # Event for when the bot is ready @client.event async def on_ready(): print(f'{client.user} has connected to Discord!') try: await client.wait_until_ready() # Wait until the bot is fully connected print(f"Commands Synced") except Exception as e: print(e) await update_presence() # Call the function to set initial presence # Function to update the bot's presence async def update_presence(): while True: guild_count = len(client.guilds) presence = discord.Activity( type=discord.ActivityType.playing, name=f"on {guild_count} servers") await client.change_presence(activity=presence) await asyncio.sleep(18000) # Update every 5 hours @client.event async def on_message(message): if message.author == client.user: return # Check if the message contains a Discord invite link if invite_link_pattern.search(message.content): await message.channel.send( "Need Horoscopes? Apex Stats? Fortnite Stats? LoL Stats? Idk Add me! <https://astrostats.vercel.app>") await client.process_commands(message) # Event for handling interaction errors @client.event async def p_error(interaction: discord.Interaction, error): if isinstance(error, commands.MissingRequiredArguments): await interaction.response.send_message('Missing required arguments.') # Event for checking if the guild is blacklisted before joining @client.event async def on_guild_join(guild): if guild.id in blacklisted_guilds: await guild.leave() print(f"Left blacklisted guild: {guild.name} ({guild.id})") # Start the bot client.run(os.getenv('TOKEN'))
解决方案
1. 修复命令同步逻辑
你的bot.py中on_ready事件仅打印了同步提示,未执行实际的命令同步操作。Discord要求显式调用client.tree.sync()才能将slash命令同步到服务器。修改on_ready事件:
@client.event async def on_ready(): print(f'{client.user} has connected to Discord!') try: await client.wait_until_ready() # 执行命令同步并打印同步数量 synced = await client.tree.sync() print(f"Synced {len(synced)} command(s)") except Exception as e: print(e) await update_presence()
- 全局命令同步后可能需要1小时左右生效,测试阶段可指定服务器ID快速同步:
# 替换为你的测试服务器ID TEST_GUILD = discord.Object(id=1234567890) synced = await client.tree.sync(guild=TEST_GUILD)
2. 修复Fortnite.py中的变量错误
在fortnite函数中,存在未定义变量的错误:
# 错误代码 if not api_platform: raise ValueError("Invalid time range. Please use Season or Lifetime")
此处api_platform未定义,应替换为实际使用的time_window变量:
# 修正后 if not time_window: raise ValueError("Invalid time range. Please use Season or Lifetime")
该错误会导致命令执行时抛出异常,也可能影响命令注册流程,必须修复。
3. 规范命令参数的必填性
虽然函数内判断了name和time是否为空,但Discord slash命令参数默认必填。如果name是必填项,可直接去掉默认值,让Discord在调用时强制要求用户输入,省去函数内的判断:
async def fortnite(interaction: discord.Interaction, time: Literal['Season', '
相关产品推荐
相关产品推荐

