261 lines
13 KiB
TypeScript
261 lines
13 KiB
TypeScript
import {opendiscord, api, utilities} from "../../index"
|
|
import fs from "fs"
|
|
import { ODPluginError } from "../api/api"
|
|
|
|
export const loadAllPlugins = async () => {
|
|
//start launching plugins
|
|
opendiscord.log("Loading plugins...","system")
|
|
let initPluginError: boolean = false
|
|
|
|
if (!fs.existsSync("./plugins")){
|
|
opendiscord.log("Couldn't find ./plugins directory, canceling all plugin execution!","error")
|
|
return
|
|
}
|
|
const plugins = fs.readdirSync("./plugins")
|
|
const pluginVersionRegex = /^(OT|OM)v(\d+)\.(\d+|x)\.(\d+|x)$/
|
|
|
|
//check & validate
|
|
plugins.forEach((p) => {
|
|
//prechecks
|
|
if (p === ".DS_Store") return //ignore MacOS DS_Store file
|
|
if (!fs.statSync("./plugins/"+p).isDirectory()) return opendiscord.log("Plugin is not a directory, canceling plugin execution...","plugin",[
|
|
{key:"plugin",value:"./plugins/"+p}
|
|
])
|
|
if (!fs.existsSync("./plugins/"+p+"/plugin.json")){
|
|
initPluginError = true
|
|
opendiscord.log("Plugin doesn't have a plugin.json, canceling plugin execution...","plugin",[
|
|
{key:"plugin",value:"./plugins/"+p}
|
|
])
|
|
return
|
|
}
|
|
|
|
//plugin loading
|
|
try {
|
|
const rawplugindata: api.ODPluginData = JSON.parse(fs.readFileSync("./plugins/"+p+"/plugin.json").toString())
|
|
|
|
if (typeof rawplugindata != "object") throw new ODPluginError("Failed to load plugin.json")
|
|
if (typeof rawplugindata.id != "string") throw new ODPluginError("Failed to load plugin.json/id")
|
|
if (typeof rawplugindata.name != "string") throw new ODPluginError("Failed to load plugin.json/name")
|
|
if (typeof rawplugindata.version != "string") throw new ODPluginError("Failed to load plugin.json/version")
|
|
if (typeof rawplugindata.startFile != "string") throw new ODPluginError("Failed to load plugin.json/startFile")
|
|
|
|
//only check "supportedVersions" if it exists (should be array)
|
|
if (rawplugindata.supportedVersions){
|
|
if (!Array.isArray(rawplugindata.supportedVersions)) throw new ODPluginError("Failed to load plugin.json/supportedVersions (must be array)")
|
|
for (const version of rawplugindata.supportedVersions){
|
|
if (typeof version !== "string"){
|
|
throw new ODPluginError("Failed to load plugin.json/supportedVersions (all items must be strings)")
|
|
}
|
|
//only OT (Open Ticket) & OM (Open Moderation) are supported at the moment
|
|
if (!pluginVersionRegex.test(version)){
|
|
throw new ODPluginError(`Failed to load plugin.json/supportedVersions (invalid format: "${version}", expected format like "OTv4.0.x" or "OMv1.0.0")`)
|
|
}
|
|
}
|
|
}
|
|
|
|
if (typeof rawplugindata.enabled != "boolean") throw new ODPluginError("Failed to load plugin.json/enabled")
|
|
if (typeof rawplugindata.priority != "number") throw new ODPluginError("Failed to load plugin.json/priority")
|
|
if (!Array.isArray(rawplugindata.events)) throw new ODPluginError("Failed to load plugin.json/events")
|
|
|
|
if (!Array.isArray(rawplugindata.npmDependencies)) throw new ODPluginError("Failed to load plugin.json/npmDependencies")
|
|
if (!Array.isArray(rawplugindata.requiredPlugins)) throw new ODPluginError("Failed to load plugin.json/requiredPlugins")
|
|
if (!Array.isArray(rawplugindata.incompatiblePlugins)) throw new ODPluginError("Failed to load plugin.json/incompatiblePlugins")
|
|
|
|
if (typeof rawplugindata.details != "object") throw new ODPluginError("Failed to load plugin.json/details")
|
|
if (typeof rawplugindata.details.author != "string") throw new ODPluginError("Failed to load plugin.json/details/author (must be string or array)")
|
|
|
|
//only check "contributors" if it exists (should be array)
|
|
if (rawplugindata.details.contributors && !Array.isArray(rawplugindata.details.contributors)) throw new ODPluginError("Failed to load plugin.json/details/contributors (must be array)")
|
|
|
|
if (typeof rawplugindata.details.shortDescription != "string") throw new ODPluginError("Failed to load plugin.json/details/shortDescription")
|
|
if (typeof rawplugindata.details.longDescription != "string") throw new ODPluginError("Failed to load plugin.json/details/longDescription")
|
|
if (typeof rawplugindata.details.imageUrl != "string") throw new ODPluginError("Failed to load plugin.json/details/imageUrl")
|
|
if (typeof rawplugindata.details.projectUrl != "string") throw new ODPluginError("Failed to load plugin.json/details/projectUrl")
|
|
if (!Array.isArray(rawplugindata.details.tags)) throw new ODPluginError("Failed to load plugin.json/details/tags")
|
|
|
|
if (rawplugindata.id != p) throw new ODPluginError("Failed to load plugin, directory name is required to match the id")
|
|
|
|
if (opendiscord.plugins.exists(rawplugindata.id)) throw new ODPluginError("Failed to load plugin, this id already exists in another plugin")
|
|
|
|
//plugin.json is valid => load plugin
|
|
const plugin = new api.ODPlugin(p,rawplugindata)
|
|
opendiscord.plugins.add(plugin)
|
|
|
|
}catch(e){
|
|
//when any of the above errors happen, crash the bot when soft mode isn't enabled
|
|
initPluginError = true
|
|
opendiscord.log(e.message+", canceling plugin execution...","plugin",[
|
|
{key:"path",value:"./plugins/"+p}
|
|
])
|
|
opendiscord.log("You can see more about this error in the ./otdebug.txt file!","info")
|
|
opendiscord.debugfile.writeText(e.stack)
|
|
|
|
//try to get some crashed plugin data
|
|
try{
|
|
const rawplugindata: api.ODPluginData = JSON.parse(fs.readFileSync("./plugins/"+p+"/plugin.json").toString())
|
|
opendiscord.plugins.unknownCrashedPlugins.push({
|
|
name:rawplugindata.name ?? "./plugins/"+p,
|
|
description:(rawplugindata.details && rawplugindata.details.shortDescription) ? rawplugindata.details.shortDescription : "This plugin crashed :(",
|
|
})
|
|
}catch{}
|
|
}
|
|
})
|
|
|
|
//sorted plugins (sorted on priority. All plugins are loaded & enabled)
|
|
const sortedPlugins = opendiscord.plugins.getAll().sort((a,b) => {
|
|
return (b.priority - a.priority)
|
|
})
|
|
|
|
//check for incompatible & missing plugins/dependencies
|
|
const incompatibilities: {from:string,to:string}[] = []
|
|
const missingDependencies: {id:string,missing:string}[] = []
|
|
const missingPlugins: {id:string,missing:string}[] = []
|
|
const versionIncompatibilities: {id:string}[] = []
|
|
|
|
//go through all plugins for errors
|
|
sortedPlugins.filter((plugin) => plugin.enabled).forEach((plugin) => {
|
|
const from = plugin.id.value
|
|
plugin.dependenciesInstalled().forEach((missing) => missingDependencies.push({id:from,missing}))
|
|
plugin.pluginsIncompatible(opendiscord.plugins).forEach((incompatible) => incompatibilities.push({from,to:incompatible}))
|
|
plugin.pluginsInstalled(opendiscord.plugins).forEach((missing) => missingPlugins.push({id:from,missing}))
|
|
|
|
//check if plugins are compatible with version of bot
|
|
if (plugin.data.supportedVersions && plugin.data.supportedVersions.length > 0){
|
|
const currentVersion = opendiscord.versions.get("opendiscord:version")
|
|
let isCompatible = false
|
|
|
|
for (const versionStr of plugin.data.supportedVersions){
|
|
const match = versionStr.match(pluginVersionRegex)
|
|
if (!match) continue
|
|
|
|
const projectPrefix = match[1]
|
|
const primary = parseInt(match[2])
|
|
const secondary = (match[3] === "x") ? null : parseInt(match[3])
|
|
const tertiary = (match[4] === "x") ? null : parseInt(match[4])
|
|
|
|
if (projectPrefix !== "OT") continue
|
|
else if (primary !== currentVersion.primary) continue
|
|
else if (typeof secondary === "number" && secondary !== currentVersion.secondary) continue
|
|
else if (typeof tertiary === "number" && tertiary !== currentVersion.tertiary) continue
|
|
else{
|
|
isCompatible = true
|
|
break
|
|
}
|
|
}
|
|
|
|
if (!isCompatible) versionIncompatibilities.push({id:from})
|
|
}
|
|
})
|
|
|
|
//handle all incompatibilities
|
|
const alreadyLoggedCompatPlugins: string[] = []
|
|
incompatibilities.forEach((match) => {
|
|
if (alreadyLoggedCompatPlugins.includes(match.from) || alreadyLoggedCompatPlugins.includes(match.to)) return
|
|
else alreadyLoggedCompatPlugins.push(match.from,match.to)
|
|
|
|
const fromPlugin = opendiscord.plugins.get(match.from)
|
|
if (fromPlugin && !fromPlugin.crashed){
|
|
fromPlugin.crashed = true
|
|
fromPlugin.crashReason = "incompatible.plugin"
|
|
}
|
|
const toPlugin = opendiscord.plugins.get(match.to)
|
|
if (toPlugin && !toPlugin.crashed){
|
|
toPlugin.crashed = true
|
|
toPlugin.crashReason = "incompatible.plugin"
|
|
}
|
|
|
|
opendiscord.log(`Incompatible plugins => "${match.from}" & "${match.to}", canceling plugin execution...`,"plugin",[
|
|
{key:"path1",value:"./plugins/"+match.from},
|
|
{key:"path2",value:"./plugins/"+match.to}
|
|
])
|
|
initPluginError = true
|
|
})
|
|
|
|
//handle all missing dependencies
|
|
missingDependencies.forEach((match) => {
|
|
const plugin = opendiscord.plugins.get(match.id)
|
|
if (plugin && !plugin.crashed){
|
|
plugin.crashed = true
|
|
plugin.crashReason = "missing.dependency"
|
|
}
|
|
|
|
opendiscord.log(`Missing npm dependency "${match.missing}", canceling plugin execution...`,"plugin",[
|
|
{key:"path",value:"./plugins/"+match.id}
|
|
])
|
|
initPluginError = true
|
|
})
|
|
|
|
//handle all missing plugins
|
|
missingPlugins.forEach((match) => {
|
|
const plugin = opendiscord.plugins.get(match.id)
|
|
if (plugin && !plugin.crashed){
|
|
plugin.crashed = true
|
|
plugin.crashReason = "missing.plugin"
|
|
}
|
|
|
|
opendiscord.log(`Missing required plugin "${match.missing}", canceling plugin execution...`,"plugin",[
|
|
{key:"path",value:"./plugins/"+match.id}
|
|
])
|
|
initPluginError = true
|
|
})
|
|
|
|
//handle all bot version incompatibilities
|
|
versionIncompatibilities.forEach((match) => {
|
|
const plugin = opendiscord.plugins.get(match.id)
|
|
if (plugin && !plugin.crashed){
|
|
plugin.crashed = true
|
|
plugin.crashReason = "incompatible.version"
|
|
}
|
|
|
|
const versions = plugin?.data.supportedVersions?.join(", ") ?? "<unknown-version>"
|
|
const currentVersion = opendiscord.versions.get("opendiscord:version").toString()
|
|
opendiscord.log(`Plugin version incompatibility: plugin requires "${versions}" but current bot version is "${currentVersion}", canceling plugin execution...`,"plugin",[
|
|
{key:"path",value:"./plugins/"+match.id}
|
|
])
|
|
initPluginError = true
|
|
})
|
|
|
|
//exit on error (when soft mode disabled)
|
|
if (!opendiscord.defaults.getDefault("softPluginLoading") && initPluginError){
|
|
console.log("")
|
|
opendiscord.log("Please fix all plugin errors above & try again!","error")
|
|
process.exit(1)
|
|
}
|
|
|
|
//preload all events required for every plugin
|
|
for (const plugin of sortedPlugins){
|
|
if (plugin.enabled) plugin.data.events.forEach((event) => opendiscord.events.add(new api.ODEvent(event)))
|
|
}
|
|
|
|
//execute all working plugins
|
|
for (const plugin of sortedPlugins){
|
|
const status = await plugin.execute(opendiscord.debug,false)
|
|
|
|
//exit on error (when soft mode disabled)
|
|
if (!status && !opendiscord.defaults.getDefault("softPluginLoading")){
|
|
console.log("")
|
|
opendiscord.log("Please fix all plugin errors above & try again!","error")
|
|
process.exit(1)
|
|
}
|
|
}
|
|
|
|
for (const plugin of sortedPlugins){
|
|
const authors = [plugin.details.author,...(plugin.details.contributors ?? [])].join(", ")
|
|
|
|
if (plugin.enabled){
|
|
opendiscord.debug.debug("Plugin \""+plugin.id.value+"\" loaded",[
|
|
{key:"status",value:(plugin.crashed ? "crashed" : "success")},
|
|
{key:"crashReason",value:(plugin.crashed ? (plugin.crashReason ?? "/") : "/")},
|
|
{key:"authors",value:authors},
|
|
{key:"version",value:plugin.version.toString()},
|
|
{key:"priority",value:plugin.priority.toString()}
|
|
])
|
|
}else{
|
|
opendiscord.debug.debug("Plugin \""+plugin.id.value+"\" disabled",[
|
|
{key:"authors",value:authors},
|
|
{key:"version",value:plugin.version.toString()},
|
|
{key:"priority",value:plugin.priority.toString()}
|
|
])
|
|
}
|
|
}
|
|
} |