Zipped Addons & Bugfixes
This commit is contained in:
parent
b0001f181e
commit
f03138f897
|
@ -44,6 +44,7 @@ importers:
|
|||
babel-plugin-module-resolver: ^4.1.0
|
||||
circular-dependency-plugin: ^5.2.2
|
||||
css-loader: ^6.5.1
|
||||
fflate: ^0.7.4
|
||||
postcss: ^8.4.5
|
||||
postcss-cli: ^9.1.0
|
||||
postcss-csso: ^6.0.0
|
||||
|
@ -52,6 +53,8 @@ importers:
|
|||
stylelint: ^14.3.0
|
||||
stylelint-config-standard: ^24.0.0
|
||||
webpack: ^5.73.0
|
||||
dependencies:
|
||||
fflate: 0.7.4
|
||||
devDependencies:
|
||||
'@babel/core': 7.18.6
|
||||
'@babel/preset-env': 7.18.6_@babel+core@7.18.6
|
||||
|
@ -2544,6 +2547,10 @@ packages:
|
|||
reusify: 1.0.4
|
||||
dev: true
|
||||
|
||||
/fflate/0.7.4:
|
||||
resolution: {integrity: sha512-5u2V/CDW15QM1XbbgS+0DfPxVB+jUKhWEKuuFuHncbk3tEEqzmoXL+2KyOFuKGqOnmdIy0/davWF1CkuwtibCw==}
|
||||
dev: false
|
||||
|
||||
/file-entry-cache/6.0.1:
|
||||
resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==}
|
||||
engines: {node: ^10.12.0 || >=12.0.0}
|
||||
|
|
|
@ -30,7 +30,10 @@
|
|||
"postcss-easy-import": "^4.0.0",
|
||||
"postcss-loader": "^6.2.1",
|
||||
"stylelint": "^14.3.0",
|
||||
"webpack": "^5.73.0",
|
||||
"stylelint-config-standard": "^24.0.0"
|
||||
"stylelint-config-standard": "^24.0.0",
|
||||
"webpack": "^5.73.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"fflate": "^0.7.4"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@ import DiscordModules from "./discordmodules";
|
|||
import Strings from "./strings";
|
||||
import AddonEditor from "../ui/misc/addoneditor";
|
||||
import FloatingWindows from "../ui/floatingwindows";
|
||||
import {unzipSync} from "fflate";
|
||||
|
||||
const React = DiscordModules.React;
|
||||
|
||||
|
@ -26,14 +27,17 @@ const stripBOM = function(fileContent) {
|
|||
return fileContent;
|
||||
};
|
||||
|
||||
const convertToString = TextDecoder.prototype.decode.bind(new TextDecoder());
|
||||
|
||||
export default class AddonManager {
|
||||
|
||||
get name() {return "";}
|
||||
get extension() {return "";}
|
||||
get extensions() {return [];}
|
||||
get duplicatePattern() {return /./;}
|
||||
get addonFolder() {return "";}
|
||||
get language() {return "";}
|
||||
get prefix() {return "addon";}
|
||||
get langExtension() {return "";}
|
||||
emit(event, ...args) {return Events.emit(`${this.prefix}-${event}`, ...args);}
|
||||
|
||||
constructor() {
|
||||
|
@ -172,25 +176,75 @@ export default class AddonManager {
|
|||
return out;
|
||||
}
|
||||
|
||||
assignMeta({meta, filename, fileContent, zip = false} = {}) {
|
||||
const stats = fs.statSync(filename);
|
||||
|
||||
if (!meta.author) meta.author = Strings.Addons.unknownAuthor;
|
||||
if (!meta.version) meta.version = "???";
|
||||
if (!meta.description) meta.description = Strings.Addons.noDescription;
|
||||
meta.id = meta.name || path.basename(filename);
|
||||
meta.slug = path.basename(filename).replace(this.extensions[zip ? 1 : 0], "").replace(/ /g, "-");
|
||||
meta.filename = path.basename(filename);
|
||||
meta.added = stats.atimeMs;
|
||||
meta.modified = stats.mtimeMs;
|
||||
meta.size = stats.size;
|
||||
meta.fileContent = fileContent;
|
||||
meta.zip = zip;
|
||||
}
|
||||
|
||||
requireZipped(filename) {
|
||||
const content = fs.readFileSync(filename, "");
|
||||
const contents = unzipSync(content);
|
||||
const entryFile = `index${this.langExtension}`;
|
||||
if (!contents["config.json"]) throw new AddonError(filename, filename, Strings.Addons.metaNotFound, {message: "", stack: ""}, this.prefix);
|
||||
if (!contents[entryFile]) throw new AddonError(filename, filename, "Zipped addon missing index file.", {message: `Missing ${entryFile} file.`, stack: ""}, this.prefix);
|
||||
const addon = JSON.parse(convertToString(contents["config.json"]));
|
||||
|
||||
this.assignMeta({
|
||||
meta: addon,
|
||||
fileContent: convertToString(contents[entryFile]),
|
||||
zip: true,
|
||||
filename: filename
|
||||
});
|
||||
|
||||
return addon;
|
||||
}
|
||||
|
||||
// Subclasses should overload this and modify the addon using the fileContent as needed to "require()"" the file
|
||||
requireAddon(filename) {
|
||||
requireOld(filename) {
|
||||
let fileContent = fs.readFileSync(filename, "utf8");
|
||||
fileContent = stripBOM(fileContent);
|
||||
const stats = fs.statSync(filename);
|
||||
const addon = this.extractMeta(fileContent, path.basename(filename));
|
||||
if (!addon.author) addon.author = Strings.Addons.unknownAuthor;
|
||||
if (!addon.version) addon.version = "???";
|
||||
if (!addon.description) addon.description = Strings.Addons.noDescription;
|
||||
// if (!addon.name || !addon.author || !addon.description || !addon.version) return new AddonError(addon.name || path.basename(filename), filename, "Addon is missing name, author, description, or version", {message: "Addon must provide name, author, description, and version.", stack: ""}, this.prefix);
|
||||
addon.id = addon.name || path.basename(filename);
|
||||
addon.slug = path.basename(filename).replace(this.extension, "").replace(/ /g, "-");
|
||||
addon.filename = path.basename(filename);
|
||||
addon.added = stats.atimeMs;
|
||||
addon.modified = stats.mtimeMs;
|
||||
addon.size = stats.size;
|
||||
addon.fileContent = fileContent;
|
||||
if (this.addonList.find(c => c.id == addon.id)) throw new AddonError(addon.name, filename, Strings.Addons.alreadyExists.format({type: this.prefix, name: addon.name}), this.prefix);
|
||||
|
||||
this.assignMeta({
|
||||
meta: addon,
|
||||
fileContent: fileContent,
|
||||
filename: filename
|
||||
});
|
||||
|
||||
return addon;
|
||||
}
|
||||
|
||||
requireAddon(filename) {
|
||||
const ext = path.extname(filename);
|
||||
const oldExt = path.extname(this.extensions[0]);
|
||||
|
||||
const addon = (() => {
|
||||
switch (ext) {
|
||||
case ".zip": return this.requireZipped(filename);
|
||||
case oldExt: return this.requireOld(filename);
|
||||
default: {
|
||||
const name = path.basename(filename);
|
||||
throw new AddonError(name, filename, "Unsupported file format: " + ext, {stack: "", message: ""}, this.prefix);
|
||||
}
|
||||
}
|
||||
})();
|
||||
|
||||
if (this.addonList.find(c => c.id == addon.id))
|
||||
throw new AddonError(addon.name, filename, Strings.Addons.alreadyExists.format({type: this.prefix, name: addon.name}), this.prefix);
|
||||
|
||||
this.addonList.push(addon);
|
||||
|
||||
return addon;
|
||||
}
|
||||
|
||||
|
@ -201,14 +255,17 @@ export default class AddonManager {
|
|||
try {
|
||||
addon = this.requireAddon(path.resolve(this.addonFolder, filename));
|
||||
}
|
||||
catch (e) {
|
||||
catch (error) {
|
||||
const partialAddon = this.addonList.find(c => c.filename == filename);
|
||||
if (partialAddon) {
|
||||
partialAddon.partial = true;
|
||||
this.state[partialAddon.id] = false;
|
||||
this.emit("loaded", partialAddon);
|
||||
}
|
||||
return e;
|
||||
|
||||
Logger.stacktrace(this.name, `Unable to compile ${path.basename(filename)}.`, error);
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
|
||||
|
@ -220,7 +277,7 @@ export default class AddonManager {
|
|||
return error;
|
||||
}
|
||||
|
||||
if (shouldToast) Toasts.success(Strings.Addons.wasUnloaded.format({name: addon.name, version: addon.version}));
|
||||
if (shouldToast) Toasts.success(Strings.Addons.wasLoaded.format({name: addon.name, version: addon.version}));
|
||||
this.emit("loaded", addon);
|
||||
|
||||
if (!this.state[addon.id]) return this.state[addon.id] = false;
|
||||
|
@ -309,7 +366,7 @@ export default class AddonManager {
|
|||
if (!stats || !stats.isFile()) continue;
|
||||
this.timeCache[filename] = stats.mtime.getTime();
|
||||
|
||||
if (!filename.endsWith(this.extension)) {
|
||||
if (!this.extensions.some(ext => filename.endsWith(ext))) {
|
||||
// Lets check to see if this filename has the duplicated file pattern `something(1).ext`
|
||||
const match = filename.match(this.duplicatePattern);
|
||||
if (!match) continue;
|
||||
|
@ -394,4 +451,4 @@ export default class AddonManager {
|
|||
confirmationText: Strings.Addons.confirmationText.format({name: addon.name})
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,11 +23,12 @@ if (typeof(module.exports) !== "function") {
|
|||
|
||||
export default new class PluginManager extends AddonManager {
|
||||
get name() {return "PluginManager";}
|
||||
get extension() {return ".plugin.js";}
|
||||
get duplicatePattern() {return /\.plugin\s?\([0-9]+\)\.js/;}
|
||||
get extensions() {return [".plugin.js", ".plugin.zip"];}
|
||||
get duplicatePattern() {return /\.plugin\s?\([0-9]+\)\.(js|zip)/;}
|
||||
get addonFolder() {return path.resolve(Config.dataPath, "plugins");}
|
||||
get prefix() {return "plugin";}
|
||||
get language() {return "javascript";}
|
||||
get langExtension() {return ".js";}
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
@ -149,7 +150,7 @@ export default new class PluginManager extends AddonManager {
|
|||
this.state[addon.id] = false;
|
||||
Toasts.error(Strings.Addons.couldNotStart.format({name: addon.name, version: addon.version}));
|
||||
Logger.stacktrace(this.name, `${addon.name} v${addon.version} could not be started.`, err);
|
||||
return new AddonError(addon.name, addon.filename, Strings.Addons.enabled.format({method: "start()"}), {message: err.message, stack: err.stack}, this.prefix);
|
||||
return new AddonError(addon.name, addon.filename, Strings.Addons.methodError.format({method: "start()"}), {message: err.message, stack: err.stack}, this.prefix);
|
||||
}
|
||||
this.emit("started", addon.id);
|
||||
Toasts.show(Strings.Addons.enabled.format({name: addon.name, version: addon.version}));
|
||||
|
@ -165,8 +166,8 @@ export default new class PluginManager extends AddonManager {
|
|||
catch (err) {
|
||||
this.state[addon.id] = false;
|
||||
Toasts.error(Strings.Addons.couldNotStop.format({name: addon.name, version: addon.version}));
|
||||
Logger.stacktrace(this.name, `${addon.name} v${addon.version} could not be started.`, err);
|
||||
return new AddonError(addon.name, addon.filename, Strings.Addons.enabled.format({method: "stop()"}), {message: err.message, stack: err.stack}, this.prefix);
|
||||
Logger.stacktrace(this.name, `${addon.name} v${addon.version} could not be stopped.`, err);
|
||||
return new AddonError(addon.name, addon.filename, Strings.Addons.methodError.format({method: "stop()"}), {message: err.message, stack: err.stack}, this.prefix);
|
||||
}
|
||||
this.emit("stopped", addon.id);
|
||||
Toasts.show(Strings.Addons.disabled.format({name: addon.name, version: addon.version}));
|
||||
|
@ -207,4 +208,4 @@ export default new class PluginManager extends AddonManager {
|
|||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
|
|
@ -13,11 +13,12 @@ const path = require("path");
|
|||
|
||||
export default new class ThemeManager extends AddonManager {
|
||||
get name() {return "ThemeManager";}
|
||||
get extension() {return ".theme.css";}
|
||||
get duplicatePattern() {return /\.theme\s?\([0-9]+\)\.css/;}
|
||||
get extension() {return [".theme.css", ".theme.zip"];}
|
||||
get duplicatePattern() {return /\.theme\s?\([0-9]+\)\.(css|zip)/;}
|
||||
get addonFolder() {return path.resolve(Config.dataPath, "themes");}
|
||||
get prefix() {return "theme";}
|
||||
get language() {return "css";}
|
||||
get langExtension() {return ".css";}
|
||||
|
||||
initialize() {
|
||||
const errors = super.initialize();
|
||||
|
@ -85,4 +86,4 @@ export default new class ThemeManager extends AddonManager {
|
|||
DOMManager.removeTheme(addon.slug + "-theme-container");
|
||||
Toasts.show(Strings.Addons.disabled.format({name: addon.name, version: addon.version}));
|
||||
}
|
||||
};
|
||||
};
|
||||
|
|
|
@ -148,7 +148,7 @@ export default function AddonList({prefix, type, title, folder, addonList, addon
|
|||
return sorted.map(addon => {
|
||||
const hasSettings = addon.instance && typeof(addon.instance.getSettingsPanel) === "function";
|
||||
const getSettings = hasSettings && addon.instance.getSettingsPanel.bind(addon.instance);
|
||||
return <ErrorBoundary><AddonCard disabled={addon.partial} type={type} editAddon={() => triggerEdit(addon.id)} deleteAddon={() => triggerDelete(addon.id)} key={addon.id} enabled={addonState[addon.id]} addon={addon} onChange={onChange} reload={reload} hasSettings={hasSettings} getSettingsPanel={getSettings} /></ErrorBoundary>;
|
||||
return <ErrorBoundary><AddonCard disabled={addon.partial} type={type} editAddon={!addon.zip && (() => triggerEdit(addon.id))} deleteAddon={() => triggerDelete(addon.id)} key={addon.id} enabled={addonState[addon.id]} addon={addon} onChange={onChange} reload={reload} hasSettings={hasSettings} getSettingsPanel={getSettings} /></ErrorBoundary>;
|
||||
});
|
||||
}, [addonList, addonState, onChange, reload, triggerDelete, triggerEdit, type, sort, ascending, query, forced]); // eslint-disable-line react-hooks/exhaustive-deps
|
||||
|
||||
|
|
|
@ -30,7 +30,8 @@ module.exports = {
|
|||
modules$: path.resolve("src", "modules"),
|
||||
data$: path.resolve("src", "modules"),
|
||||
builtins$: path.resolve("src", "modules"),
|
||||
common: path.resolve(__dirname, "..", "common")
|
||||
common: path.resolve(__dirname, "..", "common"),
|
||||
"fflate": require.resolve("fflate")
|
||||
}
|
||||
},
|
||||
module: {
|
||||
|
@ -64,4 +65,4 @@ module.exports = {
|
|||
})
|
||||
]
|
||||
}
|
||||
};
|
||||
};
|
||||
|
|
Loading…
Reference in New Issue