Grooving with Camp Counselor: Your Bandcamp Buddy

Ah, the sweet siren call of new music! If there’s one thing I’m always on the prowl for, it’s fresh tunes to tickle my eardrums. I’m practically the Bandcamp connoisseur, the aficionado of audio, if you will. My collection’s like a treasure trove of musical gems, all handpicked from the vast Bandcamp galaxy. But there’s a twist to this tale – my wishlist isn’t just a shopping list; it’s a roadmap to musical exploration.

Picture this: thousands of albums languishing in my wishlist, each one a potential masterpiece. It’s a labyrinth of sound, and in my quest for auditory bliss, I’ve encountered a conundrum. How do I keep track of what I’ve listened to and what piqued my interest? You see, I’m not just a ‘skip and forget’ kind of listener. Even if an album doesn’t quite hit the mark, it lingers on my wishlist, a musical enigma begging to be solved. But here’s the kicker – without notes, it’s like trying to recall the lyrics of a song from a dream. Frustrating, right?

So, in the spirit of ‘necessity is the mother of invention,’ I decided to craft a digital companion to my Bandcamp odyssey. Behold, Camp Counselor! This nifty tool syncs your Bandcamp wishlist and purchase history into a cozy database, giving you the power to annotate, star, and sort your albums with reckless abandon.

Now, I know what you’re thinking: “Why not just leave a public review on Bandcamp?” Well, my friend, sometimes the musical journey is a private affair, a secret rendezvous between you and your headphones. That’s where Camp Counselor shines. It’s your personal backstage pass to the music world. You can leave your thoughts, ratings, and quirks in blissful secrecy.

Now, here’s the cherry on top – I wanted to break the mold, to venture into new territories. This wasn’t just about crafting a web app; it was about exploring uncharted sonic realms. So, Camp Counselor is tailored for my trusty Pinephone, my loyal companion on my musical journeys. I built it with Vala and Gtk4 for Linux, complete with a slick and responsive design. As for Mac and Windows users, well, I haven’t given it a whirl, and truth be told, I’m dancing to a different beat.

But wait, there’s more! In the future, I might just sprinkle some more magic into Camp Counselor:

  • Imagine the sheer delight of previewing albums right within the app, eliminating the need to hop between tabs.
  • Ever wish you could magically add new albums from artists you adore to your wishlist? Well, I’m conjuring up just that.
  • Who knows? Camp Counselor might just evolve to embrace the vast world of musical data beyond Bandcamp.

So there you have it, folks – Camp Counselor, your trusty sidekick in the realm of Bandcamp. It’s time to dive into the sonic sea, navigate the waves of melodies, and rediscover your musical journey, one note at a time. Happy listening! 🎵🎉

Converting Bandcamp Email Updates to an RSS Feed

I love music. I like to closely track bands and labels to learn about upcoming releases. Unfortunately, the type of music I listen to tends to stay more underground, so there isn’t any mainstream coverage of it, which just means I get to do all the work myself.

Fortunately, a majority of the bands and labels I am interested in are on bandcamp. Bandcamp makes it pretty easy to subscribe to artists and labels and get updates from them directly. Unfortunately, this all comes through email. While it is easy enough to filter to avoid cluttering my inbox, email isn’t my desired place for this information.

I run a private freshrss instance and curate several hundred RSS feeds. It syncs nicely between my various devices, and is just a great way to consume. My goal is to get all my music updates from bandcamp here, so that’d they be in the same place as where I get music reviews and follow some other underground music blogs.

Bandcamp doesn’t provide any rss feeds. There are some options, like using RSSHub and its bandcamp addon. However, many of the updates that come through email are private, and don’t show up in an artists’ or labels’ feed.

I decided, I’d go a different route, and just convert the emails I was getting into an RSS feed directly.

Step 1 – imapfilter

I already use imapfilter heavily for filtering and tagging my emails. I decided I could use it to pipe the bandcamp emails to a custom script that would convert it into an rss entry. Here is the relevant section:

function filter_bandcamp(messages)
   -- Grab anything from Bandcamp where the subject
   --  starts with "New". This indicates a message
   --  or updated rather than a receipt.
   -- I am taking these "mailing list" bandcamps
   --  messages and converting them to an rss feed.
   results = messages:contain_from('noreply@bandcamp.com') *
             messages:contain_subject('New')

   for _, mesg in ipairs(results) do
      mbox, uid = table.unpack(mesg)
      text = mbox[uid]:fetch_message()
      pipe_to('/opt/email2rss/email2rss', text)
   end

   -- delete the bandcamp messages
   results:delete_messages()

   return messages;
end

You’ll notice I don’t want all messages from “noreply@bandcamp.com”, since that would also include things like purchases and system notifications.

Step 2 – email2rss script

The email2rss script is a python program. I am using feedgen to generate the feed, and built in python libraries to parse the emails.

One issue you’ll notice immediately, is that this script runs once for every email message. For RSS, we want to create a continuous feed with all the entries. This means we have to insert the new entry into an existing file and have some persistence. The quickest/dirtiest method was to use python’s builtin pickle to serialize and deserialize the whole state. That way, I can quickly load the previous state, create a new entry, write out the rss file, then serialize and save the state to disk.

Here is the program in its entirety:

#!/usr/bin/env python3
#

import sys
import email.parser
import datetime
import pickle
import re
import os
from feedgen.feed import FeedGenerator

DATADIR=os.path.join('/', 'opt', 'email2rss', 'data')

def default_feed():
  fg = FeedGenerator()
  fg.id('https://feeds.line72.net/')
  fg.title('Bandcamp Updates')
  fg.description('Bandcamp Updates')
  fg.link(href = 'https://feeds.line72.net')
  fg.language('en')

  return fg

def get_feedgenerator():
  try:
    with open(os.path.join(DATADIR, 'feed.obj'), 'rb') as f:
      return pickle.load(f)
  except IOError:
    return default_feed()

def save_feedgenerator(fg):
  with open(os.path.join(DATADIR, 'feed.obj'), 'wb') as f:
    pickle.dump(fg, f)

def add_item(fg, msg, content):
  msg_id_header = msg.get('Message-ID')
  msg_id = re.match(r'^\<(.*?)@.*$', msg_id_header).group(1)
  sender = msg.get('From')
  subject = msg.get('Subject')

  fe = fg.add_entry()
  fe.id(f'https://feeds.line72.net/{msg_id}')
  fe.title(subject)
  fe.author(name = 'Bandcamp', email = 'noreply@bandcamp.com')
  fe.pubDate(datetime.datetime.utcnow().astimezone())
  fe.description(subject)
  fe.content(content, type = 'CDATA')

def go():
  fg = get_feedgenerator()

  parser = email.parser.Parser()
  msg = parser.parse(sys.stdin)
  for part in msg.walk():
    if part.get_content_type() == 'text/html':
      add_item(fg, msg, part.get_payload())
      break

  fg.rss_file(os.path.join(DATADIR, 'feed.rss'), pretty = True)

  save_feedgenerator(fg)

if __name__ == '__main__':
  go()

Step 3 – Host the Feed

I just have a simple nginx server running that hosts the feed.rss. Then I simply add this new feed to my freshrss instance.

Future Work

There are still some improvements that could be made to this. The history is going to grow out of control at some point, so I really should probably go through and delete old entries, maybe to keep a history of 100 or 500 entries.

The other possible issue (I haven’t run into it yet), is that the email2rss could be run simultaneously. If that is the case, then one entry will likely be lost. I really should probably have a lock around the feed.obj to keep a second instance from doing anything until the first was written out the new state.