BetterDiscordApp-v2/common/modules/utils.js

314 lines
9.2 KiB
JavaScript

/**
* BetterDiscord Utils Module
* Copyright (c) 2015-present Jiiks/JsSucks - https://github.com/Jiiks / https://github.com/JsSucks
* All rights reserved.
* https://betterdiscord.net
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
const
path = require('path'),
fs = require('fs'),
_ = require('lodash');
import { PatchedFunction, Patch } from './monkeypatch';
import { Vendor } from 'modules';
import filetype from 'file-type';
export class Utils {
static overload(fn, cb) {
const orig = fn;
return function (...args) {
orig(...args);
cb(...args);
}
}
/**
* Monkey-patches an object's method.
*/
static monkeyPatch(object, methodName, options, f) {
const patchedFunction = new PatchedFunction(object, methodName);
const patch = new Patch(patchedFunction, options, f);
patchedFunction.addPatch(patch);
return patch;
}
/**
* Monkey-patches an object's method and returns a promise that will be resolved with the data object when the method is called.
* You will have to call data.callOriginalMethod() if it wants the original method to be called.
*/
static monkeyPatchOnce(object, methodName) {
return new Promise((resolve, reject) => {
this.monkeyPatch(object, methodName, data => {
data.patch.cancel();
resolve(data);
});
});
}
/**
* Monkeypatch function that is compatible with samogot's Lib Discord Internals.
* Don't use this for writing new plugins as it will eventually be removed!
*/
static compatibleMonkeyPatch(what, methodName, options) {
const { before, instead, after, once = false, silent = false } = options;
const cancelPatch = () => patch.cancel();
const compatible_function = _function => data => {
const compatible_data = {
thisObject: data.this,
methodArguments: data.arguments,
returnValue: data.return,
cancelPatch,
originalMethod: data.originalMethod,
callOriginalMethod: () => data.callOriginalMethod()
};
try {
_function(compatible_data);
data.arguments = compatible_data.methodArguments;
data.return = compatible_data.returnValue;
} catch (err) {
data.arguments = compatible_data.methodArguments;
data.return = compatible_data.returnValue;
throw err;
}
};
const patch = this.monkeyPatch(what, methodName, {
before: before ? compatible_function(before) : undefined,
instead: instead ? compatible_function(instead) : undefined,
after: after ? compatible_function(after) : undefined,
once
});
return cancelPatch;
}
static async tryParseJson(jsonString) {
try {
return JSON.parse(jsonString);
} catch (err) {
throw ({
'message': 'Failed to parse json',
err
});
}
}
static toCamelCase(o) {
const camelCased = {};
_.forEach(o, (value, key) => {
if (_.isPlainObject(value) || _.isArray(value)) {
value = this.toCamelCase(value);
}
camelCased[_.camelCase(key)] = value;
});
return camelCased;
}
static compare(value1, value2) {
// Check to see if value1 and value2 contain the same data
if (typeof value1 !== typeof value2) return false;
if (value1 === null && value2 === null) return true;
if (value1 === null || value2 === null) return false;
if (typeof value1 === 'object' || typeof value1 === 'array') {
// Loop through the object and check if everything's the same
let value1array = typeof value1 === 'array' ? value1 : Object.keys(value1);
let value2array = typeof value2 === 'array' ? value2 : Object.keys(value2);
if (value1array.length !== value2array.length) return false;
for (let key in value1) {
if (!this.compare(value1[key], value2[key])) return false;
}
} else if (value1 !== value2) return false;
// value1 and value2 contain the same data
return true;
}
static deepclone(value) {
if (typeof value === 'object') {
if (value instanceof Array) return value.map(i => this.deepclone(i));
const clone = Object.assign({}, value);
for (let key in clone) {
clone[key] = this.deepclone(clone[key]);
}
return clone;
}
return value;
}
static deepfreeze(object) {
if (typeof object === 'object' && object !== null) {
const properties = Object.getOwnPropertyNames(object);
for (let property of properties) {
this.deepfreeze(object[property]);
}
Object.freeze(object);
}
return object;
}
}
export class FileUtils {
static async fileExists(path) {
return new Promise((resolve, reject) => {
fs.stat(path, (err, stats) => {
if (err) return reject({
'message': `No such file or directory: ${err.path}`,
err
});
if (!stats.isFile()) return reject({
'message': `Not a file: ${path}`,
stats
});
resolve();
});
});
}
static async directoryExists(path) {
return new Promise((resolve, reject) => {
fs.stat(path, (err, stats) => {
if (err) return reject({
'message': `Directory does not exist: ${path}`,
err
});
if (!stats.isDirectory()) return reject({
'message': `Not a directory: ${path}`,
stats
});
resolve();
});
});
}
static async createDirectory(path) {
return new Promise((resolve, reject) => {
fs.mkdir(path, err => {
if (err) {
if (err.code === 'EEXIST') return resolve();
else return reject(err);
}
resolve();
});
});
}
static async ensureDirectory(path) {
try {
await this.directoryExists(path);
return true;
} catch (err) {
try {
await this.createDirectory(path);
return true;
} catch (err) {
throw err;
}
}
}
static async readFile(path) {
try {
await this.fileExists(path);
} catch (err) {
throw (err);
}
return new Promise((resolve, reject) => {
fs.readFile(path, 'utf-8', (err, data) => {
if (err) reject({
'message': `Could not read file: ${path}`,
err
});
resolve(data);
});
});
}
static async readFileBuffer(path, options) {
return new Promise((resolve, reject) => {
fs.readFile(path, options || {}, (err, data) => {
if (err) return reject(err);
resolve(data);
});
});
}
static async writeFile(path, data) {
return new Promise((resolve, reject) => {
fs.writeFile(path, data, err => {
if (err) return reject(err);
resolve();
});
});
}
static async readJsonFromFile(path) {
let readFile;
try {
readFile = await this.readFile(path);
} catch (err) {
throw (err);
}
try {
const parsed = await Utils.tryParseJson(readFile);
return parsed;
} catch (err) {
throw (Object.assign(err, { path }));
}
}
static async writeJsonToFile(path, json) {
return this.writeFile(path, JSON.stringify(json));
}
static async listDirectory(path) {
try {
await this.directoryExists(path);
return new Promise((resolve, reject) => {
fs.readdir(path, (err, files) => {
if (err) return reject(err);
resolve(files);
});
});
} catch (err) {
throw err;
}
}
static async readDir(path) {
return this.listDirectory(path);
}
static async getFileType(buffer) {
if (typeof buffer === 'string') buffer = await this.readFileBuffer(buffer);
return filetype(buffer);
}
static async toDataURI(buffer, type) {
if (typeof buffer === 'string') buffer = await this.readFileBuffer(buffer);
if (!type) type = this.getFileType(buffer).mime;
return `data:${type};base64,${buffer.toString('base64')}`;
}
}