Module main

Expand source code
from __future__ import annotations
import enum
import asyncio
import time
import argparse
import parse

BIT_SIZE = 16
MUDA = 432610292342587392
EMOJI_FEMALE = "<:female:452463537508450304>"
EMOJI_MALE = "<:male:452470164529872899>"
EMOJI_KAKERA = "<:kakera:469835869059153940>"


class Waifu:
    """
    Represents a waifu from mudae.

    A lot of the attributes will often be null since they either:
        a. Are not applicable
        b. Can't be read from the waifu message
        c. Need to be fetched with the methods
    Attributes
    ----------
    mudae: discord.User
        The mudae bot that created this waifu.
    user: discord.Client
        The client that's using this waifu.
    message: discord.Message
        The waifu message the waifu came from.
    owner: discord.Member
        The member whose harem the waifu belongs to.
    creator: discord.Member
        The member that rolled this waifu.
    suitors: list[discord.Member]
        A list of members who wished the waifu.
    name: str
        The name of the waifu.
    series: str
        The series the waifu belongs to.
    kakera: int
        The kakera value of the waifu.
    key: int
        The key level of the waifu.
    claims: int
        The claims rank of the waifu.
    likes: int
        The likes rank of the waifu
    type: Waifu.Type
        The type of the waifu.
    image: str
        URL of the image, that the waifu message had, when the object was created.
    image_count: int
        How many images the waifu has available in total.
    image_index: int
        Image index of the image attribute with respect to the avaliable images.
    image_extra: int
        How many extra images have been added to the waifu.
    is_claimed: bool
        If the waifu has been claimed yet.
    is_girl: bool
        If the waifu is female or both female and male.
    Methods
    -------
    async fetch_extra()
        Fills the suitor and creator attributes.
    async await_claim()
        Waits for a member to claim this waifu, then returns with that member.
    """

    class Type(enum.Enum):
        """
        Represents the different types of waifus.
        Enums
        -----
        roll: 0
            The waifu was rolled e.g. created with $w.
        info: 1
            The waifu came from the info command e.g. created with $im.
        """

        roll = enum.auto()
        info = enum.auto()


    class Gender(enum.Enum):
        """
        Represents the different genders of waifus.
        Enums
        -----
        female: 0
            The waifu is female.
        male: 1
            The waifu is male.
        """

        female = enum.auto()
        male = enum.auto()

    def __init__(self, mudae, user, message):
        self.mudae = mudae
        self.message = message
        self.user = user
        self.suitors = []  # Needs to be fetched with fetch_extra
        self.name = None  # Name of the waifu, appears in the title of im and w
        self.series = None  # Series the waifu belongs to, appears in the description of im and w
        self.kakera = None  # Kakera value. Always appears in the description of w and optional in im
        self.claims = None  # Claims rank, appears in the description of w
        self.likes = None  # Likes rank, appears in the description of w
        self.owner = None  # Optionally appears in the footer of im and w
        self.image = None  # URL of the image, appears in im and w
        self.creator = None  # Needs to be fetched with fetch_extra
        self.gender = None  # Appears only in the description of im
        self.type = None

        # Message is missing parts to match against and can't be a match
        if message.author != self.mudae or not len(message.embeds) == 1 or message.embeds[0].image is None:
            raise TypeError("Message passed to the Waifu constructor it not a valid mudae message")

        embed = message.embeds[0]
        desc = embed.description
        lines = desc.split("\n")
        self.name = embed.author.name
        self.image = embed.image.url

        first_line = lines[0]
        if EMOJI_MALE in first_line:
            self.gender = self.Gender.male
            first_line = first_line.replace(EMOJI_MALE, "")
        elif EMOJI_FEMALE in first_line:
            self.gender = self.Gender.female
            first_line = first_line.replace(EMOJI_FEMALE, "")
        self.series = first_line.strip()

        if "React with any emoji to claim!" in lines:
            self.type = self.Type.roll
        else:
            self.type = self.Type.info

        for line in lines:
            if EMOJI_KAKERA in line:
                self.kakera = parse.search("**{}**", line)[0]
            elif "Claim Rank" in line:
                self.claims = parse.search("Claim Rank: #{:d}", line)[0]
            elif "Like Rank" in line:
                self.likes = parse.search("Like Rank: #{:d}", line)[0]

        footer = embed.footer.text
        if footer is not None:
            match = parse.search("Belongs to {} ~~", footer)
            if match is not None:
                self.owner = match[0].strip()
                self.is_claimed = True
            else:
                self.is_claimed = False

    async def fetch_extra(self):
        """
        Fills the suitor and creator attributes, by reading messages sent before and after the waifu.
        The suitor and creator attributes are by default empty and null respectively. To get the real values, this method must be called.
        The method will only work for waifus of type roll and only if the waifu was just rolled.
        """

        state = 0
        async for message in self.message.channel.history(limit=10):
            if state == 0:
                if message.id == self.message.id:
                    state = 1
            elif state == 1:
                state += 1
                if message.author != self.mudae:
                    self.creator = message.author
                    break
                elif "wished" in message.content.lower():
                    self.suitors = message.mentions
            elif state == 5:
                break
            else:
                state += 1
                if message.author != self.mudae:
                    self.creator = message.author
                    break

        # await asyncio.sleep(1)
        # UNTESTED ------------------->
        """
        self.message = await self.message.channel.fetch_message(self.message_id)
        if self.is_claimed and self.is_roll:
            for react in self.message.reactions:
                name = react.emoji.name
                if "kakera" in name:
                    name = name.replace("kakera", "")
                    if name == "":
                        name = "K"
                    self.ka_react = name
                    break
        """

    async def await_claim(self):
        """
        Waits for a member to claim this waifu, then returns with that member.
        If the waifu has already been claimed, the owner is returned immediately.
        If the waifu doesn't have an owner, the function will wait for up to 60s for someone to claim.
        Returns none if after 60s no one has claimed.
        Returns
        -------
        Waifu
            The waifu has an owner or one is found.
        None
            No owner could be found, the waifu wasn't claimed within 60s.
        """

        if self.is_claimed:
            return self.owner

        def check(message):
            return message.author == self.mudae and self.name in message.content and "are now married" in message.content.lower()

        try:
            message = await self.user.wait_for("message", timeout=60, check=check)
            user_name = message.content.split("**")[1]
            self.owner = message.guild.get_member_named(user_name)
            self.is_claimed = True
            return self.owner

        except asyncio.TimeoutError:
            return None

    def __str__(self):
        return self.name


