239 lines
No EOL
7.7 KiB
JavaScript
239 lines
No EOL
7.7 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, maxRetries = 3) => {
|
|
const emailData = new URLSearchParams({
|
|
from: '<coryd.dev> hi@admin.coryd.dev',
|
|
to: 'hi@coryd.dev',
|
|
subject: subject,
|
|
text: text,
|
|
}).toString()
|
|
|
|
let attempt = 0
|
|
let success = false
|
|
|
|
while (attempt < maxRetries && !success) {
|
|
attempt++
|
|
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,
|
|
})
|
|
|
|
if (!response.ok) {
|
|
const responseText = await response.text()
|
|
console.error(`Attempt ${attempt}: Email API response error:`, response.status, responseText)
|
|
throw new Error(`Failed to send email: ${responseText}`)
|
|
}
|
|
|
|
console.log('Email sent successfully on attempt', attempt)
|
|
success = true
|
|
} catch (error) {
|
|
console.error(`Attempt ${attempt}: Error sending email:`, error.message)
|
|
|
|
if (attempt < maxRetries) {
|
|
console.log(`Retrying email send (attempt ${attempt + 1}/${maxRetries})...`)
|
|
} else {
|
|
console.error('All attempts to send email failed.')
|
|
}
|
|
}
|
|
}
|
|
|
|
return success
|
|
}
|
|
|
|
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 artistName = payload['Metadata']['grandparentTitle']
|
|
const albumName = payload['Metadata']['parentTitle']
|
|
const trackName = payload['Metadata']['title']
|
|
const listenedAt = Math.floor(DateTime.now().toSeconds())
|
|
const artistKey = sanitizeMediaString(artistName)
|
|
const albumKey = `${artistKey}-${sanitizeMediaString(albumName)}`
|
|
|
|
let { data: artistData, error: artistError } = await supabase
|
|
.from('artists')
|
|
.select('*')
|
|
.ilike('name_string', artistName)
|
|
.single()
|
|
|
|
if (artistError && artistError.code === 'PGRST116') {
|
|
const { error: insertArtistError } = await supabase
|
|
.from('artists')
|
|
.insert([
|
|
{
|
|
mbid: null,
|
|
art: '4cef75db-831f-4f5d-9333-79eaa5bb55ee',
|
|
name: artistName,
|
|
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: ${artistName}\nKey: ${artistKey}`,
|
|
authHeader
|
|
)
|
|
|
|
;({ data: artistData, error: artistError } = await supabase
|
|
.from('artists')
|
|
.select('*')
|
|
.ilike('name_string', artistName)
|
|
.single())
|
|
}
|
|
|
|
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: albumName,
|
|
tentative: true,
|
|
total_plays: 0,
|
|
artist: artistData.id,
|
|
},
|
|
])
|
|
|
|
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: ${albumName}\nKey: ${albumKey}\nArtist: ${artistName}`,
|
|
authHeader
|
|
)
|
|
|
|
;({ data: albumData, error: albumError } = await supabase
|
|
.from('albums')
|
|
.select('*')
|
|
.ilike('key', albumKey)
|
|
.single())
|
|
}
|
|
|
|
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: artistData['name_string'] || artistName,
|
|
album_name: albumData['name'] || albumName,
|
|
track_name: trackName,
|
|
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' } }
|
|
)
|
|
}
|
|
},
|
|
} |