« get me outta code hell

mtui - Music Text User Interface - user-friendly command line music player
about summary refs log tree commit diff
path: root/downloaders.js
diff options
context:
space:
mode:
Diffstat (limited to 'downloaders.js')
-rw-r--r--downloaders.js153
1 files changed, 83 insertions, 70 deletions
diff --git a/downloaders.js b/downloaders.js
index 4b4750c..4c1ce38 100644
--- a/downloaders.js
+++ b/downloaders.js
@@ -1,7 +1,7 @@
 const { promisifyProcess } = require('./general-util')
 const { promisify } = require('util')
 const { spawn } = require('child_process')
-const { Base64 } = require('js-base64')
+const { URL } = require('url')
 const mkdirp = promisify(require('mkdirp'))
 const fs = require('fs')
 const fetch = require('node-fetch')
@@ -31,7 +31,9 @@ const copyFile = (source, target) => {
   })
 }
 
-const cachify = (identifier, baseFunction) => {
+const disableBackResolving = arg => arg.split('/').map(str => str.replace(/^\../, '_..')).join('/')
+
+const cachify = (identifier, keyFunction, baseFunction) => {
   return async arg => {
     // If there was no argument passed (or it aws empty), nothing will work..
     if (!arg) {
@@ -41,7 +43,7 @@ const cachify = (identifier, baseFunction) => {
     // Determine where the final file will end up. This is just a directory -
     // the file's own name is determined by the downloader.
     const cacheDir = downloaders.rootCacheDir + '/' + identifier
-    const finalDirectory = cacheDir + '/' + Base64.encode(arg)
+    const finalDirectory = cacheDir + '/' + disableBackResolving(keyFunction(arg))
 
     // Check if that directory only exists. If it does, return the file in it,
     // because it being there means we've already downloaded it at some point
@@ -105,73 +107,84 @@ const downloaders = {
   // TODO: Cross-platform stuff
   rootCacheDir: process.env.HOME + '/.mtui/downloads',
 
-  http: cachify('http', arg => {
-    const out = (
-      tempy.directory() + '/' +
-      sanitize(decodeURIComponent(path.basename(arg))))
-
-    return fetch(arg)
-      .then(response => response.buffer())
-      .then(buffer => writeFile(out, buffer))
-      .then(() => out)
-  }),
-
-  youtubedl: cachify('youtubedl', arg => {
-    const outDir = tempy.directory()
-    const outFile = outDir + '/%(id)s-%(uploader)s-%(title)s.%(ext)s'
-
-    const opts = [
-      '--quiet',
-      '--no-warnings',
-      '--extract-audio',
-      '--audio-format', downloaders.extension,
-      '--output', outFile,
-      arg
-    ]
-
-    return promisifyProcess(spawn('youtube-dl', opts))
-      .then(() => readdir(outDir))
-      .then(files => outDir + '/' + files[0])
-  }),
-
-  local: cachify('local', arg => {
-    // Usually we'd just return the given argument in a local
-    // downloader, which is efficient, since there's no need to
-    // copy a file from one place on the hard drive to another.
-    // But reading from a separate drive (e.g. a USB stick or a
-    // CD) can take a lot longer than reading directly from the
-    // computer's own drive, so this downloader copies the file
-    // to a temporary file on the computer's drive.
-    // Ideally, we'd be able to check whether a file is on the
-    // computer's main drive mount or not before going through
-    // the steps to copy, but I'm not sure if there's a way to
-    // do that (and it's even less likely there'd be a cross-
-    // platform way).
-
-    // It's possible the downloader argument starts with the "file://"
-    // protocol string; in that case we'll want to snip it off and URL-
-    // decode the string.
-    arg = removeFileProtocol(arg)
-
-    // TODO: Is it necessary to sanitize here?
-    // Haha, the answer to "should I sanitize" is probably always YES..
-    const base = path.basename(arg, path.extname(arg))
-    const out = tempy.directory() + '/' + sanitize(base) + path.extname(arg)
-
-    return copyFile(arg, out)
-      .then(() => out)
-  }),
-
-  locallink: cachify('locallink', arg => {
-    // Like the local downloader, but creates a symbolic link to the argument.
-
-    arg = removeFileProtocol(arg)
-    const base = path.basename(arg, path.extname(arg))
-    const out = tempy.directory() + '/' + sanitize(base) + path.extname(arg)
-
-    return symlink(path.resolve(arg), out)
-      .then(() => out)
-  }),
+  http: cachify('http',
+    arg => {
+      const url = new URL(arg)
+      return url.hostname + url.pathname
+    },
+    arg => {
+      const out = (
+        tempy.directory() + '/' +
+        sanitize(decodeURIComponent(path.basename(arg))))
+
+      return fetch(arg)
+        .then(response => response.buffer())
+        .then(buffer => writeFile(out, buffer))
+        .then(() => out)
+    }),
+
+  youtubedl: cachify('youtubedl',
+    arg => (arg.match(/watch\?v=(.*)/) || ['', arg])[1],
+    arg => {
+      const outDir = tempy.directory()
+      const outFile = outDir + '/%(id)s-%(uploader)s-%(title)s.%(ext)s'
+
+      const opts = [
+        '--quiet',
+        '--no-warnings',
+        '--extract-audio',
+        '--audio-format', downloaders.extension,
+        '--output', outFile,
+        arg
+      ]
+
+      return promisifyProcess(spawn('youtube-dl', opts))
+        .then(() => readdir(outDir))
+        .then(files => outDir + '/' + files[0])
+    }),
+
+  local: cachify('local',
+    arg => arg,
+    arg => {
+      // Usually we'd just return the given argument in a local
+      // downloader, which is efficient, since there's no need to
+      // copy a file from one place on the hard drive to another.
+      // But reading from a separate drive (e.g. a USB stick or a
+      // CD) can take a lot longer than reading directly from the
+      // computer's own drive, so this downloader copies the file
+      // to a temporary file on the computer's drive.
+      // Ideally, we'd be able to check whether a file is on the
+      // computer's main drive mount or not before going through
+      // the steps to copy, but I'm not sure if there's a way to
+      // do that (and it's even less likely there'd be a cross-
+      // platform way).
+
+      // It's possible the downloader argument starts with the "file://"
+      // protocol string; in that case we'll want to snip it off and URL-
+      // decode the string.
+      arg = removeFileProtocol(arg)
+
+      // TODO: Is it necessary to sanitize here?
+      // Haha, the answer to "should I sanitize" is probably always YES..
+      const base = path.basename(arg, path.extname(arg))
+      const out = tempy.directory() + '/' + sanitize(base) + path.extname(arg)
+
+      return copyFile(arg, out)
+        .then(() => out)
+    }),
+
+  locallink: cachify('locallink',
+    arg => arg,
+    arg => {
+      // Like the local downloader, but creates a symbolic link to the argument.
+
+      arg = removeFileProtocol(arg)
+      const base = path.basename(arg, path.extname(arg))
+      const out = tempy.directory() + '/' + sanitize(base) + path.extname(arg)
+
+      return symlink(path.resolve(arg), out)
+        .then(() => out)
+    }),
 
   echo: arg => arg,