diff --git a/main.ts b/main.ts index ca6148d..8fd2b33 100644 --- a/main.ts +++ b/main.ts @@ -13,12 +13,15 @@ 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' + // load all song details const playlist = await fetchPlaylist(playlistURL) + // if sync, download one at a time if (runSync) { for (const song of playlist) { await downloadSong(song, downloadPath || '.') @@ -35,11 +38,12 @@ async function fetchPlaylist(url: string): Promise { throw `unaccepted url ${url}` } + // load the playlist page's dom const req = await fetch(url) const text = await req.text() - const $ = cheerio.load(text) + // get the album name from the page title const title = $.extract({ title: 'title', @@ -50,6 +54,7 @@ async function fetchPlaylist(url: string): Promise { } const albumName = titleMatch[1] + // parse all rows in playlist const columns = getPlaylistTableHeaders($) const rows = getPlaylistRows($, columns, albumName) @@ -73,13 +78,16 @@ interface PlaylistSongData { } function getPlaylistTableHeaders($: cheerio.CheerioAPI): PlaylistTableColumns { + // get table header row const header = $('#songlist tr#songlist_header') const headerCells = header.extract({ cells: ['th'], }) + // get the index for specific columns const indexes = headerCells.cells.reduce( (p, c, i) => { + // check the string content of the current cell switch (c.toLocaleLowerCase()) { case 'cd': p.cd = i @@ -92,6 +100,7 @@ function getPlaylistTableHeaders($: cheerio.CheerioAPI): PlaylistTableColumns { } return p }, + // default values { name: -1, track: -1, @@ -114,6 +123,7 @@ function getPlaylistRows( const rows = $('#songlist tr:not(#songlist_header):not(#songlist_footer)') const rowsData: PlaylistSongData[] = [] + // loop through each song in table rows.each((_, rowEl) => { const row = cheerio.load(rowEl) const rowData: PlaylistSongData = { @@ -124,6 +134,7 @@ function getPlaylistRows( cd: -1, } + // prase values from row rowData.url = row.extract({ url: { @@ -162,6 +173,7 @@ function getPlaylistRows( } async function downloadSong(song: PlaylistSongData, location: string) { + // get full url let url = song.url if (!/^http/i.test(url)) { url = 'https://downloads.khinsider.com' + url @@ -170,10 +182,12 @@ async function downloadSong(song: PlaylistSongData, location: string) { throw `unaccepted url ${url}` } + // load download page const resp = await fetch(url) const text = await resp.text() - const $ = cheerio.load(text) + + // extract the flac download link const flacUrl = $.extract({ url: { selector: '#pageContent a[href*="flac"]', @@ -185,11 +199,15 @@ async function downloadSong(song: PlaylistSongData, location: string) { throw `can't find download link for ${url}` } + // get the file and path to save the files const { pathname, fullpathname } = pathFor(location, song) + // ensure folder exists if (!existsSync(pathname)) { await mkdir(pathname) } + + // skip file if it exists if (existsSync(fullpathname)) { console.log(`skipping file already exists ${fullpathname}`) } @@ -197,9 +215,9 @@ async function downloadSong(song: PlaylistSongData, location: string) { console.log(`downloading ${fullpathname}`) console.log(` from ${flacUrl}`) + // download the file const songresp = await fetch(flacUrl) const songblob = await songresp.arrayBuffer() - return writeFile(fullpathname, toBuffer(songblob)) } @@ -209,6 +227,7 @@ interface SongPath { } function pathFor(location: string, song: PlaylistSongData): SongPath { + // clean strings for file paths const albumname = song.album.replace(REGEX_UNSAFEFORFILE, '') const songname = song.name.replace(REGEX_UNSAFEFORFILE, '') @@ -216,6 +235,30 @@ function pathFor(location: string, song: PlaylistSongData): SongPath { const track = song.track >= 0 ? song.track + '.' : '' const separator = song.cd >= 0 || song.track >= 0 ? ' ' : '' const filename = `${cd}${track}${separator}${songname}.flac` + /* + for example + + song = { + songname: 'song', + track: 1, + cd: 1, + } + then filename = '1.1. song' + + song = { + songname: 'song', + track: 1, + cd: -1, + } + then filename = '1. song' + + song = { + songname: 'song', + track: -1, + cd: -1, + } + then filename = 'song' + */ const pathname = path.resolve(location, albumname) const fullpathname = path.resolve(pathname, filename) @@ -226,7 +269,8 @@ function pathFor(location: string, song: PlaylistSongData): SongPath { } } -function toBuffer(arrayBuffer: ArrayBuffer) { +// convert ArrayBuffer to Buffer +function toBuffer(arrayBuffer: ArrayBuffer): Buffer { const buffer = Buffer.alloc(arrayBuffer.byteLength) const view = new Uint8Array(arrayBuffer) for (let i = 0; i < buffer.length; ++i) {