This commit is contained in:
Crispy 2025-07-03 01:23:46 +02:00
parent 491112768c
commit 68ec37f994
66 changed files with 6591 additions and 10096 deletions

1
computer/28/char Normal file
View file

@ -0,0 +1 @@


View file

@ -0,0 +1,477 @@
local function e(t,a)for o,i in pairs(a)do if type(i)=="table"then i=e({},i)end
t[o]=i end return t end local function n(s,h,r)local d={}d.__index=d if r then
d.__prototype=e(e({},r.__prototype),s)else d.__prototype=s end if h then local
l={}for u,c in ipairs(h)do local m,f=c[1],c[2]d[m]=function(w,y)if not
f(w,y)then w["_"..m]=y end return w end l[m]=true end function
d.__call(p,...)if type((...))=="table"then for v,b in pairs((...))do if
l[v]then p[v](p,b)end end else local g=select("#",...)for k,q in ipairs(h)do if
k>g or k>h.args then break end local arg=select(k,...)if arg~=nil then
p[q[1]](p,arg)end end end return p end end local j={}j.__index=r function
j.__call(x,...)local z=e({},x.__prototype)setmetatable(z,x)return z(...)end
return setmetatable(d,j)end local function E(T,A,O)for I,N in ipairs(A)do if
type(O)==N then return true end end
error(("bad property '%s' (%s expected, got %s)"):format(T,table.concat(A," or "),type(O)))end
local function S(H,...)local R={...}return{H,function(D,L)E(H,R,L)end}end local
U={"name",function(C,M)E("name",{"string"},M)for F in M:gmatch("%S+")do
C._name=C._name or F
table.insert(C._aliases,F)table.insert(C._public_aliases,F)if
F:find("_",1,true)then table.insert(C._aliases,(F:gsub("_","-")))end end return
true end}local W={"hidden_name",function(Y,P)E("hidden_name",{"string"},P)for V
in P:gmatch("%S+")do table.insert(Y._aliases,V)if V:find("_",1,true)then
table.insert(Y._aliases,(V:gsub("_","-")))end end return true end}local
function B(G)if tonumber(G)then return tonumber(G),tonumber(G)end if G=="*"then
return 0,math.huge end if G=="+"then return 1,math.huge end if G=="?"then
return 0,1 end if G:match"^%d+%-%d+$"then local
K,Q=G:match"^(%d+)%-(%d+)$"return tonumber(K),tonumber(Q)end if
G:match"^%d+%+$"then local J=G:match"^(%d+)%+$"return tonumber(J),math.huge end
end local function X(Z)return{Z,function(et,tt)E(Z,{"number","string"},tt)local
at,ot=B(tt)if not at then error(("bad property '%s'"):format(Z))end
et["_min"..Z],et["_max"..Z]=at,ot end}end local it={}local
nt={"action",function(st,ht)E("action",{"function","string"},ht)if
type(ht)=="string"and not it[ht]then
error(("unknown action '%s'"):format(ht))end end}local
rt={"init",function(dt)dt._has_init=true end}local
lt={"default",function(ut,ct)if type(ct)~="string"then ut._init=ct
ut._has_init=true return true end end}local
mt={"add_help",function(ft,wt)E("add_help",{"boolean","string","table"},wt)if
ft._help_option_idx then
table.remove(ft._options,ft._help_option_idx)ft._help_option_idx=nil end if wt
then local
yt=ft:flag():description"Show this help message and exit.":action(function()print(ft:get_help())error()end)if
wt~=true then yt=yt(wt)end if not yt._name then yt"-h""--help"end
ft._help_option_idx=#ft._options end end}local
pt=n({_arguments={},_options={},_commands={},_mutexes={},_groups={},_require_command=true,_handle_options=true},{args=3,S("name","string"),S("description","string"),S("epilog","string"),S("usage","string"),S("help","string"),S("require_command","boolean"),S("handle_options","boolean"),S("action","function"),S("command_target","string"),S("help_vertical_space","number"),S("usage_margin","number"),S("usage_max_width","number"),S("help_usage_margin","number"),S("help_description_margin","number"),S("help_max_width","number"),mt})local
vt=n({_aliases={},_public_aliases={}},{args=3,U,S("description","string"),S("epilog","string"),W,S("summary","string"),S("target","string"),S("usage","string"),S("help","string"),S("require_command","boolean"),S("handle_options","boolean"),S("action","function"),S("command_target","string"),S("help_vertical_space","number"),S("usage_margin","number"),S("usage_max_width","number"),S("help_usage_margin","number"),S("help_description_margin","number"),S("help_max_width","number"),S("hidden","boolean"),mt},pt)local
bt=n({_minargs=1,_maxargs=1,_mincount=1,_maxcount=1,_defmode="unused",_show_default=true},{args=5,S("name","string"),S("description","string"),lt,S("convert","function","table"),X("args"),S("target","string"),S("defmode","string"),S("show_default","boolean"),S("argname","string","table"),S("choices","table"),S("hidden","boolean"),nt,rt})local
gt=n({_aliases={},_public_aliases={},_mincount=0,_overwrite=true},{args=6,U,S("description","string"),lt,S("convert","function","table"),X("args"),X("count"),W,S("target","string"),S("defmode","string"),S("show_default","boolean"),S("overwrite","boolean"),S("argname","string","table"),S("choices","table"),S("hidden","boolean"),nt,rt},bt)function
pt:_inherit_property(kt,qt)local jt=self while true do local xt=jt["_"..kt]if
xt~=nil then return xt end if not jt._parent then return qt end jt=jt._parent
end end function bt:_get_argument_list()local zt={}local Et=1 while
Et<=math.min(self._minargs,3)do local Tt=self:_get_argname(Et)if self._default
and self._defmode:find"a"then Tt="["..Tt.."]"end table.insert(zt,Tt)Et=Et+1 end
while Et<=math.min(self._maxargs,3)do
table.insert(zt,"["..self:_get_argname(Et).."]")Et=Et+1 if
self._maxargs==math.huge then break end end if Et<self._maxargs then
table.insert(zt,"...")end return zt end function bt:_get_usage()local
At=table.concat(self:_get_argument_list()," ")if self._default and
self._defmode:find"u"then if self._maxargs>1 or(self._minargs==1 and not
self._defmode:find"a")then At="["..At.."]"end end return At end function
it.store_true(Ot,It)Ot[It]=true end function it.store_false(Nt,St)Nt[St]=false
end function it.store(Ht,Rt,Dt)Ht[Rt]=Dt end function it.count(Lt,Ut,Ct,Mt)if
not Mt then Lt[Ut]=Lt[Ut]+1 end end function
it.append(Ft,Wt,Yt,Pt)Ft[Wt]=Ft[Wt]or{}table.insert(Ft[Wt],Yt)if Pt then
table.remove(Ft[Wt],1)end end function it.concat(Vt,Bt,Gt,Kt)if Kt then
error("'concat' action can't handle too many invocations")end
Vt[Bt]=Vt[Bt]or{}for Qt,Jt in ipairs(Gt)do table.insert(Vt[Bt],Jt)end end
function bt:_get_action()local Xt,Zt if self._maxcount==1 then if
self._maxargs==0 then Xt,Zt="store_true",nil else Xt,Zt="store",nil end else if
self._maxargs==0 then Xt,Zt="count",0 else Xt,Zt="append",{}end end if
self._action then Xt=self._action end if self._has_init then Zt=self._init end
if type(Xt)=="string"then Xt=it[Xt]end return Xt,Zt end function
bt:_get_argname(ea)local ta=self._argname or self:_get_default_argname()if
type(ta)=="table"then return ta[ea]else return ta end end function
bt:_get_choices_list()return"{"..table.concat(self._choices,",").."}"end
function bt:_get_default_argname()if self._choices then return
self:_get_choices_list()else return"<"..self._name..">"end end function
gt:_get_default_argname()if self._choices then return
self:_get_choices_list()else return"<"..self:_get_default_target()..">"end end
function bt:_get_label_lines()if self._choices then
return{self:_get_choices_list()}else return{self._name}end end function
gt:_get_label_lines()local aa=self:_get_argument_list()if#aa==0 then
return{table.concat(self._public_aliases,", ")}end local oa=-1 for ia,na in
ipairs(self._public_aliases)do oa=math.max(oa,#na)end local
sa=table.concat(aa," ")local ha={}for ra,da in ipairs(self._public_aliases)do
local la=(" "):rep(oa-#da)..da.." "..sa if ra~=#self._public_aliases then
la=la..","end table.insert(ha,la)end return ha end function
vt:_get_label_lines()return{table.concat(self._public_aliases,", ")}end
function bt:_get_description()if self._default and self._show_default then if
self._description then
return("%s (default: %s)"):format(self._description,self._default)else
return("default: %s"):format(self._default)end else return self._description
or""end end function vt:_get_description()return self._summary or
self._description or""end function gt:_get_usage()local
ua=self:_get_argument_list()table.insert(ua,1,self._name)ua=table.concat(ua," ")if
self._mincount==0 or self._default then ua="["..ua.."]"end return ua end
function bt:_get_default_target()return self._name end function
gt:_get_default_target()local ca for ma,fa in ipairs(self._public_aliases)do if
fa:sub(1,1)==fa:sub(2,2)then ca=fa:sub(3)break end end ca=ca or
self._name:sub(2)return(ca:gsub("-","_"))end function gt:_is_vararg()return
self._maxargs~=self._minargs end function pt:_get_fullname(wa)local
ya=self._parent if wa and not ya then return""end local pa={self._name}while ya
do if not wa or ya._parent then table.insert(pa,1,ya._name)end ya=ya._parent
end return table.concat(pa," ")end function pt:_update_charset(va)va=va or{}for
ba,ga in ipairs(self._commands)do ga:_update_charset(va)end for ka,qa in
ipairs(self._options)do for ka,ja in ipairs(qa._aliases)do va[ja:sub(1,1)]=true
end end return va end function pt:argument(...)local
xa=bt(...)table.insert(self._arguments,xa)return xa end function
pt:option(...)local za=gt(...)table.insert(self._options,za)return za end
function pt:flag(...)return self:option():args(0)(...)end function
pt:command(...)local Ea=vt():add_help(true)(...)Ea._parent=self
table.insert(self._commands,Ea)return Ea end function pt:mutex(...)local
Ta={...}for Aa,Oa in ipairs(Ta)do local Ia=getmetatable(Oa)assert(Ia==gt or
Ia==bt,("bad argument #%d to 'mutex' (Option or Argument expected)"):format(Aa))end
table.insert(self._mutexes,Ta)return self end function
pt:group(Na,...)assert(type(Na)=="string",("bad argument #1 to 'group' (string expected, got %s)"):format(type(Na)))local
Sa={name=Na,...}for Ha,Ra in ipairs(Sa)do local
Da=getmetatable(Ra)assert(Da==gt or Da==bt or
Da==vt,("bad argument #%d to 'group' (Option or Argument or Command expected)"):format(Ha+1))end
table.insert(self._groups,Sa)return self end local La="Usage: "function
pt:get_usage()if self._usage then return self._usage end local
Ua=self:_inherit_property("usage_margin",#La)local
Ca=self:_inherit_property("usage_max_width",70)local
Ma={La..self:_get_fullname()}local function Fa(Wa)if#Ma[#Ma]+1+#Wa<=Ca then
Ma[#Ma]=Ma[#Ma].." "..Wa else Ma[#Ma+1]=(" "):rep(Ua)..Wa end end local
Ya={}local Pa={}local Va={}local Ba={}local function Ga(Ka,Qa)if Va[Ka]then
return end Va[Ka]=true local Ja={}for Xa,Za in ipairs(Ka)do if not Za._hidden
and not Pa[Za]then if getmetatable(Za)==gt or Za==Qa then
table.insert(Ja,Za:_get_usage())Pa[Za]=true end end end if#Ja==1 then
Fa(Ja[1])elseif#Ja>1 then Fa("("..table.concat(Ja," | ")..")")end end local
function eo(to)if not to._hidden and not Pa[to]then
Fa(to:_get_usage())Pa[to]=true end end for ao,oo in ipairs(self._mutexes)do
local no=false local so=false for ao,ho in ipairs(oo)do if getmetatable(ho)==gt
then if ho:_is_vararg()then no=true end else so=true
Ba[ho]=Ba[ho]or{}table.insert(Ba[ho],oo)end Ya[ho]=true end if not no and not
so then Ga(oo)end end for ro,lo in ipairs(self._options)do if not Ya[lo]and not
lo:_is_vararg()then eo(lo)end end for uo,co in ipairs(self._arguments)do local
mo if Ya[co]then for uo,fo in ipairs(Ba[co])do if not Va[fo]then mo=fo end end
end if mo then Ga(mo,co)else eo(co)end end for wo,yo in ipairs(self._mutexes)do
Ga(yo)end for po,vo in ipairs(self._options)do eo(vo)end if#self._commands>0
then if self._require_command then Fa("<command>")else Fa("[<command>]")end
Fa("...")end return table.concat(Ma,"\n")end local function bo(go)if go==""then
return{}end local ko={}if go:sub(-1)~="\n"then go=go.."\n"end for qo in
go:gmatch("([^\n]*)\n")do table.insert(ko,qo)end return ko end local function
jo(xo,zo)local Eo={}local To=xo:match("^ *")if xo:find("^ *[%*%+%-]")then
To=To.." "..xo:match("^ *[%*%+%-]( *)")end local Ao={}local Oo=0 local Io=1
while true do local No,So,Ho=xo:find("([^ ]+)",Io)if not No then break end
local Ro=xo:sub(Io,No-1)Io=So+1 if(#Ao==0)or(Oo+#Ro+#Ho<=zo)then
table.insert(Ao,Ro)table.insert(Ao,Ho)Oo=Oo+#Ro+#Ho else
table.insert(Eo,table.concat(Ao))Ao={To,Ho}Oo=#To+#Ho end end if#Ao>0 then
table.insert(Eo,table.concat(Ao))end if#Eo==0 then Eo[1]=""end return Eo end
local function Do(Lo,Uo)local Co={}for Mo,Fo in ipairs(Lo)do local
Wo=jo(Fo,Uo)for Mo,Yo in ipairs(Wo)do table.insert(Co,Yo)end end return Co end
function pt:_get_element_help(Po)local Vo=Po:_get_label_lines()local
Bo=bo(Po:_get_description())local Go={}local
Ko=self:_inherit_property("help_usage_margin",1)local Qo=(" "):rep(Ko)local
Jo=self:_inherit_property("help_description_margin",23)local
Xo=(" "):rep(Jo)local Zo=self:_inherit_property("help_max_width")if Zo then
local ei=math.max(Zo-Jo,10)Bo=Do(Bo,ei)end if#Vo[1]>=(Jo-Ko)then for ti,ai in
ipairs(Vo)do table.insert(Go,Qo..ai)end for oi,ii in ipairs(Bo)do
table.insert(Go,Xo..ii)end else for ni=1,math.max(#Vo,#Bo)do local
si=Vo[ni]local hi=Bo[ni]local ri=""if si then ri=Qo..si end if hi and
hi~=""then ri=ri..(" "):rep(Jo-#ri)..hi end table.insert(Go,ri)end end return
table.concat(Go,"\n")end local function di(li)local ui={}for ci,mi in
ipairs(li)do ui[getmetatable(mi)]=true end return ui end function
pt:_add_group_help(fi,wi,yi,pi)local vi={yi}for bi,gi in ipairs(pi)do if not
gi._hidden and not wi[gi]then wi[gi]=true
table.insert(vi,self:_get_element_help(gi))end end if#vi>1 then
table.insert(fi,table.concat(vi,("\n"):rep(self:_inherit_property("help_vertical_space",0)+1)))end
end function pt:get_help()if self._help then return self._help end local
ki={self:get_usage()}local qi=self:_inherit_property("help_max_width")if
self._description then local ji=self._description if qi then
ji=table.concat(Do(bo(ji),qi),"\n")end table.insert(ki,ji)end local
xi={[bt]={},[gt]={},[vt]={}}for zi,Ei in ipairs(self._groups)do local
Ti=di(Ei)for zi,Ai in ipairs({bt,gt,vt})do if Ti[Ai]then
table.insert(xi[Ai],Ei)break end end end local
Oi={{name="Arguments",type=bt,elements=self._arguments},{name="Options",type=gt,elements=self._options},{name="Commands",type=vt,elements=self._commands}}local
Ii={}for Ni,Si in ipairs(Oi)do local Hi=xi[Si.type]for Ni,Ri in ipairs(Hi)do
self:_add_group_help(ki,Ii,Ri.name..":",Ri)end local Di=Si.name..":"if#Hi>0
then Di="Other "..Di:gsub("^.",string.lower)end
self:_add_group_help(ki,Ii,Di,Si.elements)end if self._epilog then local
Li=self._epilog if qi then Li=table.concat(Do(bo(Li),qi),"\n")end
table.insert(ki,Li)end return table.concat(ki,"\n\n")end function
pt:add_help_command(Ui)if Ui then assert(type(Ui)=="string"or
type(Ui)=="table",("bad argument #1 to 'add_help_command' (string or table expected, got %s)"):format(type(Ui)))end
local
Ci=self:command():description"Show help for commands."Ci:argument"command":description"The command to show help for.":args"?":action(function(Mi,Mi,Fi)if
not Fi then print(self:get_help())error()else for Mi,Wi in
ipairs(self._commands)do for Mi,Yi in ipairs(Wi._aliases)do if Yi==Fi then
print(Wi:get_help())error()end end end end
Ci:error(("unknown command '%s'"):format(Fi))end)if Ui then Ci=Ci(Ui)end if not
Ci._name then Ci"help"end Ci._is_help_command=true return self end function
pt:_is_shell_safe()if self._basename then if
self._basename:find("[^%w_%-%+%.]")then return false end else for Pi,Vi in
ipairs(self._aliases)do if Vi:find("[^%w_%-%+%.]")then return false end end end
for Bi,Gi in ipairs(self._options)do for Bi,Ki in ipairs(Gi._aliases)do if
Ki:find("[^%w_%-%+%.]")then return false end end if Gi._choices then for Bi,Qi
in ipairs(Gi._choices)do if Qi:find("[%s'\"]")then return false end end end end
for Ji,Xi in ipairs(self._arguments)do if Xi._choices then for Ji,Zi in
ipairs(Xi._choices)do if Zi:find("[%s'\"]")then return false end end end end
for en,tn in ipairs(self._commands)do if not tn:_is_shell_safe()then return
false end end return true end function pt:add_complete(an)if an then
assert(type(an)=="string"or
type(an)=="table",("bad argument #1 to 'add_complete' (string or table expected, got %s)"):format(type(an)))end
local
on=self:option():description"Output a shell completion script for the specified shell.":args(1):choices{"bash","zsh","fish"}:action(function(nn,nn,sn)io.write(self["get_"..sn.."_complete"](self))error()end)if
an then on=on(an)end if not on._name then on"--completion"end return self end
function pt:add_complete_command(hn)if hn then assert(type(hn)=="string"or
type(hn)=="table",("bad argument #1 to 'add_complete_command' (string or table expected, got %s)"):format(type(hn)))end
local
rn=self:command():description"Output a shell completion script."rn:argument"shell":description"The shell to output a completion script for.":choices{"bash","zsh","fish"}:action(function(dn,dn,ln)io.write(self["get_"..ln.."_complete"](self))error()end)if
hn then rn=rn(hn)end if not rn._name then rn"completion"end return self end
local function un(cn)return cn:gsub("[/\\]*$",""):match(".*[/\\]([^/\\]*)")or
cn end local function mn(fn)local
wn=fn:_get_description():match("^(.-)%.%s")return wn or
fn:_get_description():match("^(.-)%.?$")end function pt:_get_options()local
yn={}for pn,vn in ipairs(self._options)do for pn,bn in ipairs(vn._aliases)do
table.insert(yn,bn)end end return table.concat(yn," ")end function
pt:_get_commands()local gn={}for kn,qn in ipairs(self._commands)do for kn,jn in
ipairs(qn._aliases)do table.insert(gn,jn)end end return table.concat(gn," ")end
function pt:_bash_option_args(xn,zn)local En={}for Tn,An in
ipairs(self._options)do if An._choices or An._minargs>0 then local On if
An._choices then
On='COMPREPLY=($(compgen -W "'..table.concat(An._choices," ")..'" -- "$cur"))'else
On='COMPREPLY=($(compgen -f -- "$cur"))'end
table.insert(En,(" "):rep(zn+4)..table.concat(An._aliases,"|")..")")table.insert(En,(" "):rep(zn+8)..On)table.insert(En,(" "):rep(zn+8).."return 0")table.insert(En,(" "):rep(zn+8)..";;")end
end if#En>0 then
table.insert(xn,(" "):rep(zn)..'case "$prev" in')table.insert(xn,table.concat(En,"\n"))table.insert(xn,(" "):rep(zn).."esac\n")end
end function pt:_bash_get_cmd(In,Nn)if#self._commands==0 then return end
table.insert(In,(" "):rep(Nn)..'args=("${args[@]:1}")')table.insert(In,(" "):rep(Nn)..'for arg in "${args[@]}"; do')table.insert(In,(" "):rep(Nn+4)..'case "$arg" in')for
Sn,Hn in ipairs(self._commands)do
table.insert(In,(" "):rep(Nn+8)..table.concat(Hn._aliases,"|")..")")if
self._parent then
table.insert(In,(" "):rep(Nn+12)..'cmd="$cmd '..Hn._name..'"')else
table.insert(In,(" "):rep(Nn+12)..'cmd="'..Hn._name..'"')end
table.insert(In,(" "):rep(Nn+12)..'opts="$opts '..Hn:_get_options()..'"')Hn:_bash_get_cmd(In,Nn+12)table.insert(In,(" "):rep(Nn+12).."break")table.insert(In,(" "):rep(Nn+12)..";;")end
table.insert(In,(" "):rep(Nn+4).."esac")table.insert(In,(" "):rep(Nn).."done")end
function pt:_bash_cmd_completions(Rn)local Dn={}if self._parent then
self:_bash_option_args(Dn,12)end if#self._commands>0 then
table.insert(Dn,(" "):rep(12)..'COMPREPLY=($(compgen -W "'..self:_get_commands()..'" -- "$cur"))')elseif
self._is_help_command then
table.insert(Dn,(" "):rep(12)..'COMPREPLY=($(compgen -W "'..self._parent:_get_commands()..'" -- "$cur"))')end
if#Dn>0 then
table.insert(Rn,(" "):rep(8).."'"..self:_get_fullname(true).."')")table.insert(Rn,table.concat(Dn,"\n"))table.insert(Rn,(" "):rep(12)..";;")end
for Ln,Un in ipairs(self._commands)do Un:_bash_cmd_completions(Rn)end end
function
pt:get_bash_complete()self._basename=un(self._name)assert(self:_is_shell_safe())local
Cn={([[
_%s() {
local IFS=$' \t\n'
local args cur prev cmd opts arg
args=("${COMP_WORDS[@]}")
cur="${COMP_WORDS[COMP_CWORD]}"
prev="${COMP_WORDS[COMP_CWORD-1]}"
opts="%s"
]]):format(self._basename,self:_get_options())}self:_bash_option_args(Cn,4)self:_bash_get_cmd(Cn,4)if#self._commands>0
then
table.insert(Cn,"")table.insert(Cn,(" "):rep(4)..'case "$cmd" in')self:_bash_cmd_completions(Cn)table.insert(Cn,(" "):rep(4).."esac\n")end
table.insert(Cn,([=[
if [[ "$cur" = -* ]]; then
COMPREPLY=($(compgen -W "$opts" -- "$cur"))
fi
}
complete -F _%s -o bashdefault -o default %s
]=]):format(self._basename,self._basename))return
table.concat(Cn,"\n")end function pt:_zsh_arguments(Mn,Fn,Wn)if self._parent
then
table.insert(Mn,(" "):rep(Wn).."options=(")table.insert(Mn,(" "):rep(Wn+2).."$options")else
table.insert(Mn,(" "):rep(Wn).."local -a options=(")end for Yn,Pn in
ipairs(self._options)do local Vn={}if#Pn._aliases>1 then if Pn._maxcount>1 then
table.insert(Vn,'"*"')end
table.insert(Vn,"{"..table.concat(Pn._aliases,",")..'}"')else
table.insert(Vn,'"')if Pn._maxcount>1 then table.insert(Vn,"*")end
table.insert(Vn,Pn._name)end if Pn._description then local
Bn=mn(Pn):gsub('["%]:`$]',"\\%0")table.insert(Vn,"["..Bn.."]")end if
Pn._maxargs==math.huge then table.insert(Vn,":*")end if Pn._choices then
table.insert(Vn,": :("..table.concat(Pn._choices," ")..")")elseif Pn._maxargs>0
then table.insert(Vn,": :_files")end
table.insert(Vn,'"')table.insert(Mn,(" "):rep(Wn+2)..table.concat(Vn))end
table.insert(Mn,(" "):rep(Wn)..")")table.insert(Mn,(" "):rep(Wn).."_arguments -s -S \\")table.insert(Mn,(" "):rep(Wn+2).."$options \\")if
self._is_help_command then
table.insert(Mn,(" "):rep(Wn+2)..'": :('..self._parent:_get_commands()..')" \\')else
for Gn,Kn in ipairs(self._arguments)do local Qn if Kn._choices then
Qn=": :("..table.concat(Kn._choices," ")..")"else Qn=": :_files"end if
Kn._maxargs==math.huge then
table.insert(Mn,(" "):rep(Wn+2)..'"*'..Qn..'" \\')break end for
Gn=1,Kn._maxargs do table.insert(Mn,(" "):rep(Wn+2)..'"'..Qn..'" \\')end end
if#self._commands>0 then
table.insert(Mn,(" "):rep(Wn+2)..'": :_'..Fn..'_cmds" \\')table.insert(Mn,(" "):rep(Wn+2)..'"*:: :->args" \\')end
end table.insert(Mn,(" "):rep(Wn+2).."&& return 0")end function
pt:_zsh_cmds(Jn,Xn)table.insert(Jn,"\n_"..Xn.."_cmds() {")table.insert(Jn," local -a commands=(")for
Zn,es in ipairs(self._commands)do local ts={}if#es._aliases>1 then
table.insert(ts,"{"..table.concat(es._aliases,",")..'}"')else
table.insert(ts,'"'..es._name)end if es._description then
table.insert(ts,":"..mn(es):gsub('["`$]',"\\%0"))end
table.insert(Jn," "..table.concat(ts)..'"')end
table.insert(Jn,' )\n _describe "command" commands\n}')end function
pt:_zsh_complete_help(as,os,is,ns)if#self._commands==0 then return end
self:_zsh_cmds(os,is)table.insert(as,"\n"..(" "):rep(ns).."case $words[1] in")for
ss,hs in ipairs(self._commands)do local rs=is.."_"..hs._name
table.insert(as,(" "):rep(ns+2)..table.concat(hs._aliases,"|")..")")hs:_zsh_arguments(as,rs,ns+4)hs:_zsh_complete_help(as,os,rs,ns+4)table.insert(as,(" "):rep(ns+4)..";;\n")end
table.insert(as,(" "):rep(ns).."esac")end function
pt:get_zsh_complete()self._basename=un(self._name)assert(self:_is_shell_safe())local
ds={("#compdef %s\n"):format(self._basename)}local
ls={}table.insert(ds,"_"..self._basename.."() {")if#self._commands>0 then
table.insert(ds," local context state state_descr line")table.insert(ds," typeset -A opt_args\n")end
self:_zsh_arguments(ds,self._basename,2)self:_zsh_complete_help(ds,ls,self._basename,2)table.insert(ds,"\n return 1")table.insert(ds,"}")local
us=table.concat(ds,"\n")if#ls>0 then us=us.."\n"..table.concat(ls,"\n")end
return us.."\n\n_"..self._basename.."\n"end local function cs(ms)return
ms:gsub("[\\']","\\%0")end function pt:_fish_get_cmd(fs,ws)if#self._commands==0
then return end
table.insert(fs,(" "):rep(ws).."set -e cmdline[1]")table.insert(fs,(" "):rep(ws).."for arg in $cmdline")table.insert(fs,(" "):rep(ws+4).."switch $arg")for
ys,ps in ipairs(self._commands)do
table.insert(fs,(" "):rep(ws+8).."case "..table.concat(ps._aliases," "))table.insert(fs,(" "):rep(ws+12).."set cmd $cmd "..ps._name)ps:_fish_get_cmd(fs,ws+12)table.insert(fs,(" "):rep(ws+12).."break")end
table.insert(fs,(" "):rep(ws+4).."end")table.insert(fs,(" "):rep(ws).."end")end
function pt:_fish_complete_help(vs,bs)local gs="complete -c "..bs
table.insert(vs,"")for ks,qs in ipairs(self._commands)do local
js=table.concat(qs._aliases," ")local xs if self._parent then
xs=("%s -n '__fish_%s_using_command %s' -xa '%s'"):format(gs,bs,self:_get_fullname(true),js)else
xs=("%s -n '__fish_%s_using_command' -xa '%s'"):format(gs,bs,js)end if
qs._description then xs=("%s -d '%s'"):format(xs,cs(mn(qs)))end
table.insert(vs,xs)end if self._is_help_command then local
zs=("%s -n '__fish_%s_using_command %s' -xa '%s'"):format(gs,bs,self:_get_fullname(true),self._parent:_get_commands())table.insert(vs,zs)end
for Es,Ts in ipairs(self._options)do local As={gs}if self._parent then
table.insert(As,"-n '__fish_"..bs.."_seen_command "..self:_get_fullname(true).."'")end
for Es,Os in ipairs(Ts._aliases)do if Os:match("^%-.$")then
table.insert(As,"-s "..Os:sub(2))elseif Os:match("^%-%-.+")then
table.insert(As,"-l "..Os:sub(3))end end if Ts._choices then
table.insert(As,"-xa '"..table.concat(Ts._choices," ").."'")elseif
Ts._minargs>0 then table.insert(As,"-r")end if Ts._description then
table.insert(As,"-d '"..cs(mn(Ts)).."'")end
table.insert(vs,table.concat(As," "))end for Is,Ns in ipairs(self._commands)do
Ns:_fish_complete_help(vs,bs)end end function
pt:get_fish_complete()self._basename=un(self._name)assert(self:_is_shell_safe())local
Ss={}if#self._commands>0 then
table.insert(Ss,([[
function __fish_%s_print_command
set -l cmdline (commandline -poc)
set -l cmd]]):format(self._basename))self:_fish_get_cmd(Ss,4)table.insert(Ss,([[
echo "$cmd"
end
function __fish_%s_using_command
test (__fish_%s_print_command) = "$argv"
and return 0
or return 1
end
function __fish_%s_seen_command
string match -q "$argv*" (__fish_%s_print_command)
and return 0
or return 1
end]]):format(self._basename,self._basename,self._basename,self._basename))end
self:_fish_complete_help(Ss,self._basename)return
table.concat(Ss,"\n").."\n"end local function Hs(Rs,Ds)local Ls={}local Us
local Cs={}for Ms in pairs(Rs)do if type(Ms)=="string"then for Fs=1,#Ms do
Us=Ms:sub(1,Fs-1)..Ms:sub(Fs+1)if not Ls[Us]then Ls[Us]={}end
table.insert(Ls[Us],Ms)end end end for Ws=1,#Ds+1 do
Us=Ds:sub(1,Ws-1)..Ds:sub(Ws+1)if Rs[Us]then Cs[Us]=true elseif Ls[Us]then for
Ys,Ps in ipairs(Ls[Us])do Cs[Ps]=true end end end local Vs=next(Cs)if Vs then
if next(Cs,Vs)then local Bs={}for Gs in pairs(Cs)do
table.insert(Bs,"'"..Gs.."'")end
table.sort(Bs)return"\nDid you mean one of these: "..table.concat(Bs," ").."?"else
return"\nDid you mean '"..Vs.."'?"end else return""end end local
Ks=n({invocations=0})function Ks:__call(Qs,Js)self.state=Qs
self.result=Qs.result self.element=Js self.target=Js._target or
Js:_get_default_target()self.action,self.result[self.target]=Js:_get_action()return
self end function Ks:error(Xs,...)self.state:error(Xs,...)end function
Ks:convert(Zs,eh)local th=self.element._convert if th then local ah,oh if
type(th)=="function"then ah,oh=th(Zs)elseif type(th[eh])=="function"then
ah,oh=th[eh](Zs)else ah=th[Zs]end if ah==nil then self:error(oh
and"%s"or"malformed argument '%s'",oh or Zs)end Zs=ah end return Zs end
function Ks:default(ih)return self.element._defmode:find(ih)and
self.element._default end local function nh(sh,hh,rh,dh)local lh=""if hh~=rh
then lh="at "..(dh and"most"or"least").." "end local uh=dh and rh or hh return
lh..tostring(uh).." "..sh..(uh==1 and""or"s")end function
Ks:set_name(ch)self.name=("%s '%s'"):format(ch and"option"or"argument",ch or
self.element._name)end function Ks:invoke()self.open=true self.overwrite=false
if self.invocations>=self.element._maxcount then if self.element._overwrite
then self.overwrite=true else local
mh=nh("time",self.element._mincount,self.element._maxcount,true)self:error("%s must be used %s",self.name,mh)end
else self.invocations=self.invocations+1 end self.args={}if
self.element._maxargs<=0 then self:close()end return self.open end function
Ks:check_choices(fh)if self.element._choices then for wh,yh in
ipairs(self.element._choices)do if fh==yh then return end end local
ph="'"..table.concat(self.element._choices,"', '").."'"local
vh=getmetatable(self.element)==gt self:error("%s%s must be one of %s",vh
and"argument for "or"",self.name,ph)end end function
Ks:pass(bh)self:check_choices(bh)bh=self:convert(bh,#self.args+1)table.insert(self.args,bh)if#self.args>=self.element._maxargs
then self:close()end return self.open end function
Ks:complete_invocation()while#self.args<self.element._minargs do
self:pass(self.element._default)end end function Ks:close()if self.open then
self.open=false if#self.args<self.element._minargs then if
self:default("a")then self:complete_invocation()else if#self.args==0 then if
getmetatable(self.element)==bt then self:error("missing %s",self.name)elseif
self.element._maxargs==1 then
self:error("%s requires an argument",self.name)end end
self:error("%s requires %s",self.name,nh("argument",self.element._minargs,self.element._maxargs))end
end local gh if self.element._maxargs==0 then gh=self.args[1]elseif
self.element._maxargs==1 then if self.element._minargs==0 and
self.element._mincount~=self.element._maxcount then gh=self.args else
gh=self.args[1]end else gh=self.args end
self.action(self.result,self.target,gh,self.overwrite)end end local
kh=n({result={},options={},arguments={},argument_i=1,element_to_mutexes={},mutex_to_element_state={},command_actions={}})function
kh:__call(qh,jh)self.parser=qh self.error_handler=jh
self.charset=qh:_update_charset()self:switch(qh)return self end function
kh:error(xh,...)self.error_handler(self.parser,xh:format(...))end function
kh:switch(zh)self.parser=zh if zh._action then
table.insert(self.command_actions,{action=zh._action,name=zh._name})end for
Eh,Th in ipairs(zh._options)do Th=Ks(self,Th)table.insert(self.options,Th)for
Eh,Ah in ipairs(Th.element._aliases)do self.options[Ah]=Th end end for Oh,Ih in
ipairs(zh._mutexes)do for Oh,Nh in ipairs(Ih)do if not
self.element_to_mutexes[Nh]then self.element_to_mutexes[Nh]={}end
table.insert(self.element_to_mutexes[Nh],Ih)end end for Sh,Hh in
ipairs(zh._arguments)do
Hh=Ks(self,Hh)table.insert(self.arguments,Hh)Hh:set_name()Hh:invoke()end
self.handle_options=zh._handle_options
self.argument=self.arguments[self.argument_i]self.commands=zh._commands for
Rh,Dh in ipairs(self.commands)do for Rh,Lh in ipairs(Dh._aliases)do
self.commands[Lh]=Dh end end end function kh:get_option(Uh)local
Ch=self.options[Uh]if not Ch then
self:error("unknown option '%s'%s",Uh,Hs(self.options,Uh))else return Ch end
end function kh:get_command(Mh)local Fh=self.commands[Mh]if not Fh then
if#self.commands>0 then
self:error("unknown command '%s'%s",Mh,Hs(self.commands,Mh))else
self:error("too many arguments")end else return Fh end end function
kh:check_mutexes(Wh)if self.element_to_mutexes[Wh.element]then for Yh,Ph in
ipairs(self.element_to_mutexes[Wh.element])do local
Vh=self.mutex_to_element_state[Ph]if Vh and Vh~=Wh then
self:error("%s can not be used together with %s",Wh.name,Vh.name)else
self.mutex_to_element_state[Ph]=Wh end end end end function
kh:invoke(Bh,Gh)self:close()Bh:set_name(Gh)self:check_mutexes(Bh,Gh)if
Bh:invoke()then self.option=Bh end end function kh:pass(Kh)if self.option then
if not self.option:pass(Kh)then self.option=nil end elseif self.argument then
self:check_mutexes(self.argument)if not self.argument:pass(Kh)then
self.argument_i=self.argument_i+1
self.argument=self.arguments[self.argument_i]end else local
Qh=self:get_command(Kh)self.result[Qh._target or Qh._name]=true if
self.parser._command_target then
self.result[self.parser._command_target]=Qh._name end self:switch(Qh)end end
function kh:close()if self.option then self.option:close()self.option=nil end
end function kh:finalize()self:close()for Jh=self.argument_i,#self.arguments do
local Xh=self.arguments[Jh]if#Xh.args==0 and Xh:default("u")then
Xh:complete_invocation()else Xh:close()end end if self.parser._require_command
and#self.commands>0 then self:error("a command is required")end for Zh,er in
ipairs(self.options)do er.name=er.name
or("option '%s'"):format(er.element._name)if er.invocations==0 then if
er:default("u")then er:invoke()er:complete_invocation()er:close()end end local
tr=er.element._mincount if er.invocations<tr then if er:default("a")then while
er.invocations<tr do er:invoke()er:close()end elseif er.invocations==0 then
self:error("missing %s",er.name)else
self:error("%s must be used %s",er.name,nh("time",tr,er.element._maxcount))end
end end for ar=#self.command_actions,1,-1 do
self.command_actions[ar].action(self.result,self.command_actions[ar].name)end
end function kh:parse(ir)for nr,sr in ipairs(ir)do local hr=true if
self.handle_options then local rr=sr:sub(1,1)if self.charset[rr]then if#sr>1
then hr=false if sr:sub(2,2)==rr then if#sr==2 then if self.options[sr]then
local dr=self:get_option(sr)self:invoke(dr,sr)else self:close()end
self.handle_options=false else local lr=sr:find"="if lr then local
ur=sr:sub(1,lr-1)local cr=self:get_option(ur)if cr.element._maxargs<=0 then
self:error("option '%s' does not take arguments",ur)end
self:invoke(cr,ur)self:pass(sr:sub(lr+1))else local
mr=self:get_option(sr)self:invoke(mr,sr)end end else for fr=2,#sr do local
wr=rr..sr:sub(fr,fr)local yr=self:get_option(wr)self:invoke(yr,wr)if fr~=#sr
and yr.element._maxargs>0 then self:pass(sr:sub(fr+1))break end end end end end
end if hr then self:pass(sr)end end self:finalize()return self.result end
function
pt:error(pr)io.stderr:write(("%s\n\nError: %s\n"):format(self:get_usage(),pr))error()end
local vr=rawget(_G,"arg")or{}function pt:_parse(br,gr)return
kh(self,gr):parse(br or vr)end function pt:parse(kr)return
self:_parse(kr,self.error)end local function qr(jr)return
tostring(jr).."\noriginal "..debug.traceback("",2):sub(2)end function
pt:pparse(xr)local zr local Er,Tr=xpcall(function()return
self:_parse(xr,function(Ar,Or)zr=Or error(Or,0)end)end,qr)if Er then return
true,Tr elseif not zr then error(Tr,0)else return false,zr end end local
Ir={}Ir.version="0.7.3"setmetatable(Ir,{__call=function(Nr,...)return
pt(vr[0]):add_help(true)(...)end})return
Ir

View file

@ -0,0 +1,84 @@
--[[- Library for formatting numbers
@module numberformatter
]]
--[[ numberformatter.lua
_ _ _ _ _ _ ___ ____ ____
|\ | | | |\/| |__] |___ |__/
| \| |__| | | |__] |___ | \
____ ____ ____ _ _ ____ ___ ___ ____ ____
|___ | | |__/ |\/| |__| | | |___ |__/
| |__| | \ | | | | | | |___ | \
]]
local NumberFormatter = {
--- "Metadata" - Version
_VERSION = "1.1.0",
--- "Metadata" - Description
_DESCRIPTION = "Library for formatting numbers",
--- "Metadata" - Homepage / Url
_URL = "https://github.com/Commandcracker/YouCube",
--- "Metadata" - License
_LICENSE = "GPL-3.0"
}
--[[
NumberFormatter.compact and NumberFormatter.abbreviate based on:
https://devforum.roblox.com/t/how-can-i-turn-a-number-to-a-shorter-number-i-dont-know-how-to-explain-click-to-understand-3/649496/3
]]
local Suffixes = { "k", "M", "B", "T", "qd", "Qn", "sx", "Sp", "O", "N", "de", "Ud", "DD", "tdD", "qdD", "QnD", "sxD",
"SpD", "OcD", "NvD", "Vgn", "UVg", "DVg", "TVg", "qtV", "QnV", "SeV", "SPG", "OVG", "NVG", "TGN", "UTG", "DTG",
"tsTG", "qtTG", "QnTG", "ssTG", "SpTG", "OcTG", "NoTG", "QdDR", "uQDR", "dQDR", "tQDR", "qdQDR", "QnQDR", "sxQDR",
"SpQDR", "OQDDr", "NQDDr", "qQGNT", "uQGNT", "dQGNT", "tQGNT", "qdQGNT", "QnQGNT", "sxQGNT", "SpQGNT", "OQQGNT",
"NQQGNT", "SXGNTL" }
--[[- Format number by LDML's specification for [Compact Number Formats](http://unicode.org/reports/tr35/tr35-numbers.html#Compact_Number_Formats)
@tparam number number The number to format
@treturn string formatted number
@usage Example:
local numberformatter = require("numberformatter")
print(numberformatter.compact(1000))
Output: `1k`
]]
function NumberFormatter.compact(number)
local Negative = number < 0
number = math.abs(number)
local Paired = false
for i in pairs(Suffixes) do
if not (number >= 10 ^ (3 * i)) then
number = number / 10 ^ (3 * (i - 1))
local isComplex = string.find(tostring(number), ".") and string.sub(tostring(number), 4, 4) ~= "."
number = string.sub(tostring(number), 1, isComplex and 4 or 3) .. (Suffixes[i - 1] or "")
Paired = true
break
end
end
if not Paired then
local Rounded = math.floor(number)
number = tostring(Rounded)
end
if Negative then
return "-" .. number
end
return number -- returns 1.0k for example
end
--[[- Format number - separate thousands by comma
@tparam number number The number to format
@treturn string formatted number
@usage Example:
local numberformatter = require("numberformatter")
print(numberformatter.abbreviate(1000))
Output: `1,000`
]]
function NumberFormatter.abbreviate(number)
local left, num, right = string.match(number, '^([^%d]*%d)(%d*)(.-)$')
return left .. num:reverse():gsub('(%d%d%d)', '%1,'):reverse() .. right
end
return NumberFormatter

View file

@ -0,0 +1,68 @@
local
e={_VERSION='1.2.1',_DESCRIPTION='semver for Lua',_URL='https://github.com/kikito/semver.lua',_LICENSE=[[
MIT LICENSE
Copyright (c) 2015 Enrique García Cota
Permission is hereby granted, free of charge, to any person obtaining a
copy of tother software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and tother permission notice shall be included
in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
]]}local
function
t(a,o)assert(a>=0,o..' must be a valid positive number')assert(math.floor(a)==a,o..' must be an integer')end
local function i(n)return n and n~=''end local function s(h)h=h or""local
r,d={},0 h:gsub("([^%.]+)",function(l)d=d+1 r[d]=l end)return r end local
function u(c)local m,f=c:match("^(-[^+]+)(+.+)$")if not(m and f)then
m=c:match("^(-.+)$")f=c:match("^(+.+)$")end assert(m or
f,("The parameter %q must begin with + or - to denote a prerelease or a build"):format(c))return
m,f end local function w(y)if y then local
p=y:match("^-(%w[%.%w-]*)$")assert(p,("The prerelease %q is not a slash followed by alphanumerics, dots and slashes"):format(y))return
p end end local function v(b)if b then local
g=b:match("^%+(%w[%.%w-]*)$")assert(g,("The build %q is not a + sign followed by alphanumerics, dots and slashes"):format(b))return
g end end local function k(q)if not i(q)then return nil,nil end local
j,x=u(q)local z=w(j)local E=v(x)return z,E end local function T(A)local
O,I,N,S=A:match("^(%d+)%.?(%d*)%.?(%d*)(.-)$")assert(type(O)=='string',("Could not extract version number(s) from %q"):format(A))local
H,R,D=tonumber(O),tonumber(I),tonumber(N)local L,U=k(S)return H,R,D,L,U end
local function C(M,F)return M==F and 0 or M<F and-1 or 1 end local function
W(Y,P)if Y==P then return 0 elseif not Y then return-1 elseif not P then return
1 end local V,B=tonumber(Y),tonumber(P)if V and B then return C(V,B)elseif V
then return-1 elseif B then return 1 else return C(Y,P)end end local function
G(K,Q)local J=#K local X for Z=1,J do X=W(K[Z],Q[Z])if X~=0 then return X==-1
end end return J<#Q end local function et(tt,at)if tt==at or not tt then return
false elseif not at then return true end return G(s(tt),s(at))end local
ot={}function ot:nextMajor()return e(self.major+1,0,0)end function
ot:nextMinor()return e(self.major,self.minor+1,0)end function
ot:nextPatch()return e(self.major,self.minor,self.patch+1)end local
it={__index=ot}function it:__eq(nt)return self.major==nt.major and
self.minor==nt.minor and self.patch==nt.patch and
self.prerelease==nt.prerelease end function it:__lt(st)if self.major~=st.major
then return self.major<st.major end if self.minor~=st.minor then return
self.minor<st.minor end if self.patch~=st.patch then return self.patch<st.patch
end return et(self.prerelease,st.prerelease)end function it:__pow(ht)if
self.major==0 then return self==ht end return self.major==ht.major and
self.minor<=ht.minor end function it:__tostring()local
rt={("%d.%d.%d"):format(self.major,self.minor,self.patch)}if self.prerelease
then table.insert(rt,"-"..self.prerelease)end if self.build then
table.insert(rt,"+"..self.build)end return table.concat(rt)end local function
dt(lt,ut,ct,mt,ft)assert(lt,"At least one parameter is needed")if
type(lt)=='string'then lt,ut,ct,mt,ft=T(lt)end ct=ct or 0 ut=ut or 0
t(lt,"major")t(ut,"minor")t(ct,"patch")local
wt={major=lt,minor=ut,patch=ct,prerelease=mt,build=ft}return
setmetatable(wt,it)end setmetatable(e,{__call=function(yt,...)return
dt(...)end})e._VERSION=e(e._VERSION)return
e

View file

@ -0,0 +1,562 @@
-- MIT License
--
-- Copyright (c) 2021 JackMacWindows
--
-- Permission is hereby granted, free of charge, to any person obtaining a copy
-- of this software and associated documentation files (the "Software"), to deal
-- in the Software without restriction, including without limitation the rights
-- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-- copies of the Software, and to permit persons to whom the Software is
-- furnished to do so, subject to the following conditions:
--
-- The above copyright notice and this permission notice shall be included in all
-- copies or substantial portions of the Software.
--
-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-- SOFTWARE.
-- If not using CC, replace `expect` with a suitable argument checking function.
--local expect = require "cc.expect".expect
local expect = dofile "/rom/modules/main/cc/expect.lua".expect
local ByteOrder = {BIG_ENDIAN = 1, LITTLE_ENDIAN = 2}
local isint = {b = 1, B = 1, h = 1, H = 1, l = 1, L = 1, j = 1, J = 1, T = 1}
local packoptsize_tbl = {b = 1, B = 1, x = 1, h = 2, H = 2, f = 4, j = 4, J = 4, l = 8, L = 8, T = 8, d = 8, n = 8}
local function round(n) if n % 1 >= 0.5 then return math.ceil(n) else return math.floor(n) end end
local function floatToRawIntBits(f)
if f == 0 then return 0
elseif f == -0 then return 0x80000000
elseif f == math.huge then return 0x7F800000
elseif f == -math.huge then return 0xFF800000 end
local m, e = math.frexp(f)
if e > 127 or e < -126 then error("number out of range", 3) end
e, m = e + 126, round((math.abs(m) - 0.5) * 0x1000000)
if m > 0x7FFFFF then e = e + 1 end
return bit32.bor(f < 0 and 0x80000000 or 0, bit32.lshift(bit32.band(e, 0xFF), 23), bit32.band(m, 0x7FFFFF))
end
local function doubleToRawLongBits(f)
if f == 0 then return 0, 0
elseif f == -0 then return 0x80000000, 0
elseif f == math.huge then return 0x7FF00000, 0
elseif f == -math.huge then return 0xFFF00000, 0 end
local m, e = math.frexp(f)
if e > 1023 or e < -1022 then error("number out of range", 3) end
e, m = e + 1022, round((math.abs(m) - 0.5) * 0x20000000000000)
if m > 0xFFFFFFFFFFFFF then e = e + 1 end
return bit32.bor(f < 0 and 0x80000000 or 0, bit32.lshift(bit32.band(e, 0x7FF), 20), bit32.band(m / 0x100000000, 0xFFFFF)), bit32.band(m, 0xFFFFFFFF)
end
local function intBitsToFloat(l)
if l == 0 then return 0
elseif l == 0x80000000 then return -0
elseif l == 0x7F800000 then return math.huge
elseif l == 0xFF800000 then return -math.huge end
local m, e = bit32.band(l, 0x7FFFFF), bit32.band(bit32.rshift(l, 23), 0xFF)
e, m = e - 126, m / 0x1000000 + 0.5
local n = math.ldexp(m, e)
return bit32.btest(l, 0x80000000) and -n or n
end
local function longBitsToDouble(lh, ll)
if lh == 0 and ll == 0 then return 0
elseif lh == 0x80000000 and ll == 0 then return -0
elseif lh == 0x7FF00000 and ll == 0 then return math.huge
elseif lh == 0xFFF00000 and ll == 0 then return -math.huge end
local m, e = bit32.band(lh, 0xFFFFF) * 0x100000000 + bit32.band(ll, 0xFFFFFFFF), bit32.band(bit32.rshift(lh, 20), 0x7FF)
e, m = e - 1022, m / 0x20000000000000 + 0.5
local n = math.ldexp(m, e)
return bit32.btest(lh, 0x80000000) and -n or n
end
local function packint(num, size, output, offset, alignment, endianness, signed)
local total_size = 0
if offset % math.min(size, alignment) ~= 0 and alignment > 1 then
local i = 0
while offset % math.min(size, alignment) ~= 0 and i < alignment do
output[offset] = 0
offset = offset + 1
total_size = total_size + 1
i = i + 1
end
end
if endianness == ByteOrder.BIG_ENDIAN then
local added_padding = 0
if size > 8 then for i = 0, size - 9 do
output[offset + i] = (signed and num >= 2^(size * 8 - 1) ~= 0) and 0xFF or 0
added_padding = added_padding + 1
total_size = total_size + 1
end end
for i = added_padding, size - 1 do
output[offset + i] = bit32.band(bit32.rshift(num, ((size - i - 1) * 8)), 0xFF)
total_size = total_size + 1
end
else
for i = 0, math.min(size, 8) - 1 do
output[offset + i] = num / 2^(i * 8) % 256
total_size = total_size + 1
end
for i = 8, size - 1 do
output[offset + i] = (signed and num >= 2^(size * 8 - 1) ~= 0) and 0xFF or 0
total_size = total_size + 1
end
end
return total_size
end
local function unpackint(str, offset, size, endianness, alignment, signed)
local result, rsize = 0, 0
if offset % math.min(size, alignment) ~= 0 and alignment > 1 then
for i = 0, alignment - 1 do
if offset % math.min(size, alignment) == 0 then break end
offset = offset + 1
rsize = rsize + 1
end
end
for i = 0, size - 1 do
result = result + str:byte(offset + i) * 2^((endianness == ByteOrder.BIG_ENDIAN and size - i - 1 or i) * 8)
rsize = rsize + 1
end
if (signed and result >= 2^(size * 8 - 1)) then result = result - 2^(size * 8) end
return result, rsize
end
local function packoptsize(opt, alignment)
local retval = packoptsize_tbl[opt] or 0
if (alignment > 1 and retval % alignment ~= 0) then retval = retval + (alignment - (retval % alignment)) end
return retval
end
--[[
* string.pack (fmt, v1, v2, ...)
*
* Returns a binary string containing the values v1, v2, etc.
* serialized in binary form (packed) according to the format string fmt.
]]
local function pack(...)
local fmt = expect(1, ..., "string")
local endianness = ByteOrder.LITTLE_ENDIAN
local alignment = 1
local pos = 1
local argnum = 2
local output = {}
local i = 1
while i <= #fmt do
local c = fmt:sub(i, i)
i = i + 1
if c == '=' or c == '<' then
endianness = ByteOrder.LITTLE_ENDIAN
elseif c == '>' then
endianness = ByteOrder.BIG_ENDIAN
elseif c == '!' then
local size = -1
while (i <= #fmt and fmt:sub(i, i):match("%d")) do
if (size >= 0xFFFFFFFF / 10) then error("bad argument #1 to 'pack' (invalid format)", 2) end
size = (math.max(size, 0) * 10) + tonumber(fmt:sub(i, i))
i = i + 1
end
if (size > 16 or size == 0) then error(string.format("integral size (%d) out of limits [1,16]", size), 2)
elseif (size == -1) then alignment = 4
else alignment = size end
elseif isint[c] then
local num = expect(argnum, select(argnum, ...), "number")
argnum = argnum + 1
if (num >= math.pow(2, (packoptsize(c, 0) * 8 - (c:match("%l") and 1 or 0))) or
num < (c:match("%l") and -math.pow(2, (packoptsize(c, 0) * 8 - 1)) or 0)) then
error(string.format("bad argument #%d to 'pack' (integer overflow)", argnum - 1), 2)
end
pos = pos + packint(num, packoptsize(c, 0), output, pos, alignment, endianness, false)
elseif c:lower() == 'i' then
local signed = c == 'i'
local size = -1
while i <= #fmt and fmt:sub(i, i):match("%d") do
if (size >= 0xFFFFFFFF / 10) then error("bad argument #1 to 'pack' (invalid format)", 2) end
size = (math.max(size, 0) * 10) + tonumber(fmt:sub(i, i))
i = i + 1
end
if (size > 16 or size == 0) then
error(string.format("integral size (%d) out of limits [1,16]", size), 2)
elseif (alignment > 1 and (size ~= 1 and size ~= 2 and size ~= 4 and size ~= 8 and size ~= 16)) then
error("bad argument #1 to 'pack' (format asks for alignment not power of 2)", 2)
elseif (size == -1) then size = 4 end
local num = expect(argnum, select(argnum, ...), "number")
argnum = argnum + 1
if (num >= math.pow(2, (size * 8 - (c:match("%l") and 1 or 0))) or
num < (c:match("%l") and -math.pow(2, (size * 8 - 1)) or 0)) then
error(string.format("bad argument #%d to 'pack' (integer overflow)", argnum - 1), 2)
end
pos = pos + packint(num, size, output, pos, alignment, endianness, signed)
elseif c == 'f' then
local f = expect(argnum, select(argnum, ...), "number")
argnum = argnum + 1
local l = floatToRawIntBits(f)
if (pos % math.min(4, alignment) ~= 0 and alignment > 1) then
for j = 0, alignment - 1 do
if pos % math.min(4, alignment) == 0 then break end
output[pos] = 0
pos = pos + 1
end
end
for j = 0, 3 do output[pos + (endianness == ByteOrder.BIG_ENDIAN and 3 - j or j)] = bit32.band(bit32.rshift(l, (j * 8)), 0xFF) end
pos = pos + 4
elseif c == 'd' or c == 'n' then
local f = expect(argnum, select(argnum, ...), "number")
argnum = argnum + 1
local lh, ll = doubleToRawLongBits(f)
if (pos % math.min(8, alignment) ~= 0 and alignment > 1) then
for j = 0, alignment - 1 do
if pos % math.min(8, alignment) == 0 then break end
output[pos] = 0
pos = pos + 1
end
end
for j = 0, 3 do output[pos + (endianness == ByteOrder.BIG_ENDIAN and 7 - j or j)] = bit32.band(bit32.rshift(ll, (j * 8)), 0xFF) end
for j = 4, 7 do output[pos + (endianness == ByteOrder.BIG_ENDIAN and 7 - j or j)] = bit32.band(bit32.rshift(lh, ((j - 4) * 8)), 0xFF) end
pos = pos + 8
elseif c == 'c' then
local size = 0
if (i > #fmt or not fmt:sub(i, i):match("%d")) then
error("missing size for format option 'c'", 2)
end
while (i <= #fmt and fmt:sub(i, i):match("%d")) do
if (size >= 0xFFFFFFFF / 10) then error("bad argument #1 to 'pack' (invalid format)", 2) end
size = (size * 10) + tonumber(fmt:sub(i, i))
i = i + 1
end
if (pos + size < pos or pos + size > 0xFFFFFFFF) then error("bad argument #1 to 'pack' (format result too large)", 2) end
local str = expect(argnum, select(argnum, ...), "string")
argnum = argnum + 1
if (#str > size) then error(string.format("bad argument #%d to 'pack' (string longer than given size)", argnum - 1), 2) end
if size > 0 then
for j = 0, size - 1 do output[pos+j] = str:byte(j + 1) or 0 end
pos = pos + size
end
elseif c == 'z' then
local str = expect(argnum, select(argnum, ...), "string")
argnum = argnum + 1
for b in str:gmatch "." do if (b == '\0') then error(string.format("bad argument #%d to 'pack' (string contains zeros)", argnum - 1), 2) end end
for j = 0, #str - 1 do output[pos+j] = str:byte(j + 1) end
output[pos + #str] = 0
pos = pos + #str + 1
elseif c == 's' then
local size = 0
while (i <= #fmt and fmt:sub(i, i):match("%d")) do
if (size >= 0xFFFFFFFF / 10) then error("bad argument #1 to 'pack' (invalid format)", 2) end
size = (size * 10) + tonumber(fmt:sub(i, i))
i = i + 1
end
if (size > 16) then
error(string.format("integral size (%d) out of limits [1,16]", size), 2)
elseif (size == 0) then size = 4 end
local str = expect(argnum, select(argnum, ...), "string")
argnum = argnum + 1
if (#str >= math.pow(2, (size * 8))) then
error(string.format("bad argument #%d to 'pack' (string length does not fit in given size)", argnum - 1), 2)
end
packint(#str, size, output, pos, 1, endianness, false)
for j = size, #str + size - 1 do output[pos+j] = str:byte(j - size + 1) or 0 end
pos = pos + #str + size
elseif c == 'x' then
output[pos] = 0
pos = pos + 1
elseif c == 'X' then
if (i >= #fmt) then error("invalid next option for option 'X'", 2) end
local size = 0
local c = fmt:sub(i, i)
i = i + 1
if c:lower() == 'i' then
while i <= #fmt and fmt:sub(i, i):match("%d") do
if (size >= 0xFFFFFFFF / 10) then error("bad argument #1 to 'pack' (invalid format)", 2) end
size = (size * 10) + tonumber(fmt:sub(i, i))
i = i + 1
end
if (size > 16 or size == 0) then
error(string.format("integral size (%d) out of limits [1,16]", size), 2)
end
else size = packoptsize(c, 0) end
if (size < 1) then error("invalid next option for option 'X'", 2) end
if (pos % math.min(size, alignment) ~= 0 and alignment > 1) then
for j = 1, alignment do
if pos % math.min(size, alignment) == 0 then break end
output[pos] = 0
pos = pos + 1
end
end
elseif c ~= ' ' then error(string.format("invalid format option '%s'", c), 2) end
end
return string.char(table.unpack(output))
end
--[[
* string.packsize (fmt)
*
* Returns the size of a string resulting from string.pack with the given format.
* The format string cannot have the variable-length options 's' or 'z'.
]]
local function packsize(fmt)
local pos = 0
local alignment = 1
local i = 1
while i <= #fmt do
local c = fmt:sub(i, i)
i = i + 1
if c == '!' then
local size = 0
while i <= #fmt and fmt:sub(i, i):match("%d") do
if (size >= 0xFFFFFFFF / 10) then error("bad argument #1 to 'pack' (invalid format)", 2) end
size = (size * 10) + tonumber(fmt:sub(i, i))
i = i + 1
end
if (size > 16) then error(string.format("integral size (%d) out of limits [1,16]", size), 2)
elseif (size == 0) then alignment = 4
else alignment = size end
elseif isint[c] then
local size = packoptsize(c, 0)
if (pos % math.min(size, alignment) ~= 0 and alignment > 1) then
for j = 1, alignment do
if pos % math.min(size, alignment) == 0 then break end
pos = pos + 1
end
end
pos = pos + size
elseif c:lower() == 'i' then
local size = 0
while i <= #fmt and fmt:sub(i, i):match("%d") do
if (size >= 0xFFFFFFFF / 10) then error("bad argument #1 to 'pack' (invalid format)", 2) end
size = (size * 10) + tonumber(fmt:sub(i, i))
i = i + 1
end
if (size > 16) then
error(string.format("integral size (%d) out of limits [1,16]", size))
elseif (alignment > 1 and (size ~= 1 and size ~= 2 and size ~= 4 and size ~= 8 and size ~= 16)) then
error("bad argument #1 to 'pack' (format asks for alignment not power of 2)", 2)
elseif (size == 0) then size = 4 end
if (pos % math.min(size, alignment) ~= 0 and alignment > 1) then
for j = 1, alignment do
if pos % math.min(size, alignment) == 0 then break end
pos = pos + 1
end
end
pos = pos + size
elseif c == 'f' then
if (pos % math.min(4, alignment) ~= 0 and alignment > 1) then
for j = 1, alignment do
if pos % math.min(4, alignment) == 0 then break end
pos = pos + 1
end
end
pos = pos + 4
elseif c == 'd' or c == 'n' then
if (pos % math.min(8, alignment) ~= 0 and alignment > 1) then
for j = 1, alignment do
if pos % math.min(8, alignment) == 0 then break end
pos = pos + 1
end
end
pos = pos + 8
elseif c == 'c' then
local size = 0
if (i > #fmt or not fmt:sub(i, i):match("%d")) then
error("missing size for format option 'c'", 2)
end
while i <= #fmt and fmt:sub(i, i):match("%d") do
if (size >= 0xFFFFFFFF / 10) then error("bad argument #1 to 'pack' (invalid format)", 2) end
size = (size * 10) + tonumber(fmt:sub(i, i))
i = i + 1
end
if (pos + size < pos or pos + size > 0x7FFFFFFF) then error("bad argument #1 to 'packsize' (format result too large)", 2) end
pos = pos + size
elseif c == 'x' then
pos = pos + 1
elseif c == 'X' then
if (i >= #fmt) then error("invalid next option for option 'X'", 2) end
local size = 0
local c = fmt:sub(i, i)
i = i + 1
if c:lower() == 'i' then
while i <= #fmt and fmt:sub(i, i):match("%d") do
if (size >= 0xFFFFFFFF / 10) then error("bad argument #1 to 'pack' (invalid format)", 2) end
size = (size * 10) + tonumber(fmt:sub(i, i))
i = i + 1
end
if (size > 16 or size == 0) then
error(string.format("integral size (%d) out of limits [1,16]", size), 2)
end
else size = packoptsize(c, 0) end
if (size < 1) then error("invalid next option for option 'X'", 2) end
if (pos % math.min(size, alignment) ~= 0 and alignment > 1) then
for j = 1, alignment do
if pos % math.min(size, alignment) == 0 then break end
pos = pos + 1
end
end
elseif c == 's' or c == 'z' then error("bad argument #1 to 'packsize' (variable-length format)", 2)
elseif c ~= ' ' and c ~= '<' and c ~= '>' and c ~= '=' then error(string.format("invalid format option '%s'", c), 2) end
end
return pos
end
--[[
* string.unpack (fmt, s [, pos])
*
* Returns the values packed in string s (see string.pack) according to the format string fmt.
* An optional pos marks where to start reading in s (default is 1).
* After the read values, this function also returns the index of the first unread byte in s.
]]
local function unpack(fmt, str, pos)
expect(1, fmt, "string")
expect(2, str, "string")
expect(3, pos, "number", "nil")
if pos then
if (pos < 0) then pos = #str + pos
elseif (pos == 0) then error("bad argument #3 to 'unpack' (initial position out of string)", 2) end
if (pos > #str or pos < 0) then error("bad argument #3 to 'unpack' (initial position out of string)", 2) end
else pos = 1 end
local endianness = ByteOrder.LITTLE_ENDIAN
local alignment = 1
local retval = {}
local i = 1
while i <= #fmt do
local c = fmt:sub(i, i)
i = i + 1
if c == '<' or c == '=' then
endianness = ByteOrder.LITTLE_ENDIAN
elseif c == '>' then
endianness = ByteOrder.BIG_ENDIAN
elseif c == '!' then
local size = 0
while i <= #fmt and fmt:sub(i, i):match("%d") do
if (size >= 0xFFFFFFFF / 10) then error("bad argument #1 to 'pack' (invalid format)", 2) end
size = (size * 10) + tonumber(fmt:sub(i, i))
i = i + 1
end
if (size > 16) then
error(string.format("integral size (%d) out of limits [1,16]", size))
elseif (size == 0) then alignment = 4
else alignment = size end
elseif isint[c] then
if (pos + packoptsize(c, 0) > #str + 1) then error("data string too short", 2) end
local res, ressz = unpackint(str, pos, packoptsize(c, 0), endianness, alignment, c:match("%l") ~= nil)
retval[#retval+1] = res
pos = pos + ressz
elseif c:lower() == 'i' then
local signed = c == 'i'
local size = 0
while (i <= #fmt and fmt:sub(i, i):match("%d")) do
if (size >= 0xFFFFFFFF / 10) then error("bad argument #1 to 'pack' (invalid format)", 2) end
size = (size * 10) + tonumber(fmt:sub(i, i))
i = i + 1
end
if (size > 16) then
error(string.format("integral size (%d) out of limits [1,16]", size), 2)
elseif (size > 8) then
error(string.format("%d-byte integer does not fit into Lua Integer", size), 2)
elseif (size == 0) then size = 4 end
if (pos + size > #str + 1) then error("data string too short", 2) end
local res, ressz = unpackint(str, pos, size, endianness, alignment, signed)
retval[#retval+1] = res
pos = pos + ressz
elseif c == 'f' then
if (pos % math.min(4, alignment) ~= 0 and alignment > 1) then
for j = 1, alignment do
if pos % math.min(4, alignment) == 0 then break end
pos = pos + 1
end
end
if (pos + 4 > #str + 1) then error("data string too short", 2) end
local res = unpackint(str, pos, 4, endianness, alignment, false)
retval[#retval+1] = intBitsToFloat(res)
pos = pos + 4
elseif c == 'd' or c == 'n' then
if (pos % math.min(8, alignment) ~= 0 and alignment > 1) then
for j = 1, alignment do
if pos % math.min(8, alignment) == 0 then break end
pos = pos + 1
end
end
if (pos + 8 > #str + 1) then error("data string too short", 2) end
local lh, ll = 0, 0
for j = 0, 3 do lh = bit32.bor(lh, bit32.lshift((str:byte(pos + j)), ((endianness == ByteOrder.BIG_ENDIAN and 3 - j or j) * 8))) end
for j = 0, 3 do ll = bit32.bor(ll, bit32.lshift((str:byte(pos + j + 4)), ((endianness == ByteOrder.BIG_ENDIAN and 3 - j or j) * 8))) end
if endianness == ByteOrder.LITTLE_ENDIAN then lh, ll = ll, lh end
retval[#retval+1] = longBitsToDouble(lh, ll)
pos = pos + 8
elseif c == 'c' then
local size = 0
if (i > #fmt or not fmt:sub(i, i):match("%d")) then
error("missing size for format option 'c'", 2)
end
while i <= #fmt and fmt:sub(i, i):match("%d") do
if (size >= 0xFFFFFFFF / 10) then error("bad argument #1 to 'pack' (invalid format)") end
size = (size * 10) + tonumber(fmt:sub(i, i))
i = i + 1
end
if (pos + size > #str + 1) then error("data string too short", 2) end
retval[#retval+1] = str:sub(pos, pos + size - 1)
pos = pos + size
elseif c == 'z' then
local size = 0
while (str:byte(pos + size) ~= 0) do
size = size + 1
if (pos + size > #str) then error("unfinished string for format 'z'", 2) end
end
retval[#retval+1] = str:sub(pos, pos + size - 1)
pos = pos + size + 1
elseif c == 's' then
local size = 0
while i <= #fmt and fmt:sub(i, i):match("%d") do
if (size >= 0xFFFFFFFF / 10) then error("bad argument #1 to 'pack' (invalid format)", 2) end
size = (size * 10) + tonumber(fmt:sub(i, i))
i = i + 1
end
if (size > 16) then
error(string.format("integral size (%d) out of limits [1,16]", size), 2)
elseif (size == 0) then size = 4 end
if (pos + size > #str + 1) then error("data string too short", 2) end
local num, numsz = unpackint(str, pos, size, endianness, alignment, false)
pos = pos + numsz
if (pos + num > #str + 1) then error("data string too short", 2) end
retval[#retval+1] = str:sub(pos, pos + num - 1)
pos = pos + num
elseif c == 'x' then
pos = pos + 1
elseif c == 'X' then
if (i >= #fmt) then error("invalid next option for option 'X'", 2) end
local size = 0
local c = fmt:sub(i, i)
i = i + 1
if c:lower() == 'i' then
while i <= #fmt and fmt:sub(i, i):match("%d") do
if (size >= 0xFFFFFFFF / 10) then error("bad argument #1 to 'pack' (invalid format)", 2) end
size = (size * 10) + tonumber(fmt:sub(i, i))
i = i + 1
end
if (size > 16 or size == 0) then
error(string.format("integral size (%d) out of limits [1,16]", size), 2)
elseif (size == -1) then size = 4 end
else size = packoptsize(c, 0) end
if (size < 1) then error("invalid next option for option 'X'", 2) end
if (pos % math.min(size, alignment) ~= 0 and alignment > 1) then
for j = 1, alignment do
if pos % math.min(size, alignment) == 0 then break end
pos = pos + 1
end
end
elseif c ~= ' ' then error(string.format("invalid format option '%s'", c), 2) end
end
retval[#retval+1] = pos
return table.unpack(retval)
end
return {
pack = pack,
packsize = packsize,
unpack = unpack
}

View file

@ -0,0 +1,637 @@
--[[- Lua library for accessing [YouCub's API](https://commandcracker.github.io/YouCube/)
@module youcubeapi
]]
--[[ youcubeapi.lua
_ _ ____ _ _ ____ _ _ ___ ____ ____ ___ _
\_/ | | | | | | | |__] |___ |__| |__] |
| |__| |__| |___ |__| |__] |___ | | | |
]]
--[[- "wrapper" for accessing [YouCub's API](https://commandcracker.github.io/YouCube/)
@type API
@usage Example:
local youcubeapi = require("youcubeapi")
local api = youcubeapi.API.new()
api:detect_bestest_server()
api:request_media(url)
local data = api.websocket.receive()
]]
local API = {}
--- Create's a new API instance.
-- @param websocket [Websocket](https://tweaked.cc/module/http.html#ty:Websocket) The websocket.
-- @treturn API instance
function API.new(websocket)
return setmetatable({
websocket = websocket,
}, { __index = API })
end
-- Look at the [Documentation](https://commandcracker.github.io/YouCube/) for moor information
-- Contact the server owner on Discord, when the server is down
local servers = {
"ws://127.0.0.1:5000", -- Your server!
"wss://us-ky.youcube.knijn.one", -- By EmmaKnijn
"wss://youcube.knijn.one", -- By EmmaKnijn
"wss://youcube.onrender.com", -- By Commandcracker#8528
}
if settings then
local server = settings.get("youcube.server")
if server then
table.insert(servers, 1, server)
end
end
local function websocket_with_timeout(_url, _headers, _timeout)
if http.websocketAsync then
local websocket, websocket_error = http.websocketAsync(_url, _headers)
if not websocket then
return false, websocket_error
end
local timerID = os.startTimer(_timeout)
while true do
local event, param1, param2 = os.pullEvent()
-- TODO: Close web-socket when the connection succeeds after the timeout
if event == "websocket_success" and param1 == _url then
return param2
elseif event == "websocket_failure" and param1 == _url then
return false, param2
elseif event == "timer" and param1 == timerID then
return false, "Timeout"
end
end
end
-- use websocket without timeout
-- when the CC version dos not support websocketAsync
return http.websocket(_url, _headers)
end
--- Connects to a YouCub Server
function API:detect_bestest_server(_server, _verbose)
if _server then
table.insert(servers, 1, _server)
end
for i = 1, #servers do
local server = servers[i]
local ok, err = http.checkURL(server:gsub("^ws://", "http://"):gsub("^wss://", "https://"))
if ok then
if _verbose then
print("Trying to connect to:", server)
end
local websocket, websocket_error = websocket_with_timeout(server, nil, 5)
if websocket ~= false then
term.write("Using the YouCube server: ")
term.setTextColor(colors.blue)
print(server)
term.setTextColor(colors.white)
self.websocket = websocket
break
elseif i == #servers then
error(websocket_error)
elseif _verbose then
print(websocket_error)
end
elseif i == #servers then
error(err)
elseif _verbose then
print(err)
end
end
end
--- Receive data from The YouCub Server
-- @tparam string filter action filter
-- @treturn table retval data
function API:receive(filter)
local status, retval = pcall(self.websocket.receive)
if not status then
error("Lost connection to server\n" .. retval)
end
if retval == nil then
error("Received empty message or max message size exceeded")
end
local data, err = textutils.unserialiseJSON(retval)
if data == nil then
error("Failed to parse message\n" .. err)
end
if filter then
--if type(filter) == "table" then
-- if not filter[data.action] then
-- return self:receive(filter)
-- end
--else
if data.action ~= filter then
return self:receive(filter)
end
end
return data
end
--- Send data to The YouCub Server
-- @tparam table data data to send
function API:send(data)
local status, retval = pcall(self.websocket.send, textutils.serialiseJSON(data))
if not status then
error("Lost connection to server\n" .. retval)
end
end
--[[- [Base64](https://wikipedia.org/wiki/Base64) functions
@type Base64
]]
local Base64 = {}
local b64str = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
-- based on https://github.com/MCJack123/sanjuuni/blob/c64f8725a9f24dec656819923457717dfb964515/raw-player.lua
--- Decode base64 string
-- @tparam string str base64 string
-- @treturn string string decoded string
function Base64.decode(str)
local retval = ""
for s in str:gmatch("....") do
if s:sub(3, 4) == "==" then
retval = retval
.. string.char(
bit32.bor(
bit32.lshift(b64str:find(s:sub(1, 1)) - 1, 2),
bit32.rshift(b64str:find(s:sub(2, 2)) - 1, 4)
)
)
elseif s:sub(4, 4) == "=" then
local n = (b64str:find(s:sub(1, 1)) - 1) * 4096
+ (b64str:find(s:sub(2, 2)) - 1) * 64
+ (b64str:find(s:sub(3, 3)) - 1)
retval = retval .. string.char(bit32.extract(n, 10, 8)) .. string.char(bit32.extract(n, 2, 8))
else
local n = (b64str:find(s:sub(1, 1)) - 1) * 262144
+ (b64str:find(s:sub(2, 2)) - 1) * 4096
+ (b64str:find(s:sub(3, 3)) - 1) * 64
+ (b64str:find(s:sub(4, 4)) - 1)
retval = retval
.. string.char(bit32.extract(n, 16, 8))
.. string.char(bit32.extract(n, 8, 8))
.. string.char(bit32.extract(n, 0, 8))
end
end
return retval
end
--- Request a `16 * 1024` bit chunk
-- @tparam number chunkindex The chunkindex
-- @tparam string id Media id
-- @treturn bytes chunk `16 * 1024` bit chunk
function API:get_chunk(chunkindex, id)
self:send({
["action"] = "get_chunk",
["chunkindex"] = chunkindex,
["id"] = id,
})
return Base64.decode(self:receive("chunk").chunk)
end
--- Get 32vid
-- @tparam number line The line to return
-- @tparam string id Media id
-- @tparam number width Video width
-- @tparam number height Video height
-- @treturn string line one line of the given 32vid
function API:get_vid(tracker, id, width, height)
self:send({
["action"] = "get_vid",
["tracker"] = tracker,
["id"] = id,
["width"] = width * 2,
["height"] = height * 3,
})
return self:receive("vid")
end
--- Request media
-- @tparam string url Url or Search Term
--@treturn table json response
function API:request_media(url, width, height)
local request = {
["action"] = "request_media",
["url"] = url,
}
if width and height then
request.width = width * 2
request.height = height * 3
end
self:send(request)
--return self:receive({ ["media"] = true, ["status"] = true })
end
--- Handshake - get Server capabilities and version
--@treturn table json response
function API:handshake()
self:send({
["action"] = "handshake",
})
return self:receive("handshake")
end
--[[- Abstraction for Audio Devices
@type AudioDevice
]]
local AudioDevice = {}
--- Create's a new AudioDevice instance.
-- @tparam table object Base values
-- @treturn AudioDevice instance
function AudioDevice.new(object)
-- @type AudioDevice
local self = object or {}
function self:validate() end
function self:setLabel(lable) end
function self:write(chunk) end
function self:play() end
function self:reset() end
function self:setVolume(volume) end
return self
end
--[[- @{AudioDevice} from a Speaker
@type Speaker
@usage Example:
local youcubeapi = require("youcubeapi")
local speaker = peripheral.find("speaker")
local audiodevice = youcubeapi.Speaker.new(speaker)
]]
local Speaker = {}
local decoder
local status, dfpwm = pcall(require, "cc.audio.dfpwm")
if status then
decoder = dfpwm.make_decoder()
end
--- Create's a new Tape instance.
-- @tparam speaker speaker The speaker
-- @treturn AudioDevice|Speaker instance
function Speaker.new(speaker)
local self = AudioDevice.new({ speaker = speaker })
function self:validate()
if not decoder then
return "This ComputerCraft version dos not support DFPWM"
end
end
function self:setVolume(volume)
self.volume = volume
end
function self:write(chunk)
local buffer = decoder(chunk)
while not self.speaker.playAudio(buffer, self.volume) do
os.pullEvent("speaker_audio_empty")
end
end
return self
end
--[[- @{AudioDevice} from a [Computronics tape_drive](https://wiki.vexatos.com/wiki:computronics:tape)
@type Tape
@usage Example:
local youcubeapi = require("youcubeapi")
local tape_drive = peripheral.find("tape_drive")
local audiodevice = youcubeapi.Tape.new(tape_drive)
]]
local Tape = {}
--- Create's a new Tape instance.
-- @tparam tape tape The tape_drive
-- @treturn AudioDevice|Tape instance
function Tape.new(tape)
local self = AudioDevice.new({ tape = tape })
function self:validate()
if not self.tape.isReady() then
return "You need to insert a tape"
end
end
function self:setVolume(volume)
if volume then
self.tape.setVolume(volume)
end
end
function self:play(chunk)
self.tape.seek(-self.tape.getSize())
self.tape.play()
end
function self:write(chunk)
self.tape.write(chunk)
end
function self:setLabel(lable)
self.tape.setLabel(lable)
end
function self:reset()
-- based on https://github.com/Vexatos/Computronics/blob/b0ade53cab10529dbe91ebabfa882d1b4b21fa90/src/main/resources/assets/computronics/lua/peripheral/tape_drive/programs/tape_drive/tape#L109-L123
local size = self.tape.getSize()
self.tape.stop()
self.tape.seek(-size)
self.tape.stop()
self.tape.seek(-90000)
local s = string.rep(string.char(170), 8192)
for i = 1, size + 8191, 8192 do
self.tape.write(s)
end
self.tape.seek(-size)
self.tape.seek(-90000)
end
return self
end
--[[- Abstract object for filling a @{Buffer}
@type Filler
]]
local Filler = {}
--- Create's a new Filler instance.
-- @treturn Filler instance
function Filler.new()
local self = {}
function self:next() end
return self
end
--[[- @{Filler} for Audio
@type AudioFiller
]]
local AudioFiller = {}
--- Create's a new AudioFiller instance.
-- @tparam API youcubeapi API object
-- @tparam string id Media id
-- @treturn AudioFiller|Filler instance
function AudioFiller.new(youcubeapi, id)
local self = {
id = id,
chunkindex = 0,
youcubeapi = youcubeapi,
}
function self:next()
local response = self.youcubeapi:get_chunk(self.chunkindex, self.id)
self.chunkindex = self.chunkindex + 1
return response
end
return self
end
--[[- @{Filler} for Video
@type VideoFiller
]]
local VideoFiller = {}
--- Create's a new VideoFiller instance.
-- @tparam API youcubeapi API object
-- @tparam string id Media id
-- @tparam number width Video width
-- @tparam number height Video height
-- @treturn VideoFiller|Filler instance
function VideoFiller.new(youcubeapi, id, width, height)
local self = {
id = id,
width = width,
height = height,
tracker = 0,
youcubeapi = youcubeapi,
}
function self:next()
local response = self.youcubeapi:get_vid(self.tracker, self.id, self.width, self.height)
for i = 1, #response.lines do
self.tracker = self.tracker + #response.lines[i] + 1
end
return response.lines
end
return self
end
--[[- Buffers Data
@type Buffer
]]
local Buffer = {}
--- Create's a new Buffer instance.
-- @tparam Filler filler filler instance
-- @tparam number size buffer limit
-- @treturn Buffer instance
function Buffer.new(filler, size)
local self = {
filler = filler,
size = size,
}
self.buffer = {}
function self:next()
while #self.buffer == 0 do
os.pullEvent()
end -- Wait until next is available
local next = self.buffer[1]
table.remove(self.buffer, 1)
return next
end
function self:fill()
if #self.buffer < self.size then
local next = filler:next()
if type(next) == "table" then
for i = 1, #next do
self.buffer[#self.buffer + 1] = next[i]
end
else
self.buffer[#self.buffer + 1] = next
end
return true
end
return false
end
return self
end
local currnt_palette = {}
for i = 0, 15 do
local r, g, b = term.getPaletteColour(2 ^ i)
currnt_palette[i] = { r, g, b }
end
local function reset_term()
for i = 0, 15 do
term.setPaletteColor(2 ^ i, currnt_palette[i][1], currnt_palette[i][2], currnt_palette[i][3])
end
term.setBackgroundColor(colors.black)
term.setTextColor(colors.white)
term.clear()
term.setCursorPos(1, 1)
end
--[[- Create's a new Buffer instance.
Based on [sanjuuni/raw-player.lua](https://github.com/MCJack123/sanjuuni/blob/c64f8725a9f24dec656819923457717dfb964515/raw-player.lua)
and [sanjuuni/websocket-player.lua](https://github.com/MCJack123/sanjuuni/blob/30dcabb4b56f1eb32c88e1bce384b0898367ebda/websocket-player.lua)
@tparam Buffer buffer filled with frames
]]
local function play_vid(buffer, force_fps, string_unpack)
if not string_unpack then
string_unpack = string.unpack
end
local Fwidth, Fheight = term.getSize()
local tracker = 0
if buffer:next() ~= "32Vid 1.1" then
error("Unsupported file")
end
local fps = tonumber(buffer:next())
if force_fps then
fps = force_fps
end
-- Adjust buffer size
buffer.size = math.ceil(fps) * 2
local first, second = buffer:next(), buffer:next()
if second == "" or second == nil then
fps = 0
end
term.clear()
local start = os.epoch("utc")
local frame_count = 0
while true do
frame_count = frame_count + 1
local frame
if first then
frame, first = first, nil
elseif second then
frame, second = second, nil
else
frame = buffer:next()
end
if frame == "" or frame == nil then
break
end
local mode = frame:match("^!CP([CD])")
if not mode then
error("Invalid file")
end
local b64data
if mode == "C" then
local len = tonumber(frame:sub(5, 8), 16)
b64data = frame:sub(9, len + 8)
else
local len = tonumber(frame:sub(5, 16), 16)
b64data = frame:sub(17, len + 16)
end
local data = Base64.decode(b64data)
-- TODO: maybe verify checksums?
assert(data:sub(1, 4) == "\0\0\0\0" and data:sub(9, 16) == "\0\0\0\0\0\0\0\0", "Invalid file")
local width, height = string_unpack("HH", data, 5)
local c, n, pos = string_unpack("c1B", data, 17)
local text = {}
for y = 1, height do
text[y] = ""
for x = 1, width do
text[y] = text[y] .. c
n = n - 1
if n == 0 then
c, n, pos = string_unpack("c1B", data, pos)
end
end
end
c = c:byte()
for y = 1, height do
local fg, bg = "", ""
for x = 1, width do
fg, bg = fg .. ("%x"):format(bit32.band(c, 0x0F)), bg .. ("%x"):format(bit32.rshift(c, 4))
n = n - 1
if n == 0 then
c, n, pos = string_unpack("BB", data, pos)
end
end
term.setCursorPos(1, y)
term.blit(text[y], fg, bg)
end
pos = pos - 2
local r, g, b
for i = 0, 15 do
r, g, b, pos = string_unpack("BBB", data, pos)
term.setPaletteColor(2 ^ i, r / 255, g / 255, b / 255)
end
if fps == 0 then
read()
break
else
while os.epoch("utc") < start + (frame_count + 1) / fps * 1000 do
sleep(1 / fps)
end
end
end
reset_term()
end
return {
--- "Metadata" - [YouCube API](https://commandcracker.github.io/YouCube/) Version
_API_VERSION = "0.0.0-poc.1.0.0",
--- "Metadata" - Library Version
_VERSION = "0.0.0-poc.1.4.2",
--- "Metadata" - Description
_DESCRIPTION = "Library for accessing YouCub's API",
--- "Metadata" - Homepage / Url
_URL = "https://github.com/Commandcracker/YouCube",
--- "Metadata" - License
_LICENSE = "GPL-3.0",
API = API,
AudioDevice = AudioDevice,
Speaker = Speaker,
Tape = Tape,
Base64 = Base64,
Filler = Filler,
AudioFiller = AudioFiller,
VideoFiller = VideoFiller,
Buffer = Buffer,
play_vid = play_vid,
reset_term = reset_term,
}

19
computer/28/startup.lua Normal file
View file

@ -0,0 +1,19 @@
rednet.open("right")
while true do
sender,message = rednet.receive("elevator")
if sender == 27 then
rednet.send(
sender,
{
pcall(
peripheral.call,
unpack(message)
)
},
"elevator"
)
print(unpack(message),"called")
end
end

604
computer/28/youcube.lua Normal file
View file

@ -0,0 +1,604 @@
--[[
_ _ ____ _ _ ____ _ _ ___ ____
\_/ | | | | | | | |__] |___
| |__| |__| |___ |__| |__] |___
Github Repository: https://github.com/Commandcracker/YouCube
License: GPL-3.0
]]
local _VERSION = "0.0.0-poc.1.1.2"
-- Libraries - OpenLibrarieLoader v1.0.1 --
--TODO: Optional libs:
-- For something like a JSON lib that is only needed for older CC Versions or
-- optional logging.lua support
local function is_lib(libs, lib)
for i = 1, #libs do
local value = libs[i]
if value == lib or value .. ".lua" == lib then
return true, value
end
end
return false
end
local libs = { "youcubeapi", "numberformatter", "semver", "argparse", "string_pack" }
local lib_paths = { ".", "./lib", "./apis", "./modules", "/", "/lib", "/apis", "/modules" }
-- LevelOS Support
if _G.lOS then
lib_paths[#lib_paths + 1] = "/Program_Files/YouCube/lib"
end
local function load_lib(lib)
if require then
return require(lib:gsub(".lua", ""))
end
return dofile(lib)
end
for i_path = 1, #lib_paths do
local path = lib_paths[i_path]
if fs.exists(path) then
local files = fs.list(path)
for i_file = 1, #files do
local found, lib = is_lib(libs, files[i_file])
if found and lib ~= nil and libs[lib] == nil then
libs[lib] = load_lib(path .. "/" .. files[i_file])
end
end
end
end
for i = 1, #libs do
local lib = libs[i]
if libs[lib] == nil then
error(('Library "%s" not found.'):format(lib))
end
end
-- args --
local function get_program_name()
if arg then
return arg[0]
end
return fs.getName(shell.getRunningProgram()):gsub("[\\.].*$", "")
end
-- stylua: ignore start
local parser = libs.argparse {
help_max_width = ({ term.getSize() })[1],
name = get_program_name()
}
:description "Official YouCube client for accessing media from services like YouTube"
parser:argument "URL"
:args "*"
:description "URL or search term."
parser:flag "-v" "--verbose"
:description "Enables verbose output."
:target "verbose"
:action "store_true"
parser:option "-V" "--volume"
:description "Sets the volume of the audio. A value from 0-100"
:target "volume"
parser:option "-s" "--server"
:description "The server that YC should use."
:target "server"
:args(1)
parser:flag "--nv" "--no-video"
:description "Disables video."
:target "no_video"
:action "store_true"
parser:flag "--na" "--no-audio"
:description "Disables audio."
:target "no_audio"
:action "store_true"
parser:flag "--sh" "--shuffle"
:description "Shuffles audio before playing"
:target "shuffle"
:action "store_true"
parser:flag "-l" "--loop"
:description "Loops the media."
:target "loop"
:action "store_true"
parser:flag "--lp" "--loop-playlist"
:description "Loops the playlist."
:target "loop_playlist"
:action "store_true"
parser:option "--fps"
:description "Force sanjuuni to use a specified frame rate"
:target "force_fps"
-- stylua: ignore end
local args = parser:parse({ ... })
if args.force_fps then
args.force_fps = tonumber(args.force_fps)
end
if args.volume then
args.volume = tonumber(args.volume)
if args.volume == nil then
parser:error("Volume must be a number")
end
if args.volume > 100 then
parser:error("Volume cant be over 100")
end
if args.volume < 0 then
parser:error("Volume cant be below 0")
end
args.volume = args.volume / 100
end
if #args.URL > 0 then
args.URL = table.concat(args.URL, " ")
else
args.URL = nil
end
if args.no_video and args.no_audio then
parser:error("Nothing will happen, when audio and video is disabled!")
end
-- CraftOS-PC support --
if periphemu then
periphemu.create("top", "speaker")
-- Fuck the max websocket message police
config.set("http_max_websocket_message", 2 ^ 30)
end
local function get_audiodevices()
local audiodevices = {}
local speakers = { peripheral.find("speaker") }
for i = 1, #speakers do
audiodevices[#audiodevices + 1] = libs.youcubeapi.Speaker.new(speakers[i])
end
local tapes = { peripheral.find("tape_drive") }
for i = 1, #tapes do
audiodevices[#audiodevices + 1] = libs.youcubeapi.Tape.new(tapes[i])
end
if #audiodevices == 0 then
-- Disable audio when no audiodevice is found
args.no_audio = true
return audiodevices
end
-- Validate audiodevices
local last_error
local valid_audiodevices = {}
for i = 1, #audiodevices do
local audiodevice = audiodevices[i]
local _error = audiodevice:validate()
if _error == nil then
valid_audiodevices[#valid_audiodevices + 1] = audiodevice
else
last_error = _error
end
end
if #valid_audiodevices == 0 then
error(last_error)
end
return valid_audiodevices
end
-- main --
local youcubeapi = libs.youcubeapi.API.new()
local audiodevices = get_audiodevices()
-- update check --
local function get_versions()
local url = "https://raw.githubusercontent.com/CC-YouCube/installer/main/versions.json"
-- Check if the URL is valid
local ok, err = http.checkURL(url)
if not ok then
printError("Invalid Update URL.", '"' .. url .. '" ', err)
return
end
local response, http_err = http.get(url, nil, true)
if not response then
printError('Failed to retreat data from update URL. "' .. url .. '" (' .. http_err .. ")")
return
end
local sResponse = response.readAll()
response.close()
return textutils.unserialiseJSON(sResponse)
end
local function write_colored(text, color)
term.setTextColor(color)
term.write(text)
end
local function new_line()
local w, h = term.getSize()
local x, y = term.getCursorPos()
if y + 1 <= h then
term.setCursorPos(1, y + 1)
else
term.setCursorPos(1, h)
term.scroll(1)
end
end
local function write_outdated(current, latest)
if libs.semver(current) ^ libs.semver(latest) then
term.setTextColor(colors.yellow)
else
term.setTextColor(colors.red)
end
term.write(current)
write_colored(" -> ", colors.lightGray)
write_colored(latest, colors.lime)
term.setTextColor(colors.white)
new_line()
end
local function can_update(name, current, latest)
if libs.semver(current) < libs.semver(latest) then
term.write(name .. " ")
write_outdated(current, latest)
end
end
local function update_checker()
local versions = get_versions()
if versions == nil then
return
end
can_update("youcube", _VERSION, versions.client.version)
can_update("youcubeapi", libs.youcubeapi._VERSION, versions.client.libraries.youcubeapi.version)
can_update("numberformatter", libs.numberformatter._VERSION, versions.client.libraries.numberformatter.version)
can_update("semver", tostring(libs.semver._VERSION), versions.client.libraries.semver.version)
can_update("argparse", libs.argparse.version, versions.client.libraries.argparse.version)
local handshake = youcubeapi:handshake()
if libs.semver(handshake.server.version) < libs.semver(versions.server.version) then
print("Tell the server owner to update their server!")
write_outdated(handshake.server.version, versions.server.version)
end
if not libs.semver(libs.youcubeapi._API_VERSION) ^ libs.semver(handshake.api.version) then
print("Client is not compatible with server")
write_colored(libs.youcubeapi._API_VERSION, colors.red)
write_colored(" ^ ", colors.lightGray)
write_colored(handshake.api.version, colors.red)
term.setTextColor(colors.white)
new_line()
end
if libs.semver(libs.youcubeapi._API_VERSION) < libs.semver(versions.api.version) then
print("Your client is using an outdated API version")
write_outdated(libs.youcubeapi._API_VERSION, versions.api.version)
end
if libs.semver(handshake.api.version) < libs.semver(versions.api.version) then
print("The server is using an outdated API version")
write_outdated(libs.youcubeapi._API_VERSION, versions.api.version)
end
end
local function play_audio(buffer, title)
for i = 1, #audiodevices do
local audiodevice = audiodevices[i]
audiodevice:reset()
audiodevice:setLabel(title)
audiodevice:setVolume(args.volume)
end
while true do
local chunk = buffer:next()
-- Adjust buffer size on first chunk
if buffer.filler.chunkindex == 1 then
buffer.size = math.ceil(1024 / (#chunk / 16))
end
if chunk == "" then
local play_functions = {}
for i = 1, #audiodevices do
local audiodevice = audiodevices[i]
play_functions[#play_functions + 1] = function()
audiodevice:play()
end
end
parallel.waitForAll(table.unpack(play_functions))
return
end
local write_functions = {}
for i = 1, #audiodevices do
local audiodevice = audiodevices[i]
table.insert(write_functions, function()
audiodevice:write(chunk)
end)
end
parallel.waitForAll(table.unpack(write_functions))
end
end
-- #region playback controll vars
local back_buffer = {}
local max_back = settings.get("youcube.max_back") or 32
local queue = {}
local restart = false
-- #endregion
-- keys
local skip_key = settings.get("youcube.keys.skip") or keys.d
local restart_key = settings.get("youcube.keys.restart") or keys.r
local back_key = settings.get("youcube.keys.back") or keys.a
local function play(url)
restart = false
print("Requesting media ...")
if not args.no_video then
youcubeapi:request_media(url, term.getSize())
else
youcubeapi:request_media(url)
end
local data
local x, y = term.getCursorPos()
repeat
data = youcubeapi:receive()
if data.action == "status" then
os.queueEvent("youcube:status", data)
term.setCursorPos(x, y)
term.clearLine()
term.write("Status: ")
write_colored(data.message, colors.green)
term.setTextColor(colors.white)
else
new_line()
end
until data.action == "media"
if data.action == "error" then
error(data.message)
end
term.write("Playing: ")
term.setTextColor(colors.lime)
print(data.title)
term.setTextColor(colors.white)
if data.like_count then
print("Likes: " .. libs.numberformatter.compact(data.like_count))
end
if data.view_count then
print("Views: " .. libs.numberformatter.compact(data.view_count))
end
if not args.no_video then
-- wait, that the user can see the video info
sleep(2)
end
local video_buffer = libs.youcubeapi.Buffer.new(
libs.youcubeapi.VideoFiller.new(youcubeapi, data.id, term.getSize()),
60 -- Most videos run on 30 fps, so we store 2s of video.
)
local audio_buffer = libs.youcubeapi.Buffer.new(
libs.youcubeapi.AudioFiller.new(youcubeapi, data.id),
--[[
We want to buffer 1024 chunks.
One chunks is 16 bits.
The server (with default settings) sends 32 chunks at once.
]]
32
)
if args.verbose then
term.clear()
term.setCursorPos(1, 1)
term.write("[DEBUG MODE]")
end
local function fill_buffers()
while true do
os.queueEvent("youcube:fill_buffers")
local event = os.pullEventRaw()
if event == "terminate" then
libs.youcubeapi.reset_term()
end
if not args.no_audio then
audio_buffer:fill()
end
if args.verbose then
term.setCursorPos(1, ({ term.getSize() })[2])
term.clearLine()
term.write("Audio_Buffer: " .. #audio_buffer.buffer)
end
if not args.no_video then
video_buffer:fill()
end
end
end
local function _play_video()
if not args.no_video then
local string_unpack
if not string.unpack then
string_unpack = libs.string_pack.unpack
end
os.queueEvent("youcube:vid_playing", data)
libs.youcubeapi.play_vid(video_buffer, args.force_fps, string_unpack)
os.queueEvent("youcube:vid_eof", data)
end
end
local function _play_audio()
if not args.no_audio then
os.queueEvent("youcube:audio_playing", data)
play_audio(audio_buffer, data.title)
os.queueEvent("youcube:audio_eof", data)
end
end
local function _play_media()
os.queueEvent("youcube:playing")
parallel.waitForAll(_play_video, _play_audio)
end
local function _hotkey_handler()
while true do
local _, key = os.pullEvent("key")
if key == skip_key then
back_buffer[#back_buffer + 1] = url --finished playing, push the value to the back buffer
if #back_buffer > max_back then
back_buffer[1] = nil --remove it from the front of the buffer
end
if not args.no_video then
libs.youcubeapi.reset_term()
end
break
end
if key == restart_key then
queue[#queue + 1] = url --add the current song to upcoming
if not args.no_video then
libs.youcubeapi.reset_term()
end
restart = true
break
end
end
end
parallel.waitForAny(fill_buffers, _play_media, _hotkey_handler)
if data.playlist_videos then
return data.playlist_videos
end
end
local function shuffle_playlist(playlist)
local shuffled = {}
for i = 1, #queue do
local pos = math.random(1, #shuffled + 1)
shuffled[pos] = queue[i]
end
return shuffled
end
local function play_playlist(playlist)
queue = playlist
if args.shuffle then
queue = shuffle_playlist(queue)
end
while #queue ~= 0 do
local pl = table.remove(queue)
local function handle_back_hotkey()
while true do
local _, key = os.pullEvent("key")
if key == back_key then
queue[#queue + 1] = pl --add the current song to upcoming
local prev = table.remove(back_buffer)
if prev then --nil/false check
queue[#queue + 1] = prev --add previous song to upcoming
end
if not args.no_video then
libs.youcubeapi.reset_term()
end
break
end
end
end
parallel.waitForAny(handle_back_hotkey, function()
play(pl) --play the url
end)
end
end
local function main()
youcubeapi:detect_bestest_server(args.server, args.verbose)
pcall(update_checker)
if not args.URL then
print("Enter Url or Search Term:")
term.setTextColor(colors.lightGray)
args.URL = read()
term.setTextColor(colors.white)
end
local playlist_videos = play(args.URL)
if args.loop == true then
while true do
play(args.URL)
end
end
if playlist_videos then
if args.loop_playlist == true then
while true do
if playlist_videos then
play_playlist(playlist_videos)
end
end
end
play_playlist(playlist_videos)
end
while restart do
play(args.URL)
end
youcubeapi.websocket.close()
if not args.no_video then
libs.youcubeapi.reset_term()
end
os.queueEvent("youcube:playback_ended")
end
main()