class Mudae:
    """
    Represents a mudae bot. Primarily used as a factory for Waifu objects.
    Before doing anything with this class, make sure you've configured your mudae bot properly.
    Kakera value must be visible on rolls, for this class to be able to read the messages from mudae.
    If you want to check for claim or roll resets, you must provide the optional timing parameter
    ----------
    mudae: discord.User
        The mudea bot.
    user: discord.Client
        The client that's using this class.
    has_timing: bool
        If the module was created
    Methods
    -------
    waifu_from(message)
        Returns a waifu from a waifu message.
    is_wish(message, wishes, check_name, check_series)
        Checks if the waifu from a waifu message is part of a list of wishes.
    until_roll(in_seconds)
        Returns how much time there's left until the next roll reset.
    until_claim(in_seconds)
        Returns how much time there's left until the next claim reset.
    async wait_roll()
         Pauses until next roll reset.
    async wait_claim()
         Pauses until next claim reset.
    """

    def __init__(self, user, timing: int=None):
        """
        Parameters
        ----------
        user: discord.Client
            The client that's using this class.
        timing: int
            Value encoded with information on claim and roll resets. Use the get_timing method to create one.
        """

        self.user = user
        self.mudae = user.get_user(MUDA)

        if timing:
            timings = _split_timing(timing)
            self._roll_mod = timings[0]
            self._claim_mod = timings[1]
            self._roll_rem = timings[2]
            self._claim_rem = timings[3]
            self.has_timing = True
        else:
            self.has_timing = False

    def waifu_from(self, message):
        """
        Returns a waifu from a message.
        Currently two types of messages are supported, rolls and infos. Rolls are usually created with the $w command, and infoes with the $im command.
        If the message supplied is none of the two valid types of messages, or is not valid for another reason, none is returned.
        Parameters
        ----------
        message: discord.Message
            A discord message from mudae with a waifu (a waifu message).
        Returns
        -------
        Waifu
            A waifu created from the message.
        None
            The message isn't valid.
        """

        return Waifu(self.mudae, self.user, message)

    def from_wish(self, message, wishes: list[str], check_name: bool=True, check_series: bool=False):
        """
        Checks if the waifu from a waifu message is part of a list of wishes.
        If both check_name and check_series are true, the wishes will be checked against both name and series.
        If both are false, the function will always return false.
        Parameters
        ----------
        message: discord.Message
            A discord waifu message.
        wishes: list
            A list of strings, where each string is a wish.
        check_name: bool
            Whether the wishes are wishes for specific waifus.
        check_series: bool
            Whether the wishes are wishes for specific series.
        Returns
        -------
        bool
            Whether the waifu was wished.
        """
        waifu = self.waifu_from(message)
        wishes = map(lambda wish: wish.lower(), wishes)
        if not waifu:
            return None
        if check_name and waifu.name.lower() in wishes:
            return waifu
        if check_series and waifu.series.lower() in wishes:
            return waifu
        return False

    def until_roll(self, in_seconds: bool=False):
        """
        Returns how much time there's left until the next roll reset.
        Parameters
        ----------
        in_seconds: bool
            Whether the time returned should be in seconds or minutes.
        Returns
        -------
        int
            The time left until next roll reset.
        """

        if not self.has_timing:
            raise TypeError("Missing cooldown data")
        left = self._roll_rem - (int(time.time()) % self._roll_mod)
        if left < 0:
            left += self._roll_mod
        if not in_seconds:
            left = int(left / 60)
        return left

    def until_claim(self, in_seconds: bool=False):
        """
        Returns how much time there's left until the next claim reset.
        Parameters
        ----------
        in_seconds: bool
            Whether the time returned should be in seconds or minutes.
        Returns
        -------
        int
            The time left until next claim reset.
        """

        if not self.has_timing:
            raise TypeError("Missing cooldown data")
        left = self._claim_rem - (int(time.time()) % self._claim_mod)
        if left < 0:
            left += self._claim_mod
        if not in_seconds:
            left = int(left / 60)
        return left

    async def wait_roll(self):
        """
        Pauses until next roll reset.
        """

        await asyncio.sleep(5)
        await asyncio.sleep(self.until_roll(True))

    async def wait_claim(self):
        """
        Pauses until next claim reset.
        """

        await asyncio.sleep(5)
        await asyncio.sleep(self.until_claim(True))


def get_timing(roll_mod: int, claim_mod: int, roll_rem: int, claim_rem: int, in_seconds=False) -> int:
    """
     A static method that returns an integer from the supplied parameters.
     The integer may be provided to the Mudae constructor to enable roll and claim cool-down functionality
     Parameters
     ----------
     roll_mod: int
         The time period, between roll resets. The default mudae value for this is 60 min.
     claim_mod: int
         The time period, between claim resets. The default mudae value for this is 120 min.
     roll_rem: int
         The time period, from now until the next roll reset.
     claim_rem: int
         The time period, from now until the next claim reset.
     in_seconds: bool
         If the time periods are given as seconds or minutes.
     """

    if not in_seconds:
        roll_mod *= 60
        claim_mod *= 60
        roll_rem *= 60
        claim_rem *= 60

    timings = 0
    roll_rem = (int(time.time()) + roll_rem) % roll_mod
    claim_rem = (int(time.time()) + claim_rem) % claim_mod
    all_vals = (roll_mod, claim_mod, roll_rem, claim_rem)
    for value in reversed(all_vals):
        timings <<= BIT_SIZE
        timings += value
    return timings


def _split_timing(timing: int) -> tuple[int,...]:
    mask = (1 << BIT_SIZE) - 1
    nums = []
    for _ in range(4):
        nums.append(timing & mask)
        timing >>= BIT_SIZE
    return tuple(nums)


if __name__ == '__main__':
    parser = argparse.ArgumentParser()
    parser.add_argument("--rr", type=int, help="Time until next roll reset", required=True)
    parser.add_argument("--cr", type=int, help="Time until next claim reset", required=True)
    parser.add_argument("--rm", type=int, help="Time period between each roll reset (Defaults to 60)", default=60)
    parser.add_argument("--cm", type=int, help="Time period between each claim reset (Defaults to 180)", default=180)
    args = parser.parse_args()
    print(get_timing(args.rm, args.cm, args.rr, args.cr))

Functions

def get_timing(roll_mod: int, claim_mod: int, roll_rem: int, claim_rem: int, in_seconds=False) ‑> int

