@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.
- package/LICENSE.md +713 -0
- package/README.md +104 -0
- package/dist/api/api.d.ts +26 -0
- package/dist/api/api.js +44 -0
- package/dist/api/main.d.ts +133 -0
- package/dist/api/main.js +87 -0
- package/dist/api/modules/action.d.ts +34 -0
- package/dist/api/modules/action.js +58 -0
- package/dist/api/modules/base.d.ts +329 -0
- package/dist/api/modules/base.js +804 -0
- package/dist/api/modules/builder.d.ts +647 -0
- package/dist/api/modules/builder.js +1441 -0
- package/dist/api/modules/checker.d.ts +648 -0
- package/dist/api/modules/checker.js +1324 -0
- package/dist/api/modules/client.d.ts +768 -0
- package/dist/api/modules/client.js +1859 -0
- package/dist/api/modules/code.d.ts +33 -0
- package/dist/api/modules/code.js +57 -0
- package/dist/api/modules/config.d.ts +70 -0
- package/dist/api/modules/config.js +206 -0
- package/dist/api/modules/console.d.ts +305 -0
- package/dist/api/modules/console.js +598 -0
- package/dist/api/modules/cooldown.d.ts +138 -0
- package/dist/api/modules/cooldown.js +359 -0
- package/dist/api/modules/database.d.ts +135 -0
- package/dist/api/modules/database.js +271 -0
- package/dist/api/modules/event.d.ts +43 -0
- package/dist/api/modules/event.js +100 -0
- package/dist/api/modules/flag.d.ts +40 -0
- package/dist/api/modules/flag.js +72 -0
- package/dist/api/modules/fuse.d.ts +218 -0
- package/dist/api/modules/fuse.js +123 -0
- package/dist/api/modules/helpmenu.d.ts +106 -0
- package/dist/api/modules/helpmenu.js +167 -0
- package/dist/api/modules/language.d.ts +85 -0
- package/dist/api/modules/language.js +195 -0
- package/dist/api/modules/permission.d.ts +121 -0
- package/dist/api/modules/permission.js +314 -0
- package/dist/api/modules/plugin.d.ts +128 -0
- package/dist/api/modules/plugin.js +168 -0
- package/dist/api/modules/post.d.ts +44 -0
- package/dist/api/modules/post.js +92 -0
- package/dist/api/modules/progressbar.d.ts +108 -0
- package/dist/api/modules/progressbar.js +233 -0
- package/dist/api/modules/responder.d.ts +506 -0
- package/dist/api/modules/responder.js +1468 -0
- package/dist/api/modules/session.d.ts +58 -0
- package/dist/api/modules/session.js +171 -0
- package/dist/api/modules/startscreen.d.ts +165 -0
- package/dist/api/modules/startscreen.js +293 -0
- package/dist/api/modules/stat.d.ts +142 -0
- package/dist/api/modules/stat.js +293 -0
- package/dist/api/modules/verifybar.d.ts +54 -0
- package/dist/api/modules/verifybar.js +60 -0
- package/dist/api/modules/worker.d.ts +41 -0
- package/dist/api/modules/worker.js +93 -0
- package/dist/api/utils.d.ts +61 -0
- package/dist/api/utils.js +254 -0
- package/dist/index.d.ts +4 -1
- package/dist/index.js +40 -0
- package/dist/startup/dump.d.ts +14 -0
- package/dist/startup/dump.js +79 -0
- package/dist/startup/errorHandling.d.ts +2 -0
- package/dist/startup/errorHandling.js +43 -0
- package/dist/startup/pluginLauncher.d.ts +2 -0
- package/dist/startup/pluginLauncher.js +202 -0
- package/package.json +9 -3
- package/src/api/api.ts +29 -0
- package/src/api/main.ts +189 -0
- package/src/api/modules/action.ts +58 -0
- package/src/api/modules/base.ts +811 -0
- package/src/api/modules/builder.ts +1554 -0
- package/src/api/modules/checker.ts +1549 -0
- package/src/api/modules/client.ts +2247 -0
- package/src/api/modules/code.ts +58 -0
- package/src/api/modules/config.ts +159 -0
- package/src/api/modules/console.ts +665 -0
- package/src/api/modules/cooldown.ts +348 -0
- package/src/api/modules/database.ts +278 -0
- package/src/api/modules/event.ts +99 -0
- package/src/api/modules/flag.ts +73 -0
- package/src/api/modules/fuse.ts +348 -0
- package/src/api/modules/helpmenu.ts +216 -0
- package/src/api/modules/language.ts +201 -0
- package/src/api/modules/permission.ts +340 -0
- package/src/api/modules/plugin.ts +242 -0
- package/src/api/modules/post.ts +90 -0
- package/src/api/modules/progressbar.ts +232 -0
- package/src/api/modules/responder.ts +1420 -0
- package/src/api/modules/session.ts +155 -0
- package/src/api/modules/startscreen.ts +320 -0
- package/src/api/modules/stat.ts +313 -0
- package/src/api/modules/verifybar.ts +61 -0
- package/src/api/modules/worker.ts +93 -0
- package/src/api/utils.ts +206 -0
- package/src/cli/cli.ts +151 -0
- package/src/cli/editConfig.ts +943 -0
- package/src/index.ts +6 -1
- package/src/startup/compilation.ts +186 -0
- package/src/startup/dump.ts +45 -0
- package/src/startup/errorHandling.ts +38 -0
- package/src/startup/pluginLauncher.ts +261 -0
- package/LICENSE +0 -21
package/src/index.ts
CHANGED
|
@@ -1 +1,6 @@
|
|
|
1
|
-
|
|
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.
|