Ejemplo n.º 1
0
    def _fetch_all_ids(self, url, pagelimit):
        """
        Return all user IDs returned by url, even from multiple pages.

        If not all users fit into one page returned by Habitica, a new query is
        run for the next page of users until all have been found.

        :url: Habitica API url for the interesting query
        :pagelimit: Maximum number of returned items per request.
        """
        user_ids = []
        last_id = None
        current_url = url
        while True:
            if last_id:
                current_url = "{}?lastId={}".format(url, last_id)
            data = utils.get_dict_from_api(self._header, current_url)

            for user in data:
                user_ids.append(user["id"])
            if len(data) < pagelimit:
                break
            else:
                last_id = data[len(data) - 1]["id"]

        return user_ids
Ejemplo n.º 2
0
 def _party_id(self):
     """
     Return the ID of the party user is currently in.
     """
     user_data = get_dict_from_api(self._header,
                                   "https://habitica.com/api/v3/user")
     return user_data["party"]["_id"]
Ejemplo n.º 3
0
    def join_quest(self):
        """
        If there's an unjoined quest, join it.

        :return: True if a quest was joined.
        """
        self._logger.debug("Checking if a quest can be joined.")
        questdata = get_dict_from_api(
            self._header, "https://habitica.com/api/v3/groups/party")["quest"]
        self._logger.debug("Quest information: %s", questdata)
        if ("key" in questdata and not questdata["active"]
                and (self._header["x-api-user"] not in questdata["members"]
                     or not questdata["members"][self._header["x-api-user"]])):
            self._logger.debug("New quest found")
            try:
                habrequest.post(
                    "https://habitica.com/api/v3/groups/party/quests/accept",
                    headers=self._header)
            # pylint: disable=invalid-name
            except requests.exceptions.HTTPError as e:
                self._logger.error("Quest joining failed: %s", str(e))
                # pylint: disable=raise-missing-from
                raise CommunicationFailedException(str(e))
            self._logger.info("Joined quest %s", questdata["key"])
            return True
        return False
Ejemplo n.º 4
0
 def party_description(self):
     """
     Return the description of the party
     """
     data = utils.get_dict_from_api(
         self._header, "https://habitica.com/api/v3/groups/party")
     return data["description"]
Ejemplo n.º 5
0
    def act(self, message):
        """
        Return a table containing quests and their owners.
        """
        partymember_uids = self._db_tool.get_party_user_ids()
        quests = {}
        for member_uid in partymember_uids:
            member_name = self._db_tool.get_loginname(member_uid)
            member_data = get_dict_from_api(
                HEADER,
                "https://habitica.com/api/v3/members/{}".format(member_uid))
            quest_counts = member_data["items"]["quests"]
            for quest_name in quest_counts:
                count = quest_counts[quest_name]
                if count == 1:
                    partymember_str = "@{}".format(member_name)
                elif count >= 1:
                    partymember_str = ("@{user} ({count})"
                                       "".format(user=member_name,
                                                 count=count))
                else:
                    continue

                if quest_name in quests:
                    quests[quest_name] = ", ".join(
                        [quests[quest_name], partymember_str])
                else:
                    quests[quest_name] = partymember_str

        content_lines = [
            "- **{}**: {}".format(quest, quests[quest]) for quest in quests
        ]
        return "\n".join(content_lines)
Ejemplo n.º 6
0
    def get_private_messages(self):
        """
        Fetch private messages using Habitica API.

        If there are new messages, they are written to the database and
        returned.

        No paging is implemented: all new messages are assumed to fit into the
        returned data from the API.
        """
        try:
            message_data = get_dict_from_api(
                self._header, "https://habitica.com/api/v3/inbox/messages")
        except requests.exceptions.HTTPError as err:
            raise CommunicationFailedException(err.response) from err

        messages = [None] * len(message_data)
        for i, message_dict in zip(range(len(message_data)), message_data):
            if message_dict["sent"]:
                recipient = message_dict["uuid"]
                sender = message_dict["ownerId"]
            else:
                recipient = message_dict["ownerId"]
                sender = message_dict["uuid"]
            messages[i] = PrivateMessage(sender,
                                         recipient,
                                         timestamp=timestamp_to_datetime(
                                             message_dict["timestamp"]),
                                         content=message_dict["text"],
                                         message_id=message_dict["id"])
        self._logger.debug("Fetched %d messages from Habitica API",
                           len(messages))
        self.add_PMs_to_db(messages)
Ejemplo n.º 7
0
    def get_party_messages(self):
        """
        Fetches party messages and stores them into the database.

        Both system messages (e.g. boss damage) and chat messages (sent by
        habiticians) are stored.
        """
        message_data = get_dict_from_api(
            self._header, "https://habitica.com/api/v3/groups/party/chat")
        messages = [None] * len(message_data)
        for i, message_dict in zip(range(len(message_data)), message_data):
            if "user" in message_dict:
                messages[i] = ChatMessage(
                    message_dict["uuid"],
                    message_dict["groupId"],
                    content=message_dict["text"],
                    message_id=message_dict["id"],
                    timestamp=datetime.utcfromtimestamp(
                        # Habitica saves party chat message times as unix time
                        # with three extra digits for milliseconds (no
                        # decimal separator)
                        message_dict["timestamp"] / 1000),
                    likers=self._marker_list(message_dict["likes"]),
                    flags=self._marker_list(message_dict["flags"]))
            else:
                messages[i] = SystemMessage(
                    message_dict["groupId"],
                    datetime.utcfromtimestamp(
                        # Habitica saves party chat message times as unix time
                        # with three extra digits for milliseconds (no
                        # decimal separator)
                        message_dict["timestamp"] / 1000),
                    content=message_dict["text"],
                    message_id=message_dict["id"],
                    likers=self._marker_list(message_dict["likes"]),
                    info=message_dict["info"])
        self._logger.debug("Fetched %d messages from Habitica API",
                           len(messages))

        new_messages = 0
        for message in messages:
            if isinstance(message, SystemMessage):
                new = self._write_system_message_to_db(message)
            elif isinstance(message, ChatMessage):
                new = self._write_chat_message_to_db(message)
            else:
                raise ValueError("Unexpected message type received from API")
            new_messages += 1 if new else 0
        self._logger.debug(
            "%d new chat/system messages written to the "
            "database", new_messages)
Ejemplo n.º 8
0
    def __init__(self, header, challenge_id):
        """
        Create a class for a challenge.

        :header: Header to use with API
        :challenge_id: The ID of the represented challenge
        """
        self.id = challenge_id  # pylint: disable=invalid-name
        self._header = header
        self._full_data = utils.get_dict_from_api(
            header,
            "https://habitica.com/api/v3/challenges/{}".format(challenge_id))
        self._party_tool = PartyTool(header)
        self._participants = None
        self._completers = None
Ejemplo n.º 9
0
    def _get_tasks(self, task_type=None):
        """
        Return a list of tasks.

        If task_type is given, only tasks of that type are returned.

        :task_type: None (for all tasks) or a Habitica task type ("habit",
                    "daily", or "todo)
        """
        if task_type not in ["habit", "daily", "todo", None]:
            raise ValueError("Task type {} not supported".format(task_type))

        url = "https://habitica.com/api/v3/tasks/user"
        tasks = get_dict_from_api(self._header, url)

        if task_type is None:
            return tasks

        matching_tasks = [task for task in tasks if task["type"] == task_type]
        return matching_tasks
Ejemplo n.º 10
0
    def newest_matching_challenge(self, must_haves, no_gos):
        """
        Return the newest challenge with a name that fits the given criteria.

        The challenge is chosen based on its title containing all strings in
        the given must_haves iterable and none that are in no_gos. If there are
        multiple matching challenges, the one that has the most recent "created
        at" time is returned.

        :must_haves: iterable of strings that must be present in the name
        :no_gos: iterable of strings that must not be present in the name
        :returns: A dict representing the newest matching challenge
        """
        challenges = utils.get_dict_from_api(
            self._header,
            "https://habitica.com/api/v3/challenges/groups/party")

        matching_challenge = None
        for challenge in challenges:
            name = challenge["name"]
            name_ok = True
            for substring in must_haves:
                if substring not in name:
                    name_ok = False
            for substring in no_gos:
                if substring in name:
                    name_ok = False

            if name_ok:
                if not matching_challenge:
                    matching_challenge = challenge
                else:
                    old_created = utils.timestamp_to_datetime(
                        matching_challenge["createdAt"])
                    new_created = utils.timestamp_to_datetime(
                        challenge["createdAt"])
                    if new_created > old_created:
                        matching_challenge = challenge

        return matching_challenge
Ejemplo n.º 11
0
    def __init__(self, user_id, header=None, profile_data=None):
        """
        Initialize the class with data from API/dict.

        If header is provided, data is fetched from the api, otherwise given
        profile_data is used.

        :user_id: User ID for the represented Habitica user
        :header: HTTP header for accessing Habitica API
        :profile_data: Dict containing the following data:
                        id: UID for the user (str)
                        displayname: Display name (str)
                        loginname: Login name (str)
                        birthday: Habitica birthday (datetime)
                        last_login: Last time the user logged in (datetime)
        """
        if header:
            profile_data = utils.get_dict_from_api(
                header,
                "https://habitica.com/api/v3/members/{}".format(user_id))
            self.id = profile_data["_id"]  # pylint: disable=invalid-name
            self.displayname = profile_data["profile"]["name"]
            self.login_name = profile_data["auth"]["local"]["username"]
            self.habitica_birthday = datetime.strptime(
                profile_data["auth"]["timestamps"]["created"],
                "%Y-%m-%dT%H:%M:%S.%fZ")
            self.last_login = datetime.strptime(
                profile_data["auth"]["timestamps"]["loggedin"],
                "%Y-%m-%dT%H:%M:%S.%fZ")
        elif profile_data:
            self.id = profile_data["id"]
            self.displayname = profile_data["displayname"]
            self.login_name = profile_data["loginname"]
            self.habitica_birthday = profile_data["birthday"]
            self.last_login = profile_data["last_login"]
        else:
            raise AttributeError("Either header or profile_data must be "
                                 "provided for initializing a Member.")
Ejemplo n.º 12
0
    def eligible_winners(self, challenge_id, user_ids):
        """
        Return a list of eligible challenge winners.

        Here, anyone who has completed all todo type tasks is eligible: habits
        or dailies are not inspected.

        :challenge_id: ID of challenge for which eligibility is assessed.
        :user_ids: A list of IDs for users whose eligibility is to be tested.
        :returns: A Member object.
        """
        eligible_winners = []
        for user_id in user_ids:
            progress_dict = utils.get_dict_from_api(
                self._header,
                "https://habitica.com/api/v3/challenges/{}/members/{}"
                "".format(challenge_id, user_id))
            eligible = True
            for task in progress_dict["tasks"]:
                if task["type"] == "todo" and not task["completed"]:
                    eligible = False
            if eligible:
                eligible_winners.append(Member(user_id, header=self._header))
        return eligible_winners
Ejemplo n.º 13
0
 def _get_user_data(self):
     """
     Return the full user data dict.
     """
     url = "https://habitica.com/api/v3/user"
     return get_dict_from_api(self._header, url)