This is a Bluesky client library written in Go. It requires a Bluesky account to connect through and an application password to authenticate with.
This library is highly opinionated and built around my personal preferences as to how Go code should look and behave. My goals are simplicity and security rather than flexibility.
Disclaimer: The state of the library is not even pre-alpha. Everything can change, everything can blow up, nothing may work, the whole thing might get abandoned. Don't expect API stability.
In order to authenticate to the Bluesky server, you will need a login handle and an application password. The handle might be an email address or a username recognized by the Bluesky server. The password, however, must be an application key. For security reasons this we will reject credentials that allow full access to your user.
import "errors"
import "github.com/karalabe/go-bluesky"
var (
blueskyHandle = "example.com"
blueskyAppkey = "1234-5678-9abc-def0"
)
func main() {
ctx := context.Background()
client, err := bluesky.Dial(ctx, bluesky.ServerBskySocial)
if err != nil {
panic(err)
}
defer client.Close()
err = client.Login(ctx, blueskyHandle, blueskyAppkey)
switch {
case errors.Is(err, bluesky.ErrMasterCredentials):
panic("You're not allowed to use your full-access credentials, please create an appkey")
case errors.Is(err, bluesky.ErrLoginUnauthorized):
panic("Username of application password seems incorrect, please double check")
case err != nil:
panic("Something else went wrong, please look at the returned error")
}
}
Of course, most of the time you won't care about the errors broken down like that. Logging the error and failing is probably enough in general, the introspection is meant for strange power uses.
The above code will create a client authenticated against the given Bluesky server. The client will automatically refresh the authorization token internally when it closes in on expiration. The auth will be attempted to be refreshed async without blocking API calls if there's enough time left, or by blocking if it would be cutting it too close to expiration (or already expired).
Any user's profile can be retrieved via the bluesky.Client.FetchProfile
method. This will return
some basic metadata about the user.
profile, err := client.FetchProfile(ctx, "karalabe.bsky.social")
if err != nil {
panic(err)
}
fmt.Println("Name:", profile.Name)
fmt.Println(" - Followers:", profile.FollowerCount)
fmt.Println(" - Follows:", profile.FolloweeCount)
fmt.Println(" - Posts:", profile.PostCount)
Certain fields, like avatars and banner images are not retrieved by default since they are probably
large and most use cases don't need them. If the image URLs are not enough, the images themselves
can also be retrieved lazily into image.Image
fields.
if err := profile.ResolveAvatar(ctx); err != nil {
panic(err)
}
fmt.Println("Avatar size:", profile.Avatar.Bounds())
if err := profile.ResolveBanner(ctx); err != nil {
panic(err)
}
fmt.Println("Banner size:", profile.Banner.Bounds())
Being a social network, there's not much fun without being able to hop though the social graph. A user profile is a good starting point to do that! The list of followers and followees of a user can both be retrieved lazily after fetching the profile.
fmt.Println("User followed by:")
if err := profile.ResolveFollowers(ctx); err != nil {
panic(err)
}
for _, follower := range profile.Followers {
fmt.Println(" -", follower)
}
fmt.Println("User follows:")
if err := profile.ResolveFollowees(ctx); err != nil {
panic(err)
}
for _, followee := range profile.Followees {
fmt.Println(" -", followee)
}
The above resolvers are elegant, self-contained methods, but if the follower/followee count of a user is significant, it might be suboptimal to just wait hoping for the method to eventually return without using up too much memory. A more powerful way is to request the follower/followees to be returned as a stream as they are crawled!
fmt.Println("User followed by:")
followerc, errc := profile.StreamFollowers(ctx)
for follower := range followerc { // Pull the users from the channel as they arrive
fmt.Println(" -", follower)
}
if err := <-errc; err != nil {
panic(err)
}
fmt.Println("User follows:")
followeec, errc := profile.StreamFollowees(ctx)
for followee := range followeec { // Pull the users from the channel as they arrive
fmt.Println(" -", followee)
}
if err := <-errc; err != nil {
panic(err)
}
Of course, as with the user profiles, follower and followee items also contain certain lazy resolvable fields like the profile picture. In order however to crawl the social graph further, you will need to fetch the profile of a follower/followee first and go from there.
As with any client library, there will inevitably come the time when the user wants to call something that is not wrapped (or not yet implemented because it's a new server feature). For those power use cases, the library exposes a custom caller that can be used to tap directly into the atproto APIs.
The custom caller will provide the user with an xrpc.Client
that has valid user credentials and the
user can do arbitrary atproto calls with it.
client.CustomCall(func(api *xrpc.Client) error {
_, err := atproto.ServerGetSession(context.Background(), api)
return err
})
Note, the user should not retain the xprc.Client
given to the callback as this is only a copy of
the internal one and will not be updated with new JWT tokens when the old ones are expired.
Oh boy, you're gonna freak out 😅. Since there's no Go implementation of a Bluesky API server and PDS, there's nothing to run the test against... apart from the live system 😱.
To run the tests, you will have to provide authentication credentials to interact with the official Bluesky server. Needless to say, your testing account may not become the most popular with all the potential spam it might generate, so be prepared to lose it. ¯_(ツ)_/¯
To run the tests, set the GOBLUESKY_TEST_HANDLE
, GOBLUESKY_TEST_PASSWD
and GOBLUESKY_TEST_APPKEY
env vars and run the tests via the normal Go workflow.
$ export GOBLUESKY_TEST_HANDLE=example.com
$ export GOBLUESKY_TEST_PASSWD=my-pass-phrase
$ export GOBLUESKY_TEST_APPKEY=1234-5678-9abc-def0
$ go test -v
3-Clause BSD