Skip to content

Instantly share code, notes, and snippets.

@faisalnjs
Last active February 14, 2026 02:31
Show Gist options
  • Select an option

  • Save faisalnjs/1c7cbabb9d22f077ed2199d78a6e8681 to your computer and use it in GitHub Desktop.

Select an option

Save faisalnjs/1c7cbabb9d22f077ed2199d78a6e8681 to your computer and use it in GitHub Desktop.
Fetch your Last.fm obsessions as JSON, as there is no way to do this in the official API. Use as a CommonJS module, ES module, or as a JavaScript function.
"use strict";
const { JSDOM } = require('jsdom');
async function obsessions(username = null, existingObsessions = []) {
if (!username) {
console.error('[faisalnjs/obsessions] Last.fm username is required.');
return [];
};
try {
const obsessionsHTML = await fetch(`https://www.last.fm/user/${username}/obsessions`);
const obsessionsText = await obsessionsHTML.text();
const obsessionsDOM = new JSDOM(obsessionsText);
const obsessions = [...obsessionsDOM.window.document.querySelectorAll('.obsession-history-item')].sort((a, b) =>
new Date(b.querySelector('.obsession-history-item-date a')?.textContent.trim()) -
new Date(a.querySelector('.obsession-history-item-date a')?.textContent.trim())
);
const obsessionsArray = Array.from(obsessions);
if (!obsessionsArray) return [];
const totalObsessions = Array.from(obsessionsArray).length;
if (!totalObsessions) return [];
var obsessionsJSON = obsessionsArray.map(obsessionItem => {
return {
id: Number(obsessionItem.querySelector('.obsession-history-item-heading-link')?.getAttribute('href')?.trim().split('/').slice(-1)[0]) || 0,
track: obsessionItem.querySelector('.obsession-history-item-heading')?.textContent.trim(),
artist: obsessionItem.querySelector('.obsession-history-item-artist a')?.textContent.trim(),
artistURL: obsessionItem.querySelector('.obsession-history-item-artist a')?.getAttribute('href')?.trim(),
date: new Date(obsessionItem.querySelector('.obsession-history-item-date')?.textContent.trim()),
url: `https://www.last.fm${obsessionItem.querySelector('.obsession-history-item-heading-link')?.getAttribute('href')?.trim()}`,
background: obsessionItem.querySelector('.obsession-history-item-background')?.getAttribute('style')?.split('url(')[1].split(')')[0].trim(),
first: obsessionItem.querySelector('.obsession-first') ? true : false,
playlink: obsessionItem.querySelector('.obsession-history-item-playlink') ? {
track: obsessionItem.querySelector('.obsession-history-item-playlink')?.getAttribute('data-track-name').trim(),
trackURL: obsessionItem.querySelector('.obsession-history-item-playlink')?.getAttribute('data-track-url').trim(),
artist: obsessionItem.querySelector('.obsession-history-item-playlink')?.getAttribute('data-artist-name').trim(),
artistURL: obsessionItem.querySelector('.obsession-history-item-playlink')?.getAttribute('data-artist-url').trim(),
title: obsessionItem.querySelector('.obsession-history-item-playlink')?.title.trim(),
label: obsessionItem.querySelector('.obsession-history-item-playlink')?.getAttribute('data-analytics-label').trim(),
url: obsessionItem.querySelector('.obsession-history-item-playlink')?.getAttribute('href')?.trim(),
} : null,
};
}).sort((a, b) => b.date - a.date);
var obsessionsPages = Number(obsessionsDOM.window.document.querySelectorAll('.pagination-page [data-pagination-link]')[obsessionsDOM.window.document.querySelectorAll('.pagination-page [data-pagination-link]').length - 1].textContent) || 1;
if (existingObsessions && existingObsessions.length && existingObsessions.find(obsession => obsession.id === obsessionsJSON[0].id)) return existingObsessions;
for (var page = 2; page <= obsessionsPages; page++) {
const pageHTML = await fetch(`https://www.last.fm/user/${username}/obsessions?page=${page}`);
const pageText = await pageHTML.text();
const pageDOM = new JSDOM(pageText);
const pageObsessions = pageDOM.window.document.querySelectorAll('.obsession-history-item');
const pageObsessionsArray = Array.from(pageObsessions).sort((a, b) =>
new Date(b.querySelector('.obsession-history-item-date a')?.textContent.trim()) -
new Date(a.querySelector('.obsession-history-item-date a')?.textContent.trim())
);
var pageObsessionsJSON = pageObsessionsArray.map(obsessionItem => {
return {
id: Number(obsessionItem.querySelector('.obsession-history-item-heading-link')?.getAttribute('href')?.trim().split('/').slice(-1)[0]) || 0,
track: obsessionItem.querySelector('.obsession-history-item-heading')?.textContent.trim(),
artist: obsessionItem.querySelector('.obsession-history-item-artist a')?.textContent.trim(),
artistURL: obsessionItem.querySelector('.obsession-history-item-artist a')?.getAttribute('href')?.trim(),
date: new Date(obsessionItem.querySelector('.obsession-history-item-date')?.textContent.trim()),
url: `https://www.last.fm${obsessionItem.querySelector('.obsession-history-item-heading-link')?.getAttribute('href')?.trim()}`,
background: obsessionItem.querySelector('.obsession-history-item-background')?.getAttribute('style')?.split('url(')[1].split(')')[0].trim(),
first: obsessionItem.querySelector('.obsession-first') ? true : false,
playlink: obsessionItem.querySelector('.obsession-history-item-playlink') ? {
track: obsessionItem.querySelector('.obsession-history-item-playlink')?.getAttribute('data-track-name').trim(),
trackURL: obsessionItem.querySelector('.obsession-history-item-playlink')?.getAttribute('data-track-url').trim(),
artist: obsessionItem.querySelector('.obsession-history-item-playlink')?.getAttribute('data-artist-name').trim(),
artistURL: obsessionItem.querySelector('.obsession-history-item-playlink')?.getAttribute('data-artist-url').trim(),
title: obsessionItem.querySelector('.obsession-history-item-playlink')?.title.trim(),
label: obsessionItem.querySelector('.obsession-history-item-playlink')?.getAttribute('data-analytics-label').trim(),
url: obsessionItem.querySelector('.obsession-history-item-playlink')?.getAttribute('href')?.trim(),
} : null,
};
}).sort((a, b) => b.date - a.date);
obsessionsJSON = [...obsessionsJSON, ...pageObsessionsJSON];
};
return obsessionsJSON.sort((a, b) => b.date - a.date);
} catch (error) {
console.error('[faisalnjs/obsessions] Error:', error);
};
};
module.exports = { obsessions };
"use strict";
import { JSDOM } from 'jsdom';
async function obsessions(username = null, existingObsessions = []) {
if (!username) {
console.error('[faisalnjs/obsessions] Last.fm username is required.');
return [];
};
try {
const obsessionsHTML = await fetch(`https://www.last.fm/user/${username}/obsessions`);
const obsessionsText = await obsessionsHTML.text();
const obsessionsDOM = new JSDOM(obsessionsText);
const obsessions = [...obsessionsDOM.window.document.querySelectorAll('.obsession-history-item')].sort((a, b) =>
new Date(b.querySelector('.obsession-history-item-date a')?.textContent.trim()) -
new Date(a.querySelector('.obsession-history-item-date a')?.textContent.trim())
);
const obsessionsArray = Array.from(obsessions);
if (!obsessionsArray) return [];
const totalObsessions = Array.from(obsessionsArray).length;
if (!totalObsessions) return [];
var obsessionsJSON = obsessionsArray.map(obsessionItem => {
return {
id: Number(obsessionItem.querySelector('.obsession-history-item-heading-link')?.getAttribute('href')?.trim().split('/').slice(-1)[0]) || 0,
track: obsessionItem.querySelector('.obsession-history-item-heading')?.textContent.trim(),
artist: obsessionItem.querySelector('.obsession-history-item-artist a')?.textContent.trim(),
artistURL: obsessionItem.querySelector('.obsession-history-item-artist a')?.getAttribute('href')?.trim(),
date: new Date(obsessionItem.querySelector('.obsession-history-item-date')?.textContent.trim()),
url: `https://www.last.fm${obsessionItem.querySelector('.obsession-history-item-heading-link')?.getAttribute('href')?.trim()}`,
background: obsessionItem.querySelector('.obsession-history-item-background')?.getAttribute('style')?.split('url(')[1].split(')')[0].trim(),
first: obsessionItem.querySelector('.obsession-first') ? true : false,
playlink: obsessionItem.querySelector('.obsession-history-item-playlink') ? {
track: obsessionItem.querySelector('.obsession-history-item-playlink')?.getAttribute('data-track-name').trim(),
trackURL: obsessionItem.querySelector('.obsession-history-item-playlink')?.getAttribute('data-track-url').trim(),
artist: obsessionItem.querySelector('.obsession-history-item-playlink')?.getAttribute('data-artist-name').trim(),
artistURL: obsessionItem.querySelector('.obsession-history-item-playlink')?.getAttribute('data-artist-url').trim(),
title: obsessionItem.querySelector('.obsession-history-item-playlink')?.title.trim(),
label: obsessionItem.querySelector('.obsession-history-item-playlink')?.getAttribute('data-analytics-label').trim(),
url: obsessionItem.querySelector('.obsession-history-item-playlink')?.getAttribute('href')?.trim(),
} : null,
};
}).sort((a, b) => b.date - a.date);
var obsessionsPages = Number(obsessionsDOM.window.document.querySelectorAll('.pagination-page [data-pagination-link]')[obsessionsDOM.window.document.querySelectorAll('.pagination-page [data-pagination-link]').length - 1].textContent) || 1;
if (existingObsessions && existingObsessions.length && existingObsessions.find(obsession => obsession.id === obsessionsJSON[0].id)) return existingObsessions;
for (var page = 2; page <= obsessionsPages; page++) {
const pageHTML = await fetch(`https://www.last.fm/user/${username}/obsessions?page=${page}`);
const pageText = await pageHTML.text();
const pageDOM = new JSDOM(pageText);
const pageObsessions = pageDOM.window.document.querySelectorAll('.obsession-history-item');
const pageObsessionsArray = Array.from(pageObsessions).sort((a, b) =>
new Date(b.querySelector('.obsession-history-item-date a')?.textContent.trim()) -
new Date(a.querySelector('.obsession-history-item-date a')?.textContent.trim())
);
var pageObsessionsJSON = pageObsessionsArray.map(obsessionItem => {
return {
id: Number(obsessionItem.querySelector('.obsession-history-item-heading-link')?.getAttribute('href')?.trim().split('/').slice(-1)[0]) || 0,
track: obsessionItem.querySelector('.obsession-history-item-heading')?.textContent.trim(),
artist: obsessionItem.querySelector('.obsession-history-item-artist a')?.textContent.trim(),
artistURL: obsessionItem.querySelector('.obsession-history-item-artist a')?.getAttribute('href')?.trim(),
date: new Date(obsessionItem.querySelector('.obsession-history-item-date')?.textContent.trim()),
url: `https://www.last.fm${obsessionItem.querySelector('.obsession-history-item-heading-link')?.getAttribute('href')?.trim()}`,
background: obsessionItem.querySelector('.obsession-history-item-background')?.getAttribute('style')?.split('url(')[1].split(')')[0].trim(),
first: obsessionItem.querySelector('.obsession-first') ? true : false,
playlink: obsessionItem.querySelector('.obsession-history-item-playlink') ? {
track: obsessionItem.querySelector('.obsession-history-item-playlink')?.getAttribute('data-track-name').trim(),
trackURL: obsessionItem.querySelector('.obsession-history-item-playlink')?.getAttribute('data-track-url').trim(),
artist: obsessionItem.querySelector('.obsession-history-item-playlink')?.getAttribute('data-artist-name').trim(),
artistURL: obsessionItem.querySelector('.obsession-history-item-playlink')?.getAttribute('data-artist-url').trim(),
title: obsessionItem.querySelector('.obsession-history-item-playlink')?.title.trim(),
label: obsessionItem.querySelector('.obsession-history-item-playlink')?.getAttribute('data-analytics-label').trim(),
url: obsessionItem.querySelector('.obsession-history-item-playlink')?.getAttribute('href')?.trim(),
} : null,
};
}).sort((a, b) => b.date - a.date);
obsessionsJSON = [...obsessionsJSON, ...pageObsessionsJSON];
};
return obsessionsJSON.sort((a, b) => b.date - a.date);
} catch (error) {
console.error('[faisalnjs/obsessions] Error:', error);
};
};
export { obsessions };
const { obsessions } = require('./obsessions.mjs');
async function test() {
console.log(await obsessions('faisaln', [])); // Replace with your Last.fm username and an existing obsessions(...) array, if any.
};
test();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment