Beispiel #1
0
    async def children(self, ctx:utils.Context, user:utils.converters.UserID=None):
        """
        Tells you who a user's children are.
        """

        # Get the user's info
        user_id = user or ctx.author.id
        user_name = await localutils.DiscordNameManager.fetch_name_by_id(self.bot, user_id)
        user_info = localutils.FamilyTreeMember.get(user_id, localutils.get_family_guild_id(ctx))

        # Get user's children
        if len(user_info._children) == 0:
            output = f"**{localutils.escape_markdown(user_name)}** has no children right now."
            if user_id == ctx.author.id:
                output = "You have no children right now."
        else:
            children_plural = 'child' if len(user_info._children) == 1 else 'children'
            output = f"**{localutils.escape_markdown(user_name)}** has {len(user_info._children)} {children_plural}:\n"
            if user_id == ctx.author.id:
                ouptut = f"You have {len(user_info._children)} {children_plural}:\n"
            children = [(await localutils.DiscordNameManager.fetch_name_by_id(self.bot, i), i) for i in user_info._children]
            output += "\n".join([f"* **{localutils.escape_markdown(i[0])}** (`{i[1]}`)" for i in children])

        # Return all output
        await ctx.send(output, allowed_mentions=discord.AllowedMentions.none())
    async def forceemancipate(self, ctx: utils.Context,
                              child: utils.converters.UserID):
        """
        Force emancipates a child.
        """

        # Run checks
        family_guild_id = localutils.get_family_guild_id(ctx)
        child_tree = localutils.FamilyTreeMember.get(child, family_guild_id)
        child_name = await localutils.DiscordNameManager.fetch_name_by_id(
            self.bot, child)
        if not child_tree._parent:
            return await ctx.send(
                f"**{child_name}** doesn't even have a parent .-.")

        # Update database
        async with self.bot.database() as db:
            await db(
                """DELETE FROM parents WHERE child_id=$1 AND guild_id=$2""",
                child,
                family_guild_id,
            )

        # Update cache
        try:
            child_tree.parent._children.remove(child)
        except ValueError:
            pass
        parent = child_tree.parent
        child_tree._parent = None
        async with self.bot.redis() as re:
            await re.publish('TreeMemberUpdate', child_tree.to_json())
            await re.publish('TreeMemberUpdate', parent.to_json())
        await ctx.send("Consider it done.")
Beispiel #3
0
    async def parent(self,
                     ctx: utils.Context,
                     user: utils.converters.UserID = None):
        """
        Tells you who someone's parent is.
        """

        # Get the user's info
        user_id = user or ctx.author.id
        user_name = await localutils.DiscordNameManager.fetch_name_by_id(
            self.bot, user_id)
        user_info = localutils.FamilyTreeMember.get(
            user_id, localutils.get_family_guild_id(ctx))

        # Make sure they have a parent
        if user_info._parent is None:
            if user_id == ctx.author.id:
                return await ctx.send("You have no parent.")
            return await ctx.send(
                f"**{localutils.escape_markdown(user_name)}** has no parent.",
                allowed_mentions=discord.AllowedMentions.none())
        parent_name = await localutils.DiscordNameManager.fetch_name_by_id(
            self.bot, user_info._parent)

        # Output parent
        output = f"**{localutils.escape_markdown(user_name)}**'s parent is **{localutils.escape_markdown(parent_name)}** (`{user_info._parent}`)."
        if user_id == ctx.author.id:
            output = f"Your parent is **{localutils.escape_markdown(parent_name)}** (`{user_info._parent}`)."
        return await ctx.send(output,
                              allowed_mentions=discord.AllowedMentions.none())
    async def convert(cls, ctx: vbu.Context, value: str):
        """
        A more gentle converter that accepts child names as well as pings and IDs.
        """

        # See if it's a ping or a mention
        try:
            return await super().convert(ctx, value)

        # See if it's a name
        except Exception as e:
            user_tree = utils.FamilyTreeMember.get(
                ctx.author.id, utils.get_family_guild_id(ctx))
            for child in user_tree.children:
                child_name = await utils.DiscordNameManager.fetch_name_by_id(
                    ctx.bot, child.id)
                child_name_length = len(child_name)
                if len(value) == child_name_length or len(
                        value) == len(child_name_length) - 4:
                    pass
                else:
                    raise
                if child_name.startswith(value):
                    return child.id
            raise e
    async def familysize(self,
                         ctx: vbu.Context,
                         user: vbu.converters.UserID = None):
        """
        Gives you the size of your family tree.
        """

        # Get the user's info
        user_id = user or ctx.author.id
        user_name = await utils.DiscordNameManager.fetch_name_by_id(
            self.bot, user_id)
        user_info = utils.FamilyTreeMember.get(user_id,
                                               utils.get_family_guild_id(ctx))

        # Get size
        size = user_info.family_member_count

        # Output
        output = (
            f"There {'are' if size > 1 else 'is'} {size} {'people' if size > 1 else 'person'} "
            f"in **{utils.escape_markdown(user_name)}**'s family tree.")
        if user_id == ctx.author.id:
            output = f"There {'are' if size > 1 else 'is'} {size} {'people' if size > 1 else 'person'} in your family tree."
        return await ctx.send(output,
                              allowed_mentions=discord.AllowedMentions.none(),
                              wait=False)
    async def forcedivorce(self, ctx: utils.Context,
                           usera: utils.converters.UserID):
        """
        Divorces a user from their spouse.
        """

        # Get user
        family_guild_id = localutils.get_family_guild_id(ctx)
        usera_tree = localutils.FamilyTreeMember.get(usera,
                                                     guild_id=family_guild_id)
        usera_name = await localutils.DiscordNameManager.fetch_name_by_id(
            self.bot, usera)
        if not usera_tree.partner:
            return await ctx.send(
                f"**{usera_name}** isn't even married .-.",
                allowed_mentions=discord.AllowedMentions.none())

        # Update database
        async with self.bot.database() as db:
            await db(
                """DELETE FROM marriages WHERE (user_id=$1 OR partner_id=$1) AND guild_id=$2""",
                usera,
                family_guild_id,
            )

        # Update cache
        usera_tree.partner._partner = None
        userb_tree = usera_tree.partner
        usera_tree._partner = None
        async with self.bot.redis() as re:
            await re.publish('TreeMemberUpdate', usera_tree.to_json())
            await re.publish('TreeMemberUpdate', userb_tree.to_json())
        await ctx.send("Consider it done.")
Beispiel #7
0
    async def emancipate(self, ctx: vbu.Context):
        """
        Removes your parent.
        """

        # Get the family tree member objects
        family_guild_id = utils.get_family_guild_id(ctx)
        user_tree = utils.FamilyTreeMember.get(ctx.author.id,
                                               guild_id=family_guild_id)

        # Make sure they're the child of the instigator
        parent_tree = user_tree.parent
        if not parent_tree:
            return await ctx.send("You don't have a parent right now :<",
                                  wait=False)

        # See if they're sure
        try:
            result = await utils.send_proposal_message(
                ctx,
                ctx.author,
                f"Are you sure you want to leave your parent, {ctx.author.mention}?",
                timeout_message=
                f"Timed out making sure you want to emancipate, {ctx.author.mention} :<",
                cancel_message="Alright, I've cancelled your emancipation!",
            )
        except Exception:
            result = None
        if result is None:
            return

        # Remove family caching
        user_tree._parent = None
        try:
            parent_tree._children.remove(ctx.author.id)
        except ValueError:
            pass

        # Ping them off over reids
        async with self.bot.redis() as re:
            await re.publish('TreeMemberUpdate', user_tree.to_json())
            await re.publish('TreeMemberUpdate', parent_tree.to_json())

        # Remove their relationship from the database
        async with self.bot.database() as db:
            await db(
                """DELETE FROM parents WHERE parent_id=$1 AND child_id=$2 AND guild_id=$3""",
                parent_tree.id,
                ctx.author.id,
                family_guild_id,
            )

        # And we're done
        parent_name = await utils.DiscordNameManager.fetch_name_by_id(
            self.bot, parent_tree.id)
        return await result.ctx.send(
            f"You no longer have **{utils.escape_markdown(parent_name)}** as a parent :c",
            wait=False)
    async def partner(self,
                      ctx: vbu.Context,
                      user: vbu.converters.UserID = None):
        """
        Tells you who a user is married to.
        """

        # Get the user's info
        user_id = user or ctx.author.id
        user_name = await utils.DiscordNameManager.fetch_name_by_id(
            self.bot, user_id)
        user_info = utils.FamilyTreeMember.get(user_id,
                                               utils.get_family_guild_id(ctx))

        # Check they have a partner
        if user_info._partner is None:
            if user_id == ctx.author.id:
                return await ctx.send(
                    "You're not currently married.",
                    allowed_mentions=discord.AllowedMentions.none(),
                    wait=False,
                )
            return await ctx.send(
                f"**{utils.escape_markdown(user_name)}** is not currently married.",
                allowed_mentions=discord.AllowedMentions.none(),
                wait=False,
            )
        partner_name = await utils.DiscordNameManager.fetch_name_by_id(
            self.bot, user_info._partner)

        # Get timestamp
        async with self.bot.database() as db:
            if self.bot.config.get('is_server_specific', False):
                data = await db(
                    "SELECT * FROM marriages WHERE user_id=$1 AND guild_id=$2",
                    user_id, user_info._guild_id)
            else:
                data = await db(
                    "SELECT * FROM marriages WHERE user_id=$1 AND guild_id=0",
                    user_id)
        try:
            timestamp = data[0]['timestamp']
        except Exception:
            timestamp = None

        # Output
        text = f"**{utils.escape_markdown(user_name)}** is currently married to **{utils.escape_markdown(partner_name)}** (`{user_info._partner}`). "
        if user_id == ctx.author.id:
            text = f"You're currently married to **{utils.escape_markdown(partner_name)}** (`{user_info._partner}`). "
        if timestamp:
            duration = vbu.TimeFormatter(timestamp)
            text += f"{'You' if user_id == ctx.author.id else 'They'} got married {duration.relative_time}."
        await ctx.send(text,
                       allowed_mentions=discord.AllowedMentions.none(),
                       wait=False)
Beispiel #9
0
    async def disownall(self, ctx: vbu.Context):
        """
        Disowns all of your children.
        """

        # Get the family tree member objects
        family_guild_id = utils.get_family_guild_id(ctx)
        user_tree = utils.FamilyTreeMember.get(ctx.author.id,
                                               guild_id=family_guild_id)
        child_trees = list(user_tree.children)
        if not child_trees:
            return await ctx.send("You don't have any children to disown .-.",
                                  wait=False)

        # See if they're sure
        try:
            result = await utils.send_proposal_message(
                ctx,
                ctx.author,
                f"Are you sure you want to disown all your children, {ctx.author.mention}?",
                timeout_message=
                f"Timed out making sure you want to disownall, {ctx.author.mention} :<",
                cancel_message="Alright, I've cancelled your disownall!",
            )
        except Exception:
            result = None
        if result is None:
            return

        # Disown em
        for child in child_trees:
            child._parent = None
        user_tree._children = []

        # Save em
        async with self.bot.database() as db:
            await db(
                """DELETE FROM parents WHERE parent_id=$1 AND guild_id=$2 AND child_id=ANY($3::BIGINT[])""",
                ctx.author.id,
                family_guild_id,
                [child.id for child in child_trees],
            )

        # Redis em
        async with self.bot.redis() as re:
            for person in child_trees + [user_tree]:
                await re.publish('TreeMemberUpdate', person.to_json())

        # Output to user
        await result.ctx.send(
            "You've sucessfully disowned all of your children :c", wait=False)
    async def forceadopt(self,
                         ctx: vbu.Context,
                         parent: vbu.converters.UserID,
                         child: vbu.converters.UserID = None):
        """
        Adds the child to the specified parent.
        """

        # Correct params
        if child is None:
            parent_id, child_id = ctx.author.id, parent
        else:
            parent_id, child_id = parent, child

        # Check users
        family_guild_id = utils.get_family_guild_id(ctx)
        parent_tree, child_tree = utils.FamilyTreeMember.get_multiple(
            parent_id, child_id, guild_id=family_guild_id)
        child_name = await utils.DiscordNameManager.fetch_name_by_id(
            self.bot, child_id)
        if child_tree.parent:
            return await ctx.send(
                f"**{child_name}** already has a parent.",
                allowed_mentions=discord.AllowedMentions.none(),
                wait=False)
        parent_name = await utils.DiscordNameManager.fetch_name_by_id(
            self.bot, parent_tree.id)

        # Update database
        async with self.bot.database() as db:
            try:
                await db(
                    """INSERT INTO parents (parent_id, child_id, guild_id, timestamp) VALUES ($1, $2, $3, $4)""",
                    parent_id,
                    child_id,
                    family_guild_id,
                    dt.utcnow(),
                )
            except asyncpg.UniqueViolationError:
                return await ctx.send(
                    "I ran into an error saving your family data.", wait=False)

        # Update cache
        parent_tree._children.append(child_id)
        child_tree._parent = parent_id
        async with self.bot.redis() as re:
            await re.publish('TreeMemberUpdate', parent_tree.to_json())
            await re.publish('TreeMemberUpdate', child_tree.to_json())
        await ctx.send(
            f"Added **{child_name}** to **{parent_name}**'s children list.",
            wait=False)
Beispiel #11
0
    async def divorce(self, ctx: utils.Context):
        """
        Divorces you from your current partner.
        """

        # Get the family tree member objects
        family_guild_id = localutils.get_family_guild_id(ctx)
        author_tree = localutils.FamilyTreeMember.get(ctx.author.id,
                                                      guild_id=family_guild_id)

        # See if they're married
        target_tree = author_tree.partner
        if not target_tree:
            return await ctx.send("It doesn't look like you're married yet!")

        # See if they're sure
        try:
            result = await localutils.send_proposal_message(
                ctx,
                ctx.author,
                f"Are you sure you want to divorce your partner, {ctx.author.mention}?",
                timeout_message=
                f"Timed out making sure you want to divorce, {ctx.author.mention} :<",
                cancel_message="Alright, I've cancelled your divorce!",
            )
        except Exception:
            result = None
        if result is None:
            return

        # Remove them from the database
        async with self.bot.database() as db:
            await db(
                """DELETE FROM marriages WHERE (user_id=$1 OR user_id=$2) AND guild_id=$3""",
                ctx.author.id,
                target_tree.id,
                family_guild_id,
            )
        partner_name = await localutils.DiscordNameManager.fetch_name_by_id(
            self.bot, author_tree._partner)
        await result.ctx.send(
            f"You've successfully divorced **{localutils.escape_markdown(partner_name)}** :c",
            allowed_mentions=discord.AllowedMentions.none(),
        )

        # Ping over redis
        author_tree._partner = None
        target_tree._partner = None
        async with self.bot.redis() as re:
            await re.publish('TreeMemberUpdate', author_tree.to_json())
            await re.publish('TreeMemberUpdate', target_tree.to_json())
    async def children(self,
                       ctx: vbu.Context,
                       user: vbu.converters.UserID = None):
        """
        Tells you who a user's children are.
        """

        # Get the user's info
        user_id = user or ctx.author.id
        user_name = await utils.DiscordNameManager.fetch_name_by_id(
            self.bot, user_id)
        user_info = utils.FamilyTreeMember.get(user_id,
                                               utils.get_family_guild_id(ctx))

        # See if the user has no children
        if len(user_info._children) == 0:
            if user_id == ctx.author.id:
                return await ctx.send(
                    "You have no children right now.",
                    allowed_mentions=discord.AllowedMentions.none(),
                    wait=False,
                )
            return await ctx.send(
                f"**{utils.escape_markdown(user_name)}** has no children right now.",
                allowed_mentions=discord.AllowedMentions.none(),
                wait=False,
            )

        # They do have children!
        children_plural = 'child' if len(
            user_info._children) == 1 else 'children'
        output = f"**{utils.escape_markdown(user_name)}** has {len(user_info._children)} {children_plural}:\n"
        if user_id == ctx.author.id:
            output = f"You have {len(user_info._children)} {children_plural}:\n"
        children = [(
            await utils.DiscordNameManager.fetch_name_by_id(self.bot, i),
            i,
        ) for i in user_info._children]
        output += "\n".join([
            f"\N{BULLET} **{utils.escape_markdown(i[0])}** (`{i[1]}`)"
            for i in children
        ])

        # Return all output
        if len(output) > 2_000:
            return await ctx.send(
                f"<@{user_id}>'s children list goes over 2,000 characters. Amazing.",
                wait=False)
        await ctx.send(output,
                       allowed_mentions=discord.AllowedMentions.none(),
                       wait=False)
    async def relationship(self,
                           ctx: vbu.Context,
                           user: vbu.converters.UserID,
                           other: vbu.converters.UserID = None):
        """
        Gets the relationship between the two specified users.
        """

        # Fix up the arguments
        if other is None:
            user_id, other_id = ctx.author.id, user
        else:
            user_id, other_id = user, other

        # See if they're the same person
        if user_id == other_id:
            if user_id == ctx.author.id:
                return await ctx.send(
                    "Unsurprisingly, you're pretty closely related to yourself.",
                    wait=False)
            return await ctx.send(
                "Unsurprisingly, they're pretty closely related to themselves.",
                wait=False)

        # Get their relation
        user_info, other_info = utils.FamilyTreeMember.get_multiple(
            user_id, other_id, guild_id=utils.get_family_guild_id(ctx))
        async with ctx.typing():
            relation = user_info.get_relation(other_info)

        # Get names
        user_name = await utils.DiscordNameManager.fetch_name_by_id(
            self.bot, user_id)
        other_name = await utils.DiscordNameManager.fetch_name_by_id(
            self.bot, other_id)

        # Output
        if relation is None:
            output = f"**{utils.escape_markdown(user_name)}** is not related to **{utils.escape_markdown(other_name)}**."
            if user_id == ctx.author.id:
                output = f"You're not related to **{utils.escape_markdown(other_name)}**."
        else:
            output = f"**{utils.escape_markdown(other_name)}** is **{utils.escape_markdown(user_name)}**'s {relation}."
            if user_id == ctx.author.id:
                output = f"**{utils.escape_markdown(other_name)}** is your {relation}."
        return await ctx.send(output,
                              allowed_mentions=discord.AllowedMentions.none(),
                              wait=False)
Beispiel #14
0
    async def copulate(self, ctx: vbu.Context, target: discord.Member):
        """
        Lets you... um... heck someone.
        """

        # Variables we're gonna need for later
        family_guild_id = utils.get_family_guild_id(ctx)
        author_tree, target_tree = utils.FamilyTreeMember.get_multiple(
            ctx.author.id, target.id, guild_id=family_guild_id)

        # Check they're not a bot
        if target.id == self.bot.user.id:
            return await ctx.send("Ew. No. Thanks.", wait=False)
        if target.id == ctx.author.id:
            return

        # See if they're already related
        async with ctx.typing():
            relation = author_tree.get_relation(target_tree)
        if relation and relation != "partner" and utils.guild_allows_incest(
                ctx) is False:
            return await ctx.send(
                f"Woah woah woah, it looks like you guys are related! {target.mention} is your {relation}!",
                allowed_mentions=utils.only_mention(ctx.author),
                wait=False,
            )

        # Set up the proposal
        if target.id != ctx.author.id:
            try:
                result = await utils.send_proposal_message(
                    ctx,
                    target,
                    f"Hey, {target.mention}, {ctx.author.mention} do you wanna... smash? \N{SMIRKING FACE}",
                    allow_bots=True,
                )
            except Exception:
                result = None
        if result is None:
            return

        # Respond
        await result.ctx.send(
            random.choice(utils.random_text.Copulate.VALID).format(
                author=ctx.author, target=target),
            wait=False,
        )
Beispiel #15
0
    async def familysize(self, ctx:utils.Context, user:utils.converters.UserID=None):
        """
        Gives you the size of your family tree.
        """

        # Get the user's info
        user_id = user or ctx.author.id
        user_name = await localutils.DiscordNameManager.fetch_name_by_id(self.bot, user_id)
        user_info = localutils.FamilyTreeMember.get(user_id, localutils.get_family_guild_id(ctx))

        # Get size
        async with ctx.channel.typing():
            size = user_info.family_member_count

        # Output
        output = f"There are {size} people in **{localutils.escape_markdown(user_name)}**'s family tree."
        if user_id == ctx.author.id:
            output = f"There are {size} people in your family tree."
        return await ctx.send(output, allowed_mentions=discord.AllowedMentions.none())
    async def forcemarry(self,
                         ctx: utils.Context,
                         usera: utils.converters.UserID,
                         userb: utils.converters.UserID = None):
        """
        Marries the two specified users.
        """

        # Correct params
        if userb is None:
            usera, userb = ctx.author.id, usera
        if usera == userb:
            return await ctx.send("You can't marry yourself.")

        # Get users
        family_guild_id = localutils.get_family_guild_id(ctx)
        usera_tree, userb_tree = localutils.FamilyTreeMember.get_multiple(
            usera, userb, guild_id=family_guild_id)

        # See if they have partners
        if usera_tree._partner is not None:
            user_name = await localutils.DiscordNameManager.fetch_name_by_id(
                self.bot, usera)
            return await ctx.send(
                f"**{user_name}** already has a partner.",
                allowed_mentions=discord.AllowedMentions.none())
        if userb_tree._partner is not None:
            user_name = await localutils.DiscordNameManager.fetch_name_by_id(
                self.bot, userb)
            return await ctx.send(
                f"**{user_name}** already has a partner.",
                allowed_mentions=discord.AllowedMentions.none())

        # Update database
        async with self.bot.database() as db:
            try:
                await db.start_transaction()
                await db(
                    "INSERT INTO marriages (user_id, partner_id, guild_id, timestamp) VALUES ($1, $2, $3, $4), ($2, $1, $3, $4)",
                    usera_tree.id,
                    userb_tree.id,
                    family_guild_id,
                    dt.utcnow(),
                )
                await db.commit_transaction()
            except asyncpg.UniqueViolationError:
                return await ctx.send(
                    "I ran into an error saving your family data.")
        usera_name = await localutils.DiscordNameManager.fetch_name_by_id(
            self.bot, usera_tree.id)
        userb_name = await localutils.DiscordNameManager.fetch_name_by_id(
            self.bot, userb_tree.id)
        await ctx.send(f"Married **{usera_name}** and **{userb_name}**.",
                       allowed_mentions=discord.AllowedMentions.none())

        # Update cache
        usera_tree._partner = userb
        userb_tree._partner = usera
        async with self.bot.redis() as re:
            await re.publish('TreeMemberUpdate', usera_tree.to_json())
            await re.publish('TreeMemberUpdate', userb_tree.to_json())
