Skip to content

Instantly share code, notes, and snippets.

@danikaze
Created April 18, 2021 18:26
Show Gist options
  • Select an option

  • Save danikaze/9945110998d3e88380325c59ba973eb0 to your computer and use it in GitHub Desktop.

Select an option

Save danikaze/9945110998d3e88380325c59ba973eb0 to your computer and use it in GitHub Desktop.
Download, organize and keep sync with tabletopaudio.com files with this gist
/**
* https://tabletopaudio.com/ is an awesome source of free music.
* Consider making a donation (to him) if using his creations!
*
*
* Just place the following .js+.bat files in the location you want to download
* from tabletop audio ^^
*
* Creating symboling links (to put files inside folders based on categories)
* requires admin permissions in Win :(
* But it can be requested if the JS is run from the following `.bat` file:
@echo off
:: BatchGotAdmin
:-------------------------------------
REM --> Check for permissions
IF "%PROCESSOR_ARCHITECTURE%" EQU "amd64" (
>nul 2>&1 "%SYSTEMROOT%\SysWOW64\cacls.exe" "%SYSTEMROOT%\SysWOW64\config\system"
) ELSE (
>nul 2>&1 "%SYSTEMROOT%\system32\cacls.exe" "%SYSTEMROOT%\system32\config\system"
)
REM --> If error flag set, we do not have admin.
if '%errorlevel%' NEQ '0' (
echo Requesting administrative privileges...
goto UACPrompt
) else ( goto gotAdmin )
:UACPrompt
echo Set UAC = CreateObject^("Shell.Application"^) > "%temp%\getadmin.vbs"
set params= %*
echo UAC.ShellExecute "cmd.exe", "/c ""%~s0"" %params:"=""%", "", "runas", 1 >> "%temp%\getadmin.vbs"
"%temp%\getadmin.vbs"
del "%temp%\getadmin.vbs"
exit /B
:gotAdmin
pushd "%CD%"
CD /D "%~dp0"
:--------------------------------------
node tabletop-audio-dl.js
pause
*/
/*
* .js file starts here
*/
const { get } = require('https');
const { join } = require('path');
const { existsSync, createWriteStream, mkdirSync } = require('fs');
const util = require('util');
const exec = util.promisify(require('child_process').exec);
const URL = 'https://tabletopaudio.com';
const SAVE_FOLDER = process.cwd();
const DELAY = 3000;
function getPage() {
return new Promise((resolve, reject) => {
get(URL, (res) => {
let html = '';
res.on('data', data => {
html += data;
});
res.on('end', () => resolve(html));
});
})
}
function getCategories(html) {
const cat = [];
const re = /data-filter="\.([^"]+)"/g;
let match = re.exec(html);
while(match) {
cat.push(match[1]);
match = re.exec(html);
}
return cat;
}
function getSubstring(text, start, end, offset = 0) {
const i = text.indexOf(start, offset);
if (i === -1) return;
const j = text.indexOf(end, i + start.length);
if (j === -1) return;
return {
text: text.substring(i + start.length, j),
next: j + end.length
};
}
function getSongs(html, categoryList) {
const files = [];
let foundCategories = getSubstring(html, 'class="col-md-3 mix', '"');
while (foundCategories) {
const categories = foundCategories.text.split(' ').filter(c => categoryList.indexOf(c) !== -1);
const foundFilename = getSubstring(html, `onclick="saveAs('`, "'", foundCategories.next);
if (!foundFilename) {
foundCategories = getSubstring(html, 'class="col-md-3 mix', '"', foundCategories.next);
continue;
}
const filename = foundFilename.text + '.mp3';
files.push({ filename, categories });
foundCategories = getSubstring(html, 'class="col-md-3 mix', '"', foundFilename.next);
}
return files;
}
function filterSongs(songs) {
return songs.filter(song => {
const filePath = join (SAVE_FOLDER, song.filename);
if (existsSync(filePath)) {
createLinks(song);
return false;
}
return true;
});
}
async function createLinks(song) {
for (let i = 0; i < song.categories.length; i++) {
const cat = song.categories[i];
const src = join(SAVE_FOLDER, song.filename);
const folder = join(SAVE_FOLDER, cat);
const target = join(folder, song.filename);
if (existsSync(target)) {
return;
}
if (!existsSync(folder)) {
mkdirSync(folder);
}
console.log(` > Creating link for [${cat}] ${song.filename}`);
await createLink(src, target);
}
}
async function createLink(source, target) {
try {
await exec(`mklink "${target}" "${source}"`);
} catch (e) {
console.warn(e);
}
}
function downloadSong(song) {
return new Promise((resolve, reject) => {
const targetPath = join(SAVE_FOLDER, song.filename);
const file = createWriteStream(targetPath);
const url = `https://sounds.tabletopaudio.com/${song.filename}`;
get(url, (res) => {
res.on('end', () => {
resolve()
});
res.on('close', () => resolve());
res.pipe(file)
});
})
}
function getSongsPerCategory(songs, prefix) {
const cats = songs.reduce((acc, song) => {
song.categories.forEach(cat => {
acc[cat] = (acc[cat] || 0) + 1;
})
return acc;
}, {});
return Object.keys(cats).sort().map((cat) => `${prefix}${cat} (${cats[cat]})`).join('\n');
}
function delay(ms) {
return new Promise((resolve) => {
setTimeout(resolve, ms);
});
}
async function run() {
console.log(` * Target folder: ${SAVE_FOLDER}`);
console.log(` * Requesting html: ${URL}`);
const html = await getPage();
const categories = getCategories(html);
const songs = getSongs(html, categories);
const newSongs = filterSongs(songs);
console.log(` * Detected a total of ${songs.length} songs`);
console.log(getSongsPerCategory(songs, ' - '));
console.log(` * Downloading ${newSongs.length} new files:`);
for (let i = 0; i < newSongs.length; i++) {
const song = newSongs[i];
console.log(` - Downloading [${i + 1}/${newSongs.length}] ${song.filename}`);
try {
await downloadSong(song);
createLinks(song);
await delay(DELAY);
} catch (e) {
console.warn(` (!) Error downloading ${song.filename}`, e);
}
}
}
run();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment