import {bbdVersion} from "../0globals"; import WebpackModules from "./webpackModules"; import BDV2 from "./v2"; import DOM from "./domtools"; export default class Utils { /** Document/window width */ static get screenWidth() { return Math.max(document.documentElement.clientWidth, window.innerWidth || 0); } /** Document/window height */ static get screenHeight() { return Math.max(document.documentElement.clientHeight, window.innerHeight || 0); } static get WindowConfigFile() { if (this._windowConfigFile) return this._windowConfigFile; const electron = require("electron").remote.app; const path = require("path"); const base = electron.getAppPath(); const roamingBase = electron.getPath("userData"); const roamingLocation = path.resolve(roamingBase, electron.getVersion(), "modules", "discord_desktop_core", "injector", "config.json"); const location = path.resolve(base, "..", "app", "config.json"); const fs = require("fs"); const realLocation = fs.existsSync(location) ? location : fs.existsSync(roamingLocation) ? roamingLocation : null; if (!realLocation) return this._windowConfigFile = null; return this._windowConfigFile = realLocation; } static getAllWindowPreferences() { if (!this.WindowConfigFile) return {}; // Tempfix until new injection on other platforms return __non_webpack_require__(this.WindowConfigFile); } static getWindowPreference(key) { if (!this.WindowConfigFile) return undefined; // Tempfix until new injection on other platforms return this.getAllWindowPreferences()[key]; } static setWindowPreference(key, value) { if (!this.WindowConfigFile) return; // Tempfix until new injection on other platforms const fs = require("fs"); const prefs = this.getAllWindowPreferences(); prefs[key] = value; delete __non_webpack_require__.cache[this.WindowConfigFile]; fs.writeFileSync(this.WindowConfigFile, JSON.stringify(prefs, null, 4)); } static stripBOM(content) { if (content.charCodeAt(0) === 0xFEFF) { content = content.slice(1); } return content; } static getTextArea() { return DOM.query(".channelTextArea-rNsIhG textarea"); } static insertText(textarea, text) { textarea.focus(); textarea.selectionStart = 0; textarea.selectionEnd = textarea.value.length; document.execCommand("insertText", false, text); } static escapeID(id) { return id.replace(/^[^a-z]+|[^\w-]+/gi, "-"); } static log(moduleName, message) { console.log(`%c[BandagedBD]%c [${moduleName}]%c ${message}`, "color: #3a71c1; font-weight: 700;", "color: #3a71c1;", ""); } static warn(moduleName, message) { console.warn(`%c[BandagedBD]%c [${moduleName}]%c ${message}`, "color: #E8A400; font-weight: 700;", "color: #E8A400;", ""); } static err(moduleName, message, error) { console.log(`%c[BandagedBD]%c [${moduleName}]%c ${message}`, "color: red; font-weight: 700;", "color: red;", ""); if (error) { console.groupCollapsed("%cError: " + error.message, "color: red;"); console.error(error.stack); console.groupEnd(); } } static escape(s) { return s.replace(/[-/\\^$*+?.()|[\]{}]/g, "\\$&"); } static testJSON(data) { try { return JSON.parse(data); } catch (err) { return false; } } static isEmpty(obj) { if (obj == null || obj == undefined || obj == "") return true; if (typeof(obj) !== "object") return false; if (Array.isArray(obj)) return obj.length == 0; for (const key in obj) { if (obj.hasOwnProperty(key)) return false; } return true; } static suppressErrors(method, message) { return (...params) => { try { return method(...params); } catch (e) { this.err("SuppressedError", "Error occurred in " + message, e); } }; } static monkeyPatch(what, methodName, options) { const {before, after, instead, once = false, silent = false, force = false} = options; const displayName = options.displayName || what.displayName || what[methodName].displayName || what.name || what.constructor.displayName || what.constructor.name; if (!silent) console.log("patch", methodName, "of", displayName); // eslint-disable-line no-console if (!what[methodName]) { if (force) what[methodName] = function() {}; else return console.error(methodName, "does not exist for", displayName); // eslint-disable-line no-console } const origMethod = what[methodName]; const cancel = () => { if (!silent) console.log("unpatch", methodName, "of", displayName); // eslint-disable-line no-console what[methodName] = origMethod; }; what[methodName] = function() { const data = { thisObject: this, methodArguments: arguments, cancelPatch: cancel, originalMethod: origMethod, callOriginalMethod: () => data.returnValue = data.originalMethod.apply(data.thisObject, data.methodArguments) }; if (instead) { const tempRet = Utils.suppressErrors(instead, "`instead` callback of " + what[methodName].displayName)(data); if (tempRet !== undefined) data.returnValue = tempRet; } else { if (before) Utils.suppressErrors(before, "`before` callback of " + what[methodName].displayName)(data); data.callOriginalMethod(); if (after) Utils.suppressErrors(after, "`after` callback of " + what[methodName].displayName)(data); } if (once) cancel(); return data.returnValue; }; Object.assign(what[methodName], origMethod); what[methodName].__monkeyPatched = true; what[methodName].displayName = displayName; if (!what[methodName].__originalMethod) { what[methodName].__originalMethod = origMethod; what[methodName].toString = function() {return origMethod.toString();}; } return cancel; } static onRemoved(node, callback) { const observer = new MutationObserver((mutations) => { for (let m = 0; m < mutations.length; m++) { const mutation = mutations[m]; const nodes = Array.from(mutation.removedNodes); const directMatch = nodes.indexOf(node) > -1; const parentMatch = nodes.some(parent => parent.contains(node)); if (directMatch || parentMatch) { observer.disconnect(); callback(); } } }); observer.observe(document.body, {subtree: true, childList: true}); } static getNestedProp(obj, path) { return path.split(/\s?\.\s?/).reduce(function(obj, prop) { return obj && obj[prop]; }, obj); } /** * This shows a toast similar to android towards the bottom of the screen. * * @param {string} content The string to show in the toast. * @param {object} options Options object. Optional parameter. * @param {string} options.type Changes the type of the toast stylistically and semantically. Choices: "", "info", "success", "danger"/"error", "warning"/"warn". Default: "" * @param {boolean} options.icon Determines whether the icon should show corresponding to the type. A toast without type will always have no icon. Default: true * @param {number} options.timeout Adjusts the time (in ms) the toast should be shown for before disappearing automatically. Default: 3000 */ static showToast(content, options = {}) { // if (!bdConfig.deferLoaded) return; if (!document.querySelector(".bd-toasts")) { const toastWrapper = document.createElement("div"); toastWrapper.classList.add("bd-toasts"); const boundingElement = document.querySelector(".chat-3bRxxu form, #friends, .noChannel-Z1DQK7, .activityFeed-28jde9"); toastWrapper.style.setProperty("left", boundingElement ? boundingElement.getBoundingClientRect().left + "px" : "0px"); toastWrapper.style.setProperty("width", boundingElement ? boundingElement.offsetWidth + "px" : "100%"); toastWrapper.style.setProperty("bottom", (document.querySelector(".chat-3bRxxu form") ? document.querySelector(".chat-3bRxxu form").offsetHeight : 80) + "px"); document.querySelector(".app, .app-2rEoOp").appendChild(toastWrapper); } const {type = "", icon = true, timeout = 3000} = options; const toastElem = document.createElement("div"); toastElem.classList.add("bd-toast"); if (type) toastElem.classList.add("toast-" + type); if (type && icon) toastElem.classList.add("icon"); toastElem.innerText = content; document.querySelector(".bd-toasts").appendChild(toastElem); setTimeout(() => { toastElem.classList.add("closing"); setTimeout(() => { toastElem.remove(); if (!document.querySelectorAll(".bd-toasts .bd-toast").length) document.querySelector(".bd-toasts").remove(); }, 300); }, timeout); } static alert(title, content) { const modal = DOM.createElement(`