Mirror of github.com/ewanc26/tourmaline
  • TypeScript 63.5%
  • Svelte 35.7%
  • CSS 0.5%
  • HTML 0.2%
  • JavaScript 0.1%
Find a file
2026-07-16 18:22:46 +01:00
src style: polish layout, loading states, and personality card 2026-05-11 11:43:55 +01:00
static feat: add hero SVG as OG image 2026-05-10 03:37:13 +01:00
.env.example feat: initial project — Teal.fm scrobble analyser 2026-05-09 22:38:39 +01:00
.gitignore fix: de-emphasise AT Protocol, lead with Teal.fm 2026-05-10 03:14:38 +01:00
.npmrc feat: initial project — Teal.fm scrobble analyser 2026-05-09 22:38:39 +01:00
AGENTS.md docs: correct agent guidance after source audit 2026-07-16 18:22:46 +01:00
LICENSE feat: initial project — Teal.fm scrobble analyser 2026-05-09 22:38:39 +01:00
package.json chore: bump version to 0.5.0 2026-05-11 11:00:54 +01:00
pnpm-lock.yaml refactor: move all compute to server-side 2026-05-11 10:37:15 +01:00
pnpm-workspace.yaml feat: initial project — Teal.fm scrobble analyser 2026-05-09 22:38:39 +01:00
README.md docs: add archive notice pointing to pkgs monorepo 2026-05-11 13:03:18 +01:00
svelte.config.js perf: batched enrichment updates, tree-shaken chart.js, compressed cache, adapter-vercel 2026-05-11 09:17:03 +01:00
tsconfig.json feat: initial project — Teal.fm scrobble analyser 2026-05-09 22:38:39 +01:00
vite.config.ts refactor: move all compute to server-side 2026-05-11 10:37:15 +01:00

Tourmaline

This repository is archived. Tourmaline has moved to the pkgs monorepo.

AT Protocol scrobble analyser. Ingests Teal.fm scrobbles from any handle or DID, cross-references with free music APIs, and builds a listener profile.


How it works

  1. Enter a handle (e.g. ewancroft.uk) or DID (did:plc:... or did:web:...)
  2. Resolves identity via Slingshot (handles → DIDs), then fetches the DID document for the PDS URL
  3. Fetches fm.teal.alpha.feed.play records from the user's PDS
  4. Aggregates play counts, timelines, and listening patterns
  5. Enriches artist data with MusicBrainz (genres), Last.fm (tags, similar artists), and Deezer (art, genres)
  6. Builds a listener profile: genre map, mood profile, diversity score, obscurity index, era preference, timeline heatmap

Setup

pnpm install

Copy .env.example to .env and add your Last.fm API key (optional):

cp .env.example .env

Get a Last.fm API key at https://www.last.fm/api/account/create.


Development

pnpm dev

Project structure

src/
├── lib/
│   ├── atproto/
│   │   └── resolve.ts        # Handle/DID resolution via Slingshot + scrobble fetching
│   ├── enrich/
│   │   ├── cache.ts           # SQLite cache layer
│   │   ├── musicbrainz.ts     # Genre + MBID enrichment
│   │   ├── lastfm.ts          # Tag + similar artist enrichment
│   │   └── deezer.ts           # Image + genre fallback
│   ├── analysis/
│   │   ├── aggregator.ts      # Raw scrobble aggregation
│   │   ├── genres.ts          # Genre classification
│   │   ├── timeline.ts        # Time-of-day analysis
│   │   ├── era.ts             # Release decade distribution
│   │   ├── diversity.ts       # Shannon entropy + Gini coefficient
│   │   ├── obscurity.ts       # Mainstream vs niche scoring
│   │   └── mood.ts            # Mood extraction from tags
│   └── types.ts
├── routes/
│   ├── +page.svelte           # Handle/DID input form
│   └── profile/[did]/
│       ├── +page.server.ts    # Fetch + analyse + render
│       ├── +page.svelte       # Profile display
│       ├── GenreChart.svelte
│       ├── TimelineHeatmap.svelte
│       ├── MoodRadar.svelte
│       └── EraBarChart.svelte
└── app.html

APIs used

API Purpose Auth Rate limit
Slingshot Handle → DID resolution None None
MusicBrainz Genres, MBIDs, release dates User-Agent 1 req/sec
Last.fm Tags, similar artists, listener counts API key Undocumented
Deezer Artist images, genre fallback None Undocumented

All responses cached in .cache/tourmaline.db (SQLite). Cache TTL: 30 days.


Listener profile

  • Genre map — weighted by play count, from MusicBrainz + Last.fm tags
  • Mood profile — radar chart from tag keywords (Energetic, Melancholic, Chill, etc.)
  • Diversity score — Shannon entropy normalised to 0100
  • Obscurity index — log-scaled Last.fm listener counts, 0 (mainstream) to 100 (deep cuts)
  • Era preference — decade distribution from MusicBrainz release dates
  • Timeline heatmap — hour × day listening patterns from scrobble timestamps

License: AGPL-3.0-only