· 5 years ago · May 20, 2020, 05:00 PM
1_G._p = _ENV
2local prefix = "music"
3local name = "JakeBox"
4
5settings.load ".settings"
6local key = settings.get "ytApiKey"
7local ip = "ws://"..settings.get("ip")
8
9local ws = http.websocket(ip)
10
11local queue = {}
12
13local songClass = {}
14songClass.__index = songClass
15
16function songClass:new(title, id, slot, queuer, size)
17 local song = {}
18 setmetatable(song, songClass)
19 song.title = title
20 song.id = id
21 song.downloaded = false
22 song.slot = slot or false
23 song.queuer = queuer
24 song.downloading = false
25 song.isPlaying = false
26 song.size = size
27 return song
28end
29
30local function round(n, place)
31 if place then return round(n / place) * place end
32 return n % 1 >= 0.5 and math.ceil(n) or math.floor(n)
33end
34
35local function lengthToTime(length)
36 if not length then return "unknown" end
37 local inSeconds = round(length / 6000) --6000 bytes/second
38 local mins = math.floor(inSeconds / 60)
39 local secs = inSeconds % 60
40 return tostring(mins)..":"..(secs < 10 and "0"..tostring(secs) or tostring(secs))
41end
42
43local function where(ta, func)
44 local t = {}
45 for i,v in pairs(ta) do
46 if func(v) == true then
47 table.insert(t, v)
48 end
49 end
50 return t
51end
52
53local function reconnect()
54 while not ws or not pcall(ws.send, "test") do
55 ws = http.websocket(ip)
56 sleep(1)
57 end
58end
59
60local tape_drives = where(device, function(thing) return thing.type == "tape_drive" end)
61if #tape_drives < 2 or #tape_drives > 2 then error("needs 2 tape drives (found "..tostring(#tape_drives)..")") end
62local tape = tape_drives[1].side == "bottom" and tape_drives[1] or tape_drives[2]
63local dld = tape_drives[1].side == "bottom" and tape_drives[2] or tape_drives[1]
64
65local chest = where(device, function(thing) return thing.type == "minecraft:ironshulkerbox_crystal" end)[1]
66local loc = "top"
67
68local data_length = 16 --16 bytes
69
70local function getLength(drive, slot)
71 if slot then
72 drive.pullItems(loc, slot)
73 end
74 drive.seek(-math.huge)
75 local data = drive.read(data_length)
76 local len = tonumber(data:match("%d+"))
77 drive.seek(-math.huge)
78 if slot then
79 drive.pushItems(loc, 1, 1, slot)
80 end
81 return len
82end
83
84local function erase()
85 tape.seek(-tape.getPosition())
86 tape.write(("\0"):rep(tape.getSize())) --write NUL to every byte
87 tape.seek(-tape.getSize())
88end
89
90local function download(id, title)
91 ws.send("v="..id)
92 local data = ""
93 while true do
94 local ev = {os.pullEvent("websocket_message")} --wait for message
95 if ev[2] == ip and ev[3] ~= "ping" then --if from ip and not a routine ping
96 if ev[3] == "finished" then --if server done sending
97 break
98 end
99 ws.send("ack") --acknowledge that we have received the chunk
100 data = data..ev[3] --add to data
101 end
102 end
103 if data == "" then --no data received
104 chatbox.tell(plr, "Song failed to download. (timed out)", name)
105 dl = true
106 return {}
107 else
108 if not pcall(ws.send, "checking") then --if we can't send message (closed connection)
109 reconnect()
110 end
111 ws.send("done") --this tells the server to delete the dfpwm file
112 while tape.getState() == "PLAYING" do sleep() end --wait for song to finish due to bug with audio
113 dld.pullItems(loc, 1)
114 while dld.getLabel() == nil do sleep() end --wait for tape to be pulled in
115 erase()
116 local wrote = data:len() + data_length --amount written
117 local pos = tostring(wrote)
118 local empty = string.rep("\0", data_length - pos:len()) --fill the space
119 pos = pos..empty
120 dld.write(pos..data) --metadata concatenated with actual data
121
122 dld.seek(-math.huge)
123 dld.setLabel(title)
124 local slot = #(chest.list())+1 --last item in chest (if no holes)
125 dld.pushItems(loc, 1, 1, slot)
126 end
127end
128
129local function main()
130 local response, err = http.get("https://www.googleapis.com/youtube/v3/playlistItems?part=snippet&maxResults=50&playlistId=PLutBicT0hsJpStMNUYd8p0JhNyYlTVyJK&key="..key)
131 if err then chatbox.tell(plr, "The YouTube API failed to respond. ("..err..")", name)
132 else
133 local data = json.decode(response.readAll()) --json response
134 response.close()
135 --print(textutils.serialize(data))
136 local videos = data.items
137 for i,v in pairs(videos) do
138 local sn = v.snippet
139 local id = sn.resourceId.videoId
140 local title = sn.title
141 download(id, title)
142 end
143 print "finished"
144 end
145end
146
147main()