Shortcode to Embed Bluesky Posts

, 5 minutes to read, 15 views

Apparently, Bluesky is having another surge. Fair enough, given what’s happening on Twitter and in the world at large.

I fully support leaving Twitter1, especially for journalists and similar professionals. Why would you continue publishing on and supporting a platform that actively despises you?

I’m delighted to be on Mastodon, but Bluesky is certainly better than staying on Twitter. For example, it has a useful API that allows us to embed Bluesky posts directly without loading third-party scripts:

New post...

A simple Hugo shortcode for embedding Bluesky posts • While it doesn’t do static embeds, this shortcode gives you an easy way to show content from an increasingly popular social network.

#WebDev #Hugo #Bluesky #oEmbed
A simple Hugo shortcode for embedding Bluesky posts | BryceWray.com While it doesn’t do static embeds, this shortcode gives you an easy way to show content from an increasingly popular social network.
www.brycewray.com

As you can see, this embeds the post statically. The shortcode I built—just like the one I made for Mastodon—doesn’t (yet) support all the intricacies of Bluesky posts. In its current state, it handles text posts and link embeds, mostly to demonstrate to Bryce Wray that it’s possible. In fact, my Mastodon shortcode was largely inspired by Bryce Wray’s Mastodon shortcode.

So here it is: a (very incomplete) Hugo shortcode2 for Bluesky posts.

{{ $avatarSize := 42 }}
{{ $imgWidth := 624 }}

{{ $username := .Get "username" }}
{{ $postId := .Get "postId" }}
{{ $uri := print "at://" $username "/app.bsky.feed.post/" $postId }}
{{ $urlToGet := print "https://public.api.bsky.app/xrpc/app.bsky.feed.getPostThread?uri=" $uri }}

