diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml index 88390710..04acc8c3 100644 --- a/.github/FUNDING.yml +++ b/.github/FUNDING.yml @@ -1,3 +1 @@ github: rauenzi -patreon: Zerebos -custom: ["https://paypal.me/ZackRauen"] diff --git a/README.md b/README.md index 935a1174..e3c0e8ac 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ A simple standalone program which automates the installation, removal and maintenance of BetterDiscord.

- + Chat diff --git a/package.json b/package.json index 8aa68e33..ee574415 100644 --- a/package.json +++ b/package.json @@ -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", @@ -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", diff --git a/src/renderer/actions/install.js b/src/renderer/actions/install.js index e917da2b..a2c4096f 100644 --- a/src/renderer/actions/install.js +++ b/src/renderer/actions/install.js @@ -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"; @@ -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; @@ -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; } @@ -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); } @@ -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"); diff --git a/src/renderer/actions/paths.js b/src/renderer/actions/paths.js index 5e07e570..b434fd7d 100644 --- a/src/renderer/actions/paths.js +++ b/src/renderer/actions/paths.js @@ -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 ""; }; @@ -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) { @@ -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 ""; diff --git a/src/renderer/actions/repair.js b/src/renderer/actions/repair.js index 1f687139..aef93cb2 100644 --- a/src/renderer/actions/repair.js +++ b/src/renderer/actions/repair.js @@ -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"; @@ -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; } @@ -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); diff --git a/src/renderer/actions/uninstall.js b/src/renderer/actions/uninstall.js index ac3b6347..35f77cf3 100644 --- a/src/renderer/actions/uninstall.js +++ b/src/renderer/actions/uninstall.js @@ -22,24 +22,15 @@ const RESTART_DISCORD_PROGRESS = 100; 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"); const indexFile = path.join(discordPath, "index.js"); + log("Removing " + indexFile); try { - if (process.platform === "win32" || process.platform === "darwin") { - if (await exists(appPath)) { - const error = await new Promise(r => rimraf(appPath, originalFs, r)); - if (error) throw error; // Throw instead because there are multiple throw points - } - } - else { - if (await exists(indexFile)) await fs.writeFile(indexFile, `module.exports = require("./core.asar");`); - } + 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 folder ${appPath}`); + log(`❌ Could not delete file ${indexFile}`); log(`❌ ${err.message}`); return err; } diff --git a/src/renderer/actions/utils/fail.js b/src/renderer/actions/utils/fail.js index ec01a5e9..56881785 100644 --- a/src/renderer/actions/utils/fail.js +++ b/src/renderer/actions/utils/fail.js @@ -1,7 +1,7 @@ import {log} from "./log"; import {action, status} from "../../stores/installation"; -const discordURL = "https://discord.gg/0Tmfo5ZbORCRqbAd"; +const discordURL = "https://betterdiscord.app/invite"; export default function fail() { log(""); diff --git a/src/renderer/actions/utils/kill.js b/src/renderer/actions/utils/kill.js index 2924ba72..bbe2a560 100644 --- a/src/renderer/actions/utils/kill.js +++ b/src/renderer/actions/utils/kill.js @@ -1,4 +1,3 @@ - import path from "path"; import findProcess from "find-process"; import kill from "tree-kill"; diff --git a/src/renderer/common/SocialLinks.svelte b/src/renderer/common/SocialLinks.svelte index e57ae1cc..32a5caa7 100644 --- a/src/renderer/common/SocialLinks.svelte +++ b/src/renderer/common/SocialLinks.svelte @@ -3,7 +3,7 @@ const webUrl = "https://betterdiscord.app"; const githubUrl = "http://github.com/BetterDiscord/BetterDiscord"; - const donateUrl = "https://www.patreon.com/Zerebos"; + const donateUrl = "https://github.com/sponsors/rauenzi/";