As already noted by many media articles, Bluesky Social is a decentralized social networking service with a Twitter-like UI. Bluesky Social is built on a next-generation common infrastructure called the AT Protocol, and the road ahead is very ambitious and full of unknown possibilities.
This article mainly shows how to post to Bluesky Social from the API in Dart language. I will not actively present the implementation in Flutter, but you can apply the same procedure in Flutter by reading this article.
What Is Dart?
For those who are not familiar with the Dart language, Dart is a relatively new programming language developed by Google. Dart has a syntax similar to Java and C, but with modern language specifications such as null safety. Above all, Flutter is probably the reason Dart is getting the most attention.
There is already a powerful package that makes it easy to use Bluesky Social's API with Dart and Flutter, and this article will present sample code using that package.
Install Package
To easily use Bluesky Social's API in Dart or Flutter, use the package bluesky
. I develop and maintain this package and it already supports almost all endpoints, is well tested and very stable.
It is already being used in several third-party apps, and I highly recommend this package for your Bluesky Social-related apps in Dart or Flutter.
Or you can see more details in in following official page.
Well, let's install this package with following commands.
With Dart:
dart pub add bluesky
dart pub get
With Flutter:
flutter pub add bluesky
flutter pub get
And let's check your pubspec.yaml
in your Dart or Flutter app. It is successful if it looks like this and this article uses bluesky v0.6.0
.
name: bluesky_post
description: A sample command-line application.
version: 1.0.0
environment:
sdk: ^3.0.0
dependencies:
bluesky: ^0.6.0
dev_dependencies:
lints: ^2.0.0
test: ^1.21.0
Let's Post!
The easiest way to post to Bluesky Social using the bluesky
package is as follows. The following example simply posts any text.
import 'package:bluesky/bluesky.dart' as bsky;
Future<void> main() async {
//! You need to be authenticated by credentials.
final session = await bsky.createSession(
identifier: 'HANDLE_OR_EMAIL',
password: 'PASSWORD_OR_APP_PASSWORD',
);
//! Create Bluesky from session data.
final bluesky = bsky.Bluesky.fromSession(session.data);
final ref = await bluesky.feeds.createPost(
text: 'Hello, Bluesky!',
);
//! StrongRef
print(ref.data);
}
I previously wrote an article about using the Firehose API with the bluesky
package, which did not require credentials. However, the endpoint you will be using to post to Bluesky Social will always require your credentials.
For example, the above example uses the createSession
method to authenticate with the bsky.social
server using your credentials. If the authentication fails, an error is returned by bsky.social
.
As a side note, if you have an account on a server other than bsky.social
, you can specify any server as follows:
final session = await bsky.createSession(
service: 'stem.social',
identifier: 'HANDLE_OR_EMAIL',
password: 'PASSWORD_OR_APP_PASSWORD',
);
Then, an instance of the Bluesky
object, which is required to use the bluesky
package, is created using the .fromSession
constructor. The .fromSession
constructor can be used to create a Bluesky
object from a Session
object without over or under creating it.
final bluesky = bsky.Bluesky.fromSession(session.data);
Okay now, if you could get session and Bluesky
object from session, all you need to do is post any text with the following code.
final ref = await bluesky.feeds.createPost(
text: 'Hello, Bluesky!',
);
With just this, you can send any post to Bluesky Social! The data returned from the createPost
method is a reference to the actual data generated. It is represented on the AT Protocol as StrongRef
and consists of an AT URI and CID link to the generated data.
Also, although not in the above code, you can delete a generated Post or other record from the AT URI as follows.
//! Delete any records.
await bluesky.repositories.deleteRecord(uri: ref.data.uri);
Post with Images
Now that you know how to use the bluesky
package to post arbitrary text to Bluesky Social, it would be useful to be able to attach images and such when actually using it.
Basically, in order to attach an image to a particular piece of content, you must first upload that image data and then POST the uploaded data in Multipart format. However, with the bluesky
package, you don't have to be aware of these difficult processes at all.
Well, you can send a post with an image by doing the following:
import 'dart:io';
import 'package:bluesky/bluesky.dart' as bsky;
Future<void> main() async {
final session = await bsky.createSession(
identifier: 'HANDLE_OR_EMAIL',
password: 'PASSWORD_OR_APP_PASSWORD',
);
final bluesky = bsky.Bluesky.fromSession(session.data);
//! Upload photo!
final uploaded = await bluesky.repositories.uploadBlob(
File('./funny_photo.jpg').readAsBytesSync(), // File bytes
);
final ref = await bluesky.feeds.createPost(
text: 'Hello, Bluesky!',
//! Add it.
embed: bsky.Embed.images(
data: bsky.EmbedImages(
images: [
bsky.Image(
alt: 'This is my funny photo!',
image: uploaded.data.blob,
),
],
),
),
);
print(ref);
}
The above example shows that it is very easy to send a post with an image.
All you have to do is upload an arbitrary image file using the uploadBlob
method, and pass the uploaded Blob data to the embed
parameter of createPost
.
Quote the Post
You can also quote a post that has already been submitted easily as follows.
import 'package:bluesky/bluesky.dart' as bsky;
Future<void> main() async {
final session = await bsky.createSession(
identifier: 'HANDLE_OR_EMAIL',
password: 'PASSWORD_OR_APP_PASSWORD',
);
final bluesky = bsky.Bluesky.fromSession(session.data);
//! Let's use this post as quote.
final ref = await bluesky.feeds.createPost(text: 'This will be quoted.');
await bluesky.feeds.createPost(
text: 'Hello, Bluesky!',
//! Add it.
embed: bsky.Embed.record(
data: bsky.EmbedRecord(ref: ref.data),
),
);
}
Attach a Link Card to an External Site
When using Bluesky's API to embed a post with a link card from an external site, you will need to configure that link card yourself. The bluesky
package makes it easy to set up.
import 'package:bluesky/bluesky.dart' as bsky;
Future<void> main() async {
final session = await bsky.createSession(
identifier: 'HANDLE_OR_EMAIL',
password: 'PASSWORD_OR_APP_PASSWORD',
);
final bluesky = bsky.Bluesky.fromSession(session.data);
await bluesky.feeds.createPost(
text: 'Hello, Bluesky!',
//! Add it.
embed: bsky.Embed.external(
data: bsky.EmbedExternal(
external: bsky.EmbedExternalThumbnail(
uri: 'https://shinyakato.dev',
title: 'Shinya Kato',
description: 'This is my site!',
),
),
),
);
}
Reply to a Specific Post
Replying to a specific post is a basic feature. Using the bluesky
package, you can reply as follows.
import 'package:bluesky/bluesky.dart' as bsky;
Future<void> main() async {
final session = await bsky.createSession(
identifier: 'HANDLE_OR_EMAIL',
password: 'PASSWORD_OR_APP_PASSWORD',
);
final bluesky = bsky.Bluesky.fromSession(session.data);
//! Let's reply to this post.
final root = await bluesky.feeds.createPost(
text: 'Reply to me',
);
await bluesky.feeds.createPost(
text: 'Hello, Bluesky!',
//! Add this.
reply: bsky.ReplyRef(
root: root.data,
parent: root.data,
),
);
}
With the above codes, you can easily reply to a specific post!
And you may have noticed that the ReplyRef
object has two arguments, root
and parent
. With these arguments, you can create a thread, but if you simply want to create a series of threads, the bluesky
package provides an easier way.
Let's use createThread
method.
import 'package:bluesky/bluesky.dart' as bsky;
Future<void> main() async {
final session = await bsky.createSession(
identifier: 'HANDLE_OR_EMAIL',
password: 'PASSWORD_OR_APP_PASSWORD',
);
final bluesky = bsky.Bluesky.fromSession(session.data);
//! Let's reply to this post.
final root = await bluesky.feeds.createThread([
bsky.ThreadParam(text: 'First post'),
bsky.ThreadParam(text: 'Second post'),
bsky.ThreadParam(text: 'Third post'),
]);
//! This is the root ref from first post.
print(root);
}
Using createThread
, it is very easy to create a series of threads.
Mentions and Links
In services similar to Bluesky, such as Twitter and Mastodon, mentions and hyperlinks in the text are automatically set up without any special attention. However, this is not the case at Bluesky.
For example, how would the status of this post appear on Bluesky's UI if the previous code were modified as follows?
import 'package:bluesky/bluesky.dart' as bsky;
Future<void> main() async {
final session = await bsky.createSession(
identifier: 'HANDLE_OR_EMAIL',
password: 'PASSWORD_OR_APP_PASSWORD',
);
final bluesky = bsky.Bluesky.fromSession(session.data);
final ref = await bluesky.feeds.createPost(
text: 'I am @shinyakato.dev! Check https://shinyakato.dev',
);
}
The answer is, it will post the raw I am @shinyakato.dev! Check https://shinyakato.dev
text with no link set in the mentions and hyperlinks.
So, if you are asking if you can't set up links in the Bluesky API, of course you can! You just need to set a special parameter called facet.
Modify the createPost
method in the previous example as follows.
await bluesky.feeds.createPost(
text: 'I am @shinyakato.dev! Check https://shinyakato.dev',
//! Set Facets.
facets: [
//! Facet for mention like "@shinyakato.dev"
bsky.Facet(
index: bsky.ByteSlice(
byteStart: 5,
byteEnd: 20,
),
features: [
bsky.FacetFeature.mention(
data: bsky.FacetMention(
did: 'did:plc:iijrtk7ocored6zuziwmqq3c',
),
)
],
),
//! Facet for link like "https://shinyakato.dev"
bsky.Facet(
index: bsky.ByteSlice(
byteStart: 28,
byteEnd: 50,
),
features: [
bsky.FacetFeature.link(
data: bsky.FacetLink(
uri: 'https://shinyakato.dev',
),
)
],
),
],
);
Setting up this facet is one of the most difficult operations to enable mentions and hyperlinks using the Bluesky API.
The mechanics are simple, but you need to define the ByteSlice as the start and end position of the specific token you want to link to, such as a mentions or hyperlinks in the text, and even how you want the link to be placed as features
.
In other words, this mechanism can be used to create a link that is completely different from the mentions or hyperlinks contained in the text.
However, I feel many people simply want mentions and hyperlinks like those offered by Twitter and Mastodon. Is there an easier solution to this complicated facet? Of course there is! But use the bluesky_text
package, which is different from the bluesky
package.
Let's check how to use bluesky_text
in the next section!
Use bluesky_text
for Easy facet
Resolution
As mentioned in the previous section, activating links in text using the Bluesky API requires a facet setting, which is a somewhat difficult operation. If you need simple mentions and hyperlinks like Twitter or Mastodon, use bluesky_text
.
bluesky_text
is another package I am developing, designed to integrate very easily with the bluesky
package.
And install it like bluesky
package.
With Dart:
dart pub add bluesky_text
dart pub get
With Flutter:
flutter pub add bluesky_text
flutter pub get
It is successful if it looks like this in your pubspec.yaml
and this article uses bluesky_text v0.2.2
.
name: bluesky_post
description: A sample command-line application.
version: 1.0.0
environment:
sdk: ^3.0.0
dependencies:
bluesky: ^0.6.0
bluesky_text: ^0.2.2
dev_dependencies:
lints: ^2.0.0
test: ^1.21.0
Okay let's modify the complex facet setup process described earlier as follows.
import 'package:bluesky/bluesky.dart' as bsky;
import 'package:bluesky_text/bluesky_text.dart';
Future<void> main() async {
final session = await bsky.createSession(
identifier: 'HANDLE_OR_EMAIL',
password: 'PASSWORD_OR_APP_PASSWORD',
);
final bluesky = bsky.Bluesky.fromSession(session.data);
//! Just pass text to BlueskyText.
final text = BlueskyText('I am @shinyakato.dev! Check https://shinyakato.dev');
//! Extract all the mentions and hyperlinks in the text
//! and get the facet as JSON.
final facets = await text.entities.toFacets();
await bluesky.feeds.createPost(
//! This is the text you passed to BlueskyText.
text: text.value,
//! Convert to Facet objects.
facets: facets.map(bsky.Facet.fromJson).toList(),
);
}
Where the heck did the difficult facet process of a few minutes ago disappear to?! This is bluesky_text
magic, all you have to do is just pass the text you want to post to the constructor when you create an instance of the BlueskyText object and call .entities.toFacet()
.
With just this, you can retrieve facet data contained in the text. Then, to post using the bluesky
package, convert the JSON retrieved from toFacet()
into a Facet
object with facets.map(bsky.Facet.fromJson).toList()
.
Conclusion
This is how to post to Bluesky Social using the bluesky
package. I also introduced a simple solution for facet
, which is difficult to configure when using the Bluesky API, using the bluesky_text
package.
This article mainly touches on the Dart implementation and omits the Flutter implementation, but the bluesky
and bluesky_text
packages can also be used in Flutter apps. The implementation method is the same as described in this article and will be easily integrated into your Flutter app!
In addition to the bluesky
package and bluesky_text
I have presented here, I have also developed many AT Protocol related packages for Dart/Flutter in the following monorepo. If you are interested in AT Protocol related packages for Dart/Flutter, please check it out!
myConsciousness / atproto.dart
🦋 AT Protocol and Bluesky things for Dart and Flutter.
AT Protocol and Bluesky Social Things for Dart/Flutter 🦋
- 1. Motivation 💪
- 2. Packages & Tools ⚒️
- 3. Developer Quickstart 🏎️
- 4. Who is using atproto.dart? 👀
- 5. Contribution 🏆
- 6. Contributors ✨
- 7. Support ❤️
- 8. License 🔑
- 9. More Information 🧐
Welcome to atproto.dart 🦋
This project will maximize your development productivity about AT Protocol and Bluesky things.
Give a ⭐ on GitHub repository and follow shinyakato.dev on Bluesky!
1. Motivation 💪
AT Protocol and Bluesky are awesome.
This wonderful platform needs a standard and highly integrated SDK atproto.dart provides the best development experience in such matters for Dart/Flutter devs.
2. Packages & Tools ⚒️
2.1. Dart Packages
Package | pub.dev | Docs |
---|---|---|
at_identifier: core library for the syntax in the AT Protocol standard | README | |
nsid: |
Suggestions for improvements and pull requests are also very welcome. Or you can contact me on Bluesky :)
Thank you.
Top comments (0)