@open-discord-bots/framework 0.0.2 → 0.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -1,4 +1,6 @@
1
1
  export * as api from "./api/api";
2
2
  export * as utilities from "./api/utils";
3
+ export * as cli from "./cli/cli";
3
4
  export { loadDumpCommand } from "./startup/dump";
4
5
  export { loadAllPlugins } from "./startup/pluginLauncher";
6
+ export { frameworkStartup } from "./startup/compilation";
package/dist/index.js CHANGED
@@ -33,10 +33,13 @@ var __importStar = (this && this.__importStar) || (function () {
33
33
  };
34
34
  })();
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
- exports.loadAllPlugins = exports.loadDumpCommand = exports.utilities = exports.api = void 0;
36
+ exports.frameworkStartup = exports.loadAllPlugins = exports.loadDumpCommand = exports.cli = exports.utilities = exports.api = void 0;
37
37
  exports.api = __importStar(require("./api/api"));
38
38
  exports.utilities = __importStar(require("./api/utils"));
39
+ exports.cli = __importStar(require("./cli/cli"));
39
40
  var dump_1 = require("./startup/dump");
40
41
  Object.defineProperty(exports, "loadDumpCommand", { enumerable: true, get: function () { return dump_1.loadDumpCommand; } });
41
42
  var pluginLauncher_1 = require("./startup/pluginLauncher");
42
43
  Object.defineProperty(exports, "loadAllPlugins", { enumerable: true, get: function () { return pluginLauncher_1.loadAllPlugins; } });
