This repository has been archived on 2025-03-28. You can view files and clone it, but cannot push or open issues or pull requests.
coryd.dev-eleventy/src/_data/music.js

151 lines
No EOL
4.7 KiB
JavaScript

import { createClient } from '@supabase/supabase-js'
import { DateTime } from 'luxon'
import { sanitizeMediaString, parseCountryField } from '../../config/utilities/index.js'
const SUPABASE_URL = process.env.SUPABASE_URL
const SUPABASE_KEY = process.env.SUPABASE_KEY
const supabase = createClient(SUPABASE_URL, SUPABASE_KEY)
const fetchDataForPeriod = async (startPeriod, fields, table) => {
const PAGE_SIZE = 1000
let rows = []
let rangeStart = 0
while (true) {
const { data, error } = await supabase
.from(table)
.select(fields)
.order('listened_at', { ascending: false })
.gte('listened_at', startPeriod.toSeconds())
.range(rangeStart, rangeStart + PAGE_SIZE - 1)
if (error) {
console.error(error)
break
}
rows = rows.concat(data)
if (data.length < PAGE_SIZE) break
rangeStart += PAGE_SIZE
}
return rows
}
const fetchGenreMapping = async () => {
const { data, error } = await supabase
.from('genres')
.select('id, name')
if (error) {
console.error('Error fetching genres:', error)
return {}
}
return data.reduce((acc, genre) => {
acc[genre.id] = genre.name
return acc
}, {})
}
const aggregateData = async (data, groupByField, groupByType) => {
const aggregation = {}
const genreMapping = await fetchGenreMapping()
data.forEach(item => {
const key = item[groupByField]
if (!aggregation[key]) {
if (groupByType === 'track') {
aggregation[key] = {
title: item[groupByField],
plays: 0,
mbid: item['albums']['mbid'],
url: `/music/artists/${sanitizeMediaString(item['artist_name'])}-${sanitizeMediaString(parseCountryField(item['artists']['country']))}`,
image: item['albums']?.['image'] || '',
timestamp: item['listened_at'],
type: groupByType,
genre: genreMapping[item['artists']['genres']] || ''
}
} else {
aggregation[key] = {
title: item[groupByField],
plays: 0,
mbid: item[groupByType]?.['mbid'] || '',
url: `/music/artists/${sanitizeMediaString(item['artist_name'])}-${sanitizeMediaString(parseCountryField(item['artists']['country']))}`,
image: item[groupByType]?.image || '',
type: groupByType,
genre: genreMapping[item['artists']['genres']] || ''
}
}
if (groupByType === 'track' || groupByType === 'albums') aggregation[key]['artist'] = item['artist_name']
}
aggregation[key].plays++
})
const aggregatedData = Object.values(aggregation).sort((a, b) => b.plays - a.plays)
aggregatedData.forEach((item, index) => {
item.rank = index + 1
})
return aggregatedData.filter(item => item.plays > 0)
}
const buildRecents = async (data) => {
return data.map(listen => ({
title: listen['track_name'],
artist: listen['artist_name'],
url: `/music/artists/${sanitizeMediaString(listen['artist_name'])}-${sanitizeMediaString(parseCountryField(listen['artists']['country']))}`,
timestamp: listen['listened_at'],
image: listen['albums']?.['image'] || ''
}))
}
const aggregateGenres = async (data) => {
const genreAggregation = {}
const genreMapping = await fetchGenreMapping()
data.forEach(item => {
const genre = genreMapping[item['artists']['genres']] || ''
if (!genreAggregation[genre]) genreAggregation[genre] = { genre, plays: 0 }
genreAggregation[genre]['plays']++
})
return Object.values(genreAggregation).sort((a, b) => b['plays'] - a['plays'])
}
export default async function() {
const periods = {
week: DateTime.now().minus({ days: 7 }).startOf('day'), // last week
month: DateTime.now().minus({ days: 30 }).startOf('day'), // last 30 days
threeMonth: DateTime.now().minus({ months: 3 }).startOf('day'), // last three months
}
const results = {}
const selectFields = `
track_name,
artist_name,
album_name,
album_key,
listened_at,
artists (mbid, image, genres, country),
albums (mbid, image)
`
for (const [period, startPeriod] of Object.entries(periods)) {
const periodData = await fetchDataForPeriod(startPeriod, selectFields, 'listens')
results[period] = {
artists: await aggregateData(periodData, 'artist_name', 'artists'),
albums: await aggregateData(periodData, 'album_name', 'albums'),
tracks: await aggregateData(periodData, 'track_name', 'track'),
genres: await aggregateGenres(periodData),
totalTracks: periodData?.length?.toLocaleString('en-US')
}
}
const recentData = await fetchDataForPeriod(DateTime.now().minus({ days: 7 }), selectFields, 'listens')
results['recent'] = (await buildRecents(recentData)).sort((a, b) => b.timestamp - a.timestamp)
return results
}