A static method that returns an integer from the supplied parameters. The integer may be provided to the Mudae constructor to enable roll and claim cool-down functionality Parameters


roll_mod : int
The time period, between roll resets. The default mudae value for this is 60 min.
claim_mod : int
The time period, between claim resets. The default mudae value for this is 120 min.
roll_rem : int
The time period, from now until the next roll reset.
claim_rem : int
The time period, from now until the next claim reset.
in_seconds : bool
If the time periods are given as seconds or minutes.
Expand source code
def get_timing(roll_mod: int, claim_mod: int, roll_rem: int, claim_rem: int, in_seconds=False) -> int:
    """
     A static method that returns an integer from the supplied parameters.
     The integer may be provided to the Mudae constructor to enable roll and claim cool-down functionality
     Parameters
     ----------
     roll_mod: int
         The time period, between roll resets. The default mudae value for this is 60 min.
     claim_mod: int
         The time period, between claim resets. The default mudae value for this is 120 min.
     roll_rem: int
         The time period, from now until the next roll reset.
     claim_rem: int
         The time period, from now until the next claim reset.
     in_seconds: bool
         If the time periods are given as seconds or minutes.
     """

    if not in_seconds:
        roll_mod *= 60
        claim_mod *= 60
        roll_rem *= 60
        claim_rem *= 60

    timings = 0
    roll_rem = (int(time.time()) + roll_rem) % roll_mod
    claim_rem = (int(time.time()) + claim_rem) % claim_mod
    all_vals = (roll_mod, claim_mod, roll_rem, claim_rem)
    for value in reversed(all_vals):
        timings <<= BIT_SIZE
        timings += value
    return timings

Classes

class Mudae (user, timing: int = None)

Represents a mudae bot. Primarily used as a factory for Waifu objects. Before doing anything with this class, make sure you've configured your mudae bot properly. Kakera value must be visible on rolls, for this class to be able to read the messages from mudae. If you want to check for claim or roll resets, you must provide the optional timing parameter


mudae: discord.User The mudea bot. user: discord.Client The client that's using this class. has_timing: bool If the module was created Methods


waifu_from(message) Returns a waifu from a waifu message. is_wish(message, wishes, check_name, check_series) Checks if the waifu from a waifu message is part of a list of wishes. until_roll(in_seconds) Returns how much time there's left until the next roll reset. until_claim(in_seconds) Returns how much time there's left until the next claim reset. async wait_roll() Pauses until next roll reset. async wait_claim() Pauses until next claim reset.

Parameters

