+171
-19
tools/bluesky/update_bluesky_connection.py
+171
-19
tools/bluesky/update_bluesky_connection.py
···
1
1
import os
2
2
from typing import Dict, Literal
3
+
from atproto import Client, models, AtUri
4
+
3
5
4
6
def update_bluesky_connection(
5
7
change_type: Literal["follow", "unfollow", "mute", "unmute", "block", "unblock"],
6
8
actor: str
7
9
) -> Dict:
8
10
"""
9
-
Update connection status with a Bluesky user (follow, unfollow, mute, unmute, block, unblock).
11
+
Manage social connections with Bluesky users through follow, mute, and block actions.
12
+
13
+
This tool allows you to update your relationship with other Bluesky users by following/
14
+
unfollowing them, muting/unmuting their content, or blocking/unblocking them. The tool
15
+
intelligently checks the current connection state before taking action and reports whether
16
+
changes were made. Use this tool to manage your social graph and control what content
17
+
you see on Bluesky.
18
+
19
+
Connection types explained:
20
+
- Follow/Unfollow: Subscribe or unsubscribe from a user's posts in your feed
21
+
- Mute/Unmute: Hide a user's posts and replies from your feed without unfollowing
22
+
(they won't be notified). Muting is softer than blocking.
23
+
- Block/Unblock: Prevent all interaction with a user bidirectionally. Blocking prevents
24
+
them from seeing your content and you from seeing theirs. This is the strongest action.
10
25
11
26
Args:
12
-
change_type (str): Type of connection change - "follow", "unfollow", "mute", "unmute", "block", or "unblock".
13
-
actor (str): The actor identifier (DID like 'did:plc:...' or handle like 'user.bsky.social').
27
+
change_type (Literal): The type of connection change to make. Must be one of:
28
+
29
+
- "follow": Subscribe to the user's posts. Their content will appear in your feed.
30
+
The user will be notified that you followed them.
31
+
32
+
- "unfollow": Unsubscribe from the user's posts. Their content will no longer appear
33
+
in your feed. The user may notice you unfollowed them.
34
+
35
+
- "mute": Hide the user's posts and replies from your feed without unfollowing them.
36
+
This is private - the user is not notified. Use this to reduce noise while
37
+
maintaining the connection. The user can still interact with your content.
38
+
39
+
- "unmute": Resume seeing the user's posts and replies in your feed. Removes a
40
+
previous mute. This is private - the user is not notified.
41
+
42
+
- "block": Prevent all interaction with the user bidirectionally. They cannot see
43
+
your posts or interact with them, and you cannot see theirs. This is the strongest
44
+
moderation action. The user may infer they've been blocked.
45
+
46
+
- "unblock": Remove a block and restore the ability for both of you to see and
47
+
interact with each other's content. Does not automatically re-follow.
48
+
49
+
This parameter is required and must be exactly one of the values listed above.
50
+
51
+
actor (str): The identifier for the Bluesky user to update the connection with.
52
+
This parameter is required and cannot be empty.
53
+
54
+
Accepts two formats:
55
+
- Handle: The user's full handle including domain suffix
56
+
Examples: "alice.bsky.social", "nytimes.com", "user.example.org"
57
+
- DID: The user's decentralized identifier starting with "did:plc:"
58
+
Example: "did:plc:z72i7hdynmk6r22z27h6tvur"
59
+
60
+
IMPORTANT: The tool automatically resolves handles to DIDs before performing
61
+
any operations. If you provide a handle, the tool will first fetch the user's
62
+
profile to get their DID, then use the DID for all connection operations. This
63
+
ensures reliability and consistency. If you already have a DID, it will be used
64
+
directly without additional lookups.
65
+
66
+
You can obtain actor identifiers from tools like search_bluesky or
67
+
get_bluesky_user_info.
14
68
15
69
Returns:
16
-
Dict: Status and details of the connection change.
70
+
Dict: A dictionary containing the operation result with the following keys:
71
+
72
+
Common keys (all operations):
73
+
- status (str): Either "success" or "error"
74
+
- change_type (str): The type of change that was requested
75
+
- actor (dict): Information about the target user with keys:
76
+
- did (str): The user's DID
77
+
- handle (str): The user's handle
78
+
- display_name (str): The user's display name
79
+
- message (str): Human-readable description of what happened
80
+
- action_taken (bool): Whether a change was actually made (False if already
81
+
in the desired state)
82
+
83
+
For errors:
84
+
- message (str): Human-readable error description with guidance
85
+
86
+
Examples:
87
+
# Follow a user
88
+
update_bluesky_connection(
89
+
change_type="follow",
90
+
actor="alice.bsky.social"
91
+
)
92
+
93
+
# Unfollow a user
94
+
update_bluesky_connection(
95
+
change_type="unfollow",
96
+
actor="bob.bsky.social"
97
+
)
98
+
99
+
# Mute a user to hide their content without unfollowing
100
+
update_bluesky_connection(
101
+
change_type="mute",
102
+
actor="noisy.bsky.social"
103
+
)
104
+
105
+
# Unmute a previously muted user
106
+
update_bluesky_connection(
107
+
change_type="unmute",
108
+
actor="noisy.bsky.social"
109
+
)
110
+
111
+
# Block a user (strongest moderation action)
112
+
update_bluesky_connection(
113
+
change_type="block",
114
+
actor="spammer.bsky.social"
115
+
)
116
+
117
+
# Unblock a previously blocked user
118
+
update_bluesky_connection(
119
+
change_type="unblock",
120
+
actor="spammer.bsky.social"
121
+
)
122
+
123
+
# Use DID instead of handle
124
+
update_bluesky_connection(
125
+
change_type="follow",
126
+
actor="did:plc:z72i7hdynmk6r22z27h6tvur"
127
+
)
17
128
"""
18
129
try:
19
-
from atproto import Client, models, AtUri
20
-
21
130
if not actor:
22
-
raise ValueError("Actor identifier must be provided.")
131
+
return {
132
+
"status": "error",
133
+
"message": "Error: The actor parameter is empty. To resolve this, provide a valid user identifier "
134
+
"such as a handle (like 'alice.bsky.social') or DID (like 'did:plc:...'). You can obtain "
135
+
"user identifiers from tools like search_bluesky or get_bluesky_user_info. This is a common "
136
+
"mistake and can be fixed by calling the tool again with a valid actor."
137
+
}
23
138
24
139
if change_type not in ["follow", "unfollow", "mute", "unmute", "block", "unblock"]:
25
-
raise ValueError(f"Invalid change_type: {change_type}. Must be one of: follow, unfollow, mute, unmute, block, unblock.")
140
+
return {
141
+
"status": "error",
142
+
"message": f"Error: The change_type '{change_type}' is not valid. To resolve this, use one of the "
143
+
f"supported values: 'follow', 'unfollow', 'mute', 'unmute', 'block', or 'unblock'. Each "
144
+
f"type manages a different aspect of your connection with the user. This is a parameter "
145
+
f"validation error that can be fixed by using a supported change type."
146
+
}
26
147
27
148
username = os.environ.get("BSKY_USERNAME")
28
149
password = os.environ.get("BSKY_APP_PASSWORD")
29
150
if not username or not password:
30
-
raise EnvironmentError("BSKY_USERNAME and BSKY_APP_PASSWORD must be set.")
151
+
return {
152
+
"status": "error",
153
+
"message": "Error: Missing Bluesky authentication credentials. The BSKY_USERNAME and BSKY_APP_PASSWORD "
154
+
"environment variables are not set. To resolve this, ask the user to configure these environment "
155
+
"variables with valid Bluesky credentials. This is a configuration issue that the user needs to "
156
+
"address before you can manage connections on Bluesky."
157
+
}
31
158
32
159
client = Client()
33
160
client.login(username, password)
34
161
35
-
# Get user profile to verify they exist and get current status
36
-
profile = client.get_profile(actor=actor)
162
+
# Get user profile to verify they exist, resolve handle to DID, and get current status
163
+
try:
164
+
profile = client.get_profile(actor=actor)
165
+
except Exception as e:
166
+
return {
167
+
"status": "error",
168
+
"message": f"Error: Failed to find user with identifier '{actor}'. To resolve this, verify the handle "
169
+
f"or DID is correct and that the user exists. Common issues include typos in the handle, "
170
+
f"deleted or suspended accounts, or using an incomplete handle (missing the domain like "
171
+
f"'.bsky.social'). You can use search_bluesky to find valid user identifiers. "
172
+
f"API details: {str(e)}"
173
+
}
174
+
175
+
# Extract DID for all operations (ensures we always use DID regardless of input format)
176
+
user_did = profile.did
37
177
38
178
# Check current viewer state
39
179
viewer = profile.viewer if profile.viewer else None
···
48
188
}
49
189
}
50
190
51
-
# Perform the requested action
191
+
# Perform the requested action (always using DID)
52
192
if change_type == "follow":
53
193
if viewer and viewer.following:
54
194
result["message"] = f"Already following {profile.handle}."
55
195
result["action_taken"] = False
56
196
else:
57
-
client.follow(actor)
197
+
client.follow(user_did)
58
198
result["message"] = f"Successfully followed {profile.handle}."
59
199
result["action_taken"] = True
60
200
···
72
212
result["message"] = f"Already muted {profile.handle}."
73
213
result["action_taken"] = False
74
214
else:
75
-
client.mute(actor)
215
+
client.mute(user_did)
76
216
result["message"] = f"Successfully muted {profile.handle}."
77
217
result["action_taken"] = True
78
218
79
219
elif change_type == "unmute":
80
220
if viewer and viewer.muted:
81
-
client.unmute(actor)
221
+
client.unmute(user_did)
82
222
result["message"] = f"Successfully unmuted {profile.handle}."
83
223
result["action_taken"] = True
84
224
else:
···
90
230
result["message"] = f"Already blocked {profile.handle}."
91
231
result["action_taken"] = False
92
232
else:
93
-
# Create block record with proper structure
233
+
# Create block record with proper structure (using resolved DID)
94
234
block_record = models.AppBskyGraphBlock.Record(
95
-
subject=profile.did,
235
+
subject=user_did,
96
236
created_at=client.get_current_time_iso()
97
237
)
98
238
client.app.bsky.graph.block.create(client.me.did, block_record)
···
113
253
return result
114
254
115
255
except ImportError:
116
-
raise ImportError("atproto package not installed. Install with: pip install atproto")
256
+
return {
257
+
"status": "error",
258
+
"message": "Error: The atproto Python package is not installed in the execution environment. "
259
+
"To resolve this, the system administrator needs to install it using 'pip install atproto'. "
260
+
"This is a dependency issue that prevents the tool from connecting to Bluesky. Once the "
261
+
"package is installed, this tool will work normally."
262
+
}
117
263
except Exception as e:
118
-
raise RuntimeError(f"Error updating Bluesky connection: {e}")
264
+
return {
265
+
"status": "error",
266
+
"message": f"Error: An unexpected issue occurred while updating the Bluesky connection: {str(e)}. "
267
+
f"To resolve this, verify the actor identifier is correct and that the user exists. This type "
268
+
f"of error can occur due to invalid actors, deleted accounts, temporary API issues, or network "
269
+
f"problems, and usually succeeds on retry."
270
+
}