If you’ve made the migration over to Mastodon, you’re probably looking to automate a few of your activities using your programming skills. Since I mostly write code using C# and .NET, I thought I would take the time to look at how to do just that. This post isn’t really long, but should make it easier for you to consume RSS feeds coming from Mastodon accounts.

Mastodon Users and RSS Feeds

If you’re new to Mastodon, you probably aren’t aware that everyone’s account can be instantly converted into an RSS feed of their latest posts. Take my account for instance at @khalidabuhakmeh@mastodon.social. Just by adding a .rss extension to the end of the url of https://mastodon.social/@khalidabuhakmeh you can convert my profile into an RSS feed. Very neat.

<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:webfeeds="http://webfeeds.org/rss/1.0" xmlns:media="http://search.yahoo.com/mrss/">
  <channel>
    <title>Khalid ⚡</title>
    <description>Public posts from @khalidabuhakmeh@mastodon.social</description>
    <link>https://mastodon.social/@khalidabuhakmeh</link>
    <image>
      <url>https://files.mastodon.social/accounts/avatars/000/467/424/original/62d2f4d9361a3cab.gif</url>
      <title>Khalid ⚡</title>
      <link>https://mastodon.social/@khalidabuhakmeh</link>
    </image>
    <lastBuildDate>Fri, 16 Dec 2022 16:40:41 +0000</lastBuildDate>
    <webfeeds:icon>https://files.mastodon.social/accounts/avatars/000/467/424/original/62d2f4d9361a3cab.gif</webfeeds:icon>
    <generator>Mastodon v4.0.2</generator>
    <item>
      <guid isPermaLink="true">https://mastodon.social/@khalidabuhakmeh/109524342610611176</guid>
      <link>https://mastodon.social/@khalidabuhakmeh/109524342610611176</link>
      <pubDate>Fri, 16 Dec 2022 16:40:41 +0000</pubDate>
      <description>&lt;p&gt;Is there a way to &amp;quot;page&amp;quot; someone&amp;#39;s rss feed from Mastodon?&lt;/p&gt;&lt;p&gt;Right now you only get the last 20 posts by default.&lt;/p&gt;</description>
    </item>
  </channel>
</rss>

Knowing this, you can now process RSS feeds from your favorite accounts, but you’ll need a little help from .NET.

Mastodon C# Classes and the XmlSerializer

Impowered by the knowledge that accounts also expose RSS feeds, you’ll need to take the XML and turn them into C# objects. Here are the types to deserialize that XML.

using System.ComponentModel;
using System.Xml.Serialization;

namespace Mastodon;

[XmlRoot(ElementName = "image")]
public class Image
{
    [XmlElement(ElementName = "url")] public string Url { get; set; } = "";

    [XmlElement(ElementName = "title")] public string Title { get; set; } = "";

    [XmlElement(ElementName = "link")] public string Link { get; set; } = "";
}

[XmlRoot(ElementName = "guid")]
public class Guid
{
    [XmlAttribute(AttributeName = "isPermaLink")]
    public bool IsPermalink { get; set; }

    [XmlText] public string Text { get; set; } = "";
}

[XmlRoot(ElementName = "item")]
public class Item
{
    [XmlElement(ElementName = "guid")] public Guid Guid { get; set; } = new();

    [XmlElement(ElementName = "link")] public string Link { get; set; } = "";

    [XmlIgnore] public DateTimeOffset PubDate => DateTimeOffset.Parse(PubDateString);

    [XmlElement(ElementName = "pubDate"),
     EditorBrowsable(EditorBrowsableState.Never),
     Browsable(false)]
    public string PubDateString { get; set; } = "";

    [XmlElement(ElementName = "description")]
    public string Description { get; set; } = "";

    [XmlElement(ElementName = "content", Namespace = "http://search.yahoo.com/mrss/")]
    public List<Content> Media { get; set; } = new();

    [XmlElement(ElementName = "category")] public List<string> Category { get; set; } = new();

    [XmlElement(ElementName = "enclosure")]
    public Enclosure Enclosure { get; set; } = new();
}

[XmlRoot(ElementName = "rating", Namespace = "http://search.yahoo.com/mrss/")]
public class Rating
{
    [XmlAttribute(AttributeName = "scheme")]
    public string Scheme { get; set; } = "";

    [XmlText] public string Text { get; set; } = "";
}

[XmlRoot(ElementName = "description", Namespace = "http://search.yahoo.com/mrss/")]
public class Description
{
    [XmlAttribute(AttributeName = "type")] public string Type { get; set; } = "";

    [XmlText] public string Text { get; set; } = "";
}

[XmlRoot(ElementName = "content", Namespace = "http://search.yahoo.com/mrss/")]
public class Content
{
    [XmlElement(ElementName = "rating", Namespace = "http://search.yahoo.com/mrss/")]
    public Rating Rating { get; set; } = default!;

    [XmlElement(ElementName = "description", Namespace = "http://search.yahoo.com/mrss/")]
    public Description Description { get; set; }

    [XmlAttribute(AttributeName = "url")] public string Url { get; set; } = "";

    [XmlAttribute(AttributeName = "type")] public string Type { get; set; } = "";

    [XmlAttribute(AttributeName = "fileSize")]
    public int FileSize { get; set; }

    [XmlAttribute(AttributeName = "medium")]
    public string Medium { get; set; } = "";

    [XmlText] public string Text { get; set; } = "";
}

[XmlRoot(ElementName = "enclosure")]
public class Enclosure
{
    [XmlAttribute(AttributeName = "url")] public string Url { get; set; } = "";

    [XmlAttribute(AttributeName = "length")]
    public int Length { get; set; }

    [XmlAttribute(AttributeName = "type")] public string Type { get; set; } = "";
}

[XmlRoot(ElementName = "channel")]
public class Channel
{
    [XmlElement(ElementName = "title")] public string Title { get; set; } = "";

    [XmlElement(ElementName = "description")]
    public string Description { get; set; } = "";

    [XmlElement(ElementName = "link")] public string Link { get; set; } = "";

    [XmlElement(ElementName = "image")] public Image Image { get; set; } = new();

    [XmlIgnore]
    public DateTimeOffset LastBuildDate
        => DateTimeOffset.Parse(LastBuildDateString);

    [XmlElement(ElementName = "lastBuildDate"),
     EditorBrowsable(EditorBrowsableState.Never),
     Browsable(false)]
    public string LastBuildDateString { get; set; } = "";

    [XmlElement(ElementName = "icon", Namespace = "http://webfeeds.org/rss/1.0")]
    public string Icon { get; set; } = "";

    [XmlElement(ElementName = "generator")]
    public string Generator { get; set; } = "";

    [XmlElement(ElementName = "item")] public List<Item> Items { get; set; } = new();
}

[XmlRoot(ElementName = "rss")]
public class Rss
{
    [XmlElement(ElementName = "channel")] public Channel Channel { get; set; } = new();

    [XmlAttribute(AttributeName = "version")]
    public double Version { get; set; }
}

Now, it’s as simple as reading the feed from a URL and working with the objects.

using System.Xml;
using System.Xml.Serialization;

var url = "https://mastodon.social/@khalidabuhakmeh.rss";
using var reader = XmlReader.Create(url);
var serializer = new XmlSerializer(typeof(Mastodon.Rss));
var feed = (Mastodon.Rss)serializer.Deserialize(reader)!;

Awesome!

Use Cases and Drawbacks

Currently, Mastodon search is dependent on the instance you’ve joined and kind of flaky, if it works at all. You may want to process your own RSS feed periodically and create a searchable store of your interactions online. This can help you recall recurring conversations and reference past events with more accuracy. If you’re looking to do so, I recommend search services like Typesense or Algolia.

Now, the downside. RSS feed results are limited to a certain number of results. I was reliably able to get the last 100 items from my account, but this will likely depend on your Mastodon host. If you need to build a complete history of posts from an account, you might be better off using your instance’s API endpoints.

Conclusion

There you have it. I hope you found this post helpful and I’d love to hear what you’re building with Mastodon.