314 lines
9.2 KiB
JavaScript
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')}`;
|
|
}
|
|
}
|