user : discord.Client
The client that's using this class.
timing : int
Value encoded with information on claim and roll resets. Use the get_timing method to create one.
Expand source code
class Mudae:
    """
    Represents a mudae bot. Primarily used as a factory for Waifu objects.
    Before doing anything with this class, make sure you've configured your mudae bot properly.
    Kakera value must be visible on rolls, for this class to be able to read the messages from mudae.
    If you want to check for claim or roll resets, you must provide the optional timing parameter
    ----------
    mudae: discord.User
        The mudea bot.
    user: discord.Client
        The client that's using this class.
    has_timing: bool
        If the module was created
    Methods
    -------
    waifu_from(message)
        Returns a waifu from a waifu message.
    is_wish(message, wishes, check_name, check_series)
        Checks if the waifu from a waifu message is part of a list of wishes.
    until_roll(in_seconds)
        Returns how much time there's left until the next roll reset.
    until_claim(in_seconds)
        Returns how much time there's left until the next claim reset.
    async wait_roll()
         Pauses until next roll reset.
    async wait_claim()
         Pauses until next claim reset.
    """

    def __init__(self, user, timing: int=None):
        """
        Parameters
        ----------
        user: discord.Client
            The client that's using this class.
        timing: int
            Value encoded with information on claim and roll resets. Use the get_timing method to create one.
        """

        self.user = user
        self.mudae = user.get_user(MUDA)

        if timing:
            timings = _split_timing(timing)
            self._roll_mod = timings[0]
            self._claim_mod = timings[1]
            self._roll_rem = timings[2]
            self._claim_rem = timings[3]
            self.has_timing = True
        else:
            self.has_timing = False

    def waifu_from(self, message):
        """
        Returns a waifu from a message.
        Currently two types of messages are supported, rolls and infos. Rolls are usually created with the $w command, and infoes with the $im command.
        If the message supplied is none of the two valid types of messages, or is not valid for another reason, none is returned.
        Parameters
        ----------
        message: discord.Message
            A discord message from mudae with a waifu (a waifu message).
        Returns
        -------
        Waifu
            A waifu created from the message.
        None
            The message isn't valid.
        """

        return Waifu(self.mudae, self.user, message)

    def from_wish(self, message, wishes: list[str], check_name: bool=True, check_series: bool=False):
        """
        Checks if the waifu from a waifu message is part of a list of wishes.
        If both check_name and check_series are true, the wishes will be checked against both name and series.
        If both are false, the function will always return false.
        Parameters
        ----------
        message: discord.Message
            A discord waifu message.
        wishes: list
            A list of strings, where each string is a wish.
        check_name: bool
            Whether the wishes are wishes for specific waifus.
        check_series: bool
            Whether the wishes are wishes for specific series.
        Returns
        -------
        bool
            Whether the waifu was wished.
        """
        waifu = self.waifu_from(message)
        wishes = map(lambda wish: wish.lower(), wishes)
        if not waifu:
            return None
        if check_name and waifu.name.lower() in wishes:
            return waifu
        if check_series and waifu.series.lower() in wishes:
            return waifu
        return False

    def until_roll(self, in_seconds: bool=False):
        """
        Returns how much time there's left until the next roll reset.
        Parameters
        ----------
        in_seconds: bool
            Whether the time returned should be in seconds or minutes.
        Returns
        -------
        int
            The time left until next roll reset.
        """

        if not self.has_timing:
            raise TypeError("Missing cooldown data")
        left = self._roll_rem - (int(time.time()) % self._roll_mod)
        if left < 0:
            left += self._roll_mod
        if not in_seconds:
            left = int(left / 60)
        return left

    def until_claim(self, in_seconds: bool=False):
        """
        Returns how much time there's left until the next claim reset.
        Parameters
        ----------
        in_seconds: bool
            Whether the time returned should be in seconds or minutes.
        Returns
        -------
        int
            The time left until next claim reset.
        """

        if not self.has_timing:
            raise TypeError("Missing cooldown data")
        left = self._claim_rem - (int(time.time()) % self._claim_mod)
        if left < 0:
            left += self._claim_mod
        if not in_seconds:
            left = int(left / 60)
        return left

    async def wait_roll(self):
        """
        Pauses until next roll reset.
        """

        await asyncio.sleep(5)
        await asyncio.sleep(self.until_roll(True))

    async def wait_claim(self):
        """
        Pauses until next claim reset.
        """

        await asyncio.sleep(5)
        await asyncio.sleep(self.until_claim(True))

Methods

def from_wish(self, message, wishes: list[str], check_name: bool = True, check_series: bool = False)

Checks if the waifu from a waifu message is part of a list of wishes. If both check_name and check_series are true, the wishes will be checked against both name and series. If both are false, the function will always return false. Parameters


message : discord.Message
A discord waifu message.
wishes : list
A list of strings, where each string is a wish.
check_name : bool
Whether the wishes are wishes for specific waifus.
check_series : bool
Whether the wishes are wishes for specific series.

Returns

bool
Whether the waifu was wished.
Expand source code
def from_wish(self, message, wishes: list[str], check_name: bool=True, check_series: bool=False):
    """
    Checks if the waifu from a waifu message is part of a list of wishes.
    If both check_name and check_series are true, the wishes will be checked against both name and series.
    If both are false, the function will always return false.
    Parameters
    ----------
    message: discord.Message
        A discord waifu message.
    wishes: list
        A list of strings, where each string is a wish.
    check_name: bool
        Whether the wishes are wishes for specific waifus.
    check_series: bool
        Whether the wishes are wishes for specific series.
    Returns
    -------
    bool
        Whether the waifu was wished.
    """
    waifu = self.waifu_from(message)
    wishes = map(lambda wish: wish.lower(), wishes)
    if not waifu:
        return None
    if check_name and waifu.name.lower() in wishes:
        return waifu
    if check_series and waifu.series.lower() in wishes:
        return waifu
    return False
def until_claim(self, in_seconds: bool = False)

Returns how much time there's left until the next claim reset. Parameters


in_seconds : bool
Whether the time returned should be in seconds or minutes.

Returns

