add injector-updater
This commit is contained in:
parent
8110fffdd5
commit
abcbfda554
45
js/main.js
45
js/main.js
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -2,24 +2,23 @@
|
|||
export const minimumDiscordVersion = "0.0.306";
|
||||
export const currentDiscordVersion = (window.DiscordNative && window.DiscordNative.remoteApp && window.DiscordNative.remoteApp.getVersion && window.DiscordNative.remoteApp.getVersion()) || "0.0.306";
|
||||
export const minSupportedVersion = "0.3.0";
|
||||
export const bbdVersion = "0.3.2";
|
||||
export const bbdVersion = "0.3.3";
|
||||
export const bbdChangelog = {
|
||||
description: "More big things.",
|
||||
description: "Big things are coming.",
|
||||
changes: [
|
||||
{
|
||||
title: "What's New?",
|
||||
items: [
|
||||
"**jQuery** is no longer used internally in BBD. This should speed things up and hopefully close some memory leaks.",
|
||||
"**VoiceMode** was redone to act more like it used to."
|
||||
"**In-App Updater** for the injection module now exists to try and decrease the number of issues with updates to the injector.",
|
||||
"**Window Transparency** changes were made to more compatible with external window managers and addons like Glasscord.",
|
||||
"Initialization sequence has once again been changed slightly to hopefully improve loading times."
|
||||
]
|
||||
},
|
||||
{
|
||||
title: "Improvements",
|
||||
type: "improved",
|
||||
title: "Bug Fixes",
|
||||
type: "fixed",
|
||||
items: [
|
||||
"**Copy Selector** option was revamped to be more consistent and functional.",
|
||||
"**Emote Menu** has gone through some serious changes to be more efficient and less buggy.",
|
||||
"Some speed improvements when entering the plugins and themes tabs."
|
||||
"Some fixes related to showing modals in the `BdApi`."
|
||||
]
|
||||
}
|
||||
]
|
||||
|
|
|
@ -54,6 +54,7 @@ deprecateGlobal("ClassNormalizer", ClassNormalizer);
|
|||
window.BdApi = BdApi;
|
||||
|
||||
import Core from "./modules/core";
|
||||
deprecateGlobal("mainCore", Core);
|
||||
export default class CoreWrapper {
|
||||
constructor(bdConfig) {
|
||||
Core.setConfig(bdConfig);
|
||||
|
|
|
@ -93,7 +93,7 @@ BdApi.alert = function (title, content) {
|
|||
/**
|
||||
* Shows a generic but very customizable confirmation modal with optional confirm and cancel callbacks.
|
||||
* @param {string} title - title of the modal
|
||||
* @param {(string|ReactElement|Array<string|ReactElement>)} children - a single or mixed array of react elements and strings. Everything is wrapped in Discord's `TextElement` component so strings will show and render properly.
|
||||
* @param {(string|ReactElement|Array<string|ReactElement>)} children - a single or mixed array of react elements and strings. Every string is wrapped in Discord's `Markdown` component so strings will show and render properly.
|
||||
* @param {object} [options] - options to modify the modal
|
||||
* @param {boolean} [options.danger=false] - whether the main button should be red or not
|
||||
* @param {string} [options.confirmText=Okay] - text for the confirmation/submit button
|
||||
|
@ -102,28 +102,7 @@ BdApi.alert = function (title, content) {
|
|||
* @param {callable} [options.onCancel=NOOP] - callback to occur when clicking the cancel button
|
||||
*/
|
||||
BdApi.showConfirmationModal = function (title, content, options = {}) {
|
||||
const ModalStack = BdApi.findModuleByProps("push", "update", "pop", "popWithKey");
|
||||
const TextElement = BdApi.findModuleByProps("Sizes", "Weights");
|
||||
const ConfirmationModal = BdApi.findModule(m => m.defaultProps && m.key && m.key() == "confirm-modal");
|
||||
if (!ModalStack || !ConfirmationModal || !TextElement) return mainCore.alert(title, content);
|
||||
|
||||
const {onConfirm, onCancel, confirmText, cancelText, danger = false} = options;
|
||||
if (typeof(content) == "string") content = TextElement({color: TextElement.Colors.PRIMARY, children: [content]});
|
||||
else if (Array.isArray(content)) content = TextElement({color: TextElement.Colors.PRIMARY, children: content});
|
||||
content = [content];
|
||||
|
||||
const emptyFunction = () => {};
|
||||
ModalStack.push(function(props) {
|
||||
return BdApi.React.createElement(ConfirmationModal, Object.assign({
|
||||
header: title,
|
||||
children: content,
|
||||
red: danger,
|
||||
confirmText: confirmText ? confirmText : "Okay",
|
||||
cancelText: cancelText ? cancelText : "Cancel",
|
||||
onConfirm: onConfirm ? onConfirm : emptyFunction,
|
||||
onCancel: onCancel ? onCancel : emptyFunction
|
||||
}, props));
|
||||
});
|
||||
Utils.showConfirmationModal(title, content, options);
|
||||
};
|
||||
|
||||
//Show toast alert
|
||||
|
|
|
@ -14,7 +14,10 @@ import DOM from "./domtools";
|
|||
import BDLogo from "../ui/bdLogo";
|
||||
import TooltipWrap from "../ui/tooltipWrap";
|
||||
|
||||
function Core() {}
|
||||
function Core() {
|
||||
// Object.assign(bdConfig, __non_webpack_require__(DataStore.configFile));
|
||||
// this.init();
|
||||
}
|
||||
|
||||
Core.prototype.setConfig = function(config) {
|
||||
Object.assign(bdConfig, config);
|
||||
|
@ -43,10 +46,23 @@ Core.prototype.init = async function() {
|
|||
|
||||
const latestLocalVersion = bdConfig.updater ? bdConfig.updater.LatestVersion : bdConfig.latestVersion;
|
||||
if (latestLocalVersion > bdConfig.version) {
|
||||
Utils.alert("Update Available", `
|
||||
An update for BandagedBD is available (${latestLocalVersion})! Please Reinstall!<br /><br />
|
||||
<a href='https://github.com/rauenzi/BetterDiscordApp/releases/latest' target='_blank'>Download Installer</a>
|
||||
`);
|
||||
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.<br /><br /><a href='https://github.com/rauenzi/BetterDiscordApp/releases/latest' target='_blank'>Download Installer</a>`);};
|
||||
try {
|
||||
const didUpdate = await this.updateInjector();
|
||||
if (!didUpdate) return onUpdateFailed();
|
||||
const app = require("electron").remote.app;
|
||||
app.relaunch();
|
||||
app.exit();
|
||||
}
|
||||
catch (err) {
|
||||
onUpdateFailed();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Utils.log("Startup", "Initializing Settings");
|
||||
|
@ -281,4 +297,90 @@ Core.prototype.patchGuildSeparator = function() {
|
|||
}});
|
||||
};
|
||||
|
||||
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();
|
|
@ -8,6 +8,7 @@ const releaseChannel = DiscordNative.globals.releaseChannel;
|
|||
|
||||
export default new class DataStore {
|
||||
constructor() {
|
||||
this.config =
|
||||
this.data = {settings: {stable: {}, canary: {}, ptb: {}}};
|
||||
this.pluginData = {};
|
||||
}
|
||||
|
@ -30,6 +31,19 @@ export default new class DataStore {
|
|||
}
|
||||
}
|
||||
|
||||
get injectionPath() {
|
||||
if (this._injectionPath) return this._injectionPath;
|
||||
const electron = require("electron").remote.app;
|
||||
const base = electron.getAppPath();
|
||||
const roamingBase = electron.getPath("userData");
|
||||
const roamingLocation = path.resolve(roamingBase, electron.getVersion(), "modules", "discord_desktop_core", "injector");
|
||||
const location = path.resolve(base, "..", "app");
|
||||
const realLocation = fs.existsSync(location) ? location : fs.existsSync(roamingLocation) ? roamingLocation : null;
|
||||
if (!realLocation) return this._injectionPath = null;
|
||||
return this._injectionPath = realLocation;
|
||||
}
|
||||
|
||||
get configFile() {return this._configFile || (this._configFile = path.resolve(this.injectionPath, "betterdiscord", "config.json"));}
|
||||
get BDFile() {return this._BDFile || (this._BDFile = path.resolve(bdConfig.dataPath, "bdstorage.json"));}
|
||||
get settingsFile() {return this._settingsFile || (this._settingsFile = path.resolve(bdConfig.dataPath, "bdsettings.json"));}
|
||||
getPluginFile(pluginName) {return path.resolve(ContentManager.pluginsFolder, pluginName + ".config.json");}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import {bbdVersion} from "../0globals";
|
||||
import {bbdVersion, settingsCookie} from "../0globals";
|
||||
import WebpackModules from "./webpackModules";
|
||||
import BDV2 from "./v2";
|
||||
import DOM from "./domtools";
|
||||
|
@ -10,36 +10,26 @@ export default class Utils {
|
|||
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;
|
||||
return this._windowConfigFile = null;
|
||||
}
|
||||
|
||||
static getAllWindowPreferences() {
|
||||
if (!this.WindowConfigFile) return {}; // Tempfix until new injection on other platforms
|
||||
return __non_webpack_require__(this.WindowConfigFile);
|
||||
return {
|
||||
transparent: settingsCookie["fork-wp-1"] || settingsCookie.transparency,
|
||||
frame: settingsCookie.frame
|
||||
};
|
||||
}
|
||||
|
||||
static getWindowPreference(key) {
|
||||
if (!this.WindowConfigFile) return undefined; // Tempfix until new injection on other platforms
|
||||
return this.getAllWindowPreferences()[key];
|
||||
if (key === "transparent") return settingsCookie["fork-wp-1"] || settingsCookie.transparency;
|
||||
if (key === "frame") return settingsCookie.frame;
|
||||
return null;
|
||||
}
|
||||
|
||||
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));
|
||||
if (key === "transparent") return settingsCookie["fork-wp-1"] = settingsCookie.transparency = value;
|
||||
if (key === "frame") return settingsCookie.frame = value;
|
||||
return null;
|
||||
}
|
||||
|
||||
static stripBOM(content) {
|
||||
|
@ -382,6 +372,41 @@ export default class Utils {
|
|||
}, props));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows a generic but very customizable confirmation modal with optional confirm and cancel callbacks.
|
||||
* @param {string} title - title of the modal
|
||||
* @param {(string|ReactElement|Array<string|ReactElement>)} children - a single or mixed array of react elements and strings. Every string is wrapped in Discord's `Markdown` component so strings will show and render properly.
|
||||
* @param {object} [options] - options to modify the modal
|
||||
* @param {boolean} [options.danger=false] - whether the main button should be red or not
|
||||
* @param {string} [options.confirmText=Okay] - text for the confirmation/submit button
|
||||
* @param {string} [options.cancelText=Cancel] - text for the cancel button
|
||||
* @param {callable} [options.onConfirm=NOOP] - callback to occur when clicking the submit button
|
||||
* @param {callable} [options.onCancel=NOOP] - callback to occur when clicking the cancel button
|
||||
*/
|
||||
static showConfirmationModal(title, content, options = {}) {
|
||||
const ModalStack = WebpackModules.findByProps("push", "update", "pop", "popWithKey");
|
||||
const Markdown = WebpackModules.findByDisplayName("Markdown");
|
||||
const ConfirmationModal = WebpackModules.find(m => m.defaultProps && m.key && m.key() == "confirm-modal");
|
||||
if (!ModalStack || !ConfirmationModal || !Markdown) return Utils.alert(title, content);
|
||||
|
||||
const emptyFunction = () => {};
|
||||
const {onConfirm = emptyFunction, onCancel = emptyFunction, confirmText = "Okay", cancelText = "Cancel", danger = false} = options;
|
||||
|
||||
if (!Array.isArray(content)) content = [content];
|
||||
content = content.map(c => typeof(c) === "string" ? BDV2.React.createElement(Markdown, null, c) : c);
|
||||
ModalStack.push(function(props) {
|
||||
return BDV2.React.createElement(ConfirmationModal, Object.assign({
|
||||
header: title,
|
||||
children: content,
|
||||
red: danger,
|
||||
confirmText: confirmText,
|
||||
cancelText: cancelText,
|
||||
onConfirm: onConfirm,
|
||||
onCancel: onCancel
|
||||
}, props));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Utils.showToast = Utils.suppressErrors(Utils.showToast, "Could not show toast.");
|
|
@ -1,6 +1,6 @@
|
|||
const path = require("path");
|
||||
const CircularDependencyPlugin = require("circular-dependency-plugin");
|
||||
const TerserPlugin = require('terser-webpack-plugin');
|
||||
const TerserPlugin = require("terser-webpack-plugin");
|
||||
|
||||
module.exports = {
|
||||
mode: "development",
|
||||
|
@ -19,7 +19,10 @@ module.exports = {
|
|||
fs: `require("fs")`,
|
||||
path: `require("path")`,
|
||||
request: `require("request")`,
|
||||
events: `require("events")`
|
||||
events: `require("events")`,
|
||||
rimraf: `require("rimraf")`,
|
||||
yauzl: `require("yauzl")`,
|
||||
mkdirp: `require("mkdirp")`
|
||||
},
|
||||
resolve: {
|
||||
extensions: [".js", ".jsx"],
|
||||
|
|
Loading…
Reference in New Issue