From 8c7ec8e968532e347cf0a1be661546254845c923 Mon Sep 17 00:00:00 2001 From: OmerSabic Date: Wed, 22 Oct 2025 11:32:12 +0200 Subject: [PATCH] Updated to v3 api Senseless JSDoc --- main.js | 261 ++++++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 216 insertions(+), 45 deletions(-) diff --git a/main.js b/main.js index a82bdf9..2ef487f 100644 --- a/main.js +++ b/main.js @@ -1,66 +1,229 @@ +// @ts-check + import fs from 'fs'; import https from 'https'; import chalk from 'chalk'; -import load from 'dotenv'; -load() +import {config} from 'dotenv'; +config() let songCount = 1; const AUTHTOKEN = process.env.AUTHTOKEN; -const TOKEN = process.env.TOKEN; -const USERID = process.env.USERID; -if(!AUTHTOKEN || !TOKEN || !USERID) { +if (!AUTHTOKEN) { console.error('Please enter all the required information'); process.exit(1); } +/** @typedef {("focus" | "relax" | "sleep" | "meditate")} mentalState */ +/** @type {mentalState[]} */ +let mentalStates = ["focus", "relax", "sleep", "meditate"] -let genres = [ - "Beach", - "Chimes & Bowls", - "Forest", - "Nightsounds", - "Rain", - "Rainforest", - "River", - "Thunder", - "Underwater", - "Wind", - "Acoustic", - "Atmospheric", - "Cinematic", - "Classical", - "Drone", - "Electronic", - "Grooves", - "Lofi", - "Piano", - "Post Rock", -]; +/** + * @type {Record} + */ +let genres = { + "focus": { + base: [ + "Acoustic", + "Atmospheric", + "Cinematic", + "Classical", + "Drone", + "Electronic", + "Grooves", + "Lofi", + "Piano", + "Post Rock" + ], + nature: [ + "Beach", + "Chimes & Bowls", + "Forest", + "Nightsounds", + "Rain", + "Rainforest", + "River", + "Thunder", + "Underwater", + "Wind" + ] + }, + "relax": { + base: [ + "Atmospheric", + "Electronic" + ], + nature: [ + "Beach", + "Chimes & Bowls", + "Forest", + "Nightsounds", + "Rain", + "Rainforest", + "River", + "Thunder", + "Underwater", + "Wind" + ] + }, + "sleep": { + base: [ + "Atmospheric" + ], + nature: [ + "Beach", + "Forest", + "Nightsounds", + "Rain", + "Rainforest", + "River", + "Thunder", + "Underwater", + "Wind" + ] + }, + "meditate": { + base: [ + "Atmospheric", + "Electronic" + ], + nature: [ + "Beach", + "Chimes & Bowls", + "Forest", + "Nightsounds", + "Rain", + "Rainforest", + "River", + "Thunder", + "Underwater", + "Wind" + ] + } +}; -let mentalStateIDs = { - focus: "6042a1bad80ae028b821e954", - sleep: "6042a1bad80ae028b821e95c", - relax: "6042a1bad80ae028b821e958" +// @ts-ignore +let moods = { + "focus": [ + "Brooding", + "Calm", + "Chill", + "Dark", + "Downtempo", + "Dreamlike", + "Driving", + "Energizing", + "Epic", + "Floating", + "Heavy", + "Hopeful", + "Inspiring", + "Meditative", + "Mysterious", + "Ominous", + "Optimistic", + "Playful", + "Ponderous", + "Serene", + "Strong", + "Upbeat", + "Uplifting" + ], + "relax": [ + "Brooding", + "Calm", + "Chill", + "Dark", + "Downtempo", + "Dreamlike", + "Driving", + "Energizing", + "Epic", + "Floating", + "Hopeful", + "Inspiring", + "Meditative", + "Mysterious", + "Optimistic", + "Playful", + "Ponderous", + "Serene", + "Strong", + "Upbeat", + "Uplifting" + ], + "sleep": [ + "Brooding", + "Calm", + "Chill", + "Dark", + "Dreamlike", + "Epic", + "Floating", + "Heavy", + "Meditative", + "Mysterious", + "Optimistic", + "Ponderous", + "Serene", + "Strong" + ], + "meditate": [ + "Brooding", + "Calm", + "Chill", + "Dark", + "Downtempo", + "Dreamlike", + "Driving", + "Energizing", + "Epic", + "Floating", + "Heavy", + "Hopeful", + "Inspiring", + "Meditative", + "Mysterious", + "Optimistic", + "Playful", + "Ponderous", + "Serene", + "Strong", + "Upbeat", + "Uplifting" + ] } +/** + * @typedef {{url: string, folder: string, filename: string}} QueueEntry + */ + class DownloadQueue { + /** @param {number} maxConcurrency */ constructor(maxConcurrency) { + /** @type {QueueEntry[]} */ this.queue = []; this.activeDownloads = 0; this.maxConcurrency = maxConcurrency; } + + /** + * @param {string} url + * @param {string} folder + * @param {string} filename + */ enqueue(url, folder, filename) { - this.queue.push({url, folder, filename}); + this.queue.push({ url, folder, filename }); this.processQueue(); } async processQueue() { if (this.activeDownloads < this.maxConcurrency && this.queue.length > 0) { - const {url, folder, filename} = this.queue.shift(); + // @ts-ignore + const { url, folder, filename } = this.queue.shift(); this.activeDownloads++; await downloadSong(url, folder, filename); console.log(`${songCount} songs downloaded successfully \n${this.queue.length} remaining`) @@ -72,21 +235,22 @@ class DownloadQueue { const downloadQueue = new DownloadQueue(3) -for(const genre of genres) { - console.log(chalk.red(`Starting genre ${genre}`)) - for(const mentalState of Object.keys(mentalStateIDs)) { - console.log(chalk.yellow(`Starting mental state ${mentalState}`)) +for (const mentalState of mentalStates) { + console.log(chalk.red(`Starting mental state ${mentalState}`)) + for (const genre of [...genres[mentalState].base, ...genres[mentalState].nature]) { + console.log(chalk.yellow(`Starting genre ${genre}`)) // // Phase 1 : Fetch all song data // - let data = await fetch(`https://api.brain.fm/v2/genres/${genre}/tracks?mentalStateId=${mentalStateIDs[mentalState]}&version=3`, { + let data = await fetch(`https://api.brain.fm/v3/servings/search?genre=${genre}&dynamicMentalStateId=${mentalState}`, { headers: { authorization: `Bearer ${AUTHTOKEN}` } }); data = await data.json(); + // @ts-ignore data = formatAudioData(data.result); if (checkIfJsonExists(`./json-data/${mentalState}/${genre}.json`)) continue; @@ -102,15 +266,16 @@ for(const genre of genres) { // console.log(chalk.green(`Started downloading ${genre} ${mentalState}`)) + // @ts-ignore for (const song of data) { - let activity = song.serving.track.tags.find(tag => tag.type === "activity").value; - let NEL = song.neuralEffectLevel; + // @ts-ignore + let activity = song.track.tags.filter(x => x.type == 'activity').map(x => x.value).join('/'); + let NEL = song.trackVariation.neuralEffectLevel; let level = (NEL > 0.66 ? "high" : NEL > 0.33 ? "medium" : "low"); - let hasMultipleNELs = song.hasMultipleNELs; - let folder = `./songs/${genre}/${mentalState}/${activity}/${hasMultipleNELs ? "mixed" : level}` - let filename = `${song.name.replace(' ', '_')}`; - let downloadLink = `https://audio.brain.fm/${song.url}?userId=${USERID}&token=${TOKEN}`; + let folder = `./songs/${genre}/${mentalState}/${activity}/${level}` + let filename = song.trackVariation.baseUrl; + let downloadLink = song.trackVariation.tokenedUrl; downloadQueue.enqueue(downloadLink, folder, filename); } @@ -120,6 +285,7 @@ for(const genre of genres) { +// @ts-ignore async function downloadSong(downloadLink, folder, filename) { return new Promise((resolve, reject) => { ensureDirectory(folder) @@ -127,6 +293,7 @@ async function downloadSong(downloadLink, folder, filename) { response.pipe(fs.createWriteStream(`${folder}/${filename}.mp3`)) .on('finish', () => { songCount++ + // @ts-ignore resolve(); }) .on('error', (error) => { @@ -137,19 +304,23 @@ async function downloadSong(downloadLink, folder, filename) { }); } +// @ts-ignore function formatAudioData(arr) { + // @ts-ignore return arr.map(item => { - delete item.serving.track.similarTracks; + delete item.track.similarTracks; return item; }); } +// @ts-ignore function ensureDirectory(directory) { if (!fs.existsSync(directory)) { fs.mkdirSync(directory, { recursive: true }); } } +// @ts-ignore function checkIfJsonExists(mentalState, genre) { const filePath = `./json-data/${mentalState}/${genre}.json`; return fs.existsSync(filePath);