chore: lastfm -> listenbrainz
This commit is contained in:
parent
e44a9724ff
commit
8c46947144
4 changed files with 72 additions and 73 deletions
|
@ -9,7 +9,6 @@ export const env = zodValidateSync(
|
||||||
UMAMI_HOST: z.string().url(),
|
UMAMI_HOST: z.string().url(),
|
||||||
UMAMI_TOKEN: z.string(),
|
UMAMI_TOKEN: z.string(),
|
||||||
UMAMI_SITE_ID: z.string().uuid(),
|
UMAMI_SITE_ID: z.string().uuid(),
|
||||||
LASTFM_TOKEN: z.string(),
|
|
||||||
TG_API_ID: z.coerce.number(),
|
TG_API_ID: z.coerce.number(),
|
||||||
TG_API_HASH: z.string(),
|
TG_API_HASH: z.string(),
|
||||||
TG_BOT_TOKEN: z.string(),
|
TG_BOT_TOKEN: z.string(),
|
||||||
|
|
|
@ -2,7 +2,7 @@ import { assertMatches } from '@fuman/utils'
|
||||||
|
|
||||||
import { bskyLastSeen } from './bsky.ts'
|
import { bskyLastSeen } from './bsky.ts'
|
||||||
import { githubLastSeen } from './github'
|
import { githubLastSeen } from './github'
|
||||||
import { lastfm } from './lastfm'
|
import { lastfm } from './listenbrainz.ts'
|
||||||
import { shikimoriLastSeen } from './shikimori'
|
import { shikimoriLastSeen } from './shikimori'
|
||||||
import { forgejoLastSeen } from './forgejo.ts'
|
import { forgejoLastSeen } from './forgejo.ts'
|
||||||
|
|
||||||
|
|
|
@ -1,71 +0,0 @@
|
||||||
import { z } from 'zod'
|
|
||||||
import { AsyncResource } from '@fuman/utils'
|
|
||||||
|
|
||||||
import { zodValidate } from '~/utils/zod'
|
|
||||||
import { env } from '~/backend/env'
|
|
||||||
import { ffetch } from '../../utils/fetch.ts'
|
|
||||||
|
|
||||||
import type { LastSeenItem } from './index.ts'
|
|
||||||
|
|
||||||
const LASTFM_TTL = 1000 * 60 * 5 // 5 minutes
|
|
||||||
const LASTFM_STALE_TTL = 1000 * 60 * 60 // 1 hour
|
|
||||||
const LASTFM_USERNAME = 'teidesu'
|
|
||||||
const LASTFM_TOKEN = env.LASTFM_TOKEN
|
|
||||||
|
|
||||||
const LastfmTrack = z.object({
|
|
||||||
'artist': z.object({ 'mbid': z.string(), '#text': z.string() }),
|
|
||||||
'name': z.string(),
|
|
||||||
'url': z.string(),
|
|
||||||
'date': z.object({ uts: z.string() }).optional(),
|
|
||||||
'@attr': z.object({
|
|
||||||
nowplaying: z.literal('true'),
|
|
||||||
}).partial().optional(),
|
|
||||||
})
|
|
||||||
|
|
||||||
const ResponseSchema = z.object({
|
|
||||||
recenttracks: z.object({
|
|
||||||
'track': z.array(LastfmTrack),
|
|
||||||
'@attr': z.object({
|
|
||||||
user: z.string(),
|
|
||||||
totalPages: z.string(),
|
|
||||||
page: z.string(),
|
|
||||||
perPage: z.string(),
|
|
||||||
total: z.string(),
|
|
||||||
}),
|
|
||||||
}),
|
|
||||||
})
|
|
||||||
|
|
||||||
export const lastfm = new AsyncResource<LastSeenItem | null>({
|
|
||||||
async fetcher({ current }) {
|
|
||||||
const res = await ffetch('https://ws.audioscrobbler.com/2.0/', {
|
|
||||||
query: {
|
|
||||||
method: 'user.getrecenttracks',
|
|
||||||
user: LASTFM_USERNAME,
|
|
||||||
api_key: LASTFM_TOKEN,
|
|
||||||
format: 'json',
|
|
||||||
limit: '1',
|
|
||||||
from: current ? Math.floor(current.time / 1000) : undefined,
|
|
||||||
},
|
|
||||||
}).parsedJson(ResponseSchema)
|
|
||||||
|
|
||||||
const track = res.recenttracks.track[0]
|
|
||||||
if (!track.date && track['@attr']?.nowplaying) {
|
|
||||||
track.date = { uts: Math.floor(Date.now() / 1000).toString() }
|
|
||||||
} else if (!track.date) {
|
|
||||||
throw new Error('no track found')
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
data: {
|
|
||||||
source: 'last.fm',
|
|
||||||
sourceLink: 'https://last.fm/user/teidesu',
|
|
||||||
time: Number(track.date!.uts) * 1000,
|
|
||||||
text: `${track.name} – ${track.artist['#text']}`,
|
|
||||||
link: track.url,
|
|
||||||
},
|
|
||||||
expiresIn: LASTFM_TTL,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
swr: true,
|
|
||||||
swrValidator: ({ currentExpiresAt }) => Date.now() - currentExpiresAt < LASTFM_STALE_TTL,
|
|
||||||
})
|
|
71
src/backend/service/last-seen/listenbrainz.ts
Normal file
71
src/backend/service/last-seen/listenbrainz.ts
Normal file
|
@ -0,0 +1,71 @@
|
||||||
|
import { z } from 'zod'
|
||||||
|
import { AsyncResource } from '@fuman/utils'
|
||||||
|
|
||||||
|
import { ffetch } from '../../utils/fetch.ts'
|
||||||
|
|
||||||
|
import type { LastSeenItem } from './index.ts'
|
||||||
|
|
||||||
|
const LB_TTL = 1000 * 60 * 5 // 5 minutes
|
||||||
|
const LB_STALE_TTL = 1000 * 60 * 60 // 1 hour
|
||||||
|
const LB_USERNAME = 'teidumb'
|
||||||
|
|
||||||
|
const LbListen = z.object({
|
||||||
|
listened_at: z.number(),
|
||||||
|
track_metadata: z.object({
|
||||||
|
artist_name: z.string(),
|
||||||
|
track_name: z.string(),
|
||||||
|
additional_info: z.object({
|
||||||
|
origin_url: z.string().optional(),
|
||||||
|
}).optional(),
|
||||||
|
mbid_mapping: z.object({
|
||||||
|
recording_mbid: z.string().optional(),
|
||||||
|
}).optional(),
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
|
||||||
|
const ResponseSchema = z.object({
|
||||||
|
payload: z.object({
|
||||||
|
listens: z.array(LbListen),
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
|
||||||
|
export const lastfm = new AsyncResource<LastSeenItem | null>({
|
||||||
|
async fetcher({ current }) {
|
||||||
|
const res = await ffetch(`https://api.listenbrainz.org/1/user/${LB_USERNAME}/listens`, {
|
||||||
|
query: {
|
||||||
|
count: 1,
|
||||||
|
min_ts: current ? Math.floor(current.time / 1000) : '',
|
||||||
|
},
|
||||||
|
}).parsedJson(ResponseSchema)
|
||||||
|
|
||||||
|
if (!res.payload.listens.length) {
|
||||||
|
return {
|
||||||
|
data: current,
|
||||||
|
expiresIn: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const listen = res.payload.listens[0]
|
||||||
|
|
||||||
|
let url: string | undefined
|
||||||
|
if (listen.track_metadata.mbid_mapping?.recording_mbid) {
|
||||||
|
url = `https://musicbrainz.org/recording/${listen.track_metadata.mbid_mapping.recording_mbid}`
|
||||||
|
} else if (listen.track_metadata.additional_info?.origin_url) {
|
||||||
|
url = listen.track_metadata.additional_info.origin_url
|
||||||
|
} else {
|
||||||
|
url = 'https://listenbrainz.org/user/teidumb/'
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
data: {
|
||||||
|
source: 'listenbrainz',
|
||||||
|
sourceLink: 'https://listenbrainz.org/user/teidumb/',
|
||||||
|
time: listen.listened_at * 1000,
|
||||||
|
text: `${listen.track_metadata.track_name} – ${listen.track_metadata.artist_name}`,
|
||||||
|
link: url,
|
||||||
|
},
|
||||||
|
expiresIn: LB_TTL,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
swr: true,
|
||||||
|
swrValidator: ({ currentExpiresAt }) => Date.now() - currentExpiresAt < LB_STALE_TTL,
|
||||||
|
})
|
Loading…
Reference in a new issue