├── README.md ├── index.js ├── package-lock.json ├── package.json ├── strings ├── en.strings └── zh-Hans.strings └── youtube-dl.zip /README.md: -------------------------------------------------------------------------------- 1 | # youtube-dl 2 | 3 | [JSBox Node.js](https://cyanzhong.github.io/jsbox-nodejs/#/en/) example that shows you: 4 | 5 | - How to use 3rd-party node modules like [`ytdl-core`](https://www.npmjs.com/package/ytdl-core) 6 | - How to handle input & output 7 | - How to use native modules like `ui`, `quicklook` 8 | 9 | # Instructions 10 | 11 | - Install [JSBox](https://apps.apple.com/us/app/id1312014438) 2.0 12 | - Run `npm install` to resolve dependencies 13 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | const fs = require("fs"); 2 | const ytdl = require("ytdl-core"); 3 | const sanitize = require("sanitize-filename"); 4 | 5 | const l10n = require("l10n"); 6 | const input = require("input"); 7 | const ui = require("ui"); 8 | const quicklook = require("quicklook"); 9 | const clipboard = require("clipboard"); 10 | const detector = require("detector"); 11 | 12 | function download(url) { 13 | ui.toast(url); 14 | let progress = 0; 15 | let filename = null; 16 | 17 | const options = { 18 | filter: format => { 19 | return format.container == "mp4"; 20 | } 21 | } 22 | 23 | const onInfo = info => { 24 | const title = info.player_response.videoDetails.title; 25 | console.log(title); 26 | filename = `${sanitize(title)}.mp4`; 27 | } 28 | 29 | const onProgress = (chunk, downloaded, total) => { 30 | const ratio = downloaded / total; 31 | const percent = Math.floor(ratio * 100); 32 | if (percent != progress) { 33 | ui.showProgress(ratio, l10n("DOWNLOADING")); 34 | console.log(`${l10n("DOWNLOADED")}${percent}%`); 35 | } 36 | progress = percent; 37 | } 38 | 39 | const onFinish = () => { 40 | ui.hideProgress(); 41 | console.log(l10n("FINISHED_DOWNLOADING")); 42 | fs.renameSync("tmp.mp4", filename); 43 | quicklook.file(filename); 44 | } 45 | 46 | ytdl(url, options) 47 | .on("info", onInfo) 48 | .on("progress", onProgress) 49 | .on("finish", onFinish) 50 | .pipe(fs.createWriteStream("tmp.mp4")); 51 | }; 52 | 53 | async function prompt() { 54 | const text = await input.text({ 55 | placeholder: l10n("ENTER_URL"), 56 | text: detector.link(clipboard.text()) 57 | }); 58 | 59 | if (text == undefined) { 60 | return; 61 | } 62 | 63 | const link = detector.link(text); 64 | if (link) { 65 | download(link); 66 | } else { 67 | console.error(l10n("CANNOT_FIND_URL")); 68 | setTimeout(prompt, 500); 69 | } 70 | } 71 | 72 | const link = detector.link($context.text || $context.link); 73 | if (link) { 74 | download(link); 75 | } else { 76 | prompt(); 77 | } -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "requires": true, 3 | "lockfileVersion": 1, 4 | "dependencies": { 5 | "html-entities": { 6 | "version": "1.2.1", 7 | "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-1.2.1.tgz", 8 | "integrity": "sha1-DfKTUfByEWNRXfueVUPl9u7VFi8=" 9 | }, 10 | "m3u8stream": { 11 | "version": "0.6.4", 12 | "resolved": "https://registry.npmjs.org/m3u8stream/-/m3u8stream-0.6.4.tgz", 13 | "integrity": "sha512-9WLF1VAtbVij03HWJKbVZ8L0orsoZiP53UljR5EwaDrozQFMsTGRDPe3PbzWV73He8a+j5H/hWZNoI2VkUSsiw==", 14 | "requires": { 15 | "miniget": "^1.6.1", 16 | "sax": "^1.2.4" 17 | } 18 | }, 19 | "miniget": { 20 | "version": "1.6.1", 21 | "resolved": "https://registry.npmjs.org/miniget/-/miniget-1.6.1.tgz", 22 | "integrity": "sha512-I5oBwZmcaOuJrjQn7lpS29HM+aAZDbzKbX5ouxVyhFYdg6fA6YKOTwOCgzZQwlHuMek3FlCxz6eNrd4pOXbwOA==" 23 | }, 24 | "sanitize-filename": { 25 | "version": "1.6.3", 26 | "resolved": "https://registry.npmjs.org/sanitize-filename/-/sanitize-filename-1.6.3.tgz", 27 | "integrity": "sha512-y/52Mcy7aw3gRm7IrcGDFx/bCk4AhRh2eI9luHOQM86nZsqwiRkkq2GekHXBBD+SmPidc8i2PqtYZl+pWJ8Oeg==", 28 | "requires": { 29 | "truncate-utf8-bytes": "^1.0.0" 30 | } 31 | }, 32 | "sax": { 33 | "version": "1.2.4", 34 | "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", 35 | "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==" 36 | }, 37 | "truncate-utf8-bytes": { 38 | "version": "1.0.2", 39 | "resolved": "https://registry.npmjs.org/truncate-utf8-bytes/-/truncate-utf8-bytes-1.0.2.tgz", 40 | "integrity": "sha1-QFkjkJWS1W94pYGENLC3hInKXys=", 41 | "requires": { 42 | "utf8-byte-length": "^1.0.1" 43 | } 44 | }, 45 | "utf8-byte-length": { 46 | "version": "1.0.4", 47 | "resolved": "https://registry.npmjs.org/utf8-byte-length/-/utf8-byte-length-1.0.4.tgz", 48 | "integrity": "sha1-9F8VDExm7uloGGUFq5P8u4rWv2E=" 49 | }, 50 | "ytdl-core": { 51 | "version": "1.0.3", 52 | "resolved": "https://registry.npmjs.org/ytdl-core/-/ytdl-core-1.0.3.tgz", 53 | "integrity": "sha512-sBOVokjrAigKTEn248MJ+JpS5ifay/vBzYGMDeZhG61xmgthev6yHXBgEgm+M8ySDQXXVjOTmUtY3GHbX988KA==", 54 | "requires": { 55 | "html-entities": "^1.1.3", 56 | "m3u8stream": "^0.6.3", 57 | "miniget": "^1.6.0", 58 | "sax": "^1.1.3" 59 | } 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "ytdl-core": "1.0.3", 4 | "sanitize-filename": "1.6.3" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /strings/en.strings: -------------------------------------------------------------------------------- 1 | "ENTER_URL" = "Enter a YouTube URL"; 2 | "CANNOT_FIND_URL" = "Cannot find the link, please retry"; 3 | "DOWNLOADING" = "Downloading..."; 4 | "DOWNLOADED" = "Downloaded: "; 5 | "FINISHED_DOWNLOADING" = "Finished downloading"; -------------------------------------------------------------------------------- /strings/zh-Hans.strings: -------------------------------------------------------------------------------- 1 | "ENTER_URL" = "输入 YouTube 链接"; 2 | "CANNOT_FIND_URL" = "无法找到链接,请重试"; 3 | "DOWNLOADING" = "下载中..."; 4 | "DOWNLOADED" = "已下载:"; 5 | "FINISHED_DOWNLOADING" = "下载完成"; -------------------------------------------------------------------------------- /youtube-dl.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cyanzhong/jsbox-youtube-dl/9305d14927a1f79f6876de84d5aa6ebb06ad54e5/youtube-dl.zip --------------------------------------------------------------------------------