aboutsummaryrefslogtreecommitdiffstats
path: root/apps
diff options
context:
space:
mode:
authorGravatar piotrruss <mail@pruss.it> 2021-10-17 19:47:26 +0200
committerGravatar piotrruss <mail@pruss.it> 2022-02-28 20:36:12 +0000
commitcd726524db4385fb33cf3520d0e6ef6b0ac78af3 (patch)
tree2b53eb1986b34402c2a36d38398fed64a16d65d8 /apps
downloadyt_audio_player_scrap-cd726524db4385fb33cf3520d0e6ef6b0ac78af3.tar.gz
yt_audio_player_scrap-cd726524db4385fb33cf3520d0e6ef6b0ac78af3.tar.bz2
yt_audio_player_scrap-cd726524db4385fb33cf3520d0e6ef6b0ac78af3.zip
Diffstat (limited to 'apps')
-rwxr-xr-xapps/yt203
1 files changed, 203 insertions, 0 deletions
diff --git a/apps/yt b/apps/yt
new file mode 100755
index 0000000..3cfcc5b
--- /dev/null
+++ b/apps/yt
@@ -0,0 +1,203 @@
+#!/usr/bin/env node
+/* pruss.it */
+
+const https = require('https')
+const readline = require("readline")
+const spawn = require('child_process').spawn
+
+let results = []
+
+const rl = readline.createInterface({
+ input: process.stdin,
+ output: process.stdout
+})
+
+const p = {
+ log: m => console.log(m),
+ cls: () => console.clear(),
+ inf: m => console.log(`\x1b[32m${m}\x1b[0m`),
+ err: e => console.error(`\x1b[31m${e}\x1b[0m`),
+ prt: () => prompt(),
+ res: () => showResults()
+}
+
+const joinRuns = t => t
+ ? t.runs
+ ? t.runs.map(r => r.text).join('').split(/\r\n|\r|\n/).slice(0,14).join('\n')
+ : t.simpleText
+ : '-'
+
+const convertViews = (v) => {
+ const t = v ? v.split(' ')[0].split('.') : '-'
+ switch(t.length) {
+ case 2: return t[0] + 'K'
+ case 3: return t[0] + 'M'
+ case 4: return t[0] + 'B'
+ default: return t
+ }
+}
+
+const findNested = (obj, key) => {
+ let r
+ for (let k in obj) {
+ if (obj.hasOwnProperty(k)) {
+ if (k === key) return obj[key]
+ else if (typeof obj[k] === "object") {
+ r = findNested(obj[k], key)
+ if (typeof r !== "undefined") return r
+ }
+ }
+ }
+}
+
+const findAllNested = (o, p) => {
+ var r = []
+ const f = (n, r) => {
+ if(n.hasOwnProperty(p)) r.push(n[p])
+ for(var i=0; i<Object.keys(n).length; i++){
+ if(typeof n[Object.keys(n)[i]] == "object"){
+ f(n[Object.keys(n)[i]], r)
+ }
+ }
+ return r
+ }
+ return f(o, r)
+}
+
+const showResults = () => {
+ results.forEach((r, i) => {
+ p.log(`\x1b[33m${i+1}. \x1b[36m${r.title} \x1b[32m[${r.length}] \x1b[35m${r.channel} \x1b[31m${r.views}\x1b[0m`)
+ })
+ p.log(' ')
+}
+
+const parseVideos = o => o.map(v => ({
+ id: v.videoId,
+ title: joinRuns(v.title),
+ length: v?.lengthText?.simpleText,
+ views: convertViews(v?.viewCountText?.simpleText),
+ channel: joinRuns(v?.shortBylineText)
+}))
+
+const handleResObj = (x) => {
+ const n = (findAllNested(JSON.parse(x[0])['contents'], 'videoRenderer'))
+ results = parseVideos(n)
+ p.cls(); p.res(); p.prt()
+}
+
+const objFromRes = (r, c) => {
+ const x = r.match(/(?<=ytInitialData = )(.*?)(?=;<\/script>)/gm)
+ if (x) c(x)
+}
+
+const getResponse = (u, c) => https.get(u, res => {
+ var r = ''
+ res.on('data', d => { r += d })
+ res.on('end', () => { objFromRes(r, c) })
+ res.on('error', e => { p.err(e) })
+})
+
+const isSongRange = q => q.match(/^[0-9]+-[0-9]+$/)
+
+const isSongName = q => q.match(/.{3,}/)
+
+const isRelated = q => q.match(/^r[0-9]+$/) && parseInt(q.replace('r','')) <= results.length
+
+const isSongNumber = q => q.match(/^[0-9]+$/) && parseInt(q) <= results.length
+
+const showPlaying = n => {
+ getResponse(('https://www.youtube.com/watch?v='+results[n].id), x => {
+ p.log('\x1b[32mDescription:\n------------\x1b[36m')
+ p.log(joinRuns(findNested(JSON.parse(x)['contents'], 'description')), '\x1b[0m')
+ p.log(' ')
+ })
+ p.log(`\n\x1b[32mCurrently playing:\n------------------\n\x1b[32mTitle: \x1b[36m${results[n].title}\n\x1b[32mChannel: \x1b[36m${results[n].channel}\n\x1b[32mLength: \x1b[36m${results[n].length}\n\x1b[32mViews: \x1b[36m${results[n].views}\x1b[0m\n`)
+}
+
+const showPlaylist = (r, n) => p.log(r.map(x=>`${x===n?'\x1b[33m':'\x1b[36m'}${x}. ${results[x-1].title}\x1b[0m`).join('\n'),'\n')
+
+const playNr = (n) => spawn('mpv', ['--no-video', '--msg-level=all=no,statusline=status', `https://www.youtube.com/watch?v=${results[parseInt(n)].id}`], { stdio: 'inherit' })
+
+const playNext = (r, n) => {
+ p.cls()
+ p.inf('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 {
+ p.cls(); p.res(); p.prt()
+ }
+ })
+}
+
+const handleSongRange = q => {
+ if (results.length > 0) {
+ const [a,b] = q.split('-').map(n=>parseInt(n))
+ if (a<b){
+ const range = [...Array(b-a+1).keys()].map(n=>n+a)
+ playNext(range, range[0])
+ } else {
+ p.cls(); p.res(); p.err('invalid range, try again'); p.prt()
+ }
+ } else {
+ p.cls(); p.err('first search for the videos'); p.prt()
+ }
+};
+
+const handleRelated = q => getResponse(('https://www.youtube.com/watch?v='+results[parseInt(q.substring(1)) - 1].id), x => {
+ results = parseVideos(findAllNested(JSON.parse(x)['contents'], 'compactVideoRenderer'))
+ p.cls(); p.res(); p.prt()
+})
+
+const handleSongName = q => getResponse(('https://www.youtube.com/results?search_query='+q.replaceAll(' ', '+')), handleResObj)
+
+const handleSongNumber = q => {
+ const nr = parseInt(q) - 1
+ p.cls(); showPlaying(nr)
+ const player = playNr(nr)
+ player.on('close', () => { p.cls(); p.res(); p.prt() })
+}
+
+const handleHelp = () => {
+ p.cls()
+ p.log('\x1b[36mYT Audio Player (HELP)\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 [r(nr)] (ex. [r2]) to find songs related to chosen result\n5. Type [h] and press [ENTER] to see this help page\n6. Type [q] and press [ENTER] to quit YT audio player\x1b[0m\n')
+ p.prt()
+}
+
+const handleQuit = () => {
+ rl.close(); p.cls(); p.inf('See you!\n\n'); return 0
+}
+
+const handleWrongInput = () => {
+ p.cls(); p.res(); p.err('wrong input\n'); p.prt()
+}
+
+const handlePrompt = q => {
+ rl.pause()
+
+ if (isSongRange(q)) {
+ handleSongRange(q)
+ } else if (isRelated(q)) {
+ handleRelated(q)
+ } else if (isSongName(q)) {
+ handleSongName(q)
+ } else if (isSongNumber(q)) {
+ handleSongNumber(q)
+ } else if (q === 'h') {
+ handleHelp()
+ } else if (q === 'q') {
+ handleQuit()
+ } else {
+ handleWrongInput()
+ }
+}
+
+const prompt = () => {
+ rl.resume()
+ rl.question("\x1b[32mYT Audio Player ([h]elp, [q]uit): \x1b[0m", k => { handlePrompt(k) })
+}
+
+p.cls(); p.prt()