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/workers/scrobble/index.js

221 lines
No EOL
7.1 KiB
JavaScript

import { createClient } from '@supabase/supabase-js'
import { DateTime } from 'luxon'
import slugify from 'slugify'
const sanitizeMediaString = (str) => {
const sanitizedString = str
.normalize('NFD')
.replace(/[\u0300-\u036f\u2010\-\.\?\(\)\[\]\{\}]/g, '')
.replace(/\.{3}/g, '')
return slugify(sanitizedString, {
replacement: '-',
remove: /[#,&,+()$~%.'":*?<>{}]/g,
lower: true,
})
}
const sendEmail = async (subject, text, authHeader) => {
const emailData = new URLSearchParams({
from: 'hi@admin.coryd.dev',
to: 'hi@coryd.dev',
subject: subject,
text: text,
}).toString()
try {
const response = await fetch('https://api.forwardemail.net/v1/emails', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'Authorization': authHeader,
},
body: emailData,
})
const responseText = await response.text()
if (!response.ok) {
console.error('Email API response error:', response.status, responseText)
throw new Error(`Failed to send email: ${responseText}`)
} else {
console.log('Email sent successfully')
}
} catch (error) {
console.error('Error sending email:', error.message)
}
}
export default {
async fetch(request, env) {
const SUPABASE_URL = env.SUPABASE_URL
const SUPABASE_KEY = env.SUPABASE_KEY
const FORWARDEMAIL_API_KEY = env.FORWARDEMAIL_API_KEY
const ACCOUNT_ID_PLEX = env.ACCOUNT_ID_PLEX
const supabase = createClient(SUPABASE_URL, SUPABASE_KEY)
const authHeader = 'Basic ' + btoa(`${FORWARDEMAIL_API_KEY}:`)
const url = new URL(request.url)
const params = 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' },
})
const contentType = request.headers.get('Content-Type') || ''
if (!contentType.includes('multipart/form-data')) return new Response(
JSON.stringify({
status: 'Bad request',
message: 'Invalid Content-Type. Expected multipart/form-data.',
}),
{ headers: { 'Content-Type': 'application/json' } }
)
try {
const data = await request.formData()
const payload = JSON.parse(data.get('payload'))
if (payload?.event === 'media.scrobble') {
const artist = payload['Metadata']['grandparentTitle']
const album = payload['Metadata']['parentTitle']
const track = payload['Metadata']['title']
const listenedAt = Math.floor(DateTime.now().toSeconds())
const artistKey = sanitizeMediaString(artist)
const albumKey = `${artistKey}-${sanitizeMediaString(album)}`
let { data: artistData, error: artistError } = await supabase
.from('artists')
.select('*')
.ilike('name_string', artist)
.single()
if (artistError && artistError.code === 'PGRST116') {
const { error: insertArtistError } = await supabase
.from('artists')
.insert([
{
mbid: null,
art: '4cef75db-831f-4f5d-9333-79eaa5bb55ee',
name: artist,
tentative: true,
total_plays: 0,
},
])
if (insertArtistError) {
console.error('Error inserting artist:', insertArtistError.message)
return new Response(
JSON.stringify({
status: 'error',
message: insertArtistError.message,
}),
{ headers: { 'Content-Type': 'application/json' } }
)
}
await sendEmail(
'New tentative artist record',
`A new tentative artist record was inserted:\n\nArtist: ${artist}\nKey: ${artistKey}`,
authHeader
)
({ data: artistData, error: artistError } = await supabase
.from('artists')
.select('*')
.ilike('name_string', artist)
.single())
} else if (artistError) {
console.error('Error fetching artist:', artistError.message)
return new Response(
JSON.stringify({ status: 'error', message: artistError.message }),
{ headers: { 'Content-Type': 'application/json' } }
)
}
let { data: albumData, error: albumError } = await supabase
.from('albums')
.select('*')
.ilike('key', albumKey)
.single()
if (albumError && albumError.code === 'PGRST116') {
const { error: insertAlbumError } = await supabase
.from('albums')
.insert([
{
mbid: null,
art: '4cef75db-831f-4f5d-9333-79eaa5bb55ee',
key: albumKey,
name: album,
tentative: true,
total_plays: 0,
},
])
if (insertAlbumError) {
console.error('Error inserting album:', insertAlbumError.message)
return new Response(
JSON.stringify({
status: 'error',
message: insertAlbumError.message,
}),
{ headers: { 'Content-Type': 'application/json' } }
)
}
await sendEmail(
'New tentative album record',
`A new tentative album record was inserted:\n\nAlbum: ${album}\nKey: ${albumKey}`,
authHeader
)
({ data: albumData, error: albumError } = await supabase
.from('albums')
.select('*')
.ilike('key', albumKey)
.single())
} else if (albumError) {
console.error('Error fetching album:', albumError.message)
return new Response(
JSON.stringify({ status: 'error', message: albumError.message }),
{ headers: { 'Content-Type': 'application/json' } }
)
}
const { error: listenError } = await supabase.from('listens').insert([
{
artist_name: artist,
album_name: album,
track_name: track,
listened_at: listenedAt,
album_key: albumKey,
},
])
if (listenError) {
console.error('Error inserting listen:', listenError.message)
return new Response(
JSON.stringify({ status: 'error', message: listenError.message }),
{ headers: { 'Content-Type': 'application/json' } }
)
}
console.log('Listen record inserted successfully')
}
return new Response(JSON.stringify({ status: 'success' }), {
headers: { 'Content-Type': 'application/json' },
})
} catch (e) {
console.error('Error processing request:', e.message)
return new Response(
JSON.stringify({ status: 'error', message: e.message }),
{ headers: { 'Content-Type': 'application/json' } }
)
}
},
}