manager progress
This commit is contained in:
parent
1b84d577db
commit
0ccf84f803
663
js/main.js
663
js/main.js
|
@ -2334,8 +2334,8 @@ __webpack_require__.r(__webpack_exports__);
|
|||
/* harmony import */ var _localstorage__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./localstorage */ "./src/localstorage.js");
|
||||
/* harmony import */ var _modules_core__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./modules/core */ "./src/modules/core.js");
|
||||
/* harmony import */ var _modules_pluginapi__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./modules/pluginapi */ "./src/modules/pluginapi.js");
|
||||
/* harmony import */ var _modules_pluginmanager__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ./modules/pluginmanager */ "./src/modules/pluginmanager.js");
|
||||
/* harmony import */ var _modules_thememanager__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ./modules/thememanager */ "./src/modules/thememanager.js");
|
||||
/* harmony import */ var _modules_pluginmanager2__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ./modules/pluginmanager2 */ "./src/modules/pluginmanager2.js");
|
||||
/* harmony import */ var _modules_thememanager2__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ./modules/thememanager2 */ "./src/modules/thememanager2.js");
|
||||
/* harmony import */ var _modules_oldstorage__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! ./modules/oldstorage */ "./src/modules/oldstorage.js");
|
||||
/* harmony import */ var _modules_emitter__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(/*! ./modules/emitter */ "./src/modules/emitter.js");
|
||||
|
||||
|
@ -2358,8 +2358,8 @@ window.settings = data__WEBPACK_IMPORTED_MODULE_0__["SettingsInfo"];
|
|||
window.settingsCookie = data__WEBPACK_IMPORTED_MODULE_0__["SettingsCookie"];
|
||||
window.pluginCookie = data__WEBPACK_IMPORTED_MODULE_0__["PluginCookie"];
|
||||
window.themeCookie = data__WEBPACK_IMPORTED_MODULE_0__["ThemeCookie"];
|
||||
window.pluginModule = _modules_pluginmanager__WEBPACK_IMPORTED_MODULE_4__["default"];
|
||||
window.themeModule = _modules_thememanager__WEBPACK_IMPORTED_MODULE_5__["default"];
|
||||
window.pluginModule = _modules_pluginmanager2__WEBPACK_IMPORTED_MODULE_4__["default"];
|
||||
window.themeModule = _modules_thememanager2__WEBPACK_IMPORTED_MODULE_5__["default"];
|
||||
window.bdthemes = data__WEBPACK_IMPORTED_MODULE_0__["Themes"];
|
||||
window.bdplugins = data__WEBPACK_IMPORTED_MODULE_0__["Plugins"];
|
||||
window.bdEmotes = data__WEBPACK_IMPORTED_MODULE_0__["Emotes"];
|
||||
|
@ -2915,6 +2915,283 @@ const originalCSSRequire = Module._extensions[".css"] ? Module._extensions[".css
|
|||
|
||||
/***/ }),
|
||||
|
||||
/***/ "./src/modules/contentmanager2.js":
|
||||
/*!****************************************!*\
|
||||
!*** ./src/modules/contentmanager2.js ***!
|
||||
\****************************************/
|
||||
/*! exports provided: default */
|
||||
/***/ (function(module, __webpack_exports__, __webpack_require__) {
|
||||
|
||||
"use strict";
|
||||
__webpack_require__.r(__webpack_exports__);
|
||||
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "default", function() { return ContentManager; });
|
||||
/* harmony import */ var _utilities__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./utilities */ "./src/modules/utilities.js");
|
||||
/* harmony import */ var _settingsmanager__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./settingsmanager */ "./src/modules/settingsmanager.js");
|
||||
/* harmony import */ var _emitter__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./emitter */ "./src/modules/emitter.js");
|
||||
/* harmony import */ var _datastore__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./datastore */ "./src/modules/datastore.js");
|
||||
/* harmony import */ var _structs_contenterror__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ../structs/contenterror */ "./src/structs/contenterror.js");
|
||||
/* harmony import */ var _structs_metaerror__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ../structs/metaerror */ "./src/structs/metaerror.js");
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
const path = __webpack_require__(/*! path */ "path");
|
||||
|
||||
const fs = __webpack_require__(/*! fs */ "fs");
|
||||
|
||||
const Module = __webpack_require__(/*! module */ "module").Module;
|
||||
|
||||
Module.globalPaths.push(path.resolve(__webpack_require__(/*! electron */ "electron").remote.app.getAppPath(), "node_modules"));
|
||||
const splitRegex = /[^\S\r\n]*?\n[^\S\r\n]*?\*[^\S\r\n]?/;
|
||||
const escapedAtRegex = /^\\@/;
|
||||
class ContentManager {
|
||||
get name() {
|
||||
return "";
|
||||
}
|
||||
|
||||
get moduleExtension() {
|
||||
return "";
|
||||
}
|
||||
|
||||
get extension() {
|
||||
return "";
|
||||
}
|
||||
|
||||
get contentFolder() {
|
||||
return "";
|
||||
}
|
||||
|
||||
get prefix() {
|
||||
return "content";
|
||||
}
|
||||
|
||||
get collection() {
|
||||
return "settings";
|
||||
}
|
||||
|
||||
get category() {
|
||||
return "content";
|
||||
}
|
||||
|
||||
get id() {
|
||||
return "autoReload";
|
||||
}
|
||||
|
||||
emit(event, ...args) {
|
||||
return _emitter__WEBPACK_IMPORTED_MODULE_2__["default"].emit(`${this.prefix}-${event}`, ...args);
|
||||
}
|
||||
|
||||
constructor() {
|
||||
this.timeCache = {};
|
||||
this.contentList = [];
|
||||
this.state = {};
|
||||
this.originalRequire = Module._extensions[this.moduleExtension];
|
||||
Module._extensions[this.moduleExtension] = this.getContentRequire();
|
||||
_settingsmanager__WEBPACK_IMPORTED_MODULE_1__["default"].on(this.collection, this.category, this.id, enabled => {
|
||||
if (enabled) this.watchContent();else this.unwatchContent();
|
||||
});
|
||||
}
|
||||
|
||||
loadState() {
|
||||
const saved = _datastore__WEBPACK_IMPORTED_MODULE_3__["default"].getData(`${this.prefix}s`);
|
||||
if (!saved) return;
|
||||
Object.assign(this.state, saved);
|
||||
}
|
||||
|
||||
saveState() {
|
||||
_datastore__WEBPACK_IMPORTED_MODULE_3__["default"].setData(`${this.prefix}s`, this.state);
|
||||
}
|
||||
|
||||
watchContent() {
|
||||
if (this.watcher) return _utilities__WEBPACK_IMPORTED_MODULE_0__["default"].err(this.name, "Already watching content.");
|
||||
_utilities__WEBPACK_IMPORTED_MODULE_0__["default"].log(this.name, "Starting to watch content.");
|
||||
this.watcher = fs.watch(this.contentFolder, {
|
||||
persistent: false
|
||||
}, async (eventType, filename) => {
|
||||
if (!eventType || !filename || !filename.endsWith(this.extension)) return;
|
||||
await new Promise(r => setTimeout(r, 50));
|
||||
|
||||
try {
|
||||
fs.statSync(path.resolve(this.contentFolder, filename));
|
||||
} catch (err) {
|
||||
if (err.code !== "ENOENT") return;
|
||||
delete this.timeCache[filename];
|
||||
this.unloadContent(filename, true);
|
||||
}
|
||||
|
||||
if (!fs.statSync(path.resolve(this.contentFolder, filename)).isFile()) return;
|
||||
const stats = fs.statSync(path.resolve(this.contentFolder, filename));
|
||||
if (!stats || !stats.mtime || !stats.mtime.getTime()) return;
|
||||
if (typeof stats.mtime.getTime() !== "number") return;
|
||||
if (this.timeCache[filename] == stats.mtime.getTime()) return;
|
||||
this.timeCache[filename] = stats.mtime.getTime();
|
||||
if (eventType == "rename") this.loadContent(filename, true);
|
||||
if (eventType == "change") this.reloadContent(filename, true);
|
||||
});
|
||||
}
|
||||
|
||||
unwatchContent() {
|
||||
if (!this.watcher) return _utilities__WEBPACK_IMPORTED_MODULE_0__["default"].err(this.name, "Was not watching content.");
|
||||
this.watcher.close();
|
||||
delete this.watcher;
|
||||
_utilities__WEBPACK_IMPORTED_MODULE_0__["default"].log(this.name, "No longer watching content.");
|
||||
}
|
||||
|
||||
extractMeta(content) {
|
||||
const firstLine = content.split("\n")[0];
|
||||
const hasOldMeta = firstLine.includes("//META");
|
||||
if (hasOldMeta) return this.parseOldMeta(content);
|
||||
const hasNewMeta = firstLine.includes("/**");
|
||||
if (hasNewMeta) return this.parseNewMeta(content);
|
||||
throw new _structs_metaerror__WEBPACK_IMPORTED_MODULE_5__["default"]("META was not found.");
|
||||
}
|
||||
|
||||
parseOldMeta(content) {
|
||||
const meta = content.split("\n")[0];
|
||||
const rawMeta = meta.substring(meta.lastIndexOf("//META") + 6, meta.lastIndexOf("*//"));
|
||||
if (meta.indexOf("META") < 0) throw new _structs_metaerror__WEBPACK_IMPORTED_MODULE_5__["default"]("META was not found.");
|
||||
if (!_utilities__WEBPACK_IMPORTED_MODULE_0__["default"].testJSON(rawMeta)) throw new _structs_metaerror__WEBPACK_IMPORTED_MODULE_5__["default"]("META could not be parsed.");
|
||||
const parsed = JSON.parse(rawMeta);
|
||||
if (!parsed.name) throw new _structs_metaerror__WEBPACK_IMPORTED_MODULE_5__["default"]("META missing name data.");
|
||||
return parsed;
|
||||
}
|
||||
|
||||
parseNewMeta(content) {
|
||||
const block = content.split("/**", 2)[1].split("*/", 1)[0];
|
||||
const stripped = block.replace(/^\s*\*\s?/mg, "");
|
||||
const out = {};
|
||||
let field = "";
|
||||
let accum = "";
|
||||
stripped.split("\n").forEach(line => {
|
||||
const fieldCandidate = line.split(/\s/g, 1)[0];
|
||||
|
||||
if (fieldCandidate.length > 1 && fieldCandidate.charAt(0) === "@") {
|
||||
out[field] = accum.trim();
|
||||
field = fieldCandidate.substr(1);
|
||||
accum = line.substr(fieldCandidate.length);
|
||||
} else {
|
||||
accum += " " + line.trim().replace("\\n", "\n").replace(/^\\@/, "@");
|
||||
}
|
||||
});
|
||||
out[field] = accum.trim();
|
||||
delete out[""];
|
||||
return out;
|
||||
}
|
||||
|
||||
parseNewMeta2(content) {
|
||||
const block = content.split("/**", 2)[1].split("*/", 1)[0];
|
||||
const out = {};
|
||||
let field = "";
|
||||
let accum = "";
|
||||
|
||||
for (const line of block.split(splitRegex)) {
|
||||
if (line.length === 0) continue;
|
||||
|
||||
if (line.charAt(0) === "@" && line.charAt(1) !== " ") {
|
||||
out[field] = accum;
|
||||
const l = line.indexOf(" ");
|
||||
field = line.substr(1, l - 1);
|
||||
accum = line.substr(l + 1);
|
||||
} else {
|
||||
accum += " " + line.replace("\\n", "\n").replace(escapedAtRegex, "@");
|
||||
}
|
||||
}
|
||||
|
||||
out[field] = accum.trim();
|
||||
delete out[""];
|
||||
return out;
|
||||
}
|
||||
|
||||
getContentRequire() {
|
||||
const self = this; // const baseFolder = this.contentFolder;
|
||||
|
||||
const originalRequire = this.originalRequire;
|
||||
return function (module, filename) {
|
||||
const possiblePath = path.resolve(self.contentFolder, path.basename(filename));
|
||||
if (!fs.existsSync(possiblePath) || filename !== fs.realpathSync(possiblePath)) return Reflect.apply(originalRequire, this, arguments);
|
||||
let content = fs.readFileSync(filename, "utf8");
|
||||
content = _utilities__WEBPACK_IMPORTED_MODULE_0__["default"].stripBOM(content);
|
||||
const meta = self.extractMeta(content);
|
||||
meta.id = meta.name;
|
||||
meta.filename = path.basename(filename);
|
||||
content = self.getContentModification(module, content, meta);
|
||||
|
||||
module._compile(content, filename);
|
||||
};
|
||||
} // Subclasses should use the return (if not ContentError) and push to this.contentList
|
||||
|
||||
|
||||
loadContent(filename) {
|
||||
if (typeof filename === "undefined") return;
|
||||
|
||||
try {
|
||||
require(path.resolve(this.contentFolder, filename));
|
||||
} catch (error) {
|
||||
return new _structs_contenterror__WEBPACK_IMPORTED_MODULE_4__["default"](filename, filename, "Could not be compiled.", {
|
||||
message: error.message,
|
||||
stack: error.stack
|
||||
});
|
||||
}
|
||||
|
||||
return require(path.resolve(this.contentFolder, filename));
|
||||
} // Subclasses should overload this and modify the content as needed to require() the file
|
||||
|
||||
|
||||
getContentModification(module, content) {
|
||||
return content;
|
||||
}
|
||||
|
||||
unloadContent(idOrFileOrContent) {
|
||||
const content = typeof idOrFileOrContent == "string" ? this.contentList.find(c => c.id == idOrFileOrContent || c.filename == idOrFileOrContent) : idOrFileOrContent;
|
||||
if (!content) return false;
|
||||
delete require.cache[require.resolve(path.resolve(this.contentFolder, content.filename))];
|
||||
this.contentList.splice(this.contentList.indexOf(content), 1);
|
||||
return true;
|
||||
}
|
||||
|
||||
isLoaded(idOrFile) {
|
||||
const content = this.contentList.find(c => c.id == idOrFile || c.filename == idOrFile);
|
||||
if (!content) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
reloadContent(filename, fromWatcher) {
|
||||
const didUnload = this.unloadContent(filename, fromWatcher);
|
||||
if (!didUnload) return didUnload;
|
||||
return this.loadContent(filename, fromWatcher);
|
||||
}
|
||||
|
||||
loadNewContent() {
|
||||
const files = fs.readdirSync(this.contentFolder);
|
||||
const removed = this.contentList.filter(t => !files.includes(t.filename)).map(c => c.id);
|
||||
const added = files.filter(f => !this.contentList.find(t => t.filename == f) && f.endsWith(this.extension) && fs.statSync(path.resolve(this.contentFolder, f)).isFile());
|
||||
return {
|
||||
added,
|
||||
removed
|
||||
};
|
||||
}
|
||||
|
||||
loadAllContent() {
|
||||
const errors = [];
|
||||
const files = fs.readdirSync(this.contentFolder);
|
||||
|
||||
for (const filename of files) {
|
||||
if (!fs.statSync(path.resolve(this.contentFolder, filename)).isFile() || !filename.endsWith(this.extension)) continue;
|
||||
const content = this.loadContent(filename);
|
||||
if (content instanceof _structs_contenterror__WEBPACK_IMPORTED_MODULE_4__["default"]) errors.push(content);
|
||||
}
|
||||
|
||||
if (_settingsmanager__WEBPACK_IMPORTED_MODULE_1__["default"].get(this.collection, this.category, this.id)) this.watchContent();
|
||||
return errors;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/***/ }),
|
||||
|
||||
/***/ "./src/modules/core.js":
|
||||
/*!*****************************!*\
|
||||
!*** ./src/modules/core.js ***!
|
||||
|
@ -2927,8 +3204,8 @@ __webpack_require__.r(__webpack_exports__);
|
|||
/* harmony import */ var _bdv2__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./bdv2 */ "./src/modules/bdv2.js");
|
||||
/* harmony import */ var _utilities__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./utilities */ "./src/modules/utilities.js");
|
||||
/* harmony import */ var data__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! data */ "./src/data/data.js");
|
||||
/* harmony import */ var _pluginmanager__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./pluginmanager */ "./src/modules/pluginmanager.js");
|
||||
/* harmony import */ var _thememanager__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ./thememanager */ "./src/modules/thememanager.js");
|
||||
/* harmony import */ var _pluginmanager2__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./pluginmanager2 */ "./src/modules/pluginmanager2.js");
|
||||
/* harmony import */ var _thememanager2__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ./thememanager2 */ "./src/modules/thememanager2.js");
|
||||
/* harmony import */ var _settingsmanager__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ./settingsmanager */ "./src/modules/settingsmanager.js");
|
||||
/* harmony import */ var builtins__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! builtins */ "./src/builtins/builtins.js");
|
||||
/* harmony import */ var ui__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(/*! ui */ "./src/ui/ui.js");
|
||||
|
@ -2981,9 +3258,9 @@ Core.prototype.init = async function () {
|
|||
for (const module in builtins__WEBPACK_IMPORTED_MODULE_6__) builtins__WEBPACK_IMPORTED_MODULE_6__[module].initialize();
|
||||
|
||||
_utilities__WEBPACK_IMPORTED_MODULE_1__["default"].log("Startup", "Loading Plugins");
|
||||
const pluginErrors = _pluginmanager__WEBPACK_IMPORTED_MODULE_3__["default"].loadPlugins();
|
||||
const pluginErrors = _pluginmanager2__WEBPACK_IMPORTED_MODULE_3__["default"].loadAllContent();
|
||||
_utilities__WEBPACK_IMPORTED_MODULE_1__["default"].log("Startup", "Loading Themes");
|
||||
const themeErrors = _thememanager__WEBPACK_IMPORTED_MODULE_4__["default"].loadThemes();
|
||||
const themeErrors = _thememanager2__WEBPACK_IMPORTED_MODULE_4__["default"].loadAllContent();
|
||||
$("#customcss").detach().appendTo(document.head); // PublicServers.initialize();
|
||||
// EmoteModule.autoCapitalize();
|
||||
|
||||
|
@ -3023,8 +3300,7 @@ Core.prototype.injectExternals = async function () {
|
|||
Core.prototype.initObserver = function () {
|
||||
const mainObserver = new MutationObserver(mutations => {
|
||||
for (let i = 0, mlen = mutations.length; i < mlen; i++) {
|
||||
const mutation = mutations[i];
|
||||
if (typeof _pluginmanager__WEBPACK_IMPORTED_MODULE_3__["default"] !== "undefined") _pluginmanager__WEBPACK_IMPORTED_MODULE_3__["default"].rawObserver(mutation); // if there was nothing added, skip
|
||||
const mutation = mutations[i]; // if there was nothing added, skip
|
||||
|
||||
if (!mutation.addedNodes.length || !(mutation.addedNodes[0] instanceof Element)) continue;
|
||||
const node = mutation.addedNodes[0];
|
||||
|
@ -3854,6 +4130,264 @@ PluginModule.prototype.rawObserver = function (e) {
|
|||
|
||||
/***/ }),
|
||||
|
||||
/***/ "./src/modules/pluginmanager2.js":
|
||||
/*!***************************************!*\
|
||||
!*** ./src/modules/pluginmanager2.js ***!
|
||||
\***************************************/
|
||||
/*! exports provided: default */
|
||||
/***/ (function(module, __webpack_exports__, __webpack_require__) {
|
||||
|
||||
"use strict";
|
||||
__webpack_require__.r(__webpack_exports__);
|
||||
/* harmony import */ var data__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! data */ "./src/data/data.js");
|
||||
/* harmony import */ var _contentmanager2__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./contentmanager2 */ "./src/modules/contentmanager2.js");
|
||||
/* harmony import */ var _utilities__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./utilities */ "./src/modules/utilities.js");
|
||||
/* harmony import */ var ui__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ui */ "./src/ui/ui.js");
|
||||
/* harmony import */ var _structs_contenterror__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ../structs/contenterror */ "./src/structs/contenterror.js");
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
const path = __webpack_require__(/*! path */ "path");
|
||||
|
||||
const electronRemote = __webpack_require__(/*! electron */ "electron").remote;
|
||||
|
||||
/* harmony default export */ __webpack_exports__["default"] = (new class PluginManager extends _contentmanager2__WEBPACK_IMPORTED_MODULE_1__["default"] {
|
||||
get name() {
|
||||
return "PluginManager";
|
||||
}
|
||||
|
||||
get moduleExtension() {
|
||||
return ".js";
|
||||
}
|
||||
|
||||
get extension() {
|
||||
return ".plugin.js";
|
||||
}
|
||||
|
||||
get contentFolder() {
|
||||
return path.resolve(data__WEBPACK_IMPORTED_MODULE_0__["Config"].dataPath, "plugins");
|
||||
}
|
||||
|
||||
get prefix() {
|
||||
return "plugin";
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.onSwitch = this.onSwitch.bind(this);
|
||||
this.observer = new MutationObserver(mutations => {
|
||||
for (let i = 0, mlen = mutations.length; i < mlen; i++) {
|
||||
this.onMutation(mutations[i]);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
loadContent(filename, fromWatcher) {
|
||||
const error = this.loadPlugin(filename, fromWatcher);
|
||||
if (!fromWatcher) return error;
|
||||
if (error) ui__WEBPACK_IMPORTED_MODULE_3__["Modals"].showContentErrors({
|
||||
plugins: [error]
|
||||
});
|
||||
}
|
||||
|
||||
unloadContent(idOrFileOrContent) {
|
||||
return this.unloadPlugin(idOrFileOrContent);
|
||||
}
|
||||
|
||||
getContentModification(module, content, meta) {
|
||||
module._compile(content, module.filename);
|
||||
|
||||
const didExport = !_utilities__WEBPACK_IMPORTED_MODULE_2__["default"].isEmpty(module.exports);
|
||||
|
||||
if (didExport) {
|
||||
meta.type = module.exports;
|
||||
module.exports = meta;
|
||||
return "";
|
||||
}
|
||||
|
||||
content += `\nmodule.exports = ${JSON.stringify(meta)};\nmodule.exports.type = ${meta.exports || meta.name};`;
|
||||
return content;
|
||||
}
|
||||
|
||||
loadPlugin(filename) {
|
||||
const content = super.loadContent(filename);
|
||||
if (content instanceof _structs_contenterror__WEBPACK_IMPORTED_MODULE_4__["default"]) return content;
|
||||
console.log(content);
|
||||
if (!content.type) return new _structs_contenterror__WEBPACK_IMPORTED_MODULE_4__["default"](filename, filename, "Plugin had no exports", {
|
||||
message: "Plugin had no exports or no name property.",
|
||||
stack: ""
|
||||
});
|
||||
|
||||
try {
|
||||
const thePlugin = new content.type();
|
||||
const pluginName = thePlugin.getName() || thePlugin.name || content.id;
|
||||
if (this.contentList.find(c => c.id == pluginName)) return new _structs_contenterror__WEBPACK_IMPORTED_MODULE_4__["default"](pluginName, filename, `There is already a plugin with name ${pluginName}`);
|
||||
content.plugin = thePlugin;
|
||||
content.name = pluginName;
|
||||
content.author = content.author || thePlugin.getAuthor();
|
||||
content.description = content.description || thePlugin.getDescription();
|
||||
content.version = content.version || thePlugin.getVersion();
|
||||
this.contentList.push(content);
|
||||
|
||||
try {
|
||||
if (typeof content.plugin.load == "function") content.plugin.load();
|
||||
} catch (error) {
|
||||
this.state[content.id] = false;
|
||||
return new _structs_contenterror__WEBPACK_IMPORTED_MODULE_4__["default"](pluginName, filename, "load() could not be fired.", {
|
||||
message: error.message,
|
||||
stack: error.stack
|
||||
});
|
||||
}
|
||||
|
||||
this.emit("loaded", content.id);
|
||||
if (!this.state[content.id]) return this.state[content.id] = false;
|
||||
return this.startPlugin(content);
|
||||
} catch (error) {
|
||||
return new _structs_contenterror__WEBPACK_IMPORTED_MODULE_4__["default"](filename, filename, "Could not be constructed.", {
|
||||
message: error.message,
|
||||
stack: error.stack
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
unloadPlugin(idOrFileOrContent) {
|
||||
const content = typeof idOrFileOrContent == "string" ? this.contentList.find(c => c.id == idOrFileOrContent || c.filename == idOrFileOrContent) : idOrFileOrContent;
|
||||
if (!content) return false;
|
||||
if (this.state[content.id]) this.disablePlugin(content);
|
||||
super.unloadContent(content);
|
||||
ui__WEBPACK_IMPORTED_MODULE_3__["Toasts"].success(`${content.name} was unloaded.`);
|
||||
this.emit("unloaded", content.id);
|
||||
return true;
|
||||
}
|
||||
|
||||
reloadPlugin(filename) {
|
||||
this.reloadContent(filename);
|
||||
}
|
||||
|
||||
startPlugin(idOrContent) {
|
||||
const content = typeof idOrContent == "string" ? this.contentList.find(p => p.id == idOrContent) : idOrContent;
|
||||
if (!content) return;
|
||||
const plugin = content.plugin;
|
||||
|
||||
try {
|
||||
plugin.start();
|
||||
this.emit("started", content.id);
|
||||
ui__WEBPACK_IMPORTED_MODULE_3__["Toasts"].show(`${content.name} v${content.version} has started.`);
|
||||
} catch (err) {
|
||||
this.state[content.id] = false;
|
||||
ui__WEBPACK_IMPORTED_MODULE_3__["Toasts"].error(`${content.name} v${content.version} could not be started.`);
|
||||
_utilities__WEBPACK_IMPORTED_MODULE_2__["default"].err("Plugins", content.name + " could not be started.", err);
|
||||
return new _structs_contenterror__WEBPACK_IMPORTED_MODULE_4__["default"](content.name, content.filename, "start() could not be fired.", {
|
||||
message: err.message,
|
||||
stack: err.stack
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
stopPlugin(idOrContent) {
|
||||
const content = typeof idOrContent == "string" ? this.contentList.find(p => p.id == idOrContent) : idOrContent;
|
||||
if (!content) return;
|
||||
const plugin = content.plugin;
|
||||
|
||||
try {
|
||||
plugin.stop();
|
||||
this.emit("stopped", content.id);
|
||||
ui__WEBPACK_IMPORTED_MODULE_3__["Toasts"].show(`${content.name} v${content.version} has stopped.`);
|
||||
} catch (err) {
|
||||
this.state[content.id] = false;
|
||||
ui__WEBPACK_IMPORTED_MODULE_3__["Toasts"].error(`${content.name} v${content.version} could not be stopped.`);
|
||||
_utilities__WEBPACK_IMPORTED_MODULE_2__["default"].err("Plugins", content.name + " could not be stopped.", err);
|
||||
return new _structs_contenterror__WEBPACK_IMPORTED_MODULE_4__["default"](content.name, content.filename, "stop() could not be fired.", {
|
||||
message: err.message,
|
||||
stack: err.stack
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
updatePluginList() {
|
||||
const results = this.loadNewContent();
|
||||
|
||||
for (const filename of results.added) this.loadPlugin(filename);
|
||||
|
||||
for (const name of results.removed) this.unloadPlugin(name);
|
||||
}
|
||||
|
||||
enablePlugin(idOrContent) {
|
||||
const content = typeof idOrContent == "string" ? this.contentList.find(p => p.id == idOrContent) : idOrContent;
|
||||
if (!content) return;
|
||||
if (this.state[content.id]) return;
|
||||
this.state[content.id] = true;
|
||||
this.startPlugin(content);
|
||||
this.saveState();
|
||||
}
|
||||
|
||||
disablePlugin(idOrContent) {
|
||||
const content = typeof idOrContent == "string" ? this.contentList.find(p => p.id == idOrContent) : idOrContent;
|
||||
if (!content) return;
|
||||
if (!this.state[content.id]) return;
|
||||
this.state[content.id] = false;
|
||||
this.stopPlugin(content);
|
||||
this.saveState();
|
||||
}
|
||||
|
||||
togglePlugin(id) {
|
||||
if (this.state[id]) this.disablePlugin(id);else this.enablePlugin(id);
|
||||
}
|
||||
|
||||
loadAllPlugins() {
|
||||
this.loadState();
|
||||
const errors = this.loadAllContent();
|
||||
this.saveState();
|
||||
return errors;
|
||||
}
|
||||
|
||||
setupFunctions() {
|
||||
electronRemote.getCurrentWebContents().on("did-navigate-in-page", this.onSwitch.bind(this));
|
||||
this.observer.observe(document, {
|
||||
childList: true,
|
||||
subtree: true
|
||||
});
|
||||
}
|
||||
|
||||
onSwitch() {
|
||||
this.emit("page-switch");
|
||||
|
||||
for (let i = 0; i < this.contentList.length; i++) {
|
||||
const plugin = this.contentList[i].plugin;
|
||||
if (!this.state[this.contentList[i].id]) continue;
|
||||
|
||||
if (typeof plugin.onSwitch === "function") {
|
||||
try {
|
||||
plugin.onSwitch();
|
||||
} catch (err) {
|
||||
_utilities__WEBPACK_IMPORTED_MODULE_2__["default"].err("Plugins", "Unable to fire onSwitch for " + this.contentList[i].name + ".", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onMutation(mutation) {
|
||||
for (let i = 0; i < this.contentList.length; i++) {
|
||||
const plugin = this.contentList[i].plugin;
|
||||
if (!this.state[this.contentList[i].id]) continue;
|
||||
|
||||
if (typeof plugin.observer === "function") {
|
||||
try {
|
||||
plugin.observer(mutation);
|
||||
} catch (err) {
|
||||
_utilities__WEBPACK_IMPORTED_MODULE_2__["default"].err("Plugins", "Unable to fire observer for " + this.contentList[i].name + ".", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}());
|
||||
|
||||
/***/ }),
|
||||
|
||||
/***/ "./src/modules/settingsmanager.js":
|
||||
/*!****************************************!*\
|
||||
!*** ./src/modules/settingsmanager.js ***!
|
||||
|
@ -4282,6 +4816,61 @@ ThemeModule.prototype.saveThemeData = function () {
|
|||
|
||||
/***/ }),
|
||||
|
||||
/***/ "./src/modules/thememanager2.js":
|
||||
/*!**************************************!*\
|
||||
!*** ./src/modules/thememanager2.js ***!
|
||||
\**************************************/
|
||||
/*! exports provided: default */
|
||||
/***/ (function(module, __webpack_exports__, __webpack_require__) {
|
||||
|
||||
"use strict";
|
||||
__webpack_require__.r(__webpack_exports__);
|
||||
/* harmony import */ var data__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! data */ "./src/data/data.js");
|
||||
/* harmony import */ var _contentmanager2__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./contentmanager2 */ "./src/modules/contentmanager2.js");
|
||||
/* harmony import */ var _structs_contenterror__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ../structs/contenterror */ "./src/structs/contenterror.js");
|
||||
|
||||
|
||||
|
||||
|
||||
const path = __webpack_require__(/*! path */ "path");
|
||||
|
||||
/* harmony default export */ __webpack_exports__["default"] = (new class ThemeManager extends _contentmanager2__WEBPACK_IMPORTED_MODULE_1__["default"] {
|
||||
get name() {
|
||||
return "ThemeManager";
|
||||
}
|
||||
|
||||
get moduleExtension() {
|
||||
return ".css";
|
||||
}
|
||||
|
||||
get extension() {
|
||||
return ".theme.css";
|
||||
}
|
||||
|
||||
get contentFolder() {
|
||||
return path.resolve(data__WEBPACK_IMPORTED_MODULE_0__["Config"].dataPath, "themes");
|
||||
}
|
||||
|
||||
get prefix() {
|
||||
return "theme";
|
||||
}
|
||||
|
||||
loadContent(filename) {
|
||||
const content = super.loadContent(filename);
|
||||
if (content instanceof _structs_contenterror__WEBPACK_IMPORTED_MODULE_2__["default"]) return content;
|
||||
console.log(content);
|
||||
this.contentList.push(content);
|
||||
}
|
||||
|
||||
getContentModification(module, content, meta) {
|
||||
meta.css = content.split("\n").slice(1).join("\n");
|
||||
return `module.exports = ${JSON.stringify(meta)};`;
|
||||
}
|
||||
|
||||
}());
|
||||
|
||||
/***/ }),
|
||||
|
||||
/***/ "./src/modules/utilities.js":
|
||||
/*!**********************************!*\
|
||||
!*** ./src/modules/utilities.js ***!
|
||||
|
@ -4462,6 +5051,18 @@ class Utilities {
|
|||
childList: true
|
||||
});
|
||||
}
|
||||
|
||||
static isEmpty(obj) {
|
||||
if (obj == null || obj == undefined || obj == "") return true;
|
||||
if (typeof obj !== "object") return false;
|
||||
if (Array.isArray(obj)) return obj.length == 0;
|
||||
|
||||
for (const key in obj) {
|
||||
if (obj.hasOwnProperty(key)) return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
/**
|
||||
* Generates an automatically memoizing version of an object.
|
||||
* @author Zerebos
|
||||
|
@ -5360,6 +5961,48 @@ class BuiltinModule {
|
|||
|
||||
/***/ }),
|
||||
|
||||
/***/ "./src/structs/contenterror.js":
|
||||
/*!*************************************!*\
|
||||
!*** ./src/structs/contenterror.js ***!
|
||||
\*************************************/
|
||||
/*! exports provided: default */
|
||||
/***/ (function(module, __webpack_exports__, __webpack_require__) {
|
||||
|
||||
"use strict";
|
||||
__webpack_require__.r(__webpack_exports__);
|
||||
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "default", function() { return ContentError; });
|
||||
class ContentError extends Error {
|
||||
constructor(name, filename, message, error) {
|
||||
super(message);
|
||||
this.name = name;
|
||||
this.file = filename;
|
||||
this.error = error;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/***/ }),
|
||||
|
||||
/***/ "./src/structs/metaerror.js":
|
||||
/*!**********************************!*\
|
||||
!*** ./src/structs/metaerror.js ***!
|
||||
\**********************************/
|
||||
/*! exports provided: default */
|
||||
/***/ (function(module, __webpack_exports__, __webpack_require__) {
|
||||
|
||||
"use strict";
|
||||
__webpack_require__.r(__webpack_exports__);
|
||||
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "default", function() { return MetaError; });
|
||||
class MetaError extends Error {
|
||||
constructor(message) {
|
||||
super(message);
|
||||
this.name = "MetaError";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/***/ }),
|
||||
|
||||
/***/ "./src/ui/emote.js":
|
||||
/*!*************************!*\
|
||||
!*** ./src/ui/emote.js ***!
|
||||
|
|
|
@ -2,8 +2,8 @@ import {SettingsCookie, SettingsInfo, Config, PluginCookie, ThemeCookie, Plugins
|
|||
import proxyLocalStorage from "./localstorage";
|
||||
import Core from "./modules/core";
|
||||
import BdApi from "./modules/pluginapi";
|
||||
import PluginManager from "./modules/pluginmanager";
|
||||
import ThemeManager from "./modules/thememanager";
|
||||
import PluginManager from "./modules/pluginmanager2";
|
||||
import ThemeManager from "./modules/thememanager2";
|
||||
import {bdPluginStorage} from "./modules/oldstorage";
|
||||
import Events from "./modules/emitter";
|
||||
|
||||
|
|
|
@ -1,50 +1,57 @@
|
|||
import Utilities from "./utilities";
|
||||
import Settings from "./settingsmanager";
|
||||
import Events from "./emitter";
|
||||
import DataStore from "./datastore";
|
||||
import ContentError from "../structs/contenterror";
|
||||
import MetaError from "../structs/metaerror";
|
||||
|
||||
const path = require("path");
|
||||
const fs = require("fs");
|
||||
const Module = require("module").Module;
|
||||
Module.globalPaths.push(path.resolve(require("electron").remote.app.getAppPath(), "node_modules"));
|
||||
class MetaError extends Error {
|
||||
constructor(message) {
|
||||
super(message);
|
||||
this.name = "MetaError";
|
||||
}
|
||||
}
|
||||
|
||||
class ContentError extends Error {
|
||||
constructor(name, filename, message, error) {
|
||||
super(message);
|
||||
this.name = name;
|
||||
this.file = filename;
|
||||
this.error = error;
|
||||
}
|
||||
}
|
||||
const splitRegex = /[^\S\r\n]*?\n[^\S\r\n]*?\*[^\S\r\n]?/;
|
||||
const escapedAtRegex = /^\\@/;
|
||||
|
||||
export default new class ContentManager {
|
||||
export default class ContentManager {
|
||||
|
||||
get fileExtension() {return "";}
|
||||
get name() {return "";}
|
||||
get moduleExtension() {return "";}
|
||||
get extension() {return "";}
|
||||
get contentFolder() {return "";}
|
||||
get prefix() {return "content";}
|
||||
get collection() {return "settings";}
|
||||
get category() {return "content";}
|
||||
get id() {return "autoReload";}
|
||||
getContentModification(content) {return content;}
|
||||
emit(event, ...args) {return Events.emit(`${this.prefix}-${event}`, ...args);}
|
||||
|
||||
constructor() {
|
||||
this.timeCache = {};
|
||||
this.content = [];
|
||||
this.originalRequire = Module._extensions[this.fileExtension];
|
||||
Module._extensions[this.fileExtension] = this.getContentRequire();
|
||||
this.contentList = [];
|
||||
this.state = {};
|
||||
this.originalRequire = Module._extensions[this.moduleExtension];
|
||||
Module._extensions[this.moduleExtension] = this.getContentRequire();
|
||||
Settings.on(this.collection, this.category, this.id, (enabled) => {
|
||||
if (enabled) this.watchContent();
|
||||
else this.unwatchContent();
|
||||
});
|
||||
if (Settings.get(this.collection, this.category, this.id)) this.watchContent();
|
||||
}
|
||||
|
||||
|
||||
loadState() {
|
||||
const saved = DataStore.getData(`${this.prefix}s`);
|
||||
console.log(saved);
|
||||
if (!saved) return;
|
||||
Object.assign(this.state, saved);
|
||||
}
|
||||
|
||||
saveState() {
|
||||
DataStore.setData(`${this.prefix}s`, this.state);
|
||||
}
|
||||
|
||||
watchContent() {
|
||||
if (this.watcher) return;
|
||||
if (this.watcher) return Utilities.err(this.name, "Already watching content.");
|
||||
Utilities.log(this.name, "Starting to watch content.");
|
||||
this.watcher = fs.watch(this.contentFolder, {persistent: false}, async (eventType, filename) => {
|
||||
if (!eventType || !filename || !filename.endsWith(this.extension)) return;
|
||||
await new Promise(r => setTimeout(r, 50));
|
||||
|
@ -52,7 +59,7 @@ export default new class ContentManager {
|
|||
catch (err) {
|
||||
if (err.code !== "ENOENT") return;
|
||||
delete this.timeCache[filename];
|
||||
this.unloadContent(filename);
|
||||
this.unloadContent(filename, true);
|
||||
}
|
||||
if (!fs.statSync(path.resolve(this.contentFolder, filename)).isFile()) return;
|
||||
const stats = fs.statSync(path.resolve(this.contentFolder, filename));
|
||||
|
@ -60,18 +67,28 @@ export default new class ContentManager {
|
|||
if (typeof(stats.mtime.getTime()) !== "number") return;
|
||||
if (this.timeCache[filename] == stats.mtime.getTime()) return;
|
||||
this.timeCache[filename] = stats.mtime.getTime();
|
||||
if (eventType == "rename") this.loadContent(filename);
|
||||
if (eventType == "change") this.reloadContent(filename);
|
||||
if (eventType == "rename") this.loadContent(filename, true);
|
||||
if (eventType == "change") this.reloadContent(filename, true);
|
||||
});
|
||||
}
|
||||
|
||||
unwatchContent() {
|
||||
if (!this.watcher) return;
|
||||
if (!this.watcher) return Utilities.err(this.name, "Was not watching content.");
|
||||
this.watcher.close();
|
||||
delete this.watcher;
|
||||
Utilities.log(this.name, "No longer watching content.");
|
||||
}
|
||||
|
||||
extractMeta(content) {
|
||||
const firstLine = content.split("\n")[0];
|
||||
const hasOldMeta = firstLine.includes("//META");
|
||||
if (hasOldMeta) return this.parseOldMeta(content);
|
||||
const hasNewMeta = firstLine.includes("/**");
|
||||
if (hasNewMeta) return this.parseNewMeta(content);
|
||||
throw new MetaError("META was not found.");
|
||||
}
|
||||
|
||||
parseOldMeta(content) {
|
||||
const meta = content.split("\n")[0];
|
||||
const rawMeta = meta.substring(meta.lastIndexOf("//META") + 6, meta.lastIndexOf("*//"));
|
||||
if (meta.indexOf("META") < 0) throw new MetaError("META was not found.");
|
||||
|
@ -82,68 +99,150 @@ export default new class ContentManager {
|
|||
return parsed;
|
||||
}
|
||||
|
||||
parseNewMeta(content) {
|
||||
const block = content.split("/**", 2)[1].split("*/", 1)[0];
|
||||
const stripped = block.replace(/^\s*\*\s?/mg, "");
|
||||
const out = {};
|
||||
let field = "";
|
||||
let accum = "";
|
||||
stripped.split("\n").forEach(line => {
|
||||
const fieldCandidate = line.split(/\s/g, 1)[0];
|
||||
if (fieldCandidate.length > 1 && fieldCandidate.charAt(0) === "@") {
|
||||
out[field] = accum.trim();
|
||||
field = fieldCandidate.substr(1);
|
||||
accum = line.substr(fieldCandidate.length);
|
||||
}
|
||||
else {
|
||||
accum += " " + line.trim().replace("\\n", "\n").replace(/^\\@/, "@");
|
||||
}
|
||||
});
|
||||
out[field] = accum.trim();
|
||||
delete out[""];
|
||||
return out;
|
||||
}
|
||||
|
||||
parseNewMeta2(content) {
|
||||
const block = content.split("/**", 2)[1].split("*/", 1)[0];
|
||||
const out = {};
|
||||
let field = "";
|
||||
let accum = "";
|
||||
for (const line of block.split(splitRegex)) {
|
||||
if (line.length === 0) continue;
|
||||
if (line.charAt(0) === "@" && line.charAt(1) !== " ") {
|
||||
out[field] = accum;
|
||||
const l = line.indexOf(" ");
|
||||
field = line.substr(1, l - 1);
|
||||
accum = line.substr(l + 1);
|
||||
}
|
||||
else {
|
||||
accum += " " + line.replace("\\n", "\n").replace(escapedAtRegex, "@");
|
||||
}
|
||||
}
|
||||
out[field] = accum.trim();
|
||||
delete out[""];
|
||||
return out;
|
||||
}
|
||||
|
||||
getContentRequire() {
|
||||
const self = this;
|
||||
const baseFolder = this.contentFolder;
|
||||
// const baseFolder = this.contentFolder;
|
||||
const originalRequire = this.originalRequire;
|
||||
return function(module, filename) {
|
||||
const possiblePath = path.resolve(baseFolder, path.basename(filename));
|
||||
const possiblePath = path.resolve(self.contentFolder, path.basename(filename));
|
||||
if (!fs.existsSync(possiblePath) || filename !== fs.realpathSync(possiblePath)) return Reflect.apply(originalRequire, this, arguments);
|
||||
let content = fs.readFileSync(filename, "utf8");
|
||||
content = Utilities.stripBOM(content);
|
||||
const meta = self.extractMeta(content);
|
||||
meta.id = meta.name;
|
||||
meta.filename = path.basename(filename);
|
||||
content = self.getContentModification(content, meta);
|
||||
content = self.getContentModification(module, content, meta);
|
||||
module._compile(content, filename);
|
||||
};
|
||||
}
|
||||
|
||||
// Subclasses should use the return (if not ContentError) and push to this.contentList
|
||||
loadContent(filename, type) {
|
||||
if (typeof(filename) === "undefined" || typeof(type) === "undefined") return;
|
||||
loadContent(filename) {
|
||||
if (typeof(filename) === "undefined") return;
|
||||
try {__non_webpack_require__(path.resolve(this.contentFolder, filename));}
|
||||
catch (error) {return new ContentError(filename, filename, "Could not be compiled.", {message: error.message, stack: error.stack});}
|
||||
return __non_webpack_require__(path.resolve(this.contentFolder, filename));
|
||||
}
|
||||
|
||||
unloadContent(idOrFile) {
|
||||
if (typeof(filename) === "undefined") return;
|
||||
const content = this.content.find(c => c.id == idOrFile || c.filename == idOrFile);
|
||||
// Subclasses should overload this and modify the content as needed to require() the file
|
||||
getContentModification(module, content) {return content;}
|
||||
|
||||
unloadContent(idOrFileOrContent, fromWatcher) {
|
||||
const content = typeof(idOrFileOrContent) == "string" ? this.contentList.find(c => c.id == idOrFileOrContent || c.filename == idOrFileOrContent) : idOrFileOrContent;
|
||||
if (!content) return false;
|
||||
delete __non_webpack_require__.cache[__non_webpack_require__.resolve(path.resolve(this.contentFolder, content.file))];
|
||||
this.content.splice(this.content.indexOf(content), 1);
|
||||
if (this.state[content.id]) this.disableContent(content, fromWatcher);
|
||||
delete __non_webpack_require__.cache[__non_webpack_require__.resolve(path.resolve(this.contentFolder, content.filename))];
|
||||
this.contentList.splice(this.contentList.indexOf(content), 1);
|
||||
this.emit("unloaded", content.id);
|
||||
Toasts.success(`${content.name} was unloaded.`);
|
||||
return true;
|
||||
}
|
||||
|
||||
isLoaded(idOrFile) {
|
||||
const content = this.content.find(c => c.id == idOrFile || c.filename == idOrFile);
|
||||
const content = this.contentList.find(c => c.id == idOrFile || c.filename == idOrFile);
|
||||
if (!content) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
reloadContent(filename, type) {
|
||||
const didUnload = this.unloadContent(filename, type);
|
||||
reloadContent(filename, fromWatcher) {
|
||||
const didUnload = this.unloadContent(filename, fromWatcher);
|
||||
if (!didUnload) return didUnload;
|
||||
return this.loadContent(filename, type);
|
||||
return this.loadContent(filename, fromWatcher);
|
||||
}
|
||||
|
||||
enableContent(idOrContent, fromWatcher = false) {
|
||||
const content = typeof(idOrContent) == "string" ? this.contentList.find(p => p.id == idOrContent) : idOrContent;
|
||||
if (!content) return;
|
||||
if (this.state[content.id]) return;
|
||||
this.state[content.id] = true;
|
||||
this.startContent(content);
|
||||
if (!fromWatcher) this.saveState();
|
||||
}
|
||||
|
||||
disableContent(idOrContent, fromWatcher = false) {
|
||||
const content = typeof(idOrContent) == "string" ? this.contentList.find(p => p.id == idOrContent) : idOrContent;
|
||||
if (!content) return;
|
||||
if (!this.state[content.id]) return;
|
||||
this.state[content.id] = false;
|
||||
this.stopContent(content);
|
||||
if (!fromWatcher) this.saveState();
|
||||
}
|
||||
|
||||
toggleContent(id) {
|
||||
if (this.state[id]) this.disableContent(id);
|
||||
else this.enableContent(id);
|
||||
}
|
||||
|
||||
loadNewContent() {
|
||||
const files = fs.readdirSync(this.contentFolder);
|
||||
const removed = this.content.filter(t => !files.includes(t.filename)).map(c => c.id);
|
||||
const added = files.filter(f => !this.content.find(t => t.filename == f) && f.endsWith(this.extension) && fs.statSync(path.resolve(this.contentFolder, f)).isFile());
|
||||
const removed = this.contentList.filter(t => !files.includes(t.filename)).map(c => c.id);
|
||||
const added = files.filter(f => !this.contentList.find(t => t.filename == f) && f.endsWith(this.extension) && fs.statSync(path.resolve(this.contentFolder, f)).isFile());
|
||||
return {added, removed};
|
||||
}
|
||||
|
||||
loadAllContent(type) {
|
||||
updateList() {
|
||||
const results = this.loadNewContent();
|
||||
for (const filename of results.added) this.loadContent(filename);
|
||||
for (const name of results.removed) this.unloadContent(name);
|
||||
}
|
||||
|
||||
loadAllContent() {
|
||||
this.loadState();
|
||||
const errors = [];
|
||||
const files = fs.readdirSync(this.contentFolder);
|
||||
|
||||
for (const filename of files) {
|
||||
if (!fs.statSync(path.resolve(this.contentFolder, filename)).isFile() || !filename.endsWith(this.extension)) continue;
|
||||
const content = this.loadContent(filename, type);
|
||||
const content = this.loadContent(filename);
|
||||
if (content instanceof ContentError) errors.push(content);
|
||||
}
|
||||
|
||||
this.saveState();
|
||||
if (Settings.get(this.collection, this.category, this.id)) this.watchContent();
|
||||
return errors;
|
||||
}
|
||||
};
|
||||
}
|
|
@ -3,8 +3,8 @@ import Utilities from "./utilities";
|
|||
import {Config} from "data";
|
||||
// import EmoteModule from "./emotes";
|
||||
// import QuickEmoteMenu from "../builtins/emotemenu";
|
||||
import PluginManager from "./pluginmanager";
|
||||
import ThemeManager from "./thememanager";
|
||||
import PluginManager from "./pluginmanager2";
|
||||
import ThemeManager from "./thememanager2";
|
||||
import Settings from "./settingsmanager";
|
||||
import * as Builtins from "builtins";
|
||||
import {Modals} from "ui";
|
||||
|
@ -48,10 +48,10 @@ Core.prototype.init = async function() {
|
|||
for (const module in Builtins) Builtins[module].initialize();
|
||||
|
||||
Utilities.log("Startup", "Loading Plugins");
|
||||
const pluginErrors = PluginManager.loadPlugins();
|
||||
const pluginErrors = PluginManager.loadAllContent();
|
||||
|
||||
Utilities.log("Startup", "Loading Themes");
|
||||
const themeErrors = ThemeManager.loadThemes();
|
||||
const themeErrors = ThemeManager.loadAllContent();
|
||||
|
||||
$("#customcss").detach().appendTo(document.head);
|
||||
|
||||
|
@ -93,7 +93,6 @@ Core.prototype.initObserver = function () {
|
|||
|
||||
for (let i = 0, mlen = mutations.length; i < mlen; i++) {
|
||||
const mutation = mutations[i];
|
||||
if (typeof PluginManager !== "undefined") PluginManager.rawObserver(mutation);
|
||||
|
||||
// if there was nothing added, skip
|
||||
if (!mutation.addedNodes.length || !(mutation.addedNodes[0] instanceof Element)) continue;
|
||||
|
|
|
@ -0,0 +1,165 @@
|
|||
import {Config} from "data";
|
||||
import ContentManager from "./contentmanager2";
|
||||
import Utilities from "./utilities";
|
||||
import {Toasts, Modals} from "ui";
|
||||
import ContentError from "../structs/contenterror";
|
||||
|
||||
const path = require("path");
|
||||
const electronRemote = require("electron").remote;
|
||||
|
||||
export default new class PluginManager extends ContentManager {
|
||||
get name() {return "PluginManager";}
|
||||
get moduleExtension() {return ".js";}
|
||||
get extension() {return ".plugin.js";}
|
||||
get contentFolder() {return path.resolve(Config.dataPath, "plugins");}
|
||||
get prefix() {return "plugin";}
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.onSwitch = this.onSwitch.bind(this);
|
||||
this.observer = new MutationObserver((mutations) => {
|
||||
for (let i = 0, mlen = mutations.length; i < mlen; i++) {
|
||||
this.onMutation(mutations[i]);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/* Aliases */
|
||||
updatePluginList() {return this.updateList();}
|
||||
loadAllPlugins() {return this.loadAllContent();}
|
||||
enablePlugin(idOrContent, fromWatcher = false) {return this.enableContent(idOrContent, fromWatcher);}
|
||||
disablePlugin(idOrContent, fromWatcher = false) {return this.disableContent(idOrContent, fromWatcher);}
|
||||
togglePlugin(id) {return this.toggleContent(id);}
|
||||
|
||||
loadContent(filename, fromWatcher) {
|
||||
const error = this.loadPlugin(filename, fromWatcher);
|
||||
if (!fromWatcher) return error;
|
||||
if (error) Modals.showContentErrors({plugins: [error]});
|
||||
}
|
||||
|
||||
unloadContent(idOrFileOrContent, fromWatcher) {return this.unloadPlugin(idOrFileOrContent, fromWatcher);}
|
||||
|
||||
/* Overrides */
|
||||
getContentModification(module, content, meta) {
|
||||
module._compile(content, module.filename);
|
||||
const didExport = !Utilities.isEmpty(module.exports);
|
||||
if (didExport) {
|
||||
meta.type = module.exports;
|
||||
module.exports = meta;
|
||||
return "";
|
||||
}
|
||||
content += `\nmodule.exports = ${JSON.stringify(meta)};\nmodule.exports.type = ${meta.exports || meta.name};`;
|
||||
return content;
|
||||
}
|
||||
|
||||
loadPlugin(filename) {
|
||||
const content = super.loadContent(filename);
|
||||
if (content instanceof ContentError) return content;
|
||||
console.log(content);
|
||||
if (!content.type) return new ContentError(filename, filename, "Plugin had no exports", {message: "Plugin had no exports or no name property.", stack: ""});
|
||||
try {
|
||||
const thePlugin = new content.type();
|
||||
if (this.contentList.find(c => c.id == content.id)) return new ContentError(content.id, filename, `There is already a plugin with name ${content.id}`);
|
||||
content.plugin = thePlugin;
|
||||
content.name = content.name || thePlugin.getName();
|
||||
content.author = content.author || thePlugin.getAuthor();
|
||||
content.description = content.description || thePlugin.getDescription();
|
||||
content.version = content.version || thePlugin.getVersion();
|
||||
this.contentList.push(content);
|
||||
try {
|
||||
if (typeof(content.plugin.load) == "function") content.plugin.load();
|
||||
}
|
||||
catch (error) {
|
||||
this.state[content.id] = false;
|
||||
return new ContentError(content.name, filename, "load() could not be fired.", {message: error.message, stack: error.stack});
|
||||
}
|
||||
this.emit("loaded", content.id);
|
||||
Toasts.success(`${content.name} was loaded.`);
|
||||
|
||||
if (!this.state[content.id]) return this.state[content.id] = false;
|
||||
return this.startPlugin(content);
|
||||
}
|
||||
catch (error) {return new ContentError(filename, filename, "Could not be constructed.", {message: error.message, stack: error.stack});}
|
||||
}
|
||||
|
||||
unloadPlugin(idOrFileOrContent, fromWatcher) {
|
||||
const content = typeof(idOrFileOrContent) == "string" ? this.contentList.find(c => c.id == idOrFileOrContent || c.filename == idOrFileOrContent) : idOrFileOrContent;
|
||||
if (!content) return false;
|
||||
if (this.state[content.id]) this.disablePlugin(content, fromWatcher);
|
||||
super.unloadContent(content);
|
||||
Toasts.success(`${content.name} was unloaded.`);
|
||||
this.emit("unloaded", content.id);
|
||||
return true;
|
||||
}
|
||||
|
||||
reloadPlugin(filename) {
|
||||
this.reloadContent(filename);
|
||||
}
|
||||
|
||||
startContent(id) {return this.startPlugin(id);}
|
||||
stopContent(id) {return this.stopPlugin(id);}
|
||||
startPlugin(idOrContent) {
|
||||
const content = typeof(idOrContent) == "string" ? this.contentList.find(p => p.id == idOrContent) : idOrContent;
|
||||
if (!content) return;
|
||||
const plugin = content.plugin;
|
||||
try {
|
||||
plugin.start();
|
||||
this.emit("started", content.id);
|
||||
Toasts.show(`${content.name} v${content.version} has started.`);
|
||||
}
|
||||
catch (err) {
|
||||
this.state[content.id] = false;
|
||||
Toasts.error(`${content.name} v${content.version} could not be started.`);
|
||||
Utilities.err("Plugins", content.name + " could not be started.", err);
|
||||
return new ContentError(content.name, content.filename, "start() could not be fired.", {message: err.message, stack: err.stack});
|
||||
}
|
||||
}
|
||||
|
||||
stopPlugin(idOrContent) {
|
||||
const content = typeof(idOrContent) == "string" ? this.contentList.find(p => p.id == idOrContent) : idOrContent;
|
||||
if (!content) return;
|
||||
const plugin = content.plugin;
|
||||
try {
|
||||
plugin.stop();
|
||||
this.emit("stopped", content.id);
|
||||
Toasts.show(`${content.name} v${content.version} has stopped.`);
|
||||
}
|
||||
catch (err) {
|
||||
this.state[content.id] = false;
|
||||
Toasts.error(`${content.name} v${content.version} could not be stopped.`);
|
||||
Utilities.err("Plugins", content.name + " could not be stopped.", err);
|
||||
return new ContentError(content.name, content.filename, "stop() could not be fired.", {message: err.message, stack: err.stack});
|
||||
}
|
||||
}
|
||||
|
||||
setupFunctions() {
|
||||
electronRemote.getCurrentWebContents().on("did-navigate-in-page", this.onSwitch.bind(this));
|
||||
this.observer.observe(document, {
|
||||
childList: true,
|
||||
subtree: true
|
||||
});
|
||||
}
|
||||
|
||||
onSwitch() {
|
||||
this.emit("page-switch");
|
||||
for (let i = 0; i < this.contentList.length; i++) {
|
||||
const plugin = this.contentList[i].plugin;
|
||||
if (!this.state[this.contentList[i].id]) continue;
|
||||
if (typeof(plugin.onSwitch) === "function") {
|
||||
try { plugin.onSwitch(); }
|
||||
catch (err) { Utilities.err("Plugins", "Unable to fire onSwitch for " + this.contentList[i].name + ".", err); }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onMutation(mutation) {
|
||||
for (let i = 0; i < this.contentList.length; i++) {
|
||||
const plugin = this.contentList[i].plugin;
|
||||
if (!this.state[this.contentList[i].id]) continue;
|
||||
if (typeof plugin.observer === "function") {
|
||||
try { plugin.observer(mutation); }
|
||||
catch (err) { Utilities.err("Plugins", "Unable to fire observer for " + this.contentList[i].name + ".", err); }
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
|
@ -0,0 +1,119 @@
|
|||
import {Config} from "data";
|
||||
import ContentManager from "./contentmanager2";
|
||||
import Utilities from "./utilities";
|
||||
import ContentError from "../structs/contenterror";
|
||||
import {Toasts, Modals} from "ui";
|
||||
|
||||
const path = require("path");
|
||||
|
||||
export default new class ThemeManager extends ContentManager {
|
||||
get name() {return "ThemeManager";}
|
||||
get moduleExtension() {return ".css";}
|
||||
get extension() {return ".theme.css";}
|
||||
get contentFolder() {return path.resolve(Config.dataPath, "themes");}
|
||||
get prefix() {return "theme";}
|
||||
|
||||
/* Aliases */
|
||||
updateThemeList() {return this.updateList();}
|
||||
loadAllThemes() {return this.loadAllContent();}
|
||||
enableTheme(idOrContent, fromWatcher = false) {return this.enableContent(idOrContent, fromWatcher);}
|
||||
disableTheme(idOrContent, fromWatcher = false) {return this.disableContent(idOrContent, fromWatcher);}
|
||||
toggleTheme(id) {return this.toggleContent(id);}
|
||||
unloadTheme(idOrFileOrContent, fromWatcher) {return this.unloadContent(idOrFileOrContent, fromWatcher);}
|
||||
|
||||
loadContent(filename, fromWatcher) {
|
||||
const error = this.loadTheme(filename, fromWatcher);
|
||||
if (!fromWatcher) return error;
|
||||
if (error) Modals.showContentErrors({themes: [error]});
|
||||
}
|
||||
|
||||
/* Overrides */
|
||||
getContentModification(module, content, meta) {
|
||||
meta.css = content.split("\n").slice(1).join("\n");
|
||||
return `module.exports = ${JSON.stringify(meta)};`;
|
||||
}
|
||||
|
||||
loadTheme(filename) {
|
||||
const content = super.loadContent(filename);
|
||||
if (content instanceof ContentError) return content;
|
||||
console.log(content);
|
||||
if (this.contentList.find(c => c.id == content.name)) return new ContentError(content.name, filename, `There is already a plugin with name ${content.name}`);
|
||||
this.contentList.push(content);
|
||||
Toasts.success(`${content.name} v${content.version} was loaded.`);
|
||||
this.emit("loaded", content.name);
|
||||
|
||||
if (!this.state[content.id]) return this.state[content.id] = false;
|
||||
return this.addTheme(content);
|
||||
}
|
||||
|
||||
reloadTheme(filename) {
|
||||
this.reloadContent(filename);
|
||||
}
|
||||
|
||||
startContent(id) {return this.addTheme(id);}
|
||||
stopContent(id) {return this.removeTheme(id);}
|
||||
addTheme() {
|
||||
|
||||
}
|
||||
|
||||
removeTheme() {
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
function ThemeModule() {
|
||||
|
||||
}
|
||||
|
||||
ThemeModule.prototype.enableTheme = function(theme, reload = false) {
|
||||
ThemeCookie[theme] = true;
|
||||
this.saveThemeData();
|
||||
$("head").append($("<style>", {id: Utilities.escapeID(theme), text: unescape(Themes[theme].css)}));
|
||||
if (!reload) Toasts.show(`${Themes[theme].name} v${Themes[theme].version} has been applied.`);
|
||||
};
|
||||
|
||||
ThemeModule.prototype.disableTheme = function(theme, reload = false) {
|
||||
ThemeCookie[theme] = false;
|
||||
this.saveThemeData();
|
||||
$(`#${Utilities.escapeID(Themes[theme].name)}`).remove();
|
||||
if (!reload) Toasts.show(`${Themes[theme].name} v${Themes[theme].version} has been disabled.`);
|
||||
};
|
||||
|
||||
ThemeModule.prototype.toggleTheme = function(theme) {
|
||||
if (ThemeCookie[theme]) this.disableTheme(theme);
|
||||
else this.enableTheme(theme);
|
||||
};
|
||||
|
||||
ThemeModule.prototype.unloadTheme = function(filenameOrName) {
|
||||
const bdtheme = Object.values(Themes).find(p => p.filename == filenameOrName) || Themes[filenameOrName];
|
||||
if (!bdtheme) return;
|
||||
const theme = bdtheme.name;
|
||||
if (ThemeCookie[theme]) this.disableTheme(theme, true);
|
||||
const error = ContentManager.unloadContent(Themes[theme].filename, "theme");
|
||||
delete Themes[theme];
|
||||
if (error) {
|
||||
Modals.showContentErrors({themes: [error]});
|
||||
Toasts.show(`${theme} could not be unloaded. It may have not been loaded yet.`, {type: "error"});
|
||||
return Utilities.err("ContentManager", `${theme} could not be unloaded. It may have not been loaded yet.`, error);
|
||||
}
|
||||
Utilities.log("ContentManager", `${theme} was unloaded.`);
|
||||
Toasts.show(`${theme} was unloaded.`, {type: "success"});
|
||||
Emitter.dispatch("theme-unloaded", theme);
|
||||
};
|
||||
|
||||
ThemeModule.prototype.reloadTheme = function(filenameOrName) {
|
||||
const bdtheme = Object.values(Themes).find(p => p.filename == filenameOrName) || Themes[filenameOrName];
|
||||
if (!bdtheme) return this.loadTheme(filenameOrName);
|
||||
const theme = bdtheme.name;
|
||||
const error = ContentManager.reloadContent(Themes[theme].filename, "theme");
|
||||
if (ThemeCookie[theme]) this.disableTheme(theme, true), this.enableTheme(theme, true);
|
||||
if (error) {
|
||||
Modals.showContentErrors({themes: [error]});
|
||||
Toasts.show(`${theme} could not be reloaded.`, {type: "error"});
|
||||
return Utilities.err("ContentManager", `${theme} could not be reloaded.`, error);
|
||||
}
|
||||
Utilities.log("ContentManager", `${theme} v${Themes[theme].version} was reloaded.`);
|
||||
Toasts.show(`${theme} v${Themes[theme].version} was reloaded.`, {type: "success"});
|
||||
Emitter.dispatch("theme-reloaded", theme);
|
||||
};
|
|
@ -144,6 +144,16 @@ export default class Utilities {
|
|||
observer.observe(document.body, {subtree: true, childList: true});
|
||||
}
|
||||
|
||||
static isEmpty(obj) {
|
||||
if (obj == null || obj == undefined || obj == "") return true;
|
||||
if (typeof(obj) !== "object") return false;
|
||||
if (Array.isArray(obj)) return obj.length == 0;
|
||||
for (const key in obj) {
|
||||
if (obj.hasOwnProperty(key)) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates an automatically memoizing version of an object.
|
||||
* @author Zerebos
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
export default class ContentError extends Error {
|
||||
constructor(name, filename, message, error) {
|
||||
super(message);
|
||||
this.name = name;
|
||||
this.file = filename;
|
||||
this.error = error;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
export default class MetaError extends Error {
|
||||
constructor(message) {
|
||||
super(message);
|
||||
this.name = "MetaError";
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue