« get me outta code hell

downloaders.js - mtui - Music Text User Interface - user-friendly command line music player
about summary refs log tree commit diff
path: root/downloaders.js
blob: cd78fb0536d7caea831c9b4a6b60306362d51ceb (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
const { promisifyProcess } = require('./general-util')
const { spawn } = require('child_process')
const { promisify } = require('util')
const fs = require('fs')
const fse = require('fs-extra')
const fetch = require('node-fetch')
const tempy = require('tempy')
const path = require('path')
const sanitize = require('sanitize-filename')

const writeFile = promisify(fs.writeFile)
const copyFile = fse.copy

// Pseudo-tempy!!
/*
const tempy = {
  directory: () => './tempy-fake'
}
*/

class Downloader {
  download(arg) {}
}

// oh who cares about classes or functions or kool things

const downloaders = {
  extension: 'mp3', // Generally target file extension

  cache: {
    http: {},
    youtubedl: {},
    local: {}
  },

  http: arg => {
    const cached = downloaders.cache.http[arg]
    if (cached) return cached

    const out = (
      tempy.directory() + '/' +
      sanitize(decodeURIComponent(path.basename(arg))))

    return fetch(arg)
      .then(response => response.buffer())
      .then(buffer => writeFile(out, buffer))
      .then(() => downloaders.cache.http[arg] = out)
  },

  youtubedl: arg => {
    const cached = downloaders.cache.youtubedl[arg]
    if (cached) return cached

    const out = (
      tempy.directory() + '/' + sanitize(arg) +
      '.' + downloaders.extname)

    const opts = [
      '--quiet',
      '--extract-audio',
      '--audio-format', downloaders.extension,
      '--output', out,
      arg
    ]

    return promisifyProcess(spawn('youtube-dl', opts))
      .then(() => downloaders.cache.youtubedl[arg] = out)
      .catch(err => false)
  },

  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.
    const fileProto = 'file://'
    if (arg.startsWith(fileProto)) {
      arg = decodeURIComponent(arg.slice(fileProto.length))
    }

    // 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(() => downloaders.cache.local[arg] = out)
  },

  echo: arg => arg,

  getDownloaderFor: arg => {
    if (arg.startsWith('http://') || arg.startsWith('https://')) {
      if (arg.includes('youtube.com')) {
        return downloaders.youtubedl
      } else {
        return downloaders.http
      }
    } else {
      return downloaders.local
    }
  }
}

module.exports = downloaders