Skip to content

Commit

Permalink
CI: Prepare release 'v1.2.0
Browse files Browse the repository at this point in the history
  • Loading branch information
BetterDiscord CI authored and BetterDiscord CI committed Dec 13, 2022
2 parents 57d0bfa + 5d92a88 commit bd62ca5
Show file tree
Hide file tree
Showing 12 changed files with 252 additions and 506 deletions.
2 changes: 0 additions & 2 deletions .github/FUNDING.yml
Original file line number Diff line number Diff line change
@@ -1,3 +1 @@
github: rauenzi
patreon: Zerebos
custom: ["https://paypal.me/ZackRauen"]
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
A simple standalone program which automates the installation, removal and maintenance of <a href="https://github.com/BetterDiscord/BetterDiscord">BetterDiscord</a>.
<br/>
<br/>
<a href="https://discord.gg/0Tmfo5ZbORCRqbAd" target="_blank">
<a href="https://betterdiscord.app/invite" target="_blank">
<img src="https://img.shields.io/badge/discord-join-green?labelColor=0c0d10&color=3a71c1&style=for-the-badge&logo=data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI0OCIgaGVpZ2h0PSI0OCIgdmlld0JveD0iMCAwIDQ4IDQ4IiBmaWxsPSJub25lIj4NCjxwYXRoIGQ9Ik0xNi41MzUzIDUuNDUwNTNDMzMuODIzMyAtMS40NjgwOSA1MC44ODE1IDE2Ljg4NjMgNDEuNTkyNSAzMy42MDY2QzM3LjM3MzIgNDEuMjAxMiAyNi44OTA0IDQ3LjMxNyAxNC42ODQyIDQxLjUzMjZMNi4xOTk5IDQzLjk1NjdDNC44ODM0NiA0NC4zMzI4IDMuNjY2OTMgNDMuMTIxMiA0LjAzNjM0IDQxLjgwMzdDNC41NDI3MiAzOS45OTc2IDUuNzQyNTcgMzUuNzM5OCA2LjQ0NDEgMzMuNDMyM0MxLjE4Mjc5IDI0LjA0NCA0LjczMDUgMTAuMTc0OCAxNi41MzUzIDUuNDUwNTNaTTE1Ljk5NTQgMjAuMjQ5NkMxNS45OTU0IDIwLjkzOTkgMTYuNTU1IDIxLjQ5OTYgMTcuMjQ1NCAyMS40OTk2SDMwLjc0OThDMzEuNDQwMSAyMS40OTk2IDMxLjk5OTggMjAuOTM5OSAzMS45OTk4IDIwLjI0OTZDMzEuOTk5OCAxOS41NTkyIDMxLjQ0MDEgMTguOTk5NiAzMC43NDk4IDE4Ljk5OTZIMTcuMjQ1NEMxNi41NTUgMTguOTk5NiAxNS45OTU0IDE5LjU1OTIgMTUuOTk1NCAyMC4yNDk2Wk0xNy4yNDk4IDI2LjQ3NDZDMTYuNTU5NCAyNi40NzQ2IDE1Ljk5OTggMjcuMDM0MiAxNS45OTk4IDI3LjcyNDZDMTUuOTk5OCAyOC40MTQ5IDE2LjU1OTQgMjguOTc0NiAxNy4yNDk4IDI4Ljk3NDZIMjYuNzQ5OEMyNy40NDAxIDI4Ljk3NDYgMjcuOTk5OCAyOC40MTQ5IDI3Ljk5OTggMjcuNzI0NkMyNy45OTk4IDI3LjAzNDIgMjcuNDQwMSAyNi40NzQ2IDI2Ljc0OTggMjYuNDc0NkgxNy4yNDk4WiIgZmlsbD0iIzNhNzFjMSIvPg0KPC9zdmc+" alt="Chat"/>
</a>
<a href="https://github.com/BetterDiscord/installer/releases/" target="_blank">
Expand Down
6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"productName": "BetterDiscord Installer",
"description": "A simple standalone program which automates the installation, removal and maintenance of BetterDiscord.",
"author": "BetterDiscord",
"version": "1.1.2",
"version": "1.2.0",
"license": "MIT",
"scripts": {
"dev": "electron-webpack dev",
Expand All @@ -18,8 +18,8 @@
"source-map-support": "^0.5.16"
},
"devDependencies": {
"electron": "^9.4.0",
"electron-builder": "^22.4.1",
"electron": "^13.6.9",
"electron-builder": "^23.6.0",
"electron-webpack": "^2.8.2",
"eslint": "^7.21.0",
"eslint-plugin-svelte3": "^3.1.2",
Expand Down
100 changes: 26 additions & 74 deletions src/renderer/actions/install.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import logs from "../stores/logs";
import {progress, status} from "../stores/installation";
import {remote, shell} from "electron";
import {promises as fs} from "fs";
Expand All @@ -15,43 +14,17 @@ import {showRestartNotice} from "./utils/notices";
import doSanityCheck from "./utils/sanity";

const MAKE_DIR_PROGRESS = 30;
const CHECK_OLD_INSTALL = 40;
const TRANSFER_OLD_ADDONS = 50;
const DOWNLOAD_PACKAGE_PROGRESS = 60;
const INJECT_SHIM_PROGRESS = 90;
const RESTART_DISCORD_PROGRESS = 100;

const oldBDFolder = path.join(remote.app.getPath("home"), "Library", "Preferences", "betterdiscord"); // Old MacOS
const RELEASE_API = "https://api.github.com/repos/BetterDiscord/BetterDiscord/releases";

const bdFolder = path.join(remote.app.getPath("appData"), "BetterDiscord");
const bdDataFolder = path.join(bdFolder, "data");
const bdPluginsFolder = path.join(bdFolder, "plugins");
const bdThemesFolder = path.join(bdFolder, "themes");

async function checkOldMacOS(folder) {
if (await exists(folder)) {
log(`⚠️ Found old BD installation: ${folder}`);
return true;
}
return false;
}

async function transferOldAddons(oldFolder, newFolder) {
if (await exists(oldFolder)) {
const addons = await fs.readdir(oldFolder);
for (let a = 0; a < addons.length; a++) {
const oldName = path.join(oldFolder, addons[a]);
const newName = path.join(newFolder, addons[a]);
const stats = await fs.stat(oldName);
if (!stats.isFile()) continue;
try {
await fs.rename(oldName, newName);
}
catch (err) {
log(`❌ Failed to transfer: ${addons[a]}`);
}
}
}
}

async function makeDirectories(...folders) {
const progressPerLoop = (MAKE_DIR_PROGRESS - progress.value) / folders.length;
Expand All @@ -74,23 +47,35 @@ async function makeDirectories(...folders) {
}
}

const getJSON = phin.defaults({method: "GET", parse: "json", followRedirects: true, headers: {"User-Agent": "BetterDiscord Installer"}});
const downloadFile = phin.defaults({method: "GET", followRedirects: true, headers: {"User-Agent": "BetterDiscord Installer", "Accept": "application/octet-stream"}});
const getJSON = phin.defaults({method: "GET", parse: "json", followRedirects: true, headers: {"User-Agent": "BetterDiscord/Installer"}});
const downloadFile = phin.defaults({method: "GET", followRedirects: true, headers: {"User-Agent": "BetterDiscord/Installer", "Accept": "application/octet-stream"}});
const asarPath = path.join(bdDataFolder, "betterdiscord.asar");
async function downloadAsar() {
let downloadUrl = "https://api.github.com/repos/BetterDiscord/BetterDiscord/releases";
try {
const response = await getJSON(downloadUrl);
const response = await getJSON(RELEASE_API);
const releases = response.body;
const asset = releases && releases.length ? releases[0].assets.find(a => a.name === "betterdiscord.asar") : "https://api.github.com/repos/BetterDiscord/BetterDiscord/releases/assets/39982244"; // temporary workaround
downloadUrl = asset.url;

const resp = await downloadFile(downloadUrl);
const originalFs = require("original-fs").promises; // because electron doesn't like when I write asar files
await originalFs.writeFile(asarPath, resp.body);
const asset = releases && releases.length && releases[0].assets && releases[0].assets.find(a => a.name.toLowerCase() === "betterdiscord.asar");
const assetUrl = asset && asset.url;
if (!assetUrl) {
let errMessage = "Could not get the asset url";
if (!asset) errMessage = "Could not get asset object";
if (!releases) errMessage = "Could not get response body";
if (!response) errMessage = "Could not get any response";
throw new Error(errMessage);
}
try {
const resp = await downloadFile(assetUrl);
const originalFs = require("original-fs").promises; // because electron doesn't like when I write asar files
await originalFs.writeFile(asarPath, resp.body);
}
catch (error) {
log(`❌ Failed to download package from ${assetUrl}`);
log(`❌ ${error.message}`);
return error;
}
}
catch (err) {
log(`❌ Failed to download package ${downloadUrl}`);
log(`❌ Failed to get asset url from ${RELEASE_API}`);
log(`❌ ${err.message}`);
return err;
}
Expand All @@ -100,18 +85,8 @@ async function injectShims(paths) {
const progressPerLoop = (INJECT_SHIM_PROGRESS - progress.value) / paths.length;
for (const discordPath of paths) {
log("Injecting into: " + discordPath);
const appPath = path.join(discordPath, "app");
const pkgFile = path.join(appPath, "package.json");
const indexFile = path.join(appPath, "index.js");
try {
if (process.platform === "win32" || process.platform === "darwin") {
if (!(await exists(appPath))) await fs.mkdir(appPath);
await fs.writeFile(pkgFile, JSON.stringify({name: "betterdiscord", main: "index.js"}));
await fs.writeFile(indexFile, `require("${asarPath.replace(/\\/g, "\\\\").replace(/"/g, "\\\"")}");`);
}
else {
await fs.writeFile(path.join(discordPath, "index.js"), `require("${asarPath.replace(/\\/g, "\\\\").replace(/"/g, "\\\"")}");\nmodule.exports = require("./core.asar");`);
}
await fs.writeFile(path.join(discordPath, "index.js"), `require("${asarPath.replace(/\\/g, "\\\\").replace(/"/g, "\\\"")}");\nmodule.exports = require("./core.asar");`);
log("✅ Injection successful");
progress.set(progress.value + progressPerLoop);
}
Expand Down Expand Up @@ -139,29 +114,6 @@ export default async function(config) {
if (makeDirErr) return fail();
log("✅ Directories created");
progress.set(MAKE_DIR_PROGRESS);


if (process.platform === "darwin") {
lognewline("Checking for old MacOS installation...");
const found = await checkOldMacOS(oldBDFolder);
progress.set(CHECK_OLD_INSTALL);
if (found) {
const confirmation = await remote.dialog.showMessageBox(remote.BrowserWindow.getFocusedWindow(), {
type: "question",
title: "Old Install Found",
message: "Found an old BD installation, do you want to transfer your plugins and themes?",
noLink: true,
cancelId: 1,
buttons: ["Yes", "No"]
});

if (confirmation.response === 0) {
await transferOldAddons(path.join(oldBDFolder, "plugins"), path.join(bdFolder, "plugins"));
await transferOldAddons(path.join(oldBDFolder, "themes"), path.join(bdFolder, "themes"));
progress.set(TRANSFER_OLD_ADDONS);
}
}
}


lognewline("Downloading asar file");
Expand Down
49 changes: 20 additions & 29 deletions src/renderer/actions/paths.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,27 +15,26 @@ const safeIsDir = (fullpath) => {
};

const getDiscordPath = function(releaseChannel) {
let resourcePath = "";
let desktopCorePath = "";
if (process.platform === "win32") {
let basedir = path.join(process.env.LOCALAPPDATA, releaseChannel.replace(/ /g, "")); // Normal install path in AppData\Local
if (!fs.existsSync(basedir)) basedir = path.join(process.env.PROGRAMDATA, process.env.USERNAME, releaseChannel.replace(/ /g, "")); // Atypical location in ProgramData\%username%
if (!fs.existsSync(basedir)) return "";
const version = fs.readdirSync(basedir).filter(f => safeIsDir(path.join(basedir, f)) && f.split(".").length > 1).sort().reverse()[0];
if (!version) return "";
resourcePath = path.join(basedir, version, "resources");
}
else if (process.platform === "darwin") {
resourcePath = path.join("/Applications", `${releaseChannel}.app`, "Contents", "Resources");
// To account for discord_desktop_core-1 or discord_dekstop_core-2
const coreWrap = fs.readdirSync(path.join(basedir, version, "modules")).filter(e => e.indexOf("discord_desktop_core") === 0).sort().reverse()[0];
desktopCorePath = path.join(basedir, version, "modules", coreWrap, "discord_desktop_core");
}
else {
const basedir = path.join(remote.app.getPath("userData"), "..", releaseChannel.toLowerCase().replace(" ", ""));
if (!fs.existsSync(basedir)) return "";
const version = fs.readdirSync(basedir).filter(f => safeIsDir(path.join(basedir, f)) && f.split(".").length > 1).sort().reverse()[0];
if (!version) return "";
resourcePath = path.join(basedir, version, "modules", "discord_desktop_core");
desktopCorePath = path.join(basedir, version, "modules", "discord_desktop_core");
}

if (fs.existsSync(resourcePath)) return resourcePath;
if (fs.existsSync(desktopCorePath)) return desktopCorePath;
return "";
};

Expand All @@ -45,14 +44,12 @@ for (const channel in platforms) {

export const getBrowsePath = function(channel) {
if (process.platform === "win32") return path.join(process.env.LOCALAPPDATA, platforms[channel].replace(" ", ""));
else if (process.platform === "darwin") return path.join("/Applications", `${platforms[channel]}.app`);
return path.join(remote.app.getPath("userData"), "..", platforms[channel].toLowerCase().replace(" ", ""));
};

export const validatePath = function(channel, proposedPath) {
if (process.platform === "win32") return validateWindows(channel, proposedPath);
else if (process.platform === "darwin") return validateMac(channel, proposedPath);
return validateLinux(channel, proposedPath);
return validateLinuxMac(channel, proposedPath);
};

const validateWindows = function(channel, proposedPath) {
Expand All @@ -61,36 +58,30 @@ const validateWindows = function(channel, proposedPath) {
const isParentDir = fs.existsSync(path.join(proposedPath, channelName));
if (isParentDir) proposedPath = path.join(proposedPath, channelName);

let resourcePath = "";
let corePath = "";
const selected = path.basename(proposedPath);
const isBaseDir = selected === channelName;
if (isBaseDir) {
const version = fs.readdirSync(proposedPath).filter(f => safeIsDir(path.join(proposedPath, f)) && f.split(".").length > 1).sort().reverse()[0];
if (!version) return "";
resourcePath = path.join(proposedPath, version, "resources");
// To account for discord_desktop_core-1 or discord_dekstop_core-2
const coreWrap = fs.readdirSync(path.join(proposedPath, version, "modules")).filter(e => e.indexOf("discord_desktop_core") === 0).sort().reverse()[0];
corePath = path.join(proposedPath, version, "modules", coreWrap, "discord_desktop_core");
}

if (selected.startsWith("app-") && selected.split(".").length > 2) resourcePath = path.join(proposedPath, "resources");
if (selected === "resources") resourcePath = proposedPath;

const executablePath = path.join(resourcePath, "..", `${channelName}.exe`);
if (fs.existsSync(executablePath)) return resourcePath;
return "";
};

const validateMac = function(channel, proposedPath) {
let resourcePath = "";
const selected = path.basename(proposedPath);
if (selected === `${platforms[channel]}.app`) resourcePath = path.join(proposedPath, "Contents", "Resources");
if (selected === "Contents") resourcePath = path.join(proposedPath, "Resources");
if (selected === "Resources") resourcePath = proposedPath;
if (selected.split(".").length > 2) {
// To account for discord_desktop_core-1 or discord_dekstop_core-2
const coreWrap = fs.readdirSync(path.join(proposedPath), "modules").filter(e => e.indexOf("discord_desktop_core") === 0).sort().reverse()[0];
corePath = path.join(proposedPath, "modules", coreWrap, "discord_desktop_core");
}
if (selected === "discord_desktop_core") corePath = proposedPath;

const executablePath = path.join(resourcePath, "..", "MacOS", platforms[channel]);
if (fs.existsSync(executablePath)) return resourcePath;
const coreAsar = path.join(corePath, `core.asar`);
if (fs.existsSync(coreAsar)) return corePath;
return "";
};

const validateLinux = function(channel, proposedPath) {
const validateLinuxMac = function(channel, proposedPath) {
if (proposedPath.includes("/snap/")) {
remote.dialog.showErrorBox("BetterDiscord Incompatible", "BetterDiscord is currently incompatible with Snap installs of Discord. Support for snap installs is coming soon!");
return "";
Expand Down
81 changes: 39 additions & 42 deletions src/renderer/actions/repair.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@

import {progress, status} from "../stores/installation";
import {progress} from "../stores/installation";
import {remote} from "electron";
import {promises as fs} from "fs";
import originalFs from "original-fs";
Expand All @@ -16,50 +15,48 @@ import {showKillNotice} from "./utils/notices";
import doSanityCheck from "./utils/sanity";

const KILL_DISCORD_PROGRESS = 20;
const DELETE_APP_DIRS_PROGRESS = 50;
const DELETE_MODULE_DIRS_PROGRESS = 100;
const DELETE_SHIM_PROGRESS = 60;
const DELETE_PLUGINS_JSON_PROGRESS = 100;


async function deleteAppDirs(paths) {
const progressPerLoop = (DELETE_APP_DIRS_PROGRESS - progress.value) / paths.length;
async function deleteShims(paths) {
const progressPerLoop = (DELETE_SHIM_PROGRESS - progress.value) / paths.length;
for (const discordPath of paths) {
log("Removing " + discordPath);
const appPath = path.join(discordPath, "app");
if (await exists(appPath)) {
const error = await new Promise(resolve => rimraf(appPath, originalFs, resolve));
if (error) {
log(` Could not delete folder ${appPath}`);
log(`❌ ${error.message}`);
return error;
}
const indexFile = path.join(discordPath, "index.js");
log("Removing " + indexFile);
try {
if (await exists(indexFile)) await fs.writeFile(indexFile, `module.exports = require("./core.asar");`);
log("✅ Deletion successful");
progress.set(progress.value + progressPerLoop);
}
catch (err) {
log(`❌ Could not delete file ${indexFile}`);
log(`❌ ${err.message}`);
return err;
}
log("✅ Deletion successful");
progress.set(progress.value + progressPerLoop);
}
}

const platforms = {stable: "Discord", ptb: "Discord PTB", canary: "Discord Canary"};
async function deleteModuleDirs(config) {
const size = Object.keys(config).length;
const progressPerLoop = (DELETE_MODULE_DIRS_PROGRESS - progress.value) / size;
for (const channel in config) {
const roaming = path.join(remote.app.getPath("userData"), "..", platforms[channel].replace(" ", "").toLowerCase());
const bdFolder = path.join(remote.app.getPath("appData"), "BetterDiscord");
const bdDataFolder = path.join(bdFolder, "data");
async function disableAllPlugins(channels) {
const progressPerLoop = (DELETE_PLUGINS_JSON_PROGRESS - progress.value) / channels.length;
for (const channel of channels) {
const channelFolder = path.join(bdDataFolder, channel);
const pluginsJson = path.join(channelFolder, "plugins.json");
try {
const versionDir = (await fs.readdir(roaming)).find(d => d.split(".").length > 2);
const modulesPath = path.join(roaming, versionDir, "modules");
log("Removing " + modulesPath);
if (await exists(modulesPath)) {
const error = await new Promise(resolve => rimraf(path.join(modulesPath), originalFs, resolve));
if (error) {
log(`❌ Could not delete modules in ${roaming}`);
log(`❌ ${error.message}`);
return error;
}
if (originalFs.existsSync(pluginsJson)) {
await fs.unlink(pluginsJson);
log(`✅ Deleted plugins.json`);
}
else {
log(`✅ plugins.json does not exist`);
}
log("✅ Deletion successful");
progress.set(progress.value + progressPerLoop);

}
catch (err) {
log(`❌ Could not delete modules in ${roaming}`);
log(`❌ Failed to delete plugins.json: ${pluginsJson}`);
log(`❌ ${err.message}`);
return err;
}
Expand Down Expand Up @@ -110,18 +107,18 @@ export default async function(config) {

await new Promise(r => setTimeout(r, 200));
lognewline("Deleting shims...");
const deleteShimErr = await deleteAppDirs(paths);
const deleteShimErr = await deleteShims(paths);
if (deleteShimErr) return fail();
log("✅ Shims deleted");
progress.set(DELETE_APP_DIRS_PROGRESS);
progress.set(DELETE_SHIM_PROGRESS);


await new Promise(r => setTimeout(r, 200));
lognewline("Deleting discord modules...");
const deleteModulesErr = await deleteModuleDirs(config);
if (deleteModulesErr) return fail();
log("✅ Modules deleted");
progress.set(DELETE_MODULE_DIRS_PROGRESS);
lognewline("Disabling all plugins...");
const deleteJsonErr = await disableAllPlugins(channels);
if (deleteJsonErr) return fail();
log("✅ Plugins disabled");
progress.set(DELETE_PLUGINS_JSON_PROGRESS);


showInstallNotice(config);
Expand Down
Loading

0 comments on commit bd62ca5

Please sign in to comment.