class BuyCommand(ShopCommand): name = "buy" help = "Buys an item from the shop." aliases = [] examples = ["1"] argument_spec = ArgumentSpec([ShopItemArgument], False) clean = True admin = False ignore = False async def _run(self): item = self.args["item"] inv = database.get_inv(self.server.id, self.author.id)[self.author.id] if inv >= item.cost: role = discord.get_role(self.message.guild, item.item) user = self.message.author if role in user.roles: await self.send("You already have that role!") else: database.set_inv(self.server.id, self.author.id, -item.cost, update=True) await discord.apply_role(self.message.author, role) database.set_stats_shop(self.server.id) await self.send(f"You have bought the {role.mention} role!") else: await self.send(f"You need {item.cost.line_str} to buy this item!")
class AdminsCommand(SettingsCommand): name = "admins" help = "Adds or removes CandyBot admins or shows admins" aliases = [] examples = ["", "@User", "User#1234", "123456789"] argument_spec = ArgumentSpec([UserArgument], True) clean = True ignore = False # TODO: Ask for confirmation async def _run(self): user = self.args.get("user") admins = database.get_admins(self.server.id) if user is None: self.title = ":lifter: CandyBot Admins" admins = [(await converters.to_user(str(x), self.message.guild)).mention for x in admins] await self.send("\n".join(admins) if admins else "All") else: if user.id in admins: database.set_admin(self.server.id, user.id, remove=True) await self.send( f"{user.mention} was removed as a CandyBot admin") else: database.set_admin(self.server.id, user.id, remove=False) await self.send(f"{user.mention} was added as a CandyBot admin" )
class ChannelsCommand(SettingsCommand): name = "channels" help = "Adds or removes CandyBot channels or shows enabled channels" aliases = [] examples = ["", "#channel"] argument_spec = ArgumentSpec([ChannelArgument], True) clean = True ignore = False # TODO: Ask for confirmation async def _run(self): channel = self.args.get("channel") channels = database.get_channels(self.server.id) if channel is None: self.title = ":hash: CandyBot Channels" channels = [(await converters.to_channel(str(x), self.message.guild)).mention for x in channels] await self.send("\n".join(channels) if channels else "All") else: if channel.id in channels: database.set_channel(self.server.id, channel.id, remove=True) await self.send( f"{channel.mention} was removed as a CandyBot channel") else: database.set_channel(self.server.id, channel.id, remove=False) await self.send( f"{channel.mention} was added as a CandyBot channel")
class BlacklistCommand(AdminCommand): name = "blacklist" help = "Shows current blacklist or blacklists a user from interacting with CandyBot." aliases = ["bl"] examples = ["", "@User", "User#1234", "123456789"] argument_spec = ArgumentSpec([UserArgument], True) clean = True ignore = False async def _run(self): user = self.args.get("user") blacklist = database.get_blacklist(self.server.id) if user is None: self.title = ":lock: CandyBot Blacklist" blacklist = [(await converters.to_user(str(x), self.message.guild)).mention for x in blacklist] await self.send("\n".join(blacklist)) else: if user.id in blacklist: database.set_blacklist(self.server.id, user.id, remove=True) await self.send( f"{user.mention} was removed from the CandyBot blacklist") else: database.set_blacklist(self.server.id, user.id, remove=False) await self.send( f"{user.mention} was added to the CandyBot blacklist")
class CreditsCommand(Command): name = "credits" help = "Shows CandyBot credits." aliases = [] examples = [""] argument_spec = ArgumentSpec([], False) clean = False admin = False ignore = False title = ":heart: CandyBot Credits" donate_link = "https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=4MA3ZWKYSYNB6" add_link = "https://discordapp.com/api/oauth2/authorize?client_id=409047597572030484&permissions=8224&scope=bot" github = "https://github.com/axc450/CandyBot" async def _run(self): donators = database.get_donators() donators_list_1 = "\n".join(donators[:len(donators) // 2]) donators_list_2 = "\n".join(donators[len(donators) // 2:]) credits_str = ":candy: CandyBot Created By **Super#0010**\n" \ f":computer: [Add CandyBot to your server!]({self.add_link}) | [Github]({self.github})\n" \ f":moneybag: Please consider donating [here]({self.donate_link}) to support CandyBot!" await self.send(credits_str, fields=[("Donators", donators_list_2, True), ("Donators", donators_list_1, True)])
class GiftCommand(Command): name = "gift" help = "Gifts candy to someone else." aliases = ["give"] examples = ["@User 5 🍎", "User#1234 10 apple"] argument_spec = ArgumentSpec( [UserArgument, ZeroAmountArgument, CandyArgument], optional=False) clean = True admin = False ignore = False # TODO: Reduce database calls in here if possible (use a cache?) async def _run(self): invs = database.get_inv(self.message.guild.id, self.message.author.id, self.user.id) if invs[self.message.author.id][self.candy] < self.amount: await discord.send_embed(self.message.channel, f"You don't have enough {self.candy}!", author=self.message.author) else: candy_value = CandyValue(self.candy, self.amount) database.set_inv(self.message.guild.id, self.message.author.id, -candy_value, update=True) database.set_inv(self.message.guild.id, self.user.id, candy_value, update=True) await discord.send_embed( self.message.channel, f"You have been gifted {candy_value.small_str} by {self.message.author.mention}\n" f"You now have {(invs[self.user.id][self.candy] + candy_value).small_str}", author=self.user)
class HelpCommand(Command): name = "help" help = "Shows command help/menus." aliases = [] examples = ["", "inv", "shop buy"] argument_spec = ArgumentSpec([CommandArgument], True) clean = False admin = False ignore = False title = ":question: CandyBot Help" async def _run(self): command = self.command if self.command else Command if self.ignore_command(command): return if command.subcommands: await self.menu_help(command) else: await self.command_help(command) async def menu_help(self, command): lines = [] for subcommand in command.subcommands: if self.ignore_command(subcommand): continue prefix = self.server_settings.prefix name = subcommand.full_name spec = subcommand.argument_spec help_ = subcommand.help.split("\n")[0] lines.append(f"`{prefix}{name}{' ' + str(spec) if spec else ''}` {help_}") await self.send("\n".join(lines)) async def command_help(self, command): prefix = self.server_settings.prefix command_name = command.full_name # TODO: Align values arg_help = [f"`{x.name} `{x.help}" for x in command.argument_spec.args] arg_help = "\n".join(arg_help) arg_examples = [f"`{prefix}{command_name} {x}`" for x in command.examples] arg_examples = "\n".join(arg_examples) aliases = [f"`{x}`" for x in command.aliases] aliases = "\n".join(aliases) body = (f"`{prefix}{command_name}`\n" f"{command.help}\n") usage = (f"`{prefix}{command_name} {command.argument_spec}`\n" f"{arg_help}\n") aliases = aliases if aliases else "None" examples = f"{arg_examples}" await self.send(body, fields=[("usage", usage, False), ("examples", examples, True), ("aliases", aliases, True)]) def ignore_command(self, command): return (command.ignore and command != Command) or (command.admin is True and not self.is_admin)
class ChanceCommand(SettingsCommand): name = "chance" help = "Sets the chance for Candy to proc." aliases = [] examples = ["20"] argument_spec = ArgumentSpec([PercentArgument], False) clean = True ignore = False async def _run(self): database.set_settings_chance(self.message.guild.id, self.percent / 100) await self.send(f"Candy proc chance has been changed to {self.percent}%")
class AddCandyCommand(CandySettingsCommand): name = "add" help = "Adds a candy." aliases = ["addcandy", "candyadd"] examples = ["🍎 apple"] argument_spec = ArgumentSpec([EmojiArgument, NameArgument], False) clean = True ignore = False async def _run(self): database.set_settings_candy_add(self.message.guild.id, self.name, self.emoji) await self.send(f"{self.emoji} has been added!")
class PrefixCommand(SettingsCommand): name = "prefix" help = "Sets the prefix for CandyBot to use on this server." aliases = [] examples = ["/"] argument_spec = ArgumentSpec([PrefixArgument], False) clean = True ignore = False async def _run(self): database.set_settings_prefix(self.message.guild.id, self.prefix) await self.send(f"Prefix has been changed to `{self.prefix}`")
class LeaderboardCommand(Command): name = "lb" help = "Shows the CandyBot Leaderboard." aliases = ["leaderboard"] examples = ["", "🍎"] argument_spec = ArgumentSpec([CandyArgument], True) clean = False admin = False ignore = False title = ":checkered_flag: CandyBot Leaderboard" emojis = [ "one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "keycap_ten" ] async def _run(self): candy = self.args.get("candy") invs = database.get_inv(self.server.id) sorted_invs = sorted(invs.items(), key=self._sorting_func, reverse=True) lines = await self._generate_lines(sorted_invs, candy) await self.send("\n".join(lines)) async def _generate_lines(self, sorted_invs, candy): lines = [] max_lines = len(self.emojis) for i, (user, inv) in enumerate(sorted_invs): # Break when we run out of emojis (ie. only show top 10) if i == max_lines: break # If the user didn't have any candy, exclude them from the leaderboard: if not inv: continue # If the user couldn't be found, exclude them from the leaderboard u = await converters.to_user(str(user), self.message.guild) if u is None: continue # Add a leaderboard line if candy is None: lines.append(f":{self.emojis[i]}: {u.mention} {inv.line_str}") else: v = inv[candy] if v: v = f"{candy} x **{v:,}**" lines.append(f":{self.emojis[i]}: {u.mention} {v}") return lines def _sorting_func(self, x): return x[1][self.args.get("candy")] if self.args.get( "candy") else x[1].total
class SetCommand(AdminCommand): name = "set" help = "Sets a user's Candy." aliases = [] examples = ["@User 5 🍎", "User#1234 10 apple"] argument_spec = ArgumentSpec([UserArgument, AmountArgument, CandyArgument], optional=False) clean = True ignore = False async def _run(self): candy_value = CandyValue(self.candy, self.amount) database.set_inv(self.message.guild.id, self.user.id, candy_value) await discord.send_embed(self.message.channel, f"You now have {candy_value.small_str} (set by {self.message.author.mention})", author=self.user)
class RemoveCandyCommand(CandySettingsCommand): name = "remove" help = "Deletes a Candy." aliases = ["candydelete", "candyremove", "deletecandy", "removecandy"] examples = ["🍎", "apple"] argument_spec = ArgumentSpec([CandyArgument], False) clean = True ignore = False async def _run(self): candy = self.args["candy"] database.set_settings_candy_remove(self.server.id, candy.id) await self.send(f"{candy} has been deleted!")
class MessageCandyCommand(CandySettingsCommand): name = "message" help = "Changes the candy drop message." aliases = ["candymessage", "candymsg"] examples = ["🍎 Apples appeared!", "apple Apples appeared!"] argument_spec = ArgumentSpec([CandyArgument, TextArgument], False) clean = True ignore = False async def _run(self): database.set_settings_candy_message(self.message.guild.id, self.candy.id, self.text) await self.send(f"{self.candy} drop message has been changed")
class CapCommand(SettingsCommand): name = "cap" help = "Sets the candy cap." aliases = [] examples = ["50"] argument_spec = ArgumentSpec([AmountArgument], False) clean = True ignore = False async def _run(self): amount = self.args["amount"] database.set_settings_cap(self.server.id, amount) await self.send(f"Candy cap been changed to `{amount}`")
class CommandCandyCommand(CandySettingsCommand): name = "command" help = "Changes the candy pick command." aliases = ["candycommand", "candycmd"] examples = ["catch"] argument_spec = ArgumentSpec([CandyArgument, CommandNameArgument], False) clean = True ignore = False async def _run(self): database.set_settings_candy_command(self.message.guild.id, self.candy.id, self.command) await self.send(f"{self.candy} pick command has been changed")
class PickCommand(Command): name = "pick" help = "Picks up dropped Candy" aliases = [] examples = [""] argument_spec = ArgumentSpec([], False) clean = True admin = False ignore = True def __init__(self, server_settings, message=None, raw_args=[], invocation=None): super().__init__(server_settings, message, raw_args) self.invocation = invocation if invocation else "pick" async def _run(self): inv = database.get_inv(self.server.id, self.author.id)[self.author.id] # Need to obtain the lock to avoid multiple users from picking the candy async with engine.STATE_LOCK: state = engine.STATE.get(self.channel.id) current_candy = inv[state.candy_value.candy] if state else None if state and state.command.invocation == self.invocation and current_candy < self.server_settings.cap: # This user will pick up the candy drop # Must clear the state inside the lock to avoid other users from picking del engine.STATE[self.channel.id] else: # An earlier command was chosen to be processed or the invocation didn't match return # Code here will be run after the lock is released and should handle the user successfully picking up candy await state.message.delete() # Replace the dropped candy value to not exceed the candy cap state.candy_value = self.calculate_pick(current_candy, state.candy_value) database.set_inv(self.server.id, self.author.id, state.candy_value, update=True) await self.send(state.pick_str) # TODO: Combine this with the one in GiftCommand and move def calculate_pick(self, current_candy, dropped_candy): # What the candy value would be if there was no cap new_candy = current_candy + dropped_candy.value # What the candy value should actually be calculated_candy = dropped_candy.value - max( new_candy - self.server_settings.cap, 0) return CandyValue(dropped_candy.candy, calculated_candy)
class AddCandyCommand(CandySettingsCommand): name = "add" help = "Adds a candy." aliases = ["addcandy", "candyadd"] examples = ["🍎 apple"] argument_spec = ArgumentSpec([EmojiArgument, NameArgument], False) clean = True ignore = False async def _run(self): emoji = self.args["emoji"] name = self.args["name"] database.set_settings_candy_add(self.server.id, name, emoji) await self.send(f"{emoji} has been added!")
class ResetCommand(SettingsCommand): name = "reset" help = "Resets all CandyBot settings and stats." aliases = [] examples = [""] argument_spec = ArgumentSpec([], False) clean = True ignore = False # TODO: Ask for confirmation async def _run(self): await engine.teardown(self.server.id) await engine.setup(self.server.id) await self.send("CandyBot has been reset!")
class StatsCommand(Command): name = "stats" help = "Shows CandyBot stats." aliases = ["info"] examples = [""] argument_spec = ArgumentSpec([], False) clean = False admin = False ignore = False title = ":chart_with_upwards_trend: CandyBot Stats" async def _run(self): info = self._get_info() candy = self._get_candy() fields = [("info", info, True), ("candy", candy, True)] await self.send(fields=fields) def _get_info(self): server_settings = database.get_settings(self.message.guild.id) channels = database.get_channels(self.message.guild.id) channels = [ discord.get_channel(self.message.guild, x).mention for x in channels ] return "\n".join([ self._make_field("Version", __main__.VERSION), self._make_field("Command Prefix", f"`{server_settings.prefix}`"), self._make_field("Drop Chance", f"{server_settings.chance * 100}%"), self._make_field("Drop Amount", f"{server_settings.min}-{server_settings.max}"), self._make_field("Candy Cap", server_settings.cap), self._make_field("Channels", ("\n" + "\n".join(channels)) if channels else "All") ]) def _get_candy(self): candy = database.get_stats_candy(self.message.guild.id) shop = database.get_stats_shop(self.message.guild.id) return "\n".join([ self._make_field("Candy Dropped", "\n" + candy.list_str), self._make_field("Shop Items Bought", shop), ]) @staticmethod def _make_field(name, value): return f"**{name}:** {value}"
class CostCommand(ShopCommand): name = "cost" help = "Changes a shop item cost." aliases = [] examples = ["@role 🍎 15", "12345 apple 20"] argument_spec = ArgumentSpec([RoleArgument, CandyArgument, AmountArgument], False) clean = True admin = True ignore = False async def _run(self): database.set_shop_cost(self.message.guild.id, self.role.id, self.candy, self.amount) await self.send(f"Updated the cost of {self.role.mention}")
class MaxCommand(SettingsCommand): name = "max" help = "Sets the maximum candy drop." aliases = [] examples = ["50"] argument_spec = ArgumentSpec([AmountArgument], False) clean = True ignore = False async def _run(self): amount = self.args["amount"] if amount < self.server_settings.min: return database.set_settings_max(self.server.id, amount) await self.send(f"Maximum candy drop has been changed to `{amount}`")
class MinCommand(SettingsCommand): name = "min" help = "Sets the minimum candy drop." aliases = [] examples = ["5"] argument_spec = ArgumentSpec([AmountArgument], False) clean = True ignore = False async def _run(self): if self.amount > self.server_settings.max: return database.set_settings_min(self.message.guild.id, self.amount) await self.send( f"Minimum candy drop has been changed to `{self.amount}`")
class InvCommand(Command): name = "inv" help = "Shows a user's current Inventory." aliases = ["inventory"] examples = ["", "@User", "User#1234", "123456789"] argument_spec = ArgumentSpec([UserArgument], True) admin = False clean = False ignore = False async def _run(self): user = self.user if self.user else self.message.author inv = database.get_inv(self.message.guild.id, user.id)[user.id] await discord.send_embed(self.message.channel, inv.list_str, author=user)
class ChanceCandyCommand(CandySettingsCommand): name = "chance" help = "Changes the chance value of a candy." aliases = ["candychance"] examples = ["🍎 10"] argument_spec = ArgumentSpec([CandyArgument, AmountArgument], False) clean = True ignore = False async def _run(self): database.set_settings_candy_chance(self.message.guild.id, self.candy.id, self.amount) candy = database.get_candy(self.message.guild.id) new_chance = utils.chance_value_to_percent(candy)[self.candy] await self.send( f"{self.candy} chance has been changed to {new_chance:.2f}%")
class CostCommand(ShopCommand): name = "cost" help = "Changes a shop item cost." aliases = [] examples = ["@role 🍎 15", "12345 apple 20"] argument_spec = ArgumentSpec([RoleArgument, CandyArgument, AmountArgument], False) clean = True admin = True ignore = False async def _run(self): role = self.args["role"] candy = self.args["candy"] amount = self.args["amount"] database.set_shop_cost(self.server.id, role.id, candy, amount) await self.send(f"Updated the cost of {role.mention}")
class CandyCommand(Command): name = "candy" help = "Shows the available candy." aliases = [] examples = [""] argument_spec = ArgumentSpec([], False) clean = False admin = False ignore = False title = ":candy: Candy" async def _run(self): candy = database.get_candy(self.message.guild.id) candy_chance = utils.chance_value_to_percent(candy) lines = [f"{x.emoji} {x.name} {candy_chance[x]:.2f}%" for x in candy] await self.send("\n".join(lines))
class AdminsCommand(SettingsCommand): name = "admins" help = "Adds or removes CandyBot admins" aliases = [] examples = [] argument_spec = ArgumentSpec([UserArgument], False) clean = True ignore = False # TODO: Ask for confirmation async def _run(self): admins = database.get_admins(self.message.guild.id) if self.user.id in admins: database.set_admin(self.message.guild.id, self.user.id, remove=True) await self.send(f"{self.user.mention} was removed as a CandyBot admin") else: database.set_admin(self.message.guild.id, self.user.id, remove=False) await self.send(f"{self.user.mention} was added as a CandyBot admin")
class GiftCommand(Command): name = "gift" help = "Gifts candy to someone else." aliases = ["give"] examples = ["@User 5 🍎", "User#1234 10 apple"] argument_spec = ArgumentSpec([UserArgument, ZeroAmountArgument, CandyArgument], optional=False) clean = True admin = False ignore = False # TODO: Reduce database calls in here if possible (use a cache?) async def _run(self): user = self.args["user"] amount = self.args["amount"] candy = self.args["candy"] # Users shouldn't be able to gift themselves... if self.message.author == user: return invs = database.get_inv(self.server.id, self.author.id, user.id) author_candy = invs[self.author.id][candy] user_candy = invs[user.id][candy] if author_candy < amount: await self.send(f"You don't have enough {candy}!") elif user_candy >= self.server_settings.cap: await self.send(f"{user.mention} cannot obtain any more {candy}!") else: candy_value = self.calculate_pick(user_candy, CandyValue(candy, amount)) database.set_inv(self.server.id, self.author.id, -candy_value, update=True) database.set_inv(self.server.id, user.id, candy_value, update=True) await discord.send_embed(self.message.channel, f"You have been gifted {candy_value.small_str} by {self.message.author.mention}\n" f"You now have {(user_candy + candy_value).small_str}", author=user) # TODO: Combine this with the one in PickCommand and move def calculate_pick(self, current_candy, gifted_candy): # What the candy value would be if there was no cap new_candy = current_candy + gifted_candy.value # What the candy value should actually be calculated_candy = gifted_candy.value - max(new_candy - self.server_settings.cap, 0) return CandyValue(gifted_candy.candy, calculated_candy)
class ShowCommand(ShopCommand): name = "show" help = "Shows the shop." aliases = [] examples = [""] argument_spec = ArgumentSpec([], False) clean = False admin = False ignore = False title = ":dollar: CandyBot Shop" async def _run(self): shop = database.get_shop(self.message.guild.id) shop_str = await self._shop_to_str(shop) await self.send(shop_str) async def _shop_to_str(self, shop): lines = [] for i, item in enumerate(shop.items): role = discord.get_role(self.message.guild, item.item) lines.append(f"[**{i+1}**] {role.mention} {item.cost.line_str}") return "\n".join(lines)