From f874dcd3de368e7e512ab1c0defdd17bc3026ce5 Mon Sep 17 00:00:00 2001 From: RblSb Date: Tue, 25 Mar 2025 03:02:03 +0300 Subject: Initial cache support for raw videos m3u8 videos are cached without downloading segments, only m3u8 file is downloaded and segment links are updated to use synctube proxy, so you can add video to playlist as server, ignoring ip restrictions, and stream it to everyone --- build/server.js | 1582 +++++++++++++++++++++++++++++++++---------------------- 1 file changed, 946 insertions(+), 636 deletions(-) (limited to 'build') diff --git a/build/server.js b/build/server.js index ef1de33..a6b4b7e 100644 --- a/build/server.js +++ b/build/server.js @@ -2237,6 +2237,17 @@ Lambda.find = function(it,f) { } return null; }; +Lambda.findIndex = function(it,f) { + var i = 0; + var v = $getIterator(it); + while(v.hasNext()) { + if(f(v.next())) { + return i; + } + ++i; + } + return -1; +}; var haxe_IMap = function() { }; haxe_IMap.__name__ = true; haxe_IMap.__isInterface__ = true; @@ -2454,16 +2465,7 @@ VideoList.prototype = { return Lambda.exists(this.items,f); } ,findIndex: function(f) { - var i = 0; - var _g = 0; - var _g1 = this.items; - while(_g < _g1.length) { - if(f(_g1[_g++])) { - return i; - } - ++i; - } - return -1; + return Lambda.findIndex(this.items,f); } ,addItem: function(item,atEnd) { if(atEnd) { @@ -3679,640 +3681,231 @@ json2object_PositionUtils.prototype = { } ,__class__: json2object_PositionUtils }; -var server_Cache = function(main,cacheDir) { - this.freeSpaceBlock = 10485760; - this.cachedFiles = []; - this.storageLimit = 3145728 * 1024; - this.isYtReady = false; - this.notEnoughSpaceErrorText = "Error: Not enough free space on server or file size is out of cache storage limit."; +var server_ConsoleInput = function(main) { + var _g = new haxe_ds_StringMap(); + _g.h["addAdmin"] = { args : ["name","password"], desc : "Adds channel admin"}; + _g.h["removeAdmin"] = { args : ["name"], desc : "Removes channel admin"}; + _g.h["replay"] = { args : ["name"], desc : "Replay log file on server from user/logs/"}; + _g.h["logList"] = { args : [], desc : "Show log list from user/logs/"}; + _g.h["exit"] = { args : [], desc : "Exit process"}; + this.commands = _g; this.main = main; - this.cacheDir = cacheDir; - server_Utils.ensureDir(cacheDir); - this.isYtReady = this.checkYtDeps(); - if(this.isYtReady) { - this.cleanYtInputFiles(); - } }; -server_Cache.__name__ = true; -server_Cache.prototype = { - checkYtDeps: function() { - var ytdl; - try { - ytdl = require("@distube/ytdl-core"); - } catch( _g ) { - return false; +server_ConsoleInput.__name__ = true; +server_ConsoleInput.prototype = { + initConsoleInput: function() { + var _gthis = this; + var rl = js_node_Readline.createInterface({ input : process.stdin, output : process.stdout, completer : $bind(this,this.onCompletion)}); + haxe_Log.trace = function(msg,infos) { + js_node_Readline.clearLine(process.stdout,0); + js_node_Readline.cursorTo(process.stdout,0,null); + console.log(_gthis.formatOutput(msg,infos)); + rl.prompt(true); + }; + rl.prompt(); + rl.on("line",function(line) { + _gthis.parseLine(line); + rl.prompt(); + }); + } + ,formatOutput: function(v,infos) { + var str = Std.string(v); + if(infos == null) { + return str; } - try { - js_node_ChildProcess.execSync("ffmpeg -version",{ stdio : "ignore", timeout : 3000}); - return true; - } catch( _g ) { - return false; + if(infos.customParams != null) { + var _g = 0; + var _g1 = infos.customParams; + while(_g < _g1.length) str += ", " + Std.string(_g1[_g++]); } + return str; } - ,cleanYtInputFiles: function() { - var names = js_node_Fs.readdirSync(this.cacheDir); - var _g = 0; - while(_g < names.length) { - var name = names[_g]; - ++_g; - if(!StringTools.startsWith(name,"__tmp")) { - continue; + ,onCompletion: function(line) { + var _g = []; + var item_keys = Object.keys(this.commands.h); + var item_length = item_keys.length; + var item_current = 0; + while(item_current < item_length) _g.push("/" + item_keys[item_current++] + " "); + var _g1 = []; + var _g2 = 0; + while(_g2 < _g.length) { + var v = _g[_g2]; + ++_g2; + if(StringTools.startsWith(v,line)) { + _g1.push(v); } - this.remove(name); } - } - ,getCachedFiles: function() { - return this.cachedFiles; - } - ,setCachedFiles: function(names) { - this.cachedFiles.length = 0; - var _g = 0; - while(_g < names.length) this.cachedFiles.push(names[_g++]); - var names = js_node_Fs.readdirSync(this.cacheDir); - var _g = 0; - while(_g < names.length) { - var name = names[_g]; - ++_g; - if(StringTools.startsWith(name,".")) { - continue; - } - if(sys_FileSystem.isDirectory("" + this.cacheDir + "/" + name)) { - continue; - } - if(this.cachedFiles.indexOf(name) != -1) { - continue; - } - haxe_Log.trace("Remove non-tracked cache " + name,{ fileName : "src/server/Cache.hx", lineNumber : 70, className : "server.Cache", methodName : "setCachedFiles"}); - this.remove(name); + if(_g1.length > 0) { + return [_g1,line]; } + return [_g,line]; } - ,log: function(client,msg) { - this.main.serverMessage(client,msg); - haxe_Log.trace(msg,{ fileName : "src/server/Cache.hx", lineNumber : 77, className : "server.Cache", methodName : "log"}); - } - ,cacheYoutubeVideo: function(client,url,callback) { - var _gthis = this; - if(!this.isYtReady) { - haxe_Log.trace("Do `npm i @distube/ytdl-core@latest` to use cache feature (you also need to install `ffmpeg` to build mp4 from downloaded audio/video tracks).",{ fileName : "src/server/Cache.hx", lineNumber : 82, className : "server.Cache", methodName : "cacheYoutubeVideo"}); - return; - } - var videoId = utils_YoutubeUtils.extractVideoId(url); - if(videoId == "") { - this.log(client,"Error: youtube video id not found in url: " + url); + ,parseLine: function(line) { + if(line.charCodeAt(0) != 47 || line.length < 2) { + this.printHelp(line); return; } - var outName = videoId + ".mp4"; - if(this.cachedFiles.indexOf(outName) != -1 && this.isFileExists(outName)) { - callback(outName); + var args = StringTools.trim(line).split(" "); + var command = HxOverrides.substr(args.shift(),1,null); + if(this.commands.h[command] == null) { + this.printHelp(line); return; } - var inVideoName = "__tmp-video-" + videoId; - var inAudioName = "__tmp-audio-" + videoId; - if(this.isFileExists(inVideoName)) { - this.log(client,"Caching " + outName + " already in progress"); + if(!this.isValidArgs(command,args)) { return; } - var ytdl = require("@distube/ytdl-core"); - haxe_Log.trace("Caching " + url + " to " + outName + "...",{ fileName : "src/server/Cache.hx", lineNumber : 125, className : "server.Cache", methodName : "cacheYoutubeVideo"}); - this.main.send(client,{ type : "Progress", progress : { type : "Caching", ratio : 0, data : outName}}); - var agent = null; - var cookiesPath = "" + this.main.userDir + "/cookies.json"; - if(sys_FileSystem.exists(cookiesPath)) { - agent = ytdl.createAgent(JSON.parse(js_node_Fs.readFileSync(cookiesPath,{ encoding : "utf8"}))); - } - var promise = ytdl.getInfo(url,{ agent : agent}); - promise.then(function(info) { - haxe_Log.trace("Get info with " + info.formats.length + " formats",{ fileName : "src/server/Cache.hx", lineNumber : 143, className : "server.Cache", methodName : "cacheYoutubeVideo"}); - var audioFormat; - try { - var ytdl1 = ytdl.chooseFormat; - var _g = []; - var _g1 = 0; - var _g2 = info.formats; - while(_g1 < _g2.length) { - var v = _g2[_g1]; - ++_g1; - var tmp = v.audioCodec; - if(tmp != null ? StringTools.startsWith(tmp,"mp4a") : null) { - _g.push(v); - } - } - audioFormat = ytdl1(_g,{ quality : "highestaudio"}); - } catch( _g ) { - var e = haxe_Exception.caught(_g); - _gthis.log(client,"Error: audio format not found"); - haxe_Log.trace(e,{ fileName : "src/server/Cache.hx", lineNumber : 150, className : "server.Cache", methodName : "cacheYoutubeVideo"}); - var _g1 = []; - var _g2 = 0; - var _g3 = info.formats; - while(_g2 < _g3.length) { - var v = _g3[_g2]; - ++_g2; - if(v.hasAudio) { - _g1.push(v); - } - } - haxe_Log.trace(_g1,{ fileName : "src/server/Cache.hx", lineNumber : 151, className : "server.Cache", methodName : "cacheYoutubeVideo"}); + switch(command) { + case "addAdmin": + var name = args[0]; + var password = args[1]; + if(this.main.isBadClientName(name)) { + haxe_Log.trace(StringTools.replace(Lang.get("usernameError"),"$MAX","" + this.main.config.maxLoginLength),{ fileName : "src/server/ConsoleInput.hx", lineNumber : 113, className : "server.ConsoleInput", methodName : "parseLine"}); return; } - var videoFormat; - var tmp = _gthis.getBestYoutubeVideoFormat(info.formats); - if(tmp != null) { - videoFormat = tmp; - } else { - _gthis.log(client,"Error: video format not found"); - var _g = []; - var _g1 = 0; - var _g2 = info.formats; - while(_g1 < _g2.length) { - var v = _g2[_g1]; - ++_g1; - if(v.hasVideo) { - _g.push(v); - } + this.main.addAdmin(name,password); + break; + case "exit": + this.main.exit(); + break; + case "logList": + server_Utils.ensureDir(this.main.logsDir); + var _this = js_node_Fs.readdirSync(this.main.logsDir); + var _g = []; + var _g1 = 0; + while(_g1 < _this.length) { + var v = _this[_g1]; + ++_g1; + if(StringTools.endsWith(v,".json")) { + _g.push(v); } - haxe_Log.trace(_g,{ fileName : "src/server/Cache.hx", lineNumber : 156, className : "server.Cache", methodName : "cacheYoutubeVideo"}); - return; } - var tmp = Std.parseInt(videoFormat.contentLength); - var videoSize = tmp != null ? tmp : 0; - var tmp = Std.parseInt(audioFormat.contentLength); - var audioSize = tmp != null ? tmp : 0; - var hasSpace = _gthis.removeOlderCache((videoSize + audioSize) * 2 + _gthis.freeSpaceBlock); - if(!hasSpace) { - videoFormat = _gthis.getBestYoutubeVideoFormat(info.formats,videoFormat.qualityLabel); - var tmp = Std.parseInt(videoFormat.contentLength); - var videoSize = tmp != null ? tmp : 0; - var tmp = Std.parseInt(audioFormat.contentLength); - var audioSize = tmp != null ? tmp : 0; - var hasSpace = _gthis.removeOlderCache((videoSize + audioSize) * 2 + _gthis.freeSpaceBlock); - if(!hasSpace) { - _gthis.remove(inVideoName); - _gthis.remove(inAudioName); - _gthis.main.send(client,{ type : "Progress", progress : { type : "Canceled", ratio : 1}}); - _gthis.log(client,_gthis.notEnoughSpaceErrorText); - } - if(!hasSpace) { - return; - } + var _g1 = 0; + while(_g1 < _g.length) haxe_Log.trace(haxe_io_Path.withoutExtension(_g[_g1++]),{ fileName : "src/server/ConsoleInput.hx", lineNumber : 140, className : "server.ConsoleInput", methodName : "parseLine"}); + break; + case "removeAdmin": + this.main.removeAdmin(args[0]); + break; + case "replay": + server_Utils.ensureDir(this.main.logsDir); + var path = haxe_io_Path.normalize("" + this.main.logsDir + "/" + args[0] + ".json"); + if(!sys_FileSystem.exists(path)) { + haxe_Log.trace("File \"" + path + "\" not found",{ fileName : "src/server/ConsoleInput.hx", lineNumber : 127, className : "server.ConsoleInput", methodName : "parseLine"}); + return; } - var dlVideo = ytdl(url,{ format : videoFormat, agent : agent}); - dlVideo.pipe(js_node_Fs.createWriteStream("" + _gthis.cacheDir + "/" + inVideoName)); - dlVideo.on("error",function(err) { - _gthis.log(client,"Error during video download: " + err); - _gthis.remove(inVideoName); - _gthis.remove(inAudioName); - _gthis.main.send(client,{ type : "Progress", progress : { type : "Canceled", ratio : 1}}); - }); - var dlAudio = ytdl(url,{ format : audioFormat, agent : agent}); - dlAudio.pipe(js_node_Fs.createWriteStream("" + _gthis.cacheDir + "/" + inAudioName)); - dlAudio.on("error",function(err) { - _gthis.log(client,"Error during audio download: " + err); - _gthis.remove(inVideoName); - _gthis.remove(inAudioName); - _gthis.main.send(client,{ type : "Progress", progress : { type : "Canceled", ratio : 1}}); - }); - var count = 0; - var onComplete = function(type) { - count += 1; - haxe_Log.trace("" + type + " track downloaded (" + count + "/2)",{ fileName : "src/server/Cache.hx", lineNumber : 197, className : "server.Cache", methodName : "cacheYoutubeVideo"}); - if(count < 2) { - return; - } - if(!_gthis.isFileExists(inVideoName) || !_gthis.isFileExists(inAudioName)) { - _gthis.log(client,"Input files not found for making final video"); - _gthis.remove(inVideoName); - _gthis.remove(inAudioName); - _gthis.main.send(client,{ type : "Progress", progress : { type : "Canceled", ratio : 1}}); - return; - } - var size = js_node_Fs.statSync("" + _gthis.cacheDir + "/" + inVideoName).size; - size += js_node_Fs.statSync("" + _gthis.cacheDir + "/" + inAudioName).size; - var hasSpace = _gthis.removeOlderCache(size + _gthis.freeSpaceBlock); - if(!hasSpace) { - _gthis.remove(inVideoName); - _gthis.remove(inAudioName); - _gthis.main.send(client,{ type : "Progress", progress : { type : "Canceled", ratio : 1}}); - _gthis.log(client,_gthis.notEnoughSpaceErrorText); - } - if(!hasSpace) { - return; - } - var args = ("-y -i ./" + inVideoName + " -i ./" + inAudioName + " -c copy -map 0:v -map 1:a ./" + outName).split(" "); - var $process = js_node_ChildProcess.spawn("ffmpeg",args,{ cwd : _gthis.cacheDir}); - var outputData = []; - $process.stderr.on("data",function(data) { - return outputData.push(data); - }); - $process.on("close",function(code) { - _gthis.remove(inVideoName); - _gthis.remove(inAudioName); - if(code != 0) { - _gthis.main.send(client,{ type : "Progress", progress : { type : "Canceled", ratio : 1}}); - var errCodeMsg = "Error: ffmpeg closed with code " + code; - var _g = []; - var _g1 = 0; - var _g2 = _gthis.main.clients; - while(_g1 < _g2.length) { - var v = _g2[_g1]; - ++_g1; - if((v.group & 1 << ClientGroup.Admin._hx_index) != 0) { - _g.push(v); - } - } - var admins = _g; - var _g = 0; - while(_g < admins.length) { - var client1 = admins[_g]; - ++_g; - _gthis.log(client1,js_node_buffer_Buffer.concat(outputData).toString()); - _gthis.log(client1,errCodeMsg); - } - if(admins.indexOf(client) == -1) { - _gthis.log(client,errCodeMsg); - } - return; - } - _gthis.add(outName); - callback(outName); - }); - }; - dlVideo.on("finish",function() { - onComplete("Video"); - }); - dlAudio.on("finish",function() { - onComplete("Audio"); - }); - dlVideo.on("progress",function(chunkLength,downloaded,contentLength) { - var v = downloaded / contentLength; - var ratio = v < 0 ? 0 : v > 1 ? 1 : v; - _gthis.main.send(client,{ type : "Progress", progress : { type : "Downloading", ratio : ratio}}); - }); - }).catch(function(err) { - _gthis.remove(inVideoName); - _gthis.remove(inAudioName); - _gthis.main.send(client,{ type : "Progress", progress : { type : "Canceled", ratio : 1}}); - _gthis.log(client,"" + err); - }); - } - ,setStorageLimit: function(bytes) { - var _gthis = this; - this.storageLimit = bytes; - var a = this.storageLimit; - this.storageLimit = a < 0 ? 0 : a; - this.getFreeDiskSpace(function(availSpace) { - var a = availSpace - _gthis.freeSpaceBlock; - var availSpace = a < 0 ? 0 : a; - _gthis.removeOlderCache(); - var freeSpace = _gthis.getFreeSpace(); - if(availSpace < freeSpace) { - var a = _gthis.storageLimit += availSpace - freeSpace; - _gthis.storageLimit = a < 0 ? 0 : a; - _gthis.removeOlderCache(); - } - }); - } - ,getFreeDiskSpace: function(callback) { - var _gthis = this; - var tmp = js_node_Fs.statfs; - if(tmp == null) { - haxe_Log.trace("Warning: no fs.statfs support in current nodejs version (needs v18+)",{ fileName : "src/server/Cache.hx", lineNumber : 272, className : "server.Cache", methodName : "getFreeDiskSpace"}); - callback(this.storageLimit); - return; + var text = js_node_Fs.readFileSync(path,{ encoding : "utf8"}); + var events = JSON.parse(text); + this.main.replayLog(events); + break; } - tmp("/",function(err,stats) { - if(err != null) { - haxe_Log.trace(err,{ fileName : "src/server/Cache.hx", lineNumber : 278, className : "server.Cache", methodName : "getFreeDiskSpace"}); - callback(_gthis.storageLimit); - return; - } - callback(stats.bsize * stats.bavail); - }); } - ,add: function(name) { - if(this.cachedFiles.indexOf(name) == -1) { - this.cachedFiles.unshift(name); + ,isValidArgs: function(command,args) { + var len = args.length; + var actual = this.commands.h[command].args.length; + if(len != actual) { + haxe_Log.trace("Wrong count of arguments for command \"" + command + "\" (" + len + " instead of " + actual + ")",{ fileName : "src/server/ConsoleInput.hx", lineNumber : 152, className : "server.ConsoleInput", methodName : "isValidArgs"}); + return false; } + return true; } - ,remove: function(name) { - HxOverrides.remove(this.cachedFiles,name); - this.removeFile(name); - } - ,removeOlderCache: function(addFileSize) { - if(addFileSize == null) { - addFileSize = 0; - } - var space = this.getUsedSpace(addFileSize); - while(space > this.storageLimit) { - var tmp = this.cachedFiles.pop(); - if(tmp == null) { - break; + ,printHelp: function(line) { + var maxLength = 0; + var h = this.commands.h; + var _g_keys = Object.keys(h); + var _g_length = _g_keys.length; + var _g_current = 0; + while(_g_current < _g_length) { + var key = _g_keys[_g_current++]; + var _g = { key : key, value : h[key]}; + var len = ("/" + _g.key + " " + _g.value.args.join(" ")).length; + if(maxLength < len) { + maxLength = len; } - this.removeFile(tmp); - space = this.getUsedSpace(addFileSize); - } - return space < this.storageLimit; - } - ,removeFile: function(name) { - var path = this.getFilePath(name); - if(sys_FileSystem.exists(path)) { - js_node_Fs.unlinkSync(path); } - } - ,getFreeFileName: function(fullName) { - if(fullName == null) { - fullName = "video.mp4"; - } - var baseName = haxe_io_Path.withoutDirectory(haxe_io_Path.withoutExtension(fullName)); - var ext = haxe_io_Path.extension(fullName); - var i = 1; - while(true) { - var name = "" + baseName + (i == 1 ? "" : "" + i) + "." + ext; - if(!this.isFileExists(name)) { - return name; - } - ++i; + var list = []; + var h = this.commands.h; + var _g_keys = Object.keys(h); + var _g_length = _g_keys.length; + var _g_current = 0; + while(_g_current < _g_length) { + var key = _g_keys[_g_current++]; + var _g = { key : key, value : h[key]}; + var data = _g.value; + list.push("" + StringTools.rpad("/" + _g.key + " " + data.args.join(" ")," ",maxLength) + " | " + data.desc); } + haxe_Log.trace("Unknown command \"" + line + "\". List:\n" + list.join("\n"),{ fileName : "src/server/ConsoleInput.hx", lineNumber : 171, className : "server.ConsoleInput", methodName : "printHelp"}); } - ,getFilePath: function(name) { - return "" + this.cacheDir + "/" + name; + ,__class__: server_ConsoleInput +}; +var server__$HttpServer_HttpServerConfig = function(dir,customDir,allowLocalRequests,cache) { + this.cache = null; + this.allowLocalRequests = false; + this.customDir = null; + this.dir = dir; + if(customDir != null) { + this.customDir = customDir; } - ,getFileUrl: function(name) { - return "/" + haxe_io_Path.withoutDirectory(this.cacheDir) + "/" + name; + if(allowLocalRequests != null) { + this.allowLocalRequests = allowLocalRequests; } - ,isFileExists: function(name) { - return sys_FileSystem.exists(this.getFilePath(name)); + if(cache != null) { + this.cache = cache; } - ,getFreeSpace: function() { - return this.storageLimit - this.getUsedSpace(); +}; +server__$HttpServer_HttpServerConfig.__name__ = true; +server__$HttpServer_HttpServerConfig.prototype = { + __class__: server__$HttpServer_HttpServerConfig +}; +var server_HttpServer = function(main,config) { + this.ctrlCharacters = new EReg("[\\u0000-\\u001F\\u007F-\\u009F\\u2000-\\u200D\\uFEFF]","g"); + this.matchVarString = new EReg("\\${([A-z_]+)}","g"); + this.matchLang = new EReg("^[A-z]+",""); + this.uploadingFilesLastChunks = new haxe_ds_StringMap(); + this.uploadingFilesSizes = new haxe_ds_StringMap(); + this.CHUNK_SIZE = 5242880; + this.cache = null; + this.allowLocalRequests = false; + this.allowedLocalFiles = new haxe_ds_StringMap(); + this.hasCustomRes = false; + this.main = main; + this.dir = config.dir; + this.customDir = config.customDir; + this.allowLocalRequests = config.allowLocalRequests; + this.cache = config.cache; + if(this.customDir != null) { + this.hasCustomRes = sys_FileSystem.exists(this.customDir); } - ,getUsedSpace: function(addFileSize) { - if(addFileSize == null) { - addFileSize = 0; +}; +server_HttpServer.__name__ = true; +server_HttpServer.prototype = { + serveFiles: function(req,res) { + var _gthis = this; + var url; + try { + url = new js_node_url_URL(this.safeDecodeURI(req.url),"http://localhost"); + } catch( _g ) { + url = new js_node_url_URL("/","http://localhost"); } - var total = addFileSize < 0 ? 0 : addFileSize; - var arr = this.cachedFiles; - var _g_i = arr.length - 1; - while(_g_i > -1) { - var name = arr[_g_i--]; - var path = this.getFilePath(name); - if(!sys_FileSystem.exists(path)) { - HxOverrides.remove(this.cachedFiles,name); - continue; + var filePath = this.getPath(this.dir,url); + var ext = haxe_io_Path.extension(filePath).toLowerCase(); + res.setHeader("accept-ranges","bytes"); + res.setHeader("content-type",this.getMimeType(ext)); + if(this.cache != null && req.method == "POST") { + switch(url.pathname) { + case "/upload": + this.uploadFile(req,res); + break; + case "/upload-last-chunk": + this.uploadFileLastChunk(req,res); + break; } - total += js_node_Fs.statSync(path).size; + return; } - return total; - } - ,getBestYoutubeVideoFormat: function(formats,ignoreQuality) { - var qPriority = [1080,720,480,360,240,144]; - var _g = 0; - while(_g < qPriority.length) { - var quality = "" + qPriority[_g++] + "p"; - if(quality == ignoreQuality) { - continue; - } - var _g1 = 0; - while(_g1 < formats.length) { - var format = formats[_g1]; - ++_g1; - if(format.videoCodec == null) { - continue; - } - if(format.qualityLabel == quality) { - return format; - } - } - } - return null; - } - ,__class__: server_Cache -}; -var server_ConsoleInput = function(main) { - var _g = new haxe_ds_StringMap(); - _g.h["addAdmin"] = { args : ["name","password"], desc : "Adds channel admin"}; - _g.h["removeAdmin"] = { args : ["name"], desc : "Removes channel admin"}; - _g.h["replay"] = { args : ["name"], desc : "Replay log file on server from user/logs/"}; - _g.h["logList"] = { args : [], desc : "Show log list from user/logs/"}; - _g.h["exit"] = { args : [], desc : "Exit process"}; - this.commands = _g; - this.main = main; -}; -server_ConsoleInput.__name__ = true; -server_ConsoleInput.prototype = { - initConsoleInput: function() { - var _gthis = this; - var rl = js_node_Readline.createInterface({ input : process.stdin, output : process.stdout, completer : $bind(this,this.onCompletion)}); - haxe_Log.trace = function(msg,infos) { - js_node_Readline.clearLine(process.stdout,0); - js_node_Readline.cursorTo(process.stdout,0,null); - console.log(_gthis.formatOutput(msg,infos)); - rl.prompt(true); - }; - rl.prompt(); - rl.on("line",function(line) { - _gthis.parseLine(line); - rl.prompt(); - }); - } - ,formatOutput: function(v,infos) { - var str = Std.string(v); - if(infos == null) { - return str; - } - if(infos.customParams != null) { - var _g = 0; - var _g1 = infos.customParams; - while(_g < _g1.length) str += ", " + Std.string(_g1[_g++]); - } - return str; - } - ,onCompletion: function(line) { - var _g = []; - var item_keys = Object.keys(this.commands.h); - var item_length = item_keys.length; - var item_current = 0; - while(item_current < item_length) _g.push("/" + item_keys[item_current++] + " "); - var _g1 = []; - var _g2 = 0; - while(_g2 < _g.length) { - var v = _g[_g2]; - ++_g2; - if(StringTools.startsWith(v,line)) { - _g1.push(v); - } - } - if(_g1.length > 0) { - return [_g1,line]; - } - return [_g,line]; - } - ,parseLine: function(line) { - if(line.charCodeAt(0) != 47 || line.length < 2) { - this.printHelp(line); - return; - } - var args = StringTools.trim(line).split(" "); - var command = HxOverrides.substr(args.shift(),1,null); - if(this.commands.h[command] == null) { - this.printHelp(line); - return; - } - if(!this.isValidArgs(command,args)) { - return; - } - switch(command) { - case "addAdmin": - var name = args[0]; - var password = args[1]; - if(this.main.badNickName(name)) { - haxe_Log.trace(StringTools.replace(Lang.get("usernameError"),"$MAX","" + this.main.config.maxLoginLength),{ fileName : "src/server/ConsoleInput.hx", lineNumber : 113, className : "server.ConsoleInput", methodName : "parseLine"}); - return; - } - this.main.addAdmin(name,password); - break; - case "exit": - this.main.exit(); - break; - case "logList": - server_Utils.ensureDir(this.main.logsDir); - var _this = js_node_Fs.readdirSync(this.main.logsDir); - var _g = []; - var _g1 = 0; - while(_g1 < _this.length) { - var v = _this[_g1]; - ++_g1; - if(StringTools.endsWith(v,".json")) { - _g.push(v); - } - } - var _g1 = 0; - while(_g1 < _g.length) haxe_Log.trace(haxe_io_Path.withoutExtension(_g[_g1++]),{ fileName : "src/server/ConsoleInput.hx", lineNumber : 140, className : "server.ConsoleInput", methodName : "parseLine"}); - break; - case "removeAdmin": - this.main.removeAdmin(args[0]); - break; - case "replay": - server_Utils.ensureDir(this.main.logsDir); - var path = haxe_io_Path.normalize("" + this.main.logsDir + "/" + args[0] + ".json"); - if(!sys_FileSystem.exists(path)) { - haxe_Log.trace("File \"" + path + "\" not found",{ fileName : "src/server/ConsoleInput.hx", lineNumber : 127, className : "server.ConsoleInput", methodName : "parseLine"}); - return; - } - var text = js_node_Fs.readFileSync(path,{ encoding : "utf8"}); - var events = JSON.parse(text); - this.main.replayLog(events); - break; - } - } - ,isValidArgs: function(command,args) { - var len = args.length; - var actual = this.commands.h[command].args.length; - if(len != actual) { - haxe_Log.trace("Wrong count of arguments for command \"" + command + "\" (" + len + " instead of " + actual + ")",{ fileName : "src/server/ConsoleInput.hx", lineNumber : 152, className : "server.ConsoleInput", methodName : "isValidArgs"}); - return false; - } - return true; - } - ,printHelp: function(line) { - var maxLength = 0; - var h = this.commands.h; - var _g_keys = Object.keys(h); - var _g_length = _g_keys.length; - var _g_current = 0; - while(_g_current < _g_length) { - var key = _g_keys[_g_current++]; - var _g = { key : key, value : h[key]}; - var len = ("/" + _g.key + " " + _g.value.args.join(" ")).length; - if(maxLength < len) { - maxLength = len; - } - } - var list = []; - var h = this.commands.h; - var _g_keys = Object.keys(h); - var _g_length = _g_keys.length; - var _g_current = 0; - while(_g_current < _g_length) { - var key = _g_keys[_g_current++]; - var _g = { key : key, value : h[key]}; - var data = _g.value; - list.push("" + StringTools.rpad("/" + _g.key + " " + data.args.join(" ")," ",maxLength) + " | " + data.desc); - } - haxe_Log.trace("Unknown command \"" + line + "\". List:\n" + list.join("\n"),{ fileName : "src/server/ConsoleInput.hx", lineNumber : 171, className : "server.ConsoleInput", methodName : "printHelp"}); - } - ,__class__: server_ConsoleInput -}; -var server__$HttpServer_HttpServerConfig = function(dir,customDir,allowLocalRequests,cache) { - this.cache = null; - this.allowLocalRequests = false; - this.customDir = null; - this.dir = dir; - if(customDir != null) { - this.customDir = customDir; - } - if(allowLocalRequests != null) { - this.allowLocalRequests = allowLocalRequests; - } - if(cache != null) { - this.cache = cache; - } -}; -server__$HttpServer_HttpServerConfig.__name__ = true; -server__$HttpServer_HttpServerConfig.prototype = { - __class__: server__$HttpServer_HttpServerConfig -}; -var server_HttpServer = function(main,config) { - this.ctrlCharacters = new EReg("[\\u0000-\\u001F\\u007F-\\u009F\\u2000-\\u200D\\uFEFF]","g"); - this.matchVarString = new EReg("\\${([A-z_]+)}","g"); - this.matchLang = new EReg("^[A-z]+",""); - this.uploadingFilesLastChunks = new haxe_ds_StringMap(); - this.uploadingFilesSizes = new haxe_ds_StringMap(); - this.CHUNK_SIZE = 5242880; - this.cache = null; - this.allowLocalRequests = false; - this.allowedLocalFiles = new haxe_ds_StringMap(); - this.hasCustomRes = false; - this.main = main; - this.dir = config.dir; - this.customDir = config.customDir; - this.allowLocalRequests = config.allowLocalRequests; - this.cache = config.cache; - if(this.customDir != null) { - this.hasCustomRes = sys_FileSystem.exists(this.customDir); - } -}; -server_HttpServer.__name__ = true; -server_HttpServer.prototype = { - serveFiles: function(req,res) { - var _gthis = this; - var url; - try { - url = new js_node_url_URL(this.safeDecodeURI(req.url),"http://localhost"); - } catch( _g ) { - url = new js_node_url_URL("/","http://localhost"); - } - var filePath = this.getPath(this.dir,url); - var ext = haxe_io_Path.extension(filePath).toLowerCase(); - res.setHeader("accept-ranges","bytes"); - res.setHeader("content-type",this.getMimeType(ext)); - if(this.cache != null && req.method == "POST") { - switch(url.pathname) { - case "/upload": - this.uploadFile(req,res); - break; - case "/upload-last-chunk": - this.uploadFileLastChunk(req,res); - break; - } - return; - } - if(this.allowLocalRequests && req.socket.remoteAddress == req.socket.localAddress || this.allowedLocalFiles.h[url.pathname]) { - if(this.isMediaExtension(ext)) { - this.allowedLocalFiles.h[url.pathname] = true; - var s = url.pathname; - if(this.serveMedia(req,res,decodeURIComponent(s.split("+").join(" ")))) { - return; + if(this.allowLocalRequests && req.socket.remoteAddress == req.socket.localAddress || this.allowedLocalFiles.h[url.pathname]) { + if(this.isMediaExtension(ext)) { + this.allowedLocalFiles.h[url.pathname] = true; + var s = url.pathname; + if(this.serveMedia(req,res,decodeURIComponent(s.split("+").join(" ")))) { + return; } } } @@ -4434,7 +4027,7 @@ server_HttpServer.prototype = { } }); stream.on("error",function(err) { - haxe_Log.trace(err,{ fileName : "src/server/HttpServer.hx", lineNumber : 201, className : "server.HttpServer", methodName : "uploadFile"}); + haxe_Log.trace(err,{ fileName : "src/server/HttpServer.hx", lineNumber : 202, className : "server.HttpServer", methodName : "uploadFile"}); res.statusCode = 500; res.end(JSON.stringify({ info : "File write stream error."})); var _this = _gthis.uploadingFilesSizes; @@ -4448,7 +4041,7 @@ server_HttpServer.prototype = { _gthis.cache.remove(name); }); req.on("error",function(err) { - haxe_Log.trace("Request Error:",{ fileName : "src/server/HttpServer.hx", lineNumber : 208, className : "server.HttpServer", methodName : "uploadFile", customParams : [err]}); + haxe_Log.trace("Request Error:",{ fileName : "src/server/HttpServer.hx", lineNumber : 209, className : "server.HttpServer", methodName : "uploadFile", customParams : [err]}); stream.destroy(); res.statusCode = 500; res.end(JSON.stringify({ info : "File request error."})); @@ -4736,7 +4329,7 @@ var server_Main = function(opts) { this.wsEventParser = new JsonParser_$1(); this.freeIds = []; this.clients = []; - this.playersCacheSupport = []; + this.playersCacheSupport = ["RawType"]; this.rootDir = "" + __dirname + "/.."; var _gthis = this; this.isNoState = !opts.loadState; @@ -4761,7 +4354,7 @@ var server_Main = function(opts) { this.logger = new server_Logger(this.logsDir,10,this.verbose); this.consoleInput = new server_ConsoleInput(this); this.consoleInput.initConsoleInput(); - this.cache = new server_Cache(this,this.cacheDir); + this.cache = new server_cache_Cache(this,this.cacheDir); if(this.cache.isYtReady) { this.playersCacheSupport.push("YoutubeType"); } @@ -4795,7 +4388,7 @@ var server_Main = function(opts) { preparePort = function() { server_Utils.isPortFree(_gthis.port,function(isFree) { if(!isFree && attempts > 0) { - haxe_Log.trace("Warning: port " + _gthis.port + " is already in use. Changed to " + (_gthis.port + 1),{ fileName : "src/server/Main.hx", lineNumber : 137, className : "server.Main", methodName : "new"}); + haxe_Log.trace("Warning: port " + _gthis.port + " is already in use. Changed to " + (_gthis.port + 1),{ fileName : "src/server/Main.hx", lineNumber : 138, className : "server.Main", methodName : "new"}); attempts -= 1; _gthis.port++; preparePort(); @@ -4822,16 +4415,16 @@ server_Main.jsonFilterNulls = function(key,value) { server_Main.prototype = { runServer: function() { var _gthis = this; - haxe_Log.trace("Local: http://" + this.localIp + ":" + this.port,{ fileName : "src/server/Main.hx", lineNumber : 150, className : "server.Main", methodName : "runServer"}); + haxe_Log.trace("Local: http://" + this.localIp + ":" + this.port,{ fileName : "src/server/Main.hx", lineNumber : 151, className : "server.Main", methodName : "runServer"}); if(this.config.localNetworkOnly) { - haxe_Log.trace("Global network is disabled in config",{ fileName : "src/server/Main.hx", lineNumber : 152, className : "server.Main", methodName : "runServer"}); + haxe_Log.trace("Global network is disabled in config",{ fileName : "src/server/Main.hx", lineNumber : 153, className : "server.Main", methodName : "runServer"}); } else if(!this.isNoState) { server_Utils.getGlobalIp(function(ip) { if(ip.indexOf(":") != -1) { ip = "[" + ip + "]"; } _gthis.globalIp = ip; - haxe_Log.trace("Global: http://" + _gthis.globalIp + ":" + _gthis.port,{ fileName : "src/server/Main.hx", lineNumber : 158, className : "server.Main", methodName : "runServer"}); + haxe_Log.trace("Global: http://" + _gthis.globalIp + ":" + _gthis.port,{ fileName : "src/server/Main.hx", lineNumber : 159, className : "server.Main", methodName : "runServer"}); }); } var dir = "" + this.rootDir + "/res"; @@ -4916,7 +4509,7 @@ server_Main.prototype = { var field = _g1[_g]; ++_g; if(Reflect.field(config,field) == null) { - haxe_Log.trace("Warning: config field \"" + field + "\" is unknown",{ fileName : "src/server/Main.hx", lineNumber : 232, className : "server.Main", methodName : "getUserConfig"}); + haxe_Log.trace("Warning: config field \"" + field + "\" is unknown",{ fileName : "src/server/Main.hx", lineNumber : 233, className : "server.Main", methodName : "getUserConfig"}); } config[field] = Reflect.field(customConfig,field); } @@ -4927,14 +4520,14 @@ server_Main.prototype = { var emote = _g1[_g]; ++_g; if(emoteCopies_h[emote.name]) { - haxe_Log.trace("Warning: emote name \"" + emote.name + "\" has copy",{ fileName : "src/server/Main.hx", lineNumber : 238, className : "server.Main", methodName : "getUserConfig"}); + haxe_Log.trace("Warning: emote name \"" + emote.name + "\" has copy",{ fileName : "src/server/Main.hx", lineNumber : 239, className : "server.Main", methodName : "getUserConfig"}); } emoteCopies_h[emote.name] = true; if(!this.verbose) { continue; } if(emoteCopies_h[emote.image]) { - haxe_Log.trace("Warning: emote url of name \"" + emote.name + "\" has copy",{ fileName : "src/server/Main.hx", lineNumber : 242, className : "server.Main", methodName : "getUserConfig"}); + haxe_Log.trace("Warning: emote url of name \"" + emote.name + "\" has copy",{ fileName : "src/server/Main.hx", lineNumber : 243, className : "server.Main", methodName : "getUserConfig"}); } emoteCopies_h[emote.image] = true; } @@ -4971,7 +4564,7 @@ server_Main.prototype = { js_node_Fs.writeFileSync("" + this.userDir + "/users.json",JSON.stringify({ admins : users1, bans : _g, salt : users.salt},null,"\t")); } ,saveState: function() { - haxe_Log.trace("Saving state...",{ fileName : "src/server/Main.hx", lineNumber : 280, className : "server.Main", methodName : "saveState"}); + haxe_Log.trace("Saving state...",{ fileName : "src/server/Main.hx", lineNumber : 281, className : "server.Main", methodName : "saveState"}); var json = JSON.stringify(this.getCurrentState(),null,"\t"); js_node_Fs.writeFileSync(this.statePath,json); this.writeUsers(this.userList); @@ -4986,7 +4579,7 @@ server_Main.prototype = { if(!sys_FileSystem.exists(this.statePath)) { return; } - haxe_Log.trace("Loading state...",{ fileName : "src/server/Main.hx", lineNumber : 304, className : "server.Main", methodName : "loadState"}); + haxe_Log.trace("Loading state...",{ fileName : "src/server/Main.hx", lineNumber : 305, className : "server.Main", methodName : "loadState"}); var state = JSON.parse(js_node_Fs.readFileSync(this.statePath,{ encoding : "utf8"})); state.flashbacks = state.flashbacks != null ? state.flashbacks : []; state.cachedFiles = state.cachedFiles != null ? state.cachedFiles : []; @@ -5008,7 +4601,7 @@ server_Main.prototype = { } ,logError: function(type,data) { this.cache.removeOlderCache(1048576); - haxe_Log.trace(type,{ fileName : "src/server/Main.hx", lineNumber : 328, className : "server.Main", methodName : "logError", customParams : [data]}); + haxe_Log.trace(type,{ fileName : "src/server/Main.hx", lineNumber : 329, className : "server.Main", methodName : "logError", customParams : [data]}); var crashesFolder = "" + this.userDir + "/crashes"; server_Utils.ensureDir(crashesFolder); var name = DateTools.format(new Date(),"%Y-%m-%d_%H_%M_%S") + "-" + type; @@ -5030,7 +4623,7 @@ server_Main.prototype = { if(_gthis.clients.length == 0) { return; } - haxe_Log.trace("Ping " + url,{ fileName : "src/server/Main.hx", lineNumber : 341, className : "server.Main", methodName : "initIntergationHandlers"}); + haxe_Log.trace("Ping " + url,{ fileName : "src/server/Main.hx", lineNumber : 342, className : "server.Main", methodName : "initIntergationHandlers"}); js_node_Http.get(url,null,function(r) { }); }; @@ -5049,13 +4642,13 @@ server_Main.prototype = { password += this.config.salt; var hash = haxe_crypto_Sha256.encode(password); this.userList.admins.push({ name : name, hash : hash}); - haxe_Log.trace("Admin " + name + " added.",{ fileName : "src/server/Main.hx", lineNumber : 362, className : "server.Main", methodName : "addAdmin"}); + haxe_Log.trace("Admin " + name + " added.",{ fileName : "src/server/Main.hx", lineNumber : 363, className : "server.Main", methodName : "addAdmin"}); } ,removeAdmin: function(name) { HxOverrides.remove(this.userList.admins,Lambda.find(this.userList.admins,function(item) { return item.name == name; })); - haxe_Log.trace("Admin " + name + " removed.",{ fileName : "src/server/Main.hx", lineNumber : 369, className : "server.Main", methodName : "removeAdmin"}); + haxe_Log.trace("Admin " + name + " removed.",{ fileName : "src/server/Main.hx", lineNumber : 370, className : "server.Main", methodName : "removeAdmin"}); } ,replayLog: function(events) { var _gthis = this; @@ -5122,7 +4715,7 @@ server_Main.prototype = { var ip = this.clientIp(req); var id = this.freeIds.length > 0 ? this.freeIds.shift() : this.clients.length; var name = "Guest " + (id + 1); - haxe_Log.trace(HxOverrides.dateStr(new Date()),{ fileName : "src/server/Main.hx", lineNumber : 428, className : "server.Main", methodName : "onConnect", customParams : ["" + name + " connected (" + ip + ")"]}); + haxe_Log.trace(HxOverrides.dateStr(new Date()),{ fileName : "src/server/Main.hx", lineNumber : 429, className : "server.Main", methodName : "onConnect", customParams : ["" + name + " connected (" + ip + ")"]}); var isAdmin = this.config.localAdmins && req.socket.localAddress == ip; var client = new Client(ws,req,id,name,0); client.uuid = uuid; @@ -5136,7 +4729,7 @@ server_Main.prototype = { var obj = _gthis.wsEventParser.fromJson(data.toString()); if(_gthis.wsEventParser.errors.length > 0 || _gthis.noTypeObj(obj)) { var errors = "" + ("Wrong request for type \"" + obj.type + "\":") + "\n" + json2object_ErrorUtils.convertErrorArray(_gthis.wsEventParser.errors); - haxe_Log.trace(errors,{ fileName : "src/server/Main.hx", lineNumber : 445, className : "server.Main", methodName : "onConnect"}); + haxe_Log.trace(errors,{ fileName : "src/server/Main.hx", lineNumber : 446, className : "server.Main", methodName : "onConnect"}); _gthis.serverMessage(client,errors); return; } @@ -5212,7 +4805,19 @@ server_Main.prototype = { } } else { var _g = item.playerType; - if(_g == "YoutubeType") { + switch(_g) { + case "RawType": + this.cache.cacheRawVideo(client,item.url,function(name) { + item = _$Types_VideoItemTools.withUrl(item,_gthis.cache.getFileUrl(name)); + data.addVideo.item = item; + _gthis.videoList.addItem(item,data.addVideo.atEnd); + _gthis.broadcast(data); + if(_gthis.videoList.items.length == 1) { + _gthis.restartWaitTimer(); + } + }); + break; + case "YoutubeType": this.cache.cacheYoutubeVideo(client,item.url,function(name) { item = _$Types_VideoItemTools.withUrl(item,_gthis.cache.getFileUrl(name)); if(item.duration > 1) { @@ -5225,7 +4830,8 @@ server_Main.prototype = { _gthis.restartWaitTimer(); } }); - } else { + break; + default: var name = StringTools.replace("" + _g,"Type",""); this.serverMessage(client,"No cache support for " + name + " player."); data.addVideo.item = item; @@ -5318,7 +4924,7 @@ server_Main.prototype = { if(!internal) { return; } - haxe_Log.trace(HxOverrides.dateStr(new Date()),{ fileName : "src/server/Main.hx", lineNumber : 510, className : "server.Main", methodName : "onMessage", customParams : ["Client " + client.name + " disconnected"]}); + haxe_Log.trace(HxOverrides.dateStr(new Date()),{ fileName : "src/server/Main.hx", lineNumber : 511, className : "server.Main", methodName : "onMessage", customParams : ["Client " + client.name + " disconnected"]}); server_Utils.sortedPush(this.freeIds,client.id); HxOverrides.remove(this.clients,client); this.sendClientList(); @@ -5432,7 +5038,7 @@ server_Main.prototype = { case "Login": var name = StringTools.trim(data.login.clientName); var lcName = name.toLowerCase(); - if(this.badNickName(lcName)) { + if(this.isBadClientName(lcName)) { this.serverMessage(client,"usernameError"); this.send(client,{ type : "LoginError"}); return; @@ -5458,7 +5064,7 @@ server_Main.prototype = { this.send(client,{ type : "LoginError"}); return; } - haxe_Log.trace(HxOverrides.dateStr(new Date()),{ fileName : "src/server/Main.hx", lineNumber : 601, className : "server.Main", methodName : "onMessage", customParams : ["Client " + client.name + " logged as " + name]}); + haxe_Log.trace(HxOverrides.dateStr(new Date()),{ fileName : "src/server/Main.hx", lineNumber : 602, className : "server.Main", methodName : "onMessage", customParams : ["Client " + client.name + " logged as " + name]}); client.name = name; client.setGroupFlag(ClientGroup.User,true); this.checkBan(client); @@ -5471,7 +5077,7 @@ server_Main.prototype = { var oldName = client.name; client.name = "Guest " + (this.clients.indexOf(client) + 1); client.setGroupFlag(ClientGroup.User,false); - haxe_Log.trace(HxOverrides.dateStr(new Date()),{ fileName : "src/server/Main.hx", lineNumber : 622, className : "server.Main", methodName : "onMessage", customParams : ["Client " + oldName + " logout to " + client.name]}); + haxe_Log.trace(HxOverrides.dateStr(new Date()),{ fileName : "src/server/Main.hx", lineNumber : 623, className : "server.Main", methodName : "onMessage", customParams : ["Client " + oldName + " logout to " + client.name]}); this.send(client,{ type : data.type, logout : { oldClientName : oldName, clientName : client.name, clients : this.clientList()}}); this.sendClientListExcept(client); break; @@ -5801,13 +5407,13 @@ server_Main.prototype = { client.setGroupFlag(ClientGroup.Banned,!isOutdated); if(isOutdated) { HxOverrides.remove(this.userList.bans,ban); - haxe_Log.trace("" + client.name + " ban removed",{ fileName : "src/server/Main.hx", lineNumber : 1058, className : "server.Main", methodName : "checkBan"}); + haxe_Log.trace("" + client.name + " ban removed",{ fileName : "src/server/Main.hx", lineNumber : 1064, className : "server.Main", methodName : "checkBan"}); this.sendClientList(); } break; } } - ,badNickName: function(name) { + ,isBadClientName: function(name) { if(name.length > this.config.maxLoginLength) { return true; } @@ -5907,6 +5513,11 @@ server_Main.prototype = { } return false; } + ,hasPlaylistUrl: function(url) { + return this.videoList.exists(function(item) { + return item.url == url; + }); + } ,__class__: server_Main }; var server_Utils = function() { }; @@ -6110,6 +5721,705 @@ server_VideoTimer.prototype = { } ,__class__: server_VideoTimer }; +var server_cache_Cache = function(main,cacheDir) { + this.cachedFiles = []; + this.freeSpaceBlock = 10485760; + this.storageLimit = 3145728 * 1024; + this.isYtReady = false; + this.notEnoughSpaceErrorText = "Error: Not enough free space on server or file size is out of cache storage limit."; + this.main = main; + this.cacheDir = cacheDir; + server_Utils.ensureDir(cacheDir); + this.youtubeCache = new server_cache_YoutubeCache(main,this); + this.rawCache = new server_cache_RawCache(main,this); + this.isYtReady = this.youtubeCache.checkYtDeps(); + if(this.isYtReady) { + this.youtubeCache.cleanYtInputFiles(); + } +}; +server_cache_Cache.__name__ = true; +server_cache_Cache.prototype = { + getCachedFiles: function() { + return this.cachedFiles; + } + ,setCachedFiles: function(names) { + this.cachedFiles.length = 0; + var _g = 0; + while(_g < names.length) this.cachedFiles.push(names[_g++]); + var names = js_node_Fs.readdirSync(this.cacheDir); + var _g = 0; + while(_g < names.length) { + var name = names[_g]; + ++_g; + if(StringTools.startsWith(name,".")) { + continue; + } + if(sys_FileSystem.isDirectory("" + this.cacheDir + "/" + name)) { + continue; + } + if(this.cachedFiles.indexOf(name) != -1) { + continue; + } + haxe_Log.trace("Remove non-tracked cache " + name,{ fileName : "src/server/cache/Cache.hx", lineNumber : 47, className : "server.cache.Cache", methodName : "setCachedFiles"}); + this.remove(name); + } + } + ,log: function(client,msg) { + this.main.serverMessage(client,msg); + haxe_Log.trace(msg,{ fileName : "src/server/cache/Cache.hx", lineNumber : 54, className : "server.cache.Cache", methodName : "log"}); + } + ,cacheYoutubeVideo: function(client,url,callback) { + this.youtubeCache.cacheYoutubeVideo(client,url,callback); + } + ,cacheRawVideo: function(client,url,callback) { + this.rawCache.cacheRawVideo(client,url,callback); + } + ,setStorageLimit: function(bytes) { + var _gthis = this; + this.storageLimit = bytes; + var a = this.storageLimit; + this.storageLimit = a < 0 ? 0 : a; + this.getFreeDiskSpace(function(availSpace) { + var a = availSpace - _gthis.freeSpaceBlock; + var availSpace = a < 0 ? 0 : a; + _gthis.removeOlderCache(); + var freeSpace = _gthis.getFreeSpace(); + if(availSpace < freeSpace) { + var a = _gthis.storageLimit += availSpace - freeSpace; + _gthis.storageLimit = a < 0 ? 0 : a; + _gthis.removeOlderCache(); + } + }); + } + ,getFreeDiskSpace: function(callback) { + var _gthis = this; + var tmp = js_node_Fs.statfs; + if(tmp == null) { + haxe_Log.trace("Warning: no fs.statfs support in current nodejs version (needs v18+)",{ fileName : "src/server/cache/Cache.hx", lineNumber : 83, className : "server.cache.Cache", methodName : "getFreeDiskSpace"}); + callback(this.storageLimit); + return; + } + tmp("/",function(err,stats) { + if(err != null) { + haxe_Log.trace(err,{ fileName : "src/server/cache/Cache.hx", lineNumber : 89, className : "server.cache.Cache", methodName : "getFreeDiskSpace"}); + callback(_gthis.storageLimit); + return; + } + callback(stats.bsize * stats.bavail); + }); + } + ,add: function(name) { + if(this.cachedFiles.indexOf(name) == -1) { + this.cachedFiles.unshift(name); + } + } + ,remove: function(name) { + HxOverrides.remove(this.cachedFiles,name); + this.removeFile(name); + } + ,exists: function(name) { + if(this.cachedFiles.indexOf(name) != -1) { + return this.isFileExists(name); + } else { + return false; + } + } + ,removeOlderCache: function(addFileSize) { + if(addFileSize == null) { + addFileSize = 0; + } + var space = this.getUsedSpace(addFileSize); + var arr = this.cachedFiles; + var _g_i = arr.length - 1; + while(_g_i > -1) { + var name = arr[_g_i--]; + if(space <= this.storageLimit) { + break; + } + if(this.main.hasPlaylistUrl(this.getFileUrl(name))) { + continue; + } + this.remove(name); + space = this.getUsedSpace(addFileSize); + } + return space < this.storageLimit; + } + ,removeFile: function(name) { + var path = this.getFilePath(name); + if(sys_FileSystem.exists(path)) { + js_node_Fs.unlinkSync(path); + } + } + ,getFreeFileName: function(fullName) { + if(fullName == null) { + fullName = "video.mp4"; + } + var baseName = haxe_io_Path.withoutDirectory(haxe_io_Path.withoutExtension(fullName)); + var ext = haxe_io_Path.extension(fullName); + var i = 1; + while(true) { + var name = "" + baseName + (i == 1 ? "" : "" + i) + "." + ext; + if(!this.isFileExists(name)) { + return name; + } + ++i; + } + } + ,getFilePath: function(name) { + return "" + this.cacheDir + "/" + name; + } + ,getFileUrl: function(name) { + return "/" + haxe_io_Path.withoutDirectory(this.cacheDir) + "/" + name; + } + ,isFileExists: function(name) { + return sys_FileSystem.exists(this.getFilePath(name)); + } + ,getFreeSpace: function() { + return this.storageLimit - this.getUsedSpace(); + } + ,getUsedSpace: function(addFileSize) { + if(addFileSize == null) { + addFileSize = 0; + } + var total = addFileSize < 0 ? 0 : addFileSize; + var arr = this.cachedFiles; + var _g_i = arr.length - 1; + while(_g_i > -1) { + var name = arr[_g_i--]; + var path = this.getFilePath(name); + if(!sys_FileSystem.exists(path)) { + HxOverrides.remove(this.cachedFiles,name); + continue; + } + total += js_node_Fs.statSync(path).size; + } + return total; + } + ,__class__: server_cache_Cache +}; +var server_cache_RawCache = function(main,cache) { + this.main = main; + this.cache = cache; +}; +server_cache_RawCache.__name__ = true; +server_cache_RawCache.prototype = { + cacheRawVideo: function(client,url,callback) { + var isM3U8 = url.indexOf(".m3u8") != -1; + var ext = isM3U8 ? "m3u8" : "mp4"; + var matchName = new EReg("^([^:.]+)\\.(.+)",""); + var decodedUrl; + try { + decodedUrl = decodeURIComponent(url.split("+").join(" ")); + } catch( _g ) { + decodedUrl = url; + } + var outName = matchName.match(HxOverrides.substr(decodedUrl,decodedUrl.lastIndexOf("/") + 1,null)) ? matchName.matched(1) + ("." + ext) : "video." + ext; + outName = this.cache.getFreeFileName(outName); + if(this.cache.exists(outName)) { + callback(outName); + return; + } + haxe_Log.trace("Caching " + url + " to " + outName + "...",{ fileName : "src/server/cache/RawCache.hx", lineNumber : 46, className : "server.cache.RawCache", methodName : "cacheRawVideo"}); + this.main.send(client,{ type : "Progress", progress : { type : "Caching", ratio : 0, data : outName}}); + if(isM3U8) { + this.handleM3u8(client,url,outName,callback); + } else { + this.handleMp4(client,url,outName,callback); + } + } + ,handleMp4: function(client,url,outName,callback) { + var _gthis = this; + this.downloadFile(client,url,outName,function(downloaded,total) { + var v = downloaded / total; + _gthis.main.send(client,{ type : "Progress", progress : { type : "Downloading", ratio : v < 0 ? 0 : v > 1 ? 1 : v}}); + },function() { + _gthis.cache.add(outName); + callback(outName); + },function(err) { + _gthis.log(client,"Mp4 download failed: " + err); + _gthis.cancelProgress(client); + }); + } + ,handleM3u8: function(client,url,outName,callback) { + var _gthis = this; + var useProxy = true; + this.downloadM3u8Playlist(client,url,useProxy,function(playlist,totalSize,segments) { + if(useProxy) { + totalSize = playlist.length; + } + if(!_gthis.cache.removeOlderCache(totalSize + _gthis.cache.freeSpaceBlock)) { + _gthis.log(client,_gthis.cache.notEnoughSpaceErrorText); + _gthis.cancelProgress(client); + return; + } + if(useProxy) { + _gthis.main.send(client,{ type : "Progress", progress : { type : "Caching", ratio : 1, data : outName}}); + js_node_Fs.writeFileSync("" + _gthis.cache.cacheDir + "/" + outName,playlist); + _gthis.cache.add(outName); + callback(outName); + return; + } + var activeDownloads = 0; + var maxParallelDownloads = 10; + var downloaded = 0; + var downloadNextBatch = null; + downloadNextBatch = function() { + var _g = 0; + while(_g < segments.length) { + var segment = [segments[_g]]; + ++_g; + if(activeDownloads >= maxParallelDownloads) { + break; + } + if(segment[0].started) { + continue; + } + segment[0].started = true; + activeDownloads += 1; + haxe_Log.trace("download segment",{ fileName : "src/server/cache/RawCache.hx", lineNumber : 118, className : "server.cache.RawCache", methodName : "handleM3u8", customParams : [segment[0].i]}); + _gthis.downloadFile(client,segment[0].url,segment[0].name,(function() { + return function(downloadedBytes,totalBytes) { + }; + })(),(function(segment) { + return function() { + activeDownloads -= 1; + segment[0].completed = true; + downloaded += 1; + var progress = downloaded / segments.length; + _gthis.main.send(client,{ type : "Progress", progress : { type : "Downloading", ratio : progress < 0 ? 0 : progress > 1 ? 1 : progress}}); + if(downloaded == segments.length) { + haxe_Log.trace("All " + downloaded + "/" + segments.length + " segments downloaded",{ fileName : "src/server/cache/RawCache.hx", lineNumber : 138, className : "server.cache.RawCache", methodName : "handleM3u8"}); + js_node_Fs.writeFileSync("" + _gthis.cache.cacheDir + "/" + outName,playlist); + _gthis.cache.add(outName); + callback(outName); + } else { + downloadNextBatch(); + } + }; + })(segment),(function(segment) { + return function(err) { + activeDownloads -= 1; + downloaded += 1; + _gthis.log(client,"TS segment " + segment[0].i + " download failed: " + err); + _gthis.cancelProgress(client); + var _gthis1 = _gthis; + var result = new Array(segments.length); + var _g = 0; + var _g1 = segments.length; + while(_g < _g1) { + var i = _g++; + result[i] = segments[i].name; + } + _gthis1.cleanupFiles(result); + }; + })(segment)); + } + }; + downloadNextBatch(); + },function(err) { + _gthis.log(client,"M3U8 processing failed: " + err); + _gthis.cancelProgress(client); + }); + } + ,request: function(url,options,callback) { + var httpsOptions = options != null ? options : { }; + httpsOptions.rejectUnauthorized = false; + httpsOptions.headers = httpsOptions.headers != null ? httpsOptions.headers : { }; + if(StringTools.startsWith(url,"https:")) { + return js_node_Https.request(url,httpsOptions,callback); + } else { + return js_node_Http.request(url,httpsOptions,callback); + } + } + ,downloadM3u8Playlist: function(client,url,useProxy,onSuccess,onError) { + var _gthis = this; + var req = this.request(url,{ headers : { "User-Agent" : "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36", "Accept" : "*/*"}},function(res) { + if(res.statusCode >= 300 && res.statusCode < 400) { + var redirectUrl = res.headers["location"]; + if(redirectUrl != null) { + _gthis.downloadM3u8Playlist(client,redirectUrl,useProxy,onSuccess,onError); + return; + } + } + var body = []; + res.on("data",function(chunk) { + return body.push(chunk); + }); + res.on("end",function() { + try { + var buffer = js_node_buffer_Buffer.concat(body); + var content = buffer.toString(); + if(!new EReg("^#EXTM3U","").match(content)) { + onError("Invalid M3U8 playlist"); + return; + } + var baseUrl = url.substring(0,url.lastIndexOf("/") + 1); + var segments = []; + var lines = content.split("\n"); + var _g_current = 0; + var _g_array = lines; + while(_g_current < _g_array.length) { + var _g_value = _g_array[_g_current++]; + var _g_key = _g_current - 1; + var line = StringTools.trim(_g_value); + if(line.length == 0) { + continue; + } + if(StringTools.startsWith(line,"#")) { + continue; + } + var segmentUrl = line.indexOf("://") == -1 ? baseUrl + line : line; + var i = segments.length; + var segment = { i : i, url : segmentUrl, started : false, completed : false, name : "segment" + i + ".ts"}; + segments.push(segment); + lines[_g_key] = "./" + segment.name; + if(useProxy) { + lines[_g_key] = "/proxy?url=" + segmentUrl; + } + } + var req = _gthis.request(segments[0].url,{ method : "GET"},function(res) { + var tmp = Std.parseInt(res.headers["content-length"]); + var totalSize = (tmp != null ? tmp : 0) * (segments.length + 1); + if(totalSize == 0) { + onError("Failed to get segment sizes: no content-length"); + return; + } + onSuccess(lines.join("\n"),totalSize,segments); + }); + req.on("error",function(err) { + onError("Request error: failed to get segment sizes"); + }); + req.end(); + } catch( _g ) { + var _g1 = haxe_Exception.caught(_g); + onError("Playlist processing error: " + Std.string(_g1)); + } + }); + }); + req.on("error",onError); + req.end(); + } + ,downloadFile: function(client,url,fileName,onProgress,onComplete,onError) { + var file = js_node_Fs.createWriteStream("" + this.cache.cacheDir + "/" + fileName); + var req = this.request(url,{ method : "GET", headers : { "User-Agent" : "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537." + Std.random(100), "Accept" : "*/*"}},function(res) { + var total; + var tmp = Std.parseInt(res.headers["content-length"]); + total = tmp != null ? tmp : 0; + var downloaded = 0; + res.on("data",function(chunk) { + downloaded += chunk.length; + onProgress(downloaded,total); + if(!file.write(chunk)) { + res.pause(); + file.once("drain",function() { + return res.resume(); + }); + } + }); + res.on("end",function() { + file.end(); + }); + res.on("error",function(err) { + file.destroy(); + onError("Response error: " + err); + }); + }); + file.on("finish",onComplete); + file.on("error",function(err) { + req.destroy(); + onError("File error: " + err); + }); + req.on("error",function(err) { + file.destroy(); + onError("Request failed: " + err); + }); + req.end(); + } + ,cleanupFiles: function(files) { + var _g = 0; + while(_g < files.length) { + var file = files[_g]; + ++_g; + if(sys_FileSystem.exists(file)) { + js_node_Fs.unlinkSync(file); + } + } + } + ,log: function(client,msg) { + this.cache.log(client,msg); + } + ,cancelProgress: function(client) { + this.main.send(client,{ type : "Progress", progress : { type : "Canceled", ratio : 0}}); + } + ,__class__: server_cache_RawCache +}; +var server_cache_YoutubeCache = function(main,cache) { + this.main = main; + this.cache = cache; +}; +server_cache_YoutubeCache.__name__ = true; +server_cache_YoutubeCache.prototype = { + checkYtDeps: function() { + var ytdl; + try { + ytdl = require("@distube/ytdl-core"); + } catch( _g ) { + return false; + } + try { + js_node_ChildProcess.execSync("ffmpeg -version",{ stdio : "ignore", timeout : 3000}); + return true; + } catch( _g ) { + return false; + } + } + ,cleanYtInputFiles: function() { + var names = js_node_Fs.readdirSync(this.cache.cacheDir); + var _g = 0; + while(_g < names.length) { + var name = names[_g]; + ++_g; + if(!StringTools.startsWith(name,"__tmp")) { + continue; + } + this.cache.remove(name); + } + } + ,cacheYoutubeVideo: function(client,url,callback) { + var _gthis = this; + if(!this.cache.isYtReady) { + haxe_Log.trace("Do `npm i @distube/ytdl-core@latest` to use cache feature (you also need to install `ffmpeg` to build mp4 from downloaded audio/video tracks).",{ fileName : "src/server/cache/YoutubeCache.hx", lineNumber : 46, className : "server.cache.YoutubeCache", methodName : "cacheYoutubeVideo"}); + return; + } + var videoId = utils_YoutubeUtils.extractVideoId(url); + if(videoId == "") { + this.log(client,"Error: youtube video id not found in url: " + url); + return; + } + var outName = videoId + ".mp4"; + if(this.cache.exists(outName)) { + callback(outName); + return; + } + var inVideoName = "__tmp-video-" + videoId; + var inAudioName = "__tmp-audio-" + videoId; + if(this.cache.isFileExists(inVideoName)) { + this.log(client,"Caching " + outName + " already in progress"); + return; + } + var ytdl = require("@distube/ytdl-core"); + haxe_Log.trace("Caching " + url + " to " + outName + "...",{ fileName : "src/server/cache/YoutubeCache.hx", lineNumber : 80, className : "server.cache.YoutubeCache", methodName : "cacheYoutubeVideo"}); + this.main.send(client,{ type : "Progress", progress : { type : "Caching", ratio : 0, data : outName}}); + var agent = null; + var cookiesPath = "" + this.main.userDir + "/cookies.json"; + if(sys_FileSystem.exists(cookiesPath)) { + agent = ytdl.createAgent(JSON.parse(js_node_Fs.readFileSync(cookiesPath,{ encoding : "utf8"}))); + } + var promise = ytdl.getInfo(url,{ agent : agent}); + promise.then(function(info) { + haxe_Log.trace("Get info with " + info.formats.length + " formats",{ fileName : "src/server/cache/YoutubeCache.hx", lineNumber : 98, className : "server.cache.YoutubeCache", methodName : "cacheYoutubeVideo"}); + var audioFormat; + try { + var ytdl1 = ytdl.chooseFormat; + var _g = []; + var _g1 = 0; + var _g2 = info.formats; + while(_g1 < _g2.length) { + var v = _g2[_g1]; + ++_g1; + var tmp = v.audioCodec; + if(tmp != null ? StringTools.startsWith(tmp,"mp4a") : null) { + _g.push(v); + } + } + audioFormat = ytdl1(_g,{ quality : "highestaudio"}); + } catch( _g ) { + var e = haxe_Exception.caught(_g); + _gthis.log(client,"Error: audio format not found"); + haxe_Log.trace(e,{ fileName : "src/server/cache/YoutubeCache.hx", lineNumber : 105, className : "server.cache.YoutubeCache", methodName : "cacheYoutubeVideo"}); + var _g1 = []; + var _g2 = 0; + var _g3 = info.formats; + while(_g2 < _g3.length) { + var v = _g3[_g2]; + ++_g2; + if(v.hasAudio) { + _g1.push(v); + } + } + haxe_Log.trace(_g1,{ fileName : "src/server/cache/YoutubeCache.hx", lineNumber : 106, className : "server.cache.YoutubeCache", methodName : "cacheYoutubeVideo"}); + return; + } + var videoFormat; + var tmp = _gthis.getBestYoutubeVideoFormat(info.formats); + if(tmp != null) { + videoFormat = tmp; + } else { + _gthis.log(client,"Error: video format not found"); + var _g = []; + var _g1 = 0; + var _g2 = info.formats; + while(_g1 < _g2.length) { + var v = _g2[_g1]; + ++_g1; + if(v.hasVideo) { + _g.push(v); + } + } + haxe_Log.trace(_g,{ fileName : "src/server/cache/YoutubeCache.hx", lineNumber : 111, className : "server.cache.YoutubeCache", methodName : "cacheYoutubeVideo"}); + return; + } + var tmp = Std.parseInt(videoFormat.contentLength); + var videoSize = tmp != null ? tmp : 0; + var tmp = Std.parseInt(audioFormat.contentLength); + var audioSize = tmp != null ? tmp : 0; + var hasSpace = _gthis.cache.removeOlderCache((videoSize + audioSize) * 2 + _gthis.cache.freeSpaceBlock); + if(!hasSpace) { + videoFormat = _gthis.getBestYoutubeVideoFormat(info.formats,videoFormat.qualityLabel); + var tmp = Std.parseInt(videoFormat.contentLength); + var videoSize = tmp != null ? tmp : 0; + var tmp = Std.parseInt(audioFormat.contentLength); + var audioSize = tmp != null ? tmp : 0; + var hasSpace = _gthis.cache.removeOlderCache((videoSize + audioSize) * 2 + _gthis.cache.freeSpaceBlock); + if(!hasSpace) { + _gthis.cache.remove(inVideoName); + _gthis.cache.remove(inAudioName); + _gthis.cancelProgress(client); + _gthis.log(client,_gthis.cache.notEnoughSpaceErrorText); + } + if(!hasSpace) { + return; + } + } + var dlVideo = ytdl(url,{ format : videoFormat, agent : agent}); + dlVideo.pipe(js_node_Fs.createWriteStream("" + _gthis.cache.cacheDir + "/" + inVideoName)); + dlVideo.on("error",function(err) { + _gthis.log(client,"Error during video download: " + err); + _gthis.cache.remove(inVideoName); + _gthis.cache.remove(inAudioName); + _gthis.cancelProgress(client); + }); + var dlAudio = ytdl(url,{ format : audioFormat, agent : agent}); + dlAudio.pipe(js_node_Fs.createWriteStream("" + _gthis.cache.cacheDir + "/" + inAudioName)); + dlAudio.on("error",function(err) { + _gthis.log(client,"Error during audio download: " + err); + _gthis.cache.remove(inVideoName); + _gthis.cache.remove(inAudioName); + _gthis.cancelProgress(client); + }); + var count = 0; + var onComplete = function(type) { + count += 1; + haxe_Log.trace("" + type + " track downloaded (" + count + "/2)",{ fileName : "src/server/cache/YoutubeCache.hx", lineNumber : 153, className : "server.cache.YoutubeCache", methodName : "cacheYoutubeVideo"}); + if(count < 2) { + return; + } + if(!_gthis.cache.isFileExists(inVideoName) || !_gthis.cache.isFileExists(inAudioName)) { + _gthis.log(client,"Input files not found for making final video"); + _gthis.cache.remove(inVideoName); + _gthis.cache.remove(inAudioName); + _gthis.cancelProgress(client); + return; + } + var size = js_node_Fs.statSync("" + _gthis.cache.cacheDir + "/" + inVideoName).size; + size += js_node_Fs.statSync("" + _gthis.cache.cacheDir + "/" + inAudioName).size; + var hasSpace = _gthis.cache.removeOlderCache(size + _gthis.cache.freeSpaceBlock); + if(!hasSpace) { + _gthis.cache.remove(inVideoName); + _gthis.cache.remove(inAudioName); + _gthis.cancelProgress(client); + _gthis.log(client,_gthis.cache.notEnoughSpaceErrorText); + } + if(!hasSpace) { + return; + } + var args = ("-y -i ./" + inVideoName + " -i ./" + inAudioName + " -c copy -map 0:v -map 1:a ./" + outName).split(" "); + var $process = js_node_ChildProcess.spawn("ffmpeg",args,{ cwd : _gthis.cache.cacheDir}); + var outputData = []; + $process.stderr.on("data",function(data) { + return outputData.push(data); + }); + $process.on("close",function(code) { + _gthis.cache.remove(inVideoName); + _gthis.cache.remove(inAudioName); + if(code != 0) { + _gthis.cancelProgress(client); + var errCodeMsg = "Error: ffmpeg closed with code " + code; + var _g = []; + var _g1 = 0; + var _g2 = _gthis.main.clients; + while(_g1 < _g2.length) { + var v = _g2[_g1]; + ++_g1; + if((v.group & 1 << ClientGroup.Admin._hx_index) != 0) { + _g.push(v); + } + } + var admins = _g; + var _g = 0; + while(_g < admins.length) { + var client1 = admins[_g]; + ++_g; + _gthis.log(client1,js_node_buffer_Buffer.concat(outputData).toString()); + _gthis.log(client1,errCodeMsg); + } + if(admins.indexOf(client) == -1) { + _gthis.log(client,errCodeMsg); + } + return; + } + _gthis.cache.add(outName); + callback(outName); + }); + }; + dlVideo.on("finish",function() { + onComplete("Video"); + }); + dlAudio.on("finish",function() { + onComplete("Audio"); + }); + dlVideo.on("progress",function(chunkLength,downloaded,contentLength) { + var v = downloaded / contentLength; + var ratio = v < 0 ? 0 : v > 1 ? 1 : v; + _gthis.main.send(client,{ type : "Progress", progress : { type : "Downloading", ratio : ratio}}); + }); + }).catch(function(err) { + _gthis.cache.remove(inVideoName); + _gthis.cache.remove(inAudioName); + _gthis.cancelProgress(client); + _gthis.log(client,"" + err); + }); + } + ,getBestYoutubeVideoFormat: function(formats,ignoreQuality) { + var qPriority = [1080,720,480,360,240,144]; + var _g = 0; + while(_g < qPriority.length) { + var quality = "" + qPriority[_g++] + "p"; + if(quality == ignoreQuality) { + continue; + } + var _g1 = 0; + while(_g1 < formats.length) { + var format = formats[_g1]; + ++_g1; + if(format.videoCodec == null) { + continue; + } + if(format.qualityLabel == quality) { + return format; + } + } + } + return null; + } + ,log: function(client,msg) { + this.cache.log(client,msg); + } + ,cancelProgress: function(client) { + this.main.send(client,{ type : "Progress", progress : { type : "Canceled", ratio : 0}}); + } + ,__class__: server_cache_YoutubeCache +}; var sys_FileSystem = function() { }; sys_FileSystem.__name__ = true; sys_FileSystem.exists = function(path) { -- cgit v1.2.3