We can't find the internet
Attempting to reconnect
Something went wrong!
Hang in there while we get back on track
In the previous post, we learned how to add tags to your post when posting to Bluesky.
Today, we'll look at how you can mention another user in the post.
You might think that it's as easy as just adding tags as text prepended with a @
sign, but it is a little more complicated than that unfortunately.
If you add the metions as plain text, you'll see that they are represented that way in Bluesky and that you are unable to click on them.
To add real mentions in your Bluesky post, we again need to use a concept called "facets".
Let's see how mentions work.
Step 1: Get the list of mentioned users from the text
We will start with creating a helper function which can extract the mentioned users from the text and return the proper data structure as expected by Bluesky:
defmodule BlueskyHelpers do
def get_mentions_as_facets(text) do
Regex.scan(~r/@\S+/, text, return: :index)
end
end
This function will parse all mentioned users starting with a @
from the text.
Step 2: Getting the did of the mentioned user
Before we can create the mention facet, we need to know the did of the mentioned user. To do this, we need to use the com.atproto.identity.resolveHandle
function. We can also create a simple helper function for this:
defmodule BlueskyHelpers do
def get_mentions_as_facets(text) do
Regex.scan(~r/@[a-zA-Z0-9.-]+[a-zA-Z0-9]/, text, return: :index)
end
def resolve_handle(handle) do
response =
Req.get!(
"https://bsky.social/xrpc/com.atproto.identity.resolveHandle",
params: %{"handle" => String.trim_leading(handle, "@")}
)
if response.status === 400, do: nil, else: response.body["did"]
end
end
This function resolve_handle/1
does a HTTP GET
call to the endpoint passing the handle
as a parameter. The handle should not have the leading @
. If the handle exists, it will return the did of that user, in the other cases, we'll return nil
.
Step 3: Create the list of mention facets
We can now use these two functions to construct the list of facets for the mentioned users. It is again the same structure as with the tags (by using the byte start and end), but the type will be app.bsky.richtext.facet#mention
and we need to include the did as a value.
defmodule BlueskyHelpers do
def get_mentions_as_facets(text) do
Regex.scan(~r/@[a-zA-Z0-9.-]+[a-zA-Z0-9]/, text, return: :index)
|> Enum.map(fn [{start, length}] -> mention_to_facet(text, start, length) end)
|> Enum.filter(& &1) # Filter out the mentions that didn't resolve
end
def mention_to_facet(text, start, length) do
mention =
text
|> String.slice(start, length)
|> String.trim_leading("@")
did = resolve_handle(mention)
if did !== nil do
%{
index: %{
byteStart: start,
byteEnd: start + length
},
features: [
%{
"$type": "app.bsky.richtext.facet#mention",
did: did
}
]
}
else
nil
end
end
def resolve_handle(handle) do
response =
Req.get!(
"https://bsky.social/xrpc/com.atproto.identity.resolveHandle",
params: %{"handle" => String.trim_leading(handle, "@")}
)
if response.status === 400, do: nil, else: response.body["did"]
end
end
What this code does is:
- It will use a regular expression to extract the mentions from the text
- It will then tranform each tag into the proper facet data structure, resolving the handle to a did, omitting the ones that don't exist
The helper function for turning the tag into a facet does:
- It will use the start index and the length to get the actual tag text
- It will then create a map with the index containing the byte start and end of the tag (including the
#
sign) - It will use the type
app.bsky.richtext.facet#mention
to indicate that this is a mention facet - It will specify the actual did for the facet (the result from resolving the handle)
Step 4: Creating the post record
This is again the same structure as in the previous posts, but with one change.
We now added the list of facets (combining the mentions and the tags as a single list).
# Create the current timestamp in ISO format with a trailing Z
created_at = DateTime.utc_now() |> DateTime.to_iso8601()
# The text to post
text = "Hello from @yellowduck.be #MyElixir #example"
# Get the list of facets
tag_facets = BlueskyHelpers.get_tags_as_facets(text)
mention_facets = BlueskyHelpers.get_mentions_as_facets(text)
# Create the post record
record =
%{
text: text,
createdAt: created_at,
"$type": "app.bsky.feed.post",
langs: ["en-US"],
facets: List.flatten([tag_facets | mention_facets]) # Add the flattened list of facets
}
Step 5: Post to Bluesky
We can now use the same code from the previous post to publish the record:
%{"commit" => %{"rev" => rev}} =
Req.post!(
"https://bsky.social/xrpc/com.atproto.repo.createRecord",
auth: {:bearer, token},
json: %{
repo: did, # The did value from step 1
collection: "app.bsky.feed.post", # We want to post to our timeline
record: record # The record we want to post to our timeline
}
).body
If all goes well, the tags and mentions should show up in the post as clickable items.
Next time, we'll continue with facets with adding website cards.
If this post was enjoyable or useful for you, please share it! If you have comments, questions, or feedback, you can email my personal email. To get new posts, subscribe use the RSS feed.