users.pyā¢7.41 kB
"""
User-related tools for the Product Hunt MCP server.
"""
import logging
from typing import Any, Dict
from product_hunt_mcp.api.client import execute_graphql_query
from product_hunt_mcp.api.queries import USER_POSTS_QUERY, USER_QUERY, USER_VOTED_POSTS_QUERY, VIEWER_QUERY
from product_hunt_mcp.schemas.validation import USER_SCHEMA
from product_hunt_mcp.utils.common import (
    apply_pagination_defaults,
    check_data_exists,
    execute_and_check_query,
    format_response,
    handle_errors,
    require_token,
)
from product_hunt_mcp.utils.validation import validate_with_schema
logger = logging.getLogger("ph_mcp")
def register_user_tools(mcp):
    """Register user-related tools with the MCP server."""
    @mcp.tool()
    @require_token
    @handle_errors
    @validate_with_schema(USER_SCHEMA)
    def get_user(
        id: str = None,
        username: str = None,
        posts_type: str = None,
        posts_count: int = None,
        posts_after: str = None,
    ) -> Dict[str, Any]:
        """
        Retrieve user information by ID or username, with optional retrieval of their posts.
        Parameters:
        - id (str, optional): The user's unique ID.
        - username (str, optional): The user's username.
        - posts_type (str, optional): Type of posts to retrieve. Valid values: MADE (default), VOTED.
        - posts_count (int, optional): Number of posts to return (default: 10, max: 20).
        - posts_after (str, optional): Pagination cursor for next page of posts.
        At least one of `id` or `username` must be provided.
        Returns:
        - success (bool)
        - data (dict): If successful, contains user details and optionally their posts.
        - error (dict, optional)
        - rate_limits (dict)
        Notes:
        - Returns an error if neither `id` nor `username` is provided, or if the user is not found.
        """
        params = {
            k: v
            for k, v in {
                "id": id,
                "username": username,
                "posts_type": posts_type,
                "posts_count": posts_count,
                "posts_after": posts_after,
            }.items()
            if v is not None
        }
        logger.info("users.get_user called", extra=params)
        # Determine if posts are being requested
        requesting_posts = posts_type is not None or posts_count is not None
        # Apply sensible defaults if posts are being requested
        if requesting_posts:
            posts_type = posts_type or "MADE"
            posts_count = posts_count or 10
        else:
            posts_type = None
            posts_count = None
        # Set up common variables
        variables = {}
        if id:
            variables["id"] = id
        if username:
            variables["username"] = username
        # Normalize posts_type
        posts_type = posts_type.upper() if posts_type else None
        # Case 1: Basic user info (no posts requested)
        if not requesting_posts:
            result, rate_limits, error = execute_graphql_query(USER_QUERY, variables)
            if error:
                return format_response(False, error=error, rate_limits=rate_limits)
            if not check_data_exists(result["data"], "user"):
                id_or_username = id or username
                return format_response(
                    False,
                    error={
                        "code": "NOT_FOUND",
                        "message": f"User with ID/username '{id_or_username}' not found",
                    },
                    rate_limits=rate_limits,
                )
            return format_response(True, data=result["data"]["user"], rate_limits=rate_limits)
        # Case 2 & 3: Posts requested (made or voted)
        # Set up pagination
        pagination = apply_pagination_defaults(posts_count, posts_after)
        variables.update(pagination)
        # Choose query based on posts_type
        if posts_type == "MADE":
            query = USER_POSTS_QUERY
        elif posts_type == "VOTED":
            query = USER_VOTED_POSTS_QUERY
        else:
            return format_response(
                False,
                error={
                    "code": "INVALID_PARAMETER",
                    "message": f"Invalid posts_type: {posts_type}. Valid values are MADE, VOTED.",
                },
            )
        # Execute the appropriate query
        result, rate_limits, error = execute_graphql_query(query, variables)
        if error:
            return format_response(False, error=error, rate_limits=rate_limits)
        if not check_data_exists(result["data"], "user"):
            id_or_username = id or username
            return format_response(
                False,
                error={
                    "code": "NOT_FOUND",
                    "message": f"User with ID/username '{id_or_username}' not found",
                },
                rate_limits=rate_limits,
            )
        user_data = result["data"]["user"]
        # Format response based on the query type
        if posts_type == "MADE" and check_data_exists(user_data, "madePosts"):
            posts_data = user_data["madePosts"]
            response_data = {
                "id": user_data["id"],
                "posts": posts_data
            }
            return format_response(True, data=response_data, rate_limits=rate_limits)
        elif posts_type == "VOTED" and check_data_exists(user_data, "votedPosts"):
            posts_data = user_data["votedPosts"]
            response_data = {
                "id": user_data["id"],
                "posts": posts_data
            }
            return format_response(True, data=response_data, rate_limits=rate_limits)
        # If we get here, the posts field wasn't in the response, just return what we have
        return format_response(True, data=user_data, rate_limits=rate_limits)
    @mcp.tool()
    @require_token
    @handle_errors
    def get_viewer() -> Dict[str, Any]:
        """
        Retrieve information about the currently authenticated user.
        Parameters:
        - None
        Returns:
        - success (bool)
        - data (dict): If successful, contains user details.
        - error (dict, optional)
        - rate_limits (dict)
        Notes:
        - Returns an error if the token is invalid or expired.
        """
        logger.info("users.get_viewer called")
        result, rate_limits, error = execute_graphql_query(VIEWER_QUERY)
        if error:
            return format_response(False, error=error, rate_limits=rate_limits)
        # Check if viewer info exists
        viewer_exists = check_data_exists(result["data"], "viewer")
        if not viewer_exists:
            return format_response(
                False,
                error={
                    "code": "AUTHENTICATION_ERROR",
                    "message": "Unable to get viewer information. Token may be invalid or expired.",
                },
                rate_limits=rate_limits,
            )
        # Extract viewer data
        viewer_data = result["data"]["viewer"]
        # Check if the user field exists for nested viewer structure
        if "user" in viewer_data and viewer_data["user"] is not None:
            viewer_data = viewer_data["user"]
        return format_response(True, data=viewer_data, rate_limits=rate_limits)