diff --git a/.gitignore b/.gitignore index 3c3629e..fdc547e 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ node_modules +*.lnk diff --git a/deno.lock b/deno.lock index 16aff2d..f780d3d 100644 --- a/deno.lock +++ b/deno.lock @@ -1,5 +1,35 @@ { "version": "5", + "specifiers": { + "jsr:@std/cli@*": "1.0.23", + "jsr:@std/fs@*": "1.0.19", + "npm:@types/node@^24.5.2": "24.9.1" + }, + "jsr": { + "@std/assert@1.0.15": { + "integrity": "d64018e951dbdfab9777335ecdb000c0b4e3df036984083be219ce5941e4703b" + }, + "@std/cli@1.0.23": { + "integrity": "bf95b7a9425ba2af1ae5a6359daf58c508f2decf711a76ed2993cd352498ccca" + }, + "@std/fs@1.0.19": { + "integrity": "051968c2b1eae4d2ea9f79a08a3845740ef6af10356aff43d3e2ef11ed09fb06" + }, + "@std/internal@1.0.12": { + "integrity": "972a634fd5bc34b242024402972cd5143eac68d8dffaca5eaa4dba30ce17b027" + } + }, + "npm": { + "@types/node@24.9.1": { + "integrity": "sha512-QoiaXANRkSXK6p0Duvt56W208du4P9Uye9hWLWgGMDTEoKPhuenzNcC4vGUmrNkiOKTlIrBoyNQYNpSwfEZXSg==", + "dependencies": [ + "undici-types" + ] + }, + "undici-types@7.16.0": { + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==" + } + }, "redirects": { "https://esm.sh/@types/boolbase@~1.0.3/index.d.ts": "https://esm.sh/@types/boolbase@1.0.3/index.d.ts", "https://esm.sh/@types/safer-buffer@~2.1.3/index.d.ts": "https://esm.sh/@types/safer-buffer@2.1.3/index.d.ts", diff --git a/main.ts b/main.ts index 8fd2b33..ae47efe 100644 --- a/main.ts +++ b/main.ts @@ -1,8 +1,7 @@ -import { existsSync } from 'node:fs' -import { mkdir, writeFile } from 'node:fs/promises' import path from 'node:path' -import { argv } from 'node:process' import { Buffer } from 'node:buffer' +import { parseArgs } from "jsr:@std/cli/parse-args" +import { exists } from "jsr:@std/fs/exists"; import * as cheerio from 'https://esm.sh/cheerio?target=esnext' const REGEX_PLAYLISTURL = @@ -12,23 +11,65 @@ const REGEX_SONGURL = const REGEX_ALBUMTITLE = /^(.*?) MP3 - Download/i const REGEX_UNSAFEFORFILE = /[^a-z0-9\-_=+,.()\[\]{} ]/gi -async function main() { - // deno main.ts [] [sync] - const playlistURL = argv[2] - const downloadPath = argv[3] || '.' - const runSync = argv[4]?.toLowerCase() == 'sync' +// parse args +const flags = parseArgs(Deno.args, { + alias: { + "url": ["u"], + "output": ["o"], + "sync": ["s"], + "help": ["h"], + }, + string: ["url", "output"], + boolean: ["sync", "help"], + default: { + "url": "", + "output": ".", + "sync": false + } +}) +if (!flags.url && flags._.length > 0) { + flags.url = flags._[0].toString() +} + +function printHelp() { + console.log(`deno + --allow-net --allow-write --allow-read main.ts + [--url] + [--output ] + [--sync] + [--help] + +parameters: + --url -u (default) + url to download + + --output -o + default: "." + output path + + --sync -s + download files one at a time + + --help -h + print help message +`) +} + +async function main() { // load all song details - const playlist = await fetchPlaylist(playlistURL) + const playlist = await fetchPlaylist(flags.url) // if sync, download one at a time - if (runSync) { + if (flags.sync) { + console.log('downloading one at a time\n') for (const song of playlist) { - await downloadSong(song, downloadPath || '.') + await downloadSong(song, flags.output || '.') } } else { + console.log('downloading all at once\n') await Promise.all( - playlist.map((song) => downloadSong(song, downloadPath || '.')) + playlist.map((song) => downloadSong(song, flags.output || '.')) ) } } @@ -38,6 +79,8 @@ async function fetchPlaylist(url: string): Promise { throw `unaccepted url ${url}` } + console.log(`downloading: ${url}`) + // load the playlist page's dom const req = await fetch(url) const text = await req.text() @@ -53,12 +96,12 @@ async function fetchPlaylist(url: string): Promise { throw `unable to grab album name from ${title}` } const albumName = titleMatch[1] - + console.log(` title: ${albumName}`) + // parse all rows in playlist const columns = getPlaylistTableHeaders($) const rows = getPlaylistRows($, columns, albumName) - - console.log(`downloading ${rows.length} songs`) + console.log(` songs: ${rows.length}\n`) return rows } @@ -203,12 +246,12 @@ async function downloadSong(song: PlaylistSongData, location: string) { const { pathname, fullpathname } = pathFor(location, song) // ensure folder exists - if (!existsSync(pathname)) { - await mkdir(pathname) + if (!await exists(pathname)) { + await Deno.mkdir(pathname) } // skip file if it exists - if (existsSync(fullpathname)) { + if (await exists(fullpathname)) { console.log(`skipping file already exists ${fullpathname}`) } @@ -218,7 +261,7 @@ async function downloadSong(song: PlaylistSongData, location: string) { // download the file const songresp = await fetch(flacUrl) const songblob = await songresp.arrayBuffer() - return writeFile(fullpathname, toBuffer(songblob)) + return Deno.writeFile(fullpathname, toBuffer(songblob)) } interface SongPath { @@ -279,4 +322,12 @@ function toBuffer(arrayBuffer: ArrayBuffer): Buffer { return buffer } -main().catch((e) => console.error(e)) +if (!flags.url) { + console.log('Missing URL\n\n') + printHelp() +} else if (flags.help) { + printHelp() +} else { + main().catch((e) => console.error(e)) +} +