added comments

This commit is contained in:
2025-10-11 01:07:47 -05:00
parent 95c1a568db
commit de58099ac0

52
main.ts
View File

@@ -13,12 +13,15 @@ const REGEX_ALBUMTITLE = /^(.*?) MP3 - Download/i
const REGEX_UNSAFEFORFILE = /[^a-z0-9\-_=+,.()\[\]{} ]/gi
async function main() {
// deno main.ts <playlistURL> [<downloadPath>] [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<PlaylistSongData[]> {
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<PlaylistSongData[]> {
}
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<ArrayBuffer>
function toBuffer(arrayBuffer: ArrayBuffer): Buffer<ArrayBuffer> {
const buffer = Buffer.alloc(arrayBuffer.byteLength)
const view = new Uint8Array(arrayBuffer)
for (let i = 0; i < buffer.length; ++i) {