44
+ var compilation_1 = require("./startup/compilation");
45
+ Object.defineProperty(exports, "frameworkStartup", { enumerable: true, get: function () { return compilation_1.frameworkStartup; } });
@@ -0,0 +1,2 @@
1
+ import type { ODProjectType } from "../api/api";
2
+ export declare function frameworkStartup(startupFlags: string[], project: ODProjectType, startCallback: () => void): void;
@@ -0,0 +1,179 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.frameworkStartup = frameworkStartup;
7
+ const fs_1 = __importDefault(require("fs"));
8
+ const typescript_1 = __importDefault(require("typescript"));
9
+ const crypto_1 = require("crypto");
10
+ const path_1 = __importDefault(require("path"));
11
+ const ansis_1 = __importDefault(require("ansis"));
12
+ /** ## What is this?
13
+ * This is a function which compares `./src/` with a hash stored in `./dist/hash.txt`.
14
+ * The hash is based on the modified date & file metadata of all files in `./src/`.
15
+ *
16
+ * If the hash is different, the bot will automatically re-compile.
17
+ * This will help you save CPU resources because the bot shouldn't re-compile when nothing has been changed :)
18
+ */
19
+ function computeSourceHash(dir, upperHash) {
20
+ const hash = upperHash ? upperHash : (0, crypto_1.createHash)("sha256");
21
+ const info = fs_1.default.readdirSync(dir, { withFileTypes: true });
22
+ for (const file of info) {
23
+ const fullPath = path_1.default.join(dir, file.name);
24
+ if (file.isFile() && [".js", ".ts", ".jsx", ".tsx"].some((ext) => file.name.endsWith(ext))) {
25
+ const statInfo = fs_1.default.statSync(fullPath);
26
+ //compute hash using file metadata
27
+ const fileInfo = `${fullPath}:${statInfo.size}:${statInfo.mtimeMs}`;
28
+ hash.update(fileInfo);
29
+ }
30
+ else if (file.isDirectory()) {
31
+ //recursively compute all folders
32
+ computeSourceHash(fullPath, hash);
33
+ }
34
+ }
35
+ //return when not being called recursively
36
+ if (!upperHash) {
37
+ return hash.digest("hex");
38
+ }
39
+ }
40
+ function requiresCompilation(project) {
41
+ const logTitle = (project == "openticket") ? "OT" : "OM";
42
+ //check hashes when not using "--compile-only" flag
43
+ if (process.argv.includes("--compile-only"))
44
+ return true;
45
+ console.log(logTitle + ": Comparing prebuilds with source...");
46
+ const sourceHash = computeSourceHash("./src/");
47
+ const pluginHash = computeSourceHash("./plugins/");
48
+ const hash = sourceHash + ":" + pluginHash;
49
+ if (fs_1.default.existsSync("./dist/hash.txt")) {
50
+ const distHash = fs_1.default.readFileSync("./dist/hash.txt").toString();
51
+ if (distHash === hash)
52
+ return false;
53
+ else
54
+ return true;
55
+ }
56
+ else
57
+ return true;
58
+ }
59
+ function saveNewCompilationHash() {
60
+ const sourceHash = computeSourceHash("./src/");
61
+ const pluginHash = computeSourceHash("./plugins/");
62
+ const hash = sourceHash + ":" + pluginHash;
63
+ fs_1.default.writeFileSync("./dist/hash.txt", hash);
64
+ }
65
+ function frameworkStartup(startupFlags, project, startCallback) {
66
+ const logTitle = (project == "openticket") ? "OT" : "OM";
67
+ //push additional startup flags (for pterodactyl panels)
68
+ process.argv.push(...startupFlags);
69
+ //check directory structure
70
+ const requiredStructures = [
71
+ "index.js",
72
+ "./package.json",
73
+ "./README.md",
74
+ "./LICENSE.md",
75
+ "./tsconfig.json",
76
+ "./src/",
77
+ "./src/index.ts",
78
+ "./languages/",
79
+ "./config/",
80
+ "./plugins/",
81
+ "./.github/",
82
+ "./.github/FUNDING.yml",
83
+ "./.github/SECURITY.yml"
84
+ ];
85
+ for (const path of requiredStructures) {
86
+ if (!fs_1.default.existsSync(path))
87
+ throw new Error(logTitle + ": Project uses invalid structure for Open Discord! (" + path + ")");
88
+ }
89
+ //start compilation
90
+ if (!process.argv.includes("--no-compile")) {
91
+ const requiredDependencies = new Set();
92
+ if (fs_1.default.existsSync("./plugins")) {
93
+ console.log(logTitle + ": Reading plugin.json files...");
94
+ for (const pluginDir of fs_1.default.readdirSync("./plugins")) {
95
+ if (pluginDir === ".DS_Store")
96
+ continue;
97
+ const pluginPath = path_1.default.join("./plugins", pluginDir);
98
+ if (!fs_1.default.statSync(pluginPath).isDirectory())
99
+ continue;
100
+ const pluginJsonPath = path_1.default.join(pluginPath, "plugin.json");
101
+ if (fs_1.default.existsSync(pluginJsonPath)) {
102
+ try {
103
+ const pluginData = JSON.parse(fs_1.default.readFileSync(pluginJsonPath).toString());
104
+ if (pluginData.npmDependencies && Array.isArray(pluginData.npmDependencies)) {
105
+ pluginData.npmDependencies.forEach((dep) => {
106
+ if (typeof dep === "string" && dep.trim()) {
107
+ requiredDependencies.add(dep.trim());
108
+ }
109
+ });
110
+ }
111
+ }
112
+ catch (err) {
113
+ // skip invalid plugin.json files, will be caught later
114
+ }
115
+ }
116
+ }
117
+ if (requiredDependencies.size > 0) {
118
+ console.log(logTitle + ": Checking plugin npm dependencies...");
119
+ const missingDeps = [];
120
+ for (const dep of requiredDependencies) {
121
+ try {
122
+ require.resolve(dep);
123
+ }
124
+ catch (err) {
125
+ missingDeps.push(dep);
126
+ }
127
+ }
128
+ if (missingDeps.length > 0) {
129
+ console.log(ansis_1.default.red(logTitle + ": ❌ Fatal Error --> Missing npm dependencies required by plugins:\n\n") + ansis_1.default.cyan(missingDeps.map((dep) => " - " + dep).join("\n") + "\n"));
130
+ console.log(logTitle + ": Please install missing dependencies using the following command:\n> " + ansis_1.default.bold.green("npm install " + missingDeps.join(" ")) + "\n");
131
+ process.exit(1);
132
+ }
133
+ }
134
+ }
135
+ if (requiresCompilation(project)) {
136
+ console.log(logTitle + ": Compilation Required...");
137
+ //REMOVE EXISTING BUILDS
138
+ console.log(logTitle + ": Removing Prebuilds...");
139
+ fs_1.default.rmSync("./dist", { recursive: true, force: true });
140
+ //COMPILE TYPESCRIPT
141
+ console.log(logTitle + ": Compiling Typescript...");
142
+ const configPath = path_1.default.resolve('./tsconfig.json');
143
+ const configFile = typescript_1.default.readConfigFile(configPath, typescript_1.default.sys.readFile);
144
+ //check for tsconfig errors
145
+ if (configFile.error) {
146
+ const message = typescript_1.default.formatDiagnosticsWithColorAndContext([configFile.error], typescript_1.default.createCompilerHost({}));
147
+ console.error(message);
148
+ process.exit(1);
149
+ }
150
+ //parse tsconfig file
151
+ const parsedConfig = typescript_1.default.parseJsonConfigFileContent(configFile.config, typescript_1.default.sys, path_1.default.dirname(configPath));
152
+ //create program/compiler
153
+ const program = typescript_1.default.createProgram({
154
+ rootNames: parsedConfig.fileNames,
155
+ options: parsedConfig.options
156
+ });
157
+ //emit all compiled files
158
+ const emitResult = program.emit();
159
+ //print emit errors/warnings (type errors)
160
+ const allDiagnostics = typescript_1.default.getPreEmitDiagnostics(program).concat(emitResult.diagnostics);
161
+ const formattedDiagnostics = typescript_1.default.formatDiagnosticsWithColorAndContext(allDiagnostics, typescript_1.default.createCompilerHost(parsedConfig.options));
162
+ console.log(formattedDiagnostics);
163
+ if (emitResult.emitSkipped || allDiagnostics.find((d) => d.category == typescript_1.default.DiagnosticCategory.Error || d.category == typescript_1.default.DiagnosticCategory.Warning)) {
164
+ console.log(logTitle + ": Compilation Failed!");
165
+ process.exit(1);
166
+ }
167
+ }
168
+ else
169
+ console.log(logTitle + ": No Compilation Required...");
170
+ //save new compilation hash
171
+ saveNewCompilationHash();
172
+ }
173
+ //START BOT
174
+ console.log(logTitle + ": Compilation Succeeded!");
175
+ if (process.argv.includes("--compile-only"))
176
+ process.exit(0); //exit when no startup is required!
177
+ console.log(logTitle + ": Starting Bot!");
178
+ startCallback();
179
+ }
@@ -15,6 +15,7 @@ const loadAllPlugins = async (opendiscord) => {
15
15
  return;
16
16
  }
