clean up and fix some bugs in setup script

pull/95/head
dperolio 2 years ago
parent 1131d0b078
commit fa6527eff9
No known key found for this signature in database
GPG Key ID: 4191689562D51409

@ -1,147 +1,156 @@
/* eslint-disable no-use-before-define */
/* eslint-disable no-undef */
const { existsSync, promises: { writeFile, mkdir, access, readdir, rm } } = require('fs');
const { exec: _exec, spawn } = require('child_process');
const { join, posix, sep } = require('path');
const { prompt } = require('inquirer');
const pluralize = require('pluralize');
const { promisify } = require('util');
const chalk = require('chalk');
const VIZALITY_BRAND_COLOR = '#ff006a';
const exec = promisify(_exec);
let release;
/**
* Helps Log an assortment of colorful messages to console.
* @param {...any} message Informational message
* @returns {void}
* Colors used for log styling.
*/
const info = (...message) => console.log(chalk.hex('#34b7eb')(message) + chalk.reset());
const warn = (...message) => console.log(chalk.hex('#ebd334')(message) + chalk.reset());
const success = (...message) => console.log(chalk.hex('#a5d479')(message) + chalk.reset());
const error = (...message) => console.log(chalk.hex('#f55353')(message) + chalk.reset());
const COLORS = {
Brand: '#ff006a',
Success: '#a5d479',
Warn: '#ebd334',
Info: '#7a85b8',
Error: '#f55353'
};
/**
* Dynamically create an assortment of functions that send colorful messages to console.
*/
Object.keys(COLORS).forEach(color => {
global[color.toLowerCase()] = (...message) => {
return console.log(chalk.hex(COLORS[color])(message) + chalk.reset());
};
});
/**
* Prompts the user, giving them an option to restart the injection process or exit.
* @returns {Promise<void>}
*/
const promptForRestartOrExit = async () => {
await prompt([
async function promptForRestartOrExit () {
const responses = await prompt([
{
type: 'list',
name: 'action',
message: `Would you like to start over or exit?`,
default: 'Inject',
choices: [
choices: wrapChoicesInColor([
'Start Over',
'Exit'
]
])
}
])
.then(async responses => {
if (responses.action === 'Start Over') {
await startInjectionProcess();
} else {
warn('Process has been aborted. Now exiting...');
process.exit(0);
}
});
};
]);
if (responses.action.includes('Start Over')) {
return startInjectionProcess();
}
warn('Process has been cancelled. Now exiting...');
return process.exit(0);
}
/**
* Checks whether a file or directory exists, and if so, whether the user has permissions for it.
* @param {path} path File or directory path
* @returns {boolean} Whether or not the file or directory exists, and if so, whether the user has permissions for it
*/
const confirmExistAndHasPermissions = path => {
async function confirmExistAndHasPermissions (path) {
return access(path)
.then(() => true)
.catch(() => false);
};
}
/**
* Prompts the user to manually enter a Discord installation path if one can't be found automatically.
* @param {('inject'|'uninject'|'reinject')} action Action to be performed
* @returns {Promise<void>}
*/
const promptManualPathCompletion = async action => {
await prompt([
async function promptManualPathCompletion (action) {
const responses = await prompt([
{
type: 'list',
name: 'action',
message: 'Would you like to enter the Discord directory path manually?',
default: 'Yes',
choices: [
choices: wrapChoicesInColor([
'Yes',
'No'
]
])
},
{
type: 'input',
name: 'discordPath',
message: `Please enter the absolute path to your Discord directory:`,
when: responses => responses.action === 'Yes'
when: responses => responses.action.includes('Yes')
}
])
.then(async responses => {
if (responses.action === 'No') {
]);
if (responses.action.includes('No')) {
return promptForRestartOrExit();
}
const discordPath = responses.discordPath?.trim();
if (discordPath) {
if (await confirmExistAndHasPermissions(discordPath)) {
let discordAppPath;
if (process.platform === 'win32') {
const discordDirectory = await readdir(discordPath);
/**
* Get the latest version's folder. Then find and return the app directory path.
*/
const latestVersionDirectory = discordDirectory.filter(path => path.startsWith('app-')).sort().reverse()[0];
discordAppPath = join(discordPath, latestVersionDirectory, 'resources', 'app');
} else if (process.platform === 'darwin') {
discordAppPath = join(discordPath, 'Contents', 'Resources', 'app');
} else {
discordAppPath = join(discordPath, 'resources', 'app');
}
if (!discordAppPath || !(await confirmExistAndHasPermissions(discordAppPath))) {
error(`${release}'s app directory couldn't be located and ${responses.action}ion has been cancelled.`);
return promptForRestartOrExit();
}
if (responses.discordPath && responses.discordPath.trim() !== '') {
if (await confirmExistAndHasPermissions(responses.discordPath)) {
let discordAppPath;
if (process.platform === 'win32') {
const discordDirectory = await readdir(responses.discordPath);
/**
* Get the latest version's folder. Then find and return the app directory path.
*/
const latestVersionDirectory = discordDirectory.filter(path => path.startsWith('app-')).sort().reverse()[0];
discordAppPath = join(responses.discordPath, latestVersionDirectory, 'resources', 'app');
} else if (process.platform === 'darwin') {
discordAppPath = join(responses.discordPath, 'Contents', 'Resources', 'app');
} else {
discordAppPath = join(responses.discordPath, 'resources', 'app');
}
if (!discordAppPath || !(await confirmExistAndHasPermissions(discordAppPath))) {
error(`${release}'s app directory couldn't be located and ${responses.action}ion has been aborted!`);
return promptForRestartOrExit();
}
info(`${release}'s app directory was successfully found at ${discordAppPath}`);
if (action === 'inject') {
await inject(discordAppPath);
} else if (action === 'uninject') {
await uninject(discordAppPath);
} else if (action === 'reinject') {
await reinject(discordAppPath);
} else {
return error(`Unrecognized action used. Process was aborted.`);
}
} else {
error(`${release} installation couldn't be located at ${responses.discordPath}. Please make sure the path is correct and try again!`);
return promptForRestartOrExit();
}
info(`Discord app directory was successfully found at ${chalk.hex(COLORS.Brand)(discordAppPath) + chalk.reset()}`);
if (action === 'inject') {
await inject(discordAppPath);
} else if (action === 'uninject') {
await uninject(discordAppPath);
} else if (action === 'reinject') {
await reinject(discordAppPath);
} else {
error(`You must enter a valid path in order to ${action} Vizality!`);
return promptForRestartOrExit();
return error(`Unrecognized action used. Process was cancelled.`);
}
});
};
} else {
error(`${release} installation couldn't be located at ${chalk.hex(COLORS.Brand)(discordAppPath) + chalk.reset()}.\nPlease make sure the path is correct and try again.`);
return promptForRestartOrExit();
}
} else {
error(`You must enter a valid path in order to ${action} Vizality.`);
return promptForRestartOrExit();
}
}
/**
* Determines the user's Discord app directory based on operating system and specified Discord release.
* @returns {Promise<string>} Discord app directory path
*/
const getDiscordAppPath = async () => {
async function getDiscordAppPath () {
/**
* Windows
*/
if (process.platform === 'win32') {
const discordPath = join(process.env.LOCALAPPDATA, release?.replace(' ', ''));
console.log(discordPath);
if (!discordPath || !(await confirmExistAndHasPermissions(discordPath))) {
error(`Unfortunately, the ${release} directory couldn't be located.`);
info(`Unfortunately, the ${release} directory couldn't be located.`);
return promptManualPathCompletion('inject');
}
@ -211,20 +220,20 @@ const getDiscordAppPath = async () => {
discordPath.splice(discordPath.length - 1, 1);
const discordAppPath = join('/', ...discordPath, 'resources', 'app');
return discordAppPath;
};
}
/**
* Checks for and ensures all node_modules package depedencies are installed.
* @returns {Promise<void>}
*/
const ensureDependencies = async () => {
async function ensureDependencies () {
/**
* Installs node_modules production dependencies.
* @returns {Promise<void>}
*/
const install = (cmd, args) => {
function install (args) {
return new Promise((resolve, reject) => {
const command = spawn(cmd, args, { cwd: join(__dirname, '..'), stdio: 'inherit', shell: true });
const command = spawn('npm', args, { cwd: join(__dirname, '..'), stdio: 'inherit', shell: true });
command.on('close', () => {
resolve();
});
@ -232,7 +241,7 @@ const ensureDependencies = async () => {
reject(err);
});
});
};
}
const nodeModulesPath = join(__dirname, '..', 'node_modules');
info(`${existsSync(nodeModulesPath) ? 'Checking' : 'Installing'} dependencies. Please wait...`);
@ -264,47 +273,52 @@ const ensureDependencies = async () => {
}
}
const unresolvedDependencies = [
{
dependencies: missingDependencies,
count: missingDependencies.length,
type: 'missing'
},
{
dependencies: outdatedDependencies,
count: outdatedDependencies.length,
type: 'outdated'
}
];
/**
* Install any missing dependencies.
* Try to install/update all missing and outdated dependencies.
*/
if (missingDependencies.length) {
info(`Found ${missingDependencies.length} missing ${missingDependencies.length === 1 ? 'package' : 'packages'}: ${missingDependencies.map(dependency => dependency)}. Installing...`);
for (const missingDependency of missingDependencies) {
return install('npm', [ 'install', missingDependency, '--only=production', '--silent' ])
.then(() => {
info(`Dependencies have been successfully installed!`);
})
.catch(err => {
error('An error occured while installing dependencies:', err);
});
const errors = [];
for (const dependencies of unresolvedDependencies) {
if (dependencies.count) {
info(`Found ${dependencies.count} ${dependencies.type} ${pluralize('package', dependencies.count)}: ${dependencies.dependencies.map(dependency => dependency)}. Installing...`);
for (const dependency of dependencies.dependencies) {
try {
await install('install', dependency, '--only=production', '--silent');
} catch (err) {
errors.push(`"${dependency}" - `, err.message);
}
}
}
}
/**
* Install any outdated dependencies.
* Output any errors that occurred while installing dependencies.
*/
if (outdatedDependencies.length) {
info(`Found ${missingDependencies.length} outdated ${missingDependencies.length === 1 ? 'package' : 'packages'}: ${missingDependencies.map(dependency => dependency)}. Installing...`);
for (const outdatedDependency of outdatedDependencies) {
return install('npm', [ 'install', outdatedDependency, '--only=production', '--silent' ])
.then(() => {
info(`Dependencies have been successfully installed!`);
})
.catch(err => {
error('An error occured while installing dependencies:', err);
});
}
if (errors.length) {
return error(`An error occured while installing the following:\n`, errors.join('\n'));
}
return info('Dependencies are already up-to-date!');
};
return info('Dependencies are already up-to-date.');
}
/**
* Uninjects Vizality from Discord.
* @param {path} discordAppPath Discord app path
* @returns {Promise<void>}
*/
const uninject = async discordAppPath => {
async function uninject (discordAppPath) {
/**
* Check for a valid Discord app directory.
*/
@ -317,7 +331,7 @@ const uninject = async discordAppPath => {
* Check for directory permissions.
*/
if (!(await confirmExistAndHasPermissions(discordAppPath))) {
error(`Vizality was unable to be uninjected due to missing permissions. Try again with elevated permissions!`);
error(`Vizality was unable to be uninjected due to missing permissions. Try again with elevated permissions.`);
return promptForRestartOrExit();
}
@ -325,183 +339,245 @@ const uninject = async discordAppPath => {
* Delete the Discord app directory.
*/
await rm(discordAppPath, { recursive: true, force: true });
return console.log(chalk.hex(VIZALITY_BRAND_COLOR)('Vizality has been uninjected.') + chalk.reset());
};
return info('Vizality has been uninjected.');
}
/**
* Injects Vizality into Discord.
* @param {path} discordAppPath Discord app path
* @returns {Promise<void>}
*/
const inject = async discordAppPath => {
if (await confirmExistAndHasPermissions(discordAppPath)) {
await prompt([
async function inject (discordAppPath) {
const existsAndHasPermissions = await confirmExistAndHasPermissions(discordAppPath);
if (existsAndHasPermissions) {
const responses = await prompt([
{
type: 'list',
name: 'action',
message: `It seems you already have a client modification in place. Would you like us to remove it and then try injecting again?`,
message: `${`${chalk.hex(COLORS.Info)('It seems you already have a client modification in place.\n') + chalk.reset()}Would you like us to remove it and then try injecting again?`}`,
default: 'Yes',
choices: [
choices: wrapChoicesInColor([
'Yes',
'No'
]
])
}
])
.then(async responses => {
if (responses.action === 'Yes') {
return reinject(discordAppPath);
}
warn('Vizality injection aborted. Now exiting...');
process.exit(0);
});
} else {
await ensureDependencies();
await mkdir(discordAppPath);
await Promise.all([
writeFile(join(discordAppPath, 'index.js'), `require('${join(__dirname, '..', 'injector').replace(RegExp(sep.repeat(2), 'g'), '/')}');`),
writeFile(join(discordAppPath, 'package.json'), JSON.stringify({ main: 'index.js', name: 'vizality' }, null, 2))
]);
return success(`Vizality has been successfully injected! Please restart ${release} to complete the process.`);
if (responses.action.includes('Yes')) {
return reinject(discordAppPath);
}
warn('Vizality injection canceled. Now exiting...');
return process.exit(0);
}
};
await ensureDependencies();
await mkdir(discordAppPath);
Promise.all([
writeFile(join(discordAppPath, 'index.js'), `require('${join(__dirname, '..', 'injector').replace(RegExp(sep.repeat(2), 'g'), '/')}');`),
writeFile(join(discordAppPath, 'package.json'), JSON.stringify({
main: 'index.js',
name: 'vizality'
}, null, 2))
]);
return success(`Vizality has been successfully injected! Please restart ${release} to complete the process.`);
}
/**
* Reinjects Vizality into Discord.
* @param {path} discordAppPath Discord app path
* @returns {Promise<void>}
*/
const reinject = async discordAppPath => {
return uninject(discordAppPath).then(async () => inject(discordAppPath));
};
async function reinject (discordAppPath) {
await uninject(discordAppPath);
await inject(discordAppPath);
}
const startInjectionProcess = async () => {
/**
* Start out with a nifty console message of Vizality. ( ͡° ͜ʖ ͡°)
*/
console.clear();
console.log(`
888 888 8888888 8888888888P d8888 888 8888888 88888888888 Y88b d88P
888 888 888 d88P d88888 888 888 888 Y88b d88P
888 888 888 d88P d88P888 888 888 888 Y88o88P
Y88b d88P 888 d88P d88P 888 888 888 888 Y888P
Y88b d88P 888 d88P d88P 888 888 888 888 888
Y88o88P 888 d88P d88P 888 888 888 888 888
Y888P 888 d88P d8888888888 888 888 888 888
Y8P 8888888 d8888888888 d88P 888 88888888 8888888 888 888`);
console.log(chalk.hex(VIZALITY_BRAND_COLOR)(`
[=== m a k e ---- y o u r ---- v i s i o n ---- a ---- r e a l i t y ===]
=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=
*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*\n`
));
/**
* Not sure why someone would clone into System32... but let's not let them proceed to avoid any issues.
*/
if (__dirname.toLowerCase().split(sep).join(posix.sep).includes('/windows/system32')) {
return error('It seems like Vizality is installed in your System32 directory. This can create issues and bloat your Windows installation. Please move your Vizality folder to another directory.');
}
/**
* Starts the setup process, allowing the user to inject, uninject, or reinject Vizality.
* @returns {Promise<void>}
*/
async function startInjectionProcess () {
try {
/**
* Start out with a nifty console message of Vizality. ( ͡° ͜ʖ ͡°)
*/
console.clear();
console.log(
'888 888 8888888 8888888888P d8888 888 8888888 88888888888 Y88b d88P\n' +
'888 888 888 d88P d88888 888 888 888 Y88b d88P \n' +
'888 888 888 d88P d88P888 888 888 888 Y88o88P \n' +
'Y88b d88P 888 d88P d88P 888 888 888 888 Y888P \n' +
' Y88b d88P 888 d88P d88P 888 888 888 888 888 \n' +
' Y88o88P 888 d88P d88P 888 888 888 888 888 \n' +
' Y888P 888 d88P d8888888888 888 888 888 888 \n' +
' Y8P 8888888 d8888888888 d88P 888 88888888 8888888 888 888\n'
);
brand(
'[=== m a k e ---- y o u r ---- v i s i o n ---- a ---- r e a l i t y ===]\n'
);
info(
'=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=\n' +
'*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*\n'
);
/**
* Make sure the user is on Node v14+.
*/
const NODE_MAJOR_VERSION = process.versions.node.split('.')[0];
if (NODE_MAJOR_VERSION < 14) {
return error(`It looks like you're on an outdated version of Node.js. Vizality requires you to run at least Node v14 or later. You can download a newer version here: https://nodejs.org`);
}
/**
* Not sure why someone would clone into System32... but let's not let them proceed to avoid any issues.
*/
if (process.platform === 'win32') {
if (__dirname.toLowerCase().split(sep).join(posix.sep).includes('/windows/system32')) {
error('It seems Vizality is installed in your System 32 directory. This can create issues and bloat your Windows installation. Please move your Vizality installation to a different directory.');
return process.exit(0);
}
}
/**
* Start a prompt to process whether the user wants to inject or uninject and which release
* of Discord they want to use.
*/
await prompt([
{
type: 'list',
name: 'action',
message: `Would you like to inject, uninject, or reinject Vizality?`,
transformer: () => 'pie',
default: 'Inject',
choices: [
'Inject',
'Uninject',
'Reinject'
]
},
{
type: 'list',
name: 'release',
message: `Which release of Discord would you like to inject Vizality into?`,
default: 'Discord Stable',
when: responses => responses.action === 'Inject',
choices: [
'Discord Stable',
'Discord PTB',
'Discord Canary',
'Discord Development'
]
},
{
type: 'list',
name: 'release',
message: `Which release of Discord would you like to uninject Vizality from?`,
default: 'Discord Stable',
when: responses => responses.action === 'Uninject',
choices: [
'Discord Stable',
'Discord PTB',
'Discord Canary',
'Discord Development'
]
},
{
type: 'list',
name: 'release',
message: `Which release of Discord would you like to reinject Vizality into?`,
default: 'Discord Stable',
when: responses => responses.action === 'Reinject',
choices: [
'Discord Stable',
'Discord PTB',
'Discord Canary',
'Discord Development'
]
/**
* Make sure the user is on Node v14+.
*/
const NODE_MAJOR_VERSION = process.versions.node.split('.')[0];
if (NODE_MAJOR_VERSION < 14) {
error(`It looks like you're on an outdated version of Node.js. Vizality requires you to run at least Node v14 or later. You can download a newer version here: https://nodejs.org`);
return process.exit(0);
}
])
.then(async responses => {
({ release } = responses);
/**
* Attempt to automatically find the Discord path.
*/
if (release === 'Discord Stable') {
release = 'Discord';
}
const discordAppPath = await getDiscordAppPath(release);
if (!discordAppPath) {
error(`${release}'s app directory couldn't be located. Please make sure the folder exists and you have the appropriate permissions.`);
return promptManualPathCompletion(responses.action.toLowerCase());
/**
* Start a prompt to process whether the user wants to inject or uninject and which release
* of Discord they want to use.
*/
const responses = await prompt([
{
type: 'list',
name: 'action',
message: `Would you like to inject, uninject, or reinject Vizality?`,
default: 'Inject',
choices: wrapChoicesInColor([
'Inject',
'Uninject',
'Reinject'
])
},
{
type: 'list',
name: 'release',
message: `Which release of Discord would you like to inject Vizality into?`,
default: 'Discord Stable',
when: responses => responses.action.includes('Inject'),
choices: wrapChoicesInColor([
'Discord Stable',
'Discord PTB',
'Discord Canary',
'Discord Development'
])
},
{
type: 'list',
name: 'release',
message: `Which release of Discord would you like to uninject Vizality from?`,
default: 'Discord Stable',
when: responses => responses.action.includes('Uninject'),
choices: wrapChoicesInColor([
'Discord Stable',
'Discord PTB',
'Discord Canary',
'Discord Development'
])
},
{
type: 'list',
name: 'release',
message: `Which release of Discord would you like to reinject Vizality into?`,
default: 'Discord Stable',
when: responses => responses.action.includes('Reinject'),
choices: wrapChoicesInColor([
'Discord Stable',
'Discord PTB',
'Discord Canary',
'Discord Development'
])
}
]);
if (responses.action === 'Inject') {
await inject(discordAppPath);
} else if (responses.action === 'Uninject') {
await uninject(discordAppPath);
} else if (responses.action === 'Reinject') {
await reinject(discordAppPath);
}
});
};
({ release, action } = responses);
/**
* Because we are using color insertions in our prompt, the output is not pretty.
* Let's check for includes and assign some corresponding plaintext values.
*/
switch (true) {
case release.includes('Stable'):
release = 'Discord';
break;
case release.includes('PTB'):
release = 'Discord PTB';
break;
case release.includes('Canary'):
release = 'Discord Canary';
break;
case release.includes('Development'):
release = 'Discord Development';
break;
}
switch (true) {
case action.includes('Inject'):
action = 'inject';
break;
case action.includes('Uninject'):
action = 'uninject';
break;
case action.includes('Reinject'):
action = 'reinject';
break;
}
/**
* Try to automatically find the user's Discord directory based on the specified release.
*/
const discordAppPath = await getDiscordAppPath(release);
if (!discordAppPath) {
error(`${release}'s app directory couldn't be located. Please make sure the folder exists and you have the appropriate permissions.`);
return promptManualPathCompletion(action);
}
/**
* Perform the action based on the prompt.
*/
switch (action) {
case 'inject':
return inject(discordAppPath);
case 'uninject':
return uninject(discordAppPath);
case 'reinject':
return reinject(discordAppPath);
}
} catch (err) {
return error('Something went wrong with the initial setup process:', err);
}
}
/**
* Wraps each item in a choice array in a color.
* @param {Array<string>} [choices] Choices
* @returns {Array}
*/
function wrapChoicesInColor (choices = []) {
const transformerChoices = [];
choices.forEach(choice => transformerChoices.push(`${chalk.hex(COLORS.Brand)(choice) + chalk.reset()}`));
return transformerChoices;
}
/**
* Start the injection process automatically.
*/
(async () => {
await startInjectionProcess();
})().catch(err => {
if (err.code === 'EACCES') {
return error('Vizality was unable to be injected due to missing permissions. Try again with elevated permissions!');
try {
await startInjectionProcess();
} catch (err) {
if (err.code === 'EACCES') {
return error('Vizality was unable to be injected due to missing permissions. Try again with elevated permissions.');
}
error(`The setup process encountered an issue that prevented it from running correctly:\n${err}`);
info(`If the problem persists, please join our Discord server and reach out for support: ${chalk.hex(COLORS.Brand)('https://invite.vizality.com') + chalk.reset()}.`);
return promptForRestartOrExit();
}
error('Vizality encountered an issue that prevented it from being injected:\n', err);
console.log(`If the problem persists, please join our Discord server and reach out for support: ${chalk.hex(VIZALITY_BRAND_COLOR)('https://invite.vizality.com')}.`);
return promptForRestartOrExit();
});
})();

Loading…
Cancel
Save