From 5b1900fe3da07f0c1e7a872e66d1fe79f0b85c37 Mon Sep 17 00:00:00 2001 From: Zack Rauen Date: Mon, 5 Apr 2021 01:43:30 -0400 Subject: [PATCH] Add initial pass at core updater - Fix duplicate file regex - Add core updater - Unify versions - Add sanity for plugin/theme folder --- renderer/package.json | 1 - renderer/src/data/config.js | 9 +--- renderer/src/data/strings.js | 1 - renderer/src/modules/componentpatcher.js | 11 ++-- renderer/src/modules/core.js | 67 +++++++++++++++++++++--- renderer/src/modules/datastore.js | 8 +++ renderer/src/modules/pluginmanager.js | 2 +- renderer/src/modules/thememanager.js | 2 +- renderer/src/ui/modals.js | 18 +++---- renderer/webpack.config.js | 8 ++- 10 files changed, 92 insertions(+), 35 deletions(-) diff --git a/renderer/package.json b/renderer/package.json index 9b498994..effac048 100644 --- a/renderer/package.json +++ b/renderer/package.json @@ -1,6 +1,5 @@ { "name": "betterdiscord-renderer", - "version": "1.0.0", "description": "Renderer portion of the BetterDiscord application.", "private": true, "main": "src/index.js", diff --git a/renderer/src/data/config.js b/renderer/src/data/config.js index 8670ff52..b3c5f2db 100644 --- a/renderer/src/data/config.js +++ b/renderer/src/data/config.js @@ -1,14 +1,7 @@ export default { - local: false, - localPath: "", - minified: true, - branch: "stable", - repo: "rauenzi", - minSupportedVersion: "0.3.0", - bdVersion: "1.0.0", + version: process.env.__VERSION__, // Get from main process - version: "0.6.0", path: "", appPath: "", userData: "" diff --git a/renderer/src/data/strings.js b/renderer/src/data/strings.js index f1f0ed4a..e2cfc944 100644 --- a/renderer/src/data/strings.js +++ b/renderer/src/data/strings.js @@ -287,7 +287,6 @@ export default { }, Startup: { notSupported: "Not Supported", - versionMismatch: "BetterDiscord Injector v{{injector}} is not supported by the latest remote (v{{remote}}).\n\nPlease download the latest version from [GitHub](https://github.com/rauenzi/BetterDiscordApp/releases/latest)", incompatibleApp: "BetterDiscord does not work with {{app}}. Please uninstall one of them.", updateNow: "Update Now", maybeLater: "Maybe Later", diff --git a/renderer/src/modules/componentpatcher.js b/renderer/src/modules/componentpatcher.js index 087596b9..b5ee386f 100644 --- a/renderer/src/modules/componentpatcher.js +++ b/renderer/src/modules/componentpatcher.js @@ -45,21 +45,18 @@ export default new class ComponentPatcher { children[children.length - 2].type = newOne; } - const injector = DiscordModules.React.createElement("div", {className: "colorMuted-HdFt4q size12-3cLvbJ"}, `Injector ${Config.version}`); - const versionHash = `(${Config.hash ? Config.hash.substring(0, 7) : Config.branch})`; - const additional = DiscordModules.React.createElement("div", {className: "colorMuted-HdFt4q size12-3cLvbJ"}, `BD ${Config.bdVersion} `, DiscordModules.React.createElement("span", {className: "versionHash-2gXjIB da-versionHash"}, versionHash)); - + const additional = DiscordModules.React.createElement("div", {className: "colorMuted-HdFt4q size12-3cLvbJ"}, `BetterDiscord ${Config.version}`); + 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; }; }); } - + /* patchGuildListItems() { if (this.guildListItemsPatch) return; @@ -158,7 +155,7 @@ export default new class ComponentPatcher { // Tropical's notes -/* +/* html [maximized | bd | stable | canary | ptb] .iconWrapper-2OrFZ1 [type] .sidebar-2K8pFh [guild-id] diff --git a/renderer/src/modules/core.js b/renderer/src/modules/core.js index 2d649b5e..98a9317d 100644 --- a/renderer/src/modules/core.js +++ b/renderer/src/modules/core.js @@ -1,3 +1,4 @@ +const path = require("path"); import LocaleManager from "./localemanager"; import Logger from "common/logger"; @@ -13,6 +14,7 @@ import DataStore from "./datastore"; import DiscordModules from "./discordmodules"; import ComponentPatcher from "./componentpatcher"; import Strings from "./strings"; +import IPC from "./ipc"; import LoadingIcon from "../loadingicon"; import Styles from "../styles/index.css"; @@ -39,7 +41,7 @@ export default new class Core { // console.error = toFile(window.oce); // console.exception = toFile(window.ocx); // })(); - + Config.appPath = process.env.DISCORD_APP_PATH; Config.userData = process.env.DISCORD_USER_DATA; Config.dataPath = process.env.BETTERDISCORD_DATA_PATH; @@ -55,7 +57,6 @@ export default new class Core { await LocaleManager.initialize(); Logger.log("Startup", "Performing incompatibility checks"); - if (Config.version < Config.minSupportedVersion) return Modals.alert(Strings.Startup.notSupported, Strings.Startup.versionMismatch.format({injector: Config.version, remote: Config.bdVersion})); if (window.ED) return Modals.alert(Strings.Startup.notSupported, Strings.Startup.incompatibleApp.format({app: "EnhancedDiscord"})); if (window.WebSocket && window.WebSocket.name && window.WebSocket.name.includes("Patched")) return Modals.alert(Strings.Startup.notSupported, Strings.Startup.incompatibleApp.format({app: "Powercord"})); @@ -97,10 +98,12 @@ export default new class Core { Modals.showAddonErrors({plugins: pluginErrors, themes: themeErrors}); const previousVersion = DataStore.getBDData("version"); - if (Config.bdVersion > previousVersion) { + if (Config.version > previousVersion) { Modals.showChangelogModal(Changelog); - DataStore.setBDData("version", Config.bdVersion); + DataStore.setBDData("version", Config.version); } + + this.checkForUpdate(); } waitForGuilds() { @@ -112,12 +115,64 @@ export default new class Core { const wrapper = GuildClasses.wrapper.split(" ")[0]; const guild = GuildClasses.listItem.split(" ")[0]; const blob = GuildClasses.blobContainer.split(" ")[0]; - if (document.querySelectorAll(`.${wrapper} .${guild} .${blob}`).length > 0) return resolve(Config.deferLoaded = true); - // else if (timesChecked >= 50) return resolve(Config.deferLoaded = true); + if (document.querySelectorAll(`.${wrapper} .${guild} .${blob}`).length > 0) return resolve(); + // else if (timesChecked >= 50) return resolve(); setTimeout(checkForGuilds, 100); }; checkForGuilds(); }); } + + async checkForUpdate() { + const resp = await fetch(`https://api.github.com/repos/rauenzi/BetterDiscordApp/releases/latest`,{ + method: "GET", + headers: { + "Accept": "application/json", + "Content-Type": "application/json", + "User-Agent": "BetterDiscord Updater" + } + }); + + const data = await resp.json(); + const remoteVersion = data.tag_name.startsWith("v") ? data.tag_name.slice(1) : data.tag_name; + const hasUpdate = remoteVersion > Config.version; + if (!hasUpdate) return; + + Modals.showConfirmationModal("Update", "There is an update, would you like to update now?", { + confirmText: "Update", + cancelText: "Skip", + onConfirm: () => this.update(data) + }); + } + + async update(releaseInfo) { + try { + const asar = releaseInfo.assets.find(a => a.name === "betterdiscord.asar"); + const request = require("request"); + const buff = await new Promise((resolve, reject) => + request(asar.url, {encoding: null, headers: {"User-Agent": "BD Updater", "Accept": "application/octet-stream"}}, (err, resp, body) => { + if (err || resp.statusCode != 200) return reject(err || `${resp.statusCode} ${resp.statusMessage}`); + return resolve(body); + })); + + const asarPath = path.join(DataStore.baseFolder, "betterdiscord.asar"); + console.log(asarPath); + const fs = require("original-fs"); + fs.writeFileSync(asarPath, buff); + + Modals.showConfirmationModal("Update Successful!", "BetterDiscord updated successfully. Discord needs to restart in order for it to take effect. Do you want to do this now?", { + confirmText: Strings.Modals.restartNow, + cancelText: Strings.Modals.restartLater, + danger: true, + onConfirm: () => IPC.relaunch() + }); + } + catch (err) { + console.error(err); + Modals.showConfirmationModal("Update Failed", "BetterDiscord failed to update. Please download the latest version of the installer from GitHub (https://github.com/BetterDiscord/Installer/releases/latest) and reinstall.", { + cancelText: "" + }); + } + } }; \ No newline at end of file diff --git a/renderer/src/modules/datastore.js b/renderer/src/modules/datastore.js index 92d99e24..b4fd1b38 100644 --- a/renderer/src/modules/datastore.js +++ b/renderer/src/modules/datastore.js @@ -29,6 +29,12 @@ export default new class DataStore { const bdFolderExists = fs.existsSync(Config.dataPath); if (!bdFolderExists) fs.mkdirSync(Config.dataPath); + const pluginFolderExists = fs.existsSync(this.pluginFolder); + if (!pluginFolderExists) fs.mkdirSync(this.pluginFolder); + + const themeFolderExists = fs.existsSync(this.themeFolder); + if (!themeFolderExists) fs.mkdirSync(this.themeFolder); + const newStorageExists = fs.existsSync(this.baseFolder); if (!newStorageExists) fs.mkdirSync(this.baseFolder); @@ -101,6 +107,8 @@ export default new class DataStore { return this._injectionPath = realLocation; } + get pluginFolder() {return this._pluginFolder || (this._pluginFolder = path.resolve(Config.dataPath, "plugins"));} + get themeFolder() {return this._themeFolder || (this._themeFolder = path.resolve(Config.dataPath, "themes"));} get customCSS() {return this._customCSS || (this._customCSS = path.resolve(this.dataFolder, "custom.css"));} get baseFolder() {return this._baseFolder || (this._baseFolder = path.resolve(Config.dataPath, "data"));} get dataFolder() {return this._dataFolder || (this._dataFolder = path.resolve(this.baseFolder, `${releaseChannel}`));} diff --git a/renderer/src/modules/pluginmanager.js b/renderer/src/modules/pluginmanager.js index 7e5a3c48..f0584649 100644 --- a/renderer/src/modules/pluginmanager.js +++ b/renderer/src/modules/pluginmanager.js @@ -22,7 +22,7 @@ export default new class PluginManager extends AddonManager { get name() {return "PluginManager";} get moduleExtension() {return ".js";} get extension() {return ".plugin.js";} - get duplicatePattern() {return /\.plugin\([0-9]+\)\.js/;} + get duplicatePattern() {return /\.plugin\s?\([0-9]+\)\.js/;} get addonFolder() {return path.resolve(Config.dataPath, "plugins");} get prefix() {return "plugin";} get language() {return "javascript";} diff --git a/renderer/src/modules/thememanager.js b/renderer/src/modules/thememanager.js index ccc2b617..f9a6de1e 100644 --- a/renderer/src/modules/thememanager.js +++ b/renderer/src/modules/thememanager.js @@ -14,7 +14,7 @@ export default new class ThemeManager extends AddonManager { get name() {return "ThemeManager";} get moduleExtension() {return ".css";} get extension() {return ".theme.css";} - get duplicatePattern() {return /\.theme\([0-9]+\)\.css/;} + get duplicatePattern() {return /\.theme\s?\([0-9]+\)\.css/;} get addonFolder() {return path.resolve(Config.dataPath, "themes");} get prefix() {return "theme";} get language() {return "css";} diff --git a/renderer/src/ui/modals.js b/renderer/src/ui/modals.js index ea531217..7e601b46 100644 --- a/renderer/src/ui/modals.js +++ b/renderer/src/ui/modals.js @@ -78,7 +78,7 @@ export default class Modals { const emptyFunction = () => {}; const {onConfirm = emptyFunction, onCancel = emptyFunction, confirmText = Strings.Modals.okay, cancelText = Strings.Modals.cancel, danger = false, key = undefined} = options; - + if (!Array.isArray(content)) content = [content]; content = content.map(c => typeof(c) === "string" ? React.createElement(Markdown, null, c) : c); @@ -180,8 +180,8 @@ export default class Modals { const Changelog = WebpackModules.getModule(m => m.defaultProps && m.defaultProps.selectable == false); const MarkdownParser = WebpackModules.getByProps("defaultRules", "parse"); if (!Changelog || !ModalStack || !ChangelogClasses || !TextElement || !FlexChild || !Titles || !MarkdownParser) return Logger.warn("Modals", "showChangelogModal missing modules"); - - const {image = "https://i.imgur.com/8sctUVV.png", description = "", changes = [], title = "BetterDiscord", subtitle = `v${Config.bdVersion}`, footer} = options; + + const {image = "https://i.imgur.com/8sctUVV.png", description = "", changes = [], title = "BetterDiscord", subtitle = `v${Config.version}`, footer} = options; const ce = React.createElement; const changelogItems = [options.video ? ce("video", {src: options.video, poster: options.poster, controls: true, className: ChangelogClasses.video}) : ce("img", {src: image})]; if (description) changelogItems.push(ce("p", null, MarkdownParser.parse(description))); @@ -199,7 +199,7 @@ export default class Modals { ce(TextElement, {size: TextElement.Sizes.SMALL, color: TextElement.Colors.STANDARD, className: ChangelogClasses.date}, subtitle) ); }; - + const renderFooter = () => { const Anchor = WebpackModules.getModule(m => m.displayName == "Anchor"); const AnchorClasses = WebpackModules.getByProps("anchorUnderlineOnHover") || {anchor: "anchor-3Z-8Bb", anchorUnderlineOnHover: "anchorUnderlineOnHover-2ESHQB"}; @@ -228,7 +228,7 @@ export default class Modals { renderFooter: renderFooter, }, props), changelogItems); }); - + const closeModal = ModalActions.closeModal; ModalActions.closeModal = function(k) { Reflect.apply(closeModal, this, arguments); @@ -248,7 +248,7 @@ export default class Modals { this.elementRef = React.createRef(); this.element = panel; } - + componentDidMount() { if (this.element instanceof Node) this.elementRef.current.appendChild(this.element); // if (typeof(this.element) === "string") this.elementRef.current.appendChild(this.element); @@ -268,15 +268,15 @@ export default class Modals { const mc = this.ModalComponents; const modal = props => { - return React.createElement(mc.ModalRoot, Object.assign({size: mc.ModalSize.MEDIUM, className: "bd-addon-modal"}, props), + return React.createElement(mc.ModalRoot, Object.assign({size: mc.ModalSize.MEDIUM, className: "bd-addon-modal"}, props), React.createElement(mc.ModalHeader, {separator: false, className: "bd-addon-modal-header"}, React.createElement(this.FormTitle, {tag: "h4"}, `${name} Settings`), React.createElement(this.FlexElements.Child, {grow: 0}, React.createElement(mc.ModalCloseButton, {className: "bd-modal-close", onClick: props.onClose}) ) ), - React.createElement(mc.ModalContent, {className: "bd-addon-modal-settings"}, - React.createElement(ErrorBoundary, {}, child) + React.createElement(mc.ModalContent, {className: "bd-addon-modal-settings"}, + React.createElement(ErrorBoundary, {}, child) ), React.createElement(mc.ModalFooter, {className: "bd-addon-modal-footer"}, React.createElement(this.Buttons.default, {onClick: props.onClose, className: "bd-button"}, Strings.Modals.done) diff --git a/renderer/webpack.config.js b/renderer/webpack.config.js index 6b84a5ba..e3a1103e 100644 --- a/renderer/webpack.config.js +++ b/renderer/webpack.config.js @@ -1,6 +1,8 @@ const path = require("path"); +const webpack = require("webpack"); const CircularDependencyPlugin = require("circular-dependency-plugin"); const TerserPlugin = require("terser-webpack-plugin"); +const basePkg = require("../package.json"); module.exports = { mode: "development", @@ -14,6 +16,7 @@ module.exports = { externals: { electron: `require("electron")`, fs: `require("fs")`, + "original-fs": `require("original-fs")`, path: `require("path")`, request: `require("request")`, events: `require("events")`, @@ -40,7 +43,7 @@ module.exports = { presets: [["@babel/env", { targets: { node: "12.14.1", - chrome: "80" + chrome: "83" } }], "@babel/react"] } @@ -55,6 +58,9 @@ module.exports = { new CircularDependencyPlugin({ exclude: /node_modules/, cwd: process.cwd(), + }), + new webpack.DefinePlugin({ + "process.env.__VERSION__": JSON.stringify(basePkg.version) }) ], optimization: {