spotify/server/dist/lib/sync.js
2025-10-16 13:07:44 +02:00

83 lines
4.3 KiB
JavaScript

import axios from 'axios';
import { db } from './db.js';
import { ensureValidAccessToken } from './spotify.js';
export async function syncUserData(uid) {
const accessToken = await ensureValidAccessToken(uid);
const headers = { Authorization: `Bearer ${accessToken}` };
const [recentResp, topTracksShort, topTracksMed, topArtistsShort, topArtistsMed] = await Promise.all([
axios.get('https://api.spotify.com/v1/me/player/recently-played?limit=20', { headers }),
axios.get('https://api.spotify.com/v1/me/top/tracks?time_range=short_term&limit=20', { headers }),
axios.get('https://api.spotify.com/v1/me/top/tracks?time_range=medium_term&limit=20', { headers }),
axios.get('https://api.spotify.com/v1/me/top/artists?time_range=short_term&limit=20', { headers }),
axios.get('https://api.spotify.com/v1/me/top/artists?time_range=medium_term&limit=20', { headers })
]);
const trx = db.db.transaction(() => {
const rpStmt = db.db.prepare('INSERT OR REPLACE INTO recently_played (id, user_id, played_at, track_json) VALUES (?, ?, ?, ?)');
for (const item of recentResp.data.items) {
rpStmt.run(item.played_at + ':' + item.track.id, uid, new Date(item.played_at).getTime(), JSON.stringify(item.track));
}
const ttDel = db.db.prepare('DELETE FROM top_tracks WHERE user_id=? AND time_range=?');
const ttIns = db.db.prepare('INSERT INTO top_tracks (user_id, time_range, rank, track_json) VALUES (?, ?, ?, ?)');
for (const [range, payload] of [
['short_term', topTracksShort.data],
['medium_term', topTracksMed.data],
]) {
ttDel.run(uid, range);
payload.items.forEach((t, idx) => ttIns.run(uid, range, idx + 1, JSON.stringify(t)));
}
const taDel = db.db.prepare('DELETE FROM top_artists WHERE user_id=? AND time_range=?');
const taIns = db.db.prepare('INSERT INTO top_artists (user_id, time_range, rank, artist_json) VALUES (?, ?, ?, ?)');
for (const [range, payload] of [
['short_term', topArtistsShort.data],
['medium_term', topArtistsMed.data],
]) {
taDel.run(uid, range);
payload.items.forEach((a, idx) => taIns.run(uid, range, idx + 1, JSON.stringify(a)));
}
});
trx();
db.db.prepare('UPDATE users SET last_synced_at=?, updated_at=? WHERE id=?').run(Date.now(), Date.now(), uid);
}
// Capture the user's current playback and persist as a recent play if changed
export async function captureNowPlaying(uid) {
try {
const accessToken = await ensureValidAccessToken(uid);
const headers = { Authorization: `Bearer ${accessToken}` };
const resp = await axios.get('https://api.spotify.com/v1/me/player/currently-playing?additional_types=track', {
headers,
validateStatus: () => true,
});
if (resp.status !== 200)
return; // nothing to record
const data = resp.data;
if (!data?.is_playing || !data?.item)
return;
const track = data.item;
const trackId = track?.id;
if (!trackId)
return;
// Deduplicate: update existing row if same track found recently
const recentRows = db.db.prepare('SELECT id, track_json, played_at FROM recently_played WHERE user_id=? ORDER BY played_at DESC LIMIT 50').all(uid);
for (const row of recentRows) {
try {
const rowTrack = JSON.parse(row.track_json);
if (rowTrack?.id === trackId) {
// If the last record for this track is within 15 minutes, treat as the same session; update timestamp
if (Date.now() - row.played_at < 15 * 60 * 1000) {
db.db.prepare('UPDATE recently_played SET played_at = ?, track_json = ? WHERE id = ?').run(Date.now(), JSON.stringify(track), row.id);
return;
}
break;
}
}
catch { }
}
db.db
.prepare('INSERT INTO recently_played (user_id, played_at, track_json) VALUES (?, ?, ?)')
.run(uid, Date.now(), JSON.stringify(track));
}
catch {
// ignore errors while capturing now playing to keep background loop robust
}
}