Fix #45 implement crude checking if a song already exists in tidal
This commit is contained in:
@ -46,6 +46,7 @@ type SongRow = {
|
|||||||
thumbnailUrl?: string;
|
thumbnailUrl?: string;
|
||||||
thumbnailWidth?: number;
|
thumbnailWidth?: number;
|
||||||
thumbnailHeight?: number;
|
thumbnailHeight?: number;
|
||||||
|
tidalId: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
type AccountAvatarRow = {
|
type AccountAvatarRow = {
|
||||||
@ -333,6 +334,12 @@ function getMigrations(): Migration[] {
|
|||||||
statement: `
|
statement: `
|
||||||
ALTER TABLE songs ADD COLUMN spotifyUrl TEXT NULL;
|
ALTER TABLE songs ADD COLUMN spotifyUrl TEXT NULL;
|
||||||
ALTER TABLE songs ADD COLUMN spotifyUri TEXT NULL;`
|
ALTER TABLE songs ADD COLUMN spotifyUri TEXT NULL;`
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 9,
|
||||||
|
name: 'song tidal id',
|
||||||
|
statement: `
|
||||||
|
ALTER TABLE songs ADD COLUMN tidalId TEXT NULL;`
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
@ -464,8 +471,9 @@ function saveSongInfoData(postUrl: string, songs: SongInfo[]): Promise<void> {
|
|||||||
for (const song of songs) {
|
for (const song of songs) {
|
||||||
db.run(
|
db.run(
|
||||||
`
|
`
|
||||||
INSERT INTO songs (postedUrl, overviewUrl, type, youtubeUrl, spotifyUrl, spotifyUri, title, artistName, thumbnailUrl, post_url, thumbnailWidth, thumbnailHeight)
|
INSERT INTO songs (postedUrl, overviewUrl, type, youtubeUrl, spotifyUrl, spotifyUri, tidalId,
|
||||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
title, artistName, thumbnailUrl, post_url, thumbnailWidth, thumbnailHeight)
|
||||||
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||||
`,
|
`,
|
||||||
[
|
[
|
||||||
song.postedUrl,
|
song.postedUrl,
|
||||||
@ -474,6 +482,7 @@ function saveSongInfoData(postUrl: string, songs: SongInfo[]): Promise<void> {
|
|||||||
song.youtubeUrl,
|
song.youtubeUrl,
|
||||||
song.spotifyUrl,
|
song.spotifyUrl,
|
||||||
song.spotifyUri,
|
song.spotifyUri,
|
||||||
|
song.tidalUri,
|
||||||
song.title,
|
song.title,
|
||||||
song.artistName,
|
song.artistName,
|
||||||
song.thumbnailUrl,
|
song.thumbnailUrl,
|
||||||
@ -574,7 +583,7 @@ function getSongData(postIdsParams: string, postIds: string[]): Promise<Map<stri
|
|||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
db.all(
|
db.all(
|
||||||
`SELECT post_url, songs.postedUrl, songs.overviewUrl, songs.type, songs.youtubeUrl, songs.spotifyUri, songs.spotifyUri,
|
`SELECT post_url, songs.postedUrl, songs.overviewUrl, songs.type, songs.youtubeUrl, songs.spotifyUri, songs.spotifyUri,
|
||||||
songs.title, songs.artistName, songs.thumbnailUrl, songs.post_url, songs.thumbnailWidth, songs.thumbnailHeight
|
songs.tidalId, songs.title, songs.artistName, songs.thumbnailUrl, songs.post_url, songs.thumbnailWidth, songs.thumbnailHeight
|
||||||
FROM songs
|
FROM songs
|
||||||
WHERE post_url IN (${postIdsParams});`,
|
WHERE post_url IN (${postIdsParams});`,
|
||||||
postIds,
|
postIds,
|
||||||
@ -591,6 +600,7 @@ function getSongData(postIdsParams: string, postIds: string[]): Promise<Map<stri
|
|||||||
youtubeUrl: item.youtubeUrl,
|
youtubeUrl: item.youtubeUrl,
|
||||||
spotifyUrl: item.spotifyUrl,
|
spotifyUrl: item.spotifyUrl,
|
||||||
spotifyUri: item.spotifyUri,
|
spotifyUri: item.spotifyUri,
|
||||||
|
tidalUri: item.tidalId,
|
||||||
type: item.type,
|
type: item.type,
|
||||||
title: item.title,
|
title: item.title,
|
||||||
artistName: item.artistName,
|
artistName: item.artistName,
|
||||||
@ -683,6 +693,38 @@ function getSongThumbnailData(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function doesTidalSongExist(song: SongInfo): Promise<boolean> {
|
||||||
|
if (!databaseReady) {
|
||||||
|
await waitReady();
|
||||||
|
}
|
||||||
|
if (!song.tidalUri) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const sql = `SELECT songs.title, songs.artistName, songs.tidalId
|
||||||
|
FROM songs
|
||||||
|
WHERE songs.tidalId = $tidalId
|
||||||
|
LIMIT $limit`;
|
||||||
|
|
||||||
|
// If only one exists: This is the one that has just been added
|
||||||
|
// If more exits: It has been added before
|
||||||
|
const params = {
|
||||||
|
$tidalId: song.tidalUri,
|
||||||
|
$limit: 2
|
||||||
|
};
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
db.all(sql, params, (err, rows) => {
|
||||||
|
if (err != null) {
|
||||||
|
logger.error('Error loading songs', err);
|
||||||
|
reject(err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
logger.debug('doesTidalSongExist', song.tidalUri, rows, rows.length > 1);
|
||||||
|
resolve(rows.length > 1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
export async function getPosts(
|
export async function getPosts(
|
||||||
since: string | null,
|
since: string | null,
|
||||||
before: string | null,
|
before: string | null,
|
||||||
|
@ -6,6 +6,7 @@ import { createHash } from 'crypto';
|
|||||||
import { OauthPlaylistAdder } from './oauthPlaylistAdder';
|
import { OauthPlaylistAdder } from './oauthPlaylistAdder';
|
||||||
import type { PlaylistAdder } from './playlistAdder';
|
import type { PlaylistAdder } from './playlistAdder';
|
||||||
import type { TidalAddToPlaylistResponse } from './tidalResponse';
|
import type { TidalAddToPlaylistResponse } from './tidalResponse';
|
||||||
|
import { doesTidalSongExist } from '$lib/server/db';
|
||||||
|
|
||||||
export class TidalPlaylistAdder extends OauthPlaylistAdder implements PlaylistAdder {
|
export class TidalPlaylistAdder extends OauthPlaylistAdder implements PlaylistAdder {
|
||||||
private static code_verifier?: string;
|
private static code_verifier?: string;
|
||||||
@ -117,6 +118,16 @@ export class TidalPlaylistAdder extends OauthPlaylistAdder implements PlaylistAd
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const alreadyExists = await doesTidalSongExist(song);
|
||||||
|
try {
|
||||||
|
if (alreadyExists) {
|
||||||
|
this.logger.info('Skip adding song to playlist, has already been added', song);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} catch (dbe) {
|
||||||
|
this.logger.error('Could not check for tidal dupes', dbe);
|
||||||
|
}
|
||||||
|
|
||||||
// This would be API v2, but that's still in beta and only allows adding an item *before* another one
|
// This would be API v2, but that's still in beta and only allows adding an item *before* another one
|
||||||
const options: RequestInit = {
|
const options: RequestInit = {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
@ -140,41 +151,6 @@ export class TidalPlaylistAdder extends OauthPlaylistAdder implements PlaylistAd
|
|||||||
const apiUrl = new URL(`${this.apiBase}/playlists/${TIDAL_PLAYLIST_ID}/relationships/items`);
|
const apiUrl = new URL(`${this.apiBase}/playlists/${TIDAL_PLAYLIST_ID}/relationships/items`);
|
||||||
const request = new Request(apiUrl, options);
|
const request = new Request(apiUrl, options);
|
||||||
|
|
||||||
// This would be API v1 (or api v2, but *not* the OpenAPI v2),
|
|
||||||
// but that requires r_usr and w_usr permission scopes which are impossible to request
|
|
||||||
/*
|
|
||||||
const options: RequestInit = {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
Authorization: `${token.token_type} ${token.access_token}`,
|
|
||||||
'Content-Type': 'application/json'
|
|
||||||
},
|
|
||||||
body: JSON.stringify({
|
|
||||||
onArtifactNotFound: 'SKIP',
|
|
||||||
trackIds: song.tidalUri,
|
|
||||||
//toIndex: -1
|
|
||||||
onDupes: 'SKIP'
|
|
||||||
})
|
|
||||||
};
|
|
||||||
const apiUrl = new URL(`${this.apiBase}/playlists/${TIDAL_PLAYLIST_ID}/items`);
|
|
||||||
try {
|
|
||||||
const r = await fetch(new URL(`${this.apiBase}/playlists/${TIDAL_PLAYLIST_ID}`), {
|
|
||||||
headers: {
|
|
||||||
Authorization: `${token.token_type} ${token.access_token}`
|
|
||||||
}
|
|
||||||
});
|
|
||||||
const txt = await r.text();
|
|
||||||
this.logger.debug('playlist', r.status, txt);
|
|
||||||
const rj = JSON.parse(txt);
|
|
||||||
this.logger.debug('playlist', rj);
|
|
||||||
} catch (e) {
|
|
||||||
this.logger.error('playlist fetch failed', e);
|
|
||||||
}
|
|
||||||
|
|
||||||
const request = new Request(apiUrl, options);
|
|
||||||
this.logger.debug('Adding to playlist request', request);
|
|
||||||
*/
|
|
||||||
|
|
||||||
let resp: Response | null = null;
|
let resp: Response | null = null;
|
||||||
let respTxt: string | null = null;
|
let respTxt: string | null = null;
|
||||||
try {
|
try {
|
||||||
|
@ -200,10 +200,14 @@ export class TimelineReader {
|
|||||||
if (e instanceof Error && e.cause === 429) {
|
if (e instanceof Error && e.cause === 429) {
|
||||||
this.logger.warn('song.link rate limit reached. Trying again in 10 seconds');
|
this.logger.warn('song.link rate limit reached. Trying again in 10 seconds');
|
||||||
await sleep(10_000);
|
await sleep(10_000);
|
||||||
return await this.getSongInfo(url, remainingTries - 1);
|
} else {
|
||||||
|
this.logger.error(
|
||||||
|
`Failed to load ${url} info from song.link. Trying again in 3 seconds`,
|
||||||
|
e
|
||||||
|
);
|
||||||
|
await sleep(3_000);
|
||||||
}
|
}
|
||||||
this.logger.error(`Failed to load ${url} info from song.link`, e);
|
return await this.getSongInfo(url, remainingTries - 1);
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user