async def pve(self, ctx, username=None, platform=None): """Display PvE stats across all characters on an account In order to use this command for your own account, you must first register your Destiny 2 account with the bot via the register command. `stats pve` - Display your PvE stats (preferred platform) \$`stats pve Asal#1502 bnet` - Display Asal's PvE stats on Battle.net \$`stats pve @user` - Display a registered user's PvE stats (preferred platform) \$`stats pve @user bnet` - Display a registered user's PvE stats on Battle.net """ manager = MessageManager(ctx) await ctx.channel.trigger_typing() # Get membership details. This depends on whether or not a platform or username were given. membership_details = await helpers.get_membership_details(self.bot, ctx, username, platform) # If there was an error getting membership details, display it if isinstance(membership_details, str): await manager.send_message(membership_details) return await manager.clean_messages() else: platform_id, membership_id, display_name = membership_details pve_stats_json = await self.get_stats(platform_id, membership_id, [7,4,16,17,18,46,47]) if not pve_stats_json: await manager.send_message("Sorry, I can't seem to retrieve those stats right now") return await manager.clean_messages() pve_stats = PvEStats(pve_stats_json) await manager.send_embed(pve_stats_embed(pve_stats, display_name, platform_id)) await manager.clean_messages()
async def pm(self, ctx, user_id: int, *message): """Send a PM via the bot to a user given their ID""" manager = MessageManager(ctx) user = self.bot.get_user(user_id) if ctx.author.id not in constants.MODS: return if len(message) == 0: await manager.send_message("You forgot to include your message!") return await manager.clean_messages() response = "You have received a message from my developer:\n\n**" for word in message: response += "{} ".format(word) response += ( "**\n\nYour response will not be tracked here. If you wish " + "to speak with him further, join the official **{} Support** " + "server - https://discord.gg/GXCFpkr").format(self.bot.user.name) try: await user.send(response) except: await manager.send_message( 'Could not PM user with ID {}'.format(user_id)) else: await manager.send_message('PM successfully sent.') await manager.clean_messages()
async def seteventrole(self, ctx, *, event_role): """Set the lowest role that is able to create events (Manage Server only) By default, creating events requires the user to have Manage Server permissions. But if an event role is set, then any user that is of the event role or higher may create events. **Note:** Mentioning the role directly with this command will not work. You must provide only the name of the role without mentioning it. The role name is also case sensitive! """ manager = MessageManager(ctx) guild_event_role = None for role in ctx.guild.roles: if role.name in (event_role, "@{}".format(event_role)): guild_event_role = role if not guild_event_role: await manager.send_message( "I couldn't find a role called **{}** on this server.\n\n". format(event_role) + "Note that you must provide only the name of the role. " + "Mentioning it with the @ sign won't work. The role name is also " + "case sensitive!") return await manager.clean_messages() self.bot.db.set_event_role_id(ctx.guild.id, guild_event_role.id) await manager.send_message( "The event role has been set to: **{}**".format( format_role_name(guild_event_role))) return await manager.clean_messages()
async def trials(self, ctx, username=None, platform=None): """Display Trials stats across all characters on an account In order to use this command for your own account, you must first register your Destiny 2 account with the bot via the register command. `stats trials` - Display your Trials stats (preferred platform) \$`stats trials Asal#1502 bnet` - Display Asal's Trials stats on Battle.net \$`stats trials @user` - Display a registered user's Trials stats (preferred platform) \$`stats trials @user bnet` - Display a registered user's Trials stats on Battle.net """ manager = MessageManager(ctx) await ctx.channel.trigger_typing() # Get membership details. This depends on whether or not a platform or username were given. membership_details = await helpers.get_membership_details(self.bot, ctx, username, platform) # If there was an error getting membership details, display it if isinstance(membership_details, str): await manager.send_message(membership_details) return await manager.clean_messages() else: platform_id, membership_id, display_name = membership_details trials_stats_json = (await self.get_stats(platform_id, membership_id, [39]))['trialsofthenine'].get('allTime') if not trials_stats_json: await manager.send_message("Sorry, I can't seem to retrieve those stats right now") return await manager.clean_messages() trials_stats = PvPStats(trials_stats_json) await manager.send_embed(pvp_stats_embed(trials_stats, "Trials of the Nine Stats", display_name, platform_id)) await manager.clean_messages()
async def donate(self, ctx): """Support the continued development of Spirit!""" manager = MessageManager(ctx) e = discord.Embed(colour=constants.BLUE) text = ( "Spirit is a work of love that has taken countless hours to develop. Your donation " + "will go towards server hosting costs, development tools, and if you donate " + "monthly, will also earn you some special privelges on the Spirit Discord server!\n\n" + "Donate once: https://www.paypal.me/spiritbot\n" + "Donate monthly: https://www.patreon.com/spiritbot") reward_1 = "- Colored name on the Spirit Discord server" reward_2 = ( "- Patron role and colored name on the Spirit Discord server\n" + "- Access to the developer blog on Patreon and the Spirit Discord server\n" + "- Access to a patron only channel on the Spirit Discord server which includes sneak peeks of new features!" ) reward_3 = ("- All rewards from the previous tier\n" + "- Your own personalized message built right into Spirit!") e.description = text e.add_field(name="$1/Month", value=reward_1) e.add_field(name="$5/Month", value=reward_2) e.add_field(name="$10/Month", value=reward_3) await manager.send_embed(e) await manager.clean_messages()
async def on_command_error(self, ctx, error): """Command error handler""" manager = MessageManager(ctx) if isinstance(error, commands.CommandNotFound): pass elif isinstance(error, commands.MissingRequiredArgument): pass elif isinstance(error, commands.NotOwner): pass elif isinstance(error, commands.NoPrivateMessage): await manager.send_message("You can't use that command in a private message") elif isinstance(error, commands.CheckFailure): await manager.send_message("You don't have the required permissions to do that") elif isinstance(error, commands.CommandOnCooldown): await manager.send_message(error) # Non Discord.py errors elif isinstance(error, commands.CommandInvokeError): if isinstance(error.original, discord.errors.Forbidden): pass elif isinstance(error.original, asyncio.TimeoutError): await manager.send_private_message("I'm not sure where you went. We can try this again later.") else: raise error else: raise error await manager.clean_messages()
async def about(self, ctx): """Display information about the bot itself This command was adapted from RoboDanny by Rapptz - https://www.github.com/Rapptz/RoboDanny """ manager = MessageManager(ctx) e = discord.Embed(title='Spirit v{}'.format(constants.VERSION), colour=constants.BLUE) e.description = ( "[Invite Spirit](https://discordapp.com/oauth2/authorize?client_id=335084645743984641&scope=bot&permissions=523344)\n" + "[Spirit Support Server](https://discord.gg/GXCFpkr)") owner = self.bot.get_user(118926942404608003) e.set_author(name=str(owner), icon_url=owner.avatar_url) # statistics total_members = sum(1 for _ in self.bot.get_all_members()) total_online = len({ m.id for m in self.bot.get_all_members() if m.status is discord.Status.online }) total_unique = len(self.bot.users) voice_channels = [] text_channels = [] for guild in self.bot.guilds: voice_channels.extend(guild.voice_channels) text_channels.extend(guild.text_channels) text = len(text_channels) voice = len(voice_channels) e.add_field(name='Members', value='{} total\n{} unique\n{} unique online'.format( total_members, total_unique, total_online)) e.add_field(name='Channels', value='{} total\n{} text\n{} voice'.format( text + voice, text, voice)) memory_usage = "%0.2f" % (self.process.memory_full_info().uss / 1024**2) cpu_usage = "%0.2f" % (self.process.cpu_percent() / psutil.cpu_count()) e.add_field(name='Process', value='{} MiB\n{}% CPU'.format(memory_usage, cpu_usage)) e.add_field(name='Guilds', value=len(self.bot.guilds)) e.add_field(name='Commands Run', value=self.bot.command_count) e.add_field(name='Uptime', value=self.get_bot_uptime(brief=True)) e.set_footer(text='Made with discord.py', icon_url='http://i.imgur.com/5BFecvA.png') await manager.send_embed(e) await manager.clean_messages()
async def seteventrole_error(self, ctx, error): if isinstance(error, commands.MissingRequiredArgument): manager = MessageManager(ctx) event_role = get_event_role(ctx) if not event_role: role_display = 'None (anyone can make events)' else: role_display = format_role_name(event_role) await manager.send_message( "The current event role is: **{}**\n\n".format(role_display) + "To change the event role, use '{}settings seteventrole <role_name>'" .format(ctx.prefix)) await manager.clean_messages()
async def setprefix(self, ctx, new_prefix): """ Change the server's command prefix (Manage Server only) """ manager = MessageManager(ctx) if len(new_prefix) > 5: await manager.send_message("Prefix must be less than 6 characters." ) return await manager.clean_messages() self.bot.db.set_prefix(ctx.guild.id, new_prefix) await manager.send_message("Command prefix has been changed to " + new_prefix) return await manager.clean_messages()
async def seteventdeleterole_error(self, ctx, error): if isinstance(error, commands.MissingRequiredArgument): manager = MessageManager(ctx) event_role = get_event_delete_role(ctx) if not event_role: role_display = '**None** (only Manage Sever members can delete events)' else: role_display = format_role_name(event_role) await manager.send_message( "The current event delete role is: {}\n\n".format( role_display) + "To change the event delete role, use '{}settings seteventdeleterole <role_name>'" .format(ctx.prefix)) await manager.clean_messages()
async def setclass(self, ctx, role): """ Add your Destiny 2 class to the roster Class must be one of Titan, Warlock, or Hunter """ manager = MessageManager(ctx) role = role.lower().title() if role == "Titan" or role == "Warlock" or role == "Hunter": self.bot.db.add_user(ctx.author.id) self.bot.db.update_role(ctx.author.id, role, ctx.guild.id) await manager.send_message("Your class has been updated!") else: await manager.send_message( "Class must be one of: Titan, Hunter, Warlock") await manager.clean_messages()
async def broadcast(self, ctx, *, message): """Send a message to the owner of every server the bot belongs to""" manager = MessageManager(ctx) if ctx.author.id not in constants.OWNERS: return count = 0 for guild in self.bot.guilds: try: await guild.owner.send(message) except: pass else: count += 1 await manager.send_message( "Broadcast message sent to **{}** users".format(count)) await manager.clean_messages()
async def settimezone(self, ctx, *, time_zone): """ Add your timezone to the roster For a full list of supported timezones, check out the bot's support server """ manager = MessageManager(ctx) time_zone = time_zone.upper() time_zone = "".join(time_zone.split()) if time_zone in constants.TIME_ZONES: self.bot.db.add_user(ctx.author.id) self.bot.db.update_timezone(ctx.author.id, time_zone, ctx.guild.id) await manager.send_message("Your time zone has been updated!") else: await manager.send_message( "Unsupported time zone. For a list of supported timezones, " + "check out the bot's support server.") await manager.clean_messages()
async def togglecleanup(self, ctx): """ Toggle command message cleanup on/off (Manage Server only) When enabled, command message spam will be deleted a few seconds after a command has been invoked. This feature is designed to keep bot related spam to a minimum. Only non important messages will be deleted if this is enabled; messages like the help message or the roster, for example, will not be removed. """ manager = MessageManager(ctx) self.bot.db.toggle_cleanup(ctx.guild.id) cleanup_status_text = 'enabled' if cleanup_is_enabled( ctx) else 'disabled' await manager.send_message( "Command message cleanup is now *{}*".format(cleanup_status_text)) return await manager.clean_messages()
async def countdown(self, ctx): """Show time until upcoming Destiny 2 releases""" manager = MessageManager(ctx) pst_now = datetime.now(tz=pytz.timezone('US/Pacific')) text = "" for name, date in constants.RELEASE_DATES: diff = date - pst_now days = diff.days + 1 if days == 0: text += "{}: Today!\n".format(name) elif days == 1: text += "{}: Tomorrow!\n".format(name) elif days > 1: text += "{}: {} days\n".format(name, days) if not text: text = "There are no concrete dates for our next adventure..." countdown = discord.Embed(title="Destiny 2 Countdown", color=constants.BLUE) countdown.description = text await manager.send_embed(countdown) await manager.clean_messages()
async def feedback(self, ctx, *, message): """ Send a message to the bot's developer Ex. '!feedback Your bot is awesome!' This command was adapted from RoboDanny by Rapptz - https://www.github.com/Rapptz/RoboDanny """ manager = MessageManager(ctx) e = discord.Embed(title='Feedback', colour=constants.BLUE) e.set_author(name=str(ctx.author), icon_url=ctx.author.avatar_url) e.description = message e.timestamp = ctx.message.created_at if ctx.guild is not None: e.add_field(name='Server', value='{} (ID: {})'.format(ctx.guild.name, ctx.guild.id), inline=False) e.add_field(name='Channel', value='{} (ID: {})'.format(ctx.channel, ctx.channel.id), inline=False) e.set_footer(text='Author ID: {}'.format(ctx.author.id)) feedback_channel = self.bot.get_channel(359848505654771715) if feedback_channel: await feedback_channel.send(embed=e) else: asal = await self.bot.get_user_info("118926942404608003") await asal.send(embed=e) await manager.send_message( "Your feedback has been sent to the developer!") await manager.clean_messages()
async def setprefix_error(self, ctx, error): if isinstance(error, commands.MissingRequiredArgument): manager = MessageManager(ctx) await manager.send_message("Oops! You didn't provide a new prefix." ) await manager.clean_messages()
async def show(self, ctx): """ Display the roster The roster includes the name, Destiny 2 class, and timezone of server members. Note that only users who have set a role or timezone will be displayed on the roster. """ manager = MessageManager(ctx) roster_groups = [] roster = self.bot.db.get_roster(ctx.guild.id) if len(roster) != 0: text = "```\n" for row in roster: # Add a single entry to the roster message member = ctx.guild.get_member(row.get('user_id')) role = row.get('role') timezone = row.get('timezone') if member: name = member.display_name formatted_name = (name[:16] + '..') if len(name) > 16 else name role = role if role else "---" timezone = timezone if timezone else "---" text += '{:18} {:6} {:7}\n'.format(formatted_name, timezone, role) # If the message is too big, place it into a group if len(text) > 2000: text += "```" roster_groups.append(text) text = "```\n" # Add any remaining entries into a roster group if len(text) > 5: text += "```" roster_groups.append(text) # Send the initial roster message embed_msg = discord.Embed(color=constants.BLUE) embed_msg.title = "{} Roster".format(ctx.guild.name) embed_msg.description = roster_groups[0] await manager.send_embed(embed_msg) # Send additional roster messages if the roster is too long for group in roster_groups[1:]: embed_msg = discord.Embed(color=constants.BLUE) embed_msg.title = "{} Roster (continued)".format( ctx.guild.name) embed_msg.description = group await manager.send_embed(embed_msg) else: await manager.send_message( "No roster exists yet. Use '{}roster settimezone' or '{}roster " .format(ctx.prefix, ctx.prefix) + "setclass' to add the first entry!") await manager.clean_messages()
async def event(self, ctx): """ Create an event in the events channel After invoking the event command, the bot will ask you to enter the event details. Once the event is created, it will appear in the upcoming-events channel. The upcoming-events channel is designed with the assumption that it isn't used for anything but displaying events; non event messages will be deleted. Users will be able to accept and decline the event by adding reactions. If a maximum number of attendees is set and the event is full, additional attendees will be placed in a standby section. If a spot opens up, the user at the top of the standby section will be automatically moved into the event. By default, everyone can make events. However, a minimum role requirement to create events can be defined in the settings. See `help settings seteventrole` for more information. The event creator and those with the Manage Sever permission can delete events by reacting to the event message with \U0001f480. """ manager = MessageManager(ctx) event_role = get_event_role(ctx) member_permissions = ctx.author.permissions_in(ctx.channel) if event_role: if ctx.author.top_role < event_role: event_role_str = format_role_name(event_role) await manager.send_message( "You must be of role **{}** or higher to do that.".format( event_role)) return await manager.clean_messages() await manager.send_message( 'Event creation instructions have been messaged to you') # Title await manager.send_private_message("Enter event title:") res = await manager.get_next_private_message() if not res: return await manager.clean_messages() title = res.content # Description await manager.send_private_message( "Enter event description (type 'none' for no description):") res = await manager.get_next_private_message() if not res: return await manager.clean_messages() if res.content.upper() != 'NONE': description = res.content else: description = "" # Number of attendees max_members = 0 while not max_members: await manager.send_private_message( "Enter the maximum numbers of attendees (type 'none' for no maximum):" ) res = await manager.get_next_private_message() if not res: return await manager.clean_messages() if res.content.upper() == 'NONE': break elif is_int(res.content) and int(res.content) in range(1, 10000): max_members = int(res.content) else: await manager.send_private_message( "Invalid entry. Must be a number between 1 and 9999.") # Start time start_time = None while not start_time: await manager.send_private_message( "Enter event time (YYYY-MM-DD HH:MM AM/PM):") res = await manager.get_next_private_message() if not res: return await manager.clean_messages() start_time_format = '%Y-%m-%d %I:%M %p' try: start_time = datetime.strptime(res.content, start_time_format) except ValueError: await manager.send_private_message("Invalid event time!") # Time zone time_zone = None while not time_zone: await manager.send_private_message( "Enter the time zone (PST, EST, etc):") res = await manager.get_next_private_message() if not res: return await manager.clean_messages() user_timezone = "".join(res.content.upper().split()) if user_timezone not in constants.TIME_ZONES: await manager.send_private_message("Unsupported time zone") else: time_zone = user_timezone affected_rows = self.bot.db.create_event(title, start_time, time_zone, ctx.guild.id, description, max_members, ctx.author.id) if affected_rows == 0: await manager.send_private_message( "An event with that name already exists!") return await manager.clean_messages() event_channel = await self.get_events_channel(ctx.guild) await manager.send_private_message( "Event created! The " + event_channel.mention + " channel will be updated momentarily.") await self.list_events(ctx.guild) await manager.clean_messages()
async def help(self, ctx, str_cmd=None, str_subcmd=None): """Display command information""" manager = MessageManager(ctx) # Determine which prefix to display in the help message if isinstance(ctx.channel, discord.abc.PrivateChannel): prefix = '!' else: # Don't want to display @botname as the prefix in the help message # It's way too long! if ctx.prefix != '<@{}> '.format(self.bot.user.id): prefix = ctx.prefix else: custom_prefix = self.bot.db.get_prefix(ctx.guild.id) if len(custom_prefix): prefix = custom_prefix.get('prefix') else: raise AttributeError("Could not retrieve command prefix") # User passed a command and a subcommand if str_cmd and str_subcmd: cmd = self.bot.get_command(str_cmd) if cmd is None: await manager.send_message( "There are no commands called '{}'".format(str_cmd)) return await manager.clean_messages() # Check for subcommand if hasattr(cmd, 'commands'): for sub_cmd in cmd.commands: if sub_cmd.name == str_subcmd: help_embed = self.help_embed_single(prefix, sub_cmd) await manager.send_embed(help_embed) break else: await manager.send_message( "'{}' doesn't have a subcommand called '{}'".format( str_cmd, str_subcmd)) else: await manager.send_message( "'{}' does not have any subcommands".format(str_cmd)) # User passed in a single command elif str_cmd: cmd = self.bot.get_command(str_cmd) if cmd is None: await manager.send_message( "There are no commands called '{}'".format(str_cmd)) return await manager.clean_messages() # Check if command has subcommands if hasattr(cmd, 'commands'): sub_cmds = [] for sub_cmd in cmd.commands: sub_cmds.append(sub_cmd) help_embed = self.help_embed_group(prefix, cmd, sub_cmds) else: help_embed = self.help_embed_single(prefix, cmd) await manager.send_embed(help_embed) # No command passed, print help for all commands else: help_embed = self.help_embed_all(prefix, self.bot.commands) await manager.send_embed(help_embed) await manager.clean_messages()
async def pvp(self, ctx, username=None, platform=None): """Display PvP stats across all characters on an account In order to use this command for your own account, you must first register your Destiny 2 account with the bot via the register command. `stats pvp` - Display your PvP stats (preferred platform) \$`stats pvp Asal#1502 bnet` - Display Asal's PvP stats on Battle.net \$`stats pvp @user` - Display a registered user's PvP stats (preferred platform) \$`stats pvp @user bnet` - Display a registered user's PvP stats on Battle.net """ manager = MessageManager(ctx) await ctx.channel.trigger_typing() # Get membership details. This depends on whether or not a platform or username were given. membership_details = await helpers.get_membership_details(self.bot, ctx, username, platform) # If there was an error getting membership details, display it if isinstance(membership_details, str): await manager.send_message(membership_details) return await manager.clean_messages() else: platform_id, membership_id, display_name = membership_details # Get PvP stats try: res = await self.bot.destiny.api.get_historical_stats(platform_id, membership_id, groups=['general'], modes=[5]) except: await manager.send_message("Sorry, I can't seem to retrieve those stats right now") return await manager.clean_messages() if res['ErrorCode'] != 1: await manager.send_message("Sorry, I can't seem to retrieve those stats right now") return await manager.clean_messages() pvp_stats = res['Response']['allPvP'].get('allTime') if not pvp_stats: await manager.send_message("Sorry, I can't seem to retrieve those stats right now") return await manager.clean_messages() time_played = pvp_stats['secondsPlayed']['basic']['displayValue'] kdr = pvp_stats['killsDeathsRatio']['basic']['displayValue'] best_weapon = pvp_stats['weaponBestType']['basic']['displayValue'] games_played = pvp_stats['activitiesEntered']['basic']['displayValue'] best_kills = pvp_stats['bestSingleGameKills']['basic']['displayValue'] best_spree = pvp_stats['longestKillSpree']['basic']['displayValue'] combat_rating = pvp_stats['combatRating']['basic']['displayValue'] kills = pvp_stats['kills']['basic']['displayValue'] assists = pvp_stats['assists']['basic']['displayValue'] deaths = pvp_stats['deaths']['basic']['displayValue'] kda = str(round((int(kills) + int(assists)) / int(deaths), 2)) # Can't convert a string of '-' to a float! win_ratio = pvp_stats['winLossRatio']['basic']['displayValue'] if win_ratio != '-': win_ratio = float(win_ratio) win_rate = str(round(win_ratio / (win_ratio + 1) * 100, 1)) + "%" else: win_rate = win_ratio e = discord.Embed(colour=constants.BLUE) e.set_author(name="{} | Crucible Stats".format(display_name), icon_url=constants.PLATFORM_URLS.get(platform_id)) e.add_field(name='Kills', value=kills, inline=True) e.add_field(name='Assists', value=assists, inline=True) e.add_field(name='Deaths', value=deaths, inline=True) e.add_field(name='KD', value=kdr, inline=True) e.add_field(name='KA/D', value=kda, inline=True) e.add_field(name='Win Rate', value=win_rate, inline=True) e.add_field(name='Best Spree', value=best_spree, inline=True) e.add_field(name='Most Kills in a Game', value=best_kills, inline=True) e.add_field(name='Favorite Weapon', value=best_weapon, inline=True) e.add_field(name='Combat Rating', value=combat_rating, inline=True) e.add_field(name='Games Played', value=games_played, inline=True) e.add_field(name='Time Played', value=time_played, inline=True) await manager.send_embed(e) await manager.clean_messages()
async def trials(self, ctx, username=None, platform=None): """Display Trials stats across all characters on an account In order to use this command for your own account, you must first register your Destiny 2 account with the bot via the register command. `stats trials` - Display your Trials stats (preferred platform) \$`stats trials Asal#1502 bnet` - Display Asal's Trials stats on Battle.net \$`stats trials @user` - Display a registered user's Trials stats (preferred platform) \$`stats trials @user bnet` - Display a registered user's Trials stats on Battle.net """ manager = MessageManager(ctx) await ctx.channel.trigger_typing() membership_details = await helpers.get_membership_details(self.bot, ctx, username, platform) if isinstance(membership_details, str): await manager.send_message(membership_details) return await manager.clean_messages() platform_id, membership_id, display_name = membership_details try: res = await self.bot.destiny.api.get_historical_stats(platform_id, membership_id, groups=['general'], modes=[39]) if res['ErrorCode'] != 1: await manager.send_message("Sorry, I can't seem to retrieve those stats right now") return await manager.clean_messages() trials_stats = res['Response']['trialsofthenine'].get('allTime') #| time played | KDR | best weapon | games played | most kills in sg | longest spree | combar rating | kills | assists | deaths | kda time_played = trials_stats['secondsPlayed']['basic']['displayValue'] kdr = trials_stats['killsDeathsRatio']['basic']['displayValue'] best_weapon = trials_stats['weaponBestType']['basic']['displayValue'] games_played = trials_stats['activitiesEntered']['basic']['displayValue'] best_kills = trials_stats['bestSingleGameKills']['basic']['displayValue'] best_spree = trials_stats['longestKillSpree']['basic']['displayValue'] combat_rating = trials_stats['combatRating']['basic']['displayValue'] kills = trials_stats['kills']['basic']['displayValue'] assists = trials_stats['assists']['basic']['displayValue'] deaths = trials_stats['deaths']['basic']['displayValue'] kda = str(round((int(kills) + int(assists)) /int(deaths), 2)) win_ratio = trials_stats['winLossRatio']['basic']['displayValue'] if win_ratio != '-': win_ratio = float(win_ratio) win_rate = str(round(win_ratio / (win_ratio + 1) * 100, 1)) + "%" else: win_rate = win_ratio e = discord.Embed(color=constants.BLUE) e.set_author(name="{} | Trials of the Nine stats".format(display_name), icon_url=constants.PLATFORM_URLS.get(platform_id)) e.add_field(name='Kills', value=kills, inline=True) e.add_field(name='Assists', value=assists, inline=True) e.add_field(name='Deaths', value=deaths, inline=True) e.add_field(name='KD', value=kdr, inline=True) e.add_field(name='KA/D', value=kda, inline=True) e.add_field(name='Win Rate', value=win_rate, inline=True) e.add_field(name='Best Spree', value=best_spree, inline=True) e.add_field(name='Most kills in a Game', value=best_kills, inline=True) e.add_field(name='Favorite Weapon', value=best_weapon, inline=True) e.add_field(name='Combat Rating', value=combat_rating, inline=True) e.add_field(name='Games Played', value=games_played, inline=True) e.add_field(name='Time Played', value=time_played, inline=True) await manager.send_embed(e) await manager.clean_messages() except: await manager.send_message("Sorry, I can't seem to retrieve those stats right now") return await manager.clean_messages()
async def pve(self, ctx, username=None, platform=None): """Display PvE stats across all characters on an account In order to use this command for your own account, you must first register your Destiny 2 account with the bot via the register command. `stats pve` - Display your PvE stats (preferred platform) \$`stats pve Asal#1502 bnet` - Display Asal's PvE stats on Battle.net \$`stats pve @user` - Display a registered user's PvE stats (preferred platform) \$`stats pve @user bnet` - Display a registered user's PvE stats on Battle.net """ manager = MessageManager(ctx) await ctx.channel.trigger_typing() # Get membership details. This depends on whether or not a platform or username were given. membership_details = await helpers.get_membership_details(self.bot, ctx, username, platform) # If there was an error getting membership details, display it if isinstance(membership_details, str): await manager.send_message(membership_details) return await manager.clean_messages() else: platform_id, membership_id, display_name = membership_details # Get PvE stats try: res = await self.bot.destiny.api.get_historical_stats(platform_id, membership_id, groups=['general'], modes=[7,4,16,18]) except pydest.PydestException as e: await manager.send_message("Sorry, I can't seem to retrieve those stats right now") return await manager.clean_messages() if res['ErrorCode'] != 1: await manager.send_message("Sorry, I can't seem to retrieve those stats right now") return await manager.clean_messages() pve_stats = res['Response'] if not pve_stats: await manager.send_message("Sorry, I can't seem to retrieve those stats right now") return await manager.clean_messages() time_played = pve_stats['allPvE']['allTime']['totalActivityDurationSeconds']['basic']['displayValue'] if len(pve_stats['allPvE']) else 0 best_weapon = pve_stats['allPvE']['allTime']['weaponBestType']['basic']['displayValue'] if len(pve_stats['allPvE']) else 0 num_heroic_events = pve_stats['allPvE']['allTime']['heroicPublicEventsCompleted']['basic']['displayValue'] if len(pve_stats['allPvE']) else 0 num_events = pve_stats['allPvE']['allTime']['publicEventsCompleted']['basic']['displayValue'] if len(pve_stats['allPvE']) else 0 num_raids = pve_stats['raid']['allTime']['activitiesCleared']['basic']['displayValue'] if len(pve_stats['raid']) else 0 raid_time = pve_stats['raid']['allTime']['totalActivityDurationSeconds']['basic']['displayValue'] if len(pve_stats['raid']) else 0 num_nightfall = pve_stats['nightfall']['allTime']['activitiesCleared']['basic']['displayValue'] if len(pve_stats['nightfall']) else 0 num_strikes = pve_stats['allStrikes']['allTime']['activitiesCleared']['basic']['displayValue'] if len(pve_stats['allStrikes']) else 0 fastest_nightfall = pve_stats['nightfall']['allTime']['fastestCompletionMs']['basic']['displayValue'] if len(pve_stats['nightfall']) else 0 kills = pve_stats['allPvE']['allTime']['kills']['basic']['displayValue'] if len(pve_stats['allPvE']) else 0 assists = pve_stats['allPvE']['allTime']['assists']['basic']['displayValue'] if len(pve_stats['allPvE']) else 0 deaths = pve_stats['allPvE']['allTime']['deaths']['basic']['displayValue'] if len(pve_stats['allPvE']) else 0 e = discord.Embed(colour=constants.BLUE) e.set_author(name="{} | PvE Stats".format(display_name), icon_url=constants.PLATFORM_URLS.get(platform_id)) e.add_field(name='Kills', value=kills, inline=True) e.add_field(name='Assists', value=assists, inline=True) e.add_field(name='Deaths', value=deaths, inline=True) e.add_field(name='Strikes', value=num_strikes, inline=True) e.add_field(name='Nightfalls', value=num_nightfall, inline=True) e.add_field(name='Fastest Nightfall', value=fastest_nightfall, inline=True) e.add_field(name='Public Events', value=num_events, inline=True) e.add_field(name='Heroic Public Events', value=num_heroic_events, inline=True) e.add_field(name='Favorite Weapon', value=best_weapon, inline=True) e.add_field(name='Total Raid Time', value=raid_time, inline=True) e.add_field(name='Raids', value=num_raids, inline=True) e.add_field(name='Time Played', value=time_played, inline=True) await manager.send_embed(e) await manager.clean_messages()
async def settimezone_error(self, ctx, error): if isinstance(error, commands.MissingRequiredArgument): manager = MessageManager(ctx) await manager.send_message( "Oops! You didn't include your timezone.") await manager.clean_messages()
async def register(self, ctx): """Register your Destiny 2 account with the bot This command will let the bot know which Destiny 2 profile to associate with your Discord profile. Registering is a prerequisite to using any commands that require knowledge of your Destiny 2 profile. """ manager = MessageManager(ctx) template = "https://www.bungie.net/en/OAuth/Authorize?client_id={}&response_type=code&state={}" auth_url = template.format(self.bot.bungie_client_id, ctx.author.id) bliz_name, xbox_name, psn_name, bliz_id, xbox_id, psn_id = (None, ) * 6 if not isinstance(ctx.channel, discord.abc.PrivateChannel): await manager.send_message( "Registration instructions have been messaged to you.") # Prompt user with link to Bungie.net OAuth authentication e = discord.Embed(colour=constants.BLUE) e.title = "Click Here to Register" e.url = auth_url e.description = ( "Click the above link to register your Bungie.net account with Spirit. " + "Registering will allow Spirit to access your connected Destiny " + "2 accounts.") registration_msg = await manager.send_private_embed(e) # Wait for user info from the web server via Redis res = await self.redis.subscribe(ctx.author.id) tsk = asyncio.ensure_future(self.wait_for_msg(res[0])) try: user_info = await asyncio.wait_for(tsk, timeout=120) except asyncio.TimeoutError: await manager.send_private_message( "I'm not sure where you went. We can try this again later.") await registration_msg.delete() return await manager.clean_messages() await ctx.author.dm_channel.trigger_typing() # Save OAuth credentials and bungie ID bungie_id = user_info.get('membership_id') access_token = user_info.get('access_token') refresh_token = user_info.get('refresh_token') self.bot.db.update_registration(bungie_id, access_token, refresh_token, ctx.author.id) # Fetch platform specific display names and membership IDs try: res = await self.bot.destiny.api.get_membership_data_by_id( bungie_id) except: await manager.send_private_message( "I can't seem to connect to Bungie right now. Try again later." ) await registration_msg.delete() return await manager.clean_messages() if res['ErrorCode'] != 1: await manager.send_private_message( "Oops, something went wrong during registration. Please try again." ) await registration_msg.delete() return await manager.clean_messages() if not self.user_has_connected_accounts(res): await manager.send_private_message( "Oops, you don't have any public accounts attached to your Bungie.net profile." ) await registration_msg.delete() return await manager.clean_messages() for entry in res['Response']['destinyMemberships']: if entry['membershipType'] == 4: bliz_name = entry['displayName'] bliz_id = entry['membershipId'] elif entry['membershipType'] == 1: xbox_name = entry['displayName'] xbox_id = entry['membershipId'] elif entry['membershipType'] == 2: psn_name = entry['displayName'] psn_id = entry['membershipId'] bungie_name = res['Response']['bungieNetUser']['displayName'] self.bot.db.update_display_names(ctx.author.id, bungie_name, bliz_name, xbox_name, psn_name) self.bot.db.update_membership_ids(ctx.author.id, bliz_id, xbox_id, psn_id) # Get references to platform emojis from Spirit Support server platform_reactions = [] if bliz_name: platform_reactions.append(self.bot.get_emoji(constants.BNET_ICON)) if xbox_name: platform_reactions.append(self.bot.get_emoji(constants.XBOX_ICON)) if psn_name: platform_reactions.append(self.bot.get_emoji(constants.PS_ICON)) # Display message with prompts to select a preferred platform e = self.registered_embed(bungie_name, bliz_name, xbox_name, psn_name) platform_msg = await manager.send_private_embed(e) await registration_msg.delete() # If only one account is connected, set it as preferred (don't display reactions) platform_names = (bliz_name, xbox_name, psn_name) if self.num_non_null_entries(platform_names) == 1: if bliz_name: platform_id = 4 elif xbox_name: platform_id = 1 else: platform_id = 2 self.bot.db.update_platform(ctx.author.id, platform_id) return await manager.clean_messages() func = self.add_reactions(platform_msg, platform_reactions) self.bot.loop.create_task(func) def check_reaction(reaction, user): if reaction.message.id == platform_msg.id and user == ctx.author: for emoji in platform_reactions: if reaction.emoji == emoji: return True # Wait for platform reaction from user try: reaction, user = await self.bot.wait_for('reaction_add', timeout=120.0, check=check_reaction) except asyncio.TimeoutError: await platform_msg.delete() await manager.send_private_message( "I'm not sure where you went. We can try this again later.") return await manager.clean_messages() # Save preferred platform platform = constants.PLATFORMS.get(reaction.emoji.name) self.bot.db.update_platform(ctx.author.id, platform) # Update message with preferred platform e = self.registered_embed(bungie_name, bliz_name, xbox_name, psn_name, footer=True, platform=platform) await platform_msg.edit(embed=e) return await manager.clean_messages()
async def feedback_error(self, ctx, error): if isinstance(error, commands.MissingRequiredArgument): manager = MessageManager(ctx) await manager.send_message("You forgot to include your feedback!") await manager.clean_messages()
async def item(self, ctx, *, search_term): """Search for a Destiny 2 item This command searches for and displays Destiny 2 items. The results are limited to weapons and armour. If multiple results are found, they will be displayed in a paginated format. The user who invoked the command will be able to page through the results. Note: The search term must contain a minimum of three characters. """ manager = MessageManager(ctx) paginator = Paginator(self.bot, ctx) await ctx.channel.trigger_typing() try: res = await self.bot.destiny.api.search_destiny_entities( 'DestinyInventoryItemDefinition', search_term) except pydest.PydestException as e: await manager.send_message( "Sorry, I can't seem to search for items right now.") return await manager.clean_messages() except ValueError as f: await manager.send_message( "Your search term contains unsupported characters.") return await manager.clean_messages() if res['ErrorCode'] != 1: await manager.send_message( "Sorry, I can't seem to search for items right now") return await manager.clean_messages() # Check how many results were found - we need at least one num_results = res['Response']['results']['totalResults'] if num_results == 0: await manager.send_message( "I didn't find any items that match your search.") return await manager.clean_messages() # Iterate through each result, and add them to the paginator if valid type for i, entry in enumerate(res['Response']['results']['results']): item_hash = entry['hash'] item = await self.bot.destiny.decode_hash( item_hash, 'DestinyInventoryItemDefinition') # If item isn't a weapon or armor piece, skip to the next one if item['itemType'] not in (2, 3): continue e = discord.Embed() e.set_author(name=item['displayProperties']['name']) e.description = "*{}*".format( item['displayProperties']['description']) e.set_thumbnail(url=BASE_URL + item['displayProperties']['icon']) # Set embed color based on item rarity item_rarity = int(item['inventory']['tierType']) if item_rarity == 2: e.color = discord.Color.light_grey() elif item_rarity == 3: e.color = discord.Color.dark_green() elif item_rarity == 4: e.color = discord.Color.blue() elif item_rarity == 5: e.color = discord.Color.dark_purple() elif item_rarity == 6: e.color = discord.Color.gold() else: e.color = constants.BLUE # Add armor/weapon specific information if item['itemType'] == 3: e = self.embed_weapon(e, item) e = await self.embed_perks(e, item, 4241085061) else: e = self.embed_armor(e, item) e = await self.embed_perks(e, item, 2518356196) paginator.add_embed(e) if not paginator.length: await manager.send_message( "I didn't find any items that match your search.") return await manager.clean_messages() func = manager.clean_messages() self.bot.loop.create_task(func) await paginator.paginate()
async def loadout(self, ctx, username=None, platform=None): """Display a Guardian's loadout In order to use this command for your own Guardian, you must first register your Destiny 2 account with the bot via the register command. `loadout` - Display your Guardian's loadout (preferred platform) \$`loadout Asal#1502 bnet` - Display Asal's Guardian's loadout on Battle.net \$`loadout @user` - Display a registered user's Guardian (preferred platform) \$`loadout @user bnet` - Display a registered user's Guardian on Battle.net """ manager = MessageManager(ctx) await ctx.channel.trigger_typing() # Get membership details. This depends on whether or not a platform or username were given. membership_details = await helpers.get_membership_details( self.bot, ctx, username, platform) # If there was an error getting membership details, display it if isinstance(membership_details, str): await manager.send_message(membership_details) return await manager.clean_messages() else: platform_id, membership_id, _ = membership_details # Attempt to fetch character information from Bungie.net try: res = await self.bot.destiny.api.get_profile( platform_id, membership_id, ['characters', 'characterEquipment', 'profiles']) except pydest.PydestException as e: await manager.send_message( "Sorry, I can't seem to retrieve that Guardian right now.") return await manager.clean_messages() if res['ErrorCode'] != 1: await manager.send_message( "Sorry, I can't seem to retrieve that Guardian right now.") return await manager.clean_messages() # Determine which character was last played chars_last_played = [] for character_id in res['Response']['characters']['data']: last_played_str = res['Response']['characters']['data'][ character_id]['dateLastPlayed'] date_format = '%Y-%m-%dT%H:%M:%SZ' last_played = datetime.strptime(last_played_str, date_format) chars_last_played.append((character_id, last_played)) last_played_char_id = max(chars_last_played, key=lambda t: t[1])[0] last_played_char = res['Response']['characters']['data'].get( last_played_char_id) ####################################### # ------ Decode Character Info ------ # ####################################### role_dict = await self.bot.destiny.decode_hash( last_played_char['classHash'], 'DestinyClassDefinition') role = role_dict['displayProperties']['name'] gender_dict = await self.bot.destiny.decode_hash( last_played_char['genderHash'], 'DestinyGenderDefinition') gender = gender_dict['displayProperties']['name'] race_dict = await self.bot.destiny.decode_hash( last_played_char['raceHash'], 'DestinyRaceDefinition') race = race_dict['displayProperties']['name'] char_name = res['Response']['profile']['data']['userInfo'][ 'displayName'] level = last_played_char['levelProgression']['level'] light = last_played_char['light'] emblem_url = 'https://www.bungie.net' + last_played_char['emblemPath'] stats = [] for stat_hash in ('2996146975', '392767087', '1943323491'): stat_dict = await self.bot.destiny.decode_hash( stat_hash, 'DestinyStatDefinition') stat_name = stat_dict['displayProperties']['name'] if stat_hash in last_played_char['stats'].keys(): stats.append( (stat_name, last_played_char['stats'].get(stat_hash))) else: stats.append((stat_name, 0)) ####################################### # ------ Decode Equipment Info ------ # ####################################### weapons = [['Kinetic', '-'], ['Energy', '-'], ['Power', '-']] weapons_index = 0 armor = [['Helmet', '-'], ['Gauntlets', '-'], ['Chest', '-'], ['Legs', '-'], ['Class Item', '-']] armor_index = 0 equipped_items = res['Response']['characterEquipment']['data'][ last_played_char_id]['items'] for item in equipped_items: item_dict = await self.bot.destiny.decode_hash( item['itemHash'], 'DestinyInventoryItemDefinition') item_name = "{}".format(item_dict['displayProperties']['name']) if weapons_index < 3: weapons[weapons_index][1] = item_name weapons_index += 1 elif armor_index < 5: armor[armor_index][1] = item_name armor_index += 1 ################################# # ------ Formulate Embed ------ # ################################# char_info = "Level {} {} {} {} |\N{SMALL BLUE DIAMOND}{}\n".format( level, race, gender, role, light) char_info += "{} {} • ".format(stats[0][1], stats[0][0]) char_info += "{} {} • ".format(stats[1][1], stats[1][0]) char_info += "{} {}".format(stats[2][1], stats[2][0]) weapons_info = "" for weapon in weapons: weapons_info += '**{}:** {} \n'.format(weapon[0], weapon[1]) armor_info = "" for item in armor: armor_info += '**{}:** {}\n'.format(item[0], item[1]) e = discord.Embed(colour=constants.BLUE) e.set_author(name=char_name, icon_url=constants.PLATFORM_URLS.get(platform_id)) e.description = char_info e.set_thumbnail(url=emblem_url) e.add_field(name='Weapons', value=weapons_info, inline=True) e.add_field(name='Armor', value=armor_info, inline=True) await manager.send_embed(e) await manager.clean_messages()
async def broadcast_error(self, ctx, error): manager = MessageManager(ctx) await manager.send_message("You didn't include a broadcast message") return await manager.clean_messages()
async def item_error(self, ctx, error): if isinstance(error, commands.MissingRequiredArgument): manager = MessageManager(ctx) await manager.send_message( "Oops! You didn't specify a search term.") await manager.clean_messages()