@ansango/lastfm-api 3.3.0 → 3.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +25 -0
- package/README.md +62 -3
- package/dist/client.d.ts +2 -1
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +3 -1
- package/dist/client.js.map +1 -1
- package/dist/entrypoints/insights.d.ts +4 -0
- package/dist/entrypoints/insights.d.ts.map +1 -0
- package/dist/entrypoints/insights.js +3 -0
- package/dist/entrypoints/insights.js.map +1 -0
- package/dist/entrypoints/insights.schemas.d.ts +2 -0
- package/dist/entrypoints/insights.schemas.d.ts.map +1 -0
- package/dist/entrypoints/insights.schemas.js +3 -0
- package/dist/entrypoints/insights.schemas.js.map +1 -0
- package/dist/index.d.ts +4 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/services/index.d.ts +2 -0
- package/dist/services/index.d.ts.map +1 -1
- package/dist/services/index.js +2 -0
- package/dist/services/index.js.map +1 -1
- package/dist/services/insights/binges.d.ts +8 -0
- package/dist/services/insights/binges.d.ts.map +1 -0
- package/dist/services/insights/binges.js +53 -0
- package/dist/services/insights/binges.js.map +1 -0
- package/dist/services/insights/compare.d.ts +8 -0
- package/dist/services/insights/compare.d.ts.map +1 -0
- package/dist/services/insights/compare.js +39 -0
- package/dist/services/insights/compare.js.map +1 -0
- package/dist/services/insights/discoveries.d.ts +8 -0
- package/dist/services/insights/discoveries.d.ts.map +1 -0
- package/dist/services/insights/discoveries.js +53 -0
- package/dist/services/insights/discoveries.js.map +1 -0
- package/dist/services/insights/hours.d.ts +8 -0
- package/dist/services/insights/hours.d.ts.map +1 -0
- package/dist/services/insights/hours.js +43 -0
- package/dist/services/insights/hours.js.map +1 -0
- package/dist/services/insights/lib/binges.d.ts +31 -0
- package/dist/services/insights/lib/binges.d.ts.map +1 -0
- package/dist/services/insights/lib/binges.js +61 -0
- package/dist/services/insights/lib/binges.js.map +1 -0
- package/dist/services/insights/lib/compare.d.ts +31 -0
- package/dist/services/insights/lib/compare.d.ts.map +1 -0
- package/dist/services/insights/lib/compare.js +68 -0
- package/dist/services/insights/lib/compare.js.map +1 -0
- package/dist/services/insights/lib/discoveries.d.ts +17 -0
- package/dist/services/insights/lib/discoveries.d.ts.map +1 -0
- package/dist/services/insights/lib/discoveries.js +24 -0
- package/dist/services/insights/lib/discoveries.js.map +1 -0
- package/dist/services/insights/lib/diversity.d.ts +28 -0
- package/dist/services/insights/lib/diversity.d.ts.map +1 -0
- package/dist/services/insights/lib/diversity.js +39 -0
- package/dist/services/insights/lib/diversity.js.map +1 -0
- package/dist/services/insights/lib/hours.d.ts +39 -0
- package/dist/services/insights/lib/hours.d.ts.map +1 -0
- package/dist/services/insights/lib/hours.js +66 -0
- package/dist/services/insights/lib/hours.js.map +1 -0
- package/dist/services/insights/lib/mood.d.ts +22 -0
- package/dist/services/insights/lib/mood.d.ts.map +1 -0
- package/dist/services/insights/lib/mood.js +187 -0
- package/dist/services/insights/lib/mood.js.map +1 -0
- package/dist/services/insights/lib/periods.d.ts +17 -0
- package/dist/services/insights/lib/periods.d.ts.map +1 -0
- package/dist/services/insights/lib/periods.js +66 -0
- package/dist/services/insights/lib/periods.js.map +1 -0
- package/dist/services/insights/lib/personality.d.ts +30 -0
- package/dist/services/insights/lib/personality.d.ts.map +1 -0
- package/dist/services/insights/lib/personality.js +131 -0
- package/dist/services/insights/lib/personality.js.map +1 -0
- package/dist/services/insights/lib/trends.d.ts +30 -0
- package/dist/services/insights/lib/trends.d.ts.map +1 -0
- package/dist/services/insights/lib/trends.js +67 -0
- package/dist/services/insights/lib/trends.js.map +1 -0
- package/dist/services/insights/mood.d.ts +8 -0
- package/dist/services/insights/mood.d.ts.map +1 -0
- package/dist/services/insights/mood.js +53 -0
- package/dist/services/insights/mood.js.map +1 -0
- package/dist/services/insights/now-playing.d.ts +20 -0
- package/dist/services/insights/now-playing.d.ts.map +1 -0
- package/dist/services/insights/now-playing.js +128 -0
- package/dist/services/insights/now-playing.js.map +1 -0
- package/dist/services/insights/personality.d.ts +8 -0
- package/dist/services/insights/personality.d.ts.map +1 -0
- package/dist/services/insights/personality.js +42 -0
- package/dist/services/insights/personality.js.map +1 -0
- package/dist/services/insights/summary.d.ts +8 -0
- package/dist/services/insights/summary.d.ts.map +1 -0
- package/dist/services/insights/summary.js +117 -0
- package/dist/services/insights/summary.js.map +1 -0
- package/dist/services/insights/trends.d.ts +8 -0
- package/dist/services/insights/trends.d.ts.map +1 -0
- package/dist/services/insights/trends.js +96 -0
- package/dist/services/insights/trends.js.map +1 -0
- package/dist/services/insights.d.ts +96 -0
- package/dist/services/insights.d.ts.map +1 -0
- package/dist/services/insights.js +26 -0
- package/dist/services/insights.js.map +1 -0
- package/dist/services/insights.schemas.d.ts +710 -0
- package/dist/services/insights.schemas.d.ts.map +1 -0
- package/dist/services/insights.schemas.js +307 -0
- package/dist/services/insights.schemas.js.map +1 -0
- package/package.json +12 -4
package/CHANGELOG.md
CHANGED
|
@@ -9,6 +9,31 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
9
9
|
> [ansango/lastfm-api](https://github.com/ansango/lastfm-api). Entries below preserve the
|
|
10
10
|
> historical package and repository names used by their original releases.
|
|
11
11
|
|
|
12
|
+
## [3.4.0] - 2026-08-28
|
|
13
|
+
|
|
14
|
+
### ✨ Features
|
|
15
|
+
|
|
16
|
+
- **insights**: implement compareUsers with Jaccard affinity index (closes #125) ([95f39d6](https://github.com/ansango/lastfm-api/commit/95f39d6))
|
|
17
|
+
- **insights**: implement getMood psychometric classifier and getPersonality archetypes (closes #124) ([0aae0e8](https://github.com/ansango/lastfm-api/commit/0aae0e8))
|
|
18
|
+
- **insights**: implement getTrends ranking diff and getDiscoveries new-artist detector (closes #123) ([1a07234](https://github.com/ansango/lastfm-api/commit/1a07234))
|
|
19
|
+
- **insights**: implement getHoursHistogram and getBinges temporal analytics (closes #122) ([7615ba9](https://github.com/ansango/lastfm-api/commit/7615ba9))
|
|
20
|
+
- **insights**: implement getNowPlaying with artist bio and similarity enrichment (closes #121) ([9de9213](https://github.com/ansango/lastfm-api/commit/9de9213))
|
|
21
|
+
- **insights**: register getSummary route in method registry and Scalar OpenAPI docs ([875bafe](https://github.com/ansango/lastfm-api/commit/875bafe))
|
|
22
|
+
- **insights**: implement getSummary with Shannon diversity index (closes #120) ([28b03b0](https://github.com/ansango/lastfm-api/commit/28b03b0))
|
|
23
|
+
- **insights**: wire insights namespace in method registry ([2d1db4d](https://github.com/ansango/lastfm-api/commit/2d1db4d))
|
|
24
|
+
- **insights**: base architecture, Zod schemas infrastructure, and client wiring (closes #119) ([6ce9d4e](https://github.com/ansango/lastfm-api/commit/6ce9d4e))
|
|
25
|
+
|
|
26
|
+
### 📚 Documentation
|
|
27
|
+
|
|
28
|
+
- **insights**: add dedicated OpenAPI descriptions in Scalar for all 9 insight methods ([1115785](https://github.com/ansango/lastfm-api/commit/1115785))
|
|
29
|
+
- **insights**: documentation, OpenAPI Scalar integration, and CLI migration guide (closes #126) ([f325696](https://github.com/ansango/lastfm-api/commit/f325696))
|
|
30
|
+
|
|
31
|
+
### 🔧 Chores
|
|
32
|
+
|
|
33
|
+
- remove PROJECT_IDEAS.md ([a837c39](https://github.com/ansango/lastfm-api/commit/a837c39))
|
|
34
|
+
- update dependencies and configuration files - Bump @types/node to version 26.4.0 in package.json and bun.lock - Modify build script in package.json to use tsconfig.build.json - Update changelog script to handle undefined tags - Refactor recent tracks test for better readability - Adjust tsconfig files for improved build and development settings - Add tsconfig.build.json for build-specific configurations - Update .gitignore to exclude AGENTS.md ([2a1687f](https://github.com/ansango/lastfm-api/commit/2a1687f))
|
|
35
|
+
|
|
36
|
+
|
|
12
37
|
## [3.3.0] - 2026-08-28
|
|
13
38
|
|
|
14
39
|
### ✨ Features
|
package/README.md
CHANGED
|
@@ -14,7 +14,8 @@ A universal Last.fm API client for Node.js and Browser, written in TypeScript.
|
|
|
14
14
|
## Features
|
|
15
15
|
|
|
16
16
|
- ✅ **Universal**: Works in Node.js (≥20.0.0) and Browser
|
|
17
|
-
- ✅ **Complete coverage**: All
|
|
17
|
+
- ✅ **Complete coverage**: All 56 canonical Last.fm API methods across 9 namespaces
|
|
18
|
+
- ✅ **Insights & Analytics Engine**: 9 high-level derived analytical views (Shannon diversity, enriched Now Playing, diurnal histograms, binge runs, ranking diffs, new discoveries, 2D mood classification, personality archetypes, and Jaccard user comparison)
|
|
18
19
|
- ✅ **TypeScript**: Full type safety with comprehensive type definitions
|
|
19
20
|
- ✅ **Zod Schemas**: Runtime validation schemas for all types
|
|
20
21
|
- ✅ **ESM**: Modern ES modules with tree-shaking support
|
|
@@ -130,7 +131,14 @@ const libraryArtists = await client.library.getArtists({ user: 'ansango' });
|
|
|
130
131
|
// Auth service (for scrobbling and authenticated methods)
|
|
131
132
|
const session = await client.auth.getSession({ token: 'AUTH_TOKEN' });
|
|
132
133
|
// Request an auth token (signed GET, no sk)
|
|
133
|
-
const { token } = await client.auth.getToken();
|
|
134
|
+
const { token, authUrl } = await client.auth.getToken();
|
|
135
|
+
|
|
136
|
+
// Insights & analytics engine
|
|
137
|
+
const summary = await client.insights.getSummary({ user: 'ansango', period: '7day' });
|
|
138
|
+
const nowPlaying = await client.insights.getNowPlaying({ user: 'ansango' });
|
|
139
|
+
const mood = await client.insights.getMood({ user: 'ansango', period: '1month' });
|
|
140
|
+
const personality = await client.insights.getPersonality({ user: 'ansango' });
|
|
141
|
+
const comparison = await client.insights.compareUsers({ userA: 'ansango', userB: 'friend' });
|
|
134
142
|
|
|
135
143
|
// Now-playing and tag/love mutations (all require an authenticated session)
|
|
136
144
|
await client.track.updateNowPlaying({
|
|
@@ -214,6 +222,7 @@ const tracks = await trackService.search({ track: 'Come Together' });
|
|
|
214
222
|
- `@ansango/lastfm-api/geo`
|
|
215
223
|
- `@ansango/lastfm-api/library`
|
|
216
224
|
- `@ansango/lastfm-api/auth`
|
|
225
|
+
- `@ansango/lastfm-api/insights`
|
|
217
226
|
|
|
218
227
|
## Zod Schema Validation
|
|
219
228
|
|
|
@@ -262,8 +271,56 @@ if (result.success) {
|
|
|
262
271
|
- `@ansango/lastfm-api/geo/schemas`
|
|
263
272
|
- `@ansango/lastfm-api/library/schemas`
|
|
264
273
|
- `@ansango/lastfm-api/auth/schemas`
|
|
274
|
+
- `@ansango/lastfm-api/insights/schemas`
|
|
265
275
|
- `@ansango/lastfm-api/schemas` (base types like `imageSchema`, `datePropSchema`, etc.)
|
|
266
276
|
|
|
277
|
+
## Insights & Analytics Engine
|
|
278
|
+
|
|
279
|
+
The package includes a built-in analytics engine providing 9 high-level derived views computed over Last.fm data:
|
|
280
|
+
|
|
281
|
+
```typescript
|
|
282
|
+
import { createClient } from '@ansango/lastfm-api';
|
|
283
|
+
|
|
284
|
+
const client = createClient();
|
|
285
|
+
|
|
286
|
+
// 1. Summary & Shannon Diversity
|
|
287
|
+
const summary = await client.insights.getSummary({ user: 'ansango', period: '7day' });
|
|
288
|
+
console.log(`Total Scrobbles: ${summary.totalScrobbles}, Normalized Diversity: ${summary.diversity?.normalized}`);
|
|
289
|
+
|
|
290
|
+
// 2. Enriched Now Playing with Artist Biography and Similar Artists
|
|
291
|
+
const nowPlaying = await client.insights.getNowPlaying({ user: 'ansango', similarLimit: 3 });
|
|
292
|
+
|
|
293
|
+
// 3. Diurnal & Weekday Histogram
|
|
294
|
+
const histogram = await client.insights.getHoursHistogram({ user: 'ansango', sinceDays: 30 });
|
|
295
|
+
console.log(`Peak Hour: ${histogram.peakHour}:00, Night Share: ${(histogram.nightShare * 100).toFixed(0)}%`);
|
|
296
|
+
|
|
297
|
+
// 4. Binge Listening Streak Detection
|
|
298
|
+
const binges = await client.insights.getBinges({ user: 'ansango', minLength: 3, maxGapSeconds: 1800 });
|
|
299
|
+
|
|
300
|
+
// 5. Ranking Trends Differentials (Risers, Fallers, Newcomers, Departures)
|
|
301
|
+
const trends = await client.insights.getTrends({
|
|
302
|
+
user: 'ansango',
|
|
303
|
+
target: 'artists',
|
|
304
|
+
currentPeriod: '7day',
|
|
305
|
+
previousPeriod: '1month'
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
// 6. New Artist Discoveries
|
|
309
|
+
const discoveries = await client.insights.getDiscoveries({ user: 'ansango', windowDays: 7 });
|
|
310
|
+
|
|
311
|
+
// 7. 2D Mood Classification (Energy vs. Valence)
|
|
312
|
+
const mood = await client.insights.getMood({ user: 'ansango', period: '1month' });
|
|
313
|
+
console.log(`Mood: ${mood.label} (Energy: ${mood.axes.energy}, Valence: ${mood.axes.valence})`);
|
|
314
|
+
|
|
315
|
+
// 8. Listener Personality Archetype
|
|
316
|
+
const personality = await client.insights.getPersonality({ user: 'ansango' });
|
|
317
|
+
console.log(`Archetype: ${personality.archetype.emoji} ${personality.archetype.name} - ${personality.archetype.blurb}`);
|
|
318
|
+
|
|
319
|
+
// 9. User-to-User Taste Clash (Jaccard Similarity)
|
|
320
|
+
const comparison = await client.insights.compareUsers({ userA: 'ansango', userB: 'friend' });
|
|
321
|
+
console.log(`Taste Compatibility: ${comparison.compatibilityScore}% (Jaccard: ${comparison.jaccard})`);
|
|
322
|
+
```
|
|
323
|
+
|
|
267
324
|
## Environment Variables
|
|
268
325
|
|
|
269
326
|
In Node.js environments, the client automatically loads configuration from environment variables:
|
|
@@ -425,6 +482,7 @@ class LastFmClient {
|
|
|
425
482
|
geo: GeoService;
|
|
426
483
|
library: LibraryService;
|
|
427
484
|
auth: AuthService;
|
|
485
|
+
insights: InsightsService;
|
|
428
486
|
|
|
429
487
|
constructor(config?: Partial<LastFmConfig>);
|
|
430
488
|
getConfig(): Readonly<LastFmConfig>;
|
|
@@ -450,7 +508,7 @@ function resetGlobalConfig(): void;
|
|
|
450
508
|
|
|
451
509
|
### Services
|
|
452
510
|
|
|
453
|
-
The package covers all
|
|
511
|
+
The package covers all 56 canonical Last.fm API methods across 9 namespaces, plus 9 derived analytical methods in the `InsightsService`. See [docs/api-coverage.md](docs/api-coverage.md) for the full table.
|
|
454
512
|
|
|
455
513
|
- **UserService**: 13 methods — `getInfo`, `getFriends`, `getLovedTracks`, `getRecentTracks`, `getTopAlbums`, `getTopArtists`, `getTopTags`, `getTopTracks`, `getWeeklyAlbumChart`, `getWeeklyArtistChart`, `getWeeklyChartList`, `getWeeklyTrackChart`, `getPersonalTags`
|
|
456
514
|
- **AlbumService**: 6 methods — `getInfo`, `getTags`, `getTopTags`, `search`, `addTags`¹, `removeTag`¹
|
|
@@ -461,6 +519,7 @@ The package covers all 57 canonical Last.fm API methods across 9 namespaces. See
|
|
|
461
519
|
- **GeoService**: 2 methods — `getTopArtists`, `getTopTracks`
|
|
462
520
|
- **LibraryService**: 1 method — `getArtists`
|
|
463
521
|
- **AuthService**: 2 methods — `getSession`, `getToken` (removed `getMobileSession` in v4.0.0)
|
|
522
|
+
- **InsightsService**: 9 methods — `getSummary`, `getNowPlaying`, `getHoursHistogram`, `getBinges`, `getTrends`, `getDiscoveries`, `getMood`, `getPersonality`, `compareUsers`
|
|
464
523
|
|
|
465
524
|
¹ Requires an authenticated session.
|
|
466
525
|
|
package/dist/client.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { LastFmConfig } from './config.js';
|
|
2
|
-
import { type AlbumService, type ArtistService, type AuthService, type ChartService, type GeoService, type LibraryService, type TagService, type TrackService, type UserService } from './services/index.js';
|
|
2
|
+
import { type AlbumService, type ArtistService, type AuthService, type ChartService, type GeoService, type InsightsService, type LibraryService, type TagService, type TrackService, type UserService } from './services/index.js';
|
|
3
3
|
/**
|
|
4
4
|
* Last.fm API Client
|
|
5
5
|
*
|
|
@@ -29,6 +29,7 @@ export declare class LastFmClient {
|
|
|
29
29
|
readonly geo: GeoService;
|
|
30
30
|
readonly library: LibraryService;
|
|
31
31
|
readonly auth: AuthService;
|
|
32
|
+
readonly insights: InsightsService;
|
|
32
33
|
private readonly config;
|
|
33
34
|
constructor(config?: Partial<LastFmConfig>);
|
|
34
35
|
/**
|
package/dist/client.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAE/C,OAAO,EACN,KAAK,YAAY,EACjB,KAAK,aAAa,EAClB,KAAK,WAAW,EAChB,KAAK,YAAY,
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAE/C,OAAO,EACN,KAAK,YAAY,EACjB,KAAK,aAAa,EAClB,KAAK,WAAW,EAChB,KAAK,YAAY,EAWjB,KAAK,UAAU,EACf,KAAK,eAAe,EACpB,KAAK,cAAc,EACnB,KAAK,UAAU,EACf,KAAK,YAAY,EACjB,KAAK,WAAW,EAChB,MAAM,qBAAqB,CAAA;AAE5B;;;;;;;;;;;;;;;;;;GAkBG;AACH,qBAAa,YAAY;IACxB,SAAgB,IAAI,EAAE,WAAW,CAAA;IACjC,SAAgB,KAAK,EAAE,YAAY,CAAA;IACnC,SAAgB,MAAM,EAAE,aAAa,CAAA;IACrC,SAAgB,KAAK,EAAE,YAAY,CAAA;IACnC,SAAgB,GAAG,EAAE,UAAU,CAAA;IAC/B,SAAgB,KAAK,EAAE,YAAY,CAAA;IACnC,SAAgB,GAAG,EAAE,UAAU,CAAA;IAC/B,SAAgB,OAAO,EAAE,cAAc,CAAA;IACvC,SAAgB,IAAI,EAAE,WAAW,CAAA;IACjC,SAAgB,QAAQ,EAAE,eAAe,CAAA;IAEzC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAc;gBAEzB,MAAM,CAAC,EAAE,OAAO,CAAC,YAAY,CAAC;IAgB1C;;OAEG;IACH,SAAS,IAAI,QAAQ,CAAC,YAAY,CAAC;CAGnC;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,YAAY,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC,YAAY,CAAC,GAAG,YAAY,CAEzE"}
|
package/dist/client.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createConfig, getGlobalConfig } from './config.js';
|
|
2
|
-
import { createAlbumService, createArtistService, createAuthService, createChartService, createGeoService, createLibraryService, createTagService, createTrackService, createUserService, } from './services/index.js';
|
|
2
|
+
import { createAlbumService, createArtistService, createAuthService, createChartService, createGeoService, createInsightsService, createLibraryService, createTagService, createTrackService, createUserService, } from './services/index.js';
|
|
3
3
|
/**
|
|
4
4
|
* Last.fm API Client
|
|
5
5
|
*
|
|
@@ -29,6 +29,7 @@ export class LastFmClient {
|
|
|
29
29
|
geo;
|
|
30
30
|
library;
|
|
31
31
|
auth;
|
|
32
|
+
insights;
|
|
32
33
|
config;
|
|
33
34
|
constructor(config) {
|
|
34
35
|
this.config = config ? createConfig(config) : getGlobalConfig();
|
|
@@ -42,6 +43,7 @@ export class LastFmClient {
|
|
|
42
43
|
this.geo = createGeoService(this.config);
|
|
43
44
|
this.library = createLibraryService(this.config);
|
|
44
45
|
this.auth = createAuthService(this.config);
|
|
46
|
+
this.insights = createInsightsService(this.config);
|
|
45
47
|
}
|
|
46
48
|
/**
|
|
47
49
|
* Get the current configuration (read-only)
|
package/dist/client.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,eAAe,EAAE,MAAM,aAAa,CAAA;AAC3D,OAAO,EAKN,kBAAkB,EAClB,mBAAmB,EACnB,iBAAiB,EACjB,kBAAkB,EAClB,gBAAgB,EAChB,oBAAoB,EACpB,gBAAgB,EAChB,kBAAkB,EAClB,iBAAiB,
|
|
1
|
+
{"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,eAAe,EAAE,MAAM,aAAa,CAAA;AAC3D,OAAO,EAKN,kBAAkB,EAClB,mBAAmB,EACnB,iBAAiB,EACjB,kBAAkB,EAClB,gBAAgB,EAChB,qBAAqB,EACrB,oBAAoB,EACpB,gBAAgB,EAChB,kBAAkB,EAClB,iBAAiB,GAOjB,MAAM,qBAAqB,CAAA;AAE5B;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,OAAO,YAAY;IACR,IAAI,CAAa;IACjB,KAAK,CAAc;IACnB,MAAM,CAAe;IACrB,KAAK,CAAc;IACnB,GAAG,CAAY;IACf,KAAK,CAAc;IACnB,GAAG,CAAY;IACf,OAAO,CAAgB;IACvB,IAAI,CAAa;IACjB,QAAQ,CAAiB;IAExB,MAAM,CAAc;IAErC,YAAY,MAA8B;QACzC,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC,CAAC,CAAC,YAAY,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,eAAe,EAAE,CAAA;QAE/D,0BAA0B;QAC1B,IAAI,CAAC,IAAI,GAAG,iBAAiB,CAAC,IAAI,CAAC,MAAM,CAAC,CAAA;QAC1C,IAAI,CAAC,KAAK,GAAG,kBAAkB,CAAC,IAAI,CAAC,MAAM,CAAC,CAAA;QAC5C,IAAI,CAAC,MAAM,GAAG,mBAAmB,CAAC,IAAI,CAAC,MAAM,CAAC,CAAA;QAC9C,IAAI,CAAC,KAAK,GAAG,kBAAkB,CAAC,IAAI,CAAC,MAAM,CAAC,CAAA;QAC5C,IAAI,CAAC,GAAG,GAAG,gBAAgB,CAAC,IAAI,CAAC,MAAM,CAAC,CAAA;QACxC,IAAI,CAAC,KAAK,GAAG,kBAAkB,CAAC,IAAI,CAAC,MAAM,CAAC,CAAA;QAC5C,IAAI,CAAC,GAAG,GAAG,gBAAgB,CAAC,IAAI,CAAC,MAAM,CAAC,CAAA;QACxC,IAAI,CAAC,OAAO,GAAG,oBAAoB,CAAC,IAAI,CAAC,MAAM,CAAC,CAAA;QAChD,IAAI,CAAC,IAAI,GAAG,iBAAiB,CAAC,IAAI,CAAC,MAAM,CAAC,CAAA;QAC1C,IAAI,CAAC,QAAQ,GAAG,qBAAqB,CAAC,IAAI,CAAC,MAAM,CAAC,CAAA;IACnD,CAAC;IAED;;OAEG;IACH,SAAS;QACR,OAAO,EAAE,GAAG,IAAI,CAAC,MAAM,EAAE,CAAA;IAC1B,CAAC;CACD;AAED;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,YAAY,CAAC,MAA8B;IAC1D,OAAO,IAAI,YAAY,CAAC,MAAM,CAAC,CAAA;AAChC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"insights.d.ts","sourceRoot":"","sources":["../../src/entrypoints/insights.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,qBAAqB,EAAE,KAAK,eAAe,EAAE,MAAM,yBAAyB,CAAA;AACrF,mBAAmB,iCAAiC,CAAA;AACpD,cAAc,iCAAiC,CAAA"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"insights.js","sourceRoot":"","sources":["../../src/entrypoints/insights.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,qBAAqB,EAAwB,MAAM,yBAAyB,CAAA;AAErF,cAAc,iCAAiC,CAAA"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"insights.schemas.d.ts","sourceRoot":"","sources":["../../src/entrypoints/insights.schemas.ts"],"names":[],"mappings":"AACA,cAAc,iCAAiC,CAAA"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"insights.schemas.js","sourceRoot":"","sources":["../../src/entrypoints/insights.schemas.ts"],"names":[],"mappings":"AAAA,gDAAgD;AAChD,cAAc,iCAAiC,CAAA"}
|
package/dist/index.d.ts
CHANGED
|
@@ -10,8 +10,10 @@ export type * from './services/chart.schemas.js';
|
|
|
10
10
|
export * from './services/chart.schemas.js';
|
|
11
11
|
export type * from './services/geo.schemas.js';
|
|
12
12
|
export * from './services/geo.schemas.js';
|
|
13
|
-
export type { AlbumService, ArtistService, AuthService, ChartService, GeoService, LibraryService, TagService, TrackService, UserService, } from './services/index.js';
|
|
14
|
-
export { createAlbumService, createArtistService, createAuthService, createChartService, createGeoService, createLibraryService, createTagService, createTrackService, createUserService, } from './services/index.js';
|
|
13
|
+
export type { AlbumService, ArtistService, AuthService, ChartService, GeoService, InsightsService, LibraryService, TagService, TrackService, UserService, } from './services/index.js';
|
|
14
|
+
export { createAlbumService, createArtistService, createAuthService, createChartService, createGeoService, createInsightsService, createLibraryService, createTagService, createTrackService, createUserService, } from './services/index.js';
|
|
15
|
+
export type * from './services/insights.schemas.js';
|
|
16
|
+
export * from './services/insights.schemas.js';
|
|
15
17
|
export type * from './services/library.schemas.js';
|
|
16
18
|
export * from './services/library.schemas.js';
|
|
17
19
|
export type * from './services/schemas/index.js';
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AACxD,OAAO,EACN,YAAY,EACZ,eAAe,EACf,KAAK,YAAY,EACjB,iBAAiB,EACjB,eAAe,GACf,MAAM,aAAa,CAAA;AACpB,mBAAmB,6BAA6B,CAAA;AAChD,cAAc,6BAA6B,CAAA;AAC3C,mBAAmB,8BAA8B,CAAA;AACjD,cAAc,8BAA8B,CAAA;AAC5C,mBAAmB,4BAA4B,CAAA;AAC/C,cAAc,4BAA4B,CAAA;AAC1C,mBAAmB,6BAA6B,CAAA;AAChD,cAAc,6BAA6B,CAAA;AAC3C,mBAAmB,2BAA2B,CAAA;AAC9C,cAAc,2BAA2B,CAAA;AAEzC,YAAY,EACX,YAAY,EACZ,aAAa,EACb,WAAW,EACX,YAAY,EACZ,UAAU,EACV,cAAc,EACd,UAAU,EACV,YAAY,EACZ,WAAW,GACX,MAAM,qBAAqB,CAAA;AAE5B,OAAO,EACN,kBAAkB,EAClB,mBAAmB,EACnB,iBAAiB,EACjB,kBAAkB,EAClB,gBAAgB,EAChB,oBAAoB,EACpB,gBAAgB,EAChB,kBAAkB,EAClB,iBAAiB,GACjB,MAAM,qBAAqB,CAAA;AAC5B,mBAAmB,+BAA+B,CAAA;AAClD,cAAc,+BAA+B,CAAA;AAC7C,mBAAmB,6BAA6B,CAAA;AAChD,cAAc,6BAA6B,CAAA;AAC3C,mBAAmB,2BAA2B,CAAA;AAC9C,cAAc,2BAA2B,CAAA;AACzC,mBAAmB,6BAA6B,CAAA;AAChD,cAAc,6BAA6B,CAAA;AAE3C,mBAAmB,4BAA4B,CAAA;AAE/C,cAAc,4BAA4B,CAAA;AAG1C,OAAO,EAAE,YAAY,EAAE,QAAQ,EAAE,OAAO,EAAE,iBAAiB,EAAE,cAAc,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAA"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AACxD,OAAO,EACN,YAAY,EACZ,eAAe,EACf,KAAK,YAAY,EACjB,iBAAiB,EACjB,eAAe,GACf,MAAM,aAAa,CAAA;AACpB,mBAAmB,6BAA6B,CAAA;AAChD,cAAc,6BAA6B,CAAA;AAC3C,mBAAmB,8BAA8B,CAAA;AACjD,cAAc,8BAA8B,CAAA;AAC5C,mBAAmB,4BAA4B,CAAA;AAC/C,cAAc,4BAA4B,CAAA;AAC1C,mBAAmB,6BAA6B,CAAA;AAChD,cAAc,6BAA6B,CAAA;AAC3C,mBAAmB,2BAA2B,CAAA;AAC9C,cAAc,2BAA2B,CAAA;AAEzC,YAAY,EACX,YAAY,EACZ,aAAa,EACb,WAAW,EACX,YAAY,EACZ,UAAU,EACV,eAAe,EACf,cAAc,EACd,UAAU,EACV,YAAY,EACZ,WAAW,GACX,MAAM,qBAAqB,CAAA;AAE5B,OAAO,EACN,kBAAkB,EAClB,mBAAmB,EACnB,iBAAiB,EACjB,kBAAkB,EAClB,gBAAgB,EAChB,qBAAqB,EACrB,oBAAoB,EACpB,gBAAgB,EAChB,kBAAkB,EAClB,iBAAiB,GACjB,MAAM,qBAAqB,CAAA;AAC5B,mBAAmB,gCAAgC,CAAA;AACnD,cAAc,gCAAgC,CAAA;AAC9C,mBAAmB,+BAA+B,CAAA;AAClD,cAAc,+BAA+B,CAAA;AAC7C,mBAAmB,6BAA6B,CAAA;AAChD,cAAc,6BAA6B,CAAA;AAC3C,mBAAmB,2BAA2B,CAAA;AAC9C,cAAc,2BAA2B,CAAA;AACzC,mBAAmB,6BAA6B,CAAA;AAChD,cAAc,6BAA6B,CAAA;AAE3C,mBAAmB,4BAA4B,CAAA;AAE/C,cAAc,4BAA4B,CAAA;AAG1C,OAAO,EAAE,YAAY,EAAE,QAAQ,EAAE,OAAO,EAAE,iBAAiB,EAAE,cAAc,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAA"}
|
package/dist/index.js
CHANGED
|
@@ -8,7 +8,8 @@ export * from './services/auth.schemas.js';
|
|
|
8
8
|
export * from './services/chart.schemas.js';
|
|
9
9
|
export * from './services/geo.schemas.js';
|
|
10
10
|
// Servicios (para uso avanzado)
|
|
11
|
-
export { createAlbumService, createArtistService, createAuthService, createChartService, createGeoService, createLibraryService, createTagService, createTrackService, createUserService, } from './services/index.js';
|
|
11
|
+
export { createAlbumService, createArtistService, createAuthService, createChartService, createGeoService, createInsightsService, createLibraryService, createTagService, createTrackService, createUserService, } from './services/index.js';
|
|
12
|
+
export * from './services/insights.schemas.js';
|
|
12
13
|
export * from './services/library.schemas.js';
|
|
13
14
|
export * from './services/schemas/index.js';
|
|
14
15
|
export * from './services/tag.schemas.js';
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,gBAAgB;AAEhB,mBAAmB;AACnB,OAAO,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AACxD,OAAO,EACN,YAAY,EACZ,eAAe,EAEf,iBAAiB,EACjB,eAAe,GACf,MAAM,aAAa,CAAA;AAEpB,cAAc,6BAA6B,CAAA;AAE3C,cAAc,8BAA8B,CAAA;AAE5C,cAAc,4BAA4B,CAAA;AAE1C,cAAc,6BAA6B,CAAA;AAE3C,cAAc,2BAA2B,CAAA;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,gBAAgB;AAEhB,mBAAmB;AACnB,OAAO,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AACxD,OAAO,EACN,YAAY,EACZ,eAAe,EAEf,iBAAiB,EACjB,eAAe,GACf,MAAM,aAAa,CAAA;AAEpB,cAAc,6BAA6B,CAAA;AAE3C,cAAc,8BAA8B,CAAA;AAE5C,cAAc,4BAA4B,CAAA;AAE1C,cAAc,6BAA6B,CAAA;AAE3C,cAAc,2BAA2B,CAAA;AAczC,gCAAgC;AAChC,OAAO,EACN,kBAAkB,EAClB,mBAAmB,EACnB,iBAAiB,EACjB,kBAAkB,EAClB,gBAAgB,EAChB,qBAAqB,EACrB,oBAAoB,EACpB,gBAAgB,EAChB,kBAAkB,EAClB,iBAAiB,GACjB,MAAM,qBAAqB,CAAA;AAE5B,cAAc,gCAAgC,CAAA;AAE9C,cAAc,+BAA+B,CAAA;AAE7C,cAAc,6BAA6B,CAAA;AAE3C,cAAc,2BAA2B,CAAA;AAEzC,cAAc,6BAA6B,CAAA;AAG3C,mCAAmC;AACnC,cAAc,4BAA4B,CAAA;AAE1C,oCAAoC;AACpC,OAAO,EAAE,YAAY,EAAE,QAAQ,EAAE,OAAO,EAAE,iBAAiB,EAAE,cAAc,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAA"}
|
package/dist/services/index.d.ts
CHANGED
|
@@ -8,6 +8,8 @@ export * from './chart.js';
|
|
|
8
8
|
export * from './chart.schemas.js';
|
|
9
9
|
export * from './geo.js';
|
|
10
10
|
export * from './geo.schemas.js';
|
|
11
|
+
export * from './insights.js';
|
|
12
|
+
export * from './insights.schemas.js';
|
|
11
13
|
export * from './library.js';
|
|
12
14
|
export * from './library.schemas.js';
|
|
13
15
|
export * from './schemas/index.js';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/services/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAA;AAC1B,cAAc,oBAAoB,CAAA;AAClC,cAAc,aAAa,CAAA;AAC3B,cAAc,qBAAqB,CAAA;AACnC,cAAc,WAAW,CAAA;AACzB,cAAc,mBAAmB,CAAA;AACjC,cAAc,YAAY,CAAA;AAC1B,cAAc,oBAAoB,CAAA;AAClC,cAAc,UAAU,CAAA;AACxB,cAAc,kBAAkB,CAAA;AAChC,cAAc,cAAc,CAAA;AAC5B,cAAc,sBAAsB,CAAA;AACpC,cAAc,oBAAoB,CAAA;AAClC,cAAc,UAAU,CAAA;AACxB,cAAc,kBAAkB,CAAA;AAChC,cAAc,YAAY,CAAA;AAC1B,cAAc,oBAAoB,CAAA;AAClC,cAAc,WAAW,CAAA;AAEzB,cAAc,mBAAmB,CAAA"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/services/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAA;AAC1B,cAAc,oBAAoB,CAAA;AAClC,cAAc,aAAa,CAAA;AAC3B,cAAc,qBAAqB,CAAA;AACnC,cAAc,WAAW,CAAA;AACzB,cAAc,mBAAmB,CAAA;AACjC,cAAc,YAAY,CAAA;AAC1B,cAAc,oBAAoB,CAAA;AAClC,cAAc,UAAU,CAAA;AACxB,cAAc,kBAAkB,CAAA;AAChC,cAAc,eAAe,CAAA;AAC7B,cAAc,uBAAuB,CAAA;AACrC,cAAc,cAAc,CAAA;AAC5B,cAAc,sBAAsB,CAAA;AACpC,cAAc,oBAAoB,CAAA;AAClC,cAAc,UAAU,CAAA;AACxB,cAAc,kBAAkB,CAAA;AAChC,cAAc,YAAY,CAAA;AAC1B,cAAc,oBAAoB,CAAA;AAClC,cAAc,WAAW,CAAA;AAEzB,cAAc,mBAAmB,CAAA"}
|
package/dist/services/index.js
CHANGED
|
@@ -8,6 +8,8 @@ export * from './chart.js';
|
|
|
8
8
|
export * from './chart.schemas.js';
|
|
9
9
|
export * from './geo.js';
|
|
10
10
|
export * from './geo.schemas.js';
|
|
11
|
+
export * from './insights.js';
|
|
12
|
+
export * from './insights.schemas.js';
|
|
11
13
|
export * from './library.js';
|
|
12
14
|
export * from './library.schemas.js';
|
|
13
15
|
export * from './schemas/index.js';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/services/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAA;AAC1B,cAAc,oBAAoB,CAAA;AAClC,cAAc,aAAa,CAAA;AAC3B,cAAc,qBAAqB,CAAA;AACnC,cAAc,WAAW,CAAA;AACzB,cAAc,mBAAmB,CAAA;AACjC,cAAc,YAAY,CAAA;AAC1B,cAAc,oBAAoB,CAAA;AAClC,cAAc,UAAU,CAAA;AACxB,cAAc,kBAAkB,CAAA;AAChC,cAAc,cAAc,CAAA;AAC5B,cAAc,sBAAsB,CAAA;AACpC,cAAc,oBAAoB,CAAA;AAClC,cAAc,UAAU,CAAA;AACxB,cAAc,kBAAkB,CAAA;AAChC,cAAc,YAAY,CAAA;AAC1B,cAAc,oBAAoB,CAAA;AAClC,cAAc,WAAW,CAAA;AACzB,+BAA+B;AAC/B,cAAc,mBAAmB,CAAA"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/services/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAA;AAC1B,cAAc,oBAAoB,CAAA;AAClC,cAAc,aAAa,CAAA;AAC3B,cAAc,qBAAqB,CAAA;AACnC,cAAc,WAAW,CAAA;AACzB,cAAc,mBAAmB,CAAA;AACjC,cAAc,YAAY,CAAA;AAC1B,cAAc,oBAAoB,CAAA;AAClC,cAAc,UAAU,CAAA;AACxB,cAAc,kBAAkB,CAAA;AAChC,cAAc,eAAe,CAAA;AAC7B,cAAc,uBAAuB,CAAA;AACrC,cAAc,cAAc,CAAA;AAC5B,cAAc,sBAAsB,CAAA;AACpC,cAAc,oBAAoB,CAAA;AAClC,cAAc,UAAU,CAAA;AACxB,cAAc,kBAAkB,CAAA;AAChC,cAAc,YAAY,CAAA;AAC1B,cAAc,oBAAoB,CAAA;AAClC,cAAc,WAAW,CAAA;AACzB,+BAA+B;AAC/B,cAAc,mBAAmB,CAAA"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { LastFmConfig } from '../../config.js';
|
|
2
|
+
import type { InsightsBingesRequest, InsightsBingesResponse } from '../insights.schemas.js';
|
|
3
|
+
/**
|
|
4
|
+
* Detects binge listening streaks (consecutive plays of the same artist or track)
|
|
5
|
+
* across a user's recent scrobbles.
|
|
6
|
+
*/
|
|
7
|
+
export declare function getBinges(config: LastFmConfig, params: InsightsBingesRequest, init?: RequestInit): Promise<InsightsBingesResponse>;
|
|
8
|
+
//# sourceMappingURL=binges.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"binges.d.ts","sourceRoot":"","sources":["../../../src/services/insights/binges.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAA;AACnD,OAAO,KAAK,EAAE,qBAAqB,EAAE,sBAAsB,EAAE,MAAM,wBAAwB,CAAA;AAI3F;;;GAGG;AACH,wBAAsB,SAAS,CAC9B,MAAM,EAAE,YAAY,EACpB,MAAM,EAAE,qBAAqB,EAC7B,IAAI,CAAC,EAAE,WAAW,GAChB,OAAO,CAAC,sBAAsB,CAAC,CAwDjC"}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { createUserService } from '../user.js';
|
|
2
|
+
import { findBinges } from './lib/binges.js';
|
|
3
|
+
/**
|
|
4
|
+
* Detects binge listening streaks (consecutive plays of the same artist or track)
|
|
5
|
+
* across a user's recent scrobbles.
|
|
6
|
+
*/
|
|
7
|
+
export async function getBinges(config, params, init) {
|
|
8
|
+
const userService = createUserService(config);
|
|
9
|
+
const to = params.to ?? Math.floor(Date.now() / 1000);
|
|
10
|
+
const from = params.from ?? (params.sinceDays ? to - params.sinceDays * 86400 : to - 7 * 86400);
|
|
11
|
+
const maxPages = params.maxPages ?? 50;
|
|
12
|
+
const scrobbles = [];
|
|
13
|
+
let page = 1;
|
|
14
|
+
while (page <= maxPages) {
|
|
15
|
+
const res = await userService.getRecentTracks({
|
|
16
|
+
user: params.user,
|
|
17
|
+
from: String(from),
|
|
18
|
+
to: String(to),
|
|
19
|
+
limit: 200,
|
|
20
|
+
page,
|
|
21
|
+
}, init);
|
|
22
|
+
const tracks = res.recenttracks?.track ?? [];
|
|
23
|
+
for (const t of tracks) {
|
|
24
|
+
const rawArtist = t.artist;
|
|
25
|
+
const artist = typeof rawArtist === 'object' && rawArtist !== null
|
|
26
|
+
? String(rawArtist.name ?? rawArtist['#text'] ?? '')
|
|
27
|
+
: String(rawArtist ?? '');
|
|
28
|
+
const track = String(t.name ?? '');
|
|
29
|
+
const uts = t.date?.uts ? Number(t.date.uts) : undefined;
|
|
30
|
+
if (artist.length > 0 && track.length > 0 && typeof uts === 'number' && !Number.isNaN(uts)) {
|
|
31
|
+
scrobbles.push({ artist, track, uts });
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
if (tracks.length < 200)
|
|
35
|
+
break;
|
|
36
|
+
page++;
|
|
37
|
+
}
|
|
38
|
+
// Recenttracks returns descending order; sort ascending for consecutive streak detection
|
|
39
|
+
scrobbles.sort((a, b) => a.uts - b.uts);
|
|
40
|
+
const trackKey = params.trackKey === true || params.trackKey === 'track' ? 'track' : 'artist';
|
|
41
|
+
const binges = findBinges(scrobbles, {
|
|
42
|
+
minLength: params.minLength,
|
|
43
|
+
maxGapSeconds: params.maxGapSeconds,
|
|
44
|
+
trackKey,
|
|
45
|
+
maxResults: params.maxResults,
|
|
46
|
+
});
|
|
47
|
+
return {
|
|
48
|
+
user: params.user,
|
|
49
|
+
totalScrobbles: scrobbles.length,
|
|
50
|
+
binges,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
//# sourceMappingURL=binges.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"binges.js","sourceRoot":"","sources":["../../../src/services/insights/binges.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { LastFmConfig } from '../../config.js';
|
|
2
|
+
import type { InsightsCompareRequest, InsightsCompareResponse } from '../insights.schemas.js';
|
|
3
|
+
/**
|
|
4
|
+
* Compares two Last.fm users' listening affinity using Jaccard similarity
|
|
5
|
+
* over mutual top artist rosters.
|
|
6
|
+
*/
|
|
7
|
+
export declare function compareUsers(config: LastFmConfig, params: InsightsCompareRequest, init?: RequestInit): Promise<InsightsCompareResponse>;
|
|
8
|
+
//# sourceMappingURL=compare.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"compare.d.ts","sourceRoot":"","sources":["../../../src/services/insights/compare.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAA;AACnD,OAAO,KAAK,EAAE,sBAAsB,EAAE,uBAAuB,EAAE,MAAM,wBAAwB,CAAA;AAK7F;;;GAGG;AACH,wBAAsB,YAAY,CACjC,MAAM,EAAE,YAAY,EACpB,MAAM,EAAE,sBAAsB,EAC9B,IAAI,CAAC,EAAE,WAAW,GAChB,OAAO,CAAC,uBAAuB,CAAC,CAmClC"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { createUserService } from '../user.js';
|
|
2
|
+
import { compareArtists } from './lib/compare.js';
|
|
3
|
+
import { resolvePeriod } from './lib/periods.js';
|
|
4
|
+
/**
|
|
5
|
+
* Compares two Last.fm users' listening affinity using Jaccard similarity
|
|
6
|
+
* over mutual top artist rosters.
|
|
7
|
+
*/
|
|
8
|
+
export async function compareUsers(config, params, init) {
|
|
9
|
+
const userService = createUserService(config);
|
|
10
|
+
const resolved = resolvePeriod(params.period ?? 'overall');
|
|
11
|
+
const limit = params.limit ?? 100;
|
|
12
|
+
const [topArtistsA, topArtistsB] = await Promise.all([
|
|
13
|
+
userService.getTopArtists({ user: params.userA, period: resolved.lastfm, limit }, init),
|
|
14
|
+
userService.getTopArtists({ user: params.userB, period: resolved.lastfm, limit }, init),
|
|
15
|
+
]);
|
|
16
|
+
const rosterA = (topArtistsA.topartists?.artist ?? []).map((a) => ({
|
|
17
|
+
name: a.name,
|
|
18
|
+
playcount: Number(a.playcount ?? 0),
|
|
19
|
+
}));
|
|
20
|
+
const rosterB = (topArtistsB.topartists?.artist ?? []).map((b) => ({
|
|
21
|
+
name: b.name,
|
|
22
|
+
playcount: Number(b.playcount ?? 0),
|
|
23
|
+
}));
|
|
24
|
+
const comparison = compareArtists(rosterA, rosterB);
|
|
25
|
+
return {
|
|
26
|
+
userA: params.userA,
|
|
27
|
+
userB: params.userB,
|
|
28
|
+
period: resolved.lastfm,
|
|
29
|
+
compatibilityScore: comparison.compatibilityScore,
|
|
30
|
+
jaccard: comparison.jaccard,
|
|
31
|
+
sharedCount: comparison.intersection.length,
|
|
32
|
+
userACount: comparison.aCount,
|
|
33
|
+
userBCount: comparison.bCount,
|
|
34
|
+
sharedArtists: [...comparison.sharedArtists],
|
|
35
|
+
onlyUserA: [...comparison.onlyA],
|
|
36
|
+
onlyUserB: [...comparison.onlyB],
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
//# sourceMappingURL=compare.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"compare.js","sourceRoot":"","sources":["../../../src/services/insights/compare.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAA;AAC9C,OAAO,EAAE,cAAc,EAAyB,MAAM,kBAAkB,CAAA;AACxE,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAA;AAEhD;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,YAAY,CACjC,MAAoB,EACpB,MAA8B,EAC9B,IAAkB;IAElB,MAAM,WAAW,GAAG,iBAAiB,CAAC,MAAM,CAAC,CAAA;IAC7C,MAAM,QAAQ,GAAG,aAAa,CAAC,MAAM,CAAC,MAAM,IAAI,SAAS,CAAC,CAAA;IAC1D,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,IAAI,GAAG,CAAA;IAEjC,MAAM,CAAC,WAAW,EAAE,WAAW,CAAC,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC;QACpD,WAAW,CAAC,aAAa,CAAC,EAAE,IAAI,EAAE,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,CAAC,MAAM,EAAE,KAAK,EAAE,EAAE,IAAI,CAAC;QACvF,WAAW,CAAC,aAAa,CAAC,EAAE,IAAI,EAAE,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,CAAC,MAAM,EAAE,KAAK,EAAE,EAAE,IAAI,CAAC;KACvF,CAAC,CAAA;IAEF,MAAM,OAAO,GAAuB,CAAC,WAAW,CAAC,UAAU,EAAE,MAAM,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;QACtF,IAAI,EAAE,CAAC,CAAC,IAAI;QACZ,SAAS,EAAE,MAAM,CAAC,CAAC,CAAC,SAAS,IAAI,CAAC,CAAC;KACnC,CAAC,CAAC,CAAA;IAEH,MAAM,OAAO,GAAuB,CAAC,WAAW,CAAC,UAAU,EAAE,MAAM,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;QACtF,IAAI,EAAE,CAAC,CAAC,IAAI;QACZ,SAAS,EAAE,MAAM,CAAC,CAAC,CAAC,SAAS,IAAI,CAAC,CAAC;KACnC,CAAC,CAAC,CAAA;IAEH,MAAM,UAAU,GAAG,cAAc,CAAC,OAAO,EAAE,OAAO,CAAC,CAAA;IAEnD,OAAO;QACN,KAAK,EAAE,MAAM,CAAC,KAAK;QACnB,KAAK,EAAE,MAAM,CAAC,KAAK;QACnB,MAAM,EAAE,QAAQ,CAAC,MAAM;QACvB,kBAAkB,EAAE,UAAU,CAAC,kBAAkB;QACjD,OAAO,EAAE,UAAU,CAAC,OAAO;QAC3B,WAAW,EAAE,UAAU,CAAC,YAAY,CAAC,MAAM;QAC3C,UAAU,EAAE,UAAU,CAAC,MAAM;QAC7B,UAAU,EAAE,UAAU,CAAC,MAAM;QAC7B,aAAa,EAAE,CAAC,GAAG,UAAU,CAAC,aAAa,CAAC;QAC5C,SAAS,EAAE,CAAC,GAAG,UAAU,CAAC,KAAK,CAAC;QAChC,SAAS,EAAE,CAAC,GAAG,UAAU,CAAC,KAAK,CAAC;KAChC,CAAA;AACF,CAAC"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { LastFmConfig } from '../../config.js';
|
|
2
|
+
import type { InsightsDiscoveriesRequest, InsightsDiscoveriesResponse } from '../insights.schemas.js';
|
|
3
|
+
/**
|
|
4
|
+
* Detects newly discovered artists in a recent time window by comparing
|
|
5
|
+
* against the user's historical baseline roster.
|
|
6
|
+
*/
|
|
7
|
+
export declare function getDiscoveries(config: LastFmConfig, params: InsightsDiscoveriesRequest, init?: RequestInit): Promise<InsightsDiscoveriesResponse>;
|
|
8
|
+
//# sourceMappingURL=discoveries.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"discoveries.d.ts","sourceRoot":"","sources":["../../../src/services/insights/discoveries.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAA;AACnD,OAAO,KAAK,EAAE,0BAA0B,EAAE,2BAA2B,EAAE,MAAM,wBAAwB,CAAA;AAIrG;;;GAGG;AACH,wBAAsB,cAAc,CACnC,MAAM,EAAE,YAAY,EACpB,MAAM,EAAE,0BAA0B,EAClC,IAAI,CAAC,EAAE,WAAW,GAChB,OAAO,CAAC,2BAA2B,CAAC,CA0DtC"}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { createUserService } from '../user.js';
|
|
2
|
+
import { findNewArtists } from './lib/discoveries.js';
|
|
3
|
+
/**
|
|
4
|
+
* Detects newly discovered artists in a recent time window by comparing
|
|
5
|
+
* against the user's historical baseline roster.
|
|
6
|
+
*/
|
|
7
|
+
export async function getDiscoveries(config, params, init) {
|
|
8
|
+
const userService = createUserService(config);
|
|
9
|
+
const windowDays = params.windowDays ?? 7;
|
|
10
|
+
const to = params.to ?? Math.floor(Date.now() / 1000);
|
|
11
|
+
const from = params.from ?? to - windowDays * 86400;
|
|
12
|
+
const baselineLimit = params.baselineLimit ?? 200;
|
|
13
|
+
const maxPages = params.maxPages ?? 20;
|
|
14
|
+
const maxResults = params.maxResults ?? 20;
|
|
15
|
+
// 1. Fetch baseline overall top artists
|
|
16
|
+
const baselineRes = await userService.getTopArtists({ user: params.user, period: 'overall', limit: baselineLimit }, init);
|
|
17
|
+
const baselineSet = new Set((baselineRes.topartists?.artist ?? []).map((a) => a.name));
|
|
18
|
+
// 2. Fetch window recent tracks
|
|
19
|
+
const windowScrobbles = [];
|
|
20
|
+
let page = 1;
|
|
21
|
+
while (page <= maxPages) {
|
|
22
|
+
const recentRes = await userService.getRecentTracks({
|
|
23
|
+
user: params.user,
|
|
24
|
+
from: String(from),
|
|
25
|
+
to: String(to),
|
|
26
|
+
limit: 200,
|
|
27
|
+
page,
|
|
28
|
+
}, init);
|
|
29
|
+
const tracks = recentRes.recenttracks?.track ?? [];
|
|
30
|
+
for (const t of tracks) {
|
|
31
|
+
const rawArtist = t.artist;
|
|
32
|
+
const artistName = typeof rawArtist === 'object' && rawArtist !== null
|
|
33
|
+
? String(rawArtist.name ?? rawArtist['#text'] ?? '')
|
|
34
|
+
: String(rawArtist ?? '');
|
|
35
|
+
const uts = t.date?.uts ? Number(t.date.uts) : undefined;
|
|
36
|
+
if (artistName.length > 0 && typeof uts === 'number' && !Number.isNaN(uts)) {
|
|
37
|
+
windowScrobbles.push({ name: artistName, firstSeen: uts });
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
if (tracks.length < 200)
|
|
41
|
+
break;
|
|
42
|
+
page++;
|
|
43
|
+
}
|
|
44
|
+
const discoveries = findNewArtists(windowScrobbles, baselineSet, { maxResults });
|
|
45
|
+
return {
|
|
46
|
+
user: params.user,
|
|
47
|
+
windowDays,
|
|
48
|
+
baselineSize: baselineSet.size,
|
|
49
|
+
totalDiscovered: discoveries.length,
|
|
50
|
+
discoveries,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
//# sourceMappingURL=discoveries.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"discoveries.js","sourceRoot":"","sources":["../../../src/services/insights/discoveries.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { LastFmConfig } from '../../config.js';
|
|
2
|
+
import type { InsightsHoursRequest, InsightsHoursResponse } from '../insights.schemas.js';
|
|
3
|
+
/**
|
|
4
|
+
* Buckets a user's recent scrobbles by hour-of-day (0..23) and weekday (0..6),
|
|
5
|
+
* calculating diurnal distribution and peak listening times.
|
|
6
|
+
*/
|
|
7
|
+
export declare function getHoursHistogram(config: LastFmConfig, params: InsightsHoursRequest, init?: RequestInit): Promise<InsightsHoursResponse>;
|
|
8
|
+
//# sourceMappingURL=hours.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hours.d.ts","sourceRoot":"","sources":["../../../src/services/insights/hours.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAA;AACnD,OAAO,KAAK,EAAE,oBAAoB,EAAE,qBAAqB,EAAE,MAAM,wBAAwB,CAAA;AAIzF;;;GAGG;AACH,wBAAsB,iBAAiB,CACtC,MAAM,EAAE,YAAY,EACpB,MAAM,EAAE,oBAAoB,EAC5B,IAAI,CAAC,EAAE,WAAW,GAChB,OAAO,CAAC,qBAAqB,CAAC,CA0ChC"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { createUserService } from '../user.js';
|
|
2
|
+
import { buildHourHistogram } from './lib/hours.js';
|
|
3
|
+
/**
|
|
4
|
+
* Buckets a user's recent scrobbles by hour-of-day (0..23) and weekday (0..6),
|
|
5
|
+
* calculating diurnal distribution and peak listening times.
|
|
6
|
+
*/
|
|
7
|
+
export async function getHoursHistogram(config, params, init) {
|
|
8
|
+
const userService = createUserService(config);
|
|
9
|
+
const to = params.to ?? Math.floor(Date.now() / 1000);
|
|
10
|
+
const from = params.from ?? (params.sinceDays ? to - params.sinceDays * 86400 : to - 30 * 86400);
|
|
11
|
+
const maxPages = params.maxPages ?? 50;
|
|
12
|
+
const timestamps = [];
|
|
13
|
+
let page = 1;
|
|
14
|
+
while (page <= maxPages) {
|
|
15
|
+
const res = await userService.getRecentTracks({
|
|
16
|
+
user: params.user,
|
|
17
|
+
from: String(from),
|
|
18
|
+
to: String(to),
|
|
19
|
+
limit: 200,
|
|
20
|
+
page,
|
|
21
|
+
}, init);
|
|
22
|
+
const tracks = res.recenttracks?.track ?? [];
|
|
23
|
+
for (const t of tracks) {
|
|
24
|
+
const uts = t.date?.uts;
|
|
25
|
+
if (uts) {
|
|
26
|
+
const num = Number(uts);
|
|
27
|
+
if (!Number.isNaN(num))
|
|
28
|
+
timestamps.push(num);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
if (tracks.length < 200)
|
|
32
|
+
break;
|
|
33
|
+
page++;
|
|
34
|
+
}
|
|
35
|
+
const histogram = buildHourHistogram(timestamps);
|
|
36
|
+
return {
|
|
37
|
+
user: params.user,
|
|
38
|
+
from,
|
|
39
|
+
to,
|
|
40
|
+
...histogram,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
//# sourceMappingURL=hours.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hours.js","sourceRoot":"","sources":["../../../src/services/insights/hours.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAA;AAC9C,OAAO,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAA;AAEnD;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,iBAAiB,CACtC,MAAoB,EACpB,MAA4B,EAC5B,IAAkB;IAElB,MAAM,WAAW,GAAG,iBAAiB,CAAC,MAAM,CAAC,CAAA;IAC7C,MAAM,EAAE,GAAG,MAAM,CAAC,EAAE,IAAI,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,CAAA;IACrD,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,GAAG,MAAM,CAAC,SAAS,GAAG,KAAK,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,GAAG,KAAK,CAAC,CAAA;IAChG,MAAM,QAAQ,GAAG,MAAM,CAAC,QAAQ,IAAI,EAAE,CAAA;IAEtC,MAAM,UAAU,GAAa,EAAE,CAAA;IAC/B,IAAI,IAAI,GAAG,CAAC,CAAA;IAEZ,OAAO,IAAI,IAAI,QAAQ,EAAE,CAAC;QACzB,MAAM,GAAG,GAAG,MAAM,WAAW,CAAC,eAAe,CAC5C;YACC,IAAI,EAAE,MAAM,CAAC,IAAI;YACjB,IAAI,EAAE,MAAM,CAAC,IAAI,CAAC;YAClB,EAAE,EAAE,MAAM,CAAC,EAAE,CAAC;YACd,KAAK,EAAE,GAAG;YACV,IAAI;SACJ,EACD,IAAI,CACJ,CAAA;QAED,MAAM,MAAM,GAAG,GAAG,CAAC,YAAY,EAAE,KAAK,IAAI,EAAE,CAAA;QAC5C,KAAK,MAAM,CAAC,IAAI,MAAM,EAAE,CAAC;YACxB,MAAM,GAAG,GAAG,CAAC,CAAC,IAAI,EAAE,GAAG,CAAA;YACvB,IAAI,GAAG,EAAE,CAAC;gBACT,MAAM,GAAG,GAAG,MAAM,CAAC,GAAG,CAAC,CAAA;gBACvB,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC;oBAAE,UAAU,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;YAC7C,CAAC;QACF,CAAC;QAED,IAAI,MAAM,CAAC,MAAM,GAAG,GAAG;YAAE,MAAK;QAC9B,IAAI,EAAE,CAAA;IACP,CAAC;IAED,MAAM,SAAS,GAAG,kBAAkB,CAAC,UAAU,CAAC,CAAA;IAEhD,OAAO;QACN,IAAI,EAAE,MAAM,CAAC,IAAI;QACjB,IAAI;QACJ,EAAE;QACF,GAAG,SAAS;KACZ,CAAA;AACF,CAAC"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* binges.ts — detect binge listening runs from a list of scrobbles.
|
|
3
|
+
*
|
|
4
|
+
* Groups consecutive plays of the same artist or track where the gap
|
|
5
|
+
* between adjacent scrobbles does not exceed maxGapSeconds (default 1h).
|
|
6
|
+
*/
|
|
7
|
+
export interface ScrobbleItem {
|
|
8
|
+
readonly artist: string;
|
|
9
|
+
readonly track: string;
|
|
10
|
+
readonly uts: number;
|
|
11
|
+
}
|
|
12
|
+
export type BingesTrackKey = 'artist' | 'track';
|
|
13
|
+
export interface BingeItem {
|
|
14
|
+
readonly artist: string;
|
|
15
|
+
readonly track?: string;
|
|
16
|
+
readonly length: number;
|
|
17
|
+
readonly startUts: number;
|
|
18
|
+
readonly endUts: number;
|
|
19
|
+
readonly durationSeconds: number;
|
|
20
|
+
}
|
|
21
|
+
export interface FindBingesOptions {
|
|
22
|
+
readonly minLength?: number;
|
|
23
|
+
readonly maxGapSeconds?: number;
|
|
24
|
+
readonly trackKey?: BingesTrackKey;
|
|
25
|
+
readonly maxResults?: number;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Finds consecutive listening runs in a chronologically ordered array of scrobbles.
|
|
29
|
+
*/
|
|
30
|
+
export declare function findBinges(scrobbles: readonly ScrobbleItem[], options?: FindBingesOptions): BingeItem[];
|
|
31
|
+
//# sourceMappingURL=binges.d.ts.map
|