Beispiel #17
0
    async def abandon(self, ctx: vbu.Context):
        """
        Completely removes you from the tree.
        """

        # Set up some variables
        family_guild_id = utils.get_family_guild_id(ctx)
        user_tree = utils.FamilyTreeMember.get(ctx.author.id,
                                               guild_id=family_guild_id)

        # See if they're sure
        try:
            result = await utils.send_proposal_message(
                ctx,
                ctx.author,
                f"Are you sure you want to completely abandon your family, {ctx.author.mention}? This will disown all your kids, emancipate, and divorce you",
                timeout_message=
                f"Timed out making sure you want to abandon your family, {ctx.author.mention} :<",
                cancel_message="Alright, I've cancelled your abandonment!",
            )
        except Exception:
            result = None
        if result is None:
            return

        # Grab the users from the cache
        parent_tree = user_tree.parent
        child_trees = list(user_tree.children)
        partner_tree = user_tree.partner

        # Remove children from cache
        for child in child_trees:
            child._parent = None
        user_tree._children = []

        # Remove parent from cache
        if parent_tree:
            user_tree._parent = None
            try:
                parent_tree._children.remove(ctx.author.id)
            except ValueError:
                pass

        # Remove partner from cache
        if partner_tree:
            user_tree._partner = None
            partner_tree._partner = None

        # Remove from database
        async with self.bot.database() as db:
            await db(
                """DELETE FROM parents WHERE parent_id=$1 AND guild_id=$2 AND child_id=ANY($3::BIGINT[])""",
                ctx.author.id,
                family_guild_id,
                [child.id for child in child_trees],
            )
            if parent_tree:
                await db(
                    """DELETE FROM parents WHERE parent_id=$1 AND child_id=$2 AND guild_id=$3""",
                    parent_tree.id,
                    ctx.author.id,
                    family_guild_id,
                )
            if partner_tree:
                await db(
                    """DELETE FROM marriages WHERE (user_id=$1 OR user_id=$2) AND guild_id=$3""",
                    ctx.author.id,
                    partner_tree.id,
                    family_guild_id,
                )

        # Remove from redis
        async with self.bot.redis() as re:
            for person in child_trees:
                await re.publish('TreeMemberUpdate', person.to_json())
            if parent_tree:
                await re.publish('TreeMemberUpdate', parent_tree.to_json())
            if partner_tree:
                await re.publish('TreeMemberUpdate', partner_tree.to_json())
            await re.publish('TreeMemberUpdate', user_tree.to_json())

        # And we're done
        await result.ctx.send(
            f"You've successfully left your family, {ctx.author.mention} :c",
            allowed_mentions=discord.AllowedMentions.none(),
            wait=False,
        )
Beispiel #18
0
    async def disown(self,
                     ctx: vbu.Context,
                     *,
                     target: utils.ChildIDConverter = None):
        """
        Lets you remove a user from being your child.
        """

        # Get the user family tree member
        family_guild_id = utils.get_family_guild_id(ctx)
        user_tree = utils.FamilyTreeMember.get(ctx.author.id,
                                               guild_id=family_guild_id)

        # If they didn't give a child, give them a dropdown
        if target is None:

            # Make a list of options
            child_options = []
            for index, child_tree in enumerate(user_tree.children):
                child_name = await utils.DiscordNameManager.fetch_name_by_id(
                    self.bot, child_tree.id)
                child_options.append(
                    vbu.SelectOption(label=child_name,
                                     value=f"DISOWN {child_tree.id}"))
                if index >= 25:
                    return await ctx.send(
                        ("I couldn't work out which of your children you wanted to disown. "
                         "You can ping or use their ID to disown them."),
                        wait=False,
                    )

            # See if they don't have any children
            if not child_options:
                return await ctx.send("You don't have any children!",
                                      wait=False)

            # Wait for them to pick one
            components = vbu.MessageComponents(
                vbu.ActionRow(
                    vbu.SelectMenu(custom_id="DISOWN_USER",
                                   options=child_options), ))
            m = await ctx.send(
                "Which of your children would you like to disown?",
                components=components,
                wait=True,
            )

            # Make our check
            def check(payload: vbu.ComponentInteractionPayload):
                if payload.message.id != m.id:
                    return False
                if payload.user.id != ctx.author.id:
                    self.bot.loop.create_task(
                        payload.respond("You can't respond to this message!",
                                        wait=False,
                                        ephemeral=True))
                    return False
                return True

            try:
                payload = await self.bot.wait_for("component_interaction",
                                                  check=check,
                                                  timeout=60)
                await payload.defer_update()
                await payload.message.delete()
            except asyncio.TimeoutError:
                return await ctx.send(
                    "Timed out asking for which child you want to disown :<",
                    wait=False)

            # Get the child's ID that they selected
            target = int(payload.values[0][len("DISOWN "):])

        # Get the family tree member objects
        child_tree = utils.FamilyTreeMember.get(target,
                                                guild_id=family_guild_id)
        child_name = await utils.DiscordNameManager.fetch_name_by_id(
            self.bot, child_tree.id)

        # Make sure they're actually children
        if child_tree.id not in user_tree._children:
            return await ctx.send(
                f"It doesn't look like **{utils.escape_markdown(child_name)}** is one of your children!",
                allowed_mentions=discord.AllowedMentions.none(),
                wait=False,
            )

        # See if they're sure
        try:
            result = await utils.send_proposal_message(
                ctx,
                ctx.author,
                f"Are you sure you want to disown **{utils.escape_markdown(child_name)}**, {ctx.author.mention}?",
                timeout_message=
                f"Timed out making sure you want to disown, {ctx.author.mention} :<",
                cancel_message="Alright, I've cancelled your disown!",
            )
        except Exception:
            result = None
        if result is None:
            return

        # Remove from cache
        try:
            user_tree._children.remove(child_tree.id)
        except ValueError:
            pass
        child_tree._parent = None

        # Remove from redis
        async with self.bot.redis() as re:
            await re.publish('TreeMemberUpdate', user_tree.to_json())
            await re.publish('TreeMemberUpdate', child_tree.to_json())

        # Remove from database
        async with self.bot.database() as db:
            await db(
                """DELETE FROM parents WHERE child_id=$1 AND parent_id=$2 AND guild_id=$3""",
                child_tree.id,
                ctx.author.id,
                family_guild_id,
            )

        # And we're done
        await result.ctx.send(
            f"You've successfully disowned **{utils.escape_markdown(child_name)}** :c",
            allowed_mentions=discord.AllowedMentions.none(),
            wait=False,
        )
