/** * 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')}`; } }