added comments
This commit is contained in:
52
main.ts
52
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 <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) {
|
||||
|
||||
Reference in New Issue
Block a user