Beispiel #19
0
    async def disown(self, ctx: utils.Context, *,
                     target: utils.converters.UserID):
        """
        Lets you remove a user from being your child.
        """

        # Get the family tree member objects
        family_guild_id = localutils.get_family_guild_id(ctx)
        user_tree, child_tree = localutils.FamilyTreeMember.get_multiple(
            ctx.author.id, target, guild_id=family_guild_id)
        child_name = await localutils.DiscordNameManager.fetch_name_by_id(
            self.bot, child_tree.id)

        # Make sure they're actually children
        if child_tree.id not in user_tree._children:
            return await ctx.send(
                f"It doesn't look like **{localutils.escape_markdown(child_name)}** is one of your children!",
                allowed_mentions=discord.AllowedMentions.none(),
            )

        # See if they're sure
        try:
            result = await localutils.send_proposal_message(
                ctx,
                ctx.author,
                f"Are you sure you want to disown your child, {ctx.author.mention}?",
                timeout_message=
                f"Timed out making sure you want to disown, {ctx.author.mention} :<",
                cancel_message="Alright, I've cancelled your disown!",
            )
        except Exception:
            result = None
        if result is None:
            return

        # Remove from cache
        try:
            user_tree._children.remove(child_tree.id)
        except ValueError:
            pass
        child_tree._parent = None

        # Remove from redis
        async with self.bot.redis() as re:
            await re.publish('TreeMemberUpdate', user_tree.to_json())
            await re.publish('TreeMemberUpdate', child_tree.to_json())

        # Remove from database
        async with self.bot.database() as db:
            await db(
                """DELETE FROM parents WHERE child_id=$1 AND parent_id=$2 AND guild_id=$3""",
                child_tree.id,
                ctx.author.id,
                family_guild_id,
            )

        # And we're done
        await ctx.send(
            f"You've successfully disowned **{localutils.escape_markdown(child_name)}** :c",
            allowed_mentions=discord.AllowedMentions.none(),
        )
    async def treemaker(self,
                        ctx: vbu.Context,
                        user_id: int,
                        stupid_tree: bool = False):
        """
        Handles the generation and sending of the tree to the user.
        """

        # Get their family tree
        user_info = utils.FamilyTreeMember.get(user_id,
                                               utils.get_family_guild_id(ctx))
        user_name = await utils.DiscordNameManager.fetch_name_by_id(
            self.bot, user_id)

        # Make sure they have one
        if user_info.is_empty:
            if user_id == ctx.author.id:
                return await ctx.send(
                    "You have no family to put into a tree .-.")
            return await ctx.send(
                f"**{utils.escape_markdown(user_name)}** has no family to put into a tree .-.",
                allowed_mentions=discord.AllowedMentions.none(),
            )

        # Get their customisations
        async with self.bot.database() as db:
            ctu = await utils.CustomisedTreeUser.fetch_by_id(db, ctx.author.id)

        # Get their dot script
        async with ctx.typing():
            if stupid_tree:
                dot_code = await user_info.to_full_dot_script(self.bot, ctu)
            else:
                dot_code = await user_info.to_dot_script(self.bot, ctu)

        # Write the dot to a file
        dot_filename = f'{self.bot.config["tree_file_location"]}/{ctx.author.id}.gz'
        try:
            with open(dot_filename, 'w', encoding='utf-8') as a:
                a.write(dot_code)
        except Exception as e:
            self.logger.error(f"Could not write to {dot_filename}")
            raise e

        # Convert to an image
        image_filename = f'{self.bot.config["tree_file_location"].rstrip("/")}/{ctx.author.id}.png'
        # http://www.graphviz.org/doc/info/output.html#d:png
        perks = await utils.get_marriagebot_perks(ctx.bot, ctx.author.id)
        # highest quality colour, and antialiasing
        # not using this because not much point
        # todo: add extra level for better colour, stroke etc, basically like the one in the readme (in addition to antialiasing)
        # if False:
        #     format_rendering_option = '-Tpng:cairo'  # -T:png does the same thing but this is clearer
        # normal colour, and antialising
        if perks.tree_render_quality >= 1:
            format_rendering_option = '-Tpng:cairo'
        # normal colour, no antialising
        else:
            format_rendering_option = '-Tpng:gd'

        dot = await asyncio.create_subprocess_exec('dot',
                                                   format_rendering_option,
                                                   dot_filename, '-o',
                                                   image_filename,
                                                   '-Gcharset=UTF-8')
        await asyncio.wait_for(dot.wait(), 10.0, loop=self.bot.loop)

        # Kill subprocess
        try:
            dot.kill()
        except ProcessLookupError:
            pass  # It already died
        except Exception:
            raise

        # Send file
        try:
            file = discord.File(image_filename)
        except FileNotFoundError:
            return await ctx.send(
                "I was unable to send your family tree image - please try again later."
            )
        text = "[Click here](https://marriagebot.xyz/) to customise your tree."
        if not stupid_tree:
            text += f" Use `{ctx.prefix}bloodtree` for your _entire_ family, including non-blood relatives."
        tree_message = await ctx.send(text, file=file)
        await self.bot.add_delete_reaction(tree_message)

        # Delete the files
        self.bot.loop.create_task(
            asyncio.create_subprocess_exec('rm', dot_filename))
        self.bot.loop.create_task(
            asyncio.create_subprocess_exec('rm', image_filename))
