Skip to content

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

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)

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

No labels
No labels

Projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions