Home

Awesome

Echo - RSS Cross Poster

Echo screenshot

What is it?

Echo is a node script to post new items from an RSS feed to various services including Micro.blog and Mastodon.

Why "Echo"?

It does RSS feeds, so Feeder. Feeder are a band with an album called Echo Park. Echo is a good name because the album link AND the meaning of the word echo. So there.

Requirements

Installation

  1. Clone this repository
  2. Run npm install to install the node module
  3. Run cp config.example.js config.js to create a new config file
  4. Setup your RSS feeds and services (see configuration for options)
  5. Run node index.js init to setup - this will store the latest ID so only new posts going forward will be posted. If you want to post some items from a feed, add the ID of the latest item you don't want to post in data/nameofsite.txt.
  6. Setup a cron to run node index.js regularly

🚨 Warning: If you don't run node index.js init first, the script will post all the posts in the RSS feeds. You probably don't want this.

You can also run node index.js dry. This will log which posts will be created, but will not post anything.

Echo keeps track of the last item posted so on subsequent runs it will only post new posts.

You can also run Echo with GitHub actions. See Lewis' blog post for more info

Configuration

There are two parts to configure: sites and services. sites is the RSS feeds you want to cross-post and services is the services you want to cross-post to.

Go to the Echo website to use the config generator and paste the generated config into config.js or see below for setting it up manually.

Sites

config.sites is an array of RSS feeds you wish to cross-post. A site has five attribute:

Example Site Configuration

{
    name: "example.com",
    feed: "http://example.com/feed",
    categories: ["my category"],
    services: [SERVICES.MICROBLOG, SERVICES.WEBHOOK]
    transform: {
        getId: (data) => {
            return data.id
        },
        format: (data) => {
            return {
                content: data.content,
                date: data.isoDate,
                title: data.title, // optional
            }
        },
        filter: (items) => {
            return items.filter(item => {
                return !item.link.includes('/list/')
            })
        }
    }
}

Preset Transforms

Echo has a few presets you can use instead of having to write the getId and format functions for every site. These can be seen in presets.js. For example, to use the Letterboxd or status.lol preset you can do the following:

{
    name: "letterboxd.com",
    feed: "http://letterboxd.com/exampleuser/rss",
    categories: ["movies"],
    transform: presets.letterboxd,
},
{
    name: "status.lol",
    feed: "http://exampleuser.status.lol/feed",
    categories: ["status"],
    transform: presets.statuslol,
}

You can define the body of your post in format to make your posts look exactly how you want. For ease, Echo includes helpers.js with some helper libraries.

format: (data) => {
    const formatted = presets.default.format(data)

    // get the first link with Cheerio
    // and append it to the content
    const $ = helpers.cheerioLoad(formatted.content)
    const firstLink = $('a:first').attr('href')
    formatted.content += ` ${firstLink}`

    // format to markdown
    formatted.content = helpers.toMarkdown(formatted.content)
    return formatted
}

Services

Each service requires a different set of values depending on how the API works.

Micro.blog

KeyValueNotes
siteUrlThe Micro.blog site you're posting toe.g. https://coolsite.micro.blog
apiKeyA Micro.blog API keyGet an API key from https://micro.blog/account/apps

Mastodon

KeyValueNotes
instanceYour Mastodon instancee.g. https://social.lol
accessTokenGo to Preferences > Development > New Application on your Mastodon instance and grab the access token
visibility (optional, default public)public unlisted private direct
sensitive (optional, default false)false true

Webhooks

The webhook service will send a POST request with the result of transform.format (as set in your site config) to a given url.

KeyValueNotes
urlThe URL to post to

Omnivore

The Omnivore service will save a URL to your Omnivore account.

KeyValueNotes
apiKeyYour Omnivore API key

GitHub

Create a new file on a GitHub repository.

KeyValueNotes
tokenYour GitHub token
repoThe repository to commit toe.g. rknightuk/echo
branchThe branch to commit to
committerAn object with name and email valuese.g. { name: 'Robb', email: 'robb@example.com }

For posting to Github your format function must return content and filePath where filePath is the path to where the file will be in the Github repository, for example src/posts/movies/2024-02-09.md. It can optionally return a commit message, which will fallback to New post if none is set. Example format function for GitHub:

format: (data) => {
    return {
        content: data.title,
        date: new Date(data.isoDate).toISOString(),
        filePath: `src/posts/movies/${new Date().getFullYear()}/${new Date().toISOString().split('T').md`,
        commit: `Add ${data.title}`,
    }
}

LinkAce

KeyValueNotes
domainThe LinkAce domain where you have it installede.g.https://links.example.com
apiKeyA LinkAce API key

content returns from transform.format should be a link. Categories on the site will be converted to tags. Any tags included from format will be merged with categories.

{
    name: 'mycoollinkfeed',
    feed: 'https://example.com/linkfeed.xml',
    categories: ['ATag'],
    transform: {
        getId: presets.default.getId,
        format: (data) => {
            return {
                content: data.external_url,
                date: data.date_published,
                tags: data._custom.tags,
            }
        }
    },
    services: [SERVICES.LINKACE],
},

Webmentions

KeyValueNotes
no config is required for webmentions

content returns from transform.format should be a link.