Beispiel #21
0
    async def makeparent(self, ctx: utils.Context, *,
                         target: localutils.converters.UnblockedMember):
        """
        Picks a user that you want to be your parent.
        """

        # Variables we're gonna need for later
        family_guild_id = localutils.get_family_guild_id(ctx)
        author_tree, target_tree = localutils.FamilyTreeMember.get_multiple(
            ctx.author.id, target.id, guild_id=family_guild_id)

        # Check they're not themselves
        if target.id == ctx.author.id:
            return await ctx.send(
                "That's you. You can't make yourself your parent.")

        # Check they're not a bot
        if target.id == self.bot.user.id:
            return await ctx.send(
                "I think I could do better actually, but thank you!")

        # Lock those users
        re = await self.bot.redis.get_connection()
        try:
            lock = await localutils.ProposalLock.lock(re, ctx.author.id,
                                                      target.id)
        except localutils.ProposalInProgress:
            return await ctx.send(
                "Aren't you popular! One of you is already waiting on a proposal - please try again later."
            )

        # See if the *target* is already married
        if author_tree.parent:
            await lock.unlock()
            return await ctx.send(
                f"Hey! {ctx.author.mention}, you already have a parent \N{ANGRY FACE}",
                allowed_mentions=localutils.only_mention(ctx.author),
            )

        # See if we're already married
        if ctx.author.id in target_tree._children:
            await lock.unlock()
            return await ctx.send(
                f"Hey isn't {target.mention} already your child? \N{FACE WITH ROLLING EYES}",
                allowed_mentions=localutils.only_mention(ctx.author),
            )

        # See if they're already related
        async with ctx.channel.typing():
            relation = author_tree.get_relation(target_tree)
        if relation and localutils.guild_allows_incest(ctx) is False:
            await lock.unlock()
            return await ctx.send(
                f"Woah woah woah, it looks like you guys are already related! {target.mention} is your {relation}!",
                allowed_mentions=localutils.only_mention(ctx.author),
            )

        # Manage children
        children_amount = await self.get_max_children_for_member(
            ctx.guild, target)
        if len(author_tree._children) >= children_amount:
            return await ctx.send(
                f"You're currently at the maximum amount of children you can have - see `{ctx.prefix}perks` for more information."
            )

        # Check the size of their trees
        # TODO I can make this a util because I'm going to use it a couple times
        max_family_members = localutils.get_max_family_members(ctx)
        async with ctx.channel.typing():
            family_member_count = 0
            for i in author_tree.span(add_parent=True, expand_upwards=True):
                if family_member_count >= max_family_members:
                    break
                family_member_count += 1
            for i in target_tree.span(add_parent=True, expand_upwards=True):
                if family_member_count >= max_family_members:
                    break
                family_member_count += 1
            if family_member_count >= max_family_members:
                await lock.unlock()
                return await ctx.send(
                    f"If you added {target.mention} to your family, you'd have over {max_family_members} in your family. Sorry!",
                    allowed_mentions=localutils.only_mention(ctx.author),
                )

        # Set up the proposal
        try:
            result = await localutils.send_proposal_message(
                ctx,
                target,
                f"Hey, {target.mention}, {ctx.author.mention} wants to be your child! What do you think?",
                allow_bots=True,
            )
        except Exception:
            result = None
        if result is None:
            return await lock.unlock()

        # Database it up
        async with self.bot.database() as db:
            try:
                await db(
                    """INSERT INTO parents (parent_id, child_id, guild_id, timestamp) VALUES ($1, $2, $3, $4)""",
                    target.id,
                    ctx.author.id,
                    family_guild_id,
                    dt.utcnow(),
                )
            except asyncpg.UniqueViolationError:
                await lock.unlock()
                return await ctx.send(
                    "I ran into an error saving your family data - please try again later."
                )
        await ctx.send(
            f"I'm happy to introduce {ctx.author.mention} as your child, {target.mention}!",
            ignore_error=True)

        # And we're done
        target_tree._children.append(author_tree.id)
        author_tree._parent = target.id
        await re.publish('TreeMemberUpdate', author_tree.to_json())
        await re.publish('TreeMemberUpdate', target_tree.to_json())
        await re.disconnect()
        await lock.unlock()
Beispiel #22
0
    async def propose(self, ctx:utils.Context, *, target:localutils.converters.UnblockedMember):
        """
        Lets you propose to another Discord user.
        """

        # Get the family tree member objects
        family_guild_id = localutils.get_family_guild_id(ctx)
        author_tree, target_tree = localutils.FamilyTreeMember.get_multiple(ctx.author.id, target.id, guild_id=family_guild_id)

        # Check they're not themselves
        if target.id == ctx.author.id:
            return await ctx.send("That's you. You can't marry yourself.")

        # Check they're not a bot
        if target.bot:
            if target.id == self.bot.user.id:
                return await ctx.send("I think I could do better actually, but thank you!")
            return await ctx.send("That is a robot. Robots cannot consent to marriage.")

        # Lock those users
        re = await self.bot.redis.get_connection()
        try:
            lock = await localutils.ProposalLock.lock(re, ctx.author.id, target.id)
        except localutils.ProposalInProgress:
            return await ctx.send("Aren't you popular! One of you is already waiting on a proposal - please try again later.")

        # See if we're already married
        if author_tree._partner:
            await lock.unlock()
            return await ctx.send(
                f"Hey, {ctx.author.mention}, you're already married! Try divorcing your partner first \N{FACE WITH ROLLING EYES}",
                allowed_mentions=localutils.only_mention(ctx.author),
            )

        # See if the *target* is already married
        if target_tree._partner:
            await lock.unlock()
            return await ctx.send(
                f"Sorry, {ctx.author.mention}, it looks like {target.mention} is already married \N{PENSIVE FACE}",
                allowed_mentions=localutils.only_mention(ctx.author),
            )

        # See if they're already related
        async with ctx.channel.typing():
            relation = author_tree.get_relation(target_tree)
        if relation and localutils.guild_allows_incest(ctx) is False:
            await lock.unlock()
            return await ctx.send(
                f"Woah woah woah, it looks like you guys are already related! {target.mention} is your {relation}!",
                allowed_mentions=localutils.only_mention(ctx.author),
            )

        # Check the size of their trees
        # TODO I can make this a util because I'm going to use it a couple times
        max_family_members = localutils.get_max_family_members(ctx)
        async with ctx.channel.typing():
            family_member_count = 0
            for i in author_tree.span(add_parent=True, expand_upwards=True):
                if family_member_count >= max_family_members:
                    break
                family_member_count += 1
            for i in target_tree.span(add_parent=True, expand_upwards=True):
                if family_member_count >= max_family_members:
                    break
                family_member_count += 1
            if family_member_count >= max_family_members:
                await lock.unlock()
                return await ctx.send(
                    f"If you added {target.mention} to your family, you'd have over {max_family_members} in your family. Sorry!",
                    allowed_mentions=localutils.only_mention(ctx.author),
                )

        # Set up the proposal
        try:
            result = await localutils.send_proposal_message(
                ctx, target,
                f"Hey, {target.mention}, it would make {ctx.author.mention} really happy if you would marry them. What do you say?",
            )
        except Exception:
            result = None
        if result is None:
            return await lock.unlock()

        # They said yes!
        async with self.bot.database() as db:
            try:
                await db.start_transaction()
                await db(
                    "INSERT INTO marriages (user_id, partner_id, guild_id, timestamp) VALUES ($1, $2, $3, $4), ($2, $1, $3, $4)",
                    ctx.author.id, target.id, family_guild_id, dt.utcnow(),
                )
                await db.commit_transaction()
            except asyncpg.UniqueViolationError:
                await lock.unlock()
                return await ctx.send("I ran into an error saving your family data.")
        await ctx.send(f"I'm happy to introduce {target.mention} into the family of {ctx.author.mention}!")

        # Ping over redis
        author_tree._partner = target.id
        target_tree._partner = ctx.author.id
        await re.publish('TreeMemberUpdate', author_tree.to_json())
        await re.publish('TreeMemberUpdate', target_tree.to_json())
        await re.disconnect()
        await lock.unlock()
Beispiel #23
0
    async def treemaker(self,
                        ctx: utils.Context,
                        user_id: int,
                        stupid_tree: bool = False):
        """
        Handles the generation and sending of the tree to the user.
        """

        # Get their family tree
        user_info = localutils.FamilyTreeMember.get(
            user_id, localutils.get_family_guild_id(ctx))
        user_name = await localutils.DiscordNameManager.fetch_name_by_id(
            self.bot, user_id)

        # Make sure they have one
        if user_info.is_empty:
            if user_id == ctx.author.id:
                return await ctx.send(
                    f"You have no family to put into a tree .-.")
            return await ctx.send(
                f"**{localutils.escape_markdown(user_name)}** has no family to put into a tree .-.",
                allowed_mentions=discord.AllowedMentions.none(),
            )

        # Get their customisations
        async with self.bot.database() as db:
            ctu = await localutils.CustomisedTreeUser.fetch_by_id(
                db, ctx.author.id)

        # Get their dot script
        async with ctx.channel.typing():
            if stupid_tree:
                dot_code = await user_info.to_full_dot_script(self.bot, ctu)
            else:
                dot_code = await user_info.to_dot_script(self.bot, ctu)

        # Write the dot to a file
        dot_filename = f'{self.bot.config["tree_file_location"]}/{ctx.author.id}.gz'
        try:
            with open(dot_filename, 'w', encoding='utf-8') as a:
                a.write(dot_code)
        except Exception as e:
            self.logger.error(f"Could not write to {dot_filename}")
            raise e

        # Convert to an image
        image_filename = f'{self.bot.config["tree_file_location"].rstrip("/")}/{ctx.author.id}.png'
        dot = await asyncio.create_subprocess_exec('dot', '-Tpng:gd',
                                                   dot_filename, '-o',
                                                   image_filename,
                                                   '-Gcharset=UTF-8')
        await asyncio.wait_for(dot.wait(), 10.0, loop=self.bot.loop)

        # Kill subprocess
        try:
            dot.kill()
        except ProcessLookupError:
            pass  # It already died
        except Exception:
            raise

        # Send file
        try:
            file = discord.File(image_filename)
        except FileNotFoundError:
            return await ctx.send(
                "I was unable to send your family tree image - please try again later."
            )
        text = f"[Click here](https://marriagebot.xyz/) to customise your tree."
        if not stupid_tree:
            text += f" Use `{ctx.prefix}bloodtree` for your _entire_ family, including non-blood relatives."
        await ctx.send(text, file=file)

        # Delete the files
        self.bot.loop.create_task(
            asyncio.create_subprocess_exec('rm', dot_filename))
        self.bot.loop.create_task(
            asyncio.create_subprocess_exec('rm', image_filename))
    async def siblings(self,
                       ctx: vbu.Context,
                       user: vbu.converters.UserID = None):
        """
        Tells you who a user's siblings are.
        """

        # Get the user's info
        user_id = user or ctx.author.id
        user_name = await utils.DiscordNameManager.fetch_name_by_id(
            self.bot, user_id)
        user_info = utils.FamilyTreeMember.get(user_id,
                                               utils.get_family_guild_id(ctx))

        # Make sure they have a parent
        parent_id = user_info._parent
        if not parent_id:
            if user_id == ctx.author.id:
                return await ctx.send(
                    "You have no siblings.",
                    allowed_mentions=discord.AllowedMentions.none(),
                    wait=False,
                )
            return await ctx.send(
                f"**{utils.escape_markdown(user_name)}** has no siblings.",
                allowed_mentions=discord.AllowedMentions.none(),
                wait=False,
            )

        # Get parent's children
        parent_info = user_info.parent
        sibling_list = parent_info._children

        # Remove the user from the sibling list
        sibling_list = [
            sibling for sibling in sibling_list if sibling != user_id
        ]

        # If the user has no siblings
        if not sibling_list:
            if user_id == ctx.author.id:
                return await ctx.send(
                    "You have no siblings right now.",
                    allowed_mentions=discord.AllowedMentions.none(),
                    wait=False,
                )
            return await ctx.send(
                f"**{utils.escape_markdown(user_name)}** has no siblings right now.",
                allowed_mentions=discord.AllowedMentions.none(),
                wait=False,
            )

        # They do have siblings!
        sibling_plural = 'sibling' if len(sibling_list) == 1 else 'siblings'

        # Count the siblings
        output = f"**{utils.escape_markdown(user_name)}** has {len(sibling_list)} {sibling_plural}:\n"
        if user_id == ctx.author.id:
            output = f"You have {len(sibling_list)} {sibling_plural}:\n"

        # Get the name of the siblings
        sibling_list = [(
            await utils.DiscordNameManager.fetch_name_by_id(self.bot, sibling),
            sibling,
        ) for sibling in sibling_list]
        output += "\n".join([
            f"\N{BULLET} **{utils.escape_markdown(username)}** (`{uid}`)"
            for username, uid in sibling_list
        ])

        # Return all output
        if len(output) > 2_000:
            return await ctx.send(
                f"**{utils.escape_markdown(user_name)}**'s sibling list goes over 2,000 characters. Amazing.",
                wait=False)
        await ctx.send(output,
                       allowed_mentions=discord.AllowedMentions.none(),
                       wait=False)