Last September, I blogged about how I used the Spotify API and Pipedream to discover new music: Discover New Music with the Spotify API and Pipedream. I used a Pipedream workflow to select a random track from Spotify and email me a track every morning. I've still got this process running and I enjoy seeing it every morning. More recently, I noticed a cool bit of album art in my Spotify client and it occurred to me that it would be kind of cool to see more. With that in mind, I present to you my latest Mastodon bot, Random Album Cover. You can see an example toot here:

Album: The Best Damn Thing (Expanded Edition) (
Artist(s): Avril Lavigne
Released: 2007-04-17

Image 111965939866392411 from toot 

I have no idea what you'll see when viewing this post as it will be generated during a build, but I'm looking at a striking album cover from an artist I've never heard of, NLE Choppa. So, how was it built?

For the most part, it follows the logic of my previous post, doing the following:

  • Select a random letter
  • Randomly decide to make it the beginning of a search string ("A something") or in the middle ("something A something")
  • Select a random number between 0 and 1000
  • Hit the Spotify API. Their API doesn't have a "real" random search, but we use the random letter and offset to search.
  • Given our set of results, select a random record from that.

All of the above hasn't changed from the previous post, except I switched the search from track to album. Next, I download the image to a temporary directory. This is straight from the Pipedream samples:

import stream from "stream";
import { promisify } from "util";
import fs from "fs";
import got from "got";

export default defineComponent({
  async run({ steps, $ }) {

    const pipeline = promisify(stream.pipeline);
    return await pipeline($return_value.images[0].url),


And then I post the toot. This code is pretty short as it makes use of the excellent mastodon-api package. My only real work is crafting the text to go along with the image.

import Mastodon from 'mastodon-api'
import fs from 'fs'

export default defineComponent({
  async run({ steps, $ }) {

    const M = new Mastodon({
      access_token: process.env.RANDOMALBUMCOVER_MASTODON,
      api_url: '', 

  let artists = steps.select_random_album.$return_value.artists.reduce((cur, art) => {
    if(cur == '') return;
    return cur + ', ' +

  let toot = `
Album:     ${steps.select_random_album.$} (${steps.select_random_album.$return_value.external_urls.spotify})
Artist(s): ${artists}
Released:  ${steps.select_random_album.$return_value.release_date}

  let resp = await'media', { file: fs.createReadStream('/tmp/cover.jpg') });
  await'statuses', { 
        status: toot,
        media_ids: [] 


I just want to go on record as saying that this is like the third or fourth time I've used reduce without checking the docs and I'm definitely a JavaScript expert now. Definitely.

I'll point out that I spent maybe thirty minutes total on this. The longest wait for was the Mastodon instance to approve my bot (maybe 1.5 hours). I also spent more than a few minutes wondering why my Python code wasn't running in a Node step, so maybe I'm not an expert. Maybe.

If you want to check out the complete workflow, you can do so here: