├── .gitignore ├── images ├── favicon.ico ├── favicon-96x96.png ├── apple-touch-icon.png ├── badges │ ├── mastodon-follow-88x31-a.gif │ ├── mastodon-follow-88x31-b.gif │ ├── mastodon-follow-88x31-c.gif │ ├── mastodon-follow-88x31-d.gif │ └── mastodon-follow-88x31-e.gif └── favicon.svg ├── TODO.md ├── README.md ├── js └── fedi-share.js ├── mastodon-logo.svg ├── LICENSE ├── timeline-emfed.html ├── single-post-api.html ├── timeline-mastofeed.html ├── timeline-meft.html ├── index.html ├── css └── fedi-share.css ├── mastopy.html ├── single-post-oembed.html ├── timeline-basic-rss.html ├── fedi-share.svg ├── single-post-pyscript.html └── timeline-pyscript.html /.gitignore: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /images/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andypiper/mastodon-embeds-examples/HEAD/images/favicon.ico -------------------------------------------------------------------------------- /images/favicon-96x96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andypiper/mastodon-embeds-examples/HEAD/images/favicon-96x96.png -------------------------------------------------------------------------------- /images/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andypiper/mastodon-embeds-examples/HEAD/images/apple-touch-icon.png -------------------------------------------------------------------------------- /images/badges/mastodon-follow-88x31-a.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andypiper/mastodon-embeds-examples/HEAD/images/badges/mastodon-follow-88x31-a.gif -------------------------------------------------------------------------------- /images/badges/mastodon-follow-88x31-b.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andypiper/mastodon-embeds-examples/HEAD/images/badges/mastodon-follow-88x31-b.gif -------------------------------------------------------------------------------- /images/badges/mastodon-follow-88x31-c.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andypiper/mastodon-embeds-examples/HEAD/images/badges/mastodon-follow-88x31-c.gif -------------------------------------------------------------------------------- /images/badges/mastodon-follow-88x31-d.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andypiper/mastodon-embeds-examples/HEAD/images/badges/mastodon-follow-88x31-d.gif -------------------------------------------------------------------------------- /images/badges/mastodon-follow-88x31-e.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andypiper/mastodon-embeds-examples/HEAD/images/badges/mastodon-follow-88x31-e.gif -------------------------------------------------------------------------------- /TODO.md: -------------------------------------------------------------------------------- 1 | - [ ] Lists of share buttons / plugins 2 | - [ ] CMS plugins list (WordPress etc) 3 | - [ ] Links to icons e.g. Mastodon, Fediverse logos (images/badges etc) 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Examples of Mastodon embeds 2 | 3 | ![mastodon-embeds-examples](https://socialify.git.ci/andypiper/mastodon-embeds-examples/image?description=1&descriptionEditable=Examples%20of%20embedding%20Mastodon%20timelines%20%26%20posts%20in%20HTML&font=Bitter&logo=https%3A%2F%2Fraw.githubusercontent.com%2Fandypiper%2Fmastodon-embeds-examples%2Fmain%2Fmastodon-logo.svg&name=1&owner=1&pattern=Floating%20Cogs&theme=Auto) 4 | 5 | Trying out various means to embed Mastodon posts and timelines (and combinations of other things, API, buttons, etc). 6 | 7 | Posts are straightforward to embed (Mastodon supports oEmbed, and has an embed code option on the web UI), timelines [need a bit more work](https://github.com/mastodon/mastodon/issues/6094). 8 | 9 | ## More information 10 | 11 | [I wrote about this on DEV](https://dev.to/andypiper/a-opportunity-for-developers-1ee4) 12 | 13 | ## Useful links 14 | 15 | - https://mastofeed.com 16 | - https://github.com/SamTherapy/fedifeed (fork of mastofeed) 17 | - https://gitlab.com/idotj/mastodon-embed-timeline 18 | - https://github.com/sampsyo/emfed 19 | - https://github.com/DomainDrivenArchitecture/dda-masto-embed 20 | 21 | ## Sharing buttons 22 | 23 | - https://github.com/Uden-AI/fediverse-share 24 | - https://github.com/grayleonard/mastodon-share 25 | - https://toot.kytta.dev/ 26 | - https://github.com/justinribeiro/share-to-mastodon 27 | 28 | Follow me on Mastodon -> [@andypiper@macaw.social](https://macaw.social/@andypiper) 29 | -------------------------------------------------------------------------------- /js/fedi-share.js: -------------------------------------------------------------------------------- 1 | Array.prototype.forEach||(Array.prototype.forEach=function(r){var o,t;if(null==this)throw new TypeError("this is null or not defined");var n=Object(this),e=n.length>>>0;if("function"!=typeof r)throw new TypeError(r+" is not a function");for(arguments.length>1&&(o=arguments[1]),t=0;t 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The LICENSE file for any project gives credit to the creator/author of the 2 | project, copyright information for the project, and the legal terms under 3 | which it's being shared. In other words, this is us using an MIT license to 4 | say "we wrote this and you can do whatever you want with it." 5 | 6 | ****************************************************************************** 7 | ~mastodon-embeds 8 | ****************************************************************************** 9 | MIT License 10 | 11 | Copyright (c) 2022, 2025, Andy Piper 12 | 13 | Permission is hereby granted, free of charge, to any person obtaining a copy 14 | of this software and associated documentation files (the "Software"), to deal 15 | in the Software without restriction, including without limitation the rights 16 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 17 | copies of the Software, and to permit persons to whom the Software is 18 | furnished to do so, subject to the following conditions: 19 | 20 | The above copyright notice and this permission notice shall be included in all 21 | copies or substantial portions of the Software. 22 | 23 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 24 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 25 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 26 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 27 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 28 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 29 | SOFTWARE. 30 | 31 | 32 | 33 | 34 | ****************************************************************************** 35 | 36 | THIRD-PARTY SOFTWARE 37 | This is all the software we used to build this starter project. All of these 38 | licenses are compatible with the license above. We've included links so you 39 | can learn more if you want. 40 | 41 | 1. HK Grotesk: The font we're using. 42 | 43 | 44 | ****************************************************************************** 45 | 1. HK Grotesk 46 | URL: https://hanken.co/products/hk-grotesk 47 | ****************************************************************************** 48 | HK Grotesk was designed by Hanken Design Co. It is shared using a SIL OFL 49 | license. Full license text can be found at: 50 | 51 | https://hanken.co/pages/web-fonts-eula 52 | ****************************************************************************** 53 | END, HK Grotesk 54 | ****************************************************************************** 55 | -------------------------------------------------------------------------------- /timeline-emfed.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Mastodon Timeline Embed (Emfed Example) 9 | 10 | 11 | 12 | 14 | 15 | 16 | 17 | 18 | 19 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 65 | 66 | 67 | 68 | 69 | 70 |
71 | 75 | 76 |
77 | 78 |

Mastodon Timeline Embed: Emfed

79 | 80 |
81 |
82 |

About This Method

83 |

84 | This method uses the Emfed JavaScript library. 86 | It fetches timeline data directly from the Mastodon API and renders it client-side, replacing a specific 87 | anchor (<a>) tag on the page. 88 |

89 |

90 | Configuration is done using data-* attributes directly on the anchor tag. Key attributes 91 | include: 92 |

93 |
    94 |
  • href: The URL of the Mastodon user profile (e.g., 95 | https://instance.social/@username). 96 |
  • 97 |
  • class="mastodon-feed": Required class for Emfed to find the element.
  • 98 |
  • data-toot-limit: Maximum number of posts to display.
  • 99 |
  • data-toot-account-id: The numerical Account ID for the user. Required for 100 | fetching posts.
  • 101 |
  • data-exclude-reblogs: Set to "true" to hide boosts/reblogs (optional).
  • 102 |
103 |

104 | Important: You need the Mastodon Account ID (a number, not the username) 105 | for the account you want to embed. You can often find this using API tools (like searching the account via 106 | /.well-known/webfinger on the instance and looking for the ID in the self link) 107 | or sometimes by viewing the source code of the user's profile page. 108 |

109 |
110 |
111 | 112 |
113 |
114 |

Rendered Timeline

115 |

Emfed will replace the anchor tag below with the rendered timeline:

116 | 117 | 123 |
124 |
125 | 126 |
127 |
128 | 129 | 139 | 140 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | -------------------------------------------------------------------------------- /single-post-api.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Single Post API Data Example 9 | 10 | 11 | 12 | 14 | 15 | 16 | 17 | 18 | 19 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 62 | 63 | 64 | 65 | 66 | 67 |
68 | 69 | 73 | 74 |
75 | 76 |

Mastodon API: Fetching Single Post Data

77 | 78 |
79 |
80 |

About This Method

81 |

82 | Instead of embedding a pre-formatted post, you can fetch the raw data for a specific status directly from 83 | the Mastodon API's /api/v1/statuses/:id endpoint. This gives you complete control over how 84 | you display the information. 85 |

86 |

87 | This example uses the browser's fetch() API in JavaScript to retrieve the data for post 88 | 109349688112355529 from macaw.social and displays the raw JSON response below. 89 |

90 |

91 | You could try the API endpoint directly using a tool like curl: 92 |

93 | curl https://macaw.social/api/v1/statuses/109349688112355529 94 |
95 |
96 | 97 |
98 |
99 |

Raw API Response (JSON)

100 |

The following is the raw JSON data returned by the API for the specified post:

101 | 102 |
Loading API data...
103 | 104 |
105 |
106 | 107 |
108 |
109 | 119 | 175 | 176 | 210 | 211 | 212 | 213 | 214 | -------------------------------------------------------------------------------- /timeline-mastofeed.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Mastodon Timeline Embed (Mastofeed Example) 9 | 10 | 11 | 12 | 14 | 15 | 16 | 17 | 18 | 19 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 59 | 60 | 61 | 62 | 63 | 64 |
65 | 66 | 70 | 71 |
72 | 73 |

Mastodon Timeline Embed: Mastofeed

74 | 75 |
76 |
77 |

About Mastofeed

78 |

79 | Mastofeed is a third-party 80 | service that generates an embeddable timeline for a Mastodon user profile via an 81 | <iframe>. 82 |

83 |

84 | Configuration is done via URL parameters in the iframe's src. This example also syncs the 85 | Mastofeed theme (light/dark) with the page theme toggle. 86 |

87 |
    88 |
  • userurl: The URL of the Mastodon user profile.
  • 89 |
  • theme: Set dynamically by the theme toggle (light or dark). 90 |
  • 91 |
  • header, replies, boosts, size: Other display 92 | options.
  • 93 |
94 |

95 | Note: Toggling the theme will cause the iframe below to reload. Line spacing issues may 96 | be inherent to the Mastofeed service's internal styling. 97 |

98 |
99 |
100 | 101 |
102 |
103 |

Rendered Timeline

104 |

This is what the embedded timeline looks like using the Mastofeed iframe:

105 | 106 | 110 |
111 |
112 | 113 |
114 |
115 | 116 | 126 | 127 | 209 | 210 | 211 | 212 | 213 | -------------------------------------------------------------------------------- /timeline-meft.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Mastodon Timeline Embed (MEFT Example) 9 | 10 | 11 | 12 | 14 | 15 | 16 | 17 | 18 | 19 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 31 | 32 | 83 | 84 | 85 | 86 | 87 | 88 |
89 | 90 | 94 | 95 |
96 | 97 |

Mastodon Timeline Embed: MEFT

98 | 99 |
100 |
101 |

About This Method

102 |

103 | This method uses the mastodon-embed-timeline (MEFT) JavaScript library. 105 | It fetches timeline data from the Mastodon API and renders it client-side into a designated HTML 106 | element. 107 |

108 |

109 | Configuration requires setting JavaScript variables before loading the main library script. 110 | See the Configuration Script section below the rendered timeline. 111 |

112 |

113 | Important: You need the Mastodon User ID (a number, not the username) 114 | for the account you want to embed. You can often find this using API tools (like searching the account via 115 | /.well-known/webfinger on the instance and looking for the ID in the self link) 116 | or sometimes by viewing the source code of the user's profile page. 117 |

118 |
119 |
120 |
121 |
122 |

Configuration Script

123 |

The following script block sets the configuration options for the library, and is set towards the end of 124 | the code in this example page. Modify these values as 125 | needed. Here, we're asking to fetch 40 posts, and show a maximum of 20; but we're hiding unlisted posts, 126 | replies, reblogs, and pinned posts, all of which get filtered from the number of posts fetched, so it may 127 | show fewer than 20.

128 |

129 |
<script>
130 | const myTimeline = new MastodonTimeline.Init({
131 |       instanceUrl: "https://macaw.social",
132 |       timelineType: "profile",
133 |       userId: "109332977621728450",
134 |       profileName: "@andypiper",
135 |       defaultTheme: "auto",
136 |       maxNbPostFetch: "40",
137 |       maxNbPostShow: "20",
138 |       hideUnlisted: true,
139 |       hideReplies: true,
140 |       hideReblog: true,
141 |       hidePinnedPosts: true,
142 |       // Add any other options you want to configure
143 |       // See the documentation for more details
144 | });
145 | </script>
146 |
147 |
148 | 149 |
150 |
151 |

Rendered Timeline

152 |

This is what the embedded timeline looks like using this method:

153 | 154 |
155 |
156 |
157 |
158 |
159 | 160 |
161 |
162 |
163 | 164 | 165 |
166 | 167 | 177 | 178 | 234 | 235 | 236 | 238 | 239 | 257 | 258 | 259 | 260 | 261 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Mastodon Embed Examples 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 64 | 65 | 66 | 67 | 68 | 69 |
70 | 74 |
75 | 76 |
77 |
78 |
79 | Mastodon Logo 80 |
81 |
82 |
83 |

Mastodon Embed Examples

84 |

Testing various ways to embed Mastodon content.

85 |
86 |
87 | 88 | 89 |
90 |
91 |

Introduction

92 |

93 | This is a simple project for testing embeds of content from Mastodon using different tools and methods. 94 | The goal is to provide clear, isolated examples. 95 |

96 |

97 | 📝 Read more background on DEV: The Fediverse is an Opportunity for Developers. 99 |

100 |
101 |
102 | 103 |
104 |
105 |
106 |
107 |

Mastodon Post Embeds

108 |

Embedding single posts is straightforward using Mastodon's API (it supports 109 | oEmbed, and 110 | there is an option to get embed code on the default web UI).

111 | 119 |
120 |
121 |
122 |
123 |
124 |
125 |

Mastodon Timeline Embeds

126 |

Embedding timelines requires external services or client-side libraries, as Mastodon doesn't offer a 127 | built-in timeline embed.

128 | 138 |
139 |
140 |
141 |
142 | 143 |
144 |
145 |
146 |
147 |

Other Notes

148 |

Some examples include a Fediverse sharing button.

150 |

The code for these examples is available on GitHub.

153 |
154 |
155 |
156 |
157 |
158 |
159 |

Find Me on the Fediverse

160 |

Say hello 👋🏻:

161 | 169 |
170 |
171 |
172 |
173 | 174 |
175 |
176 | 177 | 187 | 188 | 244 | 245 | 246 | 247 | 248 | 249 | -------------------------------------------------------------------------------- /css/fedi-share.css: -------------------------------------------------------------------------------- 1 | .fedi-check-toggle { 2 | height: 0; 3 | width: 0; 4 | padding: 0; 5 | margin: 0; 6 | visibility: hidden; 7 | display: none; 8 | } 9 | 10 | .fedi-check-toggle:checked + .fedi-instance { 11 | display: block !important; 12 | } 13 | 14 | .fedi-check-toggle:not(:checked) + .fedi-instance { 15 | display: none; 16 | } 17 | 18 | .fedi-share.active { 19 | padding-bottom: 10px; 20 | } 21 | 22 | .fedi-share-lg .fedi-top { 23 | cursor: pointer; 24 | position: absolute; 25 | width: 180px; 26 | top: 0; 27 | left: 0; 28 | padding: 4px 8px; 29 | z-index: 5; 30 | } 31 | 32 | .fedi-share-sm .fedi-top, 33 | .fedi-share-md .fedi-top { 34 | cursor: pointer; 35 | position: absolute; 36 | width: 18px; 37 | height: 15px; 38 | top: 0; 39 | left: 0; 40 | padding: 4px 8px; 41 | z-index: 1; 42 | } 43 | 44 | .fedi-share-sm, 45 | .fedi-share-md, 46 | .fedi-share-lg { 47 | color: white; 48 | display: inline-block; 49 | height: 23px; 50 | background-color: #292d37; 51 | border-radius: 3px; 52 | position: relative; 53 | z-index: 5; 54 | } 55 | 56 | .fedi-share-lg { 57 | width: 180px; 58 | } 59 | 60 | .fedi-share-md { 61 | width: 93px; 62 | } 63 | 64 | .fedi-share-sm { 65 | width: 34px; 66 | } 67 | 68 | .fedi-share-sm .fedi-logo { 69 | background: url("data:image/svg+xml,%3C%3Fxml version='1.0' encoding='UTF-8'%3F%3E%3Csvg width='196.52mm' height='196.52mm' version='1.1' viewBox='0 0 196.52 196.52' xmlns='http://www.w3.org/2000/svg' xmlns:cc='http://creativecommons.org/ns%23' xmlns:dc='http://purl.org/dc/elements/1.1/' xmlns:rdf='http://www.w3.org/1999/02/22-rdf-syntax-ns%23'%3E%3Cmetadata%3E%3Crdf:RDF%3E%3Ccc:Work rdf:about=''%3E%3Cdc:format%3Eimage/svg+xml%3C/dc:format%3E%3Cdc:type rdf:resource='http://purl.org/dc/dcmitype/StillImage'/%3E%3Cdc:title/%3E%3C/cc:Work%3E%3C/rdf:RDF%3E%3C/metadata%3E%3Cg transform='translate(6.679 -32.496)' shape-rendering='auto'%3E%3Cpath transform='matrix(.26458 0 0 .26458 -6.679 32.496)' d='m181.13 275.14a68.892 68.892 0 0 1-29.465 29.328l161.76 162.39 38.998-19.764zm213.36 214.19-38.998 19.764 81.963 82.283a68.892 68.892 0 0 1 29.471-29.332z' color='%23000000' color-rendering='auto' dominant-baseline='auto' fill='%23a730b8' image-rendering='auto' solid-color='%23000000' style='font-feature-settings:normal;font-variant-alternates:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-variant-position:normal;isolation:auto;mix-blend-mode:normal;shape-padding:0;text-decoration-color:%23000000;text-decoration-line:none;text-decoration-style:solid;text-indent:0;text-orientation:mixed;text-transform:none;white-space:normal'/%3E%3Cpath transform='matrix(.26458 0 0 .26458 -6.679 32.496)' d='m581.65 339.39-91.576 46.41 6.752 43.189 103.62-52.514a68.892 68.892 0 0 1-18.791-37.086zm-144.74 73.352-216.53 109.73a68.892 68.892 0 0 1 18.795 37.09l204.48-103.63z' color='%23000000' color-rendering='auto' dominant-baseline='auto' fill='%235496be' image-rendering='auto' solid-color='%23000000' style='font-feature-settings:normal;font-variant-alternates:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-variant-position:normal;isolation:auto;mix-blend-mode:normal;shape-padding:0;text-decoration-color:%23000000;text-decoration-line:none;text-decoration-style:solid;text-indent:0;text-orientation:mixed;text-transform:none;white-space:normal'/%3E%3Cpath transform='matrix(.26458 0 0 .26458 -6.679 32.496)' d='m367.28 142.44-104.48 203.97 30.848 30.967 110.62-215.96a68.892 68.892 0 0 1-36.99-18.98zm-131.65 257.02-52.922 103.31a68.892 68.892 0 0 1 36.986 18.979l46.781-91.328z' color='%23000000' color-rendering='auto' dominant-baseline='auto' fill='%23ce3d1a' image-rendering='auto' solid-color='%23000000' style='font-feature-settings:normal;font-variant-alternates:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-variant-position:normal;isolation:auto;mix-blend-mode:normal;shape-padding:0;text-decoration-color:%23000000;text-decoration-line:none;text-decoration-style:solid;text-indent:0;text-orientation:mixed;text-transform:none;white-space:normal'/%3E%3Cpath transform='matrix(.26458 0 0 .26458 -6.679 32.496)' d='m150.77 304.92a68.892 68.892 0 0 1-34.416 7.1953 68.892 68.892 0 0 1-6.6504-0.69531l30.902 197.66a68.892 68.892 0 0 1 34.416-7.1953 68.892 68.892 0 0 1 6.6465 0.69531z' color='%23000000' color-rendering='auto' dominant-baseline='auto' fill='%23d0188f' image-rendering='auto' solid-color='%23000000' style='font-feature-settings:normal;font-variant-alternates:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-variant-position:normal;isolation:auto;mix-blend-mode:normal;shape-padding:0;text-decoration-color:%23000000;text-decoration-line:none;text-decoration-style:solid;text-indent:0;text-orientation:mixed;text-transform:none;white-space:normal'/%3E%3Cpath transform='matrix(.26458 0 0 .26458 -6.679 32.496)' d='m239.34 560.54a68.892 68.892 0 0 1 0.7207 13.877 68.892 68.892 0 0 1-7.2676 27.18l197.63 31.713a68.892 68.892 0 0 1-0.72266-13.879 68.892 68.892 0 0 1 7.2695-27.178z' color='%23000000' color-rendering='auto' dominant-baseline='auto' fill='%235b36e9' image-rendering='auto' solid-color='%23000000' style='font-feature-settings:normal;font-variant-alternates:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-variant-position:normal;isolation:auto;mix-blend-mode:normal;shape-padding:0;text-decoration-color:%23000000;text-decoration-line:none;text-decoration-style:solid;text-indent:0;text-orientation:mixed;text-transform:none;white-space:normal'/%3E%3Cpath transform='matrix(.26458 0 0 .26458 -6.679 32.496)' d='m601.13 377.2-91.219 178.08a68.892 68.892 0 0 1 36.994 18.982l91.217-178.08a68.892 68.892 0 0 1-36.992-18.984z' color='%23000000' color-rendering='auto' dominant-baseline='auto' fill='%2330b873' image-rendering='auto' solid-color='%23000000' style='font-feature-settings:normal;font-variant-alternates:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-variant-position:normal;isolation:auto;mix-blend-mode:normal;shape-padding:0;text-decoration-color:%23000000;text-decoration-line:none;text-decoration-style:solid;text-indent:0;text-orientation:mixed;text-transform:none;white-space:normal'/%3E%3Cpath transform='matrix(.26458 0 0 .26458 -6.679 32.496)' d='m476.72 125.33a68.892 68.892 0 0 1-29.471 29.332l141.27 141.81a68.892 68.892 0 0 1 29.469-29.332z' color='%23000000' color-rendering='auto' dominant-baseline='auto' fill='%23ebe305' image-rendering='auto' solid-color='%23000000' style='font-feature-settings:normal;font-variant-alternates:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-variant-position:normal;isolation:auto;mix-blend-mode:normal;shape-padding:0;text-decoration-color:%23000000;text-decoration-line:none;text-decoration-style:solid;text-indent:0;text-orientation:mixed;text-transform:none;white-space:normal'/%3E%3Cpath transform='matrix(.26458 0 0 .26458 -6.679 32.496)' d='m347.79 104.63-178.58 90.498a68.892 68.892 0 0 1 18.793 37.086l178.57-90.502a68.892 68.892 0 0 1-18.791-37.082z' color='%23000000' color-rendering='auto' dominant-baseline='auto' fill='%23f47601' image-rendering='auto' solid-color='%23000000' style='font-feature-settings:normal;font-variant-alternates:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-variant-position:normal;isolation:auto;mix-blend-mode:normal;shape-padding:0;text-decoration-color:%23000000;text-decoration-line:none;text-decoration-style:solid;text-indent:0;text-orientation:mixed;text-transform:none;white-space:normal'/%3E%3Cpath transform='matrix(.26458 0 0 .26458 -6.679 32.496)' d='m446.93 154.83a68.892 68.892 0 0 1-34.982 7.4824 68.892 68.892 0 0 1-6.0293-0.63281l15.818 101.29 43.162 6.9258zm-16 167.03 37.4 239.48a68.892 68.892 0 0 1 33.914-6.9434 68.892 68.892 0 0 1 7.207 0.79101l-35.357-226.41z' color='%23000000' color-rendering='auto' dominant-baseline='auto' fill='%2357c115' image-rendering='auto' solid-color='%23000000' style='font-feature-settings:normal;font-variant-alternates:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-variant-position:normal;isolation:auto;mix-blend-mode:normal;shape-padding:0;text-decoration-color:%23000000;text-decoration-line:none;text-decoration-style:solid;text-indent:0;text-orientation:mixed;text-transform:none;white-space:normal'/%3E%3Cpath transform='matrix(.26458 0 0 .26458 -6.679 32.496)' d='m188.13 232.97a68.892 68.892 0 0 1 0.75781 14.096 68.892 68.892 0 0 1-7.1602 26.982l101.37 16.281 19.924-38.908zm173.74 27.9-19.926 38.912 239.51 38.467a68.892 68.892 0 0 1-0.69531-13.719 68.892 68.892 0 0 1 7.3496-27.324z' color='%23000000' color-rendering='auto' dominant-baseline='auto' fill='%23dbb210' image-rendering='auto' solid-color='%23000000' style='font-feature-settings:normal;font-variant-alternates:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-variant-position:normal;isolation:auto;mix-blend-mode:normal;shape-padding:0;text-decoration-color:%23000000;text-decoration-line:none;text-decoration-style:solid;text-indent:0;text-orientation:mixed;text-transform:none;white-space:normal'/%3E%3C/g%3E%3Cg transform='translate(6.679 -32.496)' fill-opacity='.99597'%3E%3Ccircle transform='rotate(3.1178)' cx='106.27' cy='51.536' r='16.571' fill='%23ffca00'/%3E%3Ccircle transform='rotate(3.1178)' cx='171.43' cy='110.19' r='16.571' fill='%2364ff00'/%3E%3Ccircle transform='rotate(3.1178)' cx='135.76' cy='190.28' r='16.571' fill='%2300a3ff'/%3E%3Ccircle transform='rotate(3.1178)' cx='48.559' cy='181.11' r='16.571' fill='%239500ff'/%3E%3Ccircle transform='rotate(3.1178)' cx='30.329' cy='95.367' r='16.571' fill='%23f00'/%3E%3C/g%3E%3C/svg%3E%0A"); 70 | } 71 | 72 | .fedi-share-sm .fedi-instance, 73 | .fedi-share-md .fedi-instance { 74 | border-radius: 0 3px 3px 3px; 75 | z-index: 0; 76 | } 77 | 78 | .fedi-instance { 79 | background-color: #292d37; 80 | padding: 8px; 81 | position: absolute; 82 | top: 20px; 83 | left: 0; 84 | margin: 0 auto; 85 | border-radius: 0 0 3px 3px; 86 | } 87 | 88 | .fedi-share span { 89 | vertical-align: top; 90 | font-family: sans-serif; 91 | font-weight: 700; 92 | font-size: 12.5px; 93 | } 94 | 95 | .fedi-share img { 96 | max-height: 19px; 97 | margin-top: -2px; 98 | margin-left: -0.5px; 99 | } 100 | 101 | .fedi-instance input[name="fedi-instance-input"] { 102 | background-color: #00000000; 103 | border: none; 104 | color: white; 105 | border-bottom: 2px solid #3087d5; 106 | font-size: 14px; 107 | font-weight: 700; 108 | max-width: 150px; 109 | } 110 | 111 | .fedi-instance input[name="fedi-instance-input"][class="invalid"] { 112 | border-bottom: 2px solid red; 113 | animation-name: shake; 114 | animation-duration: 100ms; 115 | animation-timing-function: ease-in-out; 116 | animation-iteration-count: infinite; 117 | } 118 | @-webkit-keyframes shake { 119 | 8%, 120 | 41% { 121 | -webkit-transform: translateX(-10px); 122 | } 123 | 25%, 124 | 58% { 125 | -webkit-transform: translateX(10px); 126 | } 127 | 75% { 128 | -webkit-transform: translateX(-5px); 129 | } 130 | 92% { 131 | -webkit-transform: translateX(5px); 132 | } 133 | 0%, 134 | 100% { 135 | -webkit-transform: translateX(0); 136 | } 137 | } 138 | 139 | .fedi-share input::placeholder { 140 | color: #c7c7cd; 141 | } 142 | 143 | .fedi-share input:focus { 144 | outline: none; 145 | } 146 | 147 | .fedi-share button { 148 | background-color: #00000000; 149 | color: white; 150 | border: none; 151 | font-size: 14px; 152 | font-weight: 700; 153 | padding: 0; 154 | margin: 8px 0 0 0; 155 | cursor: pointer; 156 | } 157 | 158 | .fedi-share button:hover { 159 | color: #3087d5; 160 | } 161 | -------------------------------------------------------------------------------- /mastopy.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Mastodon.py Single Post Display 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 239 | 240 | 241 | 242 |
243 |

Mastodon.py Post Viewer

244 | 245 |
246 | 247 | 248 |
249 | 250 |
251 | 252 | 253 |
254 | 255 | 256 | 257 | 260 | 261 |
262 |
263 |
264 | 265 |
266 | Powered by PyScript and 267 | Mastodon.py 268 |
269 | 270 | 271 | packages = ["pyodide-http", "Mastodon.py"] 272 | 273 | 274 | 423 | 424 | 425 | 426 | -------------------------------------------------------------------------------- /single-post-oembed.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Single Post Embed Example 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 50 | 51 | 52 | 53 | 54 | 55 | 59 | 60 |
61 |
62 | 63 |

Mastodon Single Post Embed

64 | 65 |
66 |
67 |

Using the Default Mastodon Embed Code

68 |

69 | You can quickly grab code for any Mastodon post by choosing the "Get embed code" option 70 | from the [...] menu on the Mastodon web interface when the post is loaded. 71 | This is the simplest method for single posts. It will likely give you the 72 | same blockquote code shown below. 73 |

74 |
75 |
76 | 77 |
78 |
79 |

Using the oEmbed API

80 |

81 | Another method is to programmatically get the embed code directly via the oEmbed API. 83 | For example, the embed below (from macaw.social, which is where my own account lives) could 84 | be retrieved using: 85 |

86 |
87 | curl "https://macaw.social/api/oembed?url=https://macaw.social/@andypiper/109349688112355529" 88 |
89 | 90 |

The API returns JSON data like this:

91 |
{
 92 |   "type": "rich",
 93 |   "version": "1.0",
 94 |   "author_name": "Andy Piper",
 95 |   "author_url": "https://macaw.social/@andypiper",
 96 |   "provider_name": "macaw.social",
 97 |   "provider_url": "https://macaw.social/",
 98 |   "cache_age": 86400,
 99 |   "html": "\u003cblockquote class=\"mastodon-embed\" data-embed-url=\"https://macaw.social/@andypiper/109349688112355529/embed\" style=\"background: #FCF8FF; border-radius: 8px; border: 1px solid #C9C4DA; margin: 0; max-width: 540px; min-width: 270px; overflow: hidden; padding: 0;\"\u003e \u003ca href=\"https://macaw.social/@andypiper/109349688112355529\" target=\"_blank\" style=\"align-items: center; color: #1C1A25; display: flex; flex-direction: column; font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Oxygen, Ubuntu, Cantarell, 'Fira Sans', 'Droid Sans', 'Helvetica Neue', Roboto, sans-serif; font-size: 14px; justify-content: center; letter-spacing: 0.25px; line-height: 20px; padding: 24px; text-decoration: none;\"\u003e \u003csvg xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" width=\"32\" height=\"32\" viewBox=\"0 0 79 75\"\u003e\u003cpath d=\"M74.7135 16.6043C73.6199 8.54587 66.5351 2.19527 58.1366 0.964691C56.7196 0.756754 51.351 0 38.9148 0H38.822C26.3824 0 23.7135 0.756754 22.2966 0.964691C14.1319 2.16118 6.67571 7.86752 4.86669 16.0214C3.99657 20.0369 3.90371 24.4888 4.06535 28.5726C4.29578 34.4289 4.34049 40.275 4.877 46.1075C5.24791 49.9817 5.89495 53.8251 6.81328 57.6088C8.53288 64.5968 15.4938 70.4122 22.3138 72.7848C29.6155 75.259 37.468 75.6697 44.9919 73.971C45.8196 73.7801 46.6381 73.5586 47.4475 73.3063C49.2737 72.7302 51.4164 72.086 52.9915 70.9542C53.0131 70.9384 53.0308 70.9178 53.0433 70.8942C53.0558 70.8706 53.0628 70.8445 53.0637 70.8179V65.1661C53.0634 65.1412 53.0574 65.1167 53.0462 65.0944C53.035 65.0721 53.0189 65.0525 52.9992 65.0371C52.9794 65.0218 52.9564 65.011 52.9318 65.0056C52.9073 65.0002 52.8819 65.0003 52.8574 65.0059C48.0369 66.1472 43.0971 66.7193 38.141 66.7103C29.6118 66.7103 27.3178 62.6981 26.6609 61.0278C26.1329 59.5842 25.7976 58.0784 25.6636 56.5486C25.6622 56.5229 25.667 56.4973 25.6775 56.4738C25.688 56.4502 25.7039 56.4295 25.724 56.4132C25.7441 56.397 25.7678 56.3856 25.7931 56.3801C25.8185 56.3746 25.8448 56.3751 25.8699 56.3816C30.6101 57.5151 35.4693 58.0873 40.3455 58.086C41.5183 58.086 42.6876 58.086 43.8604 58.0553C48.7647 57.919 53.9339 57.6701 58.7591 56.7361C58.8794 56.7123 58.9998 56.6918 59.103 56.6611C66.7139 55.2124 73.9569 50.665 74.6929 39.1501C74.7204 38.6967 74.7892 34.4016 74.7892 33.9312C74.7926 32.3325 75.3085 22.5901 74.7135 16.6043ZM62.9996 45.3371H54.9966V25.9069C54.9966 21.8163 53.277 19.7302 49.7793 19.7302C45.9343 19.7302 44.0083 22.1981 44.0083 27.0727V37.7082H36.0534V27.0727C36.0534 22.1981 34.124 19.7302 30.279 19.7302C26.8019 19.7302 25.0651 21.8163 25.0617 25.9069V45.3371H17.0656V25.3172C17.0656 21.2266 18.1191 17.9769 20.2262 15.568C22.3998 13.1648 25.2509 11.9308 28.7898 11.9308C32.8859 11.9308 35.9812 13.492 38.0447 16.6111L40.036 19.9245L42.0308 16.6111C44.0943 13.492 47.1896 11.9308 51.2788 11.9308C54.8143 11.9308 57.6654 13.1648 59.8459 15.568C61.9529 17.9746 63.0065 21.2243 63.0065 25.3172L62.9996 45.3371Z\" fill=\"currentColor\"/\u003e\u003c/svg\u003e \u003cdiv style=\"color: #787588; margin-top: 16px;\"\u003ePost by @andypiper@macaw.social\u003c/div\u003e \u003cdiv style=\"font-weight: 500;\"\u003eView on Mastodon\u003c/div\u003e \u003c/a\u003e \u003c/blockquote\u003e \u003cscript data-allowed-prefixes=\"https://macaw.social/\" async src=\"https://macaw.social/embed.js\"\u003e\u003c/script\u003e",
100 |   "width": 400,
101 |   "height": null
102 | }
103 |

The actual HTML embed code is found as an escaped string within the html property in the 104 | JSON response.

105 |
106 |
107 | 108 |
109 |
110 |

Rendered embed

111 |

This is what the embedded post looks like when rendered in the browser:

112 | 113 |
115 | 117 | 119 | 122 | 123 |
Post by @andypiper@macaw.social
124 |
View on Mastodon
125 |
126 |
127 | 128 | 129 |
130 |
131 | 132 |
133 |
134 | 144 | 145 | 201 | 202 | 203 | 204 | 205 | 206 | -------------------------------------------------------------------------------- /timeline-basic-rss.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | User Timeline via RSS Feed (JavaScript) 9 | 10 | 11 | 13 | 15 | 16 | 17 | 18 | 19 | 20 | 22 | 23 | 24 | 25 | 26 | 115 | 116 | 117 | 118 | 119 | 120 |
121 | 122 | 126 | 127 |
128 | 129 |

Mastodon User Timeline via RSS Feed

130 | 131 |
132 |
133 |

About This Method

134 |

135 | Mastodon instances provide a public RSS feed for each user's public posts, found at 136 | https://[instance.domain]/users/[username].rss. 137 |

138 |

139 | This example uses client-side JavaScript to attempt to fetch this RSS feed, parse the XML content using 140 | the browser's built-in DOMParser, and then render the latest posts below. 141 |

142 | 143 |
144 |
145 |

Content Limitations

146 |
147 |
148 | The RSS feed only includes the user's public, original posts. It excludes replies 149 | and boosts.
150 | The standard RSS format lacks a specific field for Content Warnings (CWs). Posts with 151 | CWs will have their full content displayed immediately. 152 |
153 |
154 | 155 |
156 |
157 |

CORS Limitation

158 |
159 |
160 | Fetching resources (like RSS feeds) directly from a different domain using JavaScript is often blocked 161 | by browser security policies (CORS). This example may not work reliably unless the Mastodon 162 | instance's server is configured to allow requests from your website's origin, or if you use a CORS proxy. It is included mainly as a demonstration of 165 | parsing the feed format. Check the browser console for errors if the feed doesn't load. 166 |
167 |
168 |
169 |
170 | 171 |
172 |
173 |

Configuration

174 |
175 | 176 |
177 | 178 |
179 |
180 |
181 | 182 |
183 | 184 |
185 |
186 |
187 | 188 |
189 | 190 |
191 |
192 |
193 |
194 | 195 |
196 |
197 |
198 |
199 | 200 |
201 |
202 |

Rendered Timeline (from RSS)

203 |

Attempting to fetch and display the latest posts from the RSS feed:

204 |
205 |

Click button above to load feed...

206 |
207 |
208 |
209 | 210 |
211 |
212 | 213 | 223 | 224 | 280 | 281 | 282 | 377 | 378 | 379 | 380 | 381 | -------------------------------------------------------------------------------- /fedi-share.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /single-post-pyscript.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | Single Post Embed via PyScript (Mastodon.py) 13 | 14 | 15 | 17 | 19 | 20 | 21 | 22 | 23 | 24 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 282 | 283 | 284 | 285 | 286 | 287 |
288 | 289 | 293 | 294 |
295 | 296 |

Mastodon Single Post Embed via PyScript + Mastodon.py

297 | 298 |
299 |
300 |

About This Method

301 |

302 | This example uses PyScript 303 | to run Python code directly in the browser, with the pyodide_http.patch_all() method 304 | to enable the Mastodon.py library. 306 |

307 |

308 | The Python code in this page fetches the raw status data for post 109349688112355529 from 309 | macaw.social using Mastodon.py's status() method (unauthenticated). 310 | It then constructs HTML to display the post details and injects it into the page using PyScript's display() function. 311 |

312 |

313 | This gives full control over rendering using Python, but requires writing the HTML generation code. Be aware 314 | that fetching data might fail if the instance requires authentication (even for public posts). 315 |

316 |
317 |
318 | 319 |
320 |
321 |

Rendered Post (via PyScript + Mastodon.py)

322 |

The following post data was fetched and rendered using Python running in the browser:

323 | 324 |
325 |

Loading post data via PyScript...

326 |
327 | 328 |
329 |
330 | 331 |
332 |
333 | 334 |
335 |

336 | Mastodon Embed Examples. 337 | Powered by PyScript and 338 | Mastodon.py. 339 | Styled using Bulma. 340 | Find me on Mastodon: @andypiper@macaw.social. 342 |

343 |
344 | 345 | 346 | 347 | packages = ["mastodon-py", "pyodide-http"] 348 | 349 | 350 | 351 | 553 | 554 | 555 | 611 | 612 | 613 | 614 | 615 | 616 | -------------------------------------------------------------------------------- /timeline-pyscript.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | User Timeline via PyScript (Mastodon.py + Auth) 12 | 13 | 14 | 16 | 18 | 19 | 20 | 21 | 22 | 23 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 220 | 221 | 222 | packages = ["mastodon-py", "pyodide-http"] 223 | 224 | 225 | 226 | 227 | 228 | 229 |
230 | 231 | 235 | 236 |
237 | 238 |
239 |

Login Required

240 |
241 |

To view the timeline, please enter your Mastodon instance URL and log in.

242 |
243 | 244 |
245 | 246 |
247 |
248 |
249 |
250 | 251 |
252 |
253 |
254 |
255 |
256 | 257 |
258 | 259 |

Mastodon User Timeline via PyScript + Mastodon.py

260 | 261 |
262 |
263 |

About This Method

264 |

265 | This example uses PyScript 266 | and the Mastodon.py library (enabled via pyodide_http.patch_all()) 268 | to fetch and render a user's timeline directly in the browser after authenticating via OAuth. 269 |

270 |

271 | The Python code uses an access token stored in the browser's localStorage to authenticate with the 272 | Mastodon API. It then fetches the 10 most recent posts for the specified Account ID 273 | (109332977621728450 from macaw.social) using 274 | api.account_statuses() and renders them below. 275 |

276 |
277 |
278 | 279 | 280 | 281 |
282 |
283 |

Rendered Timeline

284 |

Displaying the 10 most recent posts for the configured account:

285 |
286 |

Loading timeline data via PyScript...

287 |
288 |
289 |
290 |
291 |
292 |
293 | 294 | 306 | 307 | 589 | 590 | 591 | 647 | 648 | 649 | 650 | 651 | 652 | -------------------------------------------------------------------------------- /images/favicon.svg: -------------------------------------------------------------------------------- 1 | --------------------------------------------------------------------------------