From da4c8c2fda07812e643363114c67df3ec0d3925e Mon Sep 17 00:00:00 2001 From: piotrruss Date: Sun, 30 May 2021 19:20:42 +0200 Subject: init commit --- yta | 170 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 170 insertions(+) create mode 100755 yta (limited to 'yta') diff --git a/yta b/yta new file mode 100755 index 0000000..851e849 --- /dev/null +++ b/yta @@ -0,0 +1,170 @@ +#!/usr/bin/env node +const spawn = require('child_process').spawn; +const colors = require('colors'); +const prompt = require('prompt'); +const fetch = require('node-fetch'); +const { YTKEY } = require('./config'); + +let data = {ids: [], titles: [], descriptions: [], channels: [], durations: []}; + +const clearOutput = () => console.clear(); + +const showError = (msg) => console.log(colors.red('Error'), ' - ', msg); + +const showMsg = (msg) => console.log(colors.green(msg)); + +const showOsd = () => { + data.ids.forEach((id, i) => { + console.log(`${colors.yellow(i+1)}. ${colors.cyan(data.titles[i])} ${colors.green('['+data.durations[i]+']')} ${colors.red(data.channels[i])}`); + }); + console.log(''); +}; + +const showPlaying = (n) => console.log(`${colors.green('Currently playing:\n------------------')}\n${colors.yellow('Title:')} ${colors.cyan(data.titles[n])}\n${colors.yellow('Description:')} ${colors.cyan(data.descriptions[n])}\n${colors.yellow('Channel:')} ${colors.cyan(data.channels[n])}\n${colors.yellow('Length:')} ${colors.cyan(data.durations[n])}\n`); + +const showPlaylist = (r,n) => console.log(r.map(x=>x===n?(colors.yellow(x+'. '+data.titles[x-1])):(colors.cyan(x+'. '+data.titles[x-1]))).join('\n'),'\n'); + +const showHelp = () => console.log(colors.green('YT audio player\n---------------\n1. Type song name and press [ENTER] to search it in YT\n2. Type result number and press [ENTER] to play the song\n3. Type a range of songs (ex. [2-5]) to play them in a row\n4. Type "h" and press [ENTER] to see this help page\n5. Type "q" and press [ENTER] to quit YT audio player\n')); + +const searchUrl = (q) => `https://www.googleapis.com/youtube/v3/search?part=snippet&partcontentDetails&maxResults=20&q=${q}&type=video&key=${YTKEY}`; + +const detailsUrl = (a) => `https://www.googleapis.com/youtube/v3/videos?part=contentDetails&id=${a.join(',')}&key=${YTKEY}`; + +const isSongRange = q => q.match(/^[0-9]+-[0-9]+$/); + +const isSongName = q => q.match(/.{3,}/); + +const isSongNumber = q => q.match(/^[0-9]+$/) && parseInt(q) <= data.ids.length; + +const playNr = (n) => spawn('mpv', ['--no-video', '--msg-level=all=no,statusline=status', `https://www.youtube.com/watch?v=${data.ids[parseInt(n)]}`], { stdio: 'inherit' }); + +const playNext = (r, n) => { + clearOutput(); + showMsg('Playlist:\n---------'); + showPlaylist(r, n); + showPlaying(n-1); + + const player = playNr(n-1); + + player.on('close', () => { + if (n < r[r.length-1]){ + playNext(r, n+1); + } else { + clearOutput(); + showOsd(); + showPrompt(); + } + }); +}; + +const handleSongRange = (q) => { + if (data.ids.length > 0) { + const [a,b] = q.split('-').map(n=>parseInt(n)); + if (an+a); + playNext(range, range[0]); + } else { + clearOutput(); + showOsd(); + showError('invalid range, try again'); + showPrompt(); + } + } else { + clearOutput(); + showError('first search for the videos'); + showPrompt(); + } +}; + +const handleSongName = (q) => ( + fetch(searchUrl(q)) + .then(res => res.json()) + .then(json => presentResults(json)) + .catch(e => console.log(e)) +); + +const handleSongNumber = (q) => { + const nr = parseInt(q) - 1; + clearOutput(); + showPlaying(nr); + const player = playNr(nr); + player.on('close', () => { + clearOutput(); + showOsd(); + showPrompt(); + }); +}; + +const handleHelp = () => { + clearOutput(); + showHelp(); + showPrompt(); +}; + +const handleQuit = () => { + clearOutput(); + showMsg('Quiting YT audio player...'); + return 0; +}; + +const handleWrongInput = () => { + clearOutput(); + showOsd(); + showError('wrong input'); + showPrompt(); +}; + +const getDurations = (ids) => ( + fetch(detailsUrl(ids)) + .then(res => res.json()) + .then(json => json.items.map(i => i.contentDetails.duration.replace(/(PT|S)/g,'').split(/[A-Z]/g).map(d => d.padStart(2, '0')).join(':'))) + .catch(e => console.log(e)) +); + +const presentResults = (results) => { + data.titles = results.items.map(result => result.snippet.title); + data.descriptions = results.items.map(result => result.snippet.description); + data.channels = results.items.map(result => result.snippet.channelTitle); + data.ids = results.items.map(result => result.id.videoId); + getDurations(data.ids).then(durations => { + data.durations = durations; + clearOutput(); + showOsd(); + showPrompt(); + }); +}; + +const handlePrompt = (err, { query }) => { + if (err) { + console.log(err); + return 1; + } + + if (isSongRange(query)) { + handleSongRange(query); + } else if (isSongName(query)) { + handleSongName(query); + } else if (isSongNumber(query)) { + handleSongNumber(query); + } else if (query === 'h') { + handleHelp(); + } else if (query === 'q') { + handleQuit(); + } else { + handleWrongInput(); + } +}; + +const showPrompt = () => { + prompt.message = ""; + prompt.delimiter = ""; + prompt.start(); + prompt.get({ + name: 'query', + description: 'YT Search ([h]elp, [q]uit):', + required: true, + }, handlePrompt); +} + +clearOutput(); +showPrompt(); -- cgit v1.2.3