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_TOKEN: z.string(),
|
||||
UMAMI_SITE_ID: z.string().uuid(),
|
||||
LASTFM_TOKEN: z.string(),
|
||||
TG_API_ID: z.coerce.number(),
|
||||
TG_API_HASH: z.string(),
|
||||
TG_BOT_TOKEN: z.string(),
|
||||
|
|
|
@ -2,7 +2,7 @@ import { assertMatches } from '@fuman/utils'
|
|||
|
||||
import { bskyLastSeen } from './bsky.ts'
|
||||
import { githubLastSeen } from './github'
|
||||
import { lastfm } from './lastfm'
|
||||
import { lastfm } from './listenbrainz.ts'
|
||||
import { shikimoriLastSeen } from './shikimori'
|
||||
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