505 lines
14 KiB
Nim
505 lines
14 KiB
Nim
import
|
|
osproc, terminal, random, os,
|
|
strformat, strutils, json,
|
|
client, sequtils, parseutils,
|
|
httpclient, net
|
|
|
|
proc clear =
|
|
eraseScreen()
|
|
setCursorPos 0, 0
|
|
|
|
proc error*(str: string) =
|
|
styledEcho fgRed, "Error: ", str
|
|
quit QuitFailure
|
|
|
|
proc sayBlue(strList: varargs[string]) =
|
|
for str in strList:
|
|
setCursorXPos 5
|
|
styledEcho fgBlue, str
|
|
|
|
proc sayBye(str, auth: string, line = -1) =
|
|
if str == "":
|
|
error "no qoute"
|
|
|
|
styledEcho fgCyan, str, "..."
|
|
setCursorXPos 15
|
|
styledEcho fgGreen, "—", auth
|
|
|
|
if auth == "":
|
|
error "there can no be qoute without man"
|
|
if line != -1:
|
|
error fmt"@ line: {line} in qoute.json"
|
|
|
|
quit QuitSuccess
|
|
|
|
proc parseJ(x: string): JsonNode =
|
|
try:
|
|
return parseJson readFile x
|
|
except IOError:
|
|
error "base assets dont exist?"
|
|
|
|
proc parseJArray(file: string): seq[string] =
|
|
result = to(
|
|
parseJ(file){"pnimrp"},
|
|
seq[string]
|
|
)
|
|
|
|
if result.len mod 2 != 0:
|
|
error "JArrayResult.len not even"
|
|
|
|
proc exitEcho =
|
|
showCursor()
|
|
echo ""
|
|
randomize()
|
|
|
|
var
|
|
seq = parseJArray "assets/qoute.json"
|
|
qoutes, authors: seq[string] = @[]
|
|
|
|
for i in 0 .. seq.high:
|
|
case i mod 2:
|
|
of 0: qoutes.add seq[i]
|
|
else: authors.add seq[i]
|
|
|
|
var rand = rand qoutes.low .. qoutes.high
|
|
|
|
when not defined(release) or
|
|
not defined(danger):
|
|
echo fmt"free mem: {getFreeMem() / 1024} kB"
|
|
echo fmt"total/max mem: {getTotalMem() / 1024} kB"
|
|
echo fmt"occupied mem: {getOccupiedMem() / 1024} kB"
|
|
|
|
sayBye(
|
|
qoutes[rand],
|
|
authors[rand],
|
|
rand * 2
|
|
)
|
|
|
|
proc say(txt: string) =
|
|
styledEcho fgYellow, txt
|
|
|
|
proc sayPos(x: int, a: string; echo = true) =
|
|
setCursorXPos x
|
|
if echo: styledEcho fgGreen, a
|
|
else: stdout.styledWrite fgGreen, a
|
|
|
|
proc sayIter(txt: string) =
|
|
for f in splitLines txt:
|
|
setCursorXPos 5
|
|
styledEcho fgBlue, f
|
|
|
|
proc sayIter(txt: seq[string]; ret = true) =
|
|
var
|
|
chars =
|
|
@[
|
|
'1', '2', '3', '4', '5',
|
|
'6', '7', '8', '9', 'A',
|
|
'B', 'C', 'D', 'E', 'F',
|
|
'G', 'H', 'I', 'J', 'K',
|
|
'L', 'M', 'N', 'O', 'P',
|
|
'Q', 'R', 'S', 'T', 'U',
|
|
'V', 'W', 'X', 'Y', 'Z'
|
|
]
|
|
num = 0
|
|
for f in txt:
|
|
if f != "Notes": sayBlue fmt"{$chars[num]} {f}"
|
|
else: sayBlue "N Notes"
|
|
inc num
|
|
if ret: sayBlue "R Return"
|
|
sayBlue "Q Quit"
|
|
|
|
proc warn(txt: string; x = -1) =
|
|
if x != -1: setCursorXPos x
|
|
styledEcho fgRed, txt
|
|
#if echo == false: stdout.styledWrite fgRed,txt
|
|
#default Args dosent seem to be working?
|
|
sleep 750
|
|
|
|
proc inv =
|
|
cursorDown()
|
|
warn "INVALID CHOICE", 4
|
|
cursorUp()
|
|
eraseLine()
|
|
cursorUp()
|
|
|
|
template sayTermDraw8() =
|
|
say "Poor Mans Radio Player in Nim-lang " & '-'.repeat int terminalWidth() / 8
|
|
proc sayTermDraw12() =
|
|
sayPos 0, '-'.repeat((terminalWidth()/8).int) & '>'.repeat int terminalWidth() / 12
|
|
|
|
proc drawMenu(sub: string, x: string | seq[string], sect = "") =
|
|
clear()
|
|
if sect == "": say fmt"PNimRP > {sub}"
|
|
else: say fmt"PNimRP > {sub} > {sect}"
|
|
|
|
sayTermDraw12()
|
|
if sect == "": sayPos 4, fmt"{sub} Station Playing Music:"
|
|
else: sayPos 4, fmt"{sect} Station Playing Music:"
|
|
sayIter x
|
|
|
|
proc exec(x: string, args: openArray[string]; stream = false) =
|
|
discard waitForExit startProcess(x, args = args,
|
|
options =
|
|
if stream: {poUsePath, poParentStreams}
|
|
else: {poUsePath}
|
|
)
|
|
|
|
template cE(s: cint) = checkError s
|
|
|
|
proc exit(ctx: ptr Handle, isPaused: bool) =
|
|
if not isPaused:
|
|
ctx.terminateDestroy
|
|
exitEcho()
|
|
|
|
proc notes =
|
|
while true:
|
|
var j = false
|
|
const sub = "Notes"
|
|
clear()
|
|
say "PNimRP > " & sub
|
|
sayTermDraw12()
|
|
|
|
sayIter """PNimRP Copyright (C) 2021-2024 antonl05/bloomingchad
|
|
This program comes with ABSOLUTELY NO WARRANTY
|
|
This is free software, and you are welcome to redistribute
|
|
under certain conditions."""
|
|
while true:
|
|
case getch():
|
|
of 'r', 'R': j = true; break
|
|
of 'Q', 'q': exitEcho()
|
|
else: inv()
|
|
if j: break
|
|
|
|
proc init(parm: string, ctx: ptr Handle) =
|
|
let file = allocCStringArray ["loadfile", parm] #couldbe file,link,playlistfile
|
|
var val: cint = 1
|
|
cE ctx.setOption("osc", fmtFlag, addr val)
|
|
cE initialize ctx
|
|
cE ctx.cmd file
|
|
|
|
proc cleanLink(linke: string): string =
|
|
var link = linke
|
|
link.removePrefix "http://"
|
|
link.removePrefix "https://"
|
|
rsplit(link, "/", maxSplit = 1)[0] #no use strutils.delete() for nimv1.2.14
|
|
|
|
#[proc checkHttpsOnly(linke: string): bool =
|
|
var link = linke
|
|
try:
|
|
var client = newHttpClient()
|
|
link = "http://" & cleanLink link
|
|
discard client.getContent(link & "/currentsong")
|
|
except ProtocolError:
|
|
link.removePrefix "http://"
|
|
link = "https://" & link
|
|
return true
|
|
except: return false
|
|
return false]#
|
|
|
|
proc getCurrentSong(linke: string): string =
|
|
#https and http can be connect checked w/o ssl
|
|
#use mpv stream_lavf.c to get icy-title from audio buffer
|
|
var
|
|
client = newHttpClient()
|
|
link = linke
|
|
|
|
link = "http://" & cleanLink link
|
|
try: #shoutcast
|
|
echo "getCurrentSong: ", link
|
|
client.getContent(link & "/currentsong")
|
|
except ProtocolError: #ICY404 Resource Not Found?
|
|
"notimplemented"
|
|
except HttpRequestError: #icecast
|
|
try:
|
|
to(
|
|
parseJson(
|
|
client.getContent(link & "/status-json.xsl")
|
|
){"icestats"}{"source"}[1]{"yp_currently_playing"},
|
|
string
|
|
)
|
|
except HttpRequestError,
|
|
JsonParsingError, #different technique than implemented
|
|
ProtocolError, #connection refused?
|
|
KeyError:
|
|
"notimplemented"
|
|
|
|
proc splitLink(str: string):seq[string] = rsplit(str, ":", maxSplit = 1)
|
|
|
|
proc isHttps(link: string): bool = link.startsWith "https://"
|
|
|
|
proc doesLinkWork(link: string; isHttps = false): bool =
|
|
echo "doeslinkworkInit: " & link
|
|
if isHttps: return true
|
|
var seq = splitLink cleanLink link
|
|
|
|
echo "doesLinkWorkSeq: ", seq
|
|
if seq.len == 1: return true #we cannot check w/o port
|
|
try:
|
|
newSocket().connect(
|
|
seq[0],
|
|
Port(uint16 parseInt seq[1]),
|
|
timeout = 2000)
|
|
echo "link dont cause except"
|
|
return true
|
|
except HttpRequestError: warn "HttpRequestError. bad link?"
|
|
#retuns false default in except
|
|
except OSError:warn "OSError. No Internet? ConnectionRefused?"
|
|
except TimeoutError: warn "timeout of 3s failed"
|
|
|
|
proc call(sub: string; sect = ""; stat,
|
|
linke: string): Natural {.discardable.} =
|
|
var link = linke
|
|
if link == "": warn "link empty"
|
|
elif link.contains " ": warn "link dont exist or is invalid"
|
|
else:
|
|
clear()
|
|
if sect == "": say fmt"PNimRP > {sub} > {stat}"
|
|
else: say fmt"PNimRP > {sub} > {sect} > {stat}"
|
|
sayTermDraw12()
|
|
|
|
if not doesLinkWork(link, isHttps = isHttps(link)):
|
|
warn "no link work"
|
|
return
|
|
let ctx = create()
|
|
init link, ctx
|
|
var
|
|
echoPlay = true
|
|
event = ctx.waitEvent 0 #arm: pthread_mutex_lock
|
|
isPaused = false
|
|
nowPlayingExcept = false
|
|
echo "link in call() before while true: " & link
|
|
|
|
while true:
|
|
if not isPaused: #pthread_mutex_lock:
|
|
#(termux_arm)destroyer pause called on mutex that was destroyed.
|
|
var event = ctx.waitEvent 0
|
|
if event.eventID in [IDEndFile, IDShutdown, IDIdle]:
|
|
warn "end of file? bad link?"
|
|
terminateDestroy ctx
|
|
break
|
|
if echoPlay:
|
|
#var event = ctx.waitEvent 1000
|
|
|
|
var currentSong = getCurrentSong link
|
|
case currentSong:
|
|
of "notimplemented": sayPos 4, "Playing"
|
|
else: sayPos 4, "Now Playing: " & currentSong
|
|
cursorUp()
|
|
echoPlay = false
|
|
|
|
#remove cursorUp?
|
|
#add time check playing error link
|
|
#if not isPaused:
|
|
#var t0 = now().second
|
|
#event = ctx.waitEvent 1000
|
|
#if now().second - t0 >= 5:
|
|
# error "timeout of 5s"
|
|
|
|
case getch():
|
|
of 'u', 'U':
|
|
#lyrics update func -> just to restart while true
|
|
|
|
#[cursorDown()
|
|
sayPos 4, "Updated"
|
|
|
|
eraseLine()
|
|
cursorUp()]#
|
|
discard
|
|
of 'p', 'P':
|
|
if isPaused:
|
|
if nowPlayingExcept != true:
|
|
cursorUp()
|
|
eraseLine()
|
|
cursorDown()
|
|
eraseLine()
|
|
if nowPlayingExcept != true:
|
|
cursorUp()
|
|
|
|
var val: cint = 0
|
|
cE ctx.setProperty("pause", fmtFlag, addr val)
|
|
echoPlay = true
|
|
isPaused = false
|
|
|
|
else:
|
|
eraseLine()
|
|
warn "Paused", 4
|
|
cursorUp()
|
|
|
|
var val: cint = 1
|
|
cE ctx.setProperty("pause", fmtFlag, addr val)
|
|
isPaused = true
|
|
|
|
of 'm', 'M':
|
|
if isPaused:
|
|
if nowPlayingExcept != true:
|
|
cursorUp()
|
|
eraseLine()
|
|
cursorDown()
|
|
eraseLine()
|
|
if nowPlayingExcept != true:
|
|
cursorUp()
|
|
|
|
var val: cint = 0
|
|
cE ctx.setProperty("mute", fmtFlag, addr val)
|
|
echoPlay = true
|
|
isPaused = false
|
|
|
|
else:
|
|
eraseLine()
|
|
warn "Muted", 4
|
|
cursorUp()
|
|
|
|
var val: cint = 1
|
|
cE ctx.setProperty("mute", fmtFlag, addr val)
|
|
isPaused = true
|
|
|
|
of '/', '+':
|
|
var val: cint
|
|
cE ctx.getProperty("volume", fmtInt64, addr val) #nim do var block isolation
|
|
#mpv cant access it? ->dont put outside while true
|
|
val += 5
|
|
cE ctx.setProperty("volume", fmtInt64, addr val)
|
|
#echo "Volume: ", val
|
|
|
|
#[var metadata: NodeList
|
|
echo "getPropreturnVal:", ctx.getProperty("metadata", fmtNodeMap, addr metadata)
|
|
echo "metadata", metadata.num
|
|
for i in 0 .. 100:
|
|
try:echo "metadatavalues", metadata.values[i]
|
|
except:discard]#
|
|
cursorDown()
|
|
warn fmt"Volume+: {val}" , 4
|
|
cursorUp()
|
|
eraseLine()
|
|
cursorUp()
|
|
|
|
of '*', '-':
|
|
var val: cint
|
|
cE ctx.getProperty("volume", fmtInt64, addr val)
|
|
val -= 5
|
|
cE ctx.setProperty("volume", fmtInt64, addr val)
|
|
echo "newVolume: ", val
|
|
|
|
cursorDown()
|
|
warn "Volume-", 4
|
|
cursorUp()
|
|
eraseLine()
|
|
cursorUp()
|
|
|
|
of 'r', 'R':
|
|
if not isPaused: terminateDestroy ctx
|
|
break
|
|
of 'q', 'Q': exit ctx, isPaused
|
|
else: inv()
|
|
|
|
proc initJsonLists(sub, file: string; sect = ""): seq[seq[string]] =
|
|
var n, l: seq[string] = @[]
|
|
var input = parseJArray file
|
|
|
|
for f in input.low .. input.high:
|
|
case f mod 2:
|
|
of 0: n.add input[f]
|
|
of 1:
|
|
if input[f].startsWith("http://") or
|
|
input[f].startsWith "https://":
|
|
l.add input[f]
|
|
else:
|
|
l.add "http://" & input[f]
|
|
else: discard
|
|
@[n, l]
|
|
|
|
proc initIndx*(dir = "assets"): seq[seq[string]] =
|
|
var files, names, dirs: seq[string]
|
|
|
|
for file in walkFiles(dir & "/*"):
|
|
if dir == "assets":
|
|
if file != "assets/qoute.json":
|
|
files.add file
|
|
else: files.add file
|
|
var procFile = file
|
|
procFile.removePrefix(dir & "/")
|
|
procFile[0] = procFile[0].toUpperAscii
|
|
procFile.removeSuffix ".json"
|
|
if dir == "assets":
|
|
if procFile != "Qoute":
|
|
names.add procFile
|
|
else: names.add procFile
|
|
|
|
for directory in walkDirs(dir & "/*"):
|
|
var procDir = directory
|
|
procDir.removePrefix(dir & "/")
|
|
procDir = procDir & "/"
|
|
if not(procDir[0].isUpperAscii()):
|
|
discard parseChar($procDir[0].toUpperAscii(), procDir[0])
|
|
dirs.add procDir
|
|
|
|
if dir == "assets": names.add "Notes"
|
|
@[names, files, dirs]
|
|
|
|
proc menu(sub, file: string; sect = ""; ) =
|
|
let
|
|
list = initJsonLists(sub, file, sect)
|
|
n = list[0]
|
|
l = list[1]
|
|
|
|
while true:
|
|
var j = false
|
|
drawMenu sub, n, sect
|
|
#add conditiinal check for every if len not thereown size
|
|
#else no use danger use release
|
|
while true:
|
|
try:
|
|
case getch():
|
|
of '1': call sub, sect, n[0], l[0]; break
|
|
of '2': call sub, sect, n[1], l[1]; break
|
|
of '3': call sub, sect, n[2], l[2]; break
|
|
of '4': call sub, sect, n[3], l[3]; break
|
|
of '5': call sub, sect, n[4], l[4]; break
|
|
of '6': call sub, sect, n[5], l[5]; break
|
|
of '7': call sub, sect, n[6], l[6]; break
|
|
of '8': call sub, sect, n[7], l[7]; break
|
|
of '9': call sub, sect, n[8], l[8]; break
|
|
of 'A', 'a': call sub, sect, n[9], l[9]; break
|
|
of 'B', 'b': call sub, sect, n[10], l[10]; break
|
|
of 'C', 'c': call sub, sect, n[11], l[11]; break
|
|
of 'D', 'd': call sub, sect, n[12], l[12]; break
|
|
of 'E', 'e': call sub, sect, n[13], l[13]; break
|
|
of 'F', 'f': call sub, sect, n[14], l[14]; break
|
|
of 'R', 'r': j = true; break
|
|
of 'Q', 'q': exitEcho()
|
|
else: inv()
|
|
except IndexDefect: inv()
|
|
if j: break
|
|
|
|
proc menu*(names, files, dirs: seq[string]) =
|
|
#TODO menu dynamic selection; only 15 items possible!
|
|
while true:
|
|
clear()
|
|
#add drawMenu
|
|
sayTermDraw8()
|
|
sayPos 4, "Station Categories:"
|
|
sayIter names & dirs, ret = false
|
|
try:
|
|
while true:
|
|
case getch():
|
|
of '1': menu names[0], files[0]; break
|
|
of '2': menu names[1], files[1]; break
|
|
of '3': menu names[2], files[2]; break
|
|
of '4': menu names[3], files[3]; break
|
|
of '5': menu names[4], files[4]; break
|
|
of '6': menu names[5], files[5]; break
|
|
of '7': menu names[6], files[6]; break
|
|
of '8': menu names[7], files[7]; break
|
|
of '9': menu names[8], files[8]; break
|
|
of 'A', 'a': menu names[9], files[9]; break
|
|
of 'B', 'b': menu names[10], files[10]; break
|
|
of 'C', 'c': menu names[11], files[11]; break
|
|
of 'D', 'd': menu names[12], files[12]; break
|
|
of 'E', 'e': menu names[13], files[13]; break
|
|
of 'F', 'f': menu names[14], files[14]; break
|
|
of 'N', 'n': notes(); break
|
|
of 'q', 'Q': exitEcho()
|
|
else: inv()
|
|
except IndexDefect:
|
|
inv()
|