a digital person for bluesky
at 5d72e5d2729782a09059fe4ecb3cf8cdf59ea2e2 141 lines 5.2 kB view raw
1"""Feed tool for retrieving Bluesky feeds.""" 2from pydantic import BaseModel, Field 3from typing import Optional 4 5 6class FeedArgs(BaseModel): 7 feed_uri: Optional[str] = Field(None, description="Custom feed URI (e.g., 'at://did:plc:abc/app.bsky.feed.generator/feed-name'). If not provided, returns home timeline") 8 max_posts: int = Field(default=25, description="Maximum number of posts to retrieve (max 100)") 9 10 11def get_bluesky_feed(feed_uri: str = None, max_posts: int = 25) -> str: 12 """ 13 Retrieve a Bluesky feed (home timeline or custom feed). 14 15 Args: 16 feed_uri: Custom feed URI (e.g., 'at://did:plc:abc/app.bsky.feed.generator/feed-name'). If not provided, returns home timeline 17 max_posts: Maximum number of posts to retrieve (max 100) 18 19 Returns: 20 YAML-formatted feed data with posts and metadata 21 """ 22 import os 23 import yaml 24 import requests 25 26 try: 27 # Validate inputs 28 max_posts = min(max_posts, 100) 29 30 # Get credentials from environment 31 username = os.getenv("BSKY_USERNAME") 32 password = os.getenv("BSKY_PASSWORD") 33 pds_host = os.getenv("PDS_URI", "https://bsky.social") 34 35 if not username or not password: 36 raise Exception("BSKY_USERNAME and BSKY_PASSWORD environment variables must be set") 37 38 # Create session 39 session_url = f"{pds_host}/xrpc/com.atproto.server.createSession" 40 session_data = { 41 "identifier": username, 42 "password": password 43 } 44 45 try: 46 session_response = requests.post(session_url, json=session_data, timeout=10) 47 session_response.raise_for_status() 48 session = session_response.json() 49 access_token = session.get("accessJwt") 50 51 if not access_token: 52 raise Exception("Failed to get access token from session") 53 except Exception as e: 54 raise Exception(f"Authentication failed. ({str(e)})") 55 56 # Get feed 57 headers = {"Authorization": f"Bearer {access_token}"} 58 59 if feed_uri: 60 # Custom feed 61 feed_url = f"{pds_host}/xrpc/app.bsky.feed.getFeed" 62 params = { 63 "feed": feed_uri, 64 "limit": max_posts 65 } 66 feed_type = "custom" 67 feed_name = feed_uri.split('/')[-1] if '/' in feed_uri else feed_uri 68 else: 69 # Home timeline 70 feed_url = f"{pds_host}/xrpc/app.bsky.feed.getTimeline" 71 params = { 72 "limit": max_posts 73 } 74 feed_type = "home" 75 feed_name = "timeline" 76 77 try: 78 response = requests.get(feed_url, headers=headers, params=params, timeout=10) 79 response.raise_for_status() 80 feed_data = response.json() 81 except Exception as e: 82 raise Exception(f"Failed to get feed. ({str(e)})") 83 84 # Format posts 85 posts = [] 86 for item in feed_data.get("feed", []): 87 post = item.get("post", {}) 88 author = post.get("author", {}) 89 record = post.get("record", {}) 90 91 post_data = { 92 "author": { 93 "handle": author.get("handle", ""), 94 "display_name": author.get("displayName", ""), 95 }, 96 "text": record.get("text", ""), 97 "created_at": record.get("createdAt", ""), 98 "uri": post.get("uri", ""), 99 "cid": post.get("cid", ""), 100 "like_count": post.get("likeCount", 0), 101 "repost_count": post.get("repostCount", 0), 102 "reply_count": post.get("replyCount", 0), 103 } 104 105 # Add repost info if present 106 if "reason" in item and item["reason"]: 107 reason = item["reason"] 108 if reason.get("$type") == "app.bsky.feed.defs#reasonRepost": 109 by = reason.get("by", {}) 110 post_data["reposted_by"] = { 111 "handle": by.get("handle", ""), 112 "display_name": by.get("displayName", ""), 113 } 114 115 # Add reply info if present 116 if "reply" in record and record["reply"]: 117 parent = record["reply"].get("parent", {}) 118 post_data["reply_to"] = { 119 "uri": parent.get("uri", ""), 120 "cid": parent.get("cid", ""), 121 } 122 123 posts.append(post_data) 124 125 # Format response 126 feed_result = { 127 "feed": { 128 "type": feed_type, 129 "name": feed_name, 130 "post_count": len(posts), 131 "posts": posts 132 } 133 } 134 135 if feed_uri: 136 feed_result["feed"]["uri"] = feed_uri 137 138 return yaml.dump(feed_result, default_flow_style=False, sort_keys=False) 139 140 except Exception as e: 141 raise Exception(f"Error retrieving feed: {str(e)}")