17
17
  const plugins = fs_1.default.readdirSync("./plugins");
18
+ const pluginVersionRegex = /^(OT|OM)v(\d+)\.(\d+|x)\.(\d+|x)$/;
18
19
  //check & validate
19
20
  for (const p of plugins) {
20
21
  //prechecks
@@ -44,6 +45,20 @@ const loadAllPlugins = async (opendiscord) => {
44
45
  throw new index_1.api.ODPluginError("Failed to load plugin.json/version");
45
46
  if (typeof rawplugindata.startFile != "string")
46
47
  throw new index_1.api.ODPluginError("Failed to load plugin.json/startFile");
48
+ //only check "supportedVersions" if it exists (should be array)
49
+ if (rawplugindata.supportedVersions) {
50
+ if (!Array.isArray(rawplugindata.supportedVersions))
51
+ throw new index_1.api.ODPluginError("Failed to load plugin.json/supportedVersions (must be array)");
52
+ for (const version of rawplugindata.supportedVersions) {
53
+ if (typeof version !== "string") {
54
+ throw new index_1.api.ODPluginError("Failed to load plugin.json/supportedVersions (all items must be strings)");
55
+ }
56
+ //only OT (Open Ticket) & OM (Open Moderation) are supported at the moment
57
+ if (!pluginVersionRegex.test(version)) {
58
+ throw new index_1.api.ODPluginError(`Failed to load plugin.json/supportedVersions (invalid format: "${version}", expected format like "OTv4.0.x" or "OMv1.0.0")`);
59
+ }
60
+ }
61
+ }
47
62
  if (typeof rawplugindata.enabled != "boolean")
48
63
  throw new index_1.api.ODPluginError("Failed to load plugin.json/enabled");
49
64
  if (typeof rawplugindata.priority != "number")
@@ -60,6 +75,9 @@ const loadAllPlugins = async (opendiscord) => {
60
75
  throw new index_1.api.ODPluginError("Failed to load plugin.json/details");
61
76
  if (typeof rawplugindata.details.author != "string")
62
77
  throw new index_1.api.ODPluginError("Failed to load plugin.json/details/author");
78
+ //only check "contributors" if it exists (should be array)
79
+ if (rawplugindata.details.contributors && !Array.isArray(rawplugindata.details.contributors))
80
+ throw new index_1.api.ODPluginError("Failed to load plugin.json/details/contributors (must be array)");
63
81
  if (typeof rawplugindata.details.shortDescription != "string")
64
82
  throw new index_1.api.ODPluginError("Failed to load plugin.json/details/shortDescription");
65
83
  if (typeof rawplugindata.details.longDescription != "string")
@@ -105,12 +123,43 @@ const loadAllPlugins = async (opendiscord) => {
105
123
  const incompatibilities = [];
106
124
  const missingDependencies = [];
107
125
  const missingPlugins = [];
126
+ const versionIncompatibilities = [];
108
127
  //go through all plugins for errors
109
128
  sortedPlugins.filter((plugin) => plugin.enabled).forEach((plugin) => {
110
129
  const from = plugin.id.value;
111
130
  plugin.dependenciesInstalled().forEach((missing) => missingDependencies.push({ id: from, missing }));
112
131
  plugin.pluginsIncompatible(opendiscord.plugins).forEach((incompatible) => incompatibilities.push({ from, to: incompatible }));
113
132
  plugin.pluginsInstalled(opendiscord.plugins).forEach((missing) => missingPlugins.push({ id: from, missing }));
133
+ //check if plugins are compatible with version of bot
134
+ if (plugin.data.supportedVersions && plugin.data.supportedVersions.length > 0) {
135
+ const currentVersion = opendiscord.versions.get("opendiscord:version");
136
+ if (!currentVersion)
137
+ throw new index_1.api.ODSystemError("Unable to get project version: opendiscord.versions.get('opendiscord:version')!");
138
+ let isCompatible = false;
139
+ for (const versionStr of plugin.data.supportedVersions) {
140
+ const match = versionStr.match(pluginVersionRegex);
141
+ if (!match)
142
+ continue;
143
+ const projectPrefix = match[1];
144
+ const primary = parseInt(match[2]);
145
+ const secondary = (match[3] === "x") ? null : parseInt(match[3]);
146
+ const tertiary = (match[4] === "x") ? null : parseInt(match[4]);
147
+ if (projectPrefix !== "OT")
148
+ continue;
149
+ else if (primary !== currentVersion.primary)
150
+ continue;
151
+ else if (typeof secondary === "number" && secondary !== currentVersion.secondary)
152
+ continue;
153
+ else if (typeof tertiary === "number" && tertiary !== currentVersion.tertiary)
154
+ continue;
155
+ else {
156
+ isCompatible = true;
157
+ break;
158
+ }
159
+ }
160
+ if (!isCompatible)
161
+ versionIncompatibilities.push({ id: from });
162
+ }
114
163
  });
115
164
  //handle all incompatibilities
116
165
  const alreadyLoggedCompatPlugins = [];
@@ -159,6 +208,20 @@ const loadAllPlugins = async (opendiscord) => {
159
208
  ]);
160
209
  initPluginError = true;
161
210
  });
211
+ //handle all bot version incompatibilities
212
+ versionIncompatibilities.forEach((match) => {
213
+ const plugin = opendiscord.plugins.get(match.id);
214
+ if (plugin && !plugin.crashed) {
215
+ plugin.crashed = true;
216
+ plugin.crashReason = "incompatible.version";
217
+ }
218
+ const versions = plugin?.data.supportedVersions?.join(", ") ?? "<unknown-version>";
219
+ const currentVersion = opendiscord.versions.get("opendiscord:version")?.toString() ?? "<OD:UNKNOWN_VERION>";
220
+ opendiscord.log(`Plugin version incompatibility: plugin requires "${versions}" but current bot version is "${currentVersion}", canceling plugin execution...`, "plugin", [
221
+ { key: "path", value: "./plugins/" + match.id }
222
+ ]);
223
+ initPluginError = true;
224
+ });
162
225
  //exit on error (when soft mode disabled)
163
226
  if (!opendiscord.sharedFuses.getFuse("softPluginLoading") && initPluginError) {
164
227
  console.log("");
@@ -181,18 +244,19 @@ const loadAllPlugins = async (opendiscord) => {
181
244
  }
182
245
  }
183
246
  for (const plugin of sortedPlugins) {
247
+ const authors = [plugin.details.author, ...(plugin.details.contributors ?? [])].join(", ");
184
248
  if (plugin.enabled) {
185
249
  opendiscord.debug.debug("Plugin \"" + plugin.id.value + "\" loaded", [
186
250
  { key: "status", value: (plugin.crashed ? "crashed" : "success") },
187
251
  { key: "crashReason", value: (plugin.crashed ? (plugin.crashReason ?? "/") : "/") },
188
- { key: "author", value: plugin.details.author },
252
+ { key: "authors", value: authors },
189
253
  { key: "version", value: plugin.version.toString() },
190
254
  { key: "priority", value: plugin.priority.toString() }
191
255
  ]);
192
256
  }
193
257
  else {
194
258
  opendiscord.debug.debug("Plugin \"" + plugin.id.value + "\" disabled", [
195
- { key: "author", value: plugin.details.author },
259
+ { key: "authors", value: authors },
196
260
  { key: "version", value: plugin.version.toString() },
197
261
  { key: "priority", value: plugin.priority.toString() }
198
262
  ]);
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@open-discord-bots/framework",
3
3
  "author": "DJj123dj",
4
- "version": "0.0.2",
4
+ "version": "0.0.4",
5
5
  "description": "The core framework of the popular open-source discord bots: Open Ticket & Open Moderation.",
6
6
  "main": "dist/index.js",
7
7
  "module": "dist/index.js",