@open-discord-bots/framework 0.0.1 → 0.0.2

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.
Files changed (103) hide show
  1. package/LICENSE.md +713 -0
  2. package/README.md +104 -0
  3. package/dist/api/api.d.ts +26 -0
  4. package/dist/api/api.js +44 -0
  5. package/dist/api/main.d.ts +133 -0
  6. package/dist/api/main.js +87 -0
  7. package/dist/api/modules/action.d.ts +34 -0
  8. package/dist/api/modules/action.js +58 -0
  9. package/dist/api/modules/base.d.ts +329 -0
  10. package/dist/api/modules/base.js +804 -0
  11. package/dist/api/modules/builder.d.ts +647 -0
  12. package/dist/api/modules/builder.js +1441 -0
  13. package/dist/api/modules/checker.d.ts +648 -0
  14. package/dist/api/modules/checker.js +1324 -0
  15. package/dist/api/modules/client.d.ts +768 -0
  16. package/dist/api/modules/client.js +1859 -0
  17. package/dist/api/modules/code.d.ts +33 -0
  18. package/dist/api/modules/code.js +57 -0
  19. package/dist/api/modules/config.d.ts +70 -0
  20. package/dist/api/modules/config.js +206 -0
  21. package/dist/api/modules/console.d.ts +305 -0
  22. package/dist/api/modules/console.js +598 -0
  23. package/dist/api/modules/cooldown.d.ts +138 -0
  24. package/dist/api/modules/cooldown.js +359 -0
  25. package/dist/api/modules/database.d.ts +135 -0
  26. package/dist/api/modules/database.js +271 -0
  27. package/dist/api/modules/event.d.ts +43 -0
  28. package/dist/api/modules/event.js +100 -0
  29. package/dist/api/modules/flag.d.ts +40 -0
  30. package/dist/api/modules/flag.js +72 -0
  31. package/dist/api/modules/fuse.d.ts +218 -0
  32. package/dist/api/modules/fuse.js +123 -0
  33. package/dist/api/modules/helpmenu.d.ts +106 -0
  34. package/dist/api/modules/helpmenu.js +167 -0
  35. package/dist/api/modules/language.d.ts +85 -0
  36. package/dist/api/modules/language.js +195 -0
  37. package/dist/api/modules/permission.d.ts +121 -0
  38. package/dist/api/modules/permission.js +314 -0
  39. package/dist/api/modules/plugin.d.ts +128 -0
  40. package/dist/api/modules/plugin.js +168 -0
  41. package/dist/api/modules/post.d.ts +44 -0
  42. package/dist/api/modules/post.js +92 -0
  43. package/dist/api/modules/progressbar.d.ts +108 -0
  44. package/dist/api/modules/progressbar.js +233 -0
  45. package/dist/api/modules/responder.d.ts +506 -0
  46. package/dist/api/modules/responder.js +1468 -0
  47. package/dist/api/modules/session.d.ts +58 -0
  48. package/dist/api/modules/session.js +171 -0
  49. package/dist/api/modules/startscreen.d.ts +165 -0
  50. package/dist/api/modules/startscreen.js +293 -0
  51. package/dist/api/modules/stat.d.ts +142 -0
  52. package/dist/api/modules/stat.js +293 -0
  53. package/dist/api/modules/verifybar.d.ts +54 -0
  54. package/dist/api/modules/verifybar.js +60 -0
  55. package/dist/api/modules/worker.d.ts +41 -0
  56. package/dist/api/modules/worker.js +93 -0
  57. package/dist/api/utils.d.ts +61 -0
  58. package/dist/api/utils.js +254 -0
  59. package/dist/index.d.ts +4 -1
  60. package/dist/index.js +40 -0
  61. package/dist/startup/dump.d.ts +14 -0
  62. package/dist/startup/dump.js +79 -0
  63. package/dist/startup/errorHandling.d.ts +2 -0
  64. package/dist/startup/errorHandling.js +43 -0
  65. package/dist/startup/pluginLauncher.d.ts +2 -0
  66. package/dist/startup/pluginLauncher.js +202 -0
  67. package/package.json +9 -3
  68. package/src/api/api.ts +29 -0
  69. package/src/api/main.ts +189 -0
  70. package/src/api/modules/action.ts +58 -0
  71. package/src/api/modules/base.ts +811 -0
  72. package/src/api/modules/builder.ts +1554 -0
  73. package/src/api/modules/checker.ts +1549 -0
  74. package/src/api/modules/client.ts +2247 -0
  75. package/src/api/modules/code.ts +58 -0
  76. package/src/api/modules/config.ts +159 -0
  77. package/src/api/modules/console.ts +665 -0
  78. package/src/api/modules/cooldown.ts +348 -0
  79. package/src/api/modules/database.ts +278 -0
  80. package/src/api/modules/event.ts +99 -0
  81. package/src/api/modules/flag.ts +73 -0
  82. package/src/api/modules/fuse.ts +348 -0
  83. package/src/api/modules/helpmenu.ts +216 -0
  84. package/src/api/modules/language.ts +201 -0
  85. package/src/api/modules/permission.ts +340 -0
  86. package/src/api/modules/plugin.ts +242 -0
  87. package/src/api/modules/post.ts +90 -0
  88. package/src/api/modules/progressbar.ts +232 -0
  89. package/src/api/modules/responder.ts +1420 -0
  90. package/src/api/modules/session.ts +155 -0
  91. package/src/api/modules/startscreen.ts +320 -0
  92. package/src/api/modules/stat.ts +313 -0
  93. package/src/api/modules/verifybar.ts +61 -0
  94. package/src/api/modules/worker.ts +93 -0
  95. package/src/api/utils.ts +206 -0
  96. package/src/cli/cli.ts +151 -0
  97. package/src/cli/editConfig.ts +943 -0
  98. package/src/index.ts +6 -1
  99. package/src/startup/compilation.ts +186 -0
  100. package/src/startup/dump.ts +45 -0
  101. package/src/startup/errorHandling.ts +38 -0
  102. package/src/startup/pluginLauncher.ts +261 -0
  103. package/LICENSE +0 -21
package/src/index.ts CHANGED
@@ -1 +1,6 @@
1
- import * as discord from "discord.js"
1
+ export * as api from "./api/api"
2
+ export * as utilities from "./api/utils"
3
+ export * as cli from "./cli/cli"
4
+ export { loadDumpCommand } from "./startup/dump"
5
+ export { loadAllPlugins } from "./startup/pluginLauncher"
6
+ export { frameworkStartup } from "./startup/compilation"
@@ -0,0 +1,186 @@
1
+ import fs from "fs"
2
+ import ts from "typescript"
3
+ import { createHash, Hash } from "crypto"
4
+ import nodepath from "path"
5
+ import ansis from "ansis"
6
+ import type { ODProjectType } from "../api/api"
7
+
8
+ /** ## What is this?
9
+ * This is a function which compares `./src/` with a hash stored in `./dist/hash.txt`.
10
+ * The hash is based on the modified date & file metadata of all files in `./src/`.
11
+ *
12
+ * If the hash is different, the bot will automatically re-compile.
13
+ * This will help you save CPU resources because the bot shouldn't re-compile when nothing has been changed :)
14
+ */
15
+ function computeSourceHash(dir:string,upperHash?:Hash){
16
+ const hash = upperHash ? upperHash : createHash("sha256")
17
+ const info = fs.readdirSync(dir,{withFileTypes:true})
18
+
19
+ for (const file of info) {
20
+ const fullPath = nodepath.join(dir,file.name)
21
+ if (file.isFile() && [".js",".ts",".jsx",".tsx"].some((ext) => file.name.endsWith(ext))){
22
+ const statInfo = fs.statSync(fullPath)
23
+ //compute hash using file metadata
24
+ const fileInfo = `${fullPath}:${statInfo.size}:${statInfo.mtimeMs}`
25
+ hash.update(fileInfo)
26
+
27
+ }else if (file.isDirectory()){
28
+ //recursively compute all folders
29
+ computeSourceHash(fullPath,hash)
30
+ }
31
+ }
32
+ //return when not being called recursively
33
+ if (!upperHash){
34
+ return hash.digest("hex")
35
+ }
36
+ }
37
+
38
+ function requiresCompilation(project:ODProjectType){
39
+ const logTitle = (project == "openticket") ? "OT" : "OM"
40
+
41
+ //check hashes when not using "--compile-only" flag
42
+ if (process.argv.includes("--compile-only")) return true
43
+
44
+ console.log(logTitle+": Comparing prebuilds with source...")
45
+ const sourceHash = computeSourceHash("./src/")
46
+ const pluginHash = computeSourceHash("./plugins/")
47
+ const hash = sourceHash+":"+pluginHash
48
+
49
+ if (fs.existsSync("./dist/hash.txt")){
50
+ const distHash = fs.readFileSync("./dist/hash.txt").toString()
51
+ if (distHash === hash) return false
52
+ else return true
53
+ }else return true
54
+ }
55
+
56
+ function saveNewCompilationHash(){
57
+ const sourceHash = computeSourceHash("./src/")
58
+ const pluginHash = computeSourceHash("./plugins/")
59
+ const hash = sourceHash+":"+pluginHash
60
+ fs.writeFileSync("./dist/hash.txt",hash)
61
+ }
62
+
63
+ export function frameworkStartup(startupFlags:string[],project:ODProjectType,startCallback:() => void){
64
+ const logTitle = (project == "openticket") ? "OT" : "OM"
65
+
66
+ //push additional startup flags (for pterodactyl panels)
67
+ process.argv.push(...startupFlags)
68
+
69
+ //check directory structure
70
+ const requiredStructures: string[] = [
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.existsSync(path)) throw new Error(logTitle+": Project uses invalid structure for Open Discord! ("+path+")")
87
+ }
88
+
89
+ //start compilation
90
+ if (!process.argv.includes("--no-compile")){
91
+ const requiredDependencies: Set<string> = new Set()
92
+ if (fs.existsSync("./plugins")){
93
+ console.log(logTitle+": Reading plugin.json files...")
94
+ for (const pluginDir of fs.readdirSync("./plugins")){
95
+ if (pluginDir === ".DS_Store") continue
96
+ const pluginPath = nodepath.join("./plugins", pluginDir)
97
+ if (!fs.statSync(pluginPath).isDirectory()) continue
98
+
99
+ const pluginJsonPath = nodepath.join(pluginPath, "plugin.json")
100
+ if (fs.existsSync(pluginJsonPath)){
101
+ try{
102
+ const pluginData = JSON.parse(fs.readFileSync(pluginJsonPath).toString())
103
+ if (pluginData.npmDependencies && Array.isArray(pluginData.npmDependencies)){
104
+ pluginData.npmDependencies.forEach((dep) => {
105
+ if (typeof dep === "string" && dep.trim()){
106
+ requiredDependencies.add(dep.trim())
107
+ }
108
+ })
109
+ }
110
+ }catch(err){
111
+ // skip invalid plugin.json files, will be caught later
112
+ }
113
+ }
114
+ }
115
+
116
+ if (requiredDependencies.size > 0){
117
+ console.log(logTitle+": Checking plugin npm dependencies...")
118
+ const missingDeps: string[] = []
119
+ for (const dep of requiredDependencies){
120
+ try{
121
+ require.resolve(dep)
122
+ }catch(err){
123
+ missingDeps.push(dep)
124
+ }
125
+ }
126
+
127
+ if (missingDeps.length > 0){
128
+ console.log(ansis.red(logTitle+": ❌ Fatal Error --> Missing npm dependencies required by plugins:\n\n")+ansis.cyan(missingDeps.map((dep) => " - "+dep).join("\n")+"\n"))
129
+ console.log(logTitle+": Please install missing dependencies using the following command:\n> "+ansis.bold.green("npm install " + missingDeps.join(" "))+"\n")
130
+ process.exit(1)
131
+ }
132
+ }
133
+ }
134
+
135
+ if (requiresCompilation(project)){
136
+ console.log(logTitle+": Compilation Required...")
137
+
138
+ //REMOVE EXISTING BUILDS
139
+ console.log(logTitle+": Removing Prebuilds...")
140
+ fs.rmSync("./dist",{recursive:true,force:true})
141
+
142
+ //COMPILE TYPESCRIPT
143
+ console.log(logTitle+": Compiling Typescript...")
144
+ const configPath = nodepath.resolve('./tsconfig.json')
145
+ const configFile = ts.readConfigFile(configPath,ts.sys.readFile)
146
+
147
+ //check for tsconfig errors
148
+ if (configFile.error){
149
+ const message = ts.formatDiagnosticsWithColorAndContext([configFile.error],ts.createCompilerHost({}))
150
+ console.error(message)
151
+ process.exit(1)
152
+ }
153
+
154
+ //parse tsconfig file
155
+ const parsedConfig = ts.parseJsonConfigFileContent(configFile.config,ts.sys,nodepath.dirname(configPath))
156
+
157
+ //create program/compiler
158
+ const program = ts.createProgram({
159
+ rootNames:parsedConfig.fileNames,
160
+ options:parsedConfig.options
161
+ })
162
+
163
+ //emit all compiled files
164
+ const emitResult = program.emit()
165
+
166
+ //print emit errors/warnings (type errors)
167
+ const allDiagnostics = ts.getPreEmitDiagnostics(program).concat(emitResult.diagnostics)
168
+ const formattedDiagnostics = ts.formatDiagnosticsWithColorAndContext(allDiagnostics, ts.createCompilerHost(parsedConfig.options))
169
+ console.log(formattedDiagnostics)
170
+
171
+ if (emitResult.emitSkipped || allDiagnostics.find((d) => d.category == ts.DiagnosticCategory.Error || d.category == ts.DiagnosticCategory.Warning)){
172
+ console.log(logTitle+": Compilation Failed!")
173
+ process.exit(1)
174
+ }
175
+ }else console.log(logTitle+": No Compilation Required...")
176
+
177
+ //save new compilation hash
178
+ saveNewCompilationHash()
179
+ }
180
+
181
+ //START BOT
182
+ console.log(logTitle+": Compilation Succeeded!")
183
+ if (process.argv.includes("--compile-only")) process.exit(0) //exit when no startup is required!
184
+ console.log(logTitle+": Starting Bot!")
185
+ startCallback()
186
+ }
@@ -0,0 +1,45 @@
1
+ import {api, utilities} from "../index"
2
+ import * as discord from "discord.js"
3
+ import * as fs from "fs"
4
+
5
+
6
+ /** ### What is this?
7
+ * This is the `!OPENTICKET:dump` command.
8
+ * It's a utility command which can only be used by the creator of Open Discord/Ticket/Moderation or the owner of the bot.
9
+ * This command will send the `debug.txt` file in DM. It's not dangerous as the `debug.txt` file doesn't contain any sensitive data (only logs).
10
+ *
11
+ * ### Why does it exist?
12
+ * This command can be used to quickly get the `debug.txt` file without having access to the hosting
13
+ * in case you're helping someone with setting up (or debugging) Open Discord/Ticket/Moderation.
14
+ *
15
+ * ### Can I disable it?
16
+ * If you want to turn it off, you turn off the fuse using `opendiscord.sharedFuses.setFuse("allowDumpCommand",false)`
17
+ */
18
+
19
+ export const loadDumpCommand = (opendiscord:api.ODMain) => {
20
+ if (!opendiscord.sharedFuses.getFuse("allowDumpCommand")) return
21
+ opendiscord.client.textCommands.add(new api.ODTextCommand("opendiscord:dump",{
22
+ allowBots:false,
23
+ guildPermission:true,
24
+ dmPermission:true,
25
+ name:"dump",
26
+ prefix:"!OPENTICKET:"
27
+ }))
28
+
29
+ opendiscord.client.textCommands.onInteraction("!OPENTICKET:","dump",async (msg) => {
30
+ if (msg.author.id == "779742674932072469" || opendiscord.permissions.hasPermissions("developer",await opendiscord.permissions.getPermissions(msg.author,msg.channel,null))){
31
+ //user is bot owner OR creator of Open Discord/Ticket/Moderation :)
32
+ opendiscord.log("Dumped "+opendiscord.debugfile.filename+"!","system",[
33
+ {key:"user",value:msg.author.username},
34
+ {key:"id",value:msg.author.id}
35
+ ])
36
+ const debug = fs.readFileSync(opendiscord.debugfile.path)
37
+
38
+ if (msg.channel.type != discord.ChannelType.GroupDM) msg.channel.send({content:"## The `"+opendiscord.debugfile.filename+"` dump is available!",files:[
39
+ new discord.AttachmentBuilder(debug)
40
+ .setName(opendiscord.debugfile.filename)
41
+ .setDescription("The Open Discord debug dump!")
42
+ ]})
43
+ }
44
+ })
45
+ }
@@ -0,0 +1,38 @@
1
+ import {api, utilities} from "../index"
2
+
3
+ export function loadErrorHandling(opendiscord:api.ODMain,project:api.ODProjectType){
4
+ process.on("uncaughtException",async (error,origin) => {
5
+ try{
6
+ const beforeEvent = opendiscord.events.get("onErrorHandling")
7
+ if (beforeEvent) await beforeEvent.emit([error,origin])
8
+
9
+ if (opendiscord.sharedFuses.getFuse("errorHandling")){
10
+ //custom error messages for known errors
11
+ if (error.message.toLowerCase().includes("used disallowed intents")){
12
+ //invalid intents
13
+ opendiscord.log(((project === "openticket") ? "Open Ticket" : "Open Moderation")+" doesn't work without Privileged Gateway Intents enabled!","error")
14
+ opendiscord.log("Enable them in the discord developer portal!","info")
15
+ console.log("\n")
16
+ process.exit(1)
17
+ }else if (error.message.toLowerCase().includes("invalid discord bot token provided")){
18
+ //invalid token
19
+ opendiscord.log("An invalid discord auth token was provided!","error")
20
+ opendiscord.log("Check the config if you have inserted the bot token correctly!","info")
21
+ console.log("\n")
22
+ process.exit(1)
23
+ }else{
24
+ //unknown error
25
+ const errmsg = new api.ODError(error,origin)
26
+ opendiscord.log(errmsg)
27
+ if (opendiscord.sharedFuses.getFuse("crashOnError")) process.exit(1)
28
+
29
+ const afterEvent = opendiscord.events.get("afterErrorHandling")
30
+ if (afterEvent) await afterEvent.emit([error,origin,errmsg])
31
+ }
32
+ }
33
+
34
+ }catch(err){
35
+ console.log("[ERROR HANDLER ERROR]:",err)
36
+ }
37
+ })
38
+ }
@@ -0,0 +1,261 @@
1
+ import {api, utilities} from "../index"
2
+ import fs from "fs"
3
+
4
+ export const loadAllPlugins = async (opendiscord:api.ODMain) => {
5
+ //start launching plugins
6
+ opendiscord.log("Loading plugins...","system")
7
+ let initPluginError: boolean = false
8
+
9
+ if (!fs.existsSync("./plugins")){
10
+ opendiscord.log("Couldn't find ./plugins directory, canceling all plugin execution!","error")
11
+ return
12
+ }
13
+ const plugins = fs.readdirSync("./plugins")
14
+ const pluginVersionRegex = /^(OT|OM)v(\d+)\.(\d+|x)\.(\d+|x)$/
15
+
16
+ //check & validate
17
+ for (const p of plugins){
18
+ //prechecks
19
+ if (p === ".DS_Store") return //ignore MacOS DS_Store file
20
+ if (!fs.statSync("./plugins/"+p).isDirectory()) return opendiscord.log("Plugin is not a directory, canceling plugin execution...","plugin",[
21
+ {key:"plugin",value:"./plugins/"+p}
22
+ ])
23
+ if (!fs.existsSync("./plugins/"+p+"/plugin.json")){
24
+ initPluginError = true
25
+ opendiscord.log("Plugin doesn't have a plugin.json, canceling plugin execution...","plugin",[
26
+ {key:"plugin",value:"./plugins/"+p}
27
+ ])
28
+ return
29
+ }
30
+
31
+ //plugin loading
32
+ try {
33
+ const rawplugindata: api.ODPluginData = JSON.parse(fs.readFileSync("./plugins/"+p+"/plugin.json").toString())
34
+
35
+ if (typeof rawplugindata != "object") throw new api.ODPluginError("Failed to load plugin.json")
36
+ if (typeof rawplugindata.id != "string") throw new api.ODPluginError("Failed to load plugin.json/id")
37
+ if (typeof rawplugindata.name != "string") throw new api.ODPluginError("Failed to load plugin.json/name")
38
+ if (typeof rawplugindata.version != "string") throw new api.ODPluginError("Failed to load plugin.json/version")
39
+ if (typeof rawplugindata.startFile != "string") throw new api.ODPluginError("Failed to load plugin.json/startFile")
40
+
41
+ //only check "supportedVersions" if it exists (should be array)
42
+ if (rawplugindata.supportedVersions){
43
+ if (!Array.isArray(rawplugindata.supportedVersions)) throw new api.ODPluginError("Failed to load plugin.json/supportedVersions (must be array)")
44
+ for (const version of rawplugindata.supportedVersions){
45
+ if (typeof version !== "string"){
46
+ throw new api.ODPluginError("Failed to load plugin.json/supportedVersions (all items must be strings)")
47
+ }
48
+ //only OT (Open Ticket) & OM (Open Moderation) are supported at the moment
49
+ if (!pluginVersionRegex.test(version)){
50
+ throw new api.ODPluginError(`Failed to load plugin.json/supportedVersions (invalid format: "${version}", expected format like "OTv4.0.x" or "OMv1.0.0")`)
51
+ }
52
+ }
53
+ }
54
+
55
+ if (typeof rawplugindata.enabled != "boolean") throw new api.ODPluginError("Failed to load plugin.json/enabled")
56
+ if (typeof rawplugindata.priority != "number") throw new api.ODPluginError("Failed to load plugin.json/priority")
57
+ if (!Array.isArray(rawplugindata.events)) throw new api.ODPluginError("Failed to load plugin.json/events")
58
+
59
+ if (!Array.isArray(rawplugindata.npmDependencies)) throw new api.ODPluginError("Failed to load plugin.json/npmDependencies")
60
+ if (!Array.isArray(rawplugindata.requiredPlugins)) throw new api.ODPluginError("Failed to load plugin.json/requiredPlugins")
61
+ if (!Array.isArray(rawplugindata.incompatiblePlugins)) throw new api.ODPluginError("Failed to load plugin.json/incompatiblePlugins")
62
+
63
+ if (typeof rawplugindata.details != "object") throw new api.ODPluginError("Failed to load plugin.json/details")
64
+ if (typeof rawplugindata.details.author != "string") throw new api.ODPluginError("Failed to load plugin.json/details/author")
65
+
66
+ //only check "contributors" if it exists (should be array)
67
+ if (rawplugindata.details.contributors && !Array.isArray(rawplugindata.details.contributors)) throw new api.ODPluginError("Failed to load plugin.json/details/contributors (must be array)")
68
+
69
+ if (typeof rawplugindata.details.shortDescription != "string") throw new api.ODPluginError("Failed to load plugin.json/details/shortDescription")
70
+ if (typeof rawplugindata.details.longDescription != "string") throw new api.ODPluginError("Failed to load plugin.json/details/longDescription")
71
+ if (typeof rawplugindata.details.imageUrl != "string") throw new api.ODPluginError("Failed to load plugin.json/details/imageUrl")
72
+ if (typeof rawplugindata.details.projectUrl != "string") throw new api.ODPluginError("Failed to load plugin.json/details/projectUrl")
73
+ if (!Array.isArray(rawplugindata.details.tags)) throw new api.ODPluginError("Failed to load plugin.json/details/tags")
74
+
75
+ if (rawplugindata.id != p) throw new api.ODPluginError("Failed to load plugin, directory name is required to match the id")
76
+
77
+ if (opendiscord.plugins.exists(rawplugindata.id)) throw new api.ODPluginError("Failed to load plugin, this id already exists in another plugin")
78
+
79
+ //plugin.json is valid => load plugin
80
+ const plugin = new api.ODPlugin(p,rawplugindata)
81
+ opendiscord.plugins.add(plugin)
82
+
83
+ }catch(e){
84
+ //when any of the above errors happen, crash the bot when soft mode isn't enabled
85
+ initPluginError = true
86
+ opendiscord.log(e.message+", canceling plugin execution...","plugin",[
87
+ {key:"path",value:"./plugins/"+p}
88
+ ])
89
+ opendiscord.log("You can see more about this error in the ./debug.txt file!","info")
90
+ opendiscord.debugfile.writeText(e.stack)
91
+
92
+ //try to get some crashed plugin data
93
+ try{
94
+ const rawplugindata: api.ODPluginData = JSON.parse(fs.readFileSync("./plugins/"+p+"/plugin.json").toString())
95
+ opendiscord.plugins.unknownCrashedPlugins.push({
96
+ name:rawplugindata.name ?? "./plugins/"+p,
97
+ description:(rawplugindata.details && rawplugindata.details.shortDescription) ? rawplugindata.details.shortDescription : "This plugin crashed :(",
98
+ })
99
+ }catch{}
100
+ }
101
+ }
102
+
103
+ //sorted plugins (sorted on priority. All plugins are loaded & enabled)
104
+ const sortedPlugins = opendiscord.plugins.getAll().sort((a,b) => {
105
+ return (b.priority - a.priority)
106
+ })
107
+
108
+ //check for incompatible & missing plugins/dependencies
109
+ const incompatibilities: {from:string,to:string}[] = []
110
+ const missingDependencies: {id:string,missing:string}[] = []
111
+ const missingPlugins: {id:string,missing:string}[] = []
112
+ const versionIncompatibilities: {id:string}[] = []
113
+
114
+ //go through all plugins for errors
115
+ sortedPlugins.filter((plugin) => plugin.enabled).forEach((plugin) => {
116
+ const from = plugin.id.value
117
+ plugin.dependenciesInstalled().forEach((missing) => missingDependencies.push({id:from,missing}))
118
+ plugin.pluginsIncompatible(opendiscord.plugins).forEach((incompatible) => incompatibilities.push({from,to:incompatible}))
119
+ plugin.pluginsInstalled(opendiscord.plugins).forEach((missing) => missingPlugins.push({id:from,missing}))
120
+
121
+ //check if plugins are compatible with version of bot
122
+ if (plugin.data.supportedVersions && plugin.data.supportedVersions.length > 0){
123
+ const currentVersion = opendiscord.versions.get("opendiscord:version")
124
+ if (!currentVersion) throw new api.ODSystemError("Unable to get project version: opendiscord.versions.get('opendiscord:version')!")
125
+ let isCompatible = false
126
+
127
+ for (const versionStr of plugin.data.supportedVersions){
128
+ const match = versionStr.match(pluginVersionRegex)
129
+ if (!match) continue
130
+
131
+ const projectPrefix = match[1]
132
+ const primary = parseInt(match[2])
133
+ const secondary = (match[3] === "x") ? null : parseInt(match[3])
134
+ const tertiary = (match[4] === "x") ? null : parseInt(match[4])
135
+
136
+ if (projectPrefix !== "OT") continue
137
+ else if (primary !== currentVersion.primary) continue
138
+ else if (typeof secondary === "number" && secondary !== currentVersion.secondary) continue
139
+ else if (typeof tertiary === "number" && tertiary !== currentVersion.tertiary) continue
140
+ else{
141
+ isCompatible = true
142
+ break
143
+ }
144
+ }
145
+
146
+ if (!isCompatible) versionIncompatibilities.push({id:from})
147
+ }
148
+ })
149
+
150
+ //handle all incompatibilities
151
+ const alreadyLoggedCompatPlugins: string[] = []
152
+ incompatibilities.forEach((match) => {
153
+ if (alreadyLoggedCompatPlugins.includes(match.from) || alreadyLoggedCompatPlugins.includes(match.to)) return
154
+ else alreadyLoggedCompatPlugins.push(match.from,match.to)
155
+
156
+ const fromPlugin = opendiscord.plugins.get(match.from)
157
+ if (fromPlugin && !fromPlugin.crashed){
158
+ fromPlugin.crashed = true
159
+ fromPlugin.crashReason = "incompatible.plugin"
160
+ }
161
+ const toPlugin = opendiscord.plugins.get(match.to)
162
+ if (toPlugin && !toPlugin.crashed){
163
+ toPlugin.crashed = true
164
+ toPlugin.crashReason = "incompatible.plugin"
165
+ }
166
+
167
+ opendiscord.log(`Incompatible plugins => "${match.from}" & "${match.to}", canceling plugin execution...`,"plugin",[
168
+ {key:"path1",value:"./plugins/"+match.from},
169
+ {key:"path2",value:"./plugins/"+match.to}
170
+ ])
171
+ initPluginError = true
172
+ })
173
+
174
+ //handle all missing dependencies
175
+ missingDependencies.forEach((match) => {
176
+ const plugin = opendiscord.plugins.get(match.id)
177
+ if (plugin && !plugin.crashed){
178
+ plugin.crashed = true
179
+ plugin.crashReason = "missing.dependency"
180
+ }
181
+
182
+ opendiscord.log(`Missing npm dependency "${match.missing}", canceling plugin execution...`,"plugin",[
183
+ {key:"path",value:"./plugins/"+match.id}
184
+ ])
185
+ initPluginError = true
186
+ })
187
+
188
+ //handle all missing plugins
189
+ missingPlugins.forEach((match) => {
190
+ const plugin = opendiscord.plugins.get(match.id)
191
+ if (plugin && !plugin.crashed){
192
+ plugin.crashed = true
193
+ plugin.crashReason = "missing.plugin"
194
+ }
195
+
196
+ opendiscord.log(`Missing required plugin "${match.missing}", canceling plugin execution...`,"plugin",[
197
+ {key:"path",value:"./plugins/"+match.id}
198
+ ])
199
+ initPluginError = true
200
+ })
201
+
202
+ //handle all bot version incompatibilities
203
+ versionIncompatibilities.forEach((match) => {
204
+ const plugin = opendiscord.plugins.get(match.id)
205
+ if (plugin && !plugin.crashed){
206
+ plugin.crashed = true
207
+ plugin.crashReason = "incompatible.version"
208
+ }
209
+
210
+ const versions = plugin?.data.supportedVersions?.join(", ") ?? "<unknown-version>"
211
+ const currentVersion = opendiscord.versions.get("opendiscord:version")?.toString() ?? "<OD:UNKNOWN_VERION>"
212
+ opendiscord.log(`Plugin version incompatibility: plugin requires "${versions}" but current bot version is "${currentVersion}", canceling plugin execution...`,"plugin",[
213
+ {key:"path",value:"./plugins/"+match.id}
214
+ ])
215
+ initPluginError = true
216
+ })
217
+
218
+ //exit on error (when soft mode disabled)
219
+ if (!opendiscord.sharedFuses.getFuse("softPluginLoading") && initPluginError){
220
+ console.log("")
221
+ opendiscord.log("Please fix all plugin errors above & try again!","error")
222
+ process.exit(1)
223
+ }
224
+
225
+ //preload all events required for every plugin
226
+ for (const plugin of sortedPlugins){
227
+ if (plugin.enabled) plugin.data.events.forEach((event) => opendiscord.events.add(new api.ODEvent(event)))
228
+ }
229
+
230
+ //execute all working plugins
231
+ for (const plugin of sortedPlugins){
232
+ const status = await plugin.execute(opendiscord.debug,false)
233
+
234
+ //exit on error (when soft mode disabled)
235
+ if (!status && !opendiscord.sharedFuses.getFuse("softPluginLoading")){
236
+ console.log("")
237
+ opendiscord.log("Please fix all plugin errors above & try again!","error")
238
+ process.exit(1)
239
+ }
240
+ }
241
+
242
+ for (const plugin of sortedPlugins){
243
+ const authors = [plugin.details.author,...(plugin.details.contributors ?? [])].join(", ")
244
+
245
+ if (plugin.enabled){
246
+ opendiscord.debug.debug("Plugin \""+plugin.id.value+"\" loaded",[
247
+ {key:"status",value:(plugin.crashed ? "crashed" : "success")},
248
+ {key:"crashReason",value:(plugin.crashed ? (plugin.crashReason ?? "/") : "/")},
249
+ {key:"authors",value:authors},
250
+ {key:"version",value:plugin.version.toString()},
251
+ {key:"priority",value:plugin.priority.toString()}
252
+ ])
253
+ }else{
254
+ opendiscord.debug.debug("Plugin \""+plugin.id.value+"\" disabled",[
255
+ {key:"authors",value:authors},
256
+ {key:"version",value:plugin.version.toString()},
257
+ {key:"priority",value:plugin.priority.toString()}
258
+ ])
259
+ }
260
+ }
261
+ }
package/LICENSE DELETED
@@ -1,21 +0,0 @@
1
- MIT License
2
-
3
- Copyright (c) 2026 DJj123dj
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.