{{- with resources.GetRemote $urlToGet -}}
    {{ if .Err }}
        <blockquote class="bluesky">
            <p class="ctr legal">[Source not online<br />
            at time of site build.]</p>
        </blockquote>
    {{ else }}
        {{ $json := unmarshal .Content }}
        {{ $thread := $json.thread }}
        {{ $post := $thread.post }}
        {{ $author := $post.author }}
        {{ $record := $post.record }}
        {{ $embed := $post.embed }}
        {{ $authorUrl := printf "https://bsky.app/profile/%s" $author.did }} 
        
        <blockquote class="bluesky not-prose">
            <div class="header">
                {{ $avatar := resources.GetRemote $author.avatar }}
                <a class="profile" href="{{ $authorUrl }}" rel="noopener">
                    <picture>
                        <source 
                            srcset="
                                {{- range $.Site.Params.pixeldensities -}}
                                {{- if le (int (mul . $avatarSize)) (mul 1.5 $avatar.Width) -}}
                                {{ ( ( printf "%dx webp" (int (mul . $avatarSize)) ) | $avatar.Resize ).RelPermalink }} {{ . }}x,
                                {{- end -}}
                                {{- end -}}"
                            type="image/webp"
                        />
                        <source 
                            srcset="
                                {{- range $.Site.Params.pixeldensities -}}
                                {{- if le (int (mul . $avatarSize)) (mul 1.5 $avatar.Width) -}}
                                {{ ( ( printf "%dx" (int (mul . $avatarSize)) ) | $avatar.Resize ).RelPermalink }} {{ . }}x,
                                {{- end -}}
                                {{- end -}}"
                            type="{{ $avatar.MediaType.Type }}"
                        />
                        {{ with (printf "%dx" $avatarSize | $avatar.Resize) }}
                        <img src="{{ .RelPermalink }}" alt="Bluesky avatar for {{ $author.displayName }}" width="{{ .Width }}" height="{{ .Height }}">
                        {{ end }}
                    </picture>
                </a>
                <div class="author">
                    <a class="name" href="{{ $authorUrl }}" rel="noopener">{{ $author.displayName }}</a>
                    <a class="handle" href="{{ $authorUrl }}" rel="noopener">@{{ $author.handle }}</a>
                </div>
                {{ $bskyIcon := resources.Get "icons/bsky.svg" }}
                <img src={{ ($bskyIcon | fingerprint).RelPermalink }} class="icon" alt="Bluesky Icon" />
            </div>
            <div class="content">
                {{ (replace $record.text "\n" "<br>") | safeHTML }}
            </div>
            {{ with $embed }}
                {{ $embedType := index . "$type" }}
                {{ if eq $embedType "app.bsky.embed.external#view" }}
                    <a href="{{ index .external "uri" }}" target="_blank" rel="noopener">
                        <div class="card">
                            {{ with $imgUrl := .external.thumb }}
                            {{ $img := resources.GetRemote $imgUrl }}
                            <div class="image">
                                <picture class="card-image">
                                    <source 
                                        srcset="
                                            {{- range $.Site.Params.pixeldensities -}}
                                            {{- if le (int (mul . $imgWidth)) (mul 1.5 $img.Width) -}}
                                            {{ ( ( printf "%dx webp" (int (mul . $imgWidth)) ) | $img.Resize ).RelPermalink }} {{ . }}x,
                                            {{- end -}}
                                            {{- end -}}"
                                        type="image/webp"
                                    />
                                    <source 
                                        srcset="
                                            {{- range $.Site.Params.pixeldensities -}}
                                            {{- if le (int (mul . $imgWidth)) (mul 1.5 $img.Width) -}}
                                            {{ ( ( printf "%dx" (int (mul . $imgWidth)) ) | $img.Resize ).RelPermalink }} {{ . }}x,
                                            {{- end -}}
                                            {{- end -}}"
                                        type="{{ $img.MediaType.Type }}"
                                    />
                                    {{ with $img }}
                                    <img src="{{ .RelPermalink }}" alt="" width="{{ .Width }}" height="{{ .Height }}">
                                    {{ end }}
                                </picture>
                            </div>
                            {{ end }}
                            <div class="meta">
                                <span class="title">{{ index .external "title" }}</span>
                                <span class="description">{{ index .external "description" }}</span>
                                <a href="{{ (urls.Parse (index .external "uri")).Scheme }}://{{ (urls.Parse (index .external "uri")).Host }}" class="website">
                                    <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-6">
                                        <path stroke-linecap="round" stroke-linejoin="round" d="m20.893 13.393-1.135-1.135a2.252 2.252 0 0 1-.421-.585l-1.08-2.16a.414.414 0 0 0-.663-.107.827.827 0 0 1-.812.21l-1.273-.363a.89.89 0 0 0-.738 1.595l.587.39c.59.395.674 1.23.172 1.732l-.2.2c-.212.212-.33.498-.33.796v.41c0 .409-.11.809-.32 1.158l-1.315 2.191a2.11 2.11 0 0 1-1.81 1.025 1.055 1.055 0 0 1-1.055-1.055v-1.172c0-.92-.56-1.747-1.414-2.089l-.655-.261a2.25 2.25 0 0 1-1.383-2.46l.007-.042a2.25 2.25 0 0 1 .29-.787l.09-.15a2.25 2.25 0 0 1 2.37-1.048l1.178.236a1.125 1.125 0 0 0 1.302-.795l.208-.73a1.125 1.125 0 0 0-.578-1.315l-.665-.332-.091.091a2.25 2.25 0 0 1-1.591.659h-.18c-.249 0-.487.1-.662.274a.931.931 0 0 1-1.458-1.137l1.411-2.353a2.25 2.25 0 0 0 .286-.76m11.928 9.869A9 9 0 0 0 8.965 3.525m11.928 9.868A9 9 0 1 1 8.965 3.525" />
                                    </svg>
                                   <span>{{ (urls.Parse (index .external "uri")).Host }}</span></div>
                            </div>
                        </div>
                    </a>
                {{ end }}
            {{ end }}
            <div class="footer">
                {{ $date := time.AsTime $record.createdAt }}
                {{ $timestamp := $date | time.Format "2006-01-02T15:04:05-07:00" }}
                {{ $dateHuman := $date | time.Format ":date_medium" }}
                {{ $timeHuman := $date | time.Format "15:04" }}
                <a href="https://bsky.app/profile/{{ $post.author.did}}/post/{{ $postId }}" class="post-date" rel="noopener">
                    <time datetime="{{ $timestamp }}">{{ $dateHuman }}&nbsp;{{ $timeHuman }}</time>
                </a>
            </div>
        </blockquote>
    {{ end }}
{{- end -}}

Share and Use

As always, feel free to share and use this shortcode. I hope that one day we’ll have a smarter way to collaboratively create and share these tools. Until then, it’s here for you to enjoy!


  1. I’ve blocked the domains twitter.com and x.com on my network. I don’t need to access them and am tired of being tracked by them on many news sites that still embed tweets. On most pages, this works fine—Twitter embeds are just displayed as blockquote elements, which is arguably better. But not all sites (looking at you, The Verge):

    Twitter embed in a Verge article. Without highlighting the text, nothing is visible because The Verge insists on painting tweets white if you don’t load Twitter’s JavaScript.
     ↩︎

  2. I still haven’t found a great way to format Hugo templates, and syntax highlighting remains a bit of a struggle. ↩︎

Tags: Blog, Hugo, SCSS, Shortcode, Style, Tailwind CSS, Technical