Example #1
0
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!")
Example #2
0
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"
                                )
Example #3
0
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")
Example #4
0
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")
Example #5
0
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)])
Example #6
0
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)
Example #7
0
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)
Example #8
0
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}%")
Example #9
0
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!")
Example #10
0
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}`")
Example #11
0
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
Example #12
0
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)
Example #13
0
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!")
Example #14
0
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")
Example #15
0
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}`")
Example #16
0
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")
Example #17
0
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)
Example #18
0
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!")
Example #19
0
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!")
Example #20
0
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}"
Example #21
0
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}")
Example #22
0
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}`")
Example #23
0
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}`")
Example #24
0
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)
Example #25
0
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}%")
Example #26
0
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}")
Example #27
0
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))
Example #28
0
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")
Example #29
0
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)
Example #30
0
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)