feat: data storage
This commit is contained in:
parent
bcc6ea0987
commit
d8137dca96
29 changed files with 428 additions and 14449 deletions
|
@ -1,4 +1,8 @@
|
|||
import { getStore } from '@netlify/blobs'
|
||||
import { createClient } from '@supabase/supabase-js';
|
||||
|
||||
const SUPABASE_URL = process.env.SUPABASE_URL
|
||||
const SUPABASE_KEY = process.env.SUPABASE_KEY
|
||||
const supabase = createClient(SUPABASE_URL, SUPABASE_KEY);
|
||||
|
||||
const emojiMap = (genre, artist) => {
|
||||
const DEFAULT = "🎧"
|
||||
|
@ -77,28 +81,43 @@ const emojiMap = (genre, artist) => {
|
|||
}
|
||||
|
||||
export default async () => {
|
||||
const scrobbles = getStore('scrobbles')
|
||||
const { data, error } = await supabase
|
||||
.from('listens')
|
||||
.select(`
|
||||
track_name,
|
||||
artist_name,
|
||||
artists (mbid, genre),
|
||||
`)
|
||||
.order('listened_at', { ascending: false })
|
||||
.limit(1);
|
||||
|
||||
const headers = {
|
||||
"Content-Type": "application/json",
|
||||
"Cache-Control": "public, s-maxage=360, stale-while-revalidate=1080",
|
||||
}
|
||||
const scrobbleData = await scrobbles.get('now-playing', { type: 'json'})
|
||||
};
|
||||
|
||||
if (!scrobbleData) return new Response(JSON.stringify({}, { headers }))
|
||||
if (error) {
|
||||
console.error('Error fetching data:', error);
|
||||
return new Response(JSON.stringify({ error: "Failed to fetch the latest track" }), { headers });
|
||||
}
|
||||
|
||||
if (data.length === 0) {
|
||||
return new Response(JSON.stringify({ message: "No recent tracks found" }), { headers });
|
||||
}
|
||||
|
||||
const scrobbleData = data[0]
|
||||
|
||||
return new Response(JSON.stringify({
|
||||
content: `${emojiMap(
|
||||
scrobbleData['genre'],
|
||||
scrobbleData['artist']
|
||||
)} ${scrobbleData['track']} by <a href="${scrobbleData['url']}">${
|
||||
scrobbleData['artist']
|
||||
}</a>`,
|
||||
}),
|
||||
{ headers }
|
||||
)
|
||||
}
|
||||
content: `${emojiMap(
|
||||
scrobbleData.artists.genre,
|
||||
scrobbleData.artist_name
|
||||
)} ${scrobbleData.track_name} by <a href="http://musicbrainz.org/artist/${scrobbleData.artists.mbid}">${
|
||||
scrobbleData.artist_name
|
||||
}</a>`,
|
||||
}), { headers });
|
||||
};
|
||||
|
||||
export const config = {
|
||||
cache: "manual",
|
||||
path: "/api/now-playing"
|
||||
}
|
||||
};
|
126
api/scrobble.js
126
api/scrobble.js
|
@ -1,90 +1,78 @@
|
|||
import { getStore } from '@netlify/blobs'
|
||||
import { createClient } from '@supabase/supabase-js'
|
||||
import { DateTime } from 'luxon'
|
||||
|
||||
const SUPABASE_URL = Netlify.env.get('SUPABASE_URL')
|
||||
const SUPABASE_KEY = Netlify.env.get('SUPABASE_API_KEY')
|
||||
const supabase = createClient(SUPABASE_URL, SUPABASE_KEY)
|
||||
|
||||
const sanitizeMediaString = (string) => string.normalize('NFD').replace(/[\u0300-\u036f\u2010—\.\?\(\)\[\]\{\}]/g, '').replace(/\.{3}/g, '')
|
||||
|
||||
const weekKey = () => {
|
||||
const currentDate = DateTime.now();
|
||||
return `${currentDate.year}-${currentDate.weekNumber}`
|
||||
}
|
||||
|
||||
const filterOldScrobbles = (scrobbles) => {
|
||||
const windowEnd = DateTime.now().minus({ days: 7 });
|
||||
return scrobbles.filter(scrobble => {
|
||||
const timestamp = DateTime.fromISO(scrobble.timestamp);
|
||||
return timestamp >= windowEnd;
|
||||
});
|
||||
}
|
||||
|
||||
export default async (request) => {
|
||||
const ACCOUNT_ID_PLEX = Netlify.env.get('ACCOUNT_ID_PLEX');
|
||||
const MUSIC_KEY = Netlify.env.get('API_KEY_LASTFM');
|
||||
const params = new URL(request['url']).searchParams
|
||||
const ACCOUNT_ID_PLEX = process.env.ACCOUNT_ID_PLEX
|
||||
const params = new URL(request.url).searchParams
|
||||
const id = params.get('id')
|
||||
|
||||
if (!id) return new Response(JSON.stringify({
|
||||
status: 'Bad request',
|
||||
}),
|
||||
{ headers: { "Content-Type": "application/json" } }
|
||||
)
|
||||
|
||||
if (id !== ACCOUNT_ID_PLEX) return new Response(JSON.stringify({
|
||||
status: 'Forbidden',
|
||||
}),
|
||||
{ headers: { "Content-Type": "application/json" } }
|
||||
)
|
||||
if (!id) return new Response(JSON.stringify({ status: 'Bad request' }), { headers: { "Content-Type": "application/json" } })
|
||||
if (id !== ACCOUNT_ID_PLEX) return new Response(JSON.stringify({ status: 'Forbidden' }), { headers: { "Content-Type": "application/json" } })
|
||||
|
||||
const data = await request.formData()
|
||||
const payload = JSON.parse(data.get('payload'))
|
||||
const artists = getStore('artists')
|
||||
const scrobbles = getStore('scrobbles')
|
||||
|
||||
if (payload?.event === 'media.scrobble') {
|
||||
const artist = payload['Metadata']['grandparentTitle']
|
||||
const album = payload['Metadata']['parentTitle']
|
||||
const track = payload['Metadata']['title']
|
||||
const trackNumber = payload['Metadata']['index']
|
||||
const timestamp = DateTime.now()
|
||||
const artistsMap = await artists.get('artists-map', { type: 'json' })
|
||||
const artistSanitizedKey = `${sanitizeMediaString(artist).replace(/\s+/g, '-').toLowerCase()}`
|
||||
const trackScrobbleData = {
|
||||
track,
|
||||
album,
|
||||
artist,
|
||||
trackNumber,
|
||||
timestamp,
|
||||
genre: artistsMap[artistSanitizedKey]?.['genre'] || ''
|
||||
const artist = payload.Metadata.grandparentTitle
|
||||
const album = payload.Metadata.parentTitle
|
||||
const track = payload.Metadata.title
|
||||
const listenedAt = DateTime.now().toISO()
|
||||
const artistKey = sanitizeMediaString(artist).replace(/\s+/g, '-').toLowerCase()
|
||||
const albumKey = `${artistKey}-${sanitizeMediaString(album).replace(/\s+/g, '-').toLowerCase()}`
|
||||
|
||||
const { data: albumData, error: albumError } = await supabase
|
||||
.from('albums')
|
||||
.select('*')
|
||||
.eq('key', albumKey)
|
||||
.single()
|
||||
|
||||
if (albumError && albumError.code === 'PGRST116') {
|
||||
const albumImageUrl = `https://coryd.dev/media/albums/${albumKey}.jpg`
|
||||
const albumMBID = null
|
||||
const { error: insertAlbumError } = await supabase.from('albums').insert([
|
||||
{
|
||||
mbid: albumMBID,
|
||||
image: albumImageUrl,
|
||||
key: albumKey,
|
||||
name: album,
|
||||
tentative: true
|
||||
}
|
||||
])
|
||||
|
||||
if (insertAlbumError) {
|
||||
console.error('Error inserting album into Supabase:', insertAlbumError.message)
|
||||
return new Response(JSON.stringify({ status: 'error', message: insertAlbumError.message }), { headers: { "Content-Type": "application/json" } })
|
||||
}
|
||||
} else if (albumError) {
|
||||
console.error('Error querying album from Supabase:', albumError.message)
|
||||
return new Response(JSON.stringify({ status: 'error', message: albumError.message }), { headers: { "Content-Type": "application/json" } })
|
||||
}
|
||||
|
||||
const scrobbleData = await scrobbles.get(`${weekKey()}`, { type: 'json'})
|
||||
const windowData = await scrobbles.get('window', { type: 'json'})
|
||||
const artistUrl = (artistsMap[artistSanitizedKey]?.['mbid'] && artistsMap[artistSanitizedKey]?.['mbid'] !== '') ? `http://musicbrainz.org/artist/${artistsMap[artistSanitizedKey]?.['mbid']}` : `https://musicbrainz.org/search?query=${artist.replace(
|
||||
/\s+/g,
|
||||
'+'
|
||||
)}&type=artist`
|
||||
const { error: listenError } = await supabase.from('listens').insert([
|
||||
{
|
||||
artist_name: artist,
|
||||
album_name: album,
|
||||
track_name: track,
|
||||
listened_at: listenedAt,
|
||||
album_key: albumKey
|
||||
}
|
||||
])
|
||||
|
||||
await scrobbles.setJSON('now-playing', {...trackScrobbleData, ...{ url: artistUrl }})
|
||||
|
||||
let scrobbleUpdate = scrobbleData
|
||||
let windowUpdate = windowData;
|
||||
|
||||
if (scrobbleUpdate?.['data']) scrobbleUpdate['data'].push(trackScrobbleData)
|
||||
if (!scrobbleUpdate?.['data']) scrobbleUpdate = { data: [trackScrobbleData] }
|
||||
if (windowData?.['data']) windowUpdate['data'].push(trackScrobbleData)
|
||||
if (!windowData?.['data']) windowUpdate = { data: [trackScrobbleData] }
|
||||
windowUpdate = { data: filterOldScrobbles(windowUpdate.data) }
|
||||
|
||||
await scrobbles.setJSON(`${weekKey()}`, scrobbleUpdate)
|
||||
await scrobbles.setJSON('window', windowUpdate)
|
||||
if (listenError) {
|
||||
console.error('Error inserting data into Supabase:', listenError.message)
|
||||
return new Response(JSON.stringify({ status: 'error', message: listenError.message }), { headers: { "Content-Type": "application/json" } })
|
||||
}
|
||||
}
|
||||
|
||||
return new Response(JSON.stringify({
|
||||
status: 'success',
|
||||
}),
|
||||
{ headers: { "Content-Type": "application/json" } }
|
||||
)
|
||||
return new Response(JSON.stringify({ status: 'success' }), { headers: { "Content-Type": "application/json" } })
|
||||
}
|
||||
|
||||
export const config = {
|
||||
path: '/api/scrobble',
|
||||
}
|
||||
}
|
||||
|
|
Reference in a new issue