import {bdConfig, minSupportedVersion, bbdVersion, settingsCookie, bdpluginErrors, bdthemeErrors, bbdChangelog, defaultCookie, currentDiscordVersion} from "../0globals"; import Utils from "./utils"; import emoteModule from "./emoteModule"; import quickEmoteMenu from "./quickEmoteMenu"; import BDV2 from "./v2"; import settingsPanel from "./settingsPanel"; import pluginModule from "./pluginModule"; import themeModule from "./themeModule"; import DataStore from "./dataStore"; import WebpackModules from "./webpackModules"; import DOM from "./domtools"; import BDLogo from "../ui/bdLogo"; import TooltipWrap from "../ui/tooltipWrap"; const {ipcRenderer} = require("electron"); function Core() { ipcRenderer.invoke("bd-config").then(injectorConfig => { if (this.hasStarted) return; Object.assign(bdConfig, injectorConfig); this.init(); }); ipcRenderer.invoke("bd-injector-info").then(injectorInfo => { bdConfig.version = injectorInfo.version; const request = require("request"); request.get({url: `https://gitcdn.xyz/repo/rauenzi/BetterDiscordApp/injector/package.json`, json: true}, (err, resp, body) => { let remoteVersion = "0.6.1"; if (!err && resp.statusCode == 200) remoteVersion = body.version || remoteVersion; bdConfig.latestVersion = remoteVersion; this.checkInjectorUpdate(); }); }); } Core.prototype.setConfig = function(config) { if (this.hasStarted) return; Object.assign(bdConfig, config); }; Core.prototype.init = async function() { if (this.hasStarted) return; this.hasStarted = true; if (!Array.prototype.flat) { Utils.alert("Not Supported", "BetterDiscord v" + bbdVersion + " does not support this old version (" + currentDiscordVersion + ") of Discord. Please update your Discord installation before proceeding."); return; } if (bdConfig.version < minSupportedVersion) { Utils.alert("Not Supported", "BetterDiscord v" + bdConfig.version + " (your version)" + " is not supported by the latest js (" + bbdVersion + ").

Please download the latest version from GitHub"); return; } if (window.ED) { Utils.alert("Not Supported", "BandagedBD does not work with EnhancedDiscord. Please uninstall one of them."); return; } if (window.WebSocket && window.WebSocket.name && window.WebSocket.name.includes("Patched")) { Utils.alert("Not Supported", "BandagedBD does not work with Powercord. Please uninstall one of them."); return; } Utils.log("Startup", "Initializing Settings"); this.initSettings(); Utils.log("Startup", "Initializing EmoteModule"); window.emotePromise = emoteModule.init().then(() => { emoteModule.initialized = true; Utils.log("Startup", "Initializing QuickEmoteMenu"); quickEmoteMenu.init(); }); await this.injectExternals(); await this.checkForGuilds(); BDV2.initialize(); Utils.log("Startup", "Updating Settings"); settingsPanel.initializeSettings(); Utils.log("Startup", "Loading Plugins"); pluginModule.loadPlugins(); Utils.log("Startup", "Loading Themes"); themeModule.loadThemes(); DOM.addStyle("customcss", atob(DataStore.getBDData("bdcustomcss"))); window.addEventListener("beforeunload", function() { if (settingsCookie["bda-dc-0"]) document.querySelector(".btn.btn-disconnect").click(); }); Utils.log("Startup", "Removing Loading Icon"); if (document.getElementsByClassName("bd-loaderv2").length) document.getElementsByClassName("bd-loaderv2")[0].remove(); Utils.log("Startup", "Initializing Main Observer"); this.initObserver(); // Show loading errors if (settingsCookie["fork-ps-1"]) { Utils.log("Startup", "Collecting Startup Errors"); Utils.showContentErrors({plugins: bdpluginErrors, themes: bdthemeErrors}); } const previousVersion = DataStore.getBDData("version"); if (bbdVersion > previousVersion) { if (bbdChangelog) this.showChangelogModal(bbdChangelog); DataStore.setBDData("version", bbdVersion); } Utils.suppressErrors(this.patchSocial.bind(this), "BD Social Patch")(); Utils.suppressErrors(this.patchGuildPills.bind(this), "BD Guild Pills Patch")(); Utils.suppressErrors(this.patchGuildListItems.bind(this), "BD Guild List Items Patch")(); Utils.suppressErrors(this.patchGuildSeparator.bind(this), "BD Guild Separator Patch")(); Utils.suppressErrors(this.patchMessageHeader.bind(this), "BD Badge Chat Patch")(); Utils.suppressErrors(this.patchMemberList.bind(this), "BD Badge Member List Patch")(); }; Core.prototype.checkForGuilds = function() { let timesChecked = 0; return new Promise(resolve => { const checkForGuilds = function() { const wrapper = BDV2.guildClasses.wrapper.split(" ")[0]; if (document.querySelectorAll(`.${wrapper}`).length > 0) timesChecked++; const guild = BDV2.guildClasses.listItem.split(" ")[0]; const blob = BDV2.guildClasses.blobContainer.split(" ")[0]; if (document.querySelectorAll(`.${wrapper} .${guild} .${blob}`).length > 0) return resolve(bdConfig.deferLoaded = true); else if (timesChecked >= 50) return resolve(bdConfig.deferLoaded = true); setTimeout(checkForGuilds, 100); }; if (document.readyState != "loading") setTimeout(checkForGuilds, 100); document.addEventListener("DOMContentLoaded", () => {setTimeout(checkForGuilds, 100);}); }); }; Core.prototype.injectExternals = async function() { await DOM.addScript("ace-script", "https://cdnjs.cloudflare.com/ajax/libs/ace/1.4.12/ace.js"); if (window.require.original) window.require = window.require.original; if (window.$ && window.jQuery) return; // Dependencies already loaded try {await DOM.addScript("jquery", "https://ajax.googleapis.com/ajax/libs/jquery/2.0.0/jquery.min.js");} catch (_) { try { await DOM.addScript("jquery", "https://cdn.jsdelivr.net/gh/jquery/jquery@2.0.0/jquery.min.js"); } catch (__) { Utils.alert("jQuery Not Loaded", "Unable to load jQuery, some plugins may fail to work. Proceed at your own risk."); } } }; Core.prototype.initSettings = function () { DataStore.initialize(); if (!DataStore.getSettingGroup("settings")) { Object.assign(settingsCookie, defaultCookie); settingsPanel.saveSettings(); } else { settingsPanel.loadSettings(); for (const setting in defaultCookie) { if (settingsCookie[setting] == undefined) { settingsCookie[setting] = defaultCookie[setting]; settingsPanel.saveSettings(); } } } }; Core.prototype.initObserver = function () { const mainObserver = new MutationObserver((mutations) => { for (let i = 0, mlen = mutations.length; i < mlen; i++) { const mutation = mutations[i]; if (typeof pluginModule !== "undefined") pluginModule.rawObserver(mutation); // if there was nothing added, skip if (!mutation.addedNodes.length || !(mutation.addedNodes[0] instanceof Element)) continue; const node = mutation.addedNodes[0]; if (node.classList.contains("layer-3QrUeG")) { if (node.getElementsByClassName("guild-settings-base-section").length) node.setAttribute("layer-id", "server-settings"); if (node.getElementsByClassName("socialLinks-3jqNFy").length) { node.setAttribute("layer-id", "user-settings"); node.setAttribute("id", "user-settings"); if (!document.getElementById("bd-settings-sidebar")) settingsPanel.renderSidebar(); } } if (node.parentElement == document.body && node.querySelector("#ace_settingsmenu")) node.id = "ace_settingsmenu_container"; // Emoji Picker //node.getElementsByClassName("emojiPicker-3m1S-j").length && !node.querySelector(".emojiPicker-3m1S-j").parentElement.classList.contains("animatorLeft-1EQxU0") if (node.classList.contains("layer-v9HyYc") && node.getElementsByClassName("emojiPicker-3m1S-j").length && !node.querySelector(".emojiPicker-3m1S-j").parentElement.classList.contains("animatorLeft-1EQxU0")) quickEmoteMenu.obsCallback(node); } }); mainObserver.observe(document, { childList: true, subtree: true }); }; Core.prototype.showChangelogModal = function(options = {}) { return Utils.showChangelogModal(options); }; Core.prototype.alert = function(title, content) { return Utils.alert(title, content); }; Core.prototype.patchSocial = function() { if (this.socialPatch) return; const TabBar = WebpackModules.find(m => m.displayName == "TabBar"); const Anchor = WebpackModules.find(m => m.displayName == "Anchor"); if (!TabBar) return; this.socialPatch = Utils.monkeyPatch(TabBar.prototype, "render", {after: (data) => { const children = data.returnValue.props.children; if (!children || !children.length || children.length < 3) return; if (children[children.length - 3].type.displayName !== "Separator") return; if (!children[children.length - 2].type.toString().includes("socialLinks")) return; if (Anchor) { const original = children[children.length - 2].type; const newOne = function() { const returnVal = original(...arguments); returnVal.props.children.push( BDV2.React.createElement(TooltipWrap, {color: "black", side: "top", text: "BandagedBD"}, BDV2.React.createElement(Anchor, {className: "bd-social-link", href: "https://github.com/rauenzi/BetterDiscordApp", title: "BandagedBD", target: "_blank"}, BDV2.React.createElement(BDLogo, {size: "16px", className: "bd-social-logo"}) ) ) ); return returnVal; }; children[children.length - 2].type = newOne; } const injector = BDV2.react.createElement("div", {className: "colorMuted-HdFt4q size12-3cLvbJ"}, `Injector ${bdConfig.version}`); const additional = BDV2.react.createElement("div", {className: "colorMuted-HdFt4q size12-3cLvbJ"}, `BBD ${bbdVersion} `); const originalVersions = children[children.length - 1].type; children[children.length - 1].type = function() { const returnVal = originalVersions(...arguments); returnVal.props.children.splice(returnVal.props.children.length - 1, 0, injector); returnVal.props.children.splice(1, 0, additional); return returnVal; }; }}); }; const getGuildClasses = function() { const guildsWrapper = WebpackModules.findByProps("wrapper", "unreadMentionsBar"); const guilds = WebpackModules.findByProps("guildsError", "selected"); const pill = WebpackModules.findByProps("blobContainer"); return Object.assign({}, guildsWrapper, guilds, pill); }; Core.prototype.patchGuildListItems = function() { if (this.guildListItemsPatch) return; const GuildClasses = getGuildClasses(); const listItemClass = GuildClasses.listItem.split(" ")[0]; const blobClass = GuildClasses.blobContainer.split(" ")[0]; const reactInstance = BDV2.getInternalInstance(document.querySelector(`.${listItemClass} .${blobClass}`).parentElement); const GuildComponent = reactInstance.return.type; if (!GuildComponent) return; this.guildListItemsPatch = Utils.monkeyPatch(GuildComponent.prototype, "render", {after: (data) => { if (data.returnValue && data.thisObject) { const returnValue = data.returnValue; const guildData = data.thisObject.props; returnValue.props.className += " bd-guild"; if (guildData.unread) returnValue.props.className += " bd-unread"; if (guildData.selected) returnValue.props.className += " bd-selected"; if (guildData.audio) returnValue.props.className += " bd-audio"; if (guildData.video) returnValue.props.className += " bd-video"; if (guildData.badge) returnValue.props.className += " bd-badge"; if (guildData.animatable) returnValue.props.className += " bd-animatable"; return returnValue; } }}); }; Core.prototype.patchGuildPills = function() { if (this.guildPillPatch) return; const guildPill = WebpackModules.find(m => m.default && !m.default.displayName && m.default.toString && m.default.toString().includes("translate3d")); if (!guildPill) return; this.guildPillPatch = Utils.monkeyPatch(guildPill, "default", {after: (data) => { const props = data.methodArguments[0]; if (props.unread) data.returnValue.props.className += " bd-unread"; if (props.selected) data.returnValue.props.className += " bd-selected"; if (props.hovered) data.returnValue.props.className += " bd-hovered"; return data.returnValue; }}); }; Core.prototype.patchGuildSeparator = function() { if (this.guildSeparatorPatch) return; const Guilds = WebpackModules.findByDisplayName("Guilds"); const guildComponents = WebpackModules.findByProps("renderListItem"); if (!guildComponents || !Guilds) return; const GuildSeparator = function() { const returnValue = guildComponents.Separator(...arguments); returnValue.props.className += " bd-guild-separator"; return returnValue; }; this.guildSeparatorPatch = Utils.monkeyPatch(Guilds.prototype, "render", {after: (data) => { data.returnValue.props.children[1].props.children[3].type = GuildSeparator; }}); }; Core.prototype.patchMessageHeader = function() { if (this.messageHeaderPatch) return; const MessageHeader = WebpackModules.findByProps("MessageTimestamp"); const Anchor = WebpackModules.find(m => m.displayName == "Anchor"); if (!Anchor || !MessageHeader || !MessageHeader.default) return; this.messageHeaderPatch = Utils.monkeyPatch(MessageHeader, "default", {after: (data) => { const author = Utils.getNestedProp(data.methodArguments[0], "message.author"); // const header = Utils.getNestedProp(data.returnValue, "props.children.1.props"); const children = Utils.getNestedProp(data.returnValue, "props.children.1.props.children.1.props.children"); if (!children || !author || !author.id || author.id !== "249746236008169473") return; // if (header && header.className) header.className += " " if (!Array.isArray(children)) return; children.push( BDV2.React.createElement(TooltipWrap, {color: "black", side: "top", text: "BandagedBD Developer"}, BDV2.React.createElement(Anchor, {className: "bd-chat-badge", href: "https://github.com/rauenzi/BetterDiscordApp", title: "BandagedBD", target: "_blank"}, BDV2.React.createElement(BDLogo, {size: "16px", className: "bd-logo"}) ) ) ); }}); }; Core.prototype.patchMemberList = function() { if (this.memberListPatch) return; const MemberListItem = WebpackModules.findByDisplayName("MemberListItem"); const Anchor = WebpackModules.find(m => m.displayName == "Anchor"); if (!Anchor || !MemberListItem || !MemberListItem.prototype || !MemberListItem.prototype.renderDecorators) return; this.memberListPatch = Utils.monkeyPatch(MemberListItem.prototype, "renderDecorators", {after: (data) => { const user = Utils.getNestedProp(data.thisObject, "props.user"); const children = Utils.getNestedProp(data.returnValue, "props.children"); if (!children || !user || !user.id || user.id !== "249746236008169473") return; if (!Array.isArray(children)) return; children.push( BDV2.React.createElement(TooltipWrap, {color: "black", side: "top", text: "BandagedBD Developer"}, BDV2.React.createElement(Anchor, {className: "bd-member-badge", href: "https://github.com/rauenzi/BetterDiscordApp", title: "BandagedBD", target: "_blank"}, BDV2.React.createElement(BDLogo, {size: "16px", className: "bd-logo"}) ) ) ); }}); }; Core.prototype.checkInjectorUpdate = function() { const latestLocalVersion = bdConfig.updater ? bdConfig.updater.LatestVersion : bdConfig.latestVersion; if (latestLocalVersion > bdConfig.version) { Utils.showConfirmationModal("Update Available", [`There is an update available for BandagedBD's Injector (${latestLocalVersion}).`, "You can either update and restart now, or later."], { confirmText: "Update Now", cancelText: "Maybe Later", onConfirm: async () => { const onUpdateFailed = () => {Utils.alert("Could Not Update", `Unable to update automatically, please download the installer and reinstall normally.

Download Installer`);}; try { const didUpdate = await this.updateInjector(); if (!didUpdate) return onUpdateFailed(); const app = require("electron").remote.app; app.relaunch(); app.exit(); } catch (err) { onUpdateFailed(); } } }); } }; Core.prototype.updateInjector = async function() { const injectionPath = DataStore.injectionPath; if (!injectionPath) return false; const fs = require("fs"); const path = require("path"); const rmrf = require("rimraf"); const yauzl = require("yauzl"); const mkdirp = require("mkdirp"); const request = require("request"); const parentPath = path.resolve(injectionPath, ".."); const folderName = path.basename(injectionPath); const zipLink = "https://github.com/rauenzi/BetterDiscordApp/archive/injector.zip"; const savedZip = path.resolve(parentPath, "injector.zip"); const extractedFolder = path.resolve(parentPath, "BetterDiscordApp-injector"); // Download the injector zip file Utils.log("InjectorUpdate", "Downloading " + zipLink); let success = await new Promise(resolve => { request.get({url: zipLink, encoding: null}, async (error, response, body) => { if (error || response.statusCode !== 200) return resolve(false); // Save a backup in case someone has their own copy const alreadyExists = await new Promise(res => fs.exists(savedZip, res)); if (alreadyExists) await new Promise(res => fs.rename(savedZip, `${savedZip}.bak${Math.round(performance.now())}`, res)); Utils.log("InjectorUpdate", "Writing " + savedZip); fs.writeFile(savedZip, body, err => resolve(!err)); }); }); if (!success) return success; // Check and delete rename extraction const alreadyExists = await new Promise(res => fs.exists(extractedFolder, res)); if (alreadyExists) await new Promise(res => fs.rename(extractedFolder, `${extractedFolder}.bak${Math.round(performance.now())}`, res)); // Unzip the downloaded zip file const zipfile = await new Promise(r => yauzl.open(savedZip, {lazyEntries: true}, (err, zip) => r(zip))); zipfile.on("entry", function(entry) { // Skip directories, they are handled with mkdirp if (entry.fileName.endsWith("/")) return zipfile.readEntry(); Utils.log("InjectorUpdate", "Extracting " + entry.fileName); // Make any needed parent directories const fullPath = path.resolve(parentPath, entry.fileName); mkdirp.sync(path.dirname(fullPath)); zipfile.openReadStream(entry, function(err, readStream) { if (err) return success = false; readStream.on("end", function() {zipfile.readEntry();}); // Go to next file after this readStream.pipe(fs.createWriteStream(fullPath)); }); }); zipfile.readEntry(); // Start reading // Wait for the final file to finish await new Promise(resolve => zipfile.once("end", resolve)); // Save a backup in case something goes wrong during final step const backupFolder = path.resolve(parentPath, `${folderName}.bak${Math.round(performance.now())}`); await new Promise(resolve => fs.rename(injectionPath, backupFolder, resolve)); // Rename the extracted folder to what it should be Utils.log("InjectorUpdate", `Renaming ${path.basename(extractedFolder)} to ${folderName}`); success = await new Promise(resolve => fs.rename(extractedFolder, injectionPath, err => resolve(!err))); if (!success) { Utils.err("InjectorUpdate", "Failed to rename the final directory"); return success; } // If rename had issues, delete what we tried to rename and restore backup if (!success) { Utils.err("InjectorUpdate", "Something went wrong... restoring backups."); await new Promise(resolve => rmrf(extractedFolder, resolve)); await new Promise(resolve => fs.rename(backupFolder, injectionPath, resolve)); return success; } // If we've gotten to this point, everything should have gone smoothly. // Cleanup the backup folder then remove the zip await new Promise(resolve => rmrf(backupFolder, resolve)); await new Promise(resolve => fs.unlink(savedZip, resolve)); Utils.log("InjectorUpdate", "Injector Updated!"); return success; }; export default new Core();