const SpotifyWebApi = require('spotify-web-api-node'); const NodeID3 = require('node-id3') const fs = require('fs').promises; const ReadLine = require('readline'); const path = require('path'); /* { "clientId": "", "clientSecret": "", "redirectUri": "https://lunasqu.ee/callback" } */ const spotifyApi = new SpotifyWebApi(require('./credentials.json')); const audPath = process.argv[2]; if (!audPath) { throw new Error('No directory specified'); } // Create the authorization URL const rl = ReadLine.createInterface({ input: process.stdin, output: process.stdout, }); function ask(msg) { return new Promise((resolve) => rl.question(msg, resolve)); } async function setupSpotify() { let credentials = { token: null, expires: null, refresh: null, }; try { credentials = JSON.parse(await fs.readFile('.token.json')); } catch (e) {} if (!credentials.token) { const authorizeURL = spotifyApi.createAuthorizeURL(['playlist-modify-private'], 'FPEPFERHR234234trGEWErjrjsdw3666sf06'); console.log('Authorize the application'); console.log(authorizeURL); const code = await ask('Enter code []> '); if (!code) { process.exit(1); } const data = await spotifyApi.authorizationCodeGrant(code); console.log('The access token has been received.'); // Set the access token on the API object to use it in later calls spotifyApi.setAccessToken(data.body['access_token']); spotifyApi.setRefreshToken(data.body['refresh_token']); credentials.token = data.body['access_token']; credentials.expires = Math.floor(Date.now() / 1000) + data.body['expires_in']; credentials.refresh = data.body['refresh_token']; await fs.writeFile('.token.json', JSON.stringify(credentials, undefined, 2)); } else { console.log('The access token is present'); spotifyApi.setAccessToken(credentials.token); spotifyApi.setRefreshToken(credentials.refresh); } if ((credentials.expires * 1000) < Date.now()) { console.log('Refreshing access token'); try { const refresh = await spotifyApi.refreshAccessToken(); credentials.token = refresh.body['access_token']; credentials.expires = Math.floor(Date.now() / 1000) + refresh.body['expires_in']; spotifyApi.setAccessToken(credentials.token); spotifyApi.setRefreshToken(credentials.refresh); } catch (e) { console.error(e); console.log('need to reauthorize'); credentials.token = null; } await fs.writeFile('.token.json', JSON.stringify(credentials, undefined, 2)); if (!credentials.token) { return setupSpotify(); } } console.log('the token is good until', new Date(credentials.expires * 1000).toString()); } async function readTags(file) { return new Promise((resolve) => { NodeID3.read(file, {noRaw: true, exclude: ['image']}, function(err, tags) { if (err) return resolve(null) if (tags.image) tags.image = null; if (tags.private) tags.private = null; resolve(tags); }); }); } async function readAudioFiles() { let fileAndMeta = []; const contents = await fs.readdir(audPath); for (const file of contents) { const fullp = path.join(audPath, file); const tags = await readTags(fullp); fileAndMeta.push({ path: fullp, tags: tags, }); } fileAndMeta = fileAndMeta.filter((item) => item.tags && item.tags.artist && item.tags.title); await fs.writeFile('file-meta-cache.json', JSON.stringify(fileAndMeta, undefined, 2)); return fileAndMeta; } async function delay(time) { return new Promise((resolve) => setTimeout(resolve, time)); } let requestDelay = 100; async function searchTrack(query) { console.log('Searching for', query); let returned try { returned = await spotifyApi.searchTracks(query, { limit: 5, market: 'EE' }); } catch (e) { console.error(e); console.log('We may have hit a rate limit'); console.log('Waiting 30 seconds for the rate limit window'); requestDelay = 1000; await delay(30000); return searchTrack(query); } await delay(requestDelay); console.log('Got', returned.body.tracks.total, 'results'); return returned.body.tracks.items; } async function searchAllOfSpotify() { await setupSpotify(); const audioFiles = await readAudioFiles(); let tracks = []; let progress = 0; let hits = 0; for (const file of audioFiles) { console.log('=> item %d of %d', (progress++), audioFiles.length); const results = await searchTrack(`${file.tags.title} artist:${file.tags.artist}`); if (results.length) { hits++; tracks.push({ file: file.path, title: file.tags.title, artist: file.tags.artist, spotify: results, }); } } console.log('Traversed %d audio files, of which %d had potential results from Spotify', audioFiles.length, hits); await fs.writeFile('spotify.json', JSON.stringify(tracks, undefined, 2)); } searchAllOfSpotify().catch(console.error).then(() => rl.close());