Add initial pass at core updater

- Fix duplicate file regex
- Add core updater
- Unify versions
- Add sanity for plugin/theme folder
This commit is contained in:
Zack Rauen 2021-04-05 01:43:30 -04:00
parent 66d8d7a069
commit 5b1900fe3d
10 changed files with 92 additions and 35 deletions

View File

@ -1,6 +1,5 @@
{ {
"name": "betterdiscord-renderer", "name": "betterdiscord-renderer",
"version": "1.0.0",
"description": "Renderer portion of the BetterDiscord application.", "description": "Renderer portion of the BetterDiscord application.",
"private": true, "private": true,
"main": "src/index.js", "main": "src/index.js",

View File

@ -1,14 +1,7 @@
export default { export default {
local: false, version: process.env.__VERSION__,
localPath: "",
minified: true,
branch: "stable",
repo: "rauenzi",
minSupportedVersion: "0.3.0",
bdVersion: "1.0.0",
// Get from main process // Get from main process
version: "0.6.0",
path: "", path: "",
appPath: "", appPath: "",
userData: "" userData: ""

View File

@ -287,7 +287,6 @@ export default {
}, },
Startup: { Startup: {
notSupported: "Not Supported", 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.", incompatibleApp: "BetterDiscord does not work with {{app}}. Please uninstall one of them.",
updateNow: "Update Now", updateNow: "Update Now",
maybeLater: "Maybe Later", maybeLater: "Maybe Later",

View File

@ -45,21 +45,18 @@ export default new class ComponentPatcher {
children[children.length - 2].type = newOne; children[children.length - 2].type = newOne;
} }
const injector = DiscordModules.React.createElement("div", {className: "colorMuted-HdFt4q size12-3cLvbJ"}, `Injector ${Config.version}`); const additional = DiscordModules.React.createElement("div", {className: "colorMuted-HdFt4q size12-3cLvbJ"}, `BetterDiscord ${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 originalVersions = children[children.length - 1].type; const originalVersions = children[children.length - 1].type;
children[children.length - 1].type = function() { children[children.length - 1].type = function() {
const returnVal = originalVersions(...arguments); const returnVal = originalVersions(...arguments);
returnVal.props.children.splice(returnVal.props.children.length - 1, 0, injector);
returnVal.props.children.splice(1, 0, additional); returnVal.props.children.splice(1, 0, additional);
return returnVal; return returnVal;
}; };
}); });
} }
/* /*
patchGuildListItems() { patchGuildListItems() {
if (this.guildListItemsPatch) return; if (this.guildListItemsPatch) return;
@ -158,7 +155,7 @@ export default new class ComponentPatcher {
// Tropical's notes // Tropical's notes
/* /*
html [maximized | bd | stable | canary | ptb] html [maximized | bd | stable | canary | ptb]
.iconWrapper-2OrFZ1 [type] .iconWrapper-2OrFZ1 [type]
.sidebar-2K8pFh [guild-id] .sidebar-2K8pFh [guild-id]

View File

@ -1,3 +1,4 @@
const path = require("path");
import LocaleManager from "./localemanager"; import LocaleManager from "./localemanager";
import Logger from "common/logger"; import Logger from "common/logger";
@ -13,6 +14,7 @@ import DataStore from "./datastore";
import DiscordModules from "./discordmodules"; import DiscordModules from "./discordmodules";
import ComponentPatcher from "./componentpatcher"; import ComponentPatcher from "./componentpatcher";
import Strings from "./strings"; import Strings from "./strings";
import IPC from "./ipc";
import LoadingIcon from "../loadingicon"; import LoadingIcon from "../loadingicon";
import Styles from "../styles/index.css"; import Styles from "../styles/index.css";
@ -39,7 +41,7 @@ export default new class Core {
// console.error = toFile(window.oce); // console.error = toFile(window.oce);
// console.exception = toFile(window.ocx); // console.exception = toFile(window.ocx);
// })(); // })();
Config.appPath = process.env.DISCORD_APP_PATH; Config.appPath = process.env.DISCORD_APP_PATH;
Config.userData = process.env.DISCORD_USER_DATA; Config.userData = process.env.DISCORD_USER_DATA;
Config.dataPath = process.env.BETTERDISCORD_DATA_PATH; Config.dataPath = process.env.BETTERDISCORD_DATA_PATH;
@ -55,7 +57,6 @@ export default new class Core {
await LocaleManager.initialize(); await LocaleManager.initialize();
Logger.log("Startup", "Performing incompatibility checks"); 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.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"})); 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}); Modals.showAddonErrors({plugins: pluginErrors, themes: themeErrors});
const previousVersion = DataStore.getBDData("version"); const previousVersion = DataStore.getBDData("version");
if (Config.bdVersion > previousVersion) { if (Config.version > previousVersion) {
Modals.showChangelogModal(Changelog); Modals.showChangelogModal(Changelog);
DataStore.setBDData("version", Config.bdVersion); DataStore.setBDData("version", Config.version);
} }
this.checkForUpdate();
} }
waitForGuilds() { waitForGuilds() {
@ -112,12 +115,64 @@ export default new class Core {
const wrapper = GuildClasses.wrapper.split(" ")[0]; const wrapper = GuildClasses.wrapper.split(" ")[0];
const guild = GuildClasses.listItem.split(" ")[0]; const guild = GuildClasses.listItem.split(" ")[0];
const blob = GuildClasses.blobContainer.split(" ")[0]; const blob = GuildClasses.blobContainer.split(" ")[0];
if (document.querySelectorAll(`.${wrapper} .${guild} .${blob}`).length > 0) return resolve(Config.deferLoaded = true); if (document.querySelectorAll(`.${wrapper} .${guild} .${blob}`).length > 0) return resolve();
// else if (timesChecked >= 50) return resolve(Config.deferLoaded = true); // else if (timesChecked >= 50) return resolve();
setTimeout(checkForGuilds, 100); setTimeout(checkForGuilds, 100);
}; };
checkForGuilds(); 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: ""
});
}
}
}; };

View File

@ -29,6 +29,12 @@ export default new class DataStore {
const bdFolderExists = fs.existsSync(Config.dataPath); const bdFolderExists = fs.existsSync(Config.dataPath);
if (!bdFolderExists) fs.mkdirSync(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); const newStorageExists = fs.existsSync(this.baseFolder);
if (!newStorageExists) fs.mkdirSync(this.baseFolder); if (!newStorageExists) fs.mkdirSync(this.baseFolder);
@ -101,6 +107,8 @@ export default new class DataStore {
return this._injectionPath = realLocation; 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 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 baseFolder() {return this._baseFolder || (this._baseFolder = path.resolve(Config.dataPath, "data"));}
get dataFolder() {return this._dataFolder || (this._dataFolder = path.resolve(this.baseFolder, `${releaseChannel}`));} get dataFolder() {return this._dataFolder || (this._dataFolder = path.resolve(this.baseFolder, `${releaseChannel}`));}

View File

@ -22,7 +22,7 @@ export default new class PluginManager extends AddonManager {
get name() {return "PluginManager";} get name() {return "PluginManager";}
get moduleExtension() {return ".js";} get moduleExtension() {return ".js";}
get extension() {return ".plugin.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 addonFolder() {return path.resolve(Config.dataPath, "plugins");}
get prefix() {return "plugin";} get prefix() {return "plugin";}
get language() {return "javascript";} get language() {return "javascript";}

View File

@ -14,7 +14,7 @@ export default new class ThemeManager extends AddonManager {
get name() {return "ThemeManager";} get name() {return "ThemeManager";}
get moduleExtension() {return ".css";} get moduleExtension() {return ".css";}
get extension() {return ".theme.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 addonFolder() {return path.resolve(Config.dataPath, "themes");}
get prefix() {return "theme";} get prefix() {return "theme";}
get language() {return "css";} get language() {return "css";}

View File

@ -78,7 +78,7 @@ export default class Modals {
const emptyFunction = () => {}; const emptyFunction = () => {};
const {onConfirm = emptyFunction, onCancel = emptyFunction, confirmText = Strings.Modals.okay, cancelText = Strings.Modals.cancel, danger = false, key = undefined} = options; const {onConfirm = emptyFunction, onCancel = emptyFunction, confirmText = Strings.Modals.okay, cancelText = Strings.Modals.cancel, danger = false, key = undefined} = options;
if (!Array.isArray(content)) content = [content]; if (!Array.isArray(content)) content = [content];
content = content.map(c => typeof(c) === "string" ? React.createElement(Markdown, null, c) : c); 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 Changelog = WebpackModules.getModule(m => m.defaultProps && m.defaultProps.selectable == false);
const MarkdownParser = WebpackModules.getByProps("defaultRules", "parse"); const MarkdownParser = WebpackModules.getByProps("defaultRules", "parse");
if (!Changelog || !ModalStack || !ChangelogClasses || !TextElement || !FlexChild || !Titles || !MarkdownParser) return Logger.warn("Modals", "showChangelogModal missing modules"); 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 ce = React.createElement;
const changelogItems = [options.video ? ce("video", {src: options.video, poster: options.poster, controls: true, className: ChangelogClasses.video}) : ce("img", {src: image})]; 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))); 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) ce(TextElement, {size: TextElement.Sizes.SMALL, color: TextElement.Colors.STANDARD, className: ChangelogClasses.date}, subtitle)
); );
}; };
const renderFooter = () => { const renderFooter = () => {
const Anchor = WebpackModules.getModule(m => m.displayName == "Anchor"); const Anchor = WebpackModules.getModule(m => m.displayName == "Anchor");
const AnchorClasses = WebpackModules.getByProps("anchorUnderlineOnHover") || {anchor: "anchor-3Z-8Bb", anchorUnderlineOnHover: "anchorUnderlineOnHover-2ESHQB"}; const AnchorClasses = WebpackModules.getByProps("anchorUnderlineOnHover") || {anchor: "anchor-3Z-8Bb", anchorUnderlineOnHover: "anchorUnderlineOnHover-2ESHQB"};
@ -228,7 +228,7 @@ export default class Modals {
renderFooter: renderFooter, renderFooter: renderFooter,
}, props), changelogItems); }, props), changelogItems);
}); });
const closeModal = ModalActions.closeModal; const closeModal = ModalActions.closeModal;
ModalActions.closeModal = function(k) { ModalActions.closeModal = function(k) {
Reflect.apply(closeModal, this, arguments); Reflect.apply(closeModal, this, arguments);
@ -248,7 +248,7 @@ export default class Modals {
this.elementRef = React.createRef(); this.elementRef = React.createRef();
this.element = panel; this.element = panel;
} }
componentDidMount() { componentDidMount() {
if (this.element instanceof Node) this.elementRef.current.appendChild(this.element); if (this.element instanceof Node) this.elementRef.current.appendChild(this.element);
// if (typeof(this.element) === "string") 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 mc = this.ModalComponents;
const modal = props => { 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(mc.ModalHeader, {separator: false, className: "bd-addon-modal-header"},
React.createElement(this.FormTitle, {tag: "h4"}, `${name} Settings`), React.createElement(this.FormTitle, {tag: "h4"}, `${name} Settings`),
React.createElement(this.FlexElements.Child, {grow: 0}, React.createElement(this.FlexElements.Child, {grow: 0},
React.createElement(mc.ModalCloseButton, {className: "bd-modal-close", onClick: props.onClose}) React.createElement(mc.ModalCloseButton, {className: "bd-modal-close", onClick: props.onClose})
) )
), ),
React.createElement(mc.ModalContent, {className: "bd-addon-modal-settings"}, React.createElement(mc.ModalContent, {className: "bd-addon-modal-settings"},
React.createElement(ErrorBoundary, {}, child) React.createElement(ErrorBoundary, {}, child)
), ),
React.createElement(mc.ModalFooter, {className: "bd-addon-modal-footer"}, React.createElement(mc.ModalFooter, {className: "bd-addon-modal-footer"},
React.createElement(this.Buttons.default, {onClick: props.onClose, className: "bd-button"}, Strings.Modals.done) React.createElement(this.Buttons.default, {onClick: props.onClose, className: "bd-button"}, Strings.Modals.done)

View File

@ -1,6 +1,8 @@
const path = require("path"); const path = require("path");
const webpack = require("webpack");
const CircularDependencyPlugin = require("circular-dependency-plugin"); const CircularDependencyPlugin = require("circular-dependency-plugin");
const TerserPlugin = require("terser-webpack-plugin"); const TerserPlugin = require("terser-webpack-plugin");
const basePkg = require("../package.json");
module.exports = { module.exports = {
mode: "development", mode: "development",
@ -14,6 +16,7 @@ module.exports = {
externals: { externals: {
electron: `require("electron")`, electron: `require("electron")`,
fs: `require("fs")`, fs: `require("fs")`,
"original-fs": `require("original-fs")`,
path: `require("path")`, path: `require("path")`,
request: `require("request")`, request: `require("request")`,
events: `require("events")`, events: `require("events")`,
@ -40,7 +43,7 @@ module.exports = {
presets: [["@babel/env", { presets: [["@babel/env", {
targets: { targets: {
node: "12.14.1", node: "12.14.1",
chrome: "80" chrome: "83"
} }
}], "@babel/react"] }], "@babel/react"]
} }
@ -55,6 +58,9 @@ module.exports = {
new CircularDependencyPlugin({ new CircularDependencyPlugin({
exclude: /node_modules/, exclude: /node_modules/,
cwd: process.cwd(), cwd: process.cwd(),
}),
new webpack.DefinePlugin({
"process.env.__VERSION__": JSON.stringify(basePkg.version)
}) })
], ],
optimization: { optimization: {