{"id":15673537,"url":"https://github.com/lmammino/scrobbles","last_synced_at":"2025-05-07T00:42:08.277Z","repository":{"id":138311255,"uuid":"353141455","full_name":"lmammino/scrobbles","owner":"lmammino","description":"A Node.js library to fetch last.fm \"scrobbled\" songs for a given user","archived":false,"fork":false,"pushed_at":"2022-03-13T19:30:23.000Z","size":570,"stargazers_count":13,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-04-23T02:49:22.678Z","etag":null,"topics":["javascript","last-fm","lastfm","lastfm-api","music","nodejs-modules","scrobbles"],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/lmammino.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2021-03-30T21:06:09.000Z","updated_at":"2024-06-01T14:27:51.000Z","dependencies_parsed_at":null,"dependency_job_id":"ea323e9f-eaa4-4306-80e4-5f54a82e9d93","html_url":"https://github.com/lmammino/scrobbles","commit_stats":{"total_commits":20,"total_committers":2,"mean_commits":10.0,"dds":0.35,"last_synced_commit":"2520fbd680281fb8db5474176deaab92869e7bd5"},"previous_names":[],"tags_count":5,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lmammino%2Fscrobbles","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lmammino%2Fscrobbles/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lmammino%2Fscrobbles/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lmammino%2Fscrobbles/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/lmammino","download_url":"https://codeload.github.com/lmammino/scrobbles/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":252793546,"owners_count":21805053,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["javascript","last-fm","lastfm","lastfm-api","music","nodejs-modules","scrobbles"],"created_at":"2024-10-03T15:41:10.359Z","updated_at":"2025-05-07T00:42:08.228Z","avatar_url":"https://github.com/lmammino.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Scrobbles\n\n[![npm version](https://img.shields.io/npm/v/scrobbles)](https://npm.im/scrobbles)\n[![Node.js CI](https://github.com/lmammino/scrobbles/actions/workflows/node.js.yml/badge.svg)](https://github.com/lmammino/scrobbles/actions/workflows/node.js.yml)\n[![codecov](https://codecov.io/gh/lmammino/scrobbles/branch/main/graph/badge.svg?token=om2BOabUg1)](https://codecov.io/gh/lmammino/scrobbles)\n[![JavaScript Style Guide](https://img.shields.io/badge/code_style-standard-brightgreen.svg)](https://standardjs.com)\n\n\nA Node.js library to fetch [last.fm](https://www.last.fm) \"scrobbled\" for a given user.\n\n[\"Scrobble\"](https://www.quora.com/How-did-Last-fm-come-with-the-term-scrobble) is a last.fm term to indicate a songs that a user has listened to at a given moment in time. It is effectively a record of a person listening to a specific song!\n\nThis library can be useful to explore the entire muscial history of a given user. If you need a few possible use cases, there you go:\n\n  - Do some musical analytics. What the most listened song among your friends?\n  - Backup your entire music listening history somewhere \"safe\" (for some definition of \"safe\").\n  - Search for that one song that you can't fully remember (maybe you just remember the artist or some keyword in the title)!\n\n\n## Installation\n\nAs with any other Node.js package, this is as easy as:\n\n```bash\nnpm install --save scrobbles\n```\n\n## Example\n\n**Note**: In order to use this library you will need to [get an API key from Last.fm](https://www.last.fm/api/account/create).\n\nPrint all the musical listening history (from latest to oldest) of the user `loige`:\n\n```javascript\nimport { RecentTracks } from 'scrobbles'\n\nasync function getAllPages () {\n  const reader = new RecentTracks({\n    apikey: process.env.API_KEY,\n    user: 'loige'\n  })\n\n  reader.on('retry', ({ error, message, retryNum, retryAfterMs, url }) =\u003e {\n    console.error(`Failure (${retryNum}) ${url}: ${message}. Retrying in ${retryAfterMs}`)\n  })\n\n  reader.on('progress', console.log)\n\n  for await (const page of reader) {\n    for (const song of page) {\n      console.log(song)\n    }\n  }\n}\n\ngetAllPages().catch((err) =\u003e {\n  console.error(err)\n  process.exit(1)\n})\n\n```\n\nMore examples available in the [`examples` folder](/examples).\n\n\n## API \u0026 Configuration\n\nThe library exposes the `RecentTracks` reader class. This class is both an **event emitter** and an **Async Iterator**.\n\n### Initialization options\n\nYou can instantiate a new reader you can use the `RecentTracks` constructor which accepts a configuration object:\n\n```javascript\nconst reader = new RecentTracks({\n  apikey,           // mandatory - the last.fm API key\n  user,             // mandatory - the name of the last.fm user\n  limit,            // optional - the number of songs to fetch per page. Between 1 and 200. Default: 50\n  from,             // optional - a unix timestamp that indicates the earliest point in time to include \n                    //            in the list of results. Default: unbound (earliest song ever scrobbled for that user)\n  to,               // optional - a unix timestamp that indicates the latest point in time to include in \n                    //            the list of results. Default: unbound (essentially \"now\")\n  extended,         // optional - a boolean indicating wheter every record should contain extended information \n                    //            (e.g. cover pictures) or not. Default: false\n  mapTrack,         // optional - a function to remap the raw response from last.fm. See next sections for more details \n  maxRetries,       // optional - the number of retries in case of failure. Default: 5\n  retryDelayBaseMs, // optional - the base delay in milliseconds. Default: 100\n  retryBase,        // optional - the exponent to calculate the delay before the next retry: Default: 2\n})\n```\n\n### Retry logic\n\nLast.fm APIs will sometime throw random errors, so this library provides a built in retry mechanism that can be configured with the options mentioned above.\n\nThe wait time before a consecutive retry is calculated with the formula `retryBase ** retryNum * retryDelayBaseMs` (exponential fallback).\n\nIf the number of consecutive max retries is reached then an error is thrown.\n\nIf a successful response is received after a retry, the retry counter is reset.\n\n\n### Custom track mapping\n\nBy default, iterating over a reader will give you a list of pages. Every page contains a chunk of \"scrobbled\" tracks. Every track will contain the following fields:\n\n```javascript\n{\n  \"album\": \"The Music That Died Alone\",\n  \"artist\": \"The Tangent\",\n  \"date\": 1618997643,\n  \"name\": \"Up-hill From Here\",\n  \"url\": \"https://www.last.fm/music/The+Tangent/_/Up-hill+From+Here\",\n}\n```\n\nIn reality, last.fm APIs will contain a lot more information (especially if setting the `extended` option to `true`). If you want to provide your own custom mapping you can do that by passing a custom mapping function using the `mapTrack` parameter of the constructor configuration object.\n\nFor example:\n\n```javascript\nconst reader = new RecentTracks({\n  apiKey: 'mysupersecretapikey',\n  user: 'mariobros',\n  mapTrack: (rawTrack) =\u003e ({\n    date: Number(rawTrack.date.uts),\n    artist: rawTrack.artist['#text'],\n    name: rawTrack.name,\n    album: rawTrack.album['#text'],\n    url: rawTrack.url,\n    cover: rawTrack.image.find((i) =\u003e i.size === 'extralarge')['#text'],\n  }),\n}\n```\n\nTo learn more about all the fields exposed by last.fm, check out the official documentation for the [`user.getRecentTracks` API](https://www.last.fm/api/show/user.getRecentTracks).\n\n\n### Events\n\nA `RecentTracks` instance emits the following events:\n\n#### `progress`\n\nEmitted for every successful api call when a page is fetched. It gives indications about the current progress.\n\n```javascript\nreader.on('progress', e =\u003e console.log(e))\n```\n\nWill print something like this:\n\n```javascript\n{\n  \"perPage\": 4, // how many elements per page\n  \"remainingPages\": 2, // how many pages are left to fetch\n  \"progress\": 0.3333333333333333, // the allover progress in percentage\n}\n```\n\n#### `retry`\n\nIn case of error, when a retry is about to happen.\n\n```javascript\nreader.on('retry', e =\u003e console.log(e))\n```\n\nWill print something like this:\n\n```javascript\n{\n  \"error\": 1, // error code\n  \"maxRetries\": 6, // the number of max retries configured\n  \"message\": \"Unexpected Server Error\", // the error message\n  \"retryAfterMs\": 1600, // when the next retry is going to happen\n  \"retryNum\": 4, // the number of consecutive retries so far\n  \"url\": \"https://ws.audioscrobbler.com/2.0/?api_key=atestapikey\u0026user=loige\u0026limit=4\u0026extended=0\u0026method=user.getrecenttracks\u0026format=json\", // the URL of the request that failed\n}\n```\n\n\n## CLI usage\n\n`scrobbles` ships also with a convenient CLI that you can use to easily export data from a given Last.fm account.\n\nYou can install the CLI helper globally in your system with:\n\n```bash\nnpm i -g scrobbles\n```\n\nThen it will be available as an executable with the name of `scrobbles.\n\nAlternatively you can install and invoke the executably dynamically through `npx` by just running:\n\n```bash\nnpx scrobbles\n```\n\nHere's an example on how to use `scrobbles` (replace `npx scrobbles` with `scrobbles` if you already installed it globally):\n\n```bash\nSCROBBLES_APIKEY=xxx npx scrobbles -u loige -f 1998-01-01 -t 2022-03-14 -F csv \u003e lastfm_export.csv\n```\n\nThe above example exports all the tracks from 1998-01-01 to 2022-03-14 for user loige in CSV format\n\nYou can use the following command to see all the supported options:\n\n```bash\nnpx scrobbles --help\n```\n\n\n## Contributing\n\nEveryone is very welcome to contribute to this project.\nYou can contribute just by submitting bugs or suggesting improvements by\n[opening an issue on GitHub](https://github.com/lmammino/scrobbles/issues).\n\n## License\n\nLicensed under [MIT License](LICENSE). © Luciano Mammino.\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flmammino%2Fscrobbles","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Flmammino%2Fscrobbles","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flmammino%2Fscrobbles/lists"}