from typing import Annotated from arcade.sdk import ToolContext, tool from arcade.sdk.auth import Reddit from arcade.sdk.errors import ToolExecutionError from arcade_reddit.client import RedditClient from arcade_reddit.enums import ( RedditTimeFilter, SubredditListingType, ) from arcade_reddit.utils import ( create_fullname_for_multiple_posts, create_path_for_post, normalize_subreddit_name, parse_get_content_of_multiple_posts_response, parse_get_content_of_post_response, parse_get_posts_in_subreddit_response, parse_get_top_level_comments_response, parse_subreddit_rules_response, parse_user_posts_response, remove_none_values, resolve_subreddit_access, ) @tool(requires_auth=Reddit(scopes=["read"])) async def get_posts_in_subreddit( context: ToolContext, subreddit: Annotated[str, "The name of the subreddit to fetch posts from"], listing: Annotated[ SubredditListingType, ( "The type of listing to fetch. For simple listings such as 'hot', 'new', or 'rising', " "the 'time_range' parameter is ignored. For time-based listings such as " "'top' or 'controversial', the 'time_range' parameter is required." ), ] = SubredditListingType.HOT, limit: Annotated[int, "The maximum number of posts to fetch. Default is 10, max is 100."] = 10, cursor: Annotated[str | None, "The pagination token from a previous call"] = None, time_range: Annotated[ RedditTimeFilter, "The time range for filtering posts. Must be provided if the listing type is " f"{SubredditListingType.TOP.value} or {SubredditListingType.CONTROVERSIAL.value}. " f"Otherwise, it is ignored. Defaults to {RedditTimeFilter.TODAY.value}.", ] = RedditTimeFilter.TODAY, ) -> Annotated[dict, "A dictionary with a cursor for the next page and a list of posts"]: """Gets posts titles, links, and other metadata in the specified subreddit The time_range is required if the listing type is 'top' or 'controversial'. """ client = RedditClient(context.get_auth_token_or_empty()) params = {"limit": limit, "after": cursor} if listing.is_time_based(): params["t"] = time_range.to_api_value() params = remove_none_values(params) subreddit = normalize_subreddit_name(subreddit) data = await client.get(f"r/{subreddit}/{listing.value}", params=params) result = parse_get_posts_in_subreddit_response(data) return result @tool(requires_auth=Reddit(scopes=["read"])) async def get_content_of_post( context: ToolContext, post_identifier: Annotated[ str, "The identifier of the Reddit post. " "The identifier may be a reddit URL to the post, a permalink to the post, " "a fullname for the post, or a post id.", ], ) -> Annotated[dict, "The content (body) of the Reddit post"]: """Get the content (body) of a Reddit post by its identifier.""" client = RedditClient(context.get_auth_token_or_empty()) path = create_path_for_post(post_identifier) data = await client.get(f"{path}.json") result = parse_get_content_of_post_response(data) return result @tool(requires_auth=Reddit(scopes=["read"])) async def get_content_of_multiple_posts( context: ToolContext, post_identifiers: Annotated[ list[str], "A list of Reddit post identifiers. " "The identifiers may be reddit URLs to the posts, permalinks to the posts, " "fullnames for the posts, or post ids. Must be less than or equal to 100 identifiers.", ], ) -> Annotated[dict, "A dictionary containing the content of multiple Reddit posts"]: """Get the content (body) of multiple Reddit posts by their identifiers. Efficiently retrieve the content of multiple posts in a single request. Always use this tool to retrieve more than one post's content. """ client = RedditClient(context.get_auth_token_or_empty()) fullnames, warnings = create_fullname_for_multiple_posts(post_identifiers) data = await client.get("api/info.json", params={"id": ",".join(fullnames)}) posts = parse_get_content_of_multiple_posts_response(data) return {"posts": posts, "warnings": warnings} @tool(requires_auth=Reddit(scopes=["read"])) async def get_top_level_comments( context: ToolContext, post_identifier: Annotated[ str, "The identifier of the Reddit post to fetch comments from. " "The identifier may be a reddit URL, a permalink, a fullname, or a post id.", ], ) -> Annotated[dict, "A dictionary with a list of top level comments"]: """Get the first page of top-level comments of a Reddit post.""" client = RedditClient(context.get_auth_token_or_empty()) path = create_path_for_post(post_identifier) data = await client.get(f"{path}.json") result = parse_get_top_level_comments_response(data) return result @tool(requires_auth=Reddit(scopes=["read"])) async def check_subreddit_access( context: ToolContext, subreddit: Annotated[str, "The name of the subreddit to check access for"], ) -> Annotated[ dict, "A dict indicating whether the subreddit exists and is accessible to the authenticated user", ]: """ Checks whether the specified subreddit exists and also if it is accessible to the authenticated user. Returns: {"exists": True, "accessible": True} if the subreddit exists and is accessible. {"exists": True, "accessible": False} if the subreddit exists but is private or restricted. {"exists": False, "accessible": False} if the subreddit does not exist. """ client = RedditClient(context.get_auth_token_or_empty()) return await resolve_subreddit_access(client, subreddit) @tool(requires_auth=Reddit(scopes=["read"])) async def get_subreddit_rules( context: ToolContext, subreddit: Annotated[str, "The name of the subreddit for which to fetch rules"], ) -> Annotated[dict, "A dictionary containing the subreddit rules"]: """Gets the rules of the specified subreddit""" client = RedditClient(context.get_auth_token_or_empty()) normalized_subreddit = normalize_subreddit_name(subreddit) data = await client.get(f"r/{normalized_subreddit}/about/rules") return parse_subreddit_rules_response(data) @tool(requires_auth=Reddit(scopes=["identity"])) async def get_my_username(context: ToolContext) -> str: """Get the Reddit username of the authenticated user""" client = RedditClient(context.get_auth_token_or_empty()) user_info = await client.get("api/v1/me") username: str = user_info.get("name", "") if not username: raise ToolExecutionError(message="Failed to retrieve the authenticated user's name") return username @tool(requires_auth=Reddit(scopes=["identity", "history", "read"])) async def get_my_posts( context: ToolContext, limit: Annotated[ int, "The maximum number of posts to fetch. Default is 10. Maximum is 100" ] = 10, include_body: Annotated[ bool, "Whether to include the body (content) of the posts. Defaults to True." ] = True, cursor: Annotated[str | None, "The pagination token from a previous call"] = None, ) -> Annotated[ dict, "A dictionary with a cursor for the next page and " "a list of posts created by the authenticated user", ]: """Get posts that were created by the authenticated user sorted by newest first""" client = RedditClient(context.get_auth_token_or_empty()) username = await get_my_username(context=context) params = {"limit": limit, "after": cursor} params = remove_none_values(params) posts_data = await client.get(f"user/{username}/submitted", params=params) return await parse_user_posts_response(context, posts_data, include_body)