this repo has no description

Add rich text support to Bluesky replies

- Parse mentions (@handle) and resolve to DIDs
- Parse URLs and create link facets
- Use proper atproto models for facets
- Matches functionality in post creation tool

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>

+56 -6
+56 -6
bsky_utils.py
··· 206 206 207 207 def reply_to_post(client: Client, text: str, reply_to_uri: str, reply_to_cid: str, root_uri: Optional[str] = None, root_cid: Optional[str] = None) -> Dict[str, Any]: 208 208 """ 209 - Reply to a post on Bluesky. 209 + Reply to a post on Bluesky with rich text support. 210 210 211 211 Args: 212 212 client: Authenticated Bluesky client ··· 219 219 Returns: 220 220 The response from sending the post 221 221 """ 222 + import re 223 + 222 224 # If root is not provided, this is a reply to the root post 223 225 if root_uri is None: 224 226 root_uri = reply_to_uri ··· 228 230 parent_ref = models.create_strong_ref(models.ComAtprotoRepoStrongRef.Main(uri=reply_to_uri, cid=reply_to_cid)) 229 231 root_ref = models.create_strong_ref(models.ComAtprotoRepoStrongRef.Main(uri=root_uri, cid=root_cid)) 230 232 231 - # Send the reply 232 - response = client.send_post( 233 - text=text, 234 - reply_to=models.AppBskyFeedPost.ReplyRef(parent=parent_ref, root=root_ref) 235 - ) 233 + # Parse rich text facets (mentions and URLs) 234 + facets = [] 235 + text_bytes = text.encode("UTF-8") 236 + 237 + # Parse mentions 238 + mention_regex = rb"[$|\W](@([a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)" 239 + 240 + for m in re.finditer(mention_regex, text_bytes): 241 + handle = m.group(1)[1:].decode("UTF-8") # Remove @ prefix 242 + try: 243 + # Resolve handle to DID using the API 244 + resolve_resp = client.app.bsky.actor.get_profile({'actor': handle}) 245 + if resolve_resp and hasattr(resolve_resp, 'did'): 246 + facets.append( 247 + models.AppBskyRichtextFacet.Main( 248 + index=models.AppBskyRichtextFacet.ByteSlice( 249 + byteStart=m.start(1), 250 + byteEnd=m.end(1) 251 + ), 252 + features=[models.AppBskyRichtextFacet.Mention(did=resolve_resp.did)] 253 + ) 254 + ) 255 + except Exception as e: 256 + logger.debug(f"Failed to resolve handle {handle}: {e}") 257 + continue 258 + 259 + # Parse URLs 260 + url_regex = rb"[$|\W](https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&//=]*[-a-zA-Z0-9@%_\+~#//=])?)" 261 + 262 + for m in re.finditer(url_regex, text_bytes): 263 + url = m.group(1).decode("UTF-8") 264 + facets.append( 265 + models.AppBskyRichtextFacet.Main( 266 + index=models.AppBskyRichtextFacet.ByteSlice( 267 + byteStart=m.start(1), 268 + byteEnd=m.end(1) 269 + ), 270 + features=[models.AppBskyRichtextFacet.Link(uri=url)] 271 + ) 272 + ) 273 + 274 + # Send the reply with facets if any were found 275 + if facets: 276 + response = client.send_post( 277 + text=text, 278 + reply_to=models.AppBskyFeedPost.ReplyRef(parent=parent_ref, root=root_ref), 279 + facets=facets 280 + ) 281 + else: 282 + response = client.send_post( 283 + text=text, 284 + reply_to=models.AppBskyFeedPost.ReplyRef(parent=parent_ref, root=root_ref) 285 + ) 236 286 237 287 logger.info(f"Reply sent successfully: {response.uri}") 238 288 return response