'use strict'; var _fs = require("fs"); var _config = require("./config.json"); var _utils = require("./utils"); var _utils2; var _bdIpc = require('electron').ipcMain; var _error = false; var _eol = require('os').EOL; var _mainWindow; var _cfg = {}; var _extData = {}; function BetterDiscord(mainWindow) { _mainWindow = mainWindow; _cfg = _config.cfg; _cfg.version = _config.Core.Version; _cfg.os = process.platform; _utils2 = new _utils.Utils(mainWindow); createAndCheckData(); } function createAndCheckData() { getUtils().log("Checking data/cache"); _cfg.dataPath = (_cfg.os == 'win32' ? process.env.APPDATA : _cfg.os == 'darwin' ? process.env.HOME + '/Library/Preferences' : '/var/local') + '/BetterDiscordDev/'; _cfg.userFile = _cfg.dataPath + 'user.json'; try { getUtils().mkdirSync(_cfg.dataPath); if(_fs.existsSync(_cfg.userFile)) { _cfg.userCfg = JSON.parse(_fs.readFileSync(_cfg.userFile)); } if(_cfg.userCfg.cache == null) { _cfg.userCfg.cache = new Date(); } else { var currentDate = new Date(); var cacheDate = new Date(_cfg.userCfg.cache); //Check if cache is expired if(Math.abs(currentDate.getDate() - cacheDate.getDate()) > _cfg.cache.days) { _cfg.userCfg.cache = currentDate; } else { _cfg.cache.expired = false; } } //Write new cache date if expired if(_cfg.cache.expired) { getUtils().log("Cache expired or null"); _fs.writeFileSync(_cfg.userFile, JSON.stringify(_cfg.userCfg)); } init(); } catch(err) { getUtils().err(err); exit(err.message); } } function init() { getUtils().log("Initializing"); getUtils().log("Getting latest hash"); if(_cfg.branch == null) { _cfg.branch = _cfg.beta ? "beta" : "master"; } getUtils().log("Using repo: " + _cfg.repo + " with branch: " + _cfg.branch); getUtils().download("api.github.com", "/repos/" + _cfg.repo + "/BetterDiscordApp/commits/" + _cfg.branch, function(data) { try { _cfg.hash = JSON.parse(data).sha; getUtils().injectVar("_bdhash", _cfg.hash); }catch(err) { getUtils().err(err); exit("Failed to load hash"); return; } if(_cfg.hash == undefined) { exit("Failed to load hash"); return; } getUtils().log("Hash: " + _cfg.hash); initUpdater(); }); } function initUpdater() { getUtils().log("Getting updater"); getUtils().download("raw.githubusercontent.com", "/" + _cfg.repo + "/BetterDiscordApp/" + _cfg.hash + "/data/updater.json", function(data) { try { _cfg.updater = JSON.parse(data); } catch(err) { getUtils().err(err); exit("Failed to load updater"); return; } if(_cfg.updater == undefined) { exit("Failed to load updater"); return; } if(_cfg.updater.LatestVersion == undefined || _cfg.updater.CDN == undefined) { exit("Failed to load updater"); return; } getUtils().log("Latest Version: " + _cfg.updater.LatestVersion); getUtils().log("Using CDN: " + _cfg.updater.CDN); updateExtData(); start(); }); } function updateExtData() { getUtils().log("Updating ext data"); _extData = { 'load-jQueryCookie': { 'type': 'javascript', 'resource': 'jQueryCookie', 'domain': 'cdnjs.cloudflare.com', 'url': '//cdnjs.cloudflare.com/ajax/libs/jquery-cookie/1.4.1/jquery.cookie.min.js', 'localurl': null, 'message': 'load-mainCSS', 'cacheable': false, 'variable': null }, 'load-mainCSS': { 'type': 'css', 'resource': 'Main CSS', 'domain': _cfg.updater.CDN, 'url': '//' + _cfg.updater.CDN + '/' + _cfg.repo + '/BetterDiscordApp/' + _cfg.hash + '/css/main.min.css', 'localurl': _cfg.localServer + '/BetterDiscordApp/css/main.css', 'message': 'load-mainJS', 'cacheable': false, 'variable': null }, 'load-mainJS': { 'type': 'javascript', 'resource': 'Main JS', 'domain': _cfg.updater.CDN, 'url': '//' + _cfg.updater.CDN + '/' + _cfg.repo + '/BetterDiscordApp/' + _cfg.hash + '/js/main.min.js', 'localurl': _cfg.localServer + '/BetterDiscordApp/js/main.js', 'message': 'load-emoteData-twitchGlobal', 'cacheable': false, 'variable': null }, 'load-publicServers': { 'type': 'json', 'resource': 'Public Servers', 'domain': _cfg.updater.CDN, 'url': '/' + _cfg.repo + '/BetterDiscordApp/' + _cfg.hash + '/data/serverlist.json', 'localurl': null, 'message': 'load-emoteData-twitchGlobal', 'cacheable': false, 'variable': 'publicServers' }, 'load-emoteData-twitchGlobal': { 'type': 'emotedata', 'resource': 'Twitch Global Emotedata', 'domain': 'twitchemotes.com', 'url': '/api_cache/v2/global.json', 'localurl': null, 'message': 'load-emoteData-twitchSub', 'cacheable': true, 'variable': 'emotesTwitch', 'localpath': _cfg.dataPath + "/emotes_twitch_global.json", 'encoding': "utf8", 'https': true, 'parse': false, 'specialparser': 0, 'fallback': 'load-emoteData-twitchGlobal-fallback', 'self': 'load-emoteData-twitchGlobal' }, 'load-emoteData-twitchGlobal-fallback': { 'type': 'emotedata', 'resource': 'Twitch Global Emotedata', 'domain': _cfg.updater.CDN, 'url': '/' + _cfg.repo + '/BetterDiscordApp/' + _cfg.hash + '/data/emotedata_twitch_global.json', 'localurl': null, 'message': 'load-emoteData-twitchSub', 'cacheable': true, 'variable': 'emotesTwitch', 'localpath': _cfg.dataPath + "/emotes_twitch_global.json", 'encoding': "utf8", 'https': true, 'parse': false, 'specialparser': 0, 'fallback': 'load-emoteData-twitchSub', 'self': 'load-emoteData-twitchGlobal-fallback' }, 'load-emoteData-twitchSub': { 'type': 'emotedata', 'resource': 'Twitch Subscriber Emotedata', 'domain': 'twitchemotes.com', 'url': '/api_cache/v2/subscriber.json', 'localurl': null, 'message': 'load-emoteData-ffz', 'cacheable': true, 'variable': 'subEmotesTwitch', 'localpath': _cfg.dataPath + "/emotes_twitch_subscriber.json", 'encoding': "utf8", 'https': true, 'parse': true, 'specialparser': 1, 'fallback': 'load-emoteData-twitchSub-fallback', 'self': 'load-emoteData-twitchSub' }, 'load-emoteData-twitchSub-fallback': { 'type': 'emotedata', 'resource': 'Twitch Subscriber Emotedata', 'domain': _cfg.updater.CDN, 'url': '/' + _cfg.repo + '/BetterDiscordApp/' + _cfg.hash + '/data/emotedata_twitch_subscriber.json', 'localurl': null, 'message': 'load-emoteData-ffz', 'cacheable': true, 'variable': 'subEmotesTwitch', 'localpath': _cfg.dataPath + "/emotes_twitch_subscriber.json", 'encoding': "utf8", 'https': true, 'parse': true, 'specialparser': 1, 'fallback': 'load-emoteData-ffz', 'self': 'load-emoteData-twitchSub-fallback' }, 'load-emoteData-ffz': { 'type': 'emotedata', 'resource': 'FrankerFaceZ Emotedata', 'domain': _cfg.updater.CDN, 'url': '/' + _cfg.repo + '/BetterDiscordApp/' + _cfg.hash + '/data/emotedata_ffz.json', 'localurl': null, 'message': 'load-emoteData-bttv', 'cacheable': true, 'variable': 'emotesFfz', 'localpath': _cfg.dataPath + "/emotes_ffz.json", 'encoding': "utf8", 'https': true, 'parse': true, 'specialparser': 2, 'fallback': 'load-emoteData-bttv', 'self': 'load-emoteData-ffz' }, 'load-emoteData-bttv': { 'type': 'emotedata', 'resource': 'BTTV Emotedata', 'domain': 'api.betterttv.net', 'url': '/emotes', 'localurl': null, 'message': 'load-emoteData-bttv-2', 'cacheable': true, 'variable': 'emotesBTTV', 'localpath': _cfg.dataPath + "/emotes_bttv.json", 'encoding': "utf8", 'https': true, 'parse': false, 'specialparser': 3, 'fallback': 'load-emoteData-bttv-2', 'self': 'load-emoteData-bttv' }, 'load-emoteData-bttv-2': { 'type': 'emotedata', 'resource': 'BTTV Emotedata', 'domain': _cfg.updater.CDN, 'url': '/' + _cfg.repo + '/BetterDiscordApp/' + _cfg.hash + '/data/emotedata_bttv.json', 'localurl': null, 'message': 'start-bd', 'cacheable': true, 'variable': 'emotesBTTV2', 'localpath': _cfg.dataPath + "/emotes_bttv_2.json", 'encoding': "utf8", 'https': true, 'parse': false, 'specialparser': 4, 'fallback': 'start-bd', 'self': 'load-emoteData-bttv-2' } }; } function start() { getUtils().log("Starting up"); try { var webContents = getUtils().getWebContents(); getUtils().log("Hooking dom-ready"); webContents.on('dom-ready', domReady); getUtils().log("Hooked dom-ready"); webContents.on('did-finish-loading', function() { if(domReadyHooked) { return; } getUtils().log("Hooking did-finish-loading failsafe"); domReady(); getUtils().log("Hooked did-finish-loading failsafe"); }); }catch(err) { exit(err); } } var domReadyHooked = false; var ipcHooked = false; function domReady() { domReadyHooked = true; if(ipcHooked) { load(true); return; } ipcHooked = true; load(false); } function load(reload) { getUtils().log(reload ? "Reloading" : "Loading"); if(!reload) { if(_cfg.updater.LatestVersion > _cfg.version) { getUtils().alert("Update Available", "An update for BetterDiscord is available("+_cfg.updater.LatestVersion+")! BetterDiscord.net"); } getUtils().log("Hooking ipc async"); getUtils().execJs("var betterDiscordIPC = require('electron').ipcRenderer;"); _bdIpc.on('asynchronous-message', function(event, arg) { ipcAsyncMessage(event, arg); }); getUtils().log("Hooked ipc async"); } initLoaders(); } function initLoaders() { try { getUtils().mkdirSync(_cfg.dataPath); getUtils().mkdirSync(_cfg.dataPath + "plugins/"); getUtils().mkdirSync(_cfg.dataPath + "themes/"); getUtils().execJs('var themesupport2 = true'); loadPlugins(); loadThemes(); loadApp(); }catch(err) { exit(err); } } function loadPlugins() { var pluginPath = _cfg.dataPath + "plugins/"; _fs.readdir(pluginPath, function(err, files) { if(err) { getUtils().log(err); getUtils().alert(err); return; } var pluginErrors = []; getUtils().injectVarRaw("bdplugins", "{}"); files.forEach(function(fileName) { if(!fileName.endsWith(".plugin.js")) { getUtils().log("Invalid plugin detected: " + fileName); return; } var plugin = _fs.readFileSync(pluginPath + fileName, 'utf8'); var meta = plugin.split(_eol)[0]; if (meta.indexOf('META') < 0) { getUtils().warn('Plugin META not found in file: ' + fileName); pluginErrors.push(fileName + " Reason: Plugin META not found"); return; } var pluginVar = meta.substring(meta.lastIndexOf('//META') + 6, meta.lastIndexOf('*//')); var parse; try { parse = JSON.parse(pluginVar); }catch(err) { getUtils().warn("Failed to parse plugin META in file: " + fileName + "("+err+")"); pluginErrors.push(fileName + " Reason: Failed to parse plugin META (" + err + ")"); return; } if(parse["name"] == undefined) { getUtils().warn("Undefined plugin name in file: " + fileName); pluginErrors.push(fileName + " Reason: invalid plugin name"); return; } getUtils().log("Loading plugin: " + parse["name"]); getUtils().execJs(plugin); getUtils().execJs('(function() { var plugin = new ' + parse["name"] + '(); bdplugins[plugin.getName()] = { "plugin": plugin, "enabled": false } })();') }); if(pluginErrors.length > 0) { getUtils().alert("The following plugin(s) could not be loaded", pluginErrors.join("
")); } }); } function loadThemes() { var themePath = _cfg.dataPath + "themes/"; _fs.readdir(themePath, function(err, files) { if(err) { getUtils().log(err); getUtils().alert(err); return; } var themeErrors = []; getUtils().injectVarRaw("bdthemes", "{}"); files.forEach(function(fileName) { if(!fileName.endsWith(".theme.css")) { getUtils().log("Invalid theme detected " + fileName); return; } var theme = _fs.readFileSync(themePath + fileName, 'utf8'); var split = theme.split(_eol); var meta = split[0]; if(meta.indexOf('META') < 0) { getUtils().warn("Theme META not found in file: " + fileName); themeErrors.push(fileName + " Reason: Theme META not found"); return; } var themeVar = meta.substring(meta.lastIndexOf('//META') + 6, meta.lastIndexOf('*//')); var themeInfo; try { themeInfo = JSON.parse(themeVar); }catch(err) { getUtils().warn("Failed to parse theme META in file: " + fileName + "("+err+")"); themeErrors.push(fileName + " Reason: Failed to parse theme META (" + err + ")"); return; } if(themeInfo['name'] == undefined) { getUtils().warn("Missing theme name in file: " + fileName); themeErrors.push(fileName + " Reason: Missing theme name"); return; } if(themeInfo['author'] == undefined) { getUtils().warn("Missing author name in file: " + fileName); } if(themeInfo['description'] == undefined) { getUtils().warn("Missing description in file: " + fileName); } if(themeInfo['version'] == undefined) { getUtils().warn("Missing version in file: " + fileName); } getUtils().log("Loading theme: " + themeInfo['name']); split.splice(0, 1); theme = split.join(_eol); theme = theme.replace(/(\r\n|\n|\r)/gm, ''); _mainWindow.webContents.executeJavaScript('(function() { bdthemes["' + themeInfo['name'] + '"] = { "enabled": false, "name": "' + themeInfo['name'] + '", "css": "' + escape(theme) + '", "description": "' + themeInfo['description'] + '", "author":"' + themeInfo['author'] + '", "version":"' + themeInfo['version'] + '" } })();'); }); if(themeErrors.length > 0) { getUtils().alert("The following theme(s) could not be loaded", themeErrors.join("
")); } }); } function loadApp() { getUtils().execJs('var loadingNode = document.createElement("DIV");'); getUtils().execJs('loadingNode.innerHTML = \'
BetterDiscord - Loading Libraries :
\''); getUtils().execJs('var flex = document.getElementsByClassName("flex-vertical flex-spacer")[0]; flex.appendChild(loadingNode);'); getUtils().injectVar('bdVersion', _cfg.version); getUtils().injectVar('bdCdn', _cfg.CDN); getUtils().updateLoading("Loading Resource (jQuery)", 0, 100); getUtils().injectJavaScriptSync("//ajax.googleapis.com/ajax/libs/jquery/2.0.0/jquery.min.js", "load-jQueryCookie"); } function ipcAsyncMessage(event, arg) { if(_extData.hasOwnProperty(arg)) { loadExtData(_extData[arg]); } if(arg == "start-bd") { getUtils().updateLoading("Starting Up", 100, 100); getUtils().execJs('var mainCore; var startBda = function() { mainCore = new Core(); mainCore.init(); }; startBda();'); //Remove loading node setTimeout(function() { getUtils().execJs('$("#bd-status").parent().parent().hide();'); }, 2000); getUtils().saveLogs(_cfg.dataPath); } } var loadCounter = 0; function loadExtData(extData) { loadCounter++; getUtils().updateLoading("Loading Resource (" + extData.resource + ")", loadCounter / Object.keys(_extData).length * 100, 100); var url = (_cfg.local && extData.localurl != null) ? extData.localurl : extData.url; try { switch(extData.type) { case 'javascript': getUtils().injectJavaScriptSync(url, extData.message); break; case 'css': getUtils().injectStylesheetSync(url, extData.message); break; case 'json': getUtils().download(extData.domain, extData.url, function(data) { getUtils().injectVar(extData.variable, data); getUtils().sendIcpAsync(extData.message); }); break; case 'emotedata': if(extData.variable != "emotesTwitch") { getUtils().injectVarRaw(extData.variable, "{}"); } var exists = _fs.existsSync(extData.localpath); if(exists && !_cfg.cache.expired && extData.cacheable) { loadEmoteData(extData, true); } else { loadEmoteData(extData, false); } break; } }catch(err) { getUtils().warn(err); getUtils().alert("Something went wrong :( Attempting to run.", err); getUtils().sendIcpAsync(extData.message); } } function loadEmoteData(extData, local) { if(local) { getUtils().log("Reading " + extData.resource + " from file"); var data = _fs.readFileSync(extData.localpath, extData.encoding); if(testJSON(extData, data)) { injectEmoteData(extData, data); } else { getUtils().log("Deleting cached file " + extData.resource); _fs.unlinkSync(extData.localpath); getUtils().sendIcpAsync(extData.self); } return; } if(extData.https) { getUtils().download(extData.domain, extData.url, function(data) { var parsedEmoteData = parseEmoteData(extData, data); if(parsedEmoteData == null) { getUtils().sendIcpAsync(extData.fallback); return true; } saveEmoteData(extData, parsedEmoteData); injectEmoteData(extData, parsedEmoteData); }); return; } getUtils().downloadHttp(extData.url, function(data) { var parsedEmoteData = parseEmoteData(extData, data); if(parsedEmoteData == null) { getUtils().sendIcpAsync(extData.fallback); return true; } saveEmoteData(extData, parsedEmoteData); injectEmoteData(extData, parsedEmoteData); }); } function testJSON(extData, data) { getUtils().log("Validating " + extData.resource); try { var json = JSON.parse(data); getUtils().log(extData.resource + " is valid"); return true; }catch(err) { getUtils().warn(extData.resource + " is invalid"); return false; } return false; } function injectEmoteData(extData, data) { if(data == null) { getUtils().sendIcpAsync(extData.message); return; } if(data.parse) { getUtils().injectVarRaw(extData.variable, 'JSON.parse(\'' + data + '\');'); } else { getUtils().injectVarRaw(extData.variable, data); } getUtils().sendIcpAsync(extData.message); } function saveEmoteData(extData, data) { try { getUtils().log("Saving resource to file " + extData.resource); _fs.writeFileSync(extData.localpath, data, extData.encoding); } catch(err) { getUtils().err("Failed to save resource to file " + extData.resource); } } function parseEmoteData(extData, data) { getUtils().log("Parsing: " + extData.resource); var returnData; switch(extData.specialparser) { case 0: //Twitch Global Emotes //returnData = emoteData.replace(/\$/g, "\\$").replace(/'/g, "\\'").replace(/"/g, "\\\""); We don't need this anymore return data; break; case 1: //Twitch Subscriber Emotes returnData = {}; if(!testJSON(extData, data)) { return null; } data = JSON.parse(data); var channels = data["channels"]; for(var channel in channels) { var emotes = channels[channel]["emotes"]; for(var i = 0 ; i < emotes.length ; i++) { var code = emotes[i]["code"]; var id = emotes[i]["image_id"]; returnData[code] = id; } } returnData = JSON.stringify(returnData); break; case 2: //FFZ Emotes returnData = data; break; case 3: //BTTV Emotes returnData = {}; if(!testJSON(extData, data)) { return null; } data = JSON.parse(data); for(var emote in data.emotes) { emote = data.emotes[emote]; var url = emote.url; var code = emote.regex; returnData[code] = url; } returnData = JSON.stringify(returnData); break; case 4: returnData = data; break; } return returnData; } function getUtils() { return _utils2; } function exit(reason) { error = true; getUtils().log("Exiting. Reason: " + reason); getUtils().saveLogs(_cfg.dataPath); getUtils().alert("Something went wrong :(", reason); } exports.BetterDiscord = BetterDiscord;