Open Ticket v4 Beta

This commit is contained in:
DJj123dj
2024-08-21 13:24:56 +02:00
parent 9326d0fae8
commit 0c2aca7039
154 changed files with 33881 additions and 0 deletions
+205
View File
@@ -0,0 +1,205 @@
import * as fs from "fs"
let tempErrors: string[] = []
const tempError = () => {
if (tempErrors.length > 0){
console.log("\n\n==============================\n[OPEN TICKET ERROR]: "+tempErrors.join("\n[OPEN TICKET ERROR]: ")+"\n==============================\n\n")
process.exit(0)
}
tempErrors = []
}
const nodev = process.versions.node.split(".")
if (Number(nodev[0]) < 18){
tempErrors.push("Invalid node.js version. Open Ticket requires node.js v18 or above!")
}
tempError()
const moduleInstalled = (id:string, throwError:boolean) => {
try{
require.resolve(id)
return true
}catch{
if (throwError) tempErrors.push("npm module \""+id+"\" is not installed! Install it via 'npm install "+id+"'")
return false
}
}
moduleInstalled("discord.js",true)
moduleInstalled("ansis",true)
moduleInstalled("formatted-json-stringify",true)
moduleInstalled("tsx",true)
tempError()
//init API
import * as api from "../api/api" //import for local use
export * as api from "../api/api" //export to other parts of bot
export const openticket = new api.ODMain()
console.log("\n--------------------------- OPEN TICKET STARTUP ---------------------------")
openticket.log("Logging system activated!","system")
openticket.debug.debug("Using Node.js "+process.version+"!")
try{
const packageJson = JSON.parse(fs.readFileSync("./package.json").toString())
openticket.debug.debug("Using discord.js "+packageJson.dependencies["discord.js"]+"!")
openticket.debug.debug("Using ansis "+packageJson.dependencies["ansis"]+"!")
openticket.debug.debug("Using formatted-json-stringify "+packageJson.dependencies["formatted-json-stringify"]+"!")
openticket.debug.debug("Using tsx "+packageJson.dependencies["tsx"]+"!")
}catch{
openticket.debug.debug("Failed to fetch module versions!")
}
const timer = (ms:number): Promise<void> => {
return new Promise((resolve) => {
setTimeout(() => {
resolve()
},ms)
})
}
interface ODUtilities {
/**## project `utility variable`
* This is the name of the project you are currently in.
*
* Developers can use this to create a multi-plugin compatible with all bots supporting the `open-discord` framework!
*/
project:"openticket"
/**## isBeta `utility variable`
* Check if you're running a beta version of Open Ticket.
*/
isBeta:boolean
/**## moduleInstalled `utility function`
* Use this function to check if an npm package is installed or not!
* @example utilities.moduleInstalled("discord.js") //check if discord.js is installed
*/
moduleInstalled(id:string): boolean
/**## timer `utility function`
* Use this to wait for a certain amount of milliseconds. This only works when using `await`
* @example await utilities.timer(1000) //wait 1sec
*/
timer(ms:number): Promise<void>
/**## emojiTitle `utility function`
* Use this function to create a title with an emoji before/after the text. The style & divider are set in `openticket.defaults`
* @example utilities.emojiTitle("📎","Links") //create a title with an emoji based on the bot emoji style
*/
emojiTitle(emoji:string, text:string): string
/**## runAsync `utility function`
* Use this function to run a piece of code asyncronous without creating a separate function for it!
*/
runAsync(func:() => Promise<void>): void
/**## timedAwait `utility function`
* Use this function to await a promise but reject after the certain timeout has been reached.
*/
timedAwait<ReturnValue extends Promise<any>>(promise:ReturnValue, timeout:number, onError:(err:Error) => void): ReturnValue
/**## dateString `utility function`
* Use this function to create a short date string in the following format: `DD/MM/YYYY HH:MM:SS`
*/
dateString(date:Date): string
/**## asyncReplace `utility function`
* Same as `string.replace(search, value)` but with async compatibility
*/
asyncReplace(text:string, regex:RegExp, func:(value:string,...args:any[]) => Promise<string>): Promise<string>
/**## easterEggs `utility object`
* Object containing data for Open Ticket easter eggs.
*/
easterEggs: api.ODEasterEggs,
/**## ODVersionMigration `utility class`
* This class is used to manage data migration between Open Ticket versions.
*
* It shouldn't be used by plugins because this is an internal API feature!
*/
ODVersionMigration:new (version:api.ODVersion,func:() => void|Promise<void>) => ODVersionMigration
}
/**## ODVersionMigration `utility class`
* This class is used to manage data migration between Open Ticket versions.
*
* It shouldn't be used by plugins because this is an internal API feature!
*/
class ODVersionMigration {
/**The version to migrate data to */
version: api.ODVersion
/**The migration function */
#func: () => void|Promise<void>
constructor(version:api.ODVersion,func:() => void|Promise<void>){
this.version = version
this.#func = func
}
/**Run this version migration as a plugin. Returns `false` when someting goes wrong. */
async migrate(): Promise<boolean> {
try{
await this.#func()
return true
}catch{
return false
}
}
}
export const utilities: ODUtilities = {
project:"openticket",
isBeta:true,
moduleInstalled:(id:string) => {
return moduleInstalled(id,false)
},
timer,
emojiTitle(emoji:string, text:string){
const style = openticket.defaults.getDefault("emojiTitleStyle")
const divider = openticket.defaults.getDefault("emojiTitleDivider")
if (style == "disabled") return text
else if (style == "before") return emoji+divider+text
else if (style == "after") return text+divider+emoji
else if (style == "double") return emoji+divider+text+divider+emoji
else return text
},
runAsync(func){
func()
},
timedAwait<ReturnValue>(promise:ReturnValue,timeout:number,onError:(err:Error) => void): ReturnValue {
let allowResolve = true
return new Promise(async (resolve,reject) => {
//set timeout & stop if it is before the promise resolved
setTimeout(() => {
allowResolve = false
reject("utilities.timedAwait() => Promise Timeout")
},timeout)
//get promise result & return if not already rejected
try{
const res = await promise
if (allowResolve) resolve(res)
}catch(err){
onError(err)
}
return promise
}) as ReturnValue
},
dateString(date): string {
return `${date.getDate()}/${date.getMonth()}/${date.getFullYear()} ${date.getHours()}:${date.getMinutes()}:${date.getSeconds()}`
},
async asyncReplace(text,regex,func): Promise<string> {
const promises: Promise<string>[] = []
text.replace(regex,(match,...args) => {
promises.push(func(match,...args))
return match
})
const data = await Promise.all(promises)
const result = text.replace(regex,(match) => {
const replaceResult = data.shift()
return replaceResult ?? match
})
return result
},
easterEggs:{
creator:"779742674932072469",
translators:[
"779742674932072469"
]
},
ODVersionMigration
}
+84
View File
@@ -0,0 +1,84 @@
import {openticket, api, utilities} from "../../index"
export const loadVersionMigrationSystem = async () => {
//ENTER MIGRATION CONTEXT
await preloadMigrationContext()
const lastVersion = isMigrationRequired()
openticket.versions.add(lastVersion ? lastVersion : api.ODVersion.fromString("openticket:last-version",openticket.versions.get("openticket:version").toString()))
if (lastVersion && !openticket.flags.get("openticket:no-migration").value){
//MIGRATION IS REQUIRED
openticket.log("Detected old data!","info")
openticket.log("Starting closed API context...","debug")
await utilities.timer(600)
openticket.log("Migrating data to new version...","debug")
await loadAllVersionMigrations(lastVersion)
openticket.log("Stopping closed API context...","debug")
await utilities.timer(400)
openticket.log("All data is now up to date!","info")
await utilities.timer(200)
console.log("---------------------------------------------------------------------")
}
saveAllVersionsToDatabase()
//DEFAULT FLAGS
if (openticket.flags.exists("openticket:no-plugins") && openticket.flags.get("openticket:no-plugins").value) openticket.defaults.setDefault("pluginLoading",false)
if (openticket.flags.exists("openticket:soft-plugins") && openticket.flags.get("openticket:soft-plugins").value) openticket.defaults.setDefault("softPluginLoading",true)
if (openticket.flags.exists("openticket:crash") && openticket.flags.get("openticket:crash").value) openticket.defaults.setDefault("crashOnError",true)
if (openticket.flags.exists("openticket:force-slash-update") && openticket.flags.get("openticket:force-slash-update").value) openticket.defaults.setDefault("forceSlashCommandRegistration",true)
//LEAVE MIGRATION CONTEXT
await unloadMigrationContext()
}
const preloadMigrationContext = async () => {
openticket.debug.debug("-- MIGRATION CONTEXT START --")
await (await import("../../data/framework/flagLoader.ts")).loadAllFlags()
openticket.flags.init()
await (await import("../../data/framework/configLoader.ts")).loadAllConfigs()
await (await import("../../data/framework/databaseLoader.ts")).loadAllDatabases()
openticket.debug.visible = true
}
const unloadMigrationContext = async () => {
openticket.debug.visible = false
openticket.databases.getAll().forEach((database) => openticket.databases.remove(database.id))
openticket.configs.getAll().forEach((config) => openticket.configs.remove(config.id))
openticket.flags.getAll().forEach((flag) => openticket.flags.remove(flag.id))
openticket.debug.debug("-- MIGRATION CONTEXT END --")
}
const isMigrationRequired = (): false|api.ODVersion => {
const rawVersion = openticket.databases.get("openticket:global").get("openticket:last-version","openticket:version")
if (!rawVersion) return false
const version = api.ODVersion.fromString("openticket:last-version",rawVersion)
if (openticket.versions.get("openticket:version").compare(version) == "higher"){
return version
}else return false
}
const loadAllVersionMigrations = async (lastVersion:api.ODVersion) => {
const migrations = (await import("./migration.ts")).migrations
migrations.sort((a,b) => {
const comparison = a.version.compare(b.version)
if (comparison == "equal") return 0
else if (comparison == "higher") return 1
else return -1
})
for (const migration of migrations){
if (migration.version.compare(lastVersion) == "higher"){
const success = await migration.migrate()
if (success) openticket.log("Migrated data to "+migration.version.toString()+"!","debug",[
{key:"success",value:success ? "true" : "false"}
])
}
}
}
const saveAllVersionsToDatabase = async () => {
const globalDatabase = openticket.databases.get("openticket:global")
openticket.versions.getAll().forEach((version) => {
globalDatabase.set("openticket:last-version",version.id.value,version.toString())
})
}
+8
View File
@@ -0,0 +1,8 @@
import {openticket, api, utilities} from "../../index"
export const migrations = [
//MIGRATE TO v4.0.0
new utilities.ODVersionMigration(api.ODVersion.fromString("openticket:version","v4.0.0"),async () => {
//nothing needs to be transferred :)
})
]
+197
View File
@@ -0,0 +1,197 @@
import {openticket, api, utilities} from "../../index"
import fs from "fs"
import { ODPluginError } from "../api/api"
export const loadAllPlugins = async () => {
//start launching plugins
openticket.log("Loading plugins...","system")
let initPluginError: boolean = false
if (!fs.existsSync("./plugins")){
openticket.log("Couldn't find ./plugins directory, canceling all plugin execution!","error")
return
}
const plugins = fs.readdirSync("./plugins")
//check & validate
plugins.forEach(async (p) => {
//prechecks
if (!fs.statSync("./plugins/"+p).isDirectory()) return openticket.log("Plugin is not a directory, canceling plugin execution...","plugin",[
{key:"plugin",value:"./plugins/"+p}
])
if (!fs.existsSync("./plugins/"+p+"/plugin.json")){
initPluginError = true
openticket.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")
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")
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 (openticket.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)
openticket.plugins.add(plugin)
}catch(e){
//when any of the above errors happen, crash the bot when soft mode isn't enabled
initPluginError = true
openticket.log(e.message+", canceling plugin execution...","plugin",[
{key:"path",value:"./plugins/"+p}
])
openticket.log("You can see more about this error in the ./otdebug.txt file!","info")
openticket.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())
openticket.plugins.unknownCrashedPlugins.push({
name:rawplugindata.name ?? "./plugins/"+p,
description:(rawplugindata.details && rawplugindata.details.shortDescription) ? rawplugindata.details.shortDescription : "This plugin crashed :(",
})
}catch{}
}
})
//sorted plugins (based on priority)
const sortedPlugins = openticket.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}[] = []
//go trough all plugins for errors
sortedPlugins.forEach((plugin) => {
const from = plugin.id.value
plugin.data.incompatiblePlugins.forEach((to) => {
//deny incompatibility if it already exists
if (incompatibilities.find((p) => (p.from == from && p.to == to) || (p.to == from && p.from == to))) return
//check for existence of both plugins => add to list
if (openticket.plugins.exists(to)) incompatibilities.push({from,to})
})
plugin.dependenciesInstalled().forEach((missing) => missingDependencies.push({id:from,missing}))
plugin.pluginsInstalled(openticket.plugins).forEach((missing) => missingPlugins.push({id:from,missing}))
})
//handle all incompatibilities
incompatibilities.forEach((match) => {
const fromPlugin = openticket.plugins.get(match.from)
if (fromPlugin && !fromPlugin.crashed){
fromPlugin.crashed = true
fromPlugin.crashReason = "incompatible.plugin"
}
const toPlugin = openticket.plugins.get(match.to)
if (toPlugin && !toPlugin.crashed){
toPlugin.crashed = true
toPlugin.crashReason = "incompatible.plugin"
}
openticket.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 = openticket.plugins.get(match.id)
if (plugin && !plugin.crashed){
plugin.crashed = true
plugin.crashReason = "missing.dependency"
}
openticket.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 = openticket.plugins.get(match.id)
if (plugin && !plugin.crashed){
plugin.crashed = true
plugin.crashReason = "missing.plugin"
}
openticket.log(`Missing required plugin "${match.missing}", canceling plugin execution...`,"plugin",[
{key:"path",value:"./plugins/"+match.id}
])
initPluginError = true
})
//exit on error (when soft mode disabled)
if (!openticket.defaults.getDefault("softPluginLoading") && initPluginError){
console.log("")
openticket.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) => openticket.events.add(new api.ODEvent(event)))
}
//execute all working plugins
for (const plugin of sortedPlugins){
const status = await plugin.execute(openticket.debug,false)
//exit on error (when soft mode disabled)
if (!status && !openticket.defaults.getDefault("softPluginLoading")){
console.log("")
openticket.log("Please fix all plugin errors above & try again!","error")
process.exit(1)
}
}
for (const plugin of sortedPlugins){
if (plugin.enabled){
openticket.debug.debug("Plugin \""+plugin.id.value+"\" loaded",[
{key:"status",value:(plugin.crashed ? "crashed" : "success")},
{key:"crashReason",value:(plugin.crashed ? (plugin.crashReason ?? "/") : "/")},
{key:"author",value:plugin.details.author},
{key:"version",value:plugin.version.toString()},
{key:"priority",value:plugin.priority.toString()}
])
}else{
openticket.debug.debug("Plugin \""+plugin.id.value+"\" disabled",[
{key:"author",value:plugin.details.author},
{key:"version",value:plugin.version.toString()},
{key:"priority",value:plugin.priority.toString()}
])
}
}
}