2019-06-27 06:21:51 +02:00
|
|
|
import LocaleManager from "./localemanager";
|
|
|
|
|
2019-06-19 21:24:05 +02:00
|
|
|
import Logger from "./logger";
|
2020-07-16 07:42:56 +02:00
|
|
|
import {Config, Changelog} from "data";
|
2019-06-03 22:25:08 +02:00
|
|
|
// import EmoteModule from "./emotes";
|
2019-05-31 07:53:11 +02:00
|
|
|
// import QuickEmoteMenu from "../builtins/emotemenu";
|
2019-06-19 05:09:49 +02:00
|
|
|
import DOMManager from "./dommanager";
|
2019-06-08 08:35:43 +02:00
|
|
|
import PluginManager from "./pluginmanager";
|
|
|
|
import ThemeManager from "./thememanager";
|
2019-06-06 21:57:25 +02:00
|
|
|
import Settings from "./settingsmanager";
|
2019-05-30 23:18:52 +02:00
|
|
|
import * as Builtins from "builtins";
|
2019-06-23 06:11:50 +02:00
|
|
|
import Modals from "../ui/modals";
|
2019-06-22 06:37:19 +02:00
|
|
|
import ReactComponents from "./reactcomponents";
|
2019-06-23 06:11:50 +02:00
|
|
|
import DataStore from "./datastore";
|
|
|
|
import DiscordModules from "./discordmodules";
|
2019-06-27 06:21:51 +02:00
|
|
|
import ComponentPatcher from "./componentpatcher";
|
2020-07-16 07:42:56 +02:00
|
|
|
import Strings from "./strings";
|
2020-07-19 01:01:49 +02:00
|
|
|
import LoadingIcon from "../loadingicon";
|
|
|
|
import Utilities from "./utilities";
|
2019-06-27 06:21:51 +02:00
|
|
|
|
2020-07-19 01:01:49 +02:00
|
|
|
const {ipcRenderer} = require("electron");
|
2019-06-23 06:11:50 +02:00
|
|
|
const GuildClasses = DiscordModules.GuildClasses;
|
2019-05-28 23:27:25 +02:00
|
|
|
|
2020-07-19 01:01:49 +02:00
|
|
|
export default new class Core {
|
|
|
|
constructor() {
|
|
|
|
ipcRenderer.invoke("bd-config", "get").then(injectorConfig => {
|
|
|
|
if (this.hasStarted) return;
|
|
|
|
Object.assign(Config, injectorConfig);
|
|
|
|
this.init();
|
|
|
|
});
|
2019-05-28 20:19:48 +02:00
|
|
|
}
|
2020-02-28 01:00:12 +01:00
|
|
|
|
2020-07-19 01:01:49 +02:00
|
|
|
get dependencies() {
|
|
|
|
return [
|
|
|
|
{
|
|
|
|
name: "jquery",
|
|
|
|
type: "script",
|
|
|
|
url: "//ajax.googleapis.com/ajax/libs/jquery/2.0.0/jquery.min.js",
|
|
|
|
backup: "//cdn.jsdelivr.net/gh/jquery/jquery@2.0.0/jquery.min.js",
|
|
|
|
local: null
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "bd-stylesheet",
|
|
|
|
type: "style",
|
2020-07-19 06:34:23 +02:00
|
|
|
url: "//cdn.staticaly.com/gh/{{repo}}/BetterDiscordApp/{{hash}}/dist/style.css",
|
|
|
|
backup: "//rauenzi.github.io/BetterDiscordApp/dist/style.css",
|
|
|
|
local: "{{localServer}}/BetterDiscordApp/dist/style.css"
|
2020-07-19 01:01:49 +02:00
|
|
|
}
|
|
|
|
];
|
2020-02-28 01:00:12 +01:00
|
|
|
}
|
|
|
|
|
2020-07-19 01:01:49 +02:00
|
|
|
setConfig(config) {
|
|
|
|
if (this.hasStarted) return;
|
|
|
|
Object.assign(Config, config);
|
2020-02-28 01:00:12 +01:00
|
|
|
}
|
2019-06-23 06:11:50 +02:00
|
|
|
|
2020-07-19 01:01:49 +02:00
|
|
|
async init() {
|
|
|
|
if (this.hasStarted) return;
|
|
|
|
this.hasStarted = true;
|
|
|
|
|
|
|
|
// Load dependencies asynchronously if they don't exist
|
|
|
|
let dependencyPromise = new Promise(r => r());
|
|
|
|
if (!window.$ || !window.jQuery) dependencyPromise = this.loadDependencies();
|
|
|
|
|
|
|
|
DataStore.initialize();
|
|
|
|
await LocaleManager.initialize();
|
|
|
|
|
|
|
|
if (Config.version < Config.minSupportedVersion) return Modals.alert(Strings.Startup.notSupported, Strings.Startup.versionMismatch.format({injector: Config.version, remote: Config.bbdVersion}));
|
|
|
|
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"}));
|
|
|
|
|
|
|
|
const latestLocalVersion = Config.updater ? Config.updater.LatestVersion : Config.latestVersion;
|
|
|
|
if (latestLocalVersion > Config.version) {
|
|
|
|
Modals.showConfirmationModal(Strings.Startup.updateAvailable, Strings.Startup.updateInfo.format({version: latestLocalVersion}), {
|
|
|
|
confirmText: Strings.Startup.updateNow,
|
|
|
|
cancelText: Strings.Startup.maybeLater,
|
|
|
|
onConfirm: async () => {
|
|
|
|
const onUpdateFailed = () => { Modals.alert(Strings.Startup.updateFailed, Strings.Startup.manualUpdate); };
|
|
|
|
try {
|
|
|
|
const didUpdate = await this.updateInjector();
|
|
|
|
if (!didUpdate) return onUpdateFailed();
|
|
|
|
const app = require("electron").remote.app;
|
|
|
|
app.relaunch();
|
|
|
|
app.exit();
|
|
|
|
}
|
|
|
|
catch (err) {
|
|
|
|
onUpdateFailed();
|
|
|
|
}
|
2020-07-16 07:42:56 +02:00
|
|
|
}
|
2020-07-19 01:01:49 +02:00
|
|
|
});
|
|
|
|
}
|
2020-07-16 07:42:56 +02:00
|
|
|
|
2019-05-28 20:19:48 +02:00
|
|
|
|
2020-07-19 01:01:49 +02:00
|
|
|
Logger.log("Startup", "Initializing Settings");
|
|
|
|
Settings.initialize();
|
2019-05-28 20:19:48 +02:00
|
|
|
|
2020-07-19 01:01:49 +02:00
|
|
|
// DOMManager.initialize();
|
|
|
|
await this.waitForGuilds();
|
|
|
|
ReactComponents.initialize();
|
|
|
|
ComponentPatcher.initialize();
|
|
|
|
for (const module in Builtins) Builtins[module].initialize();
|
2019-05-28 20:19:48 +02:00
|
|
|
|
2020-07-19 01:01:49 +02:00
|
|
|
await dependencyPromise;
|
|
|
|
Logger.log("Startup", "Loading Plugins");
|
|
|
|
const pluginErrors = PluginManager.initialize();
|
2019-05-28 20:19:48 +02:00
|
|
|
|
2020-07-19 01:01:49 +02:00
|
|
|
Logger.log("Startup", "Loading Themes");
|
|
|
|
const themeErrors = ThemeManager.initialize();
|
2019-05-28 20:19:48 +02:00
|
|
|
|
2020-07-19 01:01:49 +02:00
|
|
|
Logger.log("Startup", "Removing Loading Icon");
|
|
|
|
LoadingIcon.hide();
|
2019-05-28 20:19:48 +02:00
|
|
|
|
2020-07-19 01:01:49 +02:00
|
|
|
// Show loading errors
|
|
|
|
Logger.log("Startup", "Collecting Startup Errors");
|
|
|
|
Modals.showAddonErrors({plugins: pluginErrors, themes: themeErrors});
|
2020-02-28 01:00:12 +01:00
|
|
|
|
2020-07-19 01:01:49 +02:00
|
|
|
const previousVersion = DataStore.getBDData("version");
|
|
|
|
if (Config.bbdVersion > previousVersion) {
|
|
|
|
Modals.showChangelogModal(Changelog);
|
|
|
|
DataStore.setBDData("version", Config.bbdVersion);
|
|
|
|
}
|
2020-07-16 07:42:56 +02:00
|
|
|
}
|
|
|
|
|
2020-07-19 01:01:49 +02:00
|
|
|
waitForGuilds() {
|
|
|
|
let timesChecked = 0;
|
|
|
|
return new Promise(resolve => {
|
|
|
|
const checkForGuilds = function () {
|
|
|
|
timesChecked++;
|
|
|
|
if (document.readyState != "complete") setTimeout(checkForGuilds, 100);
|
|
|
|
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);
|
|
|
|
setTimeout(checkForGuilds, 100);
|
|
|
|
};
|
|
|
|
|
|
|
|
checkForGuilds();
|
|
|
|
});
|
|
|
|
}
|
2020-07-16 07:42:56 +02:00
|
|
|
|
2020-07-19 01:01:49 +02:00
|
|
|
async loadDependencies() {
|
|
|
|
for (const data of this.dependencies) {
|
2020-07-19 06:34:23 +02:00
|
|
|
const url = Utilities.formatString((Config.local && data.local != null) ? data.local : data.url, {repo: Config.repo, hash: Config.hash, localServer: Config.localServer});
|
2020-07-19 01:01:49 +02:00
|
|
|
Logger.log(`Startup`, `Loading Resource (${url})`);
|
|
|
|
const injector = (data.type == "script" ? DOMManager.injectScript : DOMManager.linkStyle).bind(DOMManager);
|
|
|
|
try {
|
|
|
|
await injector(data.name, url);
|
|
|
|
}
|
|
|
|
catch (err) {
|
|
|
|
const backup = Utilities.formatString(data.backup, {minified: Config.minified ? ".min" : ""});
|
|
|
|
Logger.stacktrace(`Startup`, `Could not load ${url}. Using backup ${backup}`, err);
|
|
|
|
try {
|
|
|
|
await injector(data.name, backup);
|
|
|
|
}
|
|
|
|
catch (e) {
|
|
|
|
Logger.stacktrace(`Startup`, `Could not load ${url}. Using backup ${backup}`, err);
|
|
|
|
if (data.name === "jquery") Modals.alert(Strings.Startup.jqueryFailed, Strings.Startup.jqueryFailedDetails);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2020-07-16 07:42:56 +02:00
|
|
|
}
|
|
|
|
|
2020-07-19 01:01:49 +02:00
|
|
|
async updateInjector() {
|
|
|
|
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
|
|
|
|
Logger.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));
|
|
|
|
|
|
|
|
Logger.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();
|
|
|
|
|
|
|
|
Logger.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
|
|
|
|
Logger.log("InjectorUpdate", `Renaming ${path.basename(extractedFolder)} to ${folderName}`);
|
|
|
|
success = await new Promise(resolve => fs.rename(extractedFolder, injectionPath, err => resolve(!err)));
|
|
|
|
if (!success) {
|
|
|
|
Logger.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) {
|
|
|
|
Logger.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));
|
|
|
|
|
|
|
|
Logger.log("InjectorUpdate", "Injector Updated!");
|
2020-07-16 07:42:56 +02:00
|
|
|
return success;
|
|
|
|
}
|
2020-07-19 01:01:49 +02:00
|
|
|
};
|