int
The time left until next claim reset.
Expand source code
def until_claim(self, in_seconds: bool=False):
    """
    Returns how much time there's left until the next claim reset.
    Parameters
    ----------
    in_seconds: bool
        Whether the time returned should be in seconds or minutes.
    Returns
    -------
    int
        The time left until next claim reset.
    """

    if not self.has_timing:
        raise TypeError("Missing cooldown data")
    left = self._claim_rem - (int(time.time()) % self._claim_mod)
    if left < 0:
        left += self._claim_mod
    if not in_seconds:
        left = int(left / 60)
    return left
def until_roll(self, in_seconds: bool = False)

Returns how much time there's left until the next roll reset. Parameters


in_seconds : bool
Whether the time returned should be in seconds or minutes.

Returns

int
The time left until next roll reset.
Expand source code
def until_roll(self, in_seconds: bool=False):
    """
    Returns how much time there's left until the next roll reset.
    Parameters
    ----------
    in_seconds: bool
        Whether the time returned should be in seconds or minutes.
    Returns
    -------
    int
        The time left until next roll reset.
    """

    if not self.has_timing:
        raise TypeError("Missing cooldown data")
    left = self._roll_rem - (int(time.time()) % self._roll_mod)
    if left < 0:
        left += self._roll_mod
    if not in_seconds:
        left = int(left / 60)
    return left
def waifu_from(self, message)

Returns a waifu from a message. Currently two types of messages are supported, rolls and infos. Rolls are usually created with the $w command, and infoes with the $im command. If the message supplied is none of the two valid types of messages, or is not valid for another reason, none is returned. Parameters


message : discord.Message
A discord message from mudae with a waifu (a waifu message).

Returns

Waifu
A waifu created from the message.
None
The message isn't valid.
Expand source code
def waifu_from(self, message):
    """
    Returns a waifu from a message.
    Currently two types of messages are supported, rolls and infos. Rolls are usually created with the $w command, and infoes with the $im command.
    If the message supplied is none of the two valid types of messages, or is not valid for another reason, none is returned.
    Parameters
    ----------
    message: discord.Message
        A discord message from mudae with a waifu (a waifu message).
    Returns
    -------
    Waifu
        A waifu created from the message.
    None
        The message isn't valid.
    """

    return Waifu(self.mudae, self.user, message)
async def wait_claim(self)

Pauses until next claim reset.

Expand source code
async def wait_claim(self):
    """
    Pauses until next claim reset.
    """

    await asyncio.sleep(5)
    await asyncio.sleep(self.until_claim(True))
async def wait_roll(self)

Pauses until next roll reset.

Expand source code
async def wait_roll(self):
    """
    Pauses until next roll reset.
    """

    await asyncio.sleep(5)
    await asyncio.sleep(self.until_roll(True))
class Waifu (mudae, user, message)

Represents a waifu from mudae.

A lot of the attributes will often be null since they either: a. Are not applicable b. Can't be read from the waifu message c. Need to be fetched with the methods Attributes


mudae : discord.User
The mudae bot that created this waifu.
user : discord.Client
The client that's using this waifu.
message : discord.Message
The waifu message the waifu came from.
owner : discord.Member
The member whose harem the waifu belongs to.
creator : discord.Member
The member that rolled this waifu.
suitors : list[discord.Member]
A list of members who wished the waifu.
name : str
The name of the waifu.
series : str
The series the waifu belongs to.
kakera : int
The kakera value of the waifu.
key : int
The key level of the waifu.
claims : int
The claims rank of the waifu.
likes : int
The likes rank of the waifu
type : Waifu.Type
The type of the waifu.
image : str
URL of the image, that the waifu message had, when the object was created.
image_count : int
How many images the waifu has available in total.
image_index : int
Image index of the image attribute with respect to the avaliable images.
image_extra : int
How many extra images have been added to the waifu.
is_claimed : bool
If the waifu has been claimed yet.
is_girl : bool
If the waifu is female or both female and male.

Methods

async fetch_extra() Fills the suitor and creator attributes. async await_claim() Waits for a member to claim this waifu, then returns with that member.

