Skip to content

Support BlueSky & mastodon post embeds through JSON-LD #147

@fennifith

Description

@fennifith

Currently, the url-metadata task supports fetching post implementation only for x.com/twitter.com URLs, through the fxtwitter API. See: getEmbedDataFromPost.ts

This task is for adding support for bluesky as well as arbitrary Mastodon hostnames. This can be done using JSON-LD from the HTML head, for example:

  • Support adding post information from SocialMediaPosting (only profile & content & interaction counts, no images)
  • Fetch the author information from the author.url information
  • Upload profile images to object storage / s3

https://hachyderm.io/@playfulprogramming/114615894255662214

{
  "@context": "https://schema.org",
  "@type": "SocialMediaPosting",
  "url": "https://hachyderm.io/@playfulprogramming/114615894255662214",
  "datePublished": "2025-06-02T21:29:13Z",
  "dateModified": null,
  "author": {
    "@type": "Person",
    "name": "Playful Programming",
    "alternateName": "playfulprogramming@hachyderm.io",
    "identifier": "playfulprogramming@hachyderm.io",
    "url": "https://hachyderm.io/@playfulprogramming",
    "interactionStatistic": [
      {
        "@type": "InteractionCounter",
        "interactionType": "https://schema.org/FollowAction",
        "userInteractionCount": 47
      }
    ]
  },
  "text": "<p>Their blog posts talks about a study at Remesh that shows that starting with high-level diagrams, baking docs into workflows, reviewing designs, documenting tests, among other things all reinforce strong engineering culture. Read all the details below:</p><p><a href=\"https://ntietz.com/blog/our-case-study-on-software-documentation/\" target=\"_blank\" rel=\"nofollow noopener\" translate=\"no\"><span class=\"invisible\">https://</span><span class=\"ellipsis\">ntietz.com/blog/our-case-study</span><span class=\"invisible\">-on-software-documentation/</span></a></p>",
  "interactionStatistic": [
    {
      "@type": "InteractionCounter",
      "interactionType": "https://schema.org/LikeAction",
      "userInteractionCount": 0
    },
    {
      "@type": "InteractionCounter",
      "interactionType": "https://schema.org/ShareAction",
      "userInteractionCount": 0
    },
    {
      "@type": "InteractionCounter",
      "interactionType": "https://schema.org/ReplyAction",
      "userInteractionCount": 0
    }
  ],
  "sharedContent": {
    "@type": "WebPage",
    "url": "https://ntietz.com/blog/our-case-study-on-software-documentation/"
  }
}

https://bsky.app/profile/playfulprogramming.com/post/3mgdxznq4pe2f

 <script type="application/ld+json">
    {
      "@context": "https://schema.org",
      "@type": "DiscussionForumPosting",
      "author": {
        "@type": "Person",
        "name": "Playful Programming",
        "alternateName": "@playfulprogramming.com",
        "url": "https://bsky.app/profile/playfulprogramming.com"
      },
      "text": "In the Web Fundamentals Bootcamp today:

We’re moving from theory to practice  fetching real data and handling it correctly in React.

Because “it works” isn’t the same as “it’s modeled well.",
      
      "datePublished": "2026-03-05T23:43:01.150Z",
      "interactionStatistic": [
        {
          "@type": "InteractionCounter",
          "interactionType": "https://schema.org/LikeAction",
          "userInteractionCount": 2
        },
        {
          "@type": "InteractionCounter",
          "interactionType": "https://schema.org/CommentAction",
          "userInteractionCount": 1
        },
        {
          "@type": "InteractionCounter",
          "interactionType": "https://schema.org/ShareAction",
          "userInteractionCount": 0
        }
      ]
    }
  </script>

https://bsky.app/profile/playfulprogramming.com

  <script type="application/ld+json">
  {
    "@context": "https://schema.org",
    "@type": "ProfilePage",
    "dateCreated": "2024-08-26T18:49:19.819Z",
    "mainEntity": {
      "@type": "Person",
      "name": "Playful Programming",
      "alternateName": "@playfulprogramming.com",
      "identifier": "did:plc:5n6zdjl4ruqvij67crnyrysq",
      "description": "The perfect place to learn all kinds of programming, from introductory ideas to advanced abstractions.

Join our Discord!

https://discord.gg/FMcvc6T",
      "image": "https://cdn.bsky.app/img/avatar/plain/did:plc:5n6zdjl4ruqvij67crnyrysq/bafkreibv4lhztr2e7stfukainkcukiyvgjuokpdw2ucsooroyjhhtvqhaq",
      "interactionStatistic": [
        {
          "@type": "InteractionCounter",
          "interactionType": "https://schema.org/FollowAction",
          "userInteractionCount": 331
        }
      ],
      "agentInteractionStatistic": [
        {
          "@type": "InteractionCounter",
          "interactionType": "https://schema.org/FollowAction",
          "userInteractionCount": 13
        },
        {
          "@type": "InteractionCounter",
          "interactionType": "https://schema.org/WriteAction",
          "userInteractionCount": 312
        }
      ]
    }
  }
  </script>

Bluesky can be determined via the hostname - however, Mastodon can have any domain, so this will need to be added generically to getEmbedDataFromHtml (possibly as a branch into getEmbedDataFromLinkedData if ld+json metadata is detected)

Metadata

Metadata

Assignees

Labels

No labels
No labels
No fields configured for Feature.

Projects

Status
🏗️ In Progress

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions