···1111 Fetch posts from a Bluesky feed (user, following, or custom feed) with automatic pagination
1212 and transform into compact agent-friendly format.
13131414+ IMPORTANT: The feed_type determines which feed to fetch from.
1515+ ✅ Correct: feed_type="user", actor="alice.bsky.social"
1616+ ✅ Correct: feed_type="following"
1717+ ✅ Correct: feed_type="custom", feed_id="at://did:plc:xyz/app.bsky.feed.generator/abc"
1818+ ❌ Wrong: feed_type="user" (without actor parameter)
1919+ ❌ Wrong: feed_type="custom" (without feed_id parameter)
2020+ ❌ Wrong: feed_type="timeline" (not a valid type)
2121+ ❌ Wrong: limit=150 (exceeds maximum of 100)
2222+1423 Args:
1515- feed_type: Type of feed to fetch. Must be one of: "user", "following", or "custom"
1616- actor: The handle or DID of the user. Required when feed_type is "user"
1717- feed_id: The AT URI of the custom feed. Required when feed_type is "custom"
1818- limit: Maximum number of posts to retrieve (1-100). Default is 25
2424+ feed_type (str): Type of feed to fetch. Must be "user", "following", or "custom".
2525+ - "user": Fetch posts from a specific user's profile
2626+ - "following": Fetch posts from accounts you follow (your timeline)
2727+ - "custom": Fetch posts from a custom algorithm feed
2828+ actor (Optional[str]): The handle (e.g., "alice.bsky.social") or DID of the user.
2929+ Required when feed_type is "user". Leave as None for other feed types.
3030+ feed_id (Optional[str]): The AT URI of the custom feed (e.g., "at://did:plc:.../app.bsky.feed.generator/...").
3131+ Required when feed_type is "custom". Leave as None for other feed types.
3232+ limit (int): Maximum number of posts to retrieve. Must be between 1-100. Default is 25.
3333+ Higher limits may take longer but return more posts.
19342035 Returns:
2121- A dictionary containing the status, feed metadata, and a list of compact post objects
3636+ Dict: On success, returns dict with "status"="success", feed metadata, and "posts" list.
3737+ On error, returns dict with "status"="error" and "message" describing the issue.
3838+3939+ What you can do:
4040+ ✓ Fetch posts from any user's profile by handle or DID
4141+ ✓ Fetch your following timeline
4242+ ✓ Fetch posts from custom algorithm feeds
4343+ ✓ Retrieve 1-100 posts with automatic pagination
4444+ ✓ Get compact post data with author, message, URI, and engagement metrics
4545+ ✓ Access post URIs for use with other tools (like, repost, reply, etc.)
4646+4747+ Examples:
4848+ Fetch posts from a specific user:
4949+ fetch_bluesky_posts(feed_type="user", actor="bsky.app", limit=10)
5050+5151+ Fetch your following timeline:
5252+ fetch_bluesky_posts(feed_type="following", limit=50)
5353+5454+ Fetch from a custom feed:
5555+ fetch_bluesky_posts(
5656+ feed_type="custom",
5757+ feed_id="at://did:plc:z72i7hdynmk6r22z27h6tvur/app.bsky.feed.generator/whats-hot",
5858+ limit=25
5959+ )
6060+6161+ Get recent posts from a user by handle:
6262+ fetch_bluesky_posts(feed_type="user", actor="alice.bsky.social")
6363+6464+ Get posts from a user by DID:
6565+ fetch_bluesky_posts(feed_type="user", actor="did:plc:abc123xyz")
2266 """
2367 try:
2468 from atproto import Client, models
25692670 if limit < 1 or limit > 100:
2727- raise ValueError("Limit must be between 1 and 100.")
7171+ return {
7272+ "status": "error",
7373+ "message": f"Limit must be between 1 and 100, but received {limit}. Adjust the limit and try again."
7474+ }
28752976 username = os.environ.get("BSKY_USERNAME")
3077 password = os.environ.get("BSKY_APP_PASSWORD")
3178 if not username or not password:
3232- raise EnvironmentError("BSKY_USERNAME and BSKY_APP_PASSWORD must be set.")
7979+ return {
8080+ "status": "error",
8181+ "message": "Environment variables BSKY_USERNAME and BSKY_APP_PASSWORD are not set. Set these variables with your Bluesky credentials."
8282+ }
33833484 client = Client()
3585 client.login(username, password)
···4393 # Fetch posts using your working logic
4494 if feed_type == "user":
4595 if not actor:
4646- raise ValueError("Actor must be specified for user feed.")
9696+ return {
9797+ "status": "error",
9898+ "message": "Actor must be specified for user feed. Provide a handle like 'alice.bsky.social' or a DID."
9999+ }
47100 params = models.AppBskyFeedGetAuthorFeed.Params(
48101 actor=actor,
49102 limit=min(50, remaining),
···58111 resp = client.app.bsky.feed.get_timeline(params)
59112 elif feed_type == "custom":
60113 if not feed_id:
6161- raise ValueError("feed_id must be specified for custom feed.")
114114+ return {
115115+ "status": "error",
116116+ "message": "feed_id must be specified for custom feed. Provide an AT URI like 'at://did:plc:.../app.bsky.feed.generator/...'."
117117+ }
62118 params = models.AppBskyFeedGetFeed.Params(
63119 feed=feed_id,
64120 limit=min(50, remaining),
···66122 )
67123 resp = client.app.bsky.feed.get_feed(params)
68124 else:
6969- raise ValueError(f"Unsupported feed_type: {feed_type}")
125125+ return {
126126+ "status": "error",
127127+ "message": f"Unsupported feed_type: '{feed_type}'. Must be 'user', 'following', or 'custom'."
128128+ }
7012971130 # Append raw posts from current page
72131 posts.extend([item.model_dump() for item in resp.feed])
···108167 }
109168110169 except ImportError:
111111- raise ImportError("atproto package not installed. Install with: pip install atproto")
170170+ return {
171171+ "status": "error",
172172+ "message": "The atproto package is not installed. Install it with: pip install atproto"
173173+ }
112174 except Exception as e:
113113- raise RuntimeError(f"Error fetching Bluesky posts: {e}")
175175+ return {
176176+ "status": "error",
177177+ "message": f"Failed to fetch Bluesky posts: {str(e)}. Check the error details and try again."
178178+ }