Expand source code
class Waifu:
    """
    Represents a waifu from mudae.

    A lot of the attributes will often be null since they either:
        a. Are not applicable
        b. Can't be read from the waifu message
        c. Need to be fetched with the methods
    Attributes
    ----------
    mudae: discord.User
        The mudae bot that created this waifu.
    user: discord.Client
        The client that's using this waifu.
    message: discord.Message
        The waifu message the waifu came from.
    owner: discord.Member
        The member whose harem the waifu belongs to.
    creator: discord.Member
        The member that rolled this waifu.
    suitors: list[discord.Member]
        A list of members who wished the waifu.
    name: str
        The name of the waifu.
    series: str
        The series the waifu belongs to.
    kakera: int
        The kakera value of the waifu.
    key: int
        The key level of the waifu.
    claims: int
        The claims rank of the waifu.
    likes: int
        The likes rank of the waifu
    type: Waifu.Type
        The type of the waifu.
    image: str
        URL of the image, that the waifu message had, when the object was created.
    image_count: int
        How many images the waifu has available in total.
    image_index: int
        Image index of the image attribute with respect to the avaliable images.
    image_extra: int
        How many extra images have been added to the waifu.
    is_claimed: bool
        If the waifu has been claimed yet.
    is_girl: bool
        If the waifu is female or both female and male.
    Methods
    -------
    async fetch_extra()
        Fills the suitor and creator attributes.
    async await_claim()
        Waits for a member to claim this waifu, then returns with that member.
    """

    class Type(enum.Enum):
        """
        Represents the different types of waifus.
        Enums
        -----
        roll: 0
            The waifu was rolled e.g. created with $w.
        info: 1
            The waifu came from the info command e.g. created with $im.
        """

        roll = enum.auto()
        info = enum.auto()


    class Gender(enum.Enum):
        """
        Represents the different genders of waifus.
        Enums
        -----
        female: 0
            The waifu is female.
        male: 1
            The waifu is male.
        """

        female = enum.auto()
        male = enum.auto()

    def __init__(self, mudae, user, message):
        self.mudae = mudae
        self.message = message
        self.user = user
        self.suitors = []  # Needs to be fetched with fetch_extra
        self.name = None  # Name of the waifu, appears in the title of im and w
        self.series = None  # Series the waifu belongs to, appears in the description of im and w
        self.kakera = None  # Kakera value. Always appears in the description of w and optional in im
        self.claims = None  # Claims rank, appears in the description of w
        self.likes = None  # Likes rank, appears in the description of w
        self.owner = None  # Optionally appears in the footer of im and w
        self.image = None  # URL of the image, appears in im and w
        self.creator = None  # Needs to be fetched with fetch_extra
        self.gender = None  # Appears only in the description of im
        self.type = None

        # Message is missing parts to match against and can't be a match
        if message.author != self.mudae or not len(message.embeds) == 1 or message.embeds[0].image is None:
            raise TypeError("Message passed to the Waifu constructor it not a valid mudae message")

        embed = message.embeds[0]
        desc = embed.description
        lines = desc.split("\n")
        self.name = embed.author.name
        self.image = embed.image.url

        first_line = lines[0]
        if EMOJI_MALE in first_line:
            self.gender = self.Gender.male
            first_line = first_line.replace(EMOJI_MALE, "")
        elif EMOJI_FEMALE in first_line:
            self.gender = self.Gender.female
            first_line = first_line.replace(EMOJI_FEMALE, "")
        self.series = first_line.strip()

        if "React with any emoji to claim!" in lines:
            self.type = self.Type.roll
        else:
            self.type = self.Type.info

        for line in lines:
            if EMOJI_KAKERA in line:
                self.kakera = parse.search("**{}**", line)[0]
            elif "Claim Rank" in line:
                self.claims = parse.search("Claim Rank: #{:d}", line)[0]
            elif "Like Rank" in line:
                self.likes = parse.search("Like Rank: #{:d}", line)[0]

        footer = embed.footer.text
        if footer is not None:
            match = parse.search("Belongs to {} ~~", footer)
            if match is not None:
                self.owner = match[0].strip()
                self.is_claimed = True
            else:
                self.is_claimed = False

    async def fetch_extra(self):
        """
        Fills the suitor and creator attributes, by reading messages sent before and after the waifu.
        The suitor and creator attributes are by default empty and null respectively. To get the real values, this method must be called.
        The method will only work for waifus of type roll and only if the waifu was just rolled.
        """

        state = 0
        async for message in self.message.channel.history(limit=10):
            if state == 0:
                if message.id == self.message.id:
                    state = 1
            elif state == 1:
                state += 1
                if message.author != self.mudae:
                    self.creator = message.author
                    break
                elif "wished" in message.content.lower():
                    self.suitors = message.mentions
            elif state == 5:
                break
            else:
                state += 1
                if message.author != self.mudae:
                    self.creator = message.author
                    break

        # await asyncio.sleep(1)
        # UNTESTED ------------------->
        """
        self.message = await self.message.channel.fetch_message(self.message_id)
        if self.is_claimed and self.is_roll:
            for react in self.message.reactions:
                name = react.emoji.name
                if "kakera" in name:
                    name = name.replace("kakera", "")
                    if name == "":
                        name = "K"
                    self.ka_react = name
                    break
        """

    async def await_claim(self):
        """
        Waits for a member to claim this waifu, then returns with that member.
        If the waifu has already been claimed, the owner is returned immediately.
        If the waifu doesn't have an owner, the function will wait for up to 60s for someone to claim.
        Returns none if after 60s no one has claimed.
        Returns
        -------
        Waifu
            The waifu has an owner or one is found.
        None
            No owner could be found, the waifu wasn't claimed within 60s.
        """

        if self.is_claimed:
            return self.owner

        def check(message):
            return message.author == self.mudae and self.name in message.content and "are now married" in message.content.lower()

        try:
            message = await self.user.wait_for("message", timeout=60, check=check)
            user_name = message.content.split("**")[1]
            self.owner = message.guild.get_member_named(user_name)
            self.is_claimed = True
            return self.owner

        except asyncio.TimeoutError:
            return None

    def __str__(self):
        return self.name

