2018-01-30 16:59:27 +01:00
/ * *
* BetterDiscord Content Manager 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 .
* /
import Globals from './globals' ;
import { FileUtils , ClientLogger as Logger } from 'common' ;
import path from 'path' ;
2018-02-07 13:15:46 +01:00
import { Events } from 'modules' ;
2018-02-13 23:28:58 +01:00
import { ErrorEvent } from 'structs' ;
2018-02-13 17:44:07 +01:00
import { Modals } from 'ui' ;
2018-01-30 16:59:27 +01:00
2018-02-14 13:55:06 +01:00
/ * *
* Base class for external content managing
* /
2018-01-30 16:59:27 +01:00
export default class {
2018-02-14 13:55:06 +01:00
/ * *
* Any errors that happened
* returns { Array }
* /
2018-02-07 17:02:27 +01:00
static get errors ( ) {
return this . _errors || ( this . _errors = [ ] ) ;
}
2018-02-14 13:55:06 +01:00
/ * *
* Locallly stored content
* returns { Array }
* /
2018-01-30 16:59:27 +01:00
static get localContent ( ) {
return this . _localContent ? this . _localContent : ( this . _localContent = [ ] ) ;
}
2018-02-14 13:55:06 +01:00
/ * *
* Local path for content
* returns { String }
* /
2018-01-30 16:59:27 +01:00
static get contentPath ( ) {
return this . _contentPath ? this . _contentPath : ( this . _contentPath = Globals . getObject ( 'paths' ) . find ( path => path . id === this . pathId ) . path ) ;
}
2018-02-14 13:55:06 +01:00
/ * *
* Load all locally stored content
* @ param { bool } suppressErrors Suppress any errors that occur during loading of content
* /
static async loadAllContent ( suppressErrors = false ) {
2018-01-30 16:59:27 +01:00
try {
2018-01-30 23:21:06 +01:00
await FileUtils . ensureDirectory ( this . contentPath ) ;
const directories = await FileUtils . listDirectory ( this . contentPath ) ;
for ( let dir of directories ) {
try {
await this . preloadContent ( dir ) ;
} catch ( err ) {
2018-02-13 23:28:58 +01:00
this . errors . push ( new ErrorEvent ( {
2018-02-07 17:02:27 +01:00
module : this . moduleName ,
message : ` Failed to load ${ dir } ` ,
err
} ) ) ;
2018-02-11 20:31:24 +01:00
2018-01-30 23:21:06 +01:00
Logger . err ( this . moduleName , err ) ;
}
}
2018-02-14 00:23:52 +01:00
if ( this . errors . length && ! suppressErrors ) {
2018-02-13 17:44:07 +01:00
Modals . error ( {
header : ` ${ this . moduleName } - ${ this . errors . length } ${ this . contentType } ${ this . errors . length !== 1 ? 's' : '' } failed to load ` ,
2018-02-07 17:02:27 +01:00
module : this . moduleName ,
type : 'err' ,
content : this . errors
} ) ;
2018-02-13 17:57:05 +01:00
this . _errors = [ ] ;
2018-02-07 17:02:27 +01:00
}
2018-01-30 23:21:06 +01:00
return this . localContent ;
} catch ( err ) {
throw err ;
}
}
2018-01-30 16:59:27 +01:00
2018-02-14 13:55:06 +01:00
/ * *
* Refresh locally stored content
* /
2018-01-30 23:21:06 +01:00
static async refreshContent ( ) {
if ( ! this . localContent . length ) return this . loadAllContent ( ) ;
try {
2018-01-30 16:59:27 +01:00
await FileUtils . ensureDirectory ( this . contentPath ) ;
const directories = await FileUtils . listDirectory ( this . contentPath ) ;
for ( let dir of directories ) {
2018-01-30 23:21:06 +01:00
// If content is already loaded this should resolve.
if ( this . getContentByDirName ( dir ) ) continue ;
2018-01-30 16:59:27 +01:00
try {
2018-01-30 23:21:06 +01:00
// Load if not
2018-01-30 16:59:27 +01:00
await this . preloadContent ( dir ) ;
} catch ( err ) {
//We don't want every plugin/theme to fail loading when one does
Logger . err ( this . moduleName , err ) ;
}
}
2018-01-30 23:21:06 +01:00
for ( let content of this . localContent ) {
if ( directories . includes ( content . dirName ) ) continue ;
//Plugin/theme was deleted manually, stop it and remove any reference
this . unloadContent ( content ) ;
}
2018-01-30 16:59:27 +01:00
return this . localContent ;
2018-01-30 23:21:06 +01:00
2018-01-30 16:59:27 +01:00
} catch ( err ) {
throw err ;
}
}
2018-02-14 13:55:06 +01:00
/ * *
* Common loading procedure for loading content before passing it to the actual loader
* @ param { any } dirName Base directory for content
* @ param { any } reload Is content being reloaded
* @ param { any } index Index of content in { localContent }
* /
2018-01-30 16:59:27 +01:00
static async preloadContent ( dirName , reload = false , index ) {
try {
const contentPath = path . join ( this . contentPath , dirName ) ;
await FileUtils . directoryExists ( contentPath ) ;
if ( ! reload ) {
const loaded = this . localContent . find ( content => content . contentPath === contentPath ) ;
if ( loaded ) {
throw { 'message' : ` Attempted to load already loaded user content: ${ path } ` } ;
}
}
const readConfig = await this . readConfig ( contentPath ) ;
const mainPath = path . join ( contentPath , readConfig . main ) ;
2018-02-12 23:49:44 +01:00
readConfig . defaultConfig = readConfig . defaultConfig || [ ] ;
2018-01-30 16:59:27 +01:00
const userConfig = {
enabled : false ,
config : readConfig . defaultConfig
} ;
try {
const readUserConfig = await this . readUserConfig ( contentPath ) ;
2018-02-03 10:25:34 +01:00
userConfig . enabled = readUserConfig . enabled || false ;
2018-02-21 16:58:45 +01:00
userConfig . config = readConfig . defaultConfig . map ( category => {
let newCategory = readUserConfig . config . find ( c => c . category === category . category ) ;
2018-02-04 21:17:22 +01:00
// return userSet || config;
2018-02-21 16:58:45 +01:00
if ( ! newCategory ) newCategory = { settings : [ ] } ;
2018-02-04 21:17:22 +01:00
2018-02-21 16:58:45 +01:00
category . settings = category . settings . map ( setting => {
if ( setting . type === 'array' || setting . type === 'custom' ) setting . path = contentPath ;
const newSetting = newCategory . settings . find ( s => s . id === setting . id ) ;
if ( ! newSetting ) return setting ;
2018-02-04 21:17:22 +01:00
2018-02-21 16:58:45 +01:00
setting . value = newSetting . value ;
2018-02-04 21:17:22 +01:00
return setting ;
} ) ;
2018-02-21 16:58:45 +01:00
return category ;
2018-01-30 16:59:27 +01:00
} ) ;
2018-02-11 20:31:24 +01:00
userConfig . css = readUserConfig . css || null ;
2018-02-04 21:17:22 +01:00
// userConfig.config = readUserConfig.config;
2018-02-03 10:25:34 +01:00
} catch ( err ) { /*We don't care if this fails it either means that user config doesn't exist or there's something wrong with it so we revert to default config*/
2018-02-04 21:17:22 +01:00
2018-02-03 10:25:34 +01:00
}
2018-01-30 16:59:27 +01:00
const configs = {
defaultConfig : readConfig . defaultConfig ,
2018-02-15 18:09:06 +01:00
schemes : readConfig . configSchemes ,
2018-01-30 16:59:27 +01:00
userConfig
2018-02-21 16:58:45 +01:00
} ;
2018-01-30 16:59:27 +01:00
const paths = {
contentPath ,
dirName ,
mainPath
2018-02-21 16:58:45 +01:00
} ;
2018-01-30 16:59:27 +01:00
2018-02-14 09:05:34 +01:00
const content = await this . loadContent ( paths , configs , readConfig . info , readConfig . main , readConfig . dependencies ) ;
2018-01-31 09:17:15 +01:00
if ( reload ) this . localContent [ index ] = content ;
else this . localContent . push ( content ) ;
2018-01-30 16:59:27 +01:00
return content ;
} catch ( err ) {
throw err ;
}
}
2018-02-04 21:17:22 +01:00
2018-02-14 13:55:06 +01:00
/ * *
* Read content config file
* @ param { any } configPath Config file path
* /
2018-01-30 16:59:27 +01:00
static async readConfig ( configPath ) {
configPath = path . resolve ( configPath , 'config.json' ) ;
return FileUtils . readJsonFromFile ( configPath ) ;
}
2018-02-14 13:55:06 +01:00
/ * *
* Read content user config file
* @ param { any } configPath User config file path
* /
2018-01-30 16:59:27 +01:00
static async readUserConfig ( configPath ) {
configPath = path . resolve ( configPath , 'user.config.json' ) ;
return FileUtils . readJsonFromFile ( configPath ) ;
}
2018-02-14 13:55:06 +01:00
/ * *
* Wildcard content finder
* @ param { any } wild Content name | id | path | dirname
* /
2018-01-30 23:21:06 +01:00
//TODO make this nicer
static findContent ( wild ) {
let content = this . getContentByName ( wild ) ;
if ( content ) return content ;
content = this . getContentById ( wild ) ;
if ( content ) return content ;
content = this . getContentByPath ( wild ) ;
if ( content ) return content ;
return this . getContentByDirName ( wild ) ;
}
static getContentIndex ( content ) { return this . localContent . findIndex ( c => c === content ) }
static getContentByName ( name ) { return this . localContent . find ( c => c . name === name ) }
static getContentById ( id ) { return this . localContent . find ( c => c . id === id ) }
static getContentByPath ( path ) { return this . localContent . find ( c => c . contentPath === path ) }
static getContentByDirName ( dirName ) { return this . localContent . find ( c => c . dirName === dirName ) }
2018-02-14 13:55:06 +01:00
/ * *
* Wait for content to load
* @ param { any } content _id
* /
2018-02-12 23:49:44 +01:00
static waitForContent ( content _id ) {
return new Promise ( ( resolve , reject ) => {
const check = ( ) => {
const content = this . getContentById ( content _id ) ;
if ( content ) return resolve ( content ) ;
setTimeout ( check , 100 ) ;
} ;
check ( ) ;
} ) ;
}
2018-02-05 15:33:30 +01:00
}