Source code for scripts.commands.normalcommands

"""
PokeGambler - A Pokemon themed gambling bot for Discord.
Copyright (C) 2021 Harshith Thota

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.

You should have received a copy of the GNU Affero General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.
----------------------------------------------------------------------------

Normal Commands Module
"""

# pylint: disable=unused-argument

from __future__ import annotations

import re
from typing import TYPE_CHECKING, Callable, Optional

import discord

from ..base.models import CommandData, Profiles, Votes
from ..base.views import LinkView, MorphView
from ..helpers.parsers import CustomRstParser
from ..helpers.utils import (
    get_commands, get_embed, get_modules,
    dedent, get_modules_from_path, showable_command
)
from .basecommand import Commands, alias, ctx_command, model, override_docs

if TYPE_CHECKING:
    from discord import Message


[docs]class NormalCommands(Commands): """ Public/Normal commands for PokeGambler. """
[docs] @ctx_command @alias("cmds") @override_docs( lambda docs: docs.replace( "%MODULES%", get_modules_from_path( __file__, ["Profile", "Gamble", "Duel", "Normal", "Trade"] ) ) ) async def cmd_commands( self, message: Message, module: Optional[str] = None, role: Optional[discord.Role] = None, **kwargs ): """ :param message: The messag which triggered this command. :type message: :class:`discord.Message` :param module: The module to show commands for. :type module: Optional[str] :choices module: %MODULES% :param role: The role to show commands for. (None/Admin/Owner) :type role: Optional[:class:`discord.Role`] .. meta:: :description: Lists all usable commands for the user. :aliases: cmds .. rubric:: Syntax .. code:: coffee /commands [role:Role] [module:name] .. rubric:: Description Lists out all the commands you could use. .. note:: Admins and Dealers get access to special hidden commands .. rubric:: Examples * To check the commands list .. code:: coffee :force: /commands * To check the commands specific to admin .. code:: coffee :force: /commands role:admin * To check only profile commands .. code:: coffee :force: /commands module:Profile """ modules = get_modules(self.ctx) if module: modules = [ mod for mod in modules if mod.__class__.__name__.lower().startswith( module.lower() ) ] command_dict = { module.__class__.__name__.replace( "Commands", " Commands" ): get_commands( self.ctx, message.author, module, [str(role) if role else None] ) for module in modules } if set(command_dict.values()) == {''}: await message.reply( embed=get_embed( "Did not find any commands for the given query.", embed_type="warning", title="No Commands found" ) ) return embed = get_embed( "Use `/help command:name` for details", title="PokeGambler Commands List" ) embed_all = embed.copy() info_dict = {} for key, val in command_dict.items(): if val: if not module: emb_new = embed.copy() emb_new.add_field( name=key, value=f"**```fix\n{val}\n```**" ) info_dict[key] = emb_new embed_all.add_field( name=key, value=f"**```fix\n{val}\n```**" ) if not module: info_dict["All Commands"] = embed_all morpher = MorphView(info_dict=info_dict) await message.reply(embed=embed, view=morpher) self.ctx.loop.create_task(morpher.dispatch(module=self)) else: await message.reply(embed=embed_all)
[docs] @alias("?") async def cmd_help( self, message: Message, command: Optional[str] = None, **kwargs ): """ :param message: The messag which triggered this command. :type message: :class:`discord.Message` :param command: The command to get help for. :type command: Optional[str] .. meta:: :description: What did you expect? This is just the Help command. :aliases: ? .. rubric:: Syntax .. code:: coffee /help [command:name] .. rubric:: Description Displays the help embed. If a command is specified, it prints a help message for that command. Otherwise, it lists the available commands. .. rubric:: Examples * To view help for a the profile command .. code:: coffee :force: /help command:profile * To view help for all the commands .. code:: coffee :force: /help """ modules = get_modules(self.ctx) commands = list( { getattr(module, attr) for module in modules for attr in dir(module) if all( [ module.enabled, attr.startswith("cmd_"), showable_command( self.ctx, getattr(module, attr), message.author ) ] ) } ) if command: commands = [ cmd for cmd in commands if any([ cmd.__name__ == f"cmd_{command.lower()}", command.lower() in getattr(cmd, "alias", []) ]) ] if not commands: await message.reply( embed=get_embed( f"There's no command called **{command.title()}**\n" "Or you don't have access to it.", embed_type="error" ) ) return embeds = [] for cmd in commands: emb = self.__help_generate_embed( cmd, keep_footer=(command is not None) ) embeds.append(emb) embeds.sort(key=lambda x: x.title) await self.paginate( message, embeds, content="**PokeGambler Commands List:**" )
[docs] @model([Profiles, CommandData]) async def cmd_info(self, message: Message, **kwargs): """ :param message: The messag which triggered this command. :type message: :class:`discord.Message` .. meta:: :description: Gives info about PokeGambler. .. rubric:: Syntax .. code:: coffee /info .. rubric:: Description Gives new players information about PokeGambler. """ emb1 = self.__info_embed_one() emb2 = self.__info_embed_two() embeds = [emb1, emb2] await self.paginate(message, embeds)
[docs] @ctx_command async def cmd_invite(self, message: Message, **kwargs): """ :param message: The messag which triggered this command. :type message: :class:`discord.Message` .. meta:: :description: Invite PokeGambler to other servers. .. rubric:: Syntax .. code:: coffee /invite .. rubric:: Description Get the Invite link for PokeGambler. """ pg_den = self.ctx.get_guild(self.ctx.official_server) inv_emb = get_embed( dedent( f"""```md # Want to add me to your own server? [Invite me](By clicking the button below). * Following features are only allowed in {pg_den}: * Gamble Matches * Buying Titles * Cross Trades ```""" ), title="Invite Link", image="https://cdn.discordapp.com/attachments/" "874623706339618827/874628993939308554/pg_banner.png" ) invite_view = LinkView( emoji="<:pokegambler:844321894488342559>", url="https://discordapp.com/oauth2/authorize?client_id=" f"{self.ctx.user.id}&scope=bot%20applications.commands" "&permissions=543179533392" ) await message.reply( embed=inv_emb, view=invite_view )
[docs] @ctx_command @alias('latency') async def cmd_ping(self, message: Message, **kwargs): """ :param message: The messag which triggered this command. :type message: :class:`discord.Message` .. meta:: :description: PokeGambler Latency .. rubric:: Syntax .. code:: coffee /ping .. rubric:: Description Checks the current latency of PokeGambler. """ ping = round(self.ctx.latency * 1000, 2) await message.reply( embed=get_embed( f"**{ping}** ms", title="Current Latency" ) )
def __help_generate_embed( self, cmd: Callable, keep_footer: bool = False ): got_doc = False meta = {} aliases = "" if cmd.__doc__: if getattr(cmd, "disabled", False): cmd_name = cmd.__name__.replace('cmd_', '').title() return get_embed( f"**{cmd_name}** is under maintainence.\n" "Details unavailable, so wait for updates.", embed_type="warning", title="Command under Maintainence." ) got_doc = True doc_str = cmd.__doc__.replace( "{pokechip_emoji}", self.chip_emoji ) with CustomRstParser() as rst_parser: rst_parser.parse(doc_str) aliases = rst_parser.meta.aliases meta = { section.argument: dedent(section.to_string()) for section in rst_parser.sections } emb = discord.Embed( title=cmd.__name__.replace("cmd_", "").title(), description='\u200B', color=11068923 ) else: emb = get_embed( "No help message exists for this command.", embed_type="warning", title="No documentation found." ) for key, val in meta.items(): if val: val = val.replace(" ", " ") val = re.sub( r'\\\|\\\|\~([^\\]+)\\\|\\\|', lambda x: x[1].split('.')[-1], '\n'.join( m.lstrip() for m in val.split('\n') ) ) emb.add_field(name=f"**{key}**", value=val, inline=False) if all([ got_doc, aliases ]): alt_names = aliases.split(', ') alias_str = ', '.join(sorted(alt_names, key=len)) emb.add_field( name="**Alias**", value=f"```\n{alias_str}\n```" ) if keep_footer and got_doc: emb.set_footer( text="This command helped? You can help me too " "by donating at https://www.paypal.me/hyperclaw79.", icon_url="https://emojipedia-us.s3.dualstack.us-west-1." "amazonaws.com/thumbs/160/facebook/105/money-bag_1f4b0.png" ) return emb def __info_embed_one(self): emb = get_embed( dedent( """ ``` 𝙋𝙤𝙠𝙚𝙂𝙖𝙢𝙗𝙡𝙚𝙧 uses pokemon themed playing cards for entertaining gambling matches. It has a dedicated currency and profile system. Earned Pokechips may be cross-traded. ``` """ ), title="**Welcome to PokeGambler**", image="https://cdn.discordapp.com/attachments/" "874623706339618827/874628993939308554/pg_banner.png" ) emb.add_field( name="**Getting Started**", value=dedent( """ ```diff You can create a new profile using: /profile Every players gets free 100 Pokechips For a list of commands you can access: /commands For usage guide of these commands: /help 🛈 Check the help for every command before using it. 🛈 Also keep an eye out for sudden gambling matches. ``` """ ), inline=False ) server_owner = self.ctx.get_guild( self.ctx.official_server ).owner emb.add_field( name="**Owners**", value=dedent( f""" ```yaml Bot Owner: {self.ctx.owner} Official Server Owner: {server_owner} ``` """ ), inline=False ) return emb def __info_embed_two(self): emb = get_embed( title="**Welcome to PokeGambler**", content="\u200B", image="https://cdn.discordapp.com/attachments/" "874623706339618827/874628993939308554/pg_banner.png" ) official_server = self.ctx.get_guild(self.ctx.official_server) emb.add_field( name="**Official Server**", value="Come hang out with us in " f"[『{official_server}』](https://discord.gg/g4TmVyfwj4).", inline=False ) emb.add_field( name="**Stats**", value=f"```yaml\n{self.__info_get_stats()}\n```", inline=False ) return emb def __info_get_stats(self): """Get the stats of the bot.""" handlers = { "Latency": lambda: f"{round(self.ctx.latency * 1000, 2)} ms", # pylint: disable=no-member "Total Users": Profiles.mongo.estimated_document_count, "Total Servers": lambda: len(self.ctx.guilds), "Most Active User": self.__info_most_active_user, "Most Voted By": lambda: self.__info_most_active_user(mode="vote"), "Most Active Channel": self.__info_most_active_channel, "Most Used Command": self.__info_most_used_command } stats = {} for key, func in handlers.items(): res = func() if res is not None: stats[key] = res stats = "\n".join( f"{key}: {val}" for key, val in stats.items() ) return stats.encode('utf-8').decode('utf-8') @staticmethod def __info_most_active_channel(): top_channel = CommandData.most_active_channel() if top_channel is None: return None channel = top_channel['name'] guild = top_channel['guild']['name'] return f"{channel} ({guild})" def __info_most_active_user(self, mode: str = "command"): if mode == "vote": top_user = Votes.most_active_voter() metric = "total_votes" else: top_user = CommandData.most_active_user() metric = "num_cmds" if top_user is None: return None user = self.ctx.get_user(int(top_user['_id'])) return f"{user} ({top_user[metric]})" def __info_most_used_command(self): top_command = CommandData.most_used_command() if top_command is None: return None command = top_command['_id'] if modules := [ module for module in get_modules(self.ctx) if f"cmd_{command}" in module.alias ]: command = getattr( modules[0], f"cmd_{command}" ).__name__.replace("cmd_", "") return f"{command} ({top_command['num_cmds']})"