Class variables

var Gender

Represents the different genders of waifus. Enums


female: 0 The waifu is female. male: 1 The waifu is male.

var Type

Represents the different types of waifus. Enums


roll: 0 The waifu was rolled e.g. created with $w. info: 1 The waifu came from the info command e.g. created with $im.

Methods

async def await_claim(self)

Waits for a member to claim this waifu, then returns with that member. If the waifu has already been claimed, the owner is returned immediately. If the waifu doesn't have an owner, the function will wait for up to 60s for someone to claim. Returns none if after 60s no one has claimed. Returns


Waifu
The waifu has an owner or one is found.
None
No owner could be found, the waifu wasn't claimed within 60s.
Expand source code
async def await_claim(self):
    """
    Waits for a member to claim this waifu, then returns with that member.
    If the waifu has already been claimed, the owner is returned immediately.
    If the waifu doesn't have an owner, the function will wait for up to 60s for someone to claim.
    Returns none if after 60s no one has claimed.
    Returns
    -------
    Waifu
        The waifu has an owner or one is found.
    None
        No owner could be found, the waifu wasn't claimed within 60s.
    """

    if self.is_claimed:
        return self.owner

    def check(message):
        return message.author == self.mudae and self.name in message.content and "are now married" in message.content.lower()

    try:
        message = await self.user.wait_for("message", timeout=60, check=check)
        user_name = message.content.split("**")[1]
        self.owner = message.guild.get_member_named(user_name)
        self.is_claimed = True
        return self.owner

    except asyncio.TimeoutError:
        return None
async def fetch_extra(self)

Fills the suitor and creator attributes, by reading messages sent before and after the waifu. The suitor and creator attributes are by default empty and null respectively. To get the real values, this method must be called. The method will only work for waifus of type roll and only if the waifu was just rolled.

Expand source code
async def fetch_extra(self):
    """
    Fills the suitor and creator attributes, by reading messages sent before and after the waifu.
    The suitor and creator attributes are by default empty and null respectively. To get the real values, this method must be called.
    The method will only work for waifus of type roll and only if the waifu was just rolled.
    """

    state = 0
    async for message in self.message.channel.history(limit=10):
        if state == 0:
            if message.id == self.message.id:
                state = 1
        elif state == 1:
            state += 1
            if message.author != self.mudae:
                self.creator = message.author
                break
            elif "wished" in message.content.lower():
                self.suitors = message.mentions
        elif state == 5:
            break
        else:
            state += 1
            if message.author != self.mudae:
                self.creator = message.author
                break

    # await asyncio.sleep(1)
    # UNTESTED ------------------->
    """
    self.message = await self.message.channel.fetch_message(self.message_id)
    if self.is_claimed and self.is_roll:
        for react in self.message.reactions:
            name = react.emoji.name
            if "kakera" in name:
                name = name.replace("kakera", "")
                if name == "":
                    name = "K"
                self.ka_react = name
                break
    """