@open-discord-bots/framework 0.0.3 → 0.0.5

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,187 @@
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.md"
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! (missing: " + path + ")");
88
+ }
89
+ if (!fs_1.default.readFileSync("./.github/FUNDING.yml").toString().startsWith("github: DJj123dj"))
90
+ throw new Error(logTitle + ": Please do not use this framework in third party bots outside Open Ticket/Moderation! (1)");
91
+ const readmeContents = fs_1.default.readFileSync("./README.md").toString();
92
+ if (!readmeContents.includes(`<img src="https://apis.dj-dj.be/cdn/openticket/logo.png" alt="Open Ticket" width="650px">`) &&
93
+ !readmeContents.includes(`<img src="https://apis.dj-dj.be/cdn/openmoderation/logo.png" alt="Open Moderation" width="650px">`))
94
+ throw new Error(logTitle + ": Please do not use this framework in third party bots or outside Open Ticket/Moderation! (2)");
95
+ if (!readmeContents.includes("DJdj Development") || !readmeContents.includes("DJj123dj"))
96
+ throw new Error(logTitle + ": Please do not use this framework in third party bots or outside Open Ticket/Moderation! (3)");
97
+ //start compilation
98
+ if (!process.argv.includes("--no-compile")) {
99
+ const requiredDependencies = new Set();
100
+ if (fs_1.default.existsSync("./plugins")) {
101
+ console.log(logTitle + ": Reading plugin.json files...");
102
+ for (const pluginDir of fs_1.default.readdirSync("./plugins")) {
103
+ if (pluginDir === ".DS_Store")
104
+ continue;
105
+ const pluginPath = path_1.default.join("./plugins", pluginDir);
106
+ if (!fs_1.default.statSync(pluginPath).isDirectory())
107
+ continue;
108
+ const pluginJsonPath = path_1.default.join(pluginPath, "plugin.json");
109
+ if (fs_1.default.existsSync(pluginJsonPath)) {
110
+ try {
111
+ const pluginData = JSON.parse(fs_1.default.readFileSync(pluginJsonPath).toString());
112
+ if (pluginData.npmDependencies && Array.isArray(pluginData.npmDependencies)) {
113
+ pluginData.npmDependencies.forEach((dep) => {
114
+ if (typeof dep === "string" && dep.trim()) {
115
+ requiredDependencies.add(dep.trim());
116
+ }
117
+ });
118
+ }
119
+ }
120
+ catch (err) {
121
+ // skip invalid plugin.json files, will be caught later
122
+ }
123
+ }
124
+ }
125
+ if (requiredDependencies.size > 0) {
126
+ console.log(logTitle + ": Checking plugin npm dependencies...");
127
+ const missingDeps = [];
128
+ for (const dep of requiredDependencies) {
129
+ try {
130
+ require.resolve(dep);
131
+ }
132
+ catch (err) {
133
+ missingDeps.push(dep);
134
+ }
135
+ }
136
+ if (missingDeps.length > 0) {
137
+ 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"));
138
+ console.log(logTitle + ": Please install missing dependencies using the following command:\n> " + ansis_1.default.bold.green("npm install " + missingDeps.join(" ")) + "\n");
139
+ process.exit(1);
140
+ }
141
+ }
142
+ }
143
+ if (requiresCompilation(project)) {
144
+ console.log(logTitle + ": Compilation Required...");
145
+ //REMOVE EXISTING BUILDS
146
+ console.log(logTitle + ": Removing Prebuilds...");
147
+ fs_1.default.rmSync("./dist", { recursive: true, force: true });
148
+ //COMPILE TYPESCRIPT
149
+ console.log(logTitle + ": Compiling Typescript...");
150
+ const configPath = path_1.default.resolve('./tsconfig.json');
151
+ const configFile = typescript_1.default.readConfigFile(configPath, typescript_1.default.sys.readFile);
152
+ //check for tsconfig errors
153
+ if (configFile.error) {
154
+ const message = typescript_1.default.formatDiagnosticsWithColorAndContext([configFile.error], typescript_1.default.createCompilerHost({}));
155
+ console.error(message);
156
+ process.exit(1);
157
+ }
158
+ //parse tsconfig file
159
+ const parsedConfig = typescript_1.default.parseJsonConfigFileContent(configFile.config, typescript_1.default.sys, path_1.default.dirname(configPath));
160
+ //create program/compiler
161
+ const program = typescript_1.default.createProgram({
162
+ rootNames: parsedConfig.fileNames,
163
+ options: parsedConfig.options
164
+ });
165
+ //emit all compiled files
166
+ const emitResult = program.emit();
167
+ //print emit errors/warnings (type errors)
168
+ const allDiagnostics = typescript_1.default.getPreEmitDiagnostics(program).concat(emitResult.diagnostics);
169
+ const formattedDiagnostics = typescript_1.default.formatDiagnosticsWithColorAndContext(allDiagnostics, typescript_1.default.createCompilerHost(parsedConfig.options));
170
+ console.log(formattedDiagnostics);
171
+ if (emitResult.emitSkipped || allDiagnostics.find((d) => d.category == typescript_1.default.DiagnosticCategory.Error || d.category == typescript_1.default.DiagnosticCategory.Warning)) {
172
+ console.log(logTitle + ": Compilation Failed!");
173
+ process.exit(1);
174
+ }
175
+ }
176
+ else
177
+ console.log(logTitle + ": No Compilation Required...");
178
+ //save new compilation hash
179
+ saveNewCompilationHash();
180
+ }
181
+ //START BOT
182
+ console.log(logTitle + ": Compilation Succeeded!");
183
+ if (process.argv.includes("--compile-only"))
184
+ process.exit(0); //exit when no startup is required!
185
+ console.log(logTitle + ": Starting Bot!");
186
+ startCallback();
187
+ }
@@ -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.3",
4
+ "version": "0.0.5",
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",
@@ -9,7 +9,7 @@
9
9
  "build": "node ./tools/cleanup.js && tsc -p ./tsconfig.json"
10
10
  },
11
11
  "keywords": [],
12
- "license": "MIT",
12
+ "license": "GPL-3.0-only",
13
13
  "dependencies": {
14
14
  "@discordjs/rest": "^2.6.0",
15
15
  "@types/terminal-kit": "^2.5.7",
@@ -80,11 +80,18 @@ export function frameworkStartup(startupFlags:string[],project:ODProjectType,sta
80
80
  "./plugins/",
81
81
  "./.github/",
82
82
  "./.github/FUNDING.yml",
83
- "./.github/SECURITY.yml"
83
+ "./.github/SECURITY.md"
84
84
  ]
85
85
  for (const path of requiredStructures){
86
- if (!fs.existsSync(path)) throw new Error(logTitle+": Project uses invalid structure for Open Discord! ("+path+")")
86
+ if (!fs.existsSync(path)) throw new Error(logTitle+": Project uses invalid structure for Open Discord! (missing: "+path+")")
87
87
  }
88
+ if (!fs.readFileSync("./.github/FUNDING.yml").toString().startsWith("github: DJj123dj")) throw new Error(logTitle+": Please do not use this framework in third party bots outside Open Ticket/Moderation! (1)")
89
+ const readmeContents = fs.readFileSync("./README.md").toString()
90
+ if (
91
+ !readmeContents.includes(`<img src="https://apis.dj-dj.be/cdn/openticket/logo.png" alt="Open Ticket" width="650px">`) &&
92
+ !readmeContents.includes(`<img src="https://apis.dj-dj.be/cdn/openmoderation/logo.png" alt="Open Moderation" width="650px">`)
93
+ ) throw new Error(logTitle+": Please do not use this framework in third party bots or outside Open Ticket/Moderation! (2)")
94
+ if (!readmeContents.includes("DJdj Development") || !readmeContents.includes("DJj123dj")) throw new Error(logTitle+": Please do not use this framework in third party bots or outside Open Ticket/Moderation! (3)")
88
95
 
89
96
  //start compilation
90
97
  if (!process.argv.includes("--no-compile")){