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
Cory Dransfeldt 88a4ec4acd
# This is a combination of 3 commits.
# This is the 1st commit message:

fix: redirects + update root cdn url

# This is the commit message #2:

chore: workflow

# This is the commit message #3:

chore: naming
2024-06-09 11:16:24 -07:00

129 lines
No EOL
4.8 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,
});
};
export default {
async fetch(request, env) {
const SUPABASE_URL = env.SUPABASE_URL;
const SUPABASE_KEY = env.SUPABASE_KEY;
const ACCOUNT_ID_PLEX = env.ACCOUNT_ID_PLEX;
const supabase = createClient(SUPABASE_URL, SUPABASE_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,
image: `/artists/${artistKey}.jpg`,
key: artistKey,
name: artist,
tentative: true
}
]);
if (insertArtistError) {
return new Response(JSON.stringify({ status: 'error', message: insertArtistError.message }), { headers: { "Content-Type": "application/json" } });
}
({ data: artistData, error: artistError } = await supabase
.from('artists')
.select('*')
.ilike('name_string', artist)
.single());
} else if (artistError) {
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,
image: `/albums/${albumKey}.jpg`,
key: albumKey,
name: album,
tentative: true
}
]);
if (insertAlbumError) {
return new Response(JSON.stringify({ status: 'error', message: insertAlbumError.message }), { headers: { "Content-Type": "application/json" } });
}
({ data: albumData, error: albumError } = await supabase
.from('albums')
.select('*')
.ilike('key', albumKey)
.single());
} else if (albumError) {
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,
album_name: albumData.name,
track_name: track,
listened_at: listenedAt,
album_key: albumKey
}
]);
if (listenError) {
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" } });
} catch (e) {
return new Response(JSON.stringify({ status: 'error', message: e.message }), { headers: { "Content-Type": "application/json" } });
}
}
};