Updated to v3 api

Senseless JSDoc
This commit is contained in:
2025-10-22 11:32:12 +02:00
parent a3cbe956d7
commit 8c7ec8e968

261
main.js
View File

@@ -1,66 +1,229 @@
// @ts-check
import fs from 'fs'; import fs from 'fs';
import https from 'https'; import https from 'https';
import chalk from 'chalk'; import chalk from 'chalk';
import load from 'dotenv'; import {config} from 'dotenv';
load() config()
let songCount = 1; let songCount = 1;
const AUTHTOKEN = process.env.AUTHTOKEN; 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'); console.error('Please enter all the required information');
process.exit(1); process.exit(1);
} }
/** @typedef {("focus" | "relax" | "sleep" | "meditate")} mentalState */
/** @type {mentalState[]} */
let mentalStates = ["focus", "relax", "sleep", "meditate"]
let genres = [ /**
"Beach", * @type {Record<mentalState, {base: string[], nature: string[]}>}
"Chimes & Bowls", */
"Forest", let genres = {
"Nightsounds", "focus": {
"Rain", base: [
"Rainforest", "Acoustic",
"River", "Atmospheric",
"Thunder", "Cinematic",
"Underwater", "Classical",
"Wind", "Drone",
"Acoustic", "Electronic",
"Atmospheric", "Grooves",
"Cinematic", "Lofi",
"Classical", "Piano",
"Drone", "Post Rock"
"Electronic", ],
"Grooves", nature: [
"Lofi", "Beach",
"Piano", "Chimes & Bowls",
"Post Rock", "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 = { // @ts-ignore
focus: "6042a1bad80ae028b821e954", let moods = {
sleep: "6042a1bad80ae028b821e95c", "focus": [
relax: "6042a1bad80ae028b821e958" "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 { class DownloadQueue {
/** @param {number} maxConcurrency */
constructor(maxConcurrency) { constructor(maxConcurrency) {
/** @type {QueueEntry[]} */
this.queue = []; this.queue = [];
this.activeDownloads = 0; this.activeDownloads = 0;
this.maxConcurrency = maxConcurrency; this.maxConcurrency = maxConcurrency;
} }
/**
* @param {string} url
* @param {string} folder
* @param {string} filename
*/
enqueue(url, folder, filename) { enqueue(url, folder, filename) {
this.queue.push({url, folder, filename}); this.queue.push({ url, folder, filename });
this.processQueue(); this.processQueue();
} }
async processQueue() { async processQueue() {
if (this.activeDownloads < this.maxConcurrency && this.queue.length > 0) { 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++; this.activeDownloads++;
await downloadSong(url, folder, filename); await downloadSong(url, folder, filename);
console.log(`${songCount} songs downloaded successfully \n${this.queue.length} remaining`) console.log(`${songCount} songs downloaded successfully \n${this.queue.length} remaining`)
@@ -72,21 +235,22 @@ class DownloadQueue {
const downloadQueue = new DownloadQueue(3) const downloadQueue = new DownloadQueue(3)
for(const genre of genres) { for (const mentalState of mentalStates) {
console.log(chalk.red(`Starting genre ${genre}`)) console.log(chalk.red(`Starting mental state ${mentalState}`))
for(const mentalState of Object.keys(mentalStateIDs)) { for (const genre of [...genres[mentalState].base, ...genres[mentalState].nature]) {
console.log(chalk.yellow(`Starting mental state ${mentalState}`)) console.log(chalk.yellow(`Starting genre ${genre}`))
// //
// Phase 1 : Fetch all song data // 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: { headers: {
authorization: `Bearer ${AUTHTOKEN}` authorization: `Bearer ${AUTHTOKEN}`
} }
}); });
data = await data.json(); data = await data.json();
// @ts-ignore
data = formatAudioData(data.result); data = formatAudioData(data.result);
if (checkIfJsonExists(`./json-data/${mentalState}/${genre}.json`)) continue; 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}`)) console.log(chalk.green(`Started downloading ${genre} ${mentalState}`))
// @ts-ignore
for (const song of data) { for (const song of data) {
let activity = song.serving.track.tags.find(tag => tag.type === "activity").value; // @ts-ignore
let NEL = song.neuralEffectLevel; 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 level = (NEL > 0.66 ? "high" : NEL > 0.33 ? "medium" : "low");
let hasMultipleNELs = song.hasMultipleNELs;
let folder = `./songs/${genre}/${mentalState}/${activity}/${hasMultipleNELs ? "mixed" : level}` let folder = `./songs/${genre}/${mentalState}/${activity}/${level}`
let filename = `${song.name.replace(' ', '_')}`; let filename = song.trackVariation.baseUrl;
let downloadLink = `https://audio.brain.fm/${song.url}?userId=${USERID}&token=${TOKEN}`; let downloadLink = song.trackVariation.tokenedUrl;
downloadQueue.enqueue(downloadLink, folder, filename); downloadQueue.enqueue(downloadLink, folder, filename);
} }
@@ -120,6 +285,7 @@ for(const genre of genres) {
// @ts-ignore
async function downloadSong(downloadLink, folder, filename) { async function downloadSong(downloadLink, folder, filename) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
ensureDirectory(folder) ensureDirectory(folder)
@@ -127,6 +293,7 @@ async function downloadSong(downloadLink, folder, filename) {
response.pipe(fs.createWriteStream(`${folder}/${filename}.mp3`)) response.pipe(fs.createWriteStream(`${folder}/${filename}.mp3`))
.on('finish', () => { .on('finish', () => {
songCount++ songCount++
// @ts-ignore
resolve(); resolve();
}) })
.on('error', (error) => { .on('error', (error) => {
@@ -137,19 +304,23 @@ async function downloadSong(downloadLink, folder, filename) {
}); });
} }
// @ts-ignore
function formatAudioData(arr) { function formatAudioData(arr) {
// @ts-ignore
return arr.map(item => { return arr.map(item => {
delete item.serving.track.similarTracks; delete item.track.similarTracks;
return item; return item;
}); });
} }
// @ts-ignore
function ensureDirectory(directory) { function ensureDirectory(directory) {
if (!fs.existsSync(directory)) { if (!fs.existsSync(directory)) {
fs.mkdirSync(directory, { recursive: true }); fs.mkdirSync(directory, { recursive: true });
} }
} }
// @ts-ignore
function checkIfJsonExists(mentalState, genre) { function checkIfJsonExists(mentalState, genre) {
const filePath = `./json-data/${mentalState}/${genre}.json`; const filePath = `./json-data/${mentalState}/${genre}.json`;
return fs.existsSync(filePath); return fs.existsSync(filePath);