Boehrsi.de - Blog

Bluesky Posts mit Linkvorschau via Dart veröffentlichen

Erstellt am event Uhr von account_circle Boehrsi in label Development
Bluesky Posts mit Linkvorschau via Dart veröffentlichen Bild

In den letzten Monaten habe ich Dart als Lösung für serverseitige Implementierungen für mich entdeckt. Sowohl das Backend meines Blogs, wie auch verschiedene andere Server-Tools habe ich mittlerweile darauf umgestellt. Darunter ist auch mein Closed-Source Tool BoehrsiTweetHub, welches initial Daten mit Twitter / X geteilt hat und vor kurzem für Bluesky erweitert wurde.
Das Teilen von Blog Inhalten oder anderem Content mit Twitter / X ist relativ simpel, da dort quasi alle Informationen von der Plattform selbst gezogen werden. Dies ist bei Bluesky etwas anders, hier muss man den Content einer Card und vor allem Medieninhalte gesondert hochladen.
Das ist alles mit den aktuell vorhandenen Bibliotheken lösbar, bedarf allerdings etwas Wissen. Da ich kein komplettes Beispiel dafür finden konnte, musste ich mir einiges herleiten und anlesen, was ich euch ersparen möchte. Entsprechend findet ihr im unteren Teil des Beitrags meine Implementierung, um Webseiten bzw. Links inklusive Titel, Beschreibungstext und Bild auf Bluesky zu veröffentlichen.
Um euch die Nutzung einfacher zu machen findet ihr den kompletten Code aus lauffähigen Github Gist in den Related Links.

Vorab sind die zu nutzenden Bibliotheken zu erwähnen, welche ich im folgenden Beispiel genutzt habe. Alle Bibliotheken sind dabei Dart-Only, sodass Flutter nicht benötigt wird.

Da ich einige Probleme mit der Kompatibilität von Bibliotheken hatte hier einmal die exakten Versionen die ich in meiner pubspec.yaml nutze.

  bluesky: ^0.18.1
  bluesky_text: ^0.7.1
  http: ^1.2.2
  metadata_fetch: ^0.4.2

Die fetchDataAndUploadMedia Methode holt wie der Name suggeriert alle relevanten Daten für eine Webseite bzw. einen Link und lädt das erhaltene Thumbnail auf Bluesky hoch. Für den Upload der Medieninhalte wird die fetchAndUploadImage Methode genutzt.

Future<Embed?> fetchDataAndUploadMedia(Bluesky bluesky, String url) async {
  // Fetch / extract web content
  final data = await MetadataFetch.extract(url);
  final title = data?.title;
  final description = data?.description;

  // Upload media to Bluesky
  Blob? blob = await fetchAndUploadImage(bluesky, data?.image);

  // If everything was fetched and the media upload was successful, return the formatted data
  if (title != null && description != null && blob != null) {
    return Embed.external(
      data: EmbedExternal(
        external: EmbedExternalThumbnail(
          uri: url,
          title: title,
          description: description,
          blob: blob,
        ),
      ),
    );
  }
  // If something went wrong, return null
  return null;
}

Für den Upload wird das Bild zuerst von der angegebenen Webseite heruntergeladen und anschließend über die Bluesky Bibliothek hochgeladen.

Future<Blob?> fetchAndUploadImage(Bluesky bluesky, String? imageUrl) async {
  if (imageUrl != null) {
    try {
      // Fetch the image from the web
      final uri = Uri.parse(imageUrl);
      final http.Response response = await http.get(uri);
      
      // Upload the image to Bluesky and return the blog, which is needed to create the post
      final uploadResponse = await bluesky.atproto.repo.uploadBlob(response.bodyBytes);
      return uploadResponse.data.blob;
    } catch (e) {
      print(e);
    }
  }
  // If something went wrong, return null
  return null;
}

Um das herumreichen der Daten zu vereinfachen habe ich mir eine simple Datenklasse erstellt, welche die URL und den Text, welcher im Bluesky Post stehen soll, enthält.

class PostDataEntry {
  final String url;
  final String text;

  PostDataEntry({required this.url, required this.text});
}

Die postEntry Methode nutzt nun die zuvor definierten Methoden, um alles zu verbinden und den Post zu erstellen. Dabei laden wir alle Inhalte für die angegebene Webseite bzw. den Link, laden das Bild bei Bluesky hoch, erstellen daraus eine Card und veröffentlichen diesen Post, bestückt mit dem von uns definierten Text auf Bluesky.

Future<void> postEntry(PostDataEntry postData, String username, String password) async {
  try {
    // Create the Bluesky session. As the login is done by username and password it is
    // suggested to create an app-specifc password, to avoid using your normal credentials
    final session = await createSession(
      identifier: username,
      password: password,
    );

    // Create the actual Bluesky object from the session
    final bluesky = Bluesky.fromSession(
      await session.data,
    );

    // Format the text to make links and tags clickable within your post
    final blueskyText = BlueskyText(postData.text);
    final facets = await blueskyText.entities.toFacets();

    // Post the content, including the card and your defined text
    final entry = await bluesky.feed.post(
      text: blueskyText.value,
      facets: facets.map(Facet.fromJson).toList(),
      embed: await fetchDataAndUploadMedia(bluesky, postData.url),
    );
    print(entry);
  } on UnauthorizedException catch (e) {
    print(e);
  } on XRPCException catch (e) {
    print(e);
  }
}
Related Links
Kommentare  
Kommentar erstellen
Mit dem Abschicken des Kommentars erklären sie sich mit der in der Datenschutzerklärung dargelegten Datenerhebung für Kommentare einverstanden. Spam, unangebrachte Werbung und andere unerwünschte Inhalte werden entfernt. Das Abonnieren via E-Mail ist nur für E-Mail Adressen erlaubt die Sie rechtmäßig administrieren. Widerrechtliche Abonnements werden entfernt.