Syncing Bandcamp purchases to Android music library
![Syncing Bandcamp purchases to Android music library](/content/images/size/w1200/2025/01/photo-1605547446826-efe6b45ffe4d.jpeg)
Technical nerd post ahead!
Bandcamp would have a larger effect on the music industry if they built music library synchronization into their mobile app. Unfortunately their mobile app is mostly just a storefront.
I have over 450 Bandcamp albums, and I need to get them onto my Android device. How does one do this?
I knew that Bandcamp exposed public APIs to get purchase data. I also run Termux on Android, so I know that I could get a Linux-based solution to work.
There is an existing Python app called bandcamp-downloader which is fantastic. However, I could not get bandcamp-downloader to build in Termux because it uses a patched version ofcurl
that I could not get to compile.
If you're going to run this process on a Mac or PC or Linux, I'd recommend using the Python app.
Rewrite
I re-wrote bandcamp-downloader my way, using Typescript, to get around the patched curl
dependency. It was an extreme measure to take, but it was the easiest option to get this type of process to run on Android in Termux.
Source code: https://github.com/kindohm/bc-scraper
I wrapped my app's usage in a simple bash script, so now I can just run ./bc
in my phone's terminal to sync my entire music library to Android.
![](https://www.kindohm.com/content/images/2025/01/Screenshot_20250107_113936_Termux-1.jpg)
OK nerds, here we go.
The app's process is pretty straightforward:
- read bandcamp.com auth cookies from a cookies.txt file (more on that below)
- call the public bandcamp API to get my purchases
- visit each purchase HTML page and scrape out download data
- download each album .zip file
Additionally, may app also extracts each .zip file to a target destination folder.
Bandcamp auth cookies
Bandcamp has no OAuth handshake to call their API, so you have to log in manually and export the bandcamp.com cookies, then use those cookies in your app code.
I use the cookies.txt browser extension in Firefox to export my cookies to a cookies.txt
file. You can find similar extensions for Chrome and other browsers.
Retrieving your purchases
Bandcamp's public API has a couple of public endpoints that return information about your purchases:
- https://bandcamp.com/api/fan/2/collection_summary
- returns your
fan_id
- returns mapping information about your collection and purchases, needed for the download step
- returns your
- https://bandcamp.com/api/fancollection/1/collection_items
- this endpoint requires a few params in the POST body:
fan_id
limit
- the number of items to retrieveolder_than_token
- a string that indicates how far back in time to query for items
- this endpoint requires a few params in the POST body:
The older_than_token
The older_than_token
appears to require this form:
${unix_timestamp of date to query from}:${id of newest item}:${item_type}::
Yes, those two trailing colons are valid.
My function to generate this token string looks like this:
export const getOlderThanToken = (summary: CollectionSummary) => {
const albumKeys = Object.keys(summary.collection_summary.tralbum_lookup);
const firstKey = albumKeys[0];
const { item_type, item_id } =
summary.collection_summary.tralbum_lookup[firstKey];
const now = new Date();
const olderThanToken = `${now.getTime()}:${item_id}:${item_type}::`;
return olderThanToken;
};
// the CollectionSummary types look like this:
type CollectionSummaryItem = {
item_type: string;
item_id: number;
band_id: number;
purchased: string;
};
export type CollectionSummary = {
fan_id: number;
collection_summary: {
tralbum_lookup: Record<string, CollectionSummaryItem>;
};
};
Actually downloading a .zip file
To actually download a collection item, you must navigate to it's "redownload page" and extract a JSON data blob from a <div>
element.
That data blob contains the download data with the URL of the .zip file.
const redownloadUrl =
items.redownload_urls[`${item.sale_item_type}${item.sale_item_id}`];
const downloadDocResponse = await axios.get(redownloadUrl, {
headers: { Cookie: cookies },
});
const doc = new JSDOM(downloadDocResponse.data);
const pageDataDiv = doc.window.document.querySelector("#pagedata");
const dataBlob = pageDataDiv?.attributes.getNamedItem("data-blob")?.value;
if (!dataBlob) {
console.warn("no data blob");
return;
}
const data = JSON.parse(dataBlob);
const format = "mp3-320"; // hard-coded to always choose mp3s
if (!data.download_items[0].downloads) {
console.warn("no downloads", item.band_name, item.item_title);
continue;
}
const url = data.download_items[0]?.downloads[format]?.url;
Usage
node ./dist/index.js \
--cookies [path to bandcamp cookies file] \
--queryLimit [how many items to retrieve from your collection] \
--days [how far back to look in your collection for purchases] \
--downloadDir [where to download your collection's .zip files] \
--extractDir [where to extract the .zip files] \
--redownload [true|false, redownload .zip files already on disk] \
--reextract [true|false, unzip .zip files if destination already exists] \
Conclusion
I don't recommend anybody re-write an app like this is one already exists! If I could have gotten that weird patched version of curl
to compile in Termux, I would have used the Python app instead of doing a re-write.
Even though this app was written with Termux in mind, it runs anywhere that NodeJS will run! So you can use this app on Mac, PC, and a Linux desktop too.
Enjoy!