diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..a809847 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,13 @@ +# Exclude local build artifacts — these are regenerated inside the container +node_modules/ +dist/ +.git/ +.backup/ +.vscode/ +devconfig/ +devdatabase/ +build-docker +otdebug.txt +plugins/* +!plugins/example-plugin/ +!plugins/example-plugin/** \ No newline at end of file diff --git a/.docs/createDocs.js b/.docs/createDocs.js deleted file mode 100644 index efb5a05..0000000 --- a/.docs/createDocs.js +++ /dev/null @@ -1,328 +0,0 @@ -//@ts-check -const fs = require("fs") - -/*////// OT DOCS GENERATOR //////// -What does this file do?: -Well, this file will parse the result returned from typedoc (the documentation generator). -The resulting file will then be used in the OT Docs markdown generator to generate the Open Ticket API Reference. - -In short: -This file generates the documentation for Open Ticket :) - -Contributing: -If you want to contribute to the Open Ticket Docs, please run the following command: - -> npm run docs - -This will give you a "result.json" file which can then be used in the OT Docs. -Further instructions can be found there. -/////////////////////////////////*/ - -if (!fs.existsSync(".docs/typedoc-result.json")){ - console.log("Unable to generate documentation! Run typedoc first!") - process.exit(1) -} -const result = JSON.parse(fs.readFileSync(".docs/typedoc-result.json").toString()) -const availableElements = [] -const skipElementNames = [ - "#opendiscord-types" -] - -const handleFunction = (memberType) => { - if (!memberType || !memberType.signatures || !Array.isArray(memberType.signatures)) return {type:"unknown"} - - //try to get signature - let signature = memberType.signatures.find((s) => !s.typeParameters) - if (!signature) signature = memberType.signatures[0] - if (!signature) return {type:"unknown"} - - //try to get comment - let comment = signature.comment - if (!comment) comment = memberType.signatures.find((s) => s.comment)?.comment - if (!comment) comment = null - - const signatureInherited = signature.flags.isInherited ? true : false - const signatureComment = comment ? comment.summary?.map((c) => c.text).join("") : null - const signatureParameters = signature.parameters?.map((p) => { - return {name:p.name,details:handleType(p.type)} - }) ?? [] - const signatureReturns = handleType(signature.type) - - return {type:"function",inherited:signatureInherited,comment:signatureComment,parameters:signatureParameters,returns:signatureReturns} -} - -const handleObject = (memberType) => { - if (!memberType || !memberType.children || !Array.isArray(memberType.children)) return {type:"unknown"} - const propertyIds = memberType.groups?.find((g) => g.title == "Properties")?.children ?? [] - const methodIds = memberType.groups?.find((g) => g.title == "Methods")?.children ?? [] - - const objectChildren = [] - for (const member of (memberType.children ?? [])){ - const memberName = member.name - const memberType = propertyIds.includes(member.id) ? "property" : methodIds.includes(member.id) ? "method" : "other" - let memberComment = member.comment?.summary?.map((c) => c.text).join("") ?? null - - const rawMemberSource = member.sources ? (member.sources[0] ?? null) : null - let memberSource = rawMemberSource ? rawMemberSource.fileName+":"+rawMemberSource.line+":"+rawMemberSource.character : null - - let memberDetails = null - if (memberType == "property"){ - //INTERFACE => PROPERTY - memberDetails = handleType(member.type) - }else if (memberType == "method"){ - //INTERFACE => METHOD - memberDetails = handleFunction(member) - memberComment = memberDetails.comment - } - - objectChildren.push({ - type:memberType, - name:memberName, - comment:memberComment, - source:memberSource, - details:memberDetails - }) - } - return {type:"object",children:objectChildren} -} - -const handleType = (memberType) => { - if (!memberType || !memberType.type) return {type:"unknown"} - else if (memberType.type == "intrinsic") return {type:"primitive",name:memberType.name} - else if (memberType.type == "array") return {type:"array",child:handleType(memberType.elementType)} - else if (memberType.type == "union") return {type:"union",children:memberType.types.map((t) => handleType(t))} - else if (memberType.type == "intersection") return {type:"intersection",children:memberType.types.map((t) => handleType(t))} - else if (memberType.type == "reference"){ - const referenceTypeArguments = (memberType.typeArguments) ? memberType.typeArguments.map((ta) => handleType(ta)) : null - let referenceType = availableElements.find((el) => el.name == memberType.name)?.type ?? null - - if (referenceType){ - //member is known type (from Open Ticket) - return {type:"reference",name:memberType.name,target:referenceType,typeArguments:referenceTypeArguments} - }else if (memberType.package){ - //member is type from node.js or another package - if (memberType.package == "typescript") return {type:"internal",name:memberType.name,typeArguments:referenceTypeArguments} - if (memberType.package == "open-ticket" && memberType.refersToTypeParameter) return {type:"typeParam",name:memberType.name,typeArguments:referenceTypeArguments} - else return {type:"external",package:memberType.package,name:memberType.name,typeArguments:referenceTypeArguments} - }else return {type:"unknown"} - } - else if (memberType.type == "literal") return {type:"literal",name:(typeof memberType.value == "string") ? JSON.stringify(memberType.value) : String(memberType.value)} - else if (memberType.type == "typeOperator"){ - if (memberType.operator == "keyof") return {type:"keyof",child:handleType(memberType.target)} - else if (memberType.operator == "readonly") return {type:"readonly",child:handleType(memberType.target)} - else if (memberType.operator == "unique") return {type:"unique",child:handleType(memberType.target)} - } - else if (memberType.type == "conditional") return {type:"conditional",checker:handleType(memberType.checkType),extends:handleType(memberType.extendsType),trueValue:handleType(memberType.trueType),falseValue:handleType(memberType.falseType)} - else if (memberType.type == "indexedAccess") return {type:"index",index:handleType(memberType.indexType),object:handleType(memberType.objectType)} - else if (memberType.type == "mapped") return {type:"mapped",parameterName:memberType.parameter,parameter:handleType(memberType.parameterType),template:handleType(memberType.templateType)} - else if (memberType.type == "optional") return {type:"optional",child:handleType(memberType.elementType)} - else if (memberType.type == "predicate") return {type:"predicate",name:memberType.name,target:handleType(memberType.targetType)} - else if (memberType.type == "query") return {type:"query",target:handleType(memberType.queryType)} - else if (memberType.type == "rest") return {type:"rest",child:handleType(memberType.elementType)} - else if (memberType.type == "tuple") return {type:"tuple",children:memberType.elements.map((t) => handleType(t))} - else if (memberType.type == "templateLiteral") return {type:"template",head:memberType.head,tails:memberType.tail.map((t) => { - return {element:handleType(t[0]),text:t[1]} - })} - else if (memberType.type == "reflection" && memberType.declaration && memberType.declaration.signatures && memberType.declaration.signatures[0]){ - return handleFunction(memberType.declaration) - } - else if (memberType.type == "reflection"){ - return handleObject(memberType.declaration) - } - else return {type:"unknown"} -} - -for (const file of result.children){ - const classIds = file.groups?.find((g) => g.title == "Classes")?.children ?? [] - const interfaceIds = file.groups?.find((g) => g.title == "Interfaces")?.children ?? [] - const typeIds = file.groups?.find((g) => g.title == "Type Aliases")?.children ?? [] - const enumIds = file.groups?.find((g) => g.title == "Enumerations")?.children ?? [] - const varIds = file.groups?.find((g) => g.title == "Variables")?.children ?? [] - const funcIds = file.groups?.find((g) => g.title == "Functions")?.children ?? [] - - for (const declaration of file.children){ - const declarationName = declaration.name - const declarationType = classIds.includes(declaration.id) ? "class" : interfaceIds.includes(declaration.id) ? "interface" : typeIds.includes(declaration.id) ? "type" : enumIds.includes(declaration.id) ? "enum" : varIds.includes(declaration.id) ? "variable" : funcIds.includes(declaration.id) ? "function" : "other" - availableElements.push({name:declarationName,type:declarationType}) - } -} - -const exported = [] -for (const file of result.children){ - const classIds = file.groups?.find((g) => g.title == "Classes")?.children ?? [] - const interfaceIds = file.groups?.find((g) => g.title == "Interfaces")?.children ?? [] - const typeIds = file.groups?.find((g) => g.title == "Type Aliases")?.children ?? [] - const enumIds = file.groups?.find((g) => g.title == "Enumerations")?.children ?? [] - const varIds = file.groups?.find((g) => g.title == "Variables")?.children ?? [] - const funcIds = file.groups?.find((g) => g.title == "Functions")?.children ?? [] - - for (const declaration of (file.children ?? [])){ - const declarationName = declaration.name - if (skipElementNames.includes(declarationName)) continue - - const declarationType = classIds.includes(declaration.id) ? "class" : interfaceIds.includes(declaration.id) ? "interface" : typeIds.includes(declaration.id) ? "type" : enumIds.includes(declaration.id) ? "enum" : varIds.includes(declaration.id) ? "variable" : funcIds.includes(declaration.id) ? "function" : "other" - const declarationTypeParams = (declaration.typeParameters) ? declaration.typeParameters.map((tp) => { - return {name:tp.name,type:handleType(tp.type)} - }) : null - const declarationComment = declaration.comment?.summary?.map((c) => c.text).join("") ?? null - const declarationConstant = declaration.flags.isConst ? true : false - - const rawDeclarationSource = declaration.sources ? (declaration.sources[0] ?? null) : null - const declarationSource = rawDeclarationSource ? rawDeclarationSource.fileName+":"+rawDeclarationSource.line+":"+rawDeclarationSource.character : null - - const declarationChildren = [] - if (declarationType == "class"){ - //CLASS - const constructorIds = declaration.groups?.find((g) => g.title == "Constructors")?.children ?? [] - const propertyIds = declaration.groups?.find((g) => g.title == "Properties")?.children ?? [] - const methodIds = declaration.groups?.find((g) => g.title == "Methods")?.children ?? [] - - for (const member of (declaration.children ?? [])){ - const memberName = member.name - const memberType = constructorIds.includes(member.id) ? "constructor" : propertyIds.includes(member.id) ? "property" : methodIds.includes(member.id) ? "method" : "other" - let memberComment = member.comment?.summary?.map((c) => c.text).join("") ?? null - - const rawMemberSource = member.sources ? (member.sources[0] ?? null) : null - let memberSource = rawMemberSource ? rawMemberSource.fileName+":"+rawMemberSource.line+":"+rawMemberSource.character : null - - const memberInherited = member.flags.isInherited ? true : false - const memberStatic = member.flags.isStatic ? true : false - const memberProtected = member.flags.isProtected ? true : false - const memberOptional = member.flags.isOptional ? true : false - const memberReadonly = member.flags.isReadonly ? true : false - - let memberDetails = null - if (memberType == "property"){ - //CLASS => PROPERTY - memberDetails = handleType(member.type) - - }else if (memberType == "method"){ - //CLASS => METHOD - memberDetails = handleFunction(member) - memberComment = memberDetails.comment - - }else if (memberType == "constructor"){ - //CLASS => CONSTRUCTOR - memberDetails = handleFunction(member) - memberComment = memberDetails.comment - } - - declarationChildren.push({ - type:memberType, - name:memberName, - comment:memberComment, - source:memberSource, - details:memberDetails, - inherited:memberInherited, - static:memberStatic, - protected:memberProtected, - optional:memberOptional, - readonly:memberReadonly - }) - } - }else if (declarationType == "interface"){ - //INTERFACE - const propertyIds = declaration.groups?.find((g) => g.title == "Properties")?.children ?? [] - const methodIds = declaration.groups?.find((g) => g.title == "Methods")?.children ?? [] - - for (const member of (declaration.children ?? [])){ - const memberName = member.name - const memberType = propertyIds.includes(member.id) ? "property" : methodIds.includes(member.id) ? "method" : "other" - let memberComment = member.comment?.summary?.map((c) => c.text).join("") ?? null - - const rawMemberSource = member.sources ? (member.sources[0] ?? null) : null - let memberSource = rawMemberSource ? rawMemberSource.fileName+":"+rawMemberSource.line+":"+rawMemberSource.character : null - - const memberInherited = member.flags.isInherited ? true : false - const memberStatic = member.flags.isStatic ? true : false - const memberProtected = member.flags.isProtected ? true : false - const memberOptional = member.flags.isOptional ? true : false - const memberReadonly = member.flags.isReadonly ? true : false - - let memberDetails = null - if (memberType == "property"){ - //INTERFACE => PROPERTY - memberDetails = handleType(member.type) - }else if (memberType == "method"){ - //INTERFACE => METHOD - memberDetails = handleFunction(member) - memberComment = memberDetails.comment - } - - declarationChildren.push({ - type:memberType, - name:memberName, - comment:memberComment, - source:memberSource, - details:memberDetails, - inherited:memberInherited, - static:memberStatic, - protected:memberProtected, - optional:memberOptional, - readonly:memberReadonly - }) - } - }else if (declarationType == "type"){ - //TYPE - declarationChildren.push(handleType(declaration.type)) - }else if (declarationType == "enum"){ - //ENUM - const enumerableIds = declaration.groups?.find((g) => g.title == "Enumeration Members")?.children ?? [] - - for (const member of (declaration.children ?? [])){ - const memberName = member.name - const memberType = enumerableIds.includes(member.id) ? "enumerable" : "other" - const memberComment = member.comment?.summary?.map((c) => c.text).join("") ?? null - - const rawMemberSource = member.sources ? (member.sources[0] ?? null) : null - let memberSource = rawMemberSource ? rawMemberSource.fileName+":"+rawMemberSource.line+":"+rawMemberSource.character : null - - let memberDetails = null - if (memberType == "enumerable"){ - //ENUM => ENUMERABLE - memberDetails = handleType(member.type) - } - - const memberInherited = member.flags.isInherited ? true : false - const memberStatic = member.flags.isStatic ? true : false - const memberProtected = member.flags.isProtected ? true : false - const memberOptional = member.flags.isOptional ? true : false - const memberReadonly = member.flags.isReadonly ? true : false - - declarationChildren.push({ - type:memberType, - name:memberName, - comment:memberComment, - source:memberSource, - details:memberDetails, - inherited:memberInherited, - static:memberStatic, - protected:memberProtected, - optional:memberOptional, - readonly:memberReadonly - }) - } - }else if (declarationType == "variable"){ - //VARIABLE - declarationChildren.push(handleType(declaration.type)) - - }else if (declarationType == "function"){ - //FUNCTION - declarationChildren.push(handleFunction(declaration)) - - } - - exported.push({ - type:declarationType, - name:declarationName, - comment:declarationComment, - constant:declarationConstant, - source:declarationSource, - children:declarationChildren, - typeParams:declarationTypeParams - }) - } -} - -fs.writeFileSync(".docs/result.json",JSON.stringify(exported,null,"\t")) -fs.rmSync(".docs/typedoc-result.json") \ No newline at end of file diff --git a/.docs/typedoc-config.json b/.docs/typedoc-config.json deleted file mode 100644 index 61e64f7..0000000 --- a/.docs/typedoc-config.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "$schema": "https://typedoc.org/schema.json", - "entryPoints": [ - "../src/core/api/modules/", - "../src/core/api/defaults/", - "../src/core/api/openticket/", - "../src/core/api/main.ts", - "../src/core/startup/init.ts", - ], - "entryPointStrategy": "expand", - "json": "./typedoc-result.json", - "basePath": "../" -} \ No newline at end of file diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 59c6ac0..c7cc36e 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -1,7 +1,7 @@ # Contributing Guidelines Open Ticket Logo -[![discord](https://img.shields.io/badge/discord-support%20server-5865F2.svg?style=flat-square&logo=discord)](https://discord.com/invite/26vT9wt3n3) [![version](https://img.shields.io/badge/version-4.1.3-brightgreen.svg?style=flat-square)](https://github.com/open-discord-bots/open-ticket/releases/tag/v4.1.3) [![Sponsor DJj123dj](https://img.shields.io/badge/sponsor-DJj123dj-ea4aaa?style=flat-square&logo=githubsponsors)](https://github.com/sponsors/DJj123dj) +[![discord](https://img.shields.io/badge/discord-support%20server-5865F2.svg?style=flat-square&logo=discord)](https://discord.com/invite/26vT9wt3n3) [![version](https://img.shields.io/badge/version-4.2.0-brightgreen.svg?style=flat-square)](https://github.com/open-discord-bots/open-ticket/releases/tag/v4.2.0) [![Sponsor DJj123dj](https://img.shields.io/badge/sponsor-DJj123dj-ea4aaa?style=flat-square&logo=githubsponsors)](https://github.com/sponsors/DJj123dj) These are the Contributing Guidelines of Open Ticket!
Here you can find everything you need to know about contributing to Open Ticket.
diff --git a/.github/CONTRIBUTORS.json b/.github/CONTRIBUTORS.json new file mode 100644 index 0000000..f703118 --- /dev/null +++ b/.github/CONTRIBUTORS.json @@ -0,0 +1,93 @@ +{ + "_INFO":"This document contains all Open Ticket contributors.", + "SPONSOR_TEMPLATE":{ + "name":"INSERT_NAME", + "pictureUrl":"INSERT_PICTURE_URL", + "profileUrl":"INSERT_PROFILE_URL", + "sectionId":"INSERT_ID" + }, + "SECTION_TEMPLATE":{ + "name":"INSERT_NAME", + "id":"INSERT_ID", + "pfpSize":50, + "pfpColumns":5, + "withNames":true + }, + "sections":[ + { + "name":"🛠️ Contributors", + "id":"contributor", + "pfpSize":100, + "pfpColumns":8, + "withNames":true + }, + { + "name":"📞 Discord Support", + "id":"support", + "pfpSize":100, + "pfpColumns":8, + "withNames":true + }, + { + "name":"🧩 Plugin Developers", + "id":"plugin-dev", + "pfpSize":100, + "pfpColumns":8, + "withNames":true + }, + { + "name":"💬 Translators", + "id":"translator", + "pfpSize":100, + "pfpColumns":8, + "withNames":true + } + ], + "contributors":[ + {"sectionId":"contributor","name":"DJj123dj","pictureUrl":"https://github.com/DJj123dj.png","profileUrl":"https://github.com/DJj123dj"}, + {"sectionId":"contributor","name":"guillee3","pictureUrl":"https://github.com/guillee3.png","profileUrl":"https://github.com/guillee3"}, + {"sectionId":"contributor","name":"SKaranjaN","pictureUrl":"https://github.com/SKaranjaN.png","profileUrl":"https://github.com/SKaranjaN"}, + {"sectionId":"contributor","name":"Ashish5180","pictureUrl":"https://github.com/Ashish5180.png","profileUrl":"https://github.com/Ashish5180"}, + {"sectionId":"contributor","name":"duboiss","pictureUrl":"https://github.com/duboiss.png","profileUrl":"https://github.com/duboiss"}, + {"sectionId":"contributor","name":"sdehaarte","pictureUrl":"https://github.com/sdehaarte.png","profileUrl":"https://github.com/sdehaarte"}, + {"sectionId":"contributor","name":"MauroDruwel","pictureUrl":"https://github.com/MauroDruwel.png","profileUrl":"https://github.com/MauroDruwel"}, + {"sectionId":"support","name":"smetsliam","pictureUrl":"https://github.com/smetsliam.png","profileUrl":"https://github.com/smetsliam"}, + {"sectionId":"support","name":"Sank34","pictureUrl":"https://github.com/Sank34.png","profileUrl":"https://github.com/Sank34"}, + {"sectionId":"support","name":"FrankVissers","pictureUrl":"https://github.com/FrankVissers.png","profileUrl":"https://github.com/FrankVissers"}, + {"sectionId":"plugin-dev","name":"Rapid-Fast","pictureUrl":"https://github.com/Rapid-Fast.png","profileUrl":"https://github.com/Rapid-Fast"}, + {"sectionId":"plugin-dev","name":"NotMukundOP","pictureUrl":"https://github.com/NotMukundOP.png","profileUrl":"https://github.com/NotMukundOP"}, + {"sectionId":"plugin-dev","name":"Imperatorix17","pictureUrl":"https://github.com/imperatorix17.png","profileUrl":"https://github.com/imperatorix17"}, + {"sectionId":"plugin-dev","name":"DanoGlez","pictureUrl":"https://github.com/DanoGlez.png","profileUrl":"https://github.com/DanoGlez"}, + {"sectionId":"plugin-dev","name":"yowsef","pictureUrl":"https://github.com/Yow-sef.png","profileUrl":"https://github.com/Yow-sef"}, + {"sectionId":"plugin-dev","name":"MeneerSouf","pictureUrl":"https://github.com/MeneerSouf.png","profileUrl":"https://github.com/MeneerSouf"}, + {"sectionId":"plugin-dev","name":"challenger_nova","pictureUrl":"https://apis.dj-dj.be/cdn/openticket/default-avatar.png","profileUrl":""}, + {"sectionId":"translator","name":"HanumeshGupta","pictureUrl":"https://github.com/HanumeshGupta.png","profileUrl":"https://github.com/HanumeshGupta"}, + {"sectionId":"translator","name":"benzorich","pictureUrl":"https://github.com/benzorich.png","profileUrl":"https://github.com/benzorich"}, + {"sectionId":"translator","name":"Reddishye","pictureUrl":"https://github.com/Reddishye.png","profileUrl":"https://github.com/Reddishye"}, + {"sectionId":"translator","name":"josuens","pictureUrl":"https://github.com/josuens.png","profileUrl":"https://github.com/josuens"}, + {"sectionId":"translator","name":"quiradon","pictureUrl":"https://github.com/quiradon.png","profileUrl":"https://github.com/quiradon"}, + {"sectionId":"translator","name":"Imperatorix17","pictureUrl":"https://github.com/imperatorix17.png","profileUrl":"https://github.com/imperatorix17"}, + {"sectionId":"translator","name":"NoOneNook","pictureUrl":"https://github.com/NoOneNook.png","profileUrl":"https://github.com/NoOneNook"}, + {"sectionId":"translator","name":"guillee3","pictureUrl":"https://github.com/guillee3.png","profileUrl":"https://github.com/guillee3"}, + {"sectionId":"translator","name":"Mods HD","pictureUrl":"https://github.com/mods-hd.png","profileUrl":"https://github.com/mods-hd"}, + {"sectionId":"translator","name":"anderskiy","pictureUrl":"https://github.com/anderskiy.png","profileUrl":"https://github.com/anderskiy"}, + {"sectionId":"translator","name":"SpyEye2","pictureUrl":"https://github.com/SpyEye2.png","profileUrl":"https://github.com/SpyEye2"}, + {"sectionId":"translator","name":"Sank34","pictureUrl":"https://github.com/Sank34.png","profileUrl":"https://github.com/Sank34"}, + {"sectionId":"translator","name":"thegamer5095","pictureUrl":"https://github.com/thegamer5095.png","profileUrl":"https://github.com/thegamer5095"}, + {"sectionId":"translator","name":"danoglez","pictureUrl":"https://github.com/danoglez.png","profileUrl":"https://github.com/danoglez"}, + {"sectionId":"translator","name":"zhavis","pictureUrl":"https://github.com/zhavis.png","profileUrl":"https://github.com/zhavis"}, + {"sectionId":"translator","name":"yuuslokrobjakkroval","pictureUrl":"https://github.com/yuuslokrobjakkroval.png","profileUrl":"https://github.com/yuuslokrobjakkroval"}, + {"sectionId":"translator","name":"imLudwig","pictureUrl":"https://github.com/imLudwig.png","profileUrl":"https://github.com/imLudwig"}, + {"sectionId":"translator","name":"iamnotmega","pictureUrl":"https://github.com/iamnotmega.png","profileUrl":"https://github.com/iamnotmega"}, + {"sectionId":"translator","name":"Ronalds13424","pictureUrl":"https://github.com/Ronalds13424.png","profileUrl":"https://github.com/Ronalds13424"}, + {"sectionId":"translator","name":"dysashop","pictureUrl":"https://apis.dj-dj.be/cdn/openticket/default-avatar.png","profileUrl":""}, + {"sectionId":"translator","name":"fraden1mvp.","pictureUrl":"https://apis.dj-dj.be/cdn/openticket/default-avatar.png","profileUrl":""}, + {"sectionId":"translator","name":"palestinian","pictureUrl":"https://apis.dj-dj.be/cdn/openticket/default-avatar.png","profileUrl":""}, + {"sectionId":"translator","name":"challenger_nova","pictureUrl":"https://apis.dj-dj.be/cdn/openticket/default-avatar.png","profileUrl":""}, + {"sectionId":"translator","name":"erxg","pictureUrl":"https://apis.dj-dj.be/cdn/openticket/default-avatar.png","profileUrl":""}, + {"sectionId":"translator","name":"tsgindrius","pictureUrl":"https://apis.dj-dj.be/cdn/openticket/default-avatar.png","profileUrl":""}, + {"sectionId":"translator","name":"kornel0706","pictureUrl":"https://apis.dj-dj.be/cdn/openticket/default-avatar.png","profileUrl":""}, + {"sectionId":"translator","name":"me.october","pictureUrl":"https://apis.dj-dj.be/cdn/openticket/default-avatar.png","profileUrl":""}, + {"sectionId":"translator","name":"ngocdiep2006","pictureUrl":"https://apis.dj-dj.be/cdn/openticket/default-avatar.png","profileUrl":""} + ] +} \ No newline at end of file diff --git a/.github/CONTRIBUTORS.svg b/.github/CONTRIBUTORS.svg new file mode 100644 index 0000000..97cec2f --- /dev/null +++ b/.github/CONTRIBUTORS.svg @@ -0,0 +1,288 @@ + + + 🛠️ Contributors + + + + + DJj123dj + + + + + + guillee3 + + + + + + SKaranjaN + + + + + + Ashish5180 + + + + + + duboiss + + + + + + sdehaarte + + + + + + MauroDruwel + 📞 Discord Support + + + + + smetsliam + + + + + + Sank34 + + + + + + FrankVissers + 🧩 Plugin Developers + + + + + Rapid-Fast + + + + + + NotMukundOP + + + + + + Imperatorix17 + + + + + + DanoGlez + + + + + + yowsef + + + + + + MeneerSouf + + + + + + challenger_nova + 💬 Translators + + + + + HanumeshGupta + + + + + + benzorich + + + + + + Reddishye + + + + + + josuens + + + + + + quiradon + + + + + + Imperatorix17 + + + + + + NoOneNook + + + + + + guillee3 + + + + + + Mods HD + + + + + + anderskiy + + + + + + SpyEye2 + + + + + + Sank34 + + + + + + thegamer5095 + + + + + + danoglez + + + + + + zhavis + + + + + + yuuslokrobjakkroval + + + + + + imLudwig + + + + + + iamnotmega + + + + + + Ronalds13424 + + + + + + dysashop + + + + + + fraden1mvp. + + + + + + palestinian + + + + + + challenger_nova + + + + + + erxg + + + + + + tsgindrius + + + + + + kornel0706 + + + + + + me.october + + + + + + ngocdiep2006 + + \ No newline at end of file diff --git a/.github/SECURITY.md b/.github/SECURITY.md index 3f704b6..a381a61 100644 --- a/.github/SECURITY.md +++ b/.github/SECURITY.md @@ -19,18 +19,15 @@ This list will be updated on every release. | Version | Supported | Notes | |------------|-----------|---------------------------------------------------------------| -| 4.2.0 | 🟦 | In Development | -| 4.1.x | 🟦 | In Development | -| 4.1.3 | ✅ | | -| 4.1.2 | ✅ | | -| 4.1.1 | ✅ | Supported Until April 2026 (LTS) | +| 4.2.x | 🟦 | In Development | +| 4.2.1 | 🟦 | In Development | +| 4.2.0 | ✅ | | +| 4.1.3 | ✅ | (LTS) Long-Term-Support, Until September 2026 | +| 4.1.2 | 🚧 | | +| 4.1.1 | 🚧 | | | 4.1.0 | 🚧 | | -| 4.0.7 | 🚧 | | -| 4.0.6 | 🚧 | | -| 4.0.5 | 🟧 | Deprecated | -| 4.0.4 | 🟧 | Deprecated | -| < 4.0.4 | 🟧 | Deprecated, Transcripts v2.0, Documentation Only | -| < 4.0.0 | ❌ | | +| 4.0.7 | 🟧 | Deprecated | +| < 4.0.7 | ❌ | | ### 🕷️ Reporting Vulnerabilities You can report vulnerabilities, errors & bugs using one of the following methods: diff --git a/.github/SPONSORS.json b/.github/SPONSORS.json new file mode 100644 index 0000000..1e9399a --- /dev/null +++ b/.github/SPONSORS.json @@ -0,0 +1,90 @@ +{ + "_INFO":"This document contains all Open Ticket sponsors.", + "SPONSOR_TEMPLATE":{ + "name":"INSERT_NAME", + "pictureUrl":"INSERT_PICTURE_URL", + "profileUrl":"INSERT_PROFILE_URL", + "sectionId":"INSERT_ID" + }, + "SECTION_TEMPLATE":{ + "name":"INSERT_NAME", + "id":"INSERT_ID", + "pfpSize":50, + "pfpColumns":5, + "withNames":true + }, + "sections":[ + { + "name":"💎 Platinum Sponsors", + "id":"platinum", + "pfpSize":120, + "pfpColumns":4, + "withNames":true + }, + { + "name":"🥇 Gold Sponsors", + "id":"gold", + "pfpSize":60, + "pfpColumns":8, + "withNames":true + }, + { + "name":"🥈 Silver Sponsors", + "id":"silver", + "pfpSize":40, + "pfpColumns":16, + "withNames":false + }, + { + "name":"❤️ Past Sponsors", + "id":"past", + "pfpSize":30, + "pfpColumns":20, + "withNames":false + } + ], + "sponsors":[ + { + "name":"Guillee3", + "pictureUrl":"https://github.com/guillee3.png", + "profileUrl":"https://github.com/guillee3", + "sectionId":"platinum" + }, + { + "name":"Jacob Humston", + "pictureUrl":"https://github.com/jacobhumston.png", + "profileUrl":"https://github.com/jacobhumston", + "sectionId":"platinum" + }, + { + "name":"SpyEye2", + "pictureUrl":"https://github.com/SpyEye2.png", + "profileUrl":"https://github.com/SpyEye2", + "sectionId":"gold" + }, + { + "name":"DOSEV5", + "pictureUrl":"https://github.com/DOSEV5.png", + "profileUrl":"https://github.com/DOSEV5", + "sectionId":"gold" + }, + { + "name":"YeeetSK", + "pictureUrl":"https://github.com/YeeetSK.png", + "profileUrl":"https://github.com/YeeetSK", + "sectionId":"silver" + }, + { + "name":"BENZORICH", + "pictureUrl":"https://github.com/BENZORICH.png", + "profileUrl":"https://github.com/BENZORICH", + "sectionId":"silver" + }, + { + "name":"Mods HD", + "pictureUrl":"https://github.com/mods-hd.png", + "profileUrl":"https://github.com/mods-hd", + "sectionId":"silver" + } + ] +} \ No newline at end of file diff --git a/.github/SPONSORS.svg b/.github/SPONSORS.svg new file mode 100644 index 0000000..e3edf79 --- /dev/null +++ b/.github/SPONSORS.svg @@ -0,0 +1,61 @@ + + + + + \ No newline at end of file diff --git a/.eggs/README.md b/.github/pterodactyl-eggs/README.md similarity index 91% rename from .eggs/README.md rename to .github/pterodactyl-eggs/README.md index 4585e89..c22f4a4 100644 --- a/.eggs/README.md +++ b/.github/pterodactyl-eggs/README.md @@ -2,7 +2,7 @@ Open Ticket Logo [![discord](https://img.shields.io/badge/discord-support%20server-5865F2.svg?style=flat-square&logo=discord)](https://discord.com/invite/26vT9wt3n3) -[![version](https://img.shields.io/badge/version-4.1.3-brightgreen.svg?style=flat-square)](https://github.com/open-discord-bots/open-ticket/releases/tag/v4.1.3) +[![version](https://img.shields.io/badge/version-4.2.0-brightgreen.svg?style=flat-square)](https://github.com/open-discord-bots/open-ticket/releases/tag/v4.2.0) [![Sponsor DJj123dj](https://img.shields.io/badge/sponsor-DJj123dj-ea4aaa?style=flat-square&logo=githubsponsors)](https://github.com/sponsors/DJj123dj) [![Open Ticket supports Pterodactyl Eggs!](https://img.shields.io/badge/pterodactyl-supported-10539F?style=flat-square&logo=pterodactyl)](.eggs/README.md) @@ -20,6 +20,9 @@ It's recommended to provide at least `1GB` of **Memory/RAM** and `5GB` of **disk [**`openticket-egg-main.json` (Recommended)**](openticket-egg-main.json) - This egg will use the `main` branch of Open Ticket. +[**`openticket-egg-v4.2.0.json`**](openticket-egg-v4.2.0.json) +- This egg will always use Open Ticket `v4.2.0`. Open Ticket updates will not have an effect on this egg. + [**`openticket-egg-v4.1.3.json`**](openticket-egg-v4.1.3.json) - This egg will always use Open Ticket `v4.1.3`. Open Ticket updates will not have an effect on this egg. diff --git a/.eggs/openticket-egg-dev.json b/.github/pterodactyl-eggs/openticket-egg-dev.json similarity index 100% rename from .eggs/openticket-egg-dev.json rename to .github/pterodactyl-eggs/openticket-egg-dev.json diff --git a/.eggs/openticket-egg-main.json b/.github/pterodactyl-eggs/openticket-egg-main.json similarity index 100% rename from .eggs/openticket-egg-main.json rename to .github/pterodactyl-eggs/openticket-egg-main.json diff --git a/.eggs/openticket-egg-v3.5.9.json b/.github/pterodactyl-eggs/openticket-egg-v3.5.9.json similarity index 100% rename from .eggs/openticket-egg-v3.5.9.json rename to .github/pterodactyl-eggs/openticket-egg-v3.5.9.json diff --git a/.eggs/openticket-egg-v4.0.7.json b/.github/pterodactyl-eggs/openticket-egg-v4.0.7.json similarity index 100% rename from .eggs/openticket-egg-v4.0.7.json rename to .github/pterodactyl-eggs/openticket-egg-v4.0.7.json diff --git a/.eggs/openticket-egg-v4.1.0.json b/.github/pterodactyl-eggs/openticket-egg-v4.1.0.json similarity index 100% rename from .eggs/openticket-egg-v4.1.0.json rename to .github/pterodactyl-eggs/openticket-egg-v4.1.0.json diff --git a/.eggs/openticket-egg-v4.1.1.json b/.github/pterodactyl-eggs/openticket-egg-v4.1.1.json similarity index 100% rename from .eggs/openticket-egg-v4.1.1.json rename to .github/pterodactyl-eggs/openticket-egg-v4.1.1.json diff --git a/.eggs/openticket-egg-v4.1.2.json b/.github/pterodactyl-eggs/openticket-egg-v4.1.2.json similarity index 100% rename from .eggs/openticket-egg-v4.1.2.json rename to .github/pterodactyl-eggs/openticket-egg-v4.1.2.json diff --git a/.eggs/openticket-egg-v4.1.3.json b/.github/pterodactyl-eggs/openticket-egg-v4.1.3.json similarity index 100% rename from .eggs/openticket-egg-v4.1.3.json rename to .github/pterodactyl-eggs/openticket-egg-v4.1.3.json diff --git a/.github/pterodactyl-eggs/openticket-egg-v4.2.0.json b/.github/pterodactyl-eggs/openticket-egg-v4.2.0.json new file mode 100644 index 0000000..88530ca --- /dev/null +++ b/.github/pterodactyl-eggs/openticket-egg-v4.2.0.json @@ -0,0 +1,62 @@ +{ + "_comment": "DO NOT EDIT: FILE GENERATED AUTOMATICALLY BY PTERODACTYL PANEL - PTERODACTYL.IO", + "meta": { + "version": "PTDL_v2", + "update_url": null + }, + "exported_at": "2025-03-16T18:10:18+01:00", + "name": "Open Ticket (v4.2.0)", + "author": "support@dj-dj.be", + "description": "This is the official Pterodactyl egg for Open Ticket v4.2.0, the most advanced & customisable discord ticket bot that you will ever find! You can customise up to 300+ variables! This includes Html Transcripts, Advanced Plugins, Custom Embeds, Questions\/Modals, Stats & more!", + "features": null, + "docker_images": { + "ghcr.io\/parkervcp\/yolks:nodejs_20": "ghcr.io\/parkervcp\/yolks:nodejs_20" + }, + "file_denylist": [], + "startup": "if [[ ! -z ${NODE_PACKAGES} ]]; then npm install ${NODE_PACKAGES}; fi; if [[ ! -z ${UNNODE_PACKAGES} ]]; then npm uninstall ${UNNODE_PACKAGES}; fi; if [ -f \/home\/container\/package.json ]; then npm install; fi; node \"\/home\/container\/index.js\" ${NODE_FLAGS};", + "config": { + "files": "{}", + "startup": "{\r\n \"done\": \"STARTUP INFO:\"\r\n}", + "logs": "{}", + "stop": "^C" + }, + "scripts": { + "installation": { + "script": "#!\/bin\/bash\r\n# Open Ticket Installation Script (v4.2.0)\r\n# Inspired by: Node.js Installation Script\r\n# \u00a9 DJdj Development\r\n\r\necho -e \"[OT INSTALLER] installing dependencies. please wait...\"\r\napt update\r\napt install -y git curl\r\n\r\necho -e \"[OT INSTALLER] updating npm. please wait...\"\r\nnpm install npm@latest --location=global\r\n\r\nmkdir -p \/mnt\/server\r\ncd \/mnt\/server\r\n\r\nBRANCH=\"v4.2.0\"\r\n\r\nif [ \"$(ls -A \/mnt\/server)\" ]; then\r\n echo -e \"[OT INSTALLER] \/mnt\/server directory is not empty.\"\r\n if [ -d .git ]; then\r\n echo -e \"[OT INSTALLER] .git directory exists\"\r\n if [ -f .git\/config ]; then\r\n echo -e \"[OT INSTALLER] loading info from git config\"\r\n ORIGIN=$(git config --get remote.origin.url)\r\n else\r\n echo -e \"[OT INSTALLER] files found with no git config\"\r\n echo -e \"[OT INSTALLER] closing out without touching things to not break anything\"\r\n exit 10\r\n fi\r\n fi\r\n\r\n if [ \"${ORIGIN}\" == \"https:\/\/github.com\/open-discord-bots\/open-ticket.git\" ]; then\r\n echo \"pulling latest from github\"\r\n git pull\r\n fi\r\nelse\r\n echo -e \"[OT INSTALLER] \/mnt\/server is empty.\"\r\n echo -e \"[OT INSTALLER] cloning files into repo.\"\r\n if [ -z ${BRANCH} ]; then\r\n echo -e \"[OT INSTALLER] cloning default branch\"\r\n git clone https:\/\/github.com\/open-discord-bots\/open-ticket.git .\r\n else\r\n echo -e \"[OT INSTALLER] cloning ${BRANCH}'\"\r\n git clone --single-branch --branch ${BRANCH} https:\/\/github.com\/open-discord-bots\/open-ticket.git .\r\n fi\r\nfi\r\n\r\necho -e \"[OT INSTALLER] installing Open Ticket node.js packages.\"\r\nif [ -f \/mnt\/server\/package.json ]; then\r\n \/usr\/local\/bin\/npm install --omit=dev\r\nfi\r\n\r\necho -e \"[OT INSTALLER] installing custom node.js packages.\"\r\nif [[ ! -z ${NODE_PACKAGES} ]]; then\r\n \/usr\/local\/bin\/npm install ${NODE_PACKAGES}\r\nfi\r\n\r\necho -e \"[OT INSTALLER] install complete!\"\r\nexit 0", + "container": "node:latest", + "entrypoint": "bash" + } + }, + "variables": [ + { + "name": "Additional Npm Packages", + "description": "Specify additional npm packages used by Open Ticket plugins. Use spaces to separate.", + "env_variable": "NODE_PACKAGES", + "default_value": "", + "user_viewable": false, + "user_editable": true, + "rules": "string|nullable", + "field_type": "text" + }, + { + "name": "Startup Flags", + "description": "Start the bot with additional Open Ticket flags. Separate using spaces.\r\nA full reference list can be found in the documentation.", + "env_variable": "NODE_FLAGS", + "default_value": "", + "user_viewable": false, + "user_editable": true, + "rules": "string|nullable", + "field_type": "text" + }, + { + "name": "Uninstall Npm Packages", + "description": "A list of npm packages to uninstall. Separate by spaces.", + "env_variable": "UNNODE_PACKAGES", + "default_value": "", + "user_viewable": false, + "user_editable": true, + "rules": "string|nullable", + "field_type": "text" + } + ] +} \ No newline at end of file diff --git a/.gitignore b/.gitignore index 243efae..6eeaabc 100644 --- a/.gitignore +++ b/.gitignore @@ -17,7 +17,10 @@ otdebug.txt */*/*/.DS_Store **/.DS_Store -.docs/* -!.docs/createDocs.js -!.docs/mergeTranslations.js -!.docs/typedoc-config.json \ No newline at end of file +.backup/* +.tools/* +!.tools/createSponsors.ts +!.tools/createContributors.ts +!.tools/mergeTranslations.ts +!.tools/docker-compose.yml +!.tools/build-docker.sh \ No newline at end of file diff --git a/.tools/build-docker.sh b/.tools/build-docker.sh new file mode 100755 index 0000000..808c652 --- /dev/null +++ b/.tools/build-docker.sh @@ -0,0 +1,37 @@ +#!/usr/bin/env bash +# build.sh — Build Open Ticket Docker images for all supported architectures. +# +# Usage: ./build.sh [--no-push] +# image Full image name, e.g. djj123dj/open-ticket +# +# Example: ./build.sh djj123dj/open-ticket --no-push + +set -euo pipefail + +cd "$(dirname "$0")/.." +echo "✅ Switched working directory to Open Ticket root" +pwd + +IMAGE="${1:?Usage: ./build-docker.sh [--no-push]}" +PLATFORMS="linux/amd64,linux/arm64" # linux/arm/v6,linux/arm/v7,linux/s390x are taking too long to build, so we'll skip them for now +VERSION=$(node -p "require('./package.json').version") + +# Create a multi-arch builder if it doesn't exist yet +docker buildx inspect ot-builder &>/dev/null \ + || docker buildx create --name ot-builder --driver docker-container --bootstrap --use +docker buildx use ot-builder + +PUSH_FLAG="--push" +[[ "${2:-}" == "--no-push" ]] && PUSH_FLAG="" + +docker buildx build \ + --platform "$PLATFORMS" \ + --tag "${IMAGE}:v${VERSION}" \ + --tag "${IMAGE}:latest" \ + --load \ + $PUSH_FLAG \ + . + +[[ -n "$PUSH_FLAG" ]] \ + && echo "✅ Pushed ${IMAGE}:v${VERSION} and ${IMAGE}:latest" \ + || echo "✅ Built ${IMAGE}:v${VERSION} (not pushed)" diff --git a/.tools/createContributors.ts b/.tools/createContributors.ts new file mode 100644 index 0000000..dd5985f --- /dev/null +++ b/.tools/createContributors.ts @@ -0,0 +1,130 @@ +/// +import fs from "fs" +import path from "path" +import crypto from "crypto" + +const contributorData: {contributors:Contributor[],sections:Section[]} = JSON.parse(fs.readFileSync(path.join(process.cwd(),"./.github/CONTRIBUTORS.json")).toString()) + +//CONSTANTS +const CORNER_RADIUS = 10 +const SPACE_MULTIPLIER = 1.2 +const SVG_WIDTH = 1000 + +//TYPES +interface Contributor { + name:string, + pictureUrl:string, + profileUrl:string, + sectionId:string +} +interface Section { + name:string, + id:string, + pfpSize:number, + pfpColumns:number, + withNames:boolean +} + + +//FUNCTIONS +async function downloadPfpToBase64URL(url:string){ + const res = await fetch(url,{method:"GET"}) + if (!res.ok) return null + const buffer = Buffer.from(await res.arrayBuffer()) + console.log("Downloaded picture URL:",url) + return "data:image/png;base64,"+buffer.toString("base64") +} + +function createTitle(yPos:number,name:string){ + return `${name}` +} + +async function createPfp(yPos:number,xPos:number,size:number,contributor:Contributor,withNames:boolean){ + const randomId = crypto.randomBytes(8).toString("hex") + const nameElement = (withNames) ? `${contributor.name}` : "" + + return (` + + + + + ${nameElement} + `) +} + +async function generateSection(yPos:number,section:Section,contributors:Contributor[]){ + let sectionHtml: string = "" + sectionHtml += createTitle(yPos,section.name) + const nameOffset = (section.withNames) ? 20 : 0 + + //divide contributors in rows + const groupedContributors: Contributor[][] = [] + let currentGroup: Contributor[] = [] + for (const contributor of contributors){ + currentGroup.push(contributor) + if (currentGroup.length == section.pfpColumns){ + groupedContributors.push(currentGroup) + currentGroup = [] + } + } + if (currentGroup.length > 0) groupedContributors.push(currentGroup) + + let y = 0 + for (const contributorGroup of groupedContributors){ + let x = 0 + for (const contributor of contributorGroup){ + const pfpYPos = 40 + yPos + (y * ((section.pfpSize * SPACE_MULTIPLIER) + nameOffset)) + const pfpXPos = 20 + (x * section.pfpSize * SPACE_MULTIPLIER) + sectionHtml += await createPfp(pfpYPos,pfpXPos,section.pfpSize,contributor,section.withNames) + x++ + } + y++ + } + + let sectionHeight: number = 40 + (y * ((section.pfpSize * SPACE_MULTIPLIER) + nameOffset)) + return {sectionHtml,sectionHeight} +} + +async function generateSections(sections:Section[],contributors:Contributor[]){ + let finalHeight: number = 10 + let finalHtml: string = "" + for (const section of sections){ + const sectionContributors = contributors.filter((s) => s.sectionId === section.id) + if (sectionContributors.length < 1) continue + const {sectionHtml,sectionHeight} = await generateSection(finalHeight,section,sectionContributors) + + finalHeight += sectionHeight + finalHtml += sectionHtml + } + + finalHeight += 10 + return {finalHeight,finalHtml} +} + +function generateFinalHtml(sectionsHtml:string,sectionHeight:number){ + return (` + + ${sectionsHtml} + `) +} + +//GENERATE CONTRIBUTORS SVG +async function main(){ + const {finalHeight,finalHtml} = await generateSections(contributorData.sections,contributorData.contributors) + fs.writeFileSync(path.join(process.cwd(),"./.github/CONTRIBUTORS.svg"),generateFinalHtml(finalHtml,finalHeight)) +} +main() \ No newline at end of file diff --git a/.tools/createSponsors.ts b/.tools/createSponsors.ts new file mode 100644 index 0000000..2ad05dc --- /dev/null +++ b/.tools/createSponsors.ts @@ -0,0 +1,132 @@ +/// +import fs from "fs" +import path from "path" +import crypto from "crypto" + +const sponsorData: {sponsors:Sponsor[],sections:Section[]} = JSON.parse(fs.readFileSync(path.join(process.cwd(),"./.github/SPONSORS.json")).toString()) + +//CONSTANTS +const CORNER_RADIUS = 10 +const SPACE_MULTIPLIER = 1.2 +const SVG_WIDTH = 1000 + +//TYPES +interface Sponsor { + name:string, + pictureUrl:string, + profileUrl:string, + sectionId:string +} +interface Section { + name:string, + id:string, + pfpSize:number, + pfpColumns:number, + withNames:boolean +} + + +//FUNCTIONS +async function downloadPfpToBase64URL(url:string){ + const res = await fetch(url,{method:"GET"}) + if (!res.ok) return null + const buffer = Buffer.from(await res.arrayBuffer()) + console.log("Downloaded picture URL:",url) + return "data:image/png;base64,"+buffer.toString("base64") +} + +function createTitle(yPos:number,name:string){ + return `` +} + +async function createPfp(yPos:number,xPos:number,size:number,sponsor:Sponsor,withNames:boolean){ + const randomId = crypto.randomBytes(8).toString("hex") + const nameElement = (withNames) ? `${sponsor.name}` : "" + + return (``) +} + +async function generateSection(yPos:number,section:Section,sponsors:Sponsor[]){ + let sectionHtml: string = "" + sectionHtml += createTitle(yPos,section.name) + const nameOffset = (section.withNames) ? 20 : 0 + + //divide sponsors in rows + const groupedSponsors: Sponsor[][] = [] + let currentGroup: Sponsor[] = [] + for (const sponsor of sponsors){ + currentGroup.push(sponsor) + if (currentGroup.length == section.pfpColumns){ + groupedSponsors.push(currentGroup) + currentGroup = [] + } + } + if (currentGroup.length > 0) groupedSponsors.push(currentGroup) + + let y = 0 + for (const sponsorGroup of groupedSponsors){ + const xOffset = Math.round((SVG_WIDTH - (sponsorGroup.length * section.pfpSize * SPACE_MULTIPLIER))/2) + let x = 0 + for (const sponsor of sponsorGroup){ + const pfpYPos = 40 + yPos + (y * ((section.pfpSize * SPACE_MULTIPLIER) + nameOffset)) + const pfpXPos = xOffset + (x * section.pfpSize * SPACE_MULTIPLIER) + sectionHtml += await createPfp(pfpYPos,pfpXPos,section.pfpSize,sponsor,section.withNames) + x++ + } + y++ + } + + let sectionHeight: number = 40 + (y * ((section.pfpSize * SPACE_MULTIPLIER) + nameOffset)) + return {sectionHtml,sectionHeight} +} + +async function generateSections(sections:Section[],sponsors:Sponsor[]){ + let finalHeight: number = 10 + let finalHtml: string = "" + for (const section of sections){ + const sectionSponsors = sponsors.filter((s) => s.sectionId === section.id) + if (sectionSponsors.length < 1) continue + const {sectionHtml,sectionHeight} = await generateSection(finalHeight,section,sectionSponsors) + + finalHeight += sectionHeight + finalHtml += sectionHtml + } + + finalHeight += 10 + return {finalHeight,finalHtml} +} + +function generateFinalHtml(sectionsHtml:string,sectionHeight:number){ + return (` + + + ${sectionsHtml} + `) +} + +//GENERATE SPONSORS SVG +async function main(){ + const {finalHeight,finalHtml} = await generateSections(sponsorData.sections,sponsorData.sponsors) + fs.writeFileSync(path.join(process.cwd(),"./.github/SPONSORS.svg"),generateFinalHtml(finalHtml,finalHeight)) +} +main() \ No newline at end of file diff --git a/.tools/docker-compose.yml b/.tools/docker-compose.yml new file mode 100644 index 0000000..1788550 --- /dev/null +++ b/.tools/docker-compose.yml @@ -0,0 +1,14 @@ +# Docker Compose for Open Ticket +services: + openticket: + image: djj123dj/open-ticket:latest + volumes: + - config:/home/container/config + - database:/home/container/database + - plugins:/home/container/plugins + restart: unless-stopped + container_name: open-ticket +volumes: + config: + database: + plugins: \ No newline at end of file diff --git a/.docs/mergeTranslations.js b/.tools/mergeTranslations.ts similarity index 95% rename from .docs/mergeTranslations.js rename to .tools/mergeTranslations.ts index d789c9e..0535d40 100644 --- a/.docs/mergeTranslations.js +++ b/.tools/mergeTranslations.ts @@ -1,6 +1,7 @@ -//@ts-check -const fjs = require("formatted-json-stringify") -const fs = require("fs") +/// +import fjs from "formatted-json-stringify" +import fs from "fs" + const formatter = new fjs.ObjectFormatter(null,true,[ new fjs.ObjectFormatter("_TRANSLATION",true,[ new fjs.PropertyFormatter("otversion"), @@ -123,6 +124,8 @@ const formatter = new fjs.ObjectFormatter(null,true,[ new fjs.PropertyFormatter("helpPage"), new fjs.PropertyFormatter("withReason"), new fjs.PropertyFormatter("withoutTranscript"), + new fjs.PropertyFormatter("blacklistAdd"), + new fjs.PropertyFormatter("blacklistRemove"), ]), new fjs.ObjectFormatter("titles",true,[ new fjs.PropertyFormatter("created"), @@ -161,6 +164,7 @@ const formatter = new fjs.ObjectFormatter(null,true,[ new fjs.PropertyFormatter("prioritySet"), new fjs.PropertyFormatter("priorityGet"), new fjs.PropertyFormatter("transfer"), + new fjs.PropertyFormatter("transcripts"), ]), new fjs.ObjectFormatter("descriptions",true,[ new fjs.PropertyFormatter("create"), @@ -254,6 +258,8 @@ const formatter = new fjs.ObjectFormatter(null,true,[ new fjs.PropertyFormatter("prioritySetDm"), new fjs.PropertyFormatter("roleUpdateLog"), new fjs.PropertyFormatter("roleUpdateDm"), + new fjs.PropertyFormatter("topicSetLog"), + new fjs.PropertyFormatter("topicSetDm"), ]), ]), new fjs.ObjectFormatter("transcripts",true,[ @@ -276,6 +282,8 @@ const formatter = new fjs.ObjectFormatter(null,true,[ new fjs.PropertyFormatter("backup"), new fjs.PropertyFormatter("error"), new fjs.PropertyFormatter("title"), + new fjs.PropertyFormatter("noHistory"), + new fjs.PropertyFormatter("historyNotSupported"), ]), new fjs.ObjectFormatter("text",true,[ new fjs.PropertyFormatter("messagesTitle"), @@ -302,8 +310,9 @@ const formatter = new fjs.ObjectFormatter(null,true,[ new fjs.PropertyFormatter("unknownPanel"), new fjs.PropertyFormatter("notInGuild"), new fjs.PropertyFormatter("channelRename"), + new fjs.PropertyFormatter("channelCategory"), new fjs.PropertyFormatter("busy"), - new fjs.PropertyFormatter("permissionError"), + new fjs.PropertyFormatter("permissionError"), ]), new fjs.ObjectFormatter("descriptions",true,[ new fjs.PropertyFormatter("askForInfo"), @@ -325,11 +334,15 @@ const formatter = new fjs.ObjectFormatter(null,true,[ new fjs.PropertyFormatter("deprecatedTicket"), new fjs.PropertyFormatter("notInGuild"), new fjs.PropertyFormatter("channelRename"), + new fjs.PropertyFormatter("channelCategory"), new fjs.PropertyFormatter("channelRenameSource"), new fjs.PropertyFormatter("busy"), new fjs.PropertyFormatter("closeBeforeMessage"), new fjs.PropertyFormatter("closeBeforeAdminMessage"), new fjs.PropertyFormatter("unableToCreateTicket"), + new fjs.PropertyFormatter("messageMissing"), + new fjs.PropertyFormatter("stateExpired"), + new fjs.PropertyFormatter("panelStateExpired"), ]), new fjs.ObjectFormatter("optionInvalidReasons",true,[ new fjs.PropertyFormatter("stringRegex"), @@ -391,6 +404,8 @@ const formatter = new fjs.ObjectFormatter(null,true,[ new fjs.PropertyFormatter("syntax"), new fjs.PropertyFormatter("originalName"), new fjs.PropertyFormatter("newName"), + new fjs.PropertyFormatter("originalCategory"), + new fjs.PropertyFormatter("newCategory"), new fjs.PropertyFormatter("until"), new fjs.PropertyFormatter("validOptions"), new fjs.PropertyFormatter("validPanels"), @@ -412,6 +427,8 @@ const formatter = new fjs.ObjectFormatter(null,true,[ new fjs.PropertyFormatter("participants"), new fjs.PropertyFormatter("yes"), new fjs.PropertyFormatter("no"), + new fjs.PropertyFormatter("accept"), + new fjs.PropertyFormatter("cancel"), new fjs.PropertyFormatter("option"), new fjs.PropertyFormatter("topic"), new fjs.PropertyFormatter("uptime"), @@ -424,6 +441,7 @@ const formatter = new fjs.ObjectFormatter(null,true,[ new fjs.PropertyFormatter("admins"), new fjs.PropertyFormatter("roles"), new fjs.PropertyFormatter("size"), + ]), new fjs.ObjectFormatter("lowercase",true,[ new fjs.PropertyFormatter("text"), @@ -443,6 +461,7 @@ const formatter = new fjs.ObjectFormatter(null,true,[ new fjs.PropertyFormatter("panelAutoUpdate"), new fjs.PropertyFormatter("ticket"), new fjs.PropertyFormatter("ticketId"), + new fjs.PropertyFormatter("ticketOtherUser"), new fjs.PropertyFormatter("close"), new fjs.PropertyFormatter("delete"), new fjs.PropertyFormatter("deleteNoTranscript"), @@ -509,6 +528,8 @@ const formatter = new fjs.ObjectFormatter(null,true,[ new fjs.PropertyFormatter("priorityList"), new fjs.PropertyFormatter("transfer"), new fjs.PropertyFormatter("transferUser"), + new fjs.PropertyFormatter("transcripts"), + new fjs.PropertyFormatter("transcriptsUser"), ]), new fjs.ObjectFormatter("helpMenu",true,[ new fjs.PropertyFormatter("help"), @@ -599,6 +620,7 @@ const formatter = new fjs.ObjectFormatter(null,true,[ new fjs.PropertyFormatter("selectTicket"), new fjs.PropertyFormatter("selectRole"), new fjs.PropertyFormatter("selectOption"), + new fjs.PropertyFormatter("selectPriorityLevel"), ]), new fjs.ObjectFormatter("priorities",true,[ new fjs.PropertyFormatter("urgent"), @@ -611,14 +633,14 @@ const formatter = new fjs.ObjectFormatter(null,true,[ ]), ]) -for (const language of fs.readdirSync(".docs/languages/")){ +for (const language of fs.readdirSync(".tools/languages/")){ if (!fs.existsSync("./languages/"+language)){ console.log("language:",language,"does not exist yet in the primary ./languages/ folder. Unable to merge!") continue } console.log("merging "+language+"...") const original = JSON.parse(fs.readFileSync("./languages/"+language).toString()) - const newSentences = JSON.parse(fs.readFileSync(".docs/languages/"+language).toString()) + const newSentences = JSON.parse(fs.readFileSync(".tools/languages/"+language).toString()) for (const key of Object.keys(newSentences)){ if (key.startsWith("_")) continue @@ -643,7 +665,7 @@ for (const language of fs.readdirSync(".docs/languages/")){ } } original["_TRANSLATION"]["lastedited"] = new Date().toLocaleDateString("nl-BE",{day:"2-digit",month:"2-digit",year:"numeric"}) - original["_TRANSLATION"]["otversion"] = "v4.1.3" + original["_TRANSLATION"]["otversion"] = "v"+JSON.parse(fs.readFileSync("./package.json").toString()).version const finalText = formatter.stringify(original) fs.writeFileSync("./languages/"+language,finalText) } \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..fc1b717 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,21 @@ +# Docker File for Open Ticket +FROM node:22-alpine + +# /home/container keeps Pterodactyl panel compatibility +# chown the workdir so node user can create/remove subdirectories +RUN mkdir -p /home/container && chown node:node /home/container +WORKDIR /home/container + +# Install dependencies +COPY --chown=node:node package*.json ./ +RUN npm install + +# Copy app source +COPY --chown=node:node . . + +ENV NODE_ENV=production + +# Run as the built-in non-root node user +USER node + +CMD ["node", "index.js"] diff --git a/README.md b/README.md index 48f8a8f..32c3fbb 100644 --- a/README.md +++ b/README.md @@ -4,186 +4,172 @@ Related Projects:
Open Discord

Discord Invite Link -Open Ticket Version +Open Ticket Version Open Ticket Documentation Open Ticket License Open Ticket Stars -
Sponsor DJj123dj Open Ticket supports Docker! -Open Ticket supports Pterodactyl Eggs! +Open Ticket supports Pterodactyl Eggs!

-Open Ticket is the most advanced and customizable Discord ticket bot available. With 350+ configurable settings, you have full control over every aspect of your ticket system! -From HTML transcripts and Advanced Plugins to Claiming & Pinning, Questions & Modals, Detailed Statistics, and much more.

-The bot is fully translated into 36+ languages and has been battle-tested in large Discord servers.
-Need help or want to get involved? Feel free to join our Discord server. +Open Ticket is the most advanced and customizable Discord ticket bot available right now. It features more than 350+ configurable settings to control almost every aspect of your ticket system. +From HTML transcripts and Advanced Plugins to Claiming & Pinning, Modal Questions & Limits, Detailed Statistics, and much more. +The bot is fully translated into 38+ languages and has been battle-tested in large Discord servers. Need help or want to get involved? Feel free to join our Discord server.

⭐️ Support Open Ticket’s growth by starring this repo! ⭐️

-

❤️ Love Open Ticket? Sponsorships help fuel our HTML transcript servers and future features! ❤️

+

❤️ Love Open Ticket? Sponsorships help fuel our HTML transcript servers and future features! ❤️
+Open Ticket +

--- -> **[-> Navigate to (⏱️ Quick Setup)](#️-quick-start-using-cli)** -> **[-> Navigate to (📚 Documentation)](https://otdocs.dj-dj.be)** -> **[-> Navigate to (📞 Support Server)](https://discord.dj-dj.be)** - -### 📌 Features -- **⏳ Quick Setup** - Using the interactive Quick Setup CLI, you can **configure Open Ticket in less than 5min!** -- **🦇 Pterodactyl Support** - Open Ticket works perfect on Pterodactyl based panels. [(Download official eggs)](.eggs/README.md) -- **💩 No Credits** - Your bot won't contain any form of bloat or credits. It's all yours! -- **🔒 Private & Secure** - It has been battletested by thousands of servers and **respects security & privacy.** -- **📈 Scalable** - Made to handle huge servers and has already been **tested in servers with 100k members.** -- **📄 HTML Transcripts** - The **built-in HTML Transcripts Service** provides beautiful & easy-to-use transcripts. -- **✅ Ticket Status** - Close, reopen, delete, claim, pin, rename or move tickets in your server. -- **🇬🇧 Translation** - Every message has been translated in more than **36 languages** by our community. -- **🎨 Customisation** - More than **200+ settings** are related to customisation & advanced features. -- **🖥️ Interactions** - The bot has full support for buttons, dropdowns, slash/text commands & modals. -- **∞ Unlimited Possibilities** - Create an infinite amount of tickets, questions & panels. -- **📝 Advanced Plugins** - Create advanced plugins or use [**pre-made plugins**](#-plugins) by our community. -- **👥 Participants** - Add or remove participants & transfer ownership from one user to another. -- **📊 Detailed Statistics** - With more than **50+ statistics** for tickets, users & the server. -- **🚫 Blacklist** - Blacklist users to prevent them from creating new tickets. -- **🚨 Priorities** - Assign different **priority levels** to tickets to mark them as important. -- **❓ Modal Questions** - Give users the ability to **answer questions** in a modal before their ticket is created. -- **✨ Commands** - Manage all your tickets with more than 28+ commands. -- **🤖 Automation** - Automate ticket handling with **autoclose, autodelete** & slow mode. -- **😎 Additional Features** - For some weird reason, the bot also supports Reaction Role & URL Buttons. - -#### And even more using [pre-made community plugins](#-plugins)! - - **💬 Reviews** - Create & manage a support review system. - - **📢 Feedback** - Collect feedback & create forms for users to answer. - - **⏰ Reminders** - Create & manage customisable reminders. - - **🏷️ Tags** - Create tags & answer questions automatically using keywords. - - **📝 Forms** - Create advanced forms and automatically ask for repetitive questions. - - **🔄 Channel Display** - Create a voice channel with realtime statistics from the ticket system. - - **💾 SQLite Database** - Use an `SQLite` database for increased performance. - - **🎉 Custom Embeds** - Create your own embeds and send them using a command. - - **🎨 Customisation** - Yep, you heard it right. Even more customisation! - - **😁 And so much more...** - -### ⏱️ Quick Start (Using Interactive CLI Tool) -> 1. Download the latest version of Open Ticket on [Github](https://github.com/open-discord-bots/open-ticket). -> 2. Make sure Node.js & Npm are installed using `node -v` (minimum `v20`). -> 3. Install any required dependencies using `npm install`. -> 4. Start the **Quick Setup CLI** using `npm run setup`. -> 5. Click on `> ⏱️ Quick Setup` and follow the instructions. -> 6. Start the bot using `npm start` or `node index.js` -> - If required, the bot will give a report of errors that must be solved. -> - Follow the instructions and restart the bot. -> 7. Enjoy using Open Ticket! -> -> #### 🚦 Navigation +> **[-> Navigate to (⏱️ Quick Setup)](#️-quick-start)** > **[-> Navigate to (📚 Documentation)](https://otdocs.dj-dj.be)** > **[-> Navigate to (📞 Support Server)](https://discord.dj-dj.be)** -> **[-> Navigate to (🧩 Download Plugins)](https://odplugins.dj-dj.be)** +> **[-> Navigate to (🧩 Plugins/Addons)](https://odplugins.dj-dj.be)** +> **[-> Navigate to (🦇 Pterodactyl Eggs)](.github/pterodactyl-eggs/README.md)** + +### 📌 Features +#### Core Features +- **Ticket Management** - Close, reopen, delete, claim or pin tickets with ease. +- **Powerful Commands** - Manage your support system with **30+ commands** for staff & users. +- **Modal Questions** - Ask users **custom questions** before a ticket is created. +- **Priorities** - Assign **priority levels** to tickets to highlight urgent requests. +- **Participants** - Add or remove participants & transfer ownership from one user to another. +- **Adjustments** - Rename tickets, change ticket types or transfer ownership. +- **Blacklist & Limits** - Prevent users from creating tickets and set per-user or global ticket limits. +- **Highly Customisable** - Configure **350+ settings** covering appearance and behaviour. + +#### Ticket Automation & Workflows +- **Unlimited Possibilities** - Create unlimited tickets, panels & question flows. +- **Autoclose Tickets** - Automatically **close tickets** after predefined conditions. +- **Autodelete Tickets** - Automatically **delete closed tickets** to keep channels clean. +- **Category Routing** - Move tickets between categories based on claim or close state. + +#### Transcripts & Insights +- **HTML Transcripts** - Generate beautiful, easy-to-read **HTML transcripts** for every ticket. +- **Detailed Statistics** - Track **50+ statistics** for tickets, users and server activity. +- **Ticket Logs** - Track **all ticket events** such as creation, closures, and staff actions. + +#### User Experience +- **Fully Translated** - Available in **38+ languages**, translated and maintained by the community. +- **Modern Interactions** - Full support for buttons, dropdowns, slash/text commands & modals. +- **Panels** - Create messages with buttons or a dropdown for users to open tickets. +- **Sub-Panels** - One panel not enough? Use multiple panels to offer more choices. + +#### Plugins & Ecosystem +- **Plugin System** - Use custom plugins to **add new features** or **modify existing behavior** of the bot. +- **Community Plugins** - Use and share plugins built by the community. +- **Advanced API** - Build advanced plugins with access to ticket events and internal systems. +- **Integrations** - Connect Open Ticket with external services to automate workflows across platforms. +- **Bonus Features** - Somehow, we included Reaction Roles and URL Button support as well. + +#### Deployment +- **Quick Setup** - Easy **5-minute configuration** using the Interactive Setup CLI. +- **Scalable & Reliable** - Battle-tested in servers with **100k+ members**. +- **Private & Secure** - Used by thousands of servers with respect for security & privacy. +- **Pterodactyl Support** - 100% compatible with Pterodactyl panels. [(Download official eggs)](.eggs/README.md) +- **Docker Support** - Deploy Open Ticket in minutes with Docker containers. + +#### Extend functionality even more with our [pre-made community plugins](#-plugins)! +> - **Reviews** - Create and manage a support review system for tickets. +> - **Tags** - Define keywords that automatically trigger predefined responses. +> - **Reminders** - Create and manage custom reminders for users or staff. +> - **AI Integrations** - Connect to AI providers such as ChatGPT, Claude, or Gemini. +> - **Channel Display** - Create voice channels that display real-time ticket system statistics. +> - **Forms** - Build advanced forms for collecting structured information from users. +> - **Custom Embeds** - Create and send custom embeds via commands. +> - **Customization Tools** - Additional configuration options for advanced behavior and styling. +> - **Web Dashboard** - Configure and manage the bot through a remote web dashboard. +> - **Feedback** - Collect user feedback after ticket deletion using forms. +> - **SQLite Database** - Use an SQLite backend for improved performance and lightweight storage. +> - **And more** - Additional community plugins are available and actively expanding. + +## ⏱️ Quick Start +> 1. Download the [latest version of Open Ticket](https://github.com/open-discord-bots/open-ticket/releases/latest). +> 2. Make sure you have installed Node.js on your system (check using `node -v`, minimum `v20`). +> 3. Install any required dependencies using `npm install`. +> 4. Configure the bot in one of the following ways: +> - Method 1 (Easy): Start the **Quick Setup CLI** using `npm run setup`. +> - Method 2 (Hard): Manual **JSON configuration** in `./config/...` +> 5. If using the **Quick Setup CLI**, click on `> ⏱️ Quick Setup` and follow the instructions. +> 6. Start the bot using `npm start` or `node index.js` +> - If any config errors occur, the bot will give you a report of how to solve them. +> - Follow the instructions and restart the bot. +> 7. Enjoy using Open Ticket! +> 8. Install plugins from the [**Official Plugin Repository**](https://github.com/open-discord-bots/plugins) +> +> #### 🚦 Next Steps +> **[-> Navigate to (📚 Documentation)](https://otdocs.dj-dj.be)** +> **[-> Navigate to (📞 Support Server)](https://discord.dj-dj.be)** +> **[-> Navigate to (🧩 Plugins/Addons)](https://odplugins.dj-dj.be)** +> **[-> Navigate to (🦇 Pterodactyl Eggs)](.github/pterodactyl-eggs/README.md)** > > #### 🖥️ Recommended Hosting > - **A VPS (Virtual Private Server)** - Extra customisation & more stability. Recommended for most servers. > - **Any Pterodactyl-Based Panel** - Easy installation & configuration. -### ❤️ Sponsors -Huge thanks to our sponsors for making this project possible. Your support means everything to us. - - - - - - - - - - - -
Profile PictureProfile PictureProfile Picture
guillee3yeeetSKjacobhumston
+## 📸 Previews +Preview of: Advanced Ticket Management +Preview of: Customisable Ticket Panels +Preview of: Modal Questions & Forms +Preview of: Detailed Statistics & Insights +Preview of: 30+ Powerful Commands +Preview of: Buttons, Transcripts, Limits & More! -**Past Sponsors:**
- -SpyEye -Mods HD -DOSEV5 -BENZORICH - +## 💬 Translations +With the amazing support of our translators, we've been able to translate Open Ticket in more than **38 languages**! +#### Categories: 🟢 Available - 🤖 Partially Made Using AI - 🟠 Incomplete - 🔴 Unavailable/Outdated -## 📸 Preview -An example of a panel. -An example of a ticket message. -Examples of built-in commands. - -## 🛠️ Contributors -### 🖥️ Team & Contributors -A list of amazing people who have contributed or provided supported for **Open Ticket** and **Open Discord**. - - - - - - - - - - - - - - - - - -
Profile PictureProfile PictureProfile PictureProfile PictureProfile PictureProfile Picture
💻🧩💬 DJj123dj🧩💬 Guillee3💬 smetsliam💬 Frank Vissers💬 Sanke🧩 SKaranjaN
- -### 💬 Translators -With the amazing support of our translators, we've been able to translate Open Ticket in more than **36 languages**! -#### Categories: -- **🟢 Available** -- **🤖 Partially Made Using AI** -- **🟠 Incomplete** -- **🔴 Unavailable/Outdated** - -|🔍 |Languages (36) |Maintainer (Github/Discord) | -|----|---------------------|--------------------------------| -|🟢 |🇬🇧 English |djj123dj | -|🟢 |🇳🇱 Dutch |djj123dj | -|🟢 |🇩🇪 German |benzorich | -|🟢 |🇫🇷 French |guillee.3 | -|🟢 |🇪🇸 Spanish |redactado & josuens | -|🟢 |🇵🇹 Portuguese |quiradon | -|🟢 |🇮🇹 Italian |fraden1mvp. & imperatorix_17 | -|🟢 |🇸🇪 Swedish |NoOneNook | -|🟢 |🇳🇴 Norwegian |NoOneNook | -|🟢 |🇹🇭 Thai |modshd | -|🟢 |🇮🇳 Hindi |challenger_nova | -|🟢 |🇭🇺 Hungarian |kornel0706 | -|🟢 |🇮🇩 Indonesian |erxg | -|🟢 |🇱🇹 Lithuanian |tsgindrius | -|🟢 |🇺🇦 Ukrainian |anderskiy | -|🟢 |🇨🇿 Czech |spyeye_ | -|🟢 |🇷🇴 Romanian |sankedev | -|🟢 |🇩🇰 Danish |the_gamer | -|🟢 |🇹🇷 Turkish |palestinian | -|🟢 |🇦🇪 Arabic |palestinian | -|🟢 |🇵🇱 Polish |danoglez | -|🟢 |🇮🇷 Persian |dysashop & zhavis | -|🟢 |🇧🇩 Bengali |HanumeshGupta | -|🟢 |❓ Catalan |guillee3 | -|🤖 |🇪🇪 Estonian |iamnotmega | -|🤖 |🇫🇮 Finnish |iamnotmega | -|🤖 |🇯🇵 Japanese |HanumeshGupta | -|🤖 |🇬🇷 Greek |HanumeshGupta | -|🤖 |🇸🇮 Slovenian |HanumeshGupta | -|🤖 |🇰🇷 Korean |HanumeshGupta | -|🤖 |🇮🇳 Tamil |HanumeshGupta | -|🤖 |🇨🇳 Simplified Chinese |HanumeshGupta | -|🤖 |❓ Kurdish |HanumeshGupta | -|🤖 |🇷🇺 Russian |NoOneNook | -|🤖 |🇱🇻 Latvian |NoOneNook | -|🤖 |🇻🇳 Vietnamese |ngocdiep2006 | -|🔴 |🇨🇳 Traditional Chinese|[⭐ Contribute!](.github/CONTRIBUTING.md)| +|🔍 |Languages (38) |Config Value |Maintainers (Github/Discord) | +|----|----------------------|------------------------|--------------------------------| +|🟢 |🇬🇧 English |`"english"` |djj123dj | +|🟢 |🇳🇱 Dutch |`"dutch"` |djj123dj | +|🟢 |🇩🇪 German |`"german"` |benzorich | +|🟢 |🇫🇷 French |`"french"` |guillee.3 | +|🟢 |🇪🇸 Spanish |`"spanish"` |Reddishye & josuens | +|🟢 |🇵🇹 Portuguese |`"portuguese"` |quiradon | +|🟢 |🇮🇹 Italian |`"italian"` |fraden1mvp. & imperatorix_17 | +|🟢 |🇸🇪 Swedish |`"swedish"` |NoOneNook | +|🟢 |🇳🇴 Norwegian |`"norwegian"` |NoOneNook | +|🟢 |🇹🇭 Thai |`"thai"` |modshd | +|🟢 |🇮🇳 Hindi |`"hindi"` |challenger_nova | +|🟢 |🇭🇺 Hungarian |`"hungarian"` |kornel0706 | +|🟢 |🇮🇩 Indonesian |`"indonesian"` |erxg | +|🟢 |🇱🇹 Lithuanian |`"lithuanian"` |tsgindrius | +|🟢 |🇺🇦 Ukrainian |`"ukrainian"` |anderskiy | +|🟢 |🇨🇿 Czech |`"czech"` |spyeye_ | +|🟢 |🇷🇴 Romanian |`"romanian"` |sankedev | +|🟢 |🇩🇰 Danish |`"danish"` |the_gamer | +|🟢 |🇹🇷 Turkish |`"turkish"` |palestinian | +|🟢 |🇦🇪 Arabic |`"arabic"` |palestinian | +|🟢 |🇵🇱 Polish |`"polish"` |danoglez | +|🟢 |🇮🇷 Persian |`"persian"` |dysashop & zhavis | +|🟢 |🇧🇩 Bengali |`"bengali"` |HanumeshGupta | +|🟢 |❓ Catalan |`"catalan"` |guillee3 | +|🟢 |🇨🇳 Traditional Chinese|`"traditional-chinese"` |me.october | +|🟢 |🇰🇭 Khmer (Cambodia) |`"khmer"` |yuuslokrobjakkroval | +|🤖 |🇪🇪 Estonian |`"estonian"` |iamnotmega | +|🤖 |🇫🇮 Finnish |`"finnish"` |iamnotmega | +|🤖 |🇯🇵 Japanese |`"japanese"` |HanumeshGupta | +|🤖 |🇬🇷 Greek |`"greek"` |HanumeshGupta | +|🤖 |🇸🇮 Slovenian |`"slovenian"` |HanumeshGupta | +|🤖 |🇰🇷 Korean |`"korean"` |HanumeshGupta | +|🤖 |🇮🇳 Tamil |`"tamil"` |HanumeshGupta | +|🤖 |❓ Kurdish |`"kurdish"` |HanumeshGupta | +|🤖 |🇷🇺 Russian |`"russian"` |NoOneNook | +|🤖 |🇱🇻 Latvian |`"latvian"` |NoOneNook | +|🤖 |🇻🇳 Vietnamese |`"vietnamese"` |ngocdiep2006 | +|🤖 |🇨🇳 Simplified Chinese |`"simplified-chinese"` |HanumeshGupta | +## 😎 Hall Of Fame +The full list of contributors for Open Ticket and Open Discord. + ## ⭐️ Star History -If you enjoy using Open ticket, **consider starring** this repository. +If you enjoy using Open ticket, **consider starring** our repository. This will help us grow and reach even more people! @@ -194,66 +180,6 @@ This will help us grow and reach even more people! -## 🧩 Plugins -**Download all plugins from our [Official Plugin Repository](https://github.com/open-discord-bots/plugins)!**
-> #### ⭐ Featured Plugins (Top 5 Most Used) -> **[`ot-sqlite-database`](https://github.com/open-discord-bots/plugins/tree/main/open-ticket/ot-sqlite-database/), -> [`ot-reviews`](https://github.com/open-discord-bots/plugins/tree/main/open-ticket/ot-reviews/), -> [`ot-feedback`](https://github.com/open-discord-bots/plugins/tree/main/open-ticket/ot-feedback/), -> [`ot-tags`](https://github.com/open-discord-bots/plugins/tree/main/open-ticket/ot-tags/), -> [`ot-restrictions`](https://github.com/open-discord-bots/plugins/tree/main/open-ticket/ot-restrictions/)** - -#### Categories: -- **📄 Example** - These plugins serve as an example or starting template. -- **📢 Command** - These plugins add new commands to the bot. -- **⚙️ Utility** - These plugins help with utility systems. You might not notice them as a ticket user/admin directly. -- **🎨 Customisation** - These plugins add even more customisation to the bot. -- **💼 Management** - These plugins add features that help you manage your server or ticket system. -- **🤖 Client** - These plugins add features affecting the Discord Client or bot itself. -- Please Create a new category when your plugin doesn't fit in one of the available categories. - -### 📦 Official *(made by DJdj Development)* -|Name |Category |Description | -|----------------------------------------------------------------------|----------------------------|-------------------------| -|[`example-plugin`](https://github.com/open-discord-bots/plugins/tree/main/open-ticket/example-plugin/) |📄 Example |This is just an example plugin for people that want to create their own plugin. | -|[`example-command`](https://github.com/open-discord-bots/plugins/tree/main/open-ticket/example-command/) |📄 Example |Sample custom command using the Open Discord system. | -|[`ot-jump-to-top`](https://github.com/open-discord-bots/plugins/tree/main/open-ticket/ot-jump-to-top/) |📢 Command |Add a simple command to jump to the top of the ticket. | -|[`ot-kill-switch`](https://github.com/open-discord-bots/plugins/tree/main/open-ticket/ot-kill-switch/) |📢 Command |Temporarily disable the ticket system using a kill switch. | -|[`ot-hosting-status`](https://github.com/open-discord-bots/plugins/tree/main/open-ticket/ot-hosting-status/) |📢 Command |A simple command to send hosting status updates to a channel. | -|[`ot-shutdown`](https://github.com/open-discord-bots/plugins/tree/main/open-ticket/ot-shutdown/) |📢 Command |A simple command to turn off the bot from a slash command (server & bot owner only). | -|[`ot-sqlite-database`](https://github.com/open-discord-bots/plugins/tree/main/open-ticket/ot-sqlite-database/) |⚙️ Utility |With this plugin, the database will be an SQLite file. It's a must-have for large servers! | -|[`ot-no-slash-clear`](https://github.com/open-discord-bots/plugins/tree/main/open-ticket/ot-no-slash-clear/) |⚙️ Utility |Disable the automatic removal of slash commands that aren't used by Open Ticket. | -|[`ot-migrate-v3`](https://github.com/open-discord-bots/plugins/tree/main/open-ticket/ot-migrate-v3/) |⚙️ Utility |Use this plugin to migrate all tickets from Open Ticket v3 to v4. | -|[`ot-ticket-message-extras`](https://github.com/open-discord-bots/plugins/tree/main/open-ticket/ot-ticket-message-extras/) |🎨 Customisation |A plugin which adds a few little features to the ticket message. | -|[`ot-rename-keep-prefix`](https://github.com/open-discord-bots/plugins/tree/main/open-ticket/ot-rename-keep-prefix/) |🎨 Customisation |Simple plugin to keep the channel prefix when using the /rename command. | -|[`ot-customise-buttons`](https://github.com/open-discord-bots/plugins/tree/main/open-ticket/ot-customise-buttons/) |🎨 Customisation |Customise almost all built-in buttons. This includes the claim, reopen, close & delete buttons. | -|[`ot-ephemeral-messages`](https://github.com/open-discord-bots/plugins/tree/main/open-ticket/ot-ephemeral-messages/) |🎨 Customisation |Customise for every messages if it needs to be ephemeral or not. | -|[`ot-footers`](https://github.com/open-discord-bots/plugins/tree/main/open-ticket/ot-footers/) |🎨 Customisation |A simple plugin to add footers in all Open Ticket embeds. | -|[`ot-alt-detector`](https://github.com/open-discord-bots/plugins/tree/main/open-ticket/ot-alt-detector/) |💼 Management |Use the discord-alt-detector npm package by DJdj Development in your ticket bot. | -|[`ot-embeds`](https://github.com/open-discord-bots/plugins/tree/main/open-ticket/ot-embeds/) |💼 Management |Create custom premade embeds in the config or use the command to create one from scratch. | -|[`ot-move-actions`](https://github.com/open-discord-bots/plugins/tree/main/open-ticket/ot-move-actions/) |💼 Management |Automatically unclaim/unpin a ticket when it's moved using `/move`. | -|[`ot-reviews`](https://github.com/open-discord-bots/plugins/tree/main/open-ticket/ot-reviews/) |💼 Management |Review system for Open Ticket! It is very customisable and has lots of features. | -|[`ot-tags`](https://github.com/open-discord-bots/plugins/tree/main/open-ticket/ot-tags/) |💼 Management |Use tags to quickly reply with a pre-existing text. | -|[`ot-restrictions`](https://github.com/open-discord-bots/plugins/tree/main/open-ticket/ot-restrictions/) |💼 Management |Restrict which roles can open a specific ticket option. | -|[`ot-better-status`](https://github.com/open-discord-bots/plugins/tree/main/open-ticket/ot-better-status/) |🤖 Client |An advanced status plugin to rotate between states. It also allows for the use of variables. | -|[`ot-channel-display`](https://github.com/open-discord-bots/plugins/tree/main/open-ticket/ot-channel-display/) |🤖 Client |A plugin to show different variables in a voice channel in your server. | -|[`ot-vanity`](https://github.com/open-discord-bots/plugins/tree/main/open-ticket/ot-vanity/) |🤖 Client |A plugin to detect the vanity status of members in the server and give them exclusive privilleges. | - -### ✅ Verified *(made by community)* -|Name |Author |Category |Description | -|---------------------------------------------------------------------|----------------------------|----------------------------|-------------------------| -|[`ot-config-reload`](https://github.com/open-discord-bots/plugins/tree/main/open-ticket/ot-config-reload/) |guillee.3 |⚙️ Utility |This plugin adds a new command that allows reloading the Open Ticket config files without the need for a restart. | -|[`ot-feedback`](https://github.com/open-discord-bots/plugins/tree/main/open-ticket/ot-feedback/) |an_developer |💼 Management |A plugin to gather feedback of your support service. | -|[`ot-assign-role`](https://github.com/open-discord-bots/plugins/tree/main/open-ticket/ot-assign-role/) |rapid.fast |💼 Management |This plugin assigns a predefined role to a user upon creating a ticket. | -|[`ot-moderation`](https://github.com/open-discord-bots/plugins/tree/main/open-ticket/ot-moderation/) |NotMukundOP |💼 Management |A simple moderation plugin for Open Discord with ban, kick & warnings. | -|[`ot-template-system`](https://github.com/open-discord-bots/plugins/tree/main/open-ticket/ot-template-system/) |DanoGlez |💼 Management |Predefined template system for sending quick messages. | -|[`ot-volume-warning`](https://github.com/open-discord-bots/plugins/tree/main/open-ticket/ot-volume-warning/) |guillee.3 |💼 Management |Alerts ticket creators when too many tickets are open, indicating possible response delays. | -|[`ot-reminders`](https://github.com/open-discord-bots/plugins/tree/main/open-ticket/ot-reminders/) |guillee.3 |💼 Management |Set reminders that will be sent to a channel every specified time. | -|[`ot-ticket-forms`](https://github.com/open-discord-bots/plugins/tree/main/open-ticket/ot-ticket-forms/) |guillee.3 |💼 Management |An advanced forms plugin for Open Ticket. | -|[`ot-followups`](https://github.com/open-discord-bots/plugins/tree/main/open-ticket/ot-followups/) |guillee.3 |💼 Management |Send additional follow-up messages to a ticket. | -|[`ot-twitch-notifier`](https://github.com/open-discord-bots/plugins/tree/main/open-ticket/ot-twitch-notifier/) |guillee.3 |💼 Management |Get notified when your favorite Twitch streamers go live. | -|[`ot-translate-cmds`](https://github.com/open-discord-bots/plugins/tree/main/open-ticket/ot-translate-cmds/) |guillee.3 |🤖 Client |Translate all built-in command names, descriptions & options. | - --- Open Ticket Logo diff --git a/config/general.json b/config/general.json deleted file mode 100644 index cd43647..0000000 --- a/config/general.json +++ /dev/null @@ -1,125 +0,0 @@ -{ - "_INFO":{ - "support":"https://otdocs.dj-dj.be", - "discord":"https://discord.dj-dj.be", - "version":"open-ticket-v4.1.3" - }, - - "token":"insert your bot token here! (or leave empty when using 'tokenFromENV')", - "tokenFromENV":false, - - "mainColor":"#f8ba00", - "language":"english", - "prefix":"!ticket ", - "serverId":"discord server id", - "globalAdmins":["discord role id"], - - "slashCommands":true, - "textCommands":true, - - "status":{ - "enabled":true, - "type":"listening OR watching OR playing OR custom", - "mode":"online OR invisible OR idle OR dnd", - "text":"/help", - "state":"(additional text or leave empty)" - }, - - "system":{ - "preferSlashOverText":true, - "sendErrorOnUnknownCommand":true, - "questionFieldsInCodeBlock":true, - "displayFieldsWithQuestions":false, - "showGlobalAdminsInPanelRoles":false, - "disableVerifyBars":false, - "useRedErrorEmbeds":true, - "alwaysShowReason":false, - "emojiStyle":"before (OR after OR double OR disabled)", - "pinEmoji":"📌", - - "replyOnTicketCreation":true, - "replyOnReactionRole":true, - "askPriorityOnTicketCreation":false, - "removeParticipantsOnClose":false, - "disableAutocloseAfterReopen":true, - "autodeleteRequiresClosedTicket":true, - "adminOnlyDeleteWithoutTranscript":true, - "allowCloseBeforeMessage":false, - "allowCloseBeforeAdminMessage":true, - "useTranslatedConfigChecker":true, - "pinFirstTicketMessage":false, - - "enableTicketClaimButtons":true, - "enableTicketCloseButtons":true, - "enableTicketPinButtons":true, - "enableTicketDeleteButtons":true, - "enableTicketActionWithReason":true, - "enableDeleteWithoutTranscript":true, - - "logs":{ - "enabled":false, - "channel":"discord channel id" - }, - - "limits":{ - "enabled":true, - "globalMaximum":50, - "userMaximum":3 - }, - - "channelTopic":{ - "showOptionName":true, - "showOptionDescription":false, - "showOptionTopic":true, - "showPriority":false, - "showClosed":true, - "showClaimed":false, - "showPinned":false, - "showCreator":false, - "showParticipants":false - }, - - "permissions":{ - "help":"everyone (OR admin OR none OR role id)", - "panel":"admin (OR everyone OR none OR role id)", - "ticket":"none (OR admin OR everyone OR role id)", - "close":"everyone (OR admin OR none OR role id)", - "delete":"admin (OR everyone OR none OR role id)", - "reopen":"everyone (OR admin OR none OR role id)", - "claim":"admin (OR everyone OR none OR role id)", - "unclaim":"admin (OR everyone OR none OR role id)", - "pin":"admin (OR everyone OR none OR role id)", - "unpin":"admin (OR everyone OR none OR role id)", - "move":"admin (OR everyone OR none OR role id)", - "rename":"admin (OR everyone OR none OR role id)", - "add":"admin (OR everyone OR none OR role id)", - "remove":"admin (OR everyone OR none OR role id)", - "blacklist":"admin (OR everyone OR none OR role id)", - "stats":"everyone (OR admin OR none OR role id)", - "clear":"admin (OR everyone OR none OR role id)", - "autoclose":"admin (OR everyone OR none OR role id)", - "autodelete":"admin (OR everyone OR none OR role id)", - "transfer":"admin (OR everyone OR none OR role id)", - "topic":"admin (OR everyone OR none OR role id)", - "priority":"admin (OR everyone OR none OR role id)" - }, - - "messages":{ - "creation":{"dm":false,"logs":true}, - "closing":{"dm":false,"logs":true}, - "deleting":{"dm":false,"logs":true}, - "reopening":{"dm":false,"logs":true}, - "claiming":{"dm":false,"logs":true}, - "pinning":{"dm":false,"logs":true}, - "adding":{"dm":false,"logs":true}, - "removing":{"dm":false,"logs":true}, - "renaming":{"dm":false,"logs":true}, - "moving":{"dm":false,"logs":true}, - "blacklisting":{"dm":false,"logs":true}, - "transferring":{"dm":false,"logs":true}, - "topicChange":{"dm":false,"logs":true}, - "priorityChange":{"dm":false,"logs":true}, - "reactionRole":{"dm":false,"logs":true} - } - } -} \ No newline at end of file diff --git a/config/general.jsonc b/config/general.jsonc new file mode 100644 index 0000000..a5830b9 --- /dev/null +++ b/config/general.jsonc @@ -0,0 +1,175 @@ +/* + * Hi there! Thank you for installing Open Ticket. + * ---------------------------------------------- + * If you need any assistance with configuring the bot, + * feel free to use the documentation or join our Discord server: + * https://otdocs.dj-dj.be + * https://discord.dj-dj.be + * ---------------------------------------------- + * SETUP: + * 1. Install the required dependencies using the command: "npm install" + * 2. Configure the bot in one of the following ways: + * a. (easy) Using the Quick Setup CLI Tool + * b. (difficult) Using the JSON files in `./config/` + * + * Start the Quick Setup CLI Tool using the command: "npm run setup" + * After configuration, start the bot using the command: "npm start" + * + * Good luck! DJj123dj & contributors + */ +{ + "_CONFIG_VERSION":"open-ticket-v4.2.0", + + /* Load the bot token from .env or the "token" field below. Leave "token" empty if using "tokenFromENV". */ + "token":"INSERT_BOT_TOKEN", + "tokenFromENV":false, + + "mainColor":"#f8ba00", //Hex color used in most embeds + "language":"english", //Visit README.md for list + "prefix":"!ticket ", //Prefix used in text commands + "serverId":"DISCORD_SERVER_ID", + "globalAdmins":["DISCORD_ROLE_ID"], //Have access to all commands + + /* Enable/disable text or slash commands. */ + "slashCommands":true, + "textCommands":true, + + /* Configure the status of the bot. */ + "status":{ + "enabled":true, + "type":"listening", //Choices: listening, watching, playing, custom + "mode":"online", //Choices: online, invisible, idle, dnd + "text":"/help", + "state":"" //Additional text (Leave empty to disable) + }, + + /* Send ticket logs to a channel or in DM of the ticket creator. */ + "logs":{ + "enabled":false, + "channel":"DISCORD_CHANNEL_ID", + "logMessages":{ + "creation":{"dm":false,"logs":true}, + "closing":{"dm":false,"logs":true}, + "deleting":{"dm":false,"logs":true}, + "reopening":{"dm":false,"logs":true}, + "claiming":{"dm":false,"logs":true}, + "pinning":{"dm":false,"logs":true}, + "adding":{"dm":false,"logs":true}, + "removing":{"dm":false,"logs":true}, + "renaming":{"dm":false,"logs":true}, + "moving":{"dm":false,"logs":true}, + "blacklisting":{"dm":false,"logs":true}, + "transferring":{"dm":false,"logs":true}, + "topicChange":{"dm":false,"logs":true}, + "priorityChange":{"dm":false,"logs":true}, + "reactionRole":{"dm":false,"logs":true} + } + }, + + /* A large collection of settings for the ticket system. */ + "ticketSystem":{ + "preferSlashOverText":true, //Show slashcmds in help menu's + "sendErrorOnUnknownCommand":true, //Send error when command not found + "questionFieldsInCodeBlock":true, //Put question answers in code blocks + "displayFieldsWithQuestions":false, //Display embed fields together with question answers + "showGlobalAdminsInPanelRoles":false, //Include "globalAdmins" in panel admin lists + "disableVerifyBars":false, //Disable the (❌/✅) buttons + "useRedErrorEmbeds":true, //Make errors embeds always red + "alwaysShowReason":false, //Show reason even if none is provided + "emojiStyle":"before", //The style of emoji's in embeds. Choices: before, after, double, disabled + "pinEmoji":"📌", //Channel emoji of pinned tickets (Leave empty to disable) + "closeEmoji":"", //Channel emoji of closed tickets (Leave empty to disable) + + "replyOnTicketCreation":true, //Reply with a msg when a ticket is created + "replyOnReactionRole":true, //Reply with a msg when a reaction role is used + "askPriorityOnTicketCreation":true, //Show a dropdown to select priority + "removeParticipantsOnClose":false, //Remove non-admins when ticket is closed + "disableAutocloseAfterReopen":true, //Disable autoclose after ticket got reopened + "autodeleteRequiresClosedTicket":true, //A ticket must be closed before autodelete works + "adminOnlyDeleteWithoutTranscript":true, //Only allow "globalAdmins" to delete a ticket without transcript + "allowCloseBeforeMessage":false, //Allow closing before a message is sent + "allowCloseBeforeAdminMessage":true, //Allow closing before an admin has sent a message + "useTranslatedConfigChecker":true, //Translate config errors in the console + "pinFirstTicketMessage":true, //Pin the ticket message to the channel + + /* Enable/disable certain buttons & features of the bot. */ + "enableTicketClaimButtons":true, + "enableTicketCloseButtons":true, + "enableTicketPinButtons":true, + "enableTicketDeleteButtons":true, + "enableTicketActionWithReason":true, + "enableDeleteWithoutTranscript":true, //Allow deleting tickets without transcript + "enableCreateTicketForOtherUser":true, //Allow creating tickets for other users + + /* Set the maximum amount of simultaneous tickets. */ + "limits":{ + "enabled":true, + "globalMaximum":50, + "userMaximum":3 + }, + + /* Choose which data is shown in the channel topic. */ + "channelTopic":{ + "showOptionName":true, + "showOptionDescription":false, + "showOptionTopic":true, + "showPriority":false, + "showClosed":true, + "showClaimed":false, + "showPinned":false, + "showCreator":false, + "showParticipants":false + }, + + /* Move closed tickets to a separate category. */ + "closedCategory":{ + "enabled":false, + "categoryId":"DISCORD_CATEGORY_ID" + }, + + /* Create tickets in a backup category when the original category exceeds 50 channels. */ + "backupCategory":{ + "enabled":false, + "categoryId":"DISCORD_CATEGORY_ID" + }, + + /* Move claimed tickets to a matching category of the user that claimed the ticket. Set to empty list [] to disable. */ + "claimedCategories":[ + {"user":"DISCORD_USER_ID","category":"DISCORD_CATEGORY_ID"} + ] + }, + + /* + * Set permissions for each individual command, button or action. + * CHOICES: + * >> "none" -> Command disabled + * >> "everyone" -> Allowed for everyone + * >> "admin" -> Global & ticket admins only + * >> "DISCORD_ROLE_ID" -> Custom role only + */ + "permissions":{ + "help":"everyone", + "panel":"admin", + "ticket":"none", + "close":"everyone", + "delete":"admin", + "reopen":"everyone", + "claim":"admin", + "unclaim":"admin", + "pin":"admin", + "unpin":"admin", + "move":"admin", + "rename":"admin", + "add":"admin", + "remove":"admin", + "blacklist":"admin", + "stats":"everyone", + "clear":"admin", + "autoclose":"admin", + "autodelete":"admin", + "transfer":"admin", + "topic":"admin", + "priority":"admin", + "transcripts":"admin" + } +} \ No newline at end of file diff --git a/config/options.json b/config/options.json deleted file mode 100644 index 981a1bc..0000000 --- a/config/options.json +++ /dev/null @@ -1,126 +0,0 @@ -[ - { - "id":"example-ticket", - "name":"Question", - "description":"Create this ticket if you have a question! (or leave empty)", - "type":"ticket", - - "button":{ - "emoji":"🎫 (or leave empty)", - "label":"question (or leave empty)", - "color":"gray OR red OR green OR blue" - }, - - "ticketAdmins":["discord role id"], - "readonlyAdmins":["discord role id"], - "allowCreationByBlacklistedUsers":false, - "questions":["example-question-1","example-question-2"], - - "channel":{ - "prefix":"question-", - "suffix":"user-name OR user-id OR random-number OR random-hex OR counter-dynamic OR counter-fixed", - "category":"category id (or leave empty)", - "closedCategory":"category id (or leave empty)", - "backupCategory":"category id (or leave empty)", - "claimedCategory":[ - {"user":"user id","category":"category id"} - ], - "topic":"This is the topic of this ticket channel and is visible to everyone! (or leave empty)" - }, - - "dmMessage":{ - "enabled":false, - "text":"Thank you for creating a ticket in our server! (or leave empty)", - "embed":{ - "enabled":false, - "title":"Embed Title! (or leave empty)", - "description":"Description (or leave empty)", - "customColor":"#f8ab00 (or leave empty)", - - "image":"https://www.example.com/image.png (or leave empty)", - "thumbnail":"https://www.example.com/image.png (or leave empty)", - "fields":[ - {"name":"field name","value":"field value","inline":false} - ], - "timestamp":false - } - }, - "ticketMessage":{ - "enabled":true, - "text":"", - "embed":{ - "enabled":true, - "title":"Question Ticket", - "description":"Thank you for creating a 'Question' ticket in our server!\nOur support team will help you as soon as possible!", - "customColor":"#f8ab00 (or leave empty)", - - "image":"https://www.example.com/image.png (or leave empty)", - "thumbnail":"https://www.example.com/image.png (or leave empty)", - "fields":[ - {"name":"field name","value":"field value","inline":false} - ], - "timestamp":false - }, - "ping":{ - "@here":true, - "@everyone":false, - "custom":["discord role id"] - } - }, - "autoclose":{ - "enableInactiveHours":false, - "inactiveHours":24, - "enableUserLeave":false, - "disableOnClaim":false - }, - "autodelete":{ - "enableInactiveDays":false, - "inactiveDays":7, - "enableUserLeave":false, - "disableOnClaim":false - }, - "cooldown":{ - "enabled":false, - "cooldownMinutes":10 - }, - "limits":{ - "enabled":false, - "globalMaximum":20, - "userMaximum":3 - }, - "slowMode":{ - "enabled":false, - "slowModeSeconds":20 - } - }, - { - "id":"example-website", - "name":"Website", - "description":"Go to our website.", - "type":"website", - - "button":{ - "emoji":"😃", - "label":"Visit our website" - }, - - "url":"https://www.dj-dj.be" - }, - { - "id":"example-role", - "name":"Update Ping", - "description":"Click here to get notified on updates!", - "type":"role", - - "button":{ - "emoji":"📢", - "label":"Update Ping", - "color":"gray OR red OR green OR blue" - }, - - "roles":["discord role id"], - "mode":"add&remove OR remove OR add", - "removeRolesOnAdd":["discord role id"], - "addOnMemberJoin":false - } -] \ No newline at end of file diff --git a/config/options.jsonc b/config/options.jsonc new file mode 100644 index 0000000..c1d7bf0 --- /dev/null +++ b/config/options.jsonc @@ -0,0 +1,165 @@ +/* + * OPEN TICKET BUTTON OPTIONS + * ---------------------------------------------- + * Create customizable ticket, website, reaction-role or sub-panel button options. + * Up to 25 options can be added to each panel in (config/panels.jsonc) + * There are 4 types of options available: ticket, website, role, sub-panel + * + * TIP: Create new options by copying everything between and including the {...} brackets of an option. Paste it after the last option and make sure that they are seperated by a comma. + */ +[ + { + /* A ticket option creates a button to open a ticket. */ + "id":"example-ticket", + "name":"Question", + "description":"Want to tell us something? Create this ticket for general questions for our support team.", //Leave empty to disable + "type":"ticket", + + "button":{ + /* Configure the button style of this option. At least one of "emoji" or "label" must be provided. */ + "emoji":"🎫", + "label":"Question", + "color":"gray" //Choices: gray, red, green, blue + }, + + /* Add up to 5 modal questions IDs from (config/questions.jsonc). */ + "questions":["example-question-1","example-question-2"], + + "ticketAdmins":["DISCORD_ROLE_ID"], + "readonlyAdmins":["DISCORD_ROLE_ID"], + "allowCreationByBlacklistedUsers":false, + + "channel":{ + /* Configure the name, topic and category of the ticket option. */ + "prefix":"question-", + "suffix":"user-name", //Choices: user-name, user-id, random-number, random-hex, counter-dynamic, counter-fixed + "category":"DISCORD_CATEGORY_ID", //Leave empty to disable + "topic":"The ticket topic shown in the channel." //Leave empty to disable + }, + + "dmMessage":{ + /* Send a customisable message in DM when creating a ticket. */ + "enabled":false, + "text":"", //Leave empty to disable + "embed":{ + "enabled":false, + "title":"Question Ticket", //Leave empty to disable + "description":"Thank you for creating a ticket in our server. We will try to help you as soon as possible.", //Leave empty to disable + "customColor":"#f8ab00", //Leave empty to use default color + + "image":"", //Image URL. Leave empty to disable + "thumbnail":"", //Image URL. Leave empty to disable + /* Embed fields. Set to empty list [] to disable. */ + "fields":[ + {"name":"FIELD_NAME","value":"FIELD_VALUE","inline":false} + ], + "timestamp":false + } + }, + "ticketMessage":{ + /* Send a customisable message in the ticket with close, claim, delete, ... buttons. */ + "enabled":true, + "text":"", //Leave empty to disable + "embed":{ + "enabled":true, + "title":"Question Ticket", //Leave empty to disable + "description":"Thank you for creating a ticket in our server.\nOur support team will assist you as soon as possible.", //Leave empty to disable + "customColor":"#f8ab00", //Leave empty to use default color + + "image":"", //Image URL. Leave empty to disable + "thumbnail":"", //Image URL. Leave empty to disable + /* Embed fields. Set to empty list [] to disable. */ + "fields":[ + {"name":"FIELD_NAME","value":"FIELD_VALUE","inline":false} + ], + "timestamp":false + }, + "ping":{ + /* Customise the user & role mentions of this ticket message. */ + "@here":true, + "@everyone":false, + "custom":["DISCORD_ROLE_ID"] + } + }, + "autoclose":{ + /* Autoclose this ticket after a period of inactivity or when the creator leaves the server. */ + "enableInactiveHours":false, + "inactiveHours":24, + "enableUserLeave":false, + "disableOnClaim":false + }, + "autodelete":{ + /* Autodelete this ticket after a period of inactivity or when the creator leaves the server. */ + "enableInactiveDays":false, + "inactiveDays":7, + "enableUserLeave":false, + "disableOnClaim":false + }, + "cooldown":{ + /* Users must wait a certain period before being able to create another ticket of this type. */ + "enabled":false, + "cooldownMinutes":10 + }, + "limits":{ + /* Set the maximum amount of simultaneous tickets of this option. */ + "enabled":false, + "globalMaximum":20, + "userMaximum":3 + }, + "slowMode":{ + /* Enable slow-mode in the ticket channel. */ + "enabled":false, + "slowModeSeconds":20 + } + }, + { + /* A website option creates a button with a URL to an external website. */ + "id":"example-website", + "name":"Website", + "description":"Take a look at our amazing website.", //Leave empty to disable + "type":"website", + + "button":{ + /* Configure the button style of this option. At least one of "emoji" or "label" must be provided. */ + "emoji":"😃", + "label":"Visit Website" + }, + + "url":"https://www.dj-dj.be" + }, + { + /* A reaction-role option creates a button for members to choose roles. */ + "id":"example-role", + "name":"Update Ping", + "description":"Receive notifications about updates in our server.", //Leave empty to disable + "type":"role", + + "button":{ + /* Configure the button style of this option. At least one of "emoji" or "label" must be provided. */ + "emoji":"📢", + "label":"Update Ping", + "color":"gray" //Choices: gray, red, green, blue + }, + + "roles":["DISCORD_ROLE_ID"], + "mode":"add&remove OR remove OR add", //What to do with the roles. Choices: add&remove, add, remove + "removeRolesOnAdd":["DISCORD_ROLE_ID"], //Remove these old roles when new roles are added. + "addOnMemberJoin":false //Add these roles automatically when joining the server. + }, + { + /* A sub-panel option creates a button which sends another panel for additional options. */ + "id":"example-sub-panel", + "name":"Example Sub-Panel", + "description":"This is an example of how to implement a sub-panel in Open Ticket.", //Leave empty to disable + "type":"sub-panel", + + "button":{ + /* Configure the button style of this option. At least one of "emoji" or "label" must be provided. */ + "emoji":"📋", + "label":"Sub-Panel Example", + "color":"gray" //Choices: gray, red, green, blue + }, + + "subPanelId":"example-panel" //Choose a panel ID from (config/panels.jsonc) + } +] \ No newline at end of file diff --git a/config/panels.json b/config/panels.json deleted file mode 100644 index acc1093..0000000 --- a/config/panels.json +++ /dev/null @@ -1,39 +0,0 @@ -[ - { - "id":"example-embed", - "name":"Example Embed", - "dropdown":false, - "options":["example-ticket","example-website","example-role"], - - "text":"", - "embed":{ - "enabled":true, - "title":"Tickets:", - "description":"Create a ticket by clicking one of the buttons below!", - - "customColor":"#f8ab00 (or leave empty)", - "url":"https://openticket.dj-dj.be (or leave empty)", - - "image":"https://www.example.com/image.png (or leave empty)", - "thumbnail":"https://www.example.com/image.png (or leave empty)", - - "footer":"Open Ticket v4.1.3 (or leave empty)", - "fields":[ - {"name":"field name","value":"field value","inline":false} - ], - "timestamp":false - }, - "settings":{ - "dropdownPlaceholder":"Create a ticket!", - - "enableMaxTicketsWarningInText":false, - "enableMaxTicketsWarningInEmbed":true, - - "describeOptionsLayout":"simple OR normal OR detailed", - "describeOptionsCustomTitle":"", - "describeOptionsInText":false, - "describeOptionsInEmbedFields":true, - "describeOptionsInEmbedDescription":false - } - } -] \ No newline at end of file diff --git a/config/panels.jsonc b/config/panels.jsonc new file mode 100644 index 0000000..cf5bca8 --- /dev/null +++ b/config/panels.jsonc @@ -0,0 +1,57 @@ +/* + * OPEN TICKET PANELS + * ---------------------------------------------- + * Create customizable panel messages with buttons or a dropdown. + * Add up to 25 options to this panel from (config/options.jsonc) + * Panels can be customised with text, images, colors and more. + * + * Spawn the panel in Discord using: /panel + * + * TIP: Create new panels by copying everything between and including the {...} brackets of an panel. Paste it after the last panel and make sure that they are seperated by a comma. + */ +[ + { + /* A panel is creates a message with up to 25 options as buttons or dropdown. */ + "id":"example-panel", + "name":"Example Panel", + "dropdown":false, + + /* Add up to 5 option IDs from (config/options.jsonc). */ + "options":["example-ticket","example-website","example-role"], + + "text":"", //Leave empty to disable + "embed":{ + "enabled":true, + "title":"Tickets:", //Leave empty to disable + "description":"Create a ticket by selecting one of the options below. Once selected, a private ticket will be created for you and our support team will be able to assist you directly.", //Leave empty to disable + + "customColor":"#f8ab00", //Leave empty to use default color + "url":"", //URL. Leave empty to disable + + "image":"", //Image URL. Leave empty to disable + "thumbnail":"", //Image URL. Leave empty to disable + + "footer":"Open Ticket v4.2.0", //Leave empty to disable + /* Embed fields. Set to empty list [] to disable. */ + "fields":[ + {"name":"Field name","value":"Field value","inline":false} + ], + "timestamp":false + }, + "settings":{ + "dropdownPlaceholder":"Create a ticket...", //Leave empty to use default. + "maximumButtonsPerRow":5, + + /* Display the maximum amount of tickets per user. */ + "enableMaxTicketsWarningInText":false, + "enableMaxTicketsWarningInEmbed":true, + + /* Automatically generate option descriptions from (config/options.jsonc). */ + "describeOptionsLayout":"normal", //Choices: simple, normal, detailed + "describeOptionsCustomTitle":"", + "describeOptionsInText":false, + "describeOptionsInEmbedFields":true, + "describeOptionsInEmbedDescription":false + } + } +] \ No newline at end of file diff --git a/config/questions.json b/config/questions.json deleted file mode 100644 index 0d2556e..0000000 --- a/config/questions.json +++ /dev/null @@ -1,28 +0,0 @@ -[ - { - "id":"example-question-1", - "name":"Example Question 1", - "type":"short", - - "required":true, - "placeholder":"Insert your short answer here!", - "length":{ - "enabled":false, - "min":0, - "max":1000 - } - }, - { - "id":"example-question-2", - "name":"Example Question 2", - "type":"paragraph", - - "required":false, - "placeholder":"Insert your long answer here!", - "length":{ - "enabled":false, - "min":0, - "max":1000 - } - } -] \ No newline at end of file diff --git a/config/questions.jsonc b/config/questions.jsonc new file mode 100644 index 0000000..8f9062e --- /dev/null +++ b/config/questions.jsonc @@ -0,0 +1,102 @@ +/* + * OPEN TICKET MODAL QUESTIONS + * ---------------------------------------------- + * Create customizable modal questions that will be shown before creating a ticket. + * Each ticket option (config/options.jsonc) can contain a maximum of 5 questions. + * There are 6 types of questions available: short, paragraph, dropdown, radio-select, checkbox-select, text-display + * + * TIP: Create new questions by copying everything between and including the {...} brackets of a question. Paste it after the last question and make sure that they are seperated by a comma. + */ +[ + { + /* A short text input modal question. */ + "id":"example-question-1", + "name":"Example Question 1", + "description":"", //Leave empty to disable + "type":"short", + "required":true, + + "placeholder":"Insert answer...", + "length":{ + /* Configure length limits for the answer. */ + "enabled":false, + "min":0, + "max":1000 + } + }, + { + /* A paragraph text input modal question. */ + "id":"example-question-2", + "name":"Example Question 2", + "description":"", //Leave empty to disable + "type":"paragraph", + "required":false, + + "placeholder":"Insert answer...", + "length":{ + /* Configure length limits for the answer. */ + "enabled":false, + "min":0, + "max":1000 + } + }, + { + /* A dropdown menu input modal question with up to 25 choices. "emoji" & "description" fields are optional. */ + "id":"example-question-3", + "name":"Example Question 3", + "description":"This is a dropdown question.", //Leave empty to disable + "type":"dropdown", + "required":false, + + "placeholder":"Choose your answer...", + "choices":[ + {"title":"Choice A","description":"Apple","emoji":"🍎"}, + {"title":"Choice B","description":"Banana","emoji":"🍌"}, + {"title":"Choice C","description":"Orange","emoji":"🍊"}, + {"title":"Choice D","description":"Kiwi","emoji":"🥝"} + ] + }, + { + /* A radio select input modal question with up to 10 choices. */ + "id":"example-question-4", + "name":"Example Question 4", + "description":"This is a radio select question.", //Leave empty to disable + "type":"radio-select", + "required":true, + + "choices":[ + {"title":"Choice A","description":"Up","selectedByDefault":false}, + {"title":"Choice B","description":"Down","selectedByDefault":false}, + {"title":"Choice C","description":"Left","selectedByDefault":false}, + {"title":"Choice D","description":"Right","selectedByDefault":false} + ] + }, + { + /* A checkbox select input modal question with up to 10 choices. */ + "id":"example-question-5", + "name":"Example Question 5", + "description":"This is a checkbox select question.", //Leave empty to disable + "type":"checkbox-select", + "required":true, + + "limits":{ + /* Configure checkbox amount limits for the answer. */ + "enabled":false, + "min":0, + "max":10 + }, + "choices":[ + {"title":"Choice A","description":"Happiness","selectedByDefault":false}, + {"title":"Choice B","description":"Anger","selectedByDefault":false}, + {"title":"Choice C","description":"Sadness","selectedByDefault":false}, + {"title":"Choice D","description":"Fear","selectedByDefault":false} + ] + }, + { + /* Show text in a modal to provide extra details or explain questions. */ + "id":"example-text-display", + "type":"text-display", + + "textContents":"This is a text display. It isn't a question, but allows you to display additional details." + } +] \ No newline at end of file diff --git a/config/transcripts.json b/config/transcripts.json deleted file mode 100644 index 37651ae..0000000 --- a/config/transcripts.json +++ /dev/null @@ -1,55 +0,0 @@ -{ - "general":{ - "enabled":false, - - "enableChannel":false, - "enableCreatorDM":false, - "enableParticipantDM":false, - "enableActiveAdminDM":false, - "enableEveryAdminDM":false, - - "channel":"transcript channel id (or leave empty)", - "mode":"html OR text" - }, - "embedSettings":{ - "customColor":"#f8ab00 (or leave empty)", - "listAllParticipants":false, - "includeTicketStats":false - }, - "textTranscriptStyle":{ - "layout":"simple OR normal OR detailed", - "includeStats":true, - "includeIds":false, - "includeEmbeds":true, - "includeFiles":true, - "includeBotMessages":true, - - "fileMode":"custom OR channel-name OR channel-id OR user-name OR user-id", - "customFileName":"this-is-a-transcript (or leave empty)" - }, - "htmlTranscriptStyle":{ - "background":{ - "enableCustomBackground":false, - "backgroundColor":"#f8ba00 (or leave empty)", - "backgroundImage":"https://www.example.com/image.png (or leave empty)" - }, - "header":{ - "enableCustomHeader":false, - "backgroundColor":"#202225", - "decoColor":"#f8ba00", - "textColor":"#ffffff" - }, - "stats":{ - "enableCustomStats":false, - "backgroundColor":"#202225", - "keyTextColor":"#737373", - "valueTextColor":"#ffffff", - "hideBackgroundColor":"#40444a", - "hideTextColor":"#ffffff" - }, - "favicon":{ - "enableCustomFavicon":false, - "imageUrl":"https://t.dj-dj.be/favicon.png" - } - } -} \ No newline at end of file diff --git a/config/transcripts.jsonc b/config/transcripts.jsonc new file mode 100644 index 0000000..2a441c5 --- /dev/null +++ b/config/transcripts.jsonc @@ -0,0 +1,70 @@ +/* + * OPEN TICKET TRANSCRIPTS + * ---------------------------------------------- + * Enable transcript creation when tickets are deleted. There are 2 available transcript types: HTML & Text + * + * HTML Transcripts (recommended): + * Generate transcripts as HTML files to view in the browser. No server or domain required. HTML Transcripts use an external service to process and host the transcripts. + * + * Text Transcripts: + * Generate transcripts as simple .txt files with limited details. Processing happens fully local. + */ +{ + "general":{ + "enabled":false, + + /* Choose which users and channel get the generated transcript. */ + "enableChannel":false, + "enableCreatorDM":false, + "enableParticipantDM":false, + "enableActiveAdminDM":false, + "enableEveryAdminDM":false, + + "channel":"DISCORD_CHANNEL_ID", //Where to send transcripts. Leave empty when disabled. + "mode":"html" //The type of transcript to use. Choices: html, text + }, + "embedSettings":{ + /* Customise the embed which contains the generated transcript file or URL. */ + "customColor":"#f8ab00", //Leave empty to use default color + "listAllParticipants":false, + "includeTicketStats":false + }, + "textTranscriptStyle":{ + /* Customise layout of the text transcripts. */ + "layout":"normal", //Choices: simple, normal, detailed + "includeStats":true, + "includeIds":false, + "includeEmbeds":true, + "includeFiles":true, + "includeBotMessages":true, + + "fileMode":"custom", //How to name the transcript file? Choices: custom, channel-name, channel-id, user-name, user-id + "customFileName":"transcript" //Custom filename without extension + }, + "htmlTranscriptStyle":{ + /* Customise layout of the HTML transcripts. */ + "background":{ + "enableCustomBackground":false, + "backgroundColor":"#f8ba00", //Leave empty to use Open Ticket color (#f8ba00) + "backgroundImage":"https://www.example.com/image.png" //Image URL to fill entire background. Leave empty to disable + }, + "header":{ + "enableCustomHeader":false, + "backgroundColor":"#202225", + "decoColor":"#f8ba00", + "textColor":"#ffffff" + }, + "stats":{ + "enableCustomStats":false, + "backgroundColor":"#202225", + "keyTextColor":"#737373", + "valueTextColor":"#ffffff", + "hideBackgroundColor":"#40444a", + "hideTextColor":"#ffffff" + }, + "favicon":{ + "enableCustomFavicon":false, + "imageUrl":"https://t.dj-dj.be/favicon.png" + } + } +} \ No newline at end of file diff --git a/database/states.json b/database/states.json new file mode 100644 index 0000000..0637a08 --- /dev/null +++ b/database/states.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml deleted file mode 100644 index 8d6b02f..0000000 --- a/docker-compose.yml +++ /dev/null @@ -1,11 +0,0 @@ -# Docker Compose for Open Ticket v4 -version: '3' -services: - openticket: - build: . - volumes: - - openticket:/home/container - restart: no - container_name: open-ticket -volumes: - openticket: \ No newline at end of file diff --git a/dockerfile b/dockerfile deleted file mode 100644 index 4a0acad..0000000 --- a/dockerfile +++ /dev/null @@ -1,18 +0,0 @@ -# Docker File for Open Ticket v4 -# Use the official Node.js 20 image from Docker Hub -FROM node:20-alpine - -# Set pterodactyl working directory inside the container -WORKDIR /home/container - -# Copy package.json and package-lock.json into the container -COPY package*.json ./ - -# Install dependencies -RUN npm install - -# Copy the rest of your app's source code into the container -COPY . . - -# Run the bot from index.js -CMD ["node", "index.js"] \ No newline at end of file diff --git a/index.js b/index.js index 6cead33..2935154 100644 --- a/index.js +++ b/index.js @@ -4,7 +4,6 @@ const flags = [ //PTERODACTYL PANEL //add startup flags here (e.g. "--no-compile") when running via the panel ] -process.argv.push(...flags) /////////////// STARTUP FLAGS /////////////// /* @@ -14,170 +13,19 @@ process.argv.push(...flags) ██║ ██║██╔═══╝ ██╔══╝ ██║╚██╗██║ ██║ ██║██║ ██╔═██╗ ██╔══╝ ██║ ╚██████╔╝██║ ███████╗██║ ╚████║ ██║ ██║╚██████╗██║ ██╗███████╗ ██║ ╚═════╝ ╚═╝ ╚══════╝╚═╝ ╚═══╝ ╚═╝ ╚═╝ ╚═════╝╚═╝ ╚═╝╚══════╝ ╚═╝ - v4.1.3 - Made by DJj123dj & Contributors + v4.2.0 - Made by DJj123dj & Contributors Discord: https://discord.dj-dj.be Docs: https://otdocs.dj-dj.be Support Us: https://github.com/sponsors/DJj123dj/ */ + /////////////////////////////////////////// ////////// COMPILATION + STARTUP ////////// /////////////////////////////////////////// -const fs = require("fs") -const ts = require("typescript") -const {createHash,Hash} = require("crypto") -const nodepath = require('path') -const ansis = require("ansis") -/** ## What is this? - * This is a function which compares `./src/` with a hash stored in `./dist/hash.txt`. - * The hash is based on the modified date & file metadata of all files in `./src/`. - * - * If the hash is different, the bot will automatically re-compile. - * This will help you save CPU resources because the bot shouldn't re-compile when nothing has been changed :) - * - * @param {string} dir - * @param {Hash|null} upperHash - */ -function computeSourceHash(dir,upperHash){ - const hash = upperHash ? upperHash : createHash("sha256") - const info = fs.readdirSync(dir,{withFileTypes:true}) - - for (const file of info) { - const fullPath = nodepath.join(dir,file.name) - if (file.isFile() && [".js",".ts",".jsx",".tsx"].some((ext) => file.name.endsWith(ext))){ - const statInfo = fs.statSync(fullPath) - //compute hash using file metadata - const fileInfo = `${fullPath}:${statInfo.size}:${statInfo.mtimeMs}` - hash.update(fileInfo) - - }else if (file.isDirectory()){ - //recursively compute all folders - computeSourceHash(fullPath,hash) - } - } - //return when not being called recursively - if (!upperHash) { - return hash.digest("hex") - } -} -function requiresCompilation(){ - //check hashes when not using "--compile-only" flag - if (process.argv.includes("--compile-only")) return true - - console.log("OT: Comparing prebuilds with source...") - const sourceHash = computeSourceHash("./src/") - const pluginHash = computeSourceHash("./plugins/") - const hash = sourceHash+":"+pluginHash - - if (fs.existsSync("./dist/hash.txt")){ - const distHash = fs.readFileSync("./dist/hash.txt").toString() - if (distHash === hash) return false - else return true - }else return true -} -function saveNewCompilationHash(){ - const sourceHash = computeSourceHash("./src/") - const pluginHash = computeSourceHash("./plugins/") - const hash = sourceHash+":"+pluginHash - fs.writeFileSync("./dist/hash.txt",hash) -} - -if (!process.argv.includes("--no-compile")){ - const requiredDependencies = new Set() - if (fs.existsSync("./plugins")){ - console.log("OT: Reading plugin.json files...") - for (const pluginDir of fs.readdirSync("./plugins")){ - if (pluginDir === ".DS_Store") continue - const pluginPath = nodepath.join("./plugins", pluginDir) - if (!fs.statSync(pluginPath).isDirectory()) continue - - const pluginJsonPath = nodepath.join(pluginPath, "plugin.json") - if (fs.existsSync(pluginJsonPath)){ - try{ - const pluginData = JSON.parse(fs.readFileSync(pluginJsonPath).toString()) - if (pluginData.npmDependencies && Array.isArray(pluginData.npmDependencies)){ - pluginData.npmDependencies.forEach((dep) => { - if (typeof dep === "string" && dep.trim()){ - requiredDependencies.add(dep.trim()) - } - }) - } - }catch(err){ - // skip invalid plugin.json files, will be caught later - } - } - } - - if (requiredDependencies.size > 0){ - console.log("OT: Checking plugin npm dependencies...") - /**@type {string[]} */ - const missingDeps = [] - for (const dep of requiredDependencies){ - try{ - require.resolve(dep) - }catch(err){ - missingDeps.push(dep) - } - } - - if (missingDeps.length > 0){ - console.log(ansis.red("OT: ❌ Fatal Error --> Missing npm dependencies required by plugins:\n\n")+ansis.cyan(missingDeps.map((dep) => " - "+dep).join("\n")+"\n")) - console.log("OT: Please install missing dependencies using the following command:\n> "+ansis.bold.green("npm install " + missingDeps.join(" "))+"\n") - process.exit(1) - } - } - } - - if (requiresCompilation()){ - console.log("OT: Compilation Required...") - - //REMOVE EXISTING BUILDS - console.log("OT: Removing Prebuilds...") - fs.rmSync("./dist",{recursive:true,force:true}) - - //COMPILE TYPESCRIPT - console.log("OT: Compiling Typescript...") - const configPath = nodepath.resolve('./tsconfig.json') - const configFile = ts.readConfigFile(configPath,ts.sys.readFile) - - //check for tsconfig errors - if (configFile.error){ - const message = ts.formatDiagnosticsWithColorAndContext([configFile.error],ts.createCompilerHost({})) - console.error(message) - process.exit(1) - } - - //parse tsconfig file - const parsedConfig = ts.parseJsonConfigFileContent(configFile.config,ts.sys,nodepath.dirname(configPath)) - - //create program/compiler - const program = ts.createProgram({ - rootNames:parsedConfig.fileNames, - options:parsedConfig.options - }) - - //emit all compiled files - const emitResult = program.emit() - - //print emit errors/warnings (type errors) - const allDiagnostics = ts.getPreEmitDiagnostics(program).concat(emitResult.diagnostics) - const formattedDiagnostics = ts.formatDiagnosticsWithColorAndContext(allDiagnostics, ts.createCompilerHost(parsedConfig.options)) - console.log(formattedDiagnostics) - - if (emitResult.emitSkipped || allDiagnostics.find((d) => d.category == ts.DiagnosticCategory.Error || d.category == ts.DiagnosticCategory.Warning)){ - console.log("OT: Compilation Failed!") - process.exit(1) - } - }else console.log("OT: No Compilation Required...") - - //save new compilation hash - saveNewCompilationHash() -} - -//START BOT -console.log("OT: Compilation Succeeded!") -if (process.argv.includes("--compile-only")) process.exit(0) //exit when only compile is required! -console.log("OT: Starting Bot!") -require("./dist/src/index.js") \ No newline at end of file +import { frameworkStartup } from "@open-discord-bots/framework" +frameworkStartup(flags,"openticket",async () => { + await import("./dist/src/index.js") +}) \ No newline at end of file diff --git a/languages/arabic.json b/languages/arabic.json index 694bf33..7d3d220 100644 --- a/languages/arabic.json +++ b/languages/arabic.json @@ -1,8 +1,8 @@ { "_TRANSLATION":{ - "otversion":"v4.1.3", + "otversion":"v4.2.0", "translators":["palestinian"], - "lastedited":"16/02/2026", + "lastedited":"25/05/2026", "language":"Arabic", "automated":false }, @@ -99,7 +99,7 @@ "invalidButton":"يجب أن تحتوي هذه الزر على {0} أو {1} على الأقل!", "unusedOption":"الخيار {0} غير مستخدم في أي مكان!", "unusedQuestion":"السؤال {0} غير مستخدم في أي مكان!", - "dropdownOption":"يمكن أن تحتوي لوحة مع تمكين القائمة المنسدلة على خيارات من نوع 'تذكرة' فقط!", + "dropdownOption":"يمكن للوحة التي تحتوي على قائمة منسدلة أن تحتوي فقط على خيارات من الأنواع: 'ticket' أو 'role' أو 'sub-panel'.", "customInvalidVersion":"الإصدار المحدد في الإعدادات غير متطابق! تأكد من تحديث الإعدادات إلى أحدث إصدار!" } }, @@ -118,7 +118,9 @@ "helpSwitchText":"عرض أوامر النص", "helpPage":"صفحة {0}", "withReason":"مع سبب", - "withoutTranscript":"بدون نص" + "withoutTranscript":"بدون نص", + "blacklistAdd":"إضافة المستخدم إلى القائمة السوداء", + "blacklistRemove":"إطلاق المستخدم" }, "titles":{ "created":"تم إنشاء التذكرة", @@ -156,7 +158,8 @@ "topicSet":"تم تغيير الموضوع", "prioritySet":"تم تغيير الأولوية", "priorityGet":"أولوية التذكرة", - "transfer":"تم نقل التذكرة" + "transfer":"تم نقل التذكرة", + "transcripts":"سجل النصوص" }, "descriptions":{ "create":"تم إنشاء تذكرتك. انقر على الزر أدناه للوصول إليها!", @@ -249,7 +252,9 @@ "prioritySetLog":"تم تغيير أولوية هذه التذكرة إلى {0} بواسطة {1}!", "prioritySetDm":"تم تغيير أولوية تذكرتك إلى {0} في خادمنا!", "roleUpdateLog":"قام {0} بتحديث أدواره!", - "roleUpdateDm":"تم تحديث أدوارك في خادمنا!" + "roleUpdateDm":"تم تحديث أدوارك في خادمنا!", + "topicSetLog":"تم تعيين أولوية هذه التذكرة إلى {0} بواسطة {1}.", + "topicSetDm":"تم تعيين أولوية تذكرتك إلى {0}." } }, "transcripts":{ @@ -271,7 +276,9 @@ "continue":"حذف بدون نص", "backup":"إنشاء نص احتياطي", "error":"حدث خطأ أثناء محاولة إنشاء النص.\nماذا تريد أن تفعل؟\n\nلن يتم حذف هذه التذكرة حتى تضغط على أحد هذه الأزرار.", - "title":"خطأ في النسخ" + "title":"خطأ في النسخ", + "noHistory":"لا يملك هذا المستخدم أي نصوص حتى الآن.", + "historyNotSupported":"سجل النصوص مدعوم حاليًا فقط مع HTML Transcripts.\nسيكون سجل النصوص النصية متاحًا في الإصدارات المستقبلية." }, "text":{ "messagesTitle":"الرسائل", @@ -298,6 +305,7 @@ "unknownPanel":"لوحة غير معروفة", "notInGuild":"غير موجود في الخادم", "channelRename":"غير قادر على إعادة تسمية القناة", + "channelCategory":"تعذر تغيير الفئة", "busy":"التذكرة مشغولة", "permissionError":"خطأ في الصلاحيات" }, @@ -321,11 +329,15 @@ "deprecatedTicket":"القناة الحالية ليست تذكرة صالحة! قد تكون تذكرة من إصدار قديم من Open Ticket!", "notInGuild":"هذا {0} لا يعمل في DM! يرجى المحاولة مرة أخرى في خادم!", "channelRename":"بسبب حدود معدل ديسكورد، من المستحيل حاليًا على البوت إعادة تسمية القناة. سيتم إعادة تسمية القناة تلقائيًا بعد 10 دقائق إذا لم يتم إعادة تشغيل البوت.", + "channelCategory":"بسبب حدود المعدل الخاصة بـ Discord، تعذر تغيير فئة القناة فورًا. سيتم تغييرها تلقائيًا خلال 10 دقائق إذا ظل البوت متصلاً.", "channelRenameSource":"مصدر هذا الخطأ هو: {0}", "busy":"غير قادر على استخدام هذا {0}!\nالتذكرة قيد المعالجة حاليًا بواسطة البوت.\n\nيرجى المحاولة مرة أخرى بعد بضع ثوانٍ!", "closeBeforeMessage":"لا يمكن إغلاق/حذف هذه التذكرة قبل إرسال رسالة من مستخدم.", "closeBeforeAdminMessage":"لا يمكن إغلاق/حذف هذه التذكرة قبل إرسال رسالة من مشرف تذاكر أو عضو دعم.", - "unableToCreateTicket":"لا يمكنك إنشاء تذكرة." + "unableToCreateTicket":"لا يمكنك إنشاء تذكرة.", + "messageMissing":"تعذر العثور على رسالة التفاعل. استخدم الأمر `{0}` بدلاً من ذلك.", + "stateExpired":"لم تعد هذه العملية صالحة أو انتهت صلاحيتها. استخدم الأمر `{0}` بدلاً من ذلك. من الطبيعي ظهور هذا الخطأ بعد تحديث كبير لـ Open Ticket.", + "panelStateExpired":"لم تعد هذه اللوحة صالحة أو انتهت صلاحيتها. قم بإنشاء لوحة جديدة باستخدام `{0}` لحل المشكلة. من الطبيعي ظهور هذا الخطأ بعد تحديث كبير لـ Open Ticket." }, "optionInvalidReasons":{ "stringRegex":"القيمة لا تتطابق مع النمط!", @@ -387,6 +399,8 @@ "syntax":"بناء الجملة", "originalName":"الاسم الأصلي", "newName":"الاسم الجديد", + "originalCategory":"الفئة الأصلية", + "newCategory":"الفئة الجديدة", "until":"حتى", "validOptions":"خيارات صالحة", "validPanels":"لوحات صالحة", @@ -408,6 +422,8 @@ "participants":"المشاركون", "yes":"نعم", "no":"لا", + "accept":"قبول", + "cancel":"إلغاء", "option":"خيار", "topic":"الموضوع", "uptime":"مدة تشغيل النظام", @@ -439,6 +455,7 @@ "panelAutoUpdate":"هل تريد أن يتم تحديث هذه اللوحة تلقائيًا عند تعديلها؟", "ticket":"قم بإنشاء تذكرة على الفور.", "ticketId":"معرف التذكرة التي تريد إنشائها.", + "ticketOtherUser":"إنشاء تذكرة لمستخدم آخر.", "close":"أغلق تذكرة.", "delete":"احذف تذكرة.", "deleteNoTranscript":"احذف هذه التذكرة دون إنشاء نص.", @@ -504,7 +521,9 @@ "priorityGet":"الحصول على أولوية التذكرة.", "priorityList":"الحصول على قائمة بجميع التذاكر مع حالة أولويتها.", "transfer":"نقل ملكية التذكرة من مستخدم إلى آخر.", - "transferUser":"المستخدم الذي سيتم النقل إليه." + "transferUser":"المستخدم الذي سيتم النقل إليه.", + "transcripts":"عرض سجل نصوص التذاكر لمستخدم.", + "transcriptsUser":"المستخدم المراد عرضه." }, "helpMenu":{ "help":"احصل على قائمة بجميع الأوامر المتاحة.", @@ -594,7 +613,8 @@ "panel":{ "selectTicket":"اختر تذكرتك", "selectRole":"اختر دورك", - "selectOption":"اختر خيارك" + "selectOption":"اختر خيارك", + "selectPriorityLevel":"اختر مستوى الأولوية" }, "priorities":{ "urgent":"عاجل", diff --git a/languages/bengali.json b/languages/bengali.json index fe5894f..80fd4e4 100644 --- a/languages/bengali.json +++ b/languages/bengali.json @@ -1,8 +1,8 @@ { "_TRANSLATION":{ - "otversion":"v4.1.3", + "otversion":"v4.2.0", "translators":["HanumeshGupta"], - "lastedited":"16/02/2026", + "lastedited":"25/05/2026", "language":"Bengali", "automated":false }, @@ -99,7 +99,7 @@ "invalidButton":"এই বাটনটিতে কমপক্ষে একটি {0} বা {1} থাকতে হবে!", "unusedOption":"অপশন {0} কোথাও ব্যবহার করা হয়নি!", "unusedQuestion":"প্রশ্ন {0} কোথাও ব্যবহার করা হয়নি!", - "dropdownOption":"ড্রপডাউন সক্রিয় থাকা একটি প্যানেল শুধুমাত্র 'টিকেট' টাইপের অপশন ধারণ করতে পারে!", + "dropdownOption":"ড্রপডাউনসহ একটি প্যানেলে শুধুমাত্র এই ধরনের অপশন থাকতে পারে: 'ticket', 'role' বা 'sub-panel'।", "customInvalidVersion":"আপনার কনফিগারেশনে উল্লেখিত সংস্করণ মেলছে না! নিশ্চিত করুন যে কনফিগারেশন সর্বশেষ সংস্করণে আপডেট হয়েছে!" } }, @@ -118,7 +118,9 @@ "helpSwitchText":"টেক্সট কমান্ড দেখুন", "helpPage":"পৃষ্ঠা {0}", "withReason":"কারণ সহ", - "withoutTranscript":"ট্রান্সক্রিপ্ট ছাড়া" + "withoutTranscript":"ট্রান্সক্রিপ্ট ছাড়া", + "blacklistAdd":"ব্যবহারকারীকে ব্ল্যাকলিস্টে যোগ করুন", + "blacklistRemove":"ব্যবহারকারীকে মুক্ত করুন" }, "titles":{ "created":"টিকেট তৈরি হয়েছে", @@ -156,7 +158,8 @@ "topicSet":"বিষয় পরিবর্তিত হয়েছে", "prioritySet":"অগ্রাধিকার পরিবর্তিত হয়েছে", "priorityGet":"টিকিটের অগ্রাধিকার", - "transfer":"টিকিট স্থানান্তরিত হয়েছে" + "transfer":"টিকিট স্থানান্তরিত হয়েছে", + "transcripts":"ট্রান্সক্রিপ্ট ইতিহাস" }, "descriptions":{ "create":"আপনার টিকেট তৈরি করা হয়েছে। এটি অ্যাক্সেস করতে নীচের বাটনে ক্লিক করুন!", @@ -249,7 +252,9 @@ "prioritySetLog":"এই টিকিটের অগ্রাধিকার {1} দ্বারা {0}-এ পরিবর্তিত হয়েছে!", "prioritySetDm":"আপনার টিকিটের অগ্রাধিকার আমাদের সার্ভারে {0}-এ পরিবর্তিত হয়েছে!", "roleUpdateLog":"{0} তাদের ভূমিকা আপডেট করেছেন!", - "roleUpdateDm":"আপনার সার্ভারে ভূমিকা আপডেট করা হয়েছে!" + "roleUpdateDm":"আপনার সার্ভারে ভূমিকা আপডেট করা হয়েছে!", + "topicSetLog":"এই টিকিটের অগ্রাধিকার {1} দ্বারা {0} এ সেট করা হয়েছে।", + "topicSetDm":"আপনার টিকিটের অগ্রাধিকার {0} এ সেট করা হয়েছে।" } }, "transcripts":{ @@ -271,7 +276,9 @@ "continue":"ট্রান্সক্রিপ্ট ছাড়া মুছুন", "backup":"ব্যাকআপ ট্রান্সক্রিপ্ট তৈরি করুন", "error":"ট্রান্সক্রিপ্ট তৈরি করার সময় কিছু ভুল হয়েছে।\nআপনি কি করতে চান?\n\nআপনি এই বাটনগুলির একটিতে ক্লিক না করা পর্যন্ত এই টিকেটটি মুছে ফেলা হবে না।", - "title":"ট্রান্সক্রিপ্ট ত্রুটি" + "title":"ট্রান্সক্রিপ্ট ত্রুটি", + "noHistory":"এই ব্যবহারকারীর এখনও কোনো ট্রান্সক্রিপ্ট নেই।", + "historyNotSupported":"ট্রান্সক্রিপ্ট ইতিহাস বর্তমানে শুধুমাত্র HTML Transcripts-এর সাথে সমর্থিত।\nটেক্সট ট্রান্সক্রিপ্ট ইতিহাস ভবিষ্যৎ সংস্করণে উপলব্ধ হবে।" }, "text":{ "messagesTitle":"বার্তা", @@ -298,6 +305,7 @@ "unknownPanel":"অজানা প্যানেল", "notInGuild":"সার্ভারে নেই", "channelRename":"চ্যানেলের নাম পরিবর্তন করতে অক্ষম", + "channelCategory":"ক্যাটাগরি পরিবর্তন করা যায়নি", "busy":"টিকেট ব্যস্ত", "permissionError":"অনুমতি ত্রুটি" }, @@ -321,11 +329,15 @@ "deprecatedTicket":"বর্তমান চ্যানেলটি একটি বৈধ টিকেট নয়! এটি Open Ticket-এর পুরানো সংস্করণের একটি টিকেট হতে পারে!", "notInGuild":"এই {0} DM-এ কাজ করে না! অনুগ্রহ করে এটি একটি সার্ভারে আবার চেষ্টা করুন!", "channelRename":"ডিসকর্ড রেটলিমিটের কারণে, বটের পক্ষে চ্যানেলের নাম পরিবর্তন করা বর্তমানে অসম্ভব। বট রিবুট না করা হলে চ্যানেলটি ১০ মিনিটের মধ্যে স্বয়ংক্রিয়ভাবে নাম পরিবর্তন করা হবে।", + "channelCategory":"Discord রেট সীমার কারণে চ্যানেলের ক্যাটাগরি তাৎক্ষণিকভাবে পরিবর্তন করা সম্ভব নয়। বট অনলাইনে থাকলে এটি 10 মিনিটের মধ্যে স্বয়ংক্রিয়ভাবে পরিবর্তিত হবে।", "channelRenameSource":"এই ত্রুটির উৎস হল: {0}", "busy":"এই {0} ব্যবহার করতে অক্ষম!\nটিকেটটি বর্তমানে বট দ্বারা প্রক্রিয়াধীন রয়েছে।\n\nঅনুগ্রহ করে কয়েক সেকেন্ড পর আবার চেষ্টা করুন!", "closeBeforeMessage":"একজন ব্যবহারকারী বার্তা পাঠানোর আগে এই টিকিট বন্ধ/মুছে ফেলা যাবে না।", "closeBeforeAdminMessage":"একজন টিকিট অ্যাডমিন বা সাপোর্ট সদস্য বার্তা পাঠানোর আগে এই টিকিট বন্ধ/মুছে ফেলা যাবে না।", - "unableToCreateTicket":"আপনি টিকিট তৈরি করতে পারবেন না।" + "unableToCreateTicket":"আপনি টিকিট তৈরি করতে পারবেন না।", + "messageMissing":"ইন্টারঅ্যাকশনের বার্তা খুঁজে পাওয়া যায়নি। পরিবর্তে `{0}` কমান্ড ব্যবহার করুন।", + "stateExpired":"এই ইন্টারঅ্যাকশন আর বৈধ নয় বা মেয়াদোত্তীর্ণ হয়েছে। পরিবর্তে `{0}` কমান্ড ব্যবহার করুন। Open Ticket-এর বড় আপডেটের পরে এটি স্বাভাবিক।", + "panelStateExpired":"এই প্যানেল আর বৈধ নয় বা মেয়াদোত্তীর্ণ হয়েছে। সমস্যা সমাধানের জন্য `{0}` ব্যবহার করে একটি নতুন প্যানেল তৈরি করুন। Open Ticket-এর বড় আপডেটের পরে এটি স্বাভাবিক।" }, "optionInvalidReasons":{ "stringRegex":"মান প্যাটার্নের সাথে মেলে না!", @@ -387,6 +399,8 @@ "syntax":"সিনট্যাক্স", "originalName":"আসল নাম", "newName":"নতুন নাম", + "originalCategory":"মূল ক্যাটাগরি", + "newCategory":"নতুন ক্যাটাগরি", "until":"যতক্ষণ না", "validOptions":"বৈধ অপশনসমূহ", "validPanels":"বৈধ প্যানেলসমূহ", @@ -408,6 +422,8 @@ "participants":"অংশগ্রহণকারী", "yes":"হ্যাঁ", "no":"না", + "accept":"গ্রহণ করুন", + "cancel":"বাতিল", "option":"বিকল্প", "topic":"বিষয়", "uptime":"সিস্টেম চালু সময়", @@ -439,6 +455,7 @@ "panelAutoUpdate":"আপনি কি এই প্যানেলটিকে সম্পাদনা করার সময় স্বয়ংক্রিয়ভাবে আপডেট করতে চান?", "ticket":"তাৎক্ষণিকভাবে একটি টিকেট তৈরি করুন।", "ticketId":"আপনি যে টিকেট তৈরি করতে চান তার আইডেন্টিফায়ার।", + "ticketOtherUser":"অন্য ব্যবহারকারীর জন্য একটি টিকিট তৈরি করুন।", "close":"একটি টিকেট বন্ধ করুন।", "delete":"একটি টিকেট মুছুন।", "deleteNoTranscript":"ট্রান্সক্রিপ্ট তৈরি না করে এই টিকেটটি মুছুন।", @@ -504,7 +521,9 @@ "priorityGet":"টিকিটের অগ্রাধিকার দেখুন।", "priorityList":"সমস্ত টিকিটের তালিকা এবং তাদের অগ্রাধিকার দেখুন।", "transfer":"একজন ব্যবহারকারীর থেকে অন্য ব্যবহারকারীর কাছে টিকিটের মালিকানা স্থানান্তর করুন।", - "transferUser":"যার কাছে স্থানান্তর করতে হবে সেই ব্যবহারকারী।" + "transferUser":"যার কাছে স্থানান্তর করতে হবে সেই ব্যবহারকারী।", + "transcripts":"একজন ব্যবহারকারীর টিকিট ট্রান্সক্রিপ্ট ইতিহাস দেখুন।", + "transcriptsUser":"যে ব্যবহারকারীকে দেখতে হবে।" }, "helpMenu":{ "help":"সমস্ত উপলব্ধ কমান্ডের তালিকা পান।", @@ -594,7 +613,8 @@ "panel":{ "selectTicket":"আপনার টিকিট নির্বাচন করুন", "selectRole":"আপনার ভূমিকা নির্বাচন করুন", - "selectOption":"আপনার বিকল্প নির্বাচন করুন" + "selectOption":"আপনার বিকল্প নির্বাচন করুন", + "selectPriorityLevel":"অগ্রাধিকার স্তর নির্বাচন করুন" }, "priorities":{ "urgent":"জরুরি", diff --git a/languages/catalan.json b/languages/catalan.json index 1336d34..b2f5bf3 100644 --- a/languages/catalan.json +++ b/languages/catalan.json @@ -1,8 +1,8 @@ { "_TRANSLATION":{ - "otversion":"v4.1.3", + "otversion":"v4.2.0", "translators":["guillee3"], - "lastedited":"16/02/2026", + "lastedited":"25/05/2026", "language":"Catalan", "automated":false }, @@ -99,7 +99,7 @@ "invalidButton":"Aquest botó ha de tenir almenys un {0} o {1}!", "unusedOption":"L'opció {0} no s'utilitza enlloc!", "unusedQuestion":"La pregunta {0} no s'utilitza enlloc!", - "dropdownOption":"Un panell amb desplegable activat només pot contenir opcions del tipus 'ticket'!", + "dropdownOption":"Un panell amb desplegable només pot contenir opcions dels tipus: 'ticket', 'role' o 'sub-panel'.", "customInvalidVersion":"La versió especificada a la teva configuració no coincideix! Assegura't d'haver actualitzat la configuració a la versió més recent!" } }, @@ -118,7 +118,9 @@ "helpSwitchText":"Veure Comandes de Text", "helpPage":"Pàgina {0}", "withReason":"Amb Motiu", - "withoutTranscript":"Sense Transcripció" + "withoutTranscript":"Sense Transcripció", + "blacklistAdd":"Afegir Usuari a la Llista Negra", + "blacklistRemove":"Alliberar Usuari" }, "titles":{ "created":"Ticket Creat", @@ -156,7 +158,8 @@ "topicSet":"Tema Canviat", "prioritySet":"Prioritat Canviada", "priorityGet":"Prioritat del Ticket", - "transfer":"Ticket Transferit" + "transfer":"Ticket Transferit", + "transcripts":"Historial de Transcripcions" }, "descriptions":{ "create":"El teu ticket ha estat creat. Fes clic al botó a continuació per accedir-hi!", @@ -249,7 +252,9 @@ "prioritySetLog":"La prioritat d'aquest ticket ha estat canviada a {0} per {1}!", "prioritySetDm":"La prioritat del teu ticket ha estat canviada a {0} al nostre servidor!", "roleUpdateLog":"{0} ha actualitzat els seus rols!", - "roleUpdateDm":"Els teus rols al nostre servidor han estat actualitzats!" + "roleUpdateDm":"Els teus rols al nostre servidor han estat actualitzats!", + "topicSetLog":"La prioritat d'aquest tiquet ha estat establerta a {0} per {1}.", + "topicSetDm":"La prioritat del teu tiquet ha estat establerta a {0}." } }, "transcripts":{ @@ -271,7 +276,9 @@ "continue":"Elimina Sense Transcripció", "backup":"Crea Transcripció de Backup", "error":"Alguna cosa ha anat malament mentre intentàvem crear la transcripció.\nQuè t'agradaria fer?\n\nAquest ticket no s'eliminarà fins que facis clic en un d'aquests botons.", - "title":"Error de transcripció" + "title":"Error de transcripció", + "noHistory":"Aquest usuari encara no té cap transcripció.", + "historyNotSupported":"L'historial de transcripcions només és compatible actualment amb HTML Transcripts.\nL'historial de transcripcions de text estarà disponible en futures versions." }, "text":{ "messagesTitle":"MISSATGES", @@ -298,6 +305,7 @@ "unknownPanel":"Panell Desconegut", "notInGuild":"No Ets Al Servidor", "channelRename":"No Es Pot Canviar El Nom Del Canal", + "channelCategory":"No Es Pot Canviar la Categoria", "busy":"El Ticket Està Ocupat", "permissionError":"Error de Permisos" }, @@ -321,11 +329,15 @@ "deprecatedTicket":"El canal actual no és un ticket vàlid! Pot ser que fos un ticket d'una versió antiga de Open Ticket!", "notInGuild":"Aquest {0} no funciona en DM! Si us plau, torna a intentar-ho en un servidor!", "channelRename":"Degut a les limitacions de discord, actualment és impossible per al bot canviar el nom del canal. El canal es canviarà automàticament en 10 minuts si el bot no es reinicia.", + "channelCategory":"A causa dels límits de Discord, la categoria del canal no es pot canviar immediatament. Es canviarà automàticament en 10 minuts si el bot continua en línia.", "channelRenameSource":"La font d'aquest error és: {0}", "busy":"No es pot utilitzar aquest {0}!\nEl ticket està sent processat pel bot.\n\nSi us plau, torna a intentar-ho en uns segons!", "closeBeforeMessage":"Aquest ticket no pot ser tancat/eliminat abans que un usuari hagi enviat un missatge.", "closeBeforeAdminMessage":"Aquest ticket no pot ser tancat/eliminat abans que un administrador o membre de suport hagi enviat un missatge.", - "unableToCreateTicket":"No pots crear cap ticket." + "unableToCreateTicket":"No pots crear cap ticket.", + "messageMissing":"No s'ha pogut localitzar el missatge de la interacció. Utilitza el comandament `{0}` en el seu lloc.", + "stateExpired":"Aquesta interacció ja no és vàlida o ha expirat. Utilitza el comandament `{0}` en el seu lloc. És normal rebre aquest error després d'una actualització important d'Open Ticket.", + "panelStateExpired":"Aquest panell ja no és vàlid o ha expirat. Crea un nou panell utilitzant `{0}` per solucionar el problema. És normal rebre aquest error després d'una actualització important d'Open Ticket." }, "optionInvalidReasons":{ "stringRegex":"El valor no coincideix amb el patró!", @@ -387,6 +399,8 @@ "syntax":"Sintaxi", "originalName":"Nom Original", "newName":"Nom Nou", + "originalCategory":"Categoria Original", + "newCategory":"Nova Categoria", "until":"Fins a", "validOptions":"Opcions Vàlides", "validPanels":"Panells Vàlids", @@ -408,6 +422,8 @@ "participants":"Participants", "yes":"Si", "no":"No", + "accept":"Acceptar", + "cancel":"Cancel·lar", "option":"Opció", "topic":"Tema", "uptime":"Temps d'Activitat del Sistema", @@ -439,6 +455,7 @@ "panelAutoUpdate":"Vols que aquest panell s'actualitzi automàticament quan s'edita?", "ticket":"Crea instantàniament un ticket.", "ticketId":"L'identificador del ticket que vols crear.", + "ticketOtherUser":"Crea un tiquet per a un altre usuari.", "close":"Tanca un ticket.", "delete":"Elimina un ticket.", "deleteNoTranscript":"Elimina aquest ticket sense crear una transcripció.", @@ -504,7 +521,9 @@ "priorityGet":"Obtenir la prioritat del ticket.", "priorityList":"Obté una llista de tots els tickets amb el seu estat de prioritat.", "transfer":"Transfereix la propietat del ticket d'un usuari a un altre.", - "transferUser":"L'usuari al qual transferir." + "transferUser":"L'usuari al qual transferir.", + "transcripts":"Veure l'historial de transcripcions de tiquets d'un usuari.", + "transcriptsUser":"L'usuari a visualitzar." }, "helpMenu":{ "help":"Obtingues una llista de totes les comandes disponibles.", @@ -594,7 +613,8 @@ "panel":{ "selectTicket":"Selecciona el teu ticket", "selectRole":"Selecciona el teu rol", - "selectOption":"Selecciona la teva opció" + "selectOption":"Selecciona la teva opció", + "selectPriorityLevel":"Selecciona el nivell de prioritat" }, "priorities":{ "urgent":"Urgent", diff --git a/languages/custom.json b/languages/custom.json index 7b9888d..022ef77 100644 --- a/languages/custom.json +++ b/languages/custom.json @@ -1,8 +1,8 @@ { "_TRANSLATION":{ - "otversion":"v4.1.3", + "otversion":"v4.2.0", "translators":["DJj123dj"], - "lastedited":"16/02/2026", + "lastedited":"25/05/2026", "language":"Custom", "automated":false }, @@ -13,94 +13,94 @@ "typeWarning":"[WARNING]", "typeInfo":"[INFO]", "headerConfigChecker":"CONFIG CHECKER", - "headerDescription":"check for errors in your config files!", - "footerError":"the bot won't start until all {0}'s are fixed!", - "footerWarning":"it's recommended to fix all {0}'s before starting!", + "headerDescription":"Validating config files...", + "footerError":"The bot will not start until all {0}'s are resolved.", + "footerWarning":"The bot may behave unexpectedly until all {0}'s are resolved.", "footerSupport":"SUPPORT: {0} - DOCS: {1}", - "compactInformation":"use {0} for more information!", + "compactInformation":"Use {0} for a detailed config report.", "dataPath":"path", "dataDocs":"docs", "dataMessages":"message" }, "messages":{ - "stringTooShort":"This string can't be shorter than {0} characters!", - "stringTooLong":"This string can't be longer than {0} characters!", - "stringLengthInvalid":"This string needs to be {0} characters long!", - "stringStartsWith":"This string needs to start with {0}!", - "stringEndsWith":"This string needs to end with {0}!", - "stringContains":"This string needs to contain {0}!", - "stringChoices":"This string can only be one of the following values: {0}!", - "stringRegex":"This string is invalid!", - "stringInvertedContains":"This string is not allowed to contain {0}!", - "stringLowercase":"This string must be written in lowercase only!", - "stringUppercase":"This string must be written in uppercase only!", - "stringSpecialCharacters":"This string is not allowed to contain any special characters! (a-z, 0-9 & space only)", - "stringNoSpaces":"This string is not allowed to contain spaces!", - "stringCapitalWord":"It's recommended that each word in this string starts with a capital letter!", - "stringCapitalSentence":"It looks like some sentences in this string don't start with a capital letter!", - "stringPunctuation":"It looks like the sentence in this string doesn't end with a punctuation mark!", + "stringTooShort":"Text must be at least {0} characters long", + "stringTooLong":"Text must be no longer than {0} characters", + "stringLengthInvalid":"Text must be exactly {0} characters long", + "stringStartsWith":"Text must start with {0}", + "stringEndsWith":"Text must end with {0}", + "stringContains":"Text must contain {0}", + "stringChoices":"Text must be one of the following: {0}", + "stringRegex":"Text does not match the required format", + "stringInvertedContains":"Text must not contain {0}", + "stringLowercase":"Text must be entirely lowercase", + "stringUppercase":"Text must be entirely uppercase", + "stringSpecialCharacters":"Text must only contain letters (a–z), numbers (0–9), and spaces", + "stringNoSpaces":"Text must not contain spaces", + "stringCapitalWord":"Each word in this value should start with a capital letter", + "stringCapitalSentence":"One or more sentences in this value do not start with a capital letter", + "stringPunctuation":"The sentence in this value does not end with a punctuation mark", - "numberTooShort":"This number can't be shorter than {0} characters!", - "numberTooLong":"This number can't be longer than {0} characters!", - "numberLengthInvalid":"This number needs to be {0} characters long!", - "numberTooSmall":"This number needs to be at least {0}!", - "numberTooLarge":"This number needs to be at most {0}!", - "numberNotEqual":"This number needs to be {0}!", - "numberStep":"This number needs to be a multiple of {0}!", - "numberStepOffset":"This number needs to be a multiple of {0} starting with {1}!", - "numberStartsWith":"This number needs to start with {0}!", - "numberEndsWith":"This number needs to end with {0}!", - "numberContains":"This number needs to contain {0}!", - "numberChoices":"This number can only be one of the following values: {0}!", - "numberFloat":"This number can't be a decimal!", - "numberNegative":"This number can't be negative!", - "numberPositive":"This number can't be positive!", - "numberZero":"This number can't be zero!", - "numberNan":"This number can't be NaN (Not A Number)!", - "numberInvertedContains":"This number is not allowed to contain {0}!", + "numberTooShort":"Number must be at least {0} digits long", + "numberTooLong":"Number must be no longer than {0} digits", + "numberLengthInvalid":"Number must be exactly {0} digits long", + "numberTooSmall":"Number must be at least {0}", + "numberTooLarge":"Number must be at most {0}", + "numberNotEqual":"Number must be exactly {0}", + "numberStep":"Number must be a multiple of {0}", + "numberStepOffset":"Number must be a multiple of {0}, starting from {1}", + "numberStartsWith":"Number must start with {0}", + "numberEndsWith":"Number must end with {0}", + "numberContains":"Number must contain {0}", + "numberChoices":"Number must be one of the following: {0}", + "numberFloat":"Number must be a whole number", + "numberNegative":"Number must be a positive number", + "numberPositive":"Number must be a negative number", + "numberZero":"Number must not be zero", + "numberNan":"Number must be a valid number", + "numberInvertedContains":"Number must not contain {0}", - "booleanTrue":"This boolean can't be true!", - "booleanFalse":"This boolean can't be false!", + "booleanTrue":"Boolean must be false", + "booleanFalse":"Boolean must be true", - "arrayEmptyDisabled":"This array isn't allowed to be empty!", - "arrayEmptyRequired":"This array is required to be empty!", - "arrayTooShort":"This array needs to have a length of at least {0}!", - "arrayTooLong":"This array needs to have a length of at most {0}!", - "arrayLengthInvalid":"This array needs to have a length of {0}!", - "arrayInvalidTypes":"This array can only contain the following types: {0}!", - "arrayDouble":"This array doesn't allow the same value twice!", + "arrayEmptyDisabled":"List must not be empty", + "arrayEmptyRequired":"List must be empty", + "arrayTooShort":"List must have at least {0} items", + "arrayTooLong":"List must have at most {0} items", + "arrayLengthInvalid":"List must have exactly {0} items", + "arrayInvalidTypes":"List may only contain the following types: {0}", + "arrayDouble":"List must not contain duplicate values", - "discordInvalidId":"This is an invalid discord {0} id!", - "discordInvalidIdOptions":"This is an invalid discord {0} id! You can also use one of these: {1}!", - "discordInvalidToken":"This is an invalid discord token (syntactically)!", - "colorInvalid":"This is an invalid hex color!", - "emojiTooShort":"This string needs to have at least {0} emoji's!", - "emojiTooLong":"This string needs to have at most {0} emoji's!", - "emojiCustom":"This emoji can't be a custom discord emoji!", - "emojiInvalid":"This is an invalid emoji!", - "urlInvalid":"This url is invalid!", - "urlInvalidHttp":"This url can only use the https:// protocol!", - "urlInvalidProtocol":"This url can only use the http:// & https:// protocols!", - "urlInvalidHostname":"This url has a disallowed hostname!", - "urlInvalidExtension":"This url has an invalid extension! Choose between: {0}!", - "urlInvalidPath":"This url has an invalid path!", - "idNotUnique":"This id isn't unique, use another id instead!", - "idNonExistent":"The id {0} doesn't exist!", + "discordInvalidId":"Invalid Discord {0} ID", + "discordInvalidIdOptions":"Invalid Discord {0} ID. Alternatively, use one of the following: {1}", + "discordInvalidToken":"Invalid Discord token", + "colorInvalid":"Invalid hex color", + "emojiTooShort":"Value must contain at least {0} emoji", + "emojiTooLong":"Value must contain at most {0} emoji", + "emojiCustom":"Custom Discord emojis are not allowed here", + "emojiInvalid":"Invalid emoji", + "urlInvalid":"Invalid URL", + "urlInvalidHttp":"URL must use the https:// protocol", + "urlInvalidProtocol":"URL must use the http:// or https:// protocol", + "urlInvalidHostname":"URL hostname is not allowed", + "urlInvalidExtension":"Invalid URL extension. Allowed extensions: {0}", + "urlInvalidPath":"Invalid URL path", + "idNotUnique":"This ID is already in use. Please choose a unique ID", + "idNonExistent":"ID {0} does not exist", - "invalidType":"This property needs to be the type: {0}!", - "propertyMissing":"The property {0} is missing from this object!", - "propertyOptional":"The property {0} is optional in this object!", - "objectDisabled":"This object is disabled, enable it using {0}!", - "nullInvalid":"This property can't be null!", - "switchInvalidType":"This needs to be one of the following types: {0}!", - "objectSwitchInvalid":"This object needs to be one of the following types: {0}!", + "invalidType":"Property must be of type: {0}", + "propertyMissing":"Required property {0} is missing from the object", + "propertyOptional":"Property {0} is optional in the object", + "objectDisabled":"This object is disabled. Enable it using {0}", + "nullInvalid":"Property must not be null", + "switchInvalidType":"Value must be one of the following types: {0}", + "objectSwitchInvalid":"Object must be one of the following types: {0}", - "invalidLanguage":"This is an invalid language!", - "invalidButton":"This button needs to have at least an {0} or {1}!", - "unusedOption":"The option {0} isn't used anywhere!", - "unusedQuestion":"The question {0} isn't used anywhere!", - "dropdownOption":"A panel with dropdown enabled can only contain options of the 'ticket' type!", - "customInvalidVersion":"The version specified in your config does not match! Make sure you have updated the config to the latest version!" + "invalidLanguage":"Invalid language", + "invalidButton":"Button must have at least an {0} or {1}", + "unusedOption":"Option {0} is not used anywhere", + "unusedQuestion":"Question {0} is not used anywhere", + "dropdownOption":"A panel with dropdown can only contain options of the types: 'ticket', 'role' or 'sub-panel'.", + "customInvalidVersion":"Config version mismatch. Make sure to update your config to the latest version" } }, "actions":{ @@ -118,7 +118,9 @@ "helpSwitchText":"View Text Commands", "helpPage":"Page {0}", "withReason":"With Reason", - "withoutTranscript":"Without Transcript" + "withoutTranscript":"Without Transcript", + "blacklistAdd":"Blacklist User", + "blacklistRemove":"Release User" }, "titles":{ "created":"Ticket Created", @@ -156,122 +158,127 @@ "topicSet":"Topic Changed", "prioritySet":"Priority Changed", "priorityGet":"Ticket Priority", - "transfer":"Ticket Transferred" + "transfer":"Ticket Transferred", + "transcripts":"Transcript History" }, "descriptions":{ - "create":"Your ticket has been created. Click the button below to access it!", - "close":"The ticket has been closed successfully!", - "delete":"The ticket has been deleted successfully!", - "reopen":"The ticket has been reopened successfully!", - "claim":"The ticket has been claimed successfully!", - "unclaim":"The ticket has been unclaimed successfully!", - "pin":"The ticket has been pinned successfully!", - "unpin":"The ticket has been unpinned successfully!", - "rename":"The ticket has been renamed to {0} successfully!", - "move":"The ticket has been moved to {0} successfully!", - "add":"{0} has been added to the ticket successfully!", - "remove":"{0} has been removed from the ticket successfully!", + "create":"Your ticket is ready. Click the button below to view and continue.", + "close":"The ticket has been closed.", + "delete":"The ticket has been deleted.", + "reopen":"The ticket has been reopened.", + "claim":"The ticket has been claimed.", + "unclaim":"The ticket has been unclaimed.", + "pin":"The ticket has been pinned.", + "unpin":"The ticket has been unpinned.", + "rename":"The ticket has been renamed to {0}.", + "move":"The ticket has been moved to {0}.", + "add":"{0} has been added to the ticket.", + "remove":"{0} has been removed from the ticket.", "helpExplanation":"`` => required parameter\n`[name]` => optional parameter", - "statsReset":"The bot stats have been reset successfully!", - "statsError":"Unable to view ticket stats!\n{0} is not a ticket!", - "blacklistAdd":"{0} has been blacklisted successfully!", - "blacklistRemove":"{0} has been released successfully!", - "blacklistGetSuccess":"{0} is currently blacklisted!", - "blacklistGetEmpty":"{0} is currently not blacklisted!", - "blacklistViewEmpty":"No-one has been blacklisted yet!", - "blacklistViewTip":"Use \"/blacklist add\" to blacklist a user!", - "clearVerify":"Are you sure you want to delete multiple tickets?\nThis action can't be undone!", - "clearReady":"{0} tickets have been deleted successfully!", - "rolesEmpty":"No roles have been updated!", + "statsReset":"The bot statistics have been reset.", + "statsError":"Unable to retrieve ticket statistics.\n{0} is not a valid ticket.", + "blacklistAdd":"{0} has been blacklisted.", + "blacklistRemove":"{0} has been released.", + "blacklistGetSuccess":"{0} is blacklisted!", + "blacklistGetEmpty":"{0} is not blacklisted!", + "blacklistViewEmpty":"No users have been blacklisted yet.", + "blacklistViewTip":"Use \"/blacklist add\" to add a user to the blacklist.", + "clearVerify":"Are you sure you want to delete multiple tickets?\nThis action cannot be undone.", + "clearReady":"{0} ticket(s) have been deleted.", + "rolesEmpty":"No roles were modified.", - "autocloseLeave":"This ticket has been autoclosed because the creator left the server!", - "autocloseTimeout":"This ticket has been autoclosed because it has been inactive for more than `{0}h`!", - "autodeleteLeave":"This ticket has been autodeleted because the creator left the server!", - "autodeleteTimeout":"This ticket has been autodeleted because it has been inactive for more than `{0} days`!", - "autocloseEnabled":"Autoclose has been enabled in this ticket!\nIt will be closed when it is inactive for more than `{0}h`!", - "autocloseDisabled":"Autoclose has been disabled in this ticket!\nIt won't be closed automatically anymore!", - "autodeleteEnabled":"Autodelete has been enabled in this ticket!\nIt will be deleted when it is inactive for more than `{0} days`!", - "autodeleteDisabled":"Autodelete has been disabled in this ticket!\nIt won't be deleted automatically anymore!", + "autocloseLeave":"This ticket was automatically closed because its creator left the server.", + "autocloseTimeout":"This ticket was automatically closed due to inactivity exceeding `{0}h`.", + "autodeleteLeave":"This ticket was automatically deleted because its creator left the server.", + "autodeleteTimeout":"This ticket was automatically deleted due to inactivity exceeding `{0} days`.", + "autocloseEnabled":"Autoclose has been enabled for this ticket.\nIt will close after `{0}h` of inactivity.", + "autocloseDisabled":"Autoclose has been disabled for this ticket.\nThis ticket will no longer close automatically.", + "autodeleteEnabled":"Autodelete has been enabled for this ticket.\nIt will be deleted after `{0} days` of inactivity.", + "autodeleteDisabled":"Autodelete has been disabled for this ticket.\nThis ticket will no longer be deleted automatically.", - "ticketMessageLimit":"You can only create {0} ticket(s) at the same time!", - "ticketMessageAutoclose":"This ticket will be autoclosed when inactive for {0}h!", - "ticketMessageAutodelete":"This ticket will be autodeleted when inactive for {0} days!", - "panelReady":"The panel is available in the followup message!\nThis message can now be deleted!", + "ticketMessageLimit":"You can only have {0} active ticket(s) at a time.", + "ticketMessageAutoclose":"This ticket will automatically close after `{0}h` of inactivity.", + "ticketMessageAutodelete":"This ticket will automatically be deleted after `{0} days` of inactivity.", + "panelReady":"The panel has been sent in the follow-up message.\nYou may now delete this message.", - "topicSet":"The channel topic has been changed by {0} successfully!", - "prioritySet":"The ticket priority has been changed to {0} by {1} successfully!", - "priorityGet":"The current priority of this ticket is {0}.", - "transfer":"The ticket ownership has been transferred from {0} to {1} by {2} successfully!" + "topicSet":"The channel topic has been changed by {0}.", + "prioritySet":"The ticket priority has been changed to {0} by {1}.", + "priorityGet":"The priority of this ticket is {0}.", + "transfer":"The ticket ownership has been transferred from {0} to {1} by {2}." }, "modal":{ - "closePlaceholder":"Why did you close this ticket?", - "deletePlaceholder":"Why did you delete this ticket?", - "reopenPlaceholder":"Why did you reopen this ticket?", - "claimPlaceholder":"Why did you claim this ticket?", - "unclaimPlaceholder":"Why did you unclaim this ticket?", - "pinPlaceholder":"Why did you pin this ticket?", - "unpinPlaceholder":"Why did you unpin this ticket?" + "closePlaceholder":"Why would you like to close this ticket?", + "deletePlaceholder":"Why would you like to delete this ticket?", + "reopenPlaceholder":"Why would you like to reopen this ticket?", + "claimPlaceholder":"Why would you like to claim this ticket?", + "unclaimPlaceholder":"Why would you like to unclaim this ticket?", + "pinPlaceholder":"Why would you like to pin this ticket?", + "unpinPlaceholder":"Why would you like to unpin this ticket?" }, "logs":{ - "createLog":"A new ticket got created by {0}!", - "closeLog":"This ticket has been closed by {0}!", - "closeDm":"Your ticket has been closed in our server!", - "deleteLog":"This ticket has been deleted by {0}!", - "deleteDm":"Your ticket has been deleted in our server!", - "reopenLog":"This ticket has been reopened by {0}!", - "reopenDm":"Your ticket has been reopened in our server!", - "claimLog":"This ticket has been claimed by {0}!", - "claimDm":"Your ticket has been claimed in our server!", - "unclaimLog":"This ticket has been unclaimed by {0}!", - "unclaimDm":"Your ticket has been unclaimed in our server!", - "pinLog":"This ticket has been pinned by {0}!", - "pinDm":"Your ticket has been pinned in our server!", - "unpinLog":"This ticket has been unpinned by {0}!", - "unpinDm":"Your ticket has been unpinned in our server!", - "renameLog":"This ticket has been renamed to {0} by {1}!", - "renameDm":"Your ticket has been renamed to {0} in our server!", - "moveLog":"This ticket has been moved to {0} by {1}!", - "moveDm":"Your ticket has been moved to {0} in our server!", - "addLog":"{0} has been added to this ticket by {1}!", - "addDm":"{0} has been added to your ticket in our server!", - "removeLog":"{0} has been removed from this ticket by {1}!", - "removeDm":"{0} has been removed from your ticket in our server!", + "createLog":"A new ticket got created by {0}.", + "closeLog":"This ticket has been closed by {0}.", + "closeDm":"Your ticket has been closed.", + "deleteLog":"This ticket has been deleted by {0}.", + "deleteDm":"Your ticket has been deleted.", + "reopenLog":"This ticket has been reopened by {0}.", + "reopenDm":"Your ticket has been reopened.", + "claimLog":"This ticket has been claimed by {0}.", + "claimDm":"Your ticket has been claimed.", + "unclaimLog":"This ticket has been unclaimed by {0}.", + "unclaimDm":"Your ticket has been unclaimed.", + "pinLog":"This ticket has been pinned by {0}.", + "pinDm":"Your ticket has been pinned.", + "unpinLog":"This ticket has been unpinned by {0}.", + "unpinDm":"Your ticket has been unpinned.", + "renameLog":"This ticket has been renamed to {0} by {1}.", + "renameDm":"Your ticket has been renamed to {0}.", + "moveLog":"This ticket has been moved to {0} by {1}.", + "moveDm":"Your ticket has been moved to {0}.", + "addLog":"{0} has been added to this ticket by {1}.", + "addDm":"{0} has been added to your ticket.", + "removeLog":"{0} has been removed from this ticket by {1}.", + "removeDm":"{0} has been removed from your ticket.", - "blacklistAddLog":"{0} was blacklisted by {1}!", - "blacklistRemoveLog":"{0} was removed from the blacklist by {1}!", - "blacklistAddDm":"You have been blacklisted in our server!\nFrom now on, you are unable to create a ticket!", - "blacklistRemoveDm":"You have been removed from the blacklist in our server!\nNow you can create tickets again!", - "clearLog":"{0} tickets have been deleted by {1}!", + "blacklistAddLog":"{0} has been blacklisted by {1}.", + "blacklistRemoveLog":"{0} has been removed from the blacklist by {1}.", + "blacklistAddDm":"You have been blacklisted from this server.\nYou are no longer able to create tickets.", + "blacklistRemoveDm":"You have been removed from the blacklist.\nYou can now create tickets again.", + "clearLog":"{0} ticket(s) have been deleted by {1}.", - "transferLog":"The ownership of this ticket has been transferred from {0} to {1} by {2}!", - "transferDm":"The ownership of your ticket has been transferred from {0} to {1} in our server!", - "prioritySetLog":"The priority of this ticket has been changed to {0} by {1}!", - "prioritySetDm":"The priority of your ticket has been changed to {0} in our server!", - "roleUpdateLog":"{0} has updated their roles!", - "roleUpdateDm":"Your roles in our server have been updated!" + "transferLog":"Ticket ownership has been transferred from {0} to {1} by {2}.", + "transferDm":"Ownership of your ticket has been transferred from {0} to {1}.", + "prioritySetLog":"The priority of this ticket has been set to {0} by {1}.", + "prioritySetDm":"The priority of your ticket has been set to {0}.", + "roleUpdateLog":"{0} has modified their roles.", + "roleUpdateDm":"Your roles have been modified.", + "topicSetLog":"The priority of this ticket has been set to {0} by {1}.", + "topicSetDm":"The priority of your ticket has been set to {0}." } }, "transcripts":{ "success":{ "visit":"Visit Transcript", "ready":"Transcript Created", - "textFileDescription":"This is the text transcript of a deleted ticket!", - "htmlProgress":"Please wait while this html transcript is getting processed...", + "textFileDescription":"This is a text transcript of a deleted ticket.", + "htmlProgress":"Please wait while the HTML transcript is being generated...", - "createdChannel":"A new {0} transcript has been created in the server!", - "createdCreator":"A new {0} transcript has been created for one of your tickets!", - "createdParticipant":"A new {0} transcript has been created in one of the tickets you participated in!", - "createdActiveAdmin":"A new {0} transcript has been created in one of the tickets you participated as admin!", - "createdEveryAdmin":"A new {0} transcript has been created in one of the tickets you were admin in!", - "createdOther":"A new {0} transcript has been created!" + "createdChannel":"A new {0} transcript has been created in the server.", + "createdCreator":"A new {0} transcript has been created for one of your tickets.", + "createdParticipant":"A new {0} transcript has been created for a ticket you participated in.", + "createdActiveAdmin":"A new {0} transcript has been created for a ticket you participated in as an admin.", + "createdEveryAdmin":"A new {0} transcript has been created for a ticket you managed as an admin.", + "createdOther":"A new {0} transcript has been created." }, "errors":{ "retry":"Retry", "continue":"Delete Without Transcript", "backup":"Create Backup Transcript", - "error":"Something went wrong while trying to create the transcript.\nWhat would you like to do?\n\nThis ticket won't be deleted until you click one of these buttons.", - "title":"Transcript Error" + "error":"Something went wrong while creating the transcript.\nWhat would you like to do?\n\nThis ticket will not be deleted until you select one of the options below.", + "title":"Transcript Error", + "noHistory":"This user does not yet have any transcripts.", + "historyNotSupported":"Transcript history is currently only supported with HTML Transcripts.\nText transcript history will be available in future versions." }, "text":{ "messagesTitle":"MESSAGES", @@ -279,7 +286,7 @@ "fileTitle":"FILE", "fieldsTitle":"FIELDS", "reactionsTitle":"REACTIONS", - "statsTitle":"STATS", + "statsTitle":"STATISTICS", "emptyContent":"", "noTitle":"", "noDesc":"" @@ -298,72 +305,77 @@ "unknownPanel":"Unknown Panel", "notInGuild":"Not In Server", "channelRename":"Unable To Rename Channel", + "channelCategory":"Unable To Change Category", "busy":"Ticket Is Busy", "permissionError":"Permission Error" }, "descriptions":{ - "askForInfo":"Contact the owner of this bot for more info!", - "askForInfoResolve":"Contact the bot owner of this bot if this issue doesn't resolve after a few tries.", - "internalError":"Failed to respond to this {0} due to an internal error!", - "optionMissing":"A required parameter is missing in this command!", - "optionInvalid":"A parameter in this command is invalid!", - "optionInvalidChoose":"Choose between", - "unknownCommand":"Try visiting the help menu for more info!", - "noPermissions":"You are not allowed to use this {0}!", - "noPermissionsList":"Required Permissions: (one of them)", - "noPermissionsCooldown":"You are not allowed to use this {0} because you have a cooldown!", - "noPermissionsBlacklist":"You are not allowed to use this {0} because you have been blacklisted!", - "noPermissionsLimitGlobal":"You are not allowed to create a ticket because the server reached the max tickets limit!", - "noPermissionsLimitGlobalUser":"You are not allowed to create a ticket because you reached the max tickets limit!", - "noPermissionsLimitOption":"You are not allowed to create a ticket because the server reached the max tickets limit for this option!", - "noPermissionsLimitOptionUser":"You are not allowed to create a ticket because you reached the max tickets limit for this option!", - "unknownTicket":"Try this command again in a valid ticket!", - "deprecatedTicket":"The current channel is not a valid ticket! It might have been a ticket from an old Open Ticket version!", - "notInGuild":"This {0} doesn't work in DM! Please try it again in a server!", - "channelRename":"Due to discord ratelimits, it's currently impossible for the bot to rename the channel. The channel will automatically be renamed over 10 minutes if the bot isn't rebooted.", - "channelRenameSource":"The source of this error is: {0}", - "busy":"Unable to use this {0}!\nThe ticket is currently being processed by the bot.\n\nPlease try again in a few seconds!", - "closeBeforeMessage":"This ticket cannot be closed/deleted before a message has been sent by a user.", - "closeBeforeAdminMessage":"This ticket cannot be closed/deleted before a message has been sent by a ticket admin or support member.", - "unableToCreateTicket":"You are unable to create a ticket." + "askForInfo":"Please contact the bot owner for more information.", + "askForInfoResolve":"If the issue persists after a few attempts, please contact the bot owner.", + "internalError":"An internal error occurred while processing this {0}.", + "optionMissing":"A required parameter is missing for this command.", + "optionInvalid":"One or more parameters provided for this command are invalid.", + "optionInvalidChoose":"Please choose between", + "unknownCommand":"Please use the help menu for more information.", + "noPermissions":"You are not permitted to use this {0}.", + "noPermissionsList":"Required permissions (one of the following):", + "noPermissionsCooldown":"You cannot use this {0} while on cooldown.", + "noPermissionsBlacklist":"You cannot use this {0} because you are blacklisted.", + "noPermissionsLimitGlobal":"You cannot create a ticket because the server has reached its maximum ticket limit.", + "noPermissionsLimitGlobalUser":"You cannot create a ticket because you have reached your maximum ticket limit.", + "noPermissionsLimitOption":"You cannot create a ticket because the server has reached the maximum ticket limit for this option.", + "noPermissionsLimitOptionUser":"You cannot create a ticket because you have reached the maximum ticket limit for this option.", + "unknownTicket":"Please run this command inside a valid ticket channel.", + "deprecatedTicket":"This channel is not recognized as a valid ticket. It may have been created with an older version of the bot.", + "notInGuild":"This {0} cannot be used in direct messages. Please use it in a server.", + "channelRename":"Due to Discord rate limits, the channel could not be renamed immediately. It will be renamed automatically within 10 minutes if the bot remains online.", + "channelCategory":"Due to Discord rate limits, the channel category could not be changed immediately. It will be changed automatically within 10 minutes if the bot remains online.", + "channelRenameSource":"Error source: {0}", + "busy":"This {0} is currently unavailable.\nThe ticket is being processed by the bot.\n\nPlease try again in a few seconds.", + "closeBeforeMessage":"This ticket cannot be closed or deleted before a user has sent a message.", + "closeBeforeAdminMessage":"This ticket cannot be closed or deleted before a support member or admin has sent a message.", + "unableToCreateTicket":"You are currently unable to create a ticket.", + "messageMissing":"Unable to locate message of interaction. Use the command `{0}` instead.", + "stateExpired":"This interaction is no longer valid or has expired. Use the command `{0}` instead. It is normal to receive this error after a major Open Ticket update.", + "panelStateExpired":"This panel is no longer valid or has expired. Create a new panel using `{0}` to solve the issue. It is normal to receive this error after a major Open Ticket update." }, "optionInvalidReasons":{ - "stringRegex":"Value doesn't match pattern!", - "stringMinLength":"Value needs to be at least {0} characters!", - "stringMaxLength":"Value needs to be at most {0} characters!", - "numberInvalid":"Invalid number!", - "numberMin":"Number needs to be at least {0}!", - "numberMax":"Number needs to be at most {0}!", - "numberDecimal":"Number is not allowed to be a decimal!", - "numberNegative":"Number is not allowed to be negative!", - "numberPositive":"Number is not allowed to be positive!", - "numberZero":"Number is not allowed to be zero!", - "channelNotFound":"Unable to find channel!", - "userNotFound":"Unable to find user!", - "roleNotFound":"Unable to find role!", - "memberNotFound":"Unable to find user!", - "mentionableNotFound":"Unable to find user or role!", - "channelType":"Invalid channel type!", - "notInGuild":"This option requires you to be in a server!" + "stringRegex":"The value does not match the required pattern.", + "stringMinLength":"The value must be at least {0} characters long.", + "stringMaxLength":"The value must be at most {0} characters long.", + "numberInvalid":"Invalid number provided.", + "numberMin":"The number must be at least {0}.", + "numberMax":"The number must be at most {0}.", + "numberDecimal":"Decimals are not allowed.", + "numberNegative":"Negative numbers are not allowed.", + "numberPositive":"Positive numbers are not allowed.", + "numberZero":"Zero is not allowed.", + "channelNotFound":"Channel not found.", + "userNotFound":"User not found.", + "roleNotFound":"Role not found.", + "memberNotFound":"Member not found.", + "mentionableNotFound":"User or role not found.", + "channelType":"Invalid channel type.", + "notInGuild":"This option can only be used in a server." }, "permissions":{ - "developer":"You need to be the developer of the bot.", - "owner":"You need to be the server owner.", - "admin":"You need to be a server admin.", - "moderator":"You need to be a moderator.", - "support":"You need to be in the support team.", - "member":"You need to be a member.", - "discord-administrator":"You need to have the `ADMINISTRATOR` permission." + "developer":"This action is restricted to the bot developer.", + "owner":"You must be the server owner to use this.", + "admin":"You must have administrator permissions to use this.", + "moderator":"You must be a moderator to use this.", + "support":"You must be part of the support team to use this.", + "member":"You must be a server member to use this.", + "discord-administrator":"You must have the `ADMINISTRATOR` permission." }, "actionInvalid":{ - "close":"Ticket is already closed!", - "reopen":"Ticket is not closed yet!", - "claim":"Ticket is already claimed!", - "unclaim":"Ticket is not claimed yet!", - "pin":"Ticket is already pinned!", - "unpin":"Ticket is not pinned yet!", - "add":"This user is already able to access the ticket!", - "remove":"Unable to remove this user from the ticket!" + "close":"This ticket is already closed.", + "reopen":"This ticket is not closed.", + "claim":"This ticket is already claimed.", + "unclaim":"This ticket is not claimed.", + "pin":"This ticket is already pinned.", + "unpin":"This ticket is not pinned.", + "add":"This user already has access to this ticket.", + "remove":"This user does not have access to this ticket." } }, "params":{ @@ -387,6 +399,8 @@ "syntax":"Syntax", "originalName":"Original Name", "newName":"New Name", + "originalCategory":"Original Category", + "newCategory":"New Category", "until":"Until", "validOptions":"Valid Options", "validPanels":"Valid Panels", @@ -408,6 +422,8 @@ "participants":"Participants", "yes":"Yes", "no":"No", + "accept":"Accept", + "cancel":"Cancel", "option":"Option", "topic":"Topic", "uptime":"System Uptime", @@ -432,107 +448,110 @@ } }, "commands":{ - "reason":"Specify an optional reason that will be visible in logs.", - "help":"Get a list of all the available commands.", - "panel":"Spawn a message with a dropdown or buttons (for ticket creation).", - "panelId":"The identifier of the panel that you want to spawn.", - "panelAutoUpdate":"Do you want this panel to automatically update when edited?", - "ticket":"Instantly create a ticket.", - "ticketId":"The identifier of the ticket that you want to create.", - "close":"Close a ticket.", - "delete":"Delete a ticket.", - "deleteNoTranscript":"Delete this ticket without creating a transcript.", - "reopen":"Reopen a ticket.", - "claim":"Claim a ticket.", - "claimUser":"Claim this ticket to someone else instead of yourself.", - "unclaim":"Unclaim a ticket.", - "pin":"Pin a ticket.", - "unpin":"Unpin a ticket.", + "reason":"Optional reason shown in logs.", + "help":"Display all available commands.", + "panel":"Create a ticket panel (buttons or dropdown).", + "panelId":"ID of the panel to spawn.", + "panelAutoUpdate":"Automatically update this panel when edited.", + "ticket":"Create a ticket instantly.", + "ticketId":"ID of the ticket to create.", + "ticketOtherUser":"Create a ticket for another user.", + "close":"Close this ticket.", + "delete":"Delete this ticket.", + "deleteNoTranscript":"Delete ticket without saving a transcript.", + "reopen":"Reopen a closed ticket.", + "claim":"Claim this ticket.", + "claimUser":"Claim the ticket for another user.", + "unclaim":"Unclaim this ticket.", + "pin":"Pin this ticket.", + "unpin":"Unpin this ticket.", - "move":"Move a ticket.", - "moveId":"The identifier of the option that you want to move to.", - "rename":"Rename a ticket.", - "renameName":"The new name for this ticket.", - "add":"Add a user to a ticket.", - "addUser":"The user to add.", - "remove":"Remove a user from a ticket.", - "removeUser":"The user to remove.", + "move":"Move ticket to another option.", + "moveId":"Target option ID.", + "rename":"Rename this ticket.", + "renameName":"New name for the ticket.", + "add":"Add a user to this ticket.", + "addUser":"User to add.", + "remove":"Remove a user from this ticket.", + "removeUser":"User to remove.", - "blacklist":"Manage the ticket blacklist.", - "blacklistView":"View a list of the current blacklist.", + "blacklist":"Manage the blacklist.", + "blacklistView":"View all blacklisted users.", "blacklistAdd":"Add a user to the blacklist.", "blacklistRemove":"Remove a user from the blacklist.", - "blacklistGet":"Get the details from a blacklisted user.", - "blacklistGetUser":"The user to get details from.", - "stats":"View statistics from the bot, a member or a ticket.", - "statsReset":"Reset all the stats of the bot (and start counting from zero).", - "statsGlobal":"View the global stats.", - "statsUser":"View the stats from a user in the server.", - "statsUserUser":"The user to view.", - "statsTicket":"View the stats of a ticket in the server.", - "statsTicketTicket":"The ticket to view.", + "blacklistGet":"View blacklist entry details.", + "blacklistGetUser":"User to look up.", + "stats":"View bot, user or ticket statistics.", + "statsReset":"Reset all bot statistics.", + "statsGlobal":"View global bot statistics.", + "statsUser":"View a user's statistics.", + "statsUserUser":"User to view.", + "statsTicket":"View ticket statistics.", + "statsTicketTicket":"Ticket to view.", - "clear":"Delete multiple tickets at the same time.", - "clearFilter":"The filter for clearing tickets.", + "clear":"Delete multiple tickets at once.", + "clearFilter":"Filter used when clearing tickets.", "clearFilters":{ - "all":"All", - "open":"Open", - "close":"Closed", - "claim":"Claimed", - "unclaim":"Unclaimed", - "pin":"Pinned", - "unpin":"Unpinned", - "autoclose":"Autoclosed" + "all":"All tickets", + "open":"Open tickets", + "close":"Closed tickets", + "claim":"Claimed tickets", + "unclaim":"Unclaimed tickets", + "pin":"Pinned tickets", + "unpin":"Unpinned tickets", + "autoclose":"Autoclosed tickets" }, - "autoclose":"Manage autoclose in a ticket.", - "autocloseDisable":"Disable autoclose in this ticket.", - "autocloseEnable":"Enable autoclose in this ticket.", - "autocloseEnableTime":"The amount of hours this ticket needs to be inactive to close it.", - "autodelete":"Manage autodelete in a ticket.", - "autodeleteDisable":"Disable autodelete in this ticket.", - "autodeleteEnable":"Enable autodelete in this ticket.", - "autodeleteEnableTime":"The amount of days this ticket needs to be inactive to delete it.", + "autoclose":"Manage ticket autoclose.", + "autocloseDisable":"Disable autoclose.", + "autocloseEnable":"Enable autoclose.", + "autocloseEnableTime":"Hours of inactivity before closing.", + "autodelete":"Manage ticket autodelete.", + "autodeleteDisable":"Disable autodelete.", + "autodeleteEnable":"Enable autodelete.", + "autodeleteEnableTime":"Days of inactivity before deletion.", - "topic":"Manage the topic of the ticket channel.", - "topicSet":"Set the topic of the ticket channel.", - "topicValue":"The new topic of the channel.", - "topicList":"Get a list of all tickets with their topic and stats.", - "priority":"Manage the priority of the ticket.", - "prioritySet":"Set the priority of the ticket.", - "priorityValue":"The priority of the channel.", - "priorityGet":"Get the priority of the ticket.", - "priorityList":"Get a list of all tickets with their priority status.", - "transfer":"Transfer the ticket ownership from one user to another.", - "transferUser":"The user to transfer to." + "topic":"Manage ticket channel topic.", + "topicSet":"Set the ticket channel topic.", + "topicValue":"New channel topic text.", + "topicList":"List all ticket channel topics.", + "priority":"Manage ticket priority.", + "prioritySet":"Set ticket priority.", + "priorityValue":"Priority level.", + "priorityGet":"View ticket priority.", + "priorityList":"List all ticket priorities.", + "transfer":"Transfer ticket ownership.", + "transferUser":"User to transfer to.", + "transcripts":"View ticket transcript history of a user.", + "transcriptsUser":"The user to view." }, "helpMenu":{ - "help":"Get a list of all the available commands.", - "ticket":"Instantly create a ticket.", - "close":"Close a ticket, this disables writing in this channel.", - "delete":"Delete a ticket, this creates a transcript when enabled.", - "reopen":"Reopen a ticket, this enables writing in this channel again.", - "pin":"Pin a ticket. This will move the ticket to the top and will add a '📌' emoij to the name.", - "unpin":"Unpin a ticket. The ticket will stay on it's position but will lose the '📌' emoij.", - "move":"Move a ticket. This will change the type of this ticket.", - "rename":"Rename a ticket. This will change the channel name of this ticket.", - "claim":"Claim a ticket. With this, you can let your team know you are handling this ticket.", - "unclaim":"Unclaim a ticket. With this, you can let your team know that this ticket is free again.", - "add":"Add a user to a ticket. This will allow the user to read & write in this ticket.", - "remove":"Remove a user from a ticket. This will remove the ability to read & write for a user in this ticket.", - "panel":"Spawn a message with a dropdown or buttons (for ticket creation).", - "blacklistView":"View a list of the current blacklist.", + "help":"View all available commands.", + "ticket":"Create a ticket instantly.", + "close":"Close this ticket and disable messaging in the channel.", + "delete":"Delete this ticket (creates a transcript if enabled).", + "reopen":"Reopen a closed ticket and restore messaging.", + "pin":"Pin this ticket and move it to the top with '📌'.", + "unpin":"Unpin this ticket and remove the '📌' emoji.", + "move":"Move this ticket to a different option or ticket type.", + "rename":"Rename this ticket channel.", + "claim":"Claim this ticket to show you are handling it.", + "unclaim":"Unclaim this ticket to mark it as available.", + "add":"Add a user to this ticket.", + "remove":"Remove a user's access from this ticket.", + "panel":"Create a ticket panel with buttons or a dropdown.", + "blacklistView":"View all blacklisted users.", "blacklistAdd":"Add a user to the blacklist.", "blacklistRemove":"Remove a user from the blacklist.", - "blacklistGet":"Get the details from a blacklisted user.", - "statsGlobal":"View the global stats.", - "statsTicket":"View the stats of a ticket in the server.", - "statsUser":"View the stats from a user in the server.", - "statsReset":"Reset all the stats of the bot (and start counting from zero).", - "autocloseDisable":"Disable autoclose in this ticket.", - "autocloseEnable":"Enable autoclose in this ticket.", - "autodeleteDisable":"Disable autodelete in this ticket.", - "autodeleteEnable":"Enable autodelete in this ticket.", + "blacklistGet":"View details of a blacklisted user.", + "statsGlobal":"View global bot statistics.", + "statsTicket":"View statistics for a ticket.", + "statsUser":"View statistics for a user.", + "statsReset":"Reset all bot statistics.", + "autocloseDisable":"Disable autoclose for this ticket.", + "autocloseEnable":"Enable autoclose for this ticket.", + "autodeleteDisable":"Disable autodelete for this ticket.", + "autodeleteEnable":"Enable autodelete for this ticket.", "categories":{ "general":"General Commands", "basicTicket":"Basic Ticket Commands", @@ -545,10 +564,10 @@ }, "stats":{ "scopes":{ - "global":"Global Stats", - "system":"System Stats", - "user":"User Stats", - "ticket":"Ticket Stats", + "global":"Global Statistics", + "system":"System Statistics", + "user":"User Statistics", + "ticket":"Ticket Statistics", "participants":"Participants", "messages":"Messages" }, @@ -592,9 +611,10 @@ } }, "panel":{ - "selectTicket":"Select your ticket", - "selectRole":"Select your role", - "selectOption":"Select your option" + "selectTicket":"Select a ticket", + "selectRole":"Select a role", + "selectOption":"Select an option", + "selectPriorityLevel":"Select priority level" }, "priorities":{ "urgent":"Urgent", diff --git a/languages/czech.json b/languages/czech.json index ef4a93a..45ac89b 100644 --- a/languages/czech.json +++ b/languages/czech.json @@ -1,8 +1,8 @@ { "_TRANSLATION":{ - "otversion":"v4.1.3", + "otversion":"v4.2.0", "translators":["spyeye_"], - "lastedited":"16/02/2026", + "lastedited":"25/05/2026", "language":"Czech", "automated":false }, @@ -99,7 +99,7 @@ "invalidButton":"Toto tlačítko musí mít alespoň {0} nebo {1}!", "unusedOption":"Možnost {0} se nikde nepoužívá!", "unusedQuestion":"Otázka {0} není nikde použita!", - "dropdownOption":"Panel s povoleným rozevíracím seznamem může obsahovat pouze možnosti typu „ticketu“!", + "dropdownOption":"Panel s rozbalovací nabídkou může obsahovat pouze možnosti typu: 'ticket', 'role' nebo 'sub-panel'.", "customInvalidVersion":"Verze uvedená ve vaší konfiguraci neodpovídá! Ujistěte se, že jste konfiguraci aktualizovali na nejnovější verzi!" } }, @@ -118,7 +118,9 @@ "helpSwitchText":"Zobrazit textové příkazy", "helpPage":"Strana {0}", "withReason":"S důvodem", - "withoutTranscript":"Bez důvodu" + "withoutTranscript":"Bez důvodu", + "blacklistAdd":"Přidat Uživatele na Blacklist", + "blacklistRemove":"Uvolnit Uživatele" }, "titles":{ "created":"Ticket vytvořen", @@ -156,7 +158,8 @@ "topicSet":"Téma změněno", "prioritySet":"Priorita změněna", "priorityGet":"Priorita tiketu", - "transfer":"Ticket přenesen" + "transfer":"Ticket přenesen", + "transcripts":"Historie Přepisů" }, "descriptions":{ "create":"Tvůj ticket byl vytvořen. Klikni na tlačítko pod zprávou pro zobrazení!", @@ -249,7 +252,9 @@ "prioritySetLog":"Priorita tohoto tiketu byla změněna na {0} {1}!", "prioritySetDm":"Priorita vašeho tiketu byla změněna na {0} na našem serveru!", "roleUpdateLog":"{0} aktualizoval své role!", - "roleUpdateDm":"Vaše role na našem serveru byly aktualizovány!" + "roleUpdateDm":"Vaše role na našem serveru byly aktualizovány!", + "topicSetLog":"Priorita tohoto ticketu byla nastavena na {0} uživatelem {1}.", + "topicSetDm":"Priorita vašeho ticketu byla nastavena na {0}." } }, "transcripts":{ @@ -271,7 +276,9 @@ "continue":"Vymazat bez přepisu", "backup":"Vytvořit záložní přepis", "error":"Při pokusu o vytvoření přepisu se něco pokazilo.\nCo chcete udělat?\n\nTento ticket nebude smazán, dokud nekliknete na jedno z těchto tlačítek.", - "title":"Chyba přepisu" + "title":"Chyba přepisu", + "noHistory":"Tento uživatel zatím nemá žádné přepisy.", + "historyNotSupported":"Historie přepisů je momentálně podporována pouze s HTML Transcripts.\nHistorie textových přepisů bude dostupná v budoucích verzích." }, "text":{ "messagesTitle":"ZPRÁVY", @@ -298,6 +305,7 @@ "unknownPanel":"Neznámý panel", "notInGuild":"Nejsi na serveru", "channelRename":"Nepodařilo se přejmenovat kanál", + "channelCategory":"Nelze Změnit Kategorii", "busy":"Ticket je zaneprázdněn", "permissionError":"Chyba oprávnění" }, @@ -321,11 +329,15 @@ "deprecatedTicket":"Aktuální kanál není platný ticket! Může se jednat o ticket ze starší verze Open Ticketu!", "notInGuild":"Tento {0} nefunguje v DM! Zkus to znovu na serveru!", "channelRename":"Kvůli ratelimitům Discordu není momentálně možné, aby bot přejmenoval kanál. Kanál bude automaticky přejmenován do 10 minut, pokud nedojde k restartu bota.", + "channelCategory":"Kvůli omezením Discord rate limitů nebylo možné kategorii kanálu okamžitě změnit. Pokud bot zůstane online, bude automaticky změněna do 10 minut.", "channelRenameSource":"Zdroj této chyby je: {0}", "busy":"Nelze použít tento {0}!\nTicket je momentálně zpracováván botem.\n\nZkus to prosím znovu za několik sekund!", "closeBeforeMessage":"Tento tiket nelze uzavřít/smazat, dokud uživatel neodešle zprávu.", "closeBeforeAdminMessage":"Tento tiket nelze uzavřít/smazat, dokud admin tiketu nebo člen podpory neodešle zprávu.", - "unableToCreateTicket":"Nemůžete vytvořit tiket." + "unableToCreateTicket":"Nemůžete vytvořit tiket.", + "messageMissing":"Nepodařilo se najít zprávu interakce. Místo toho použijte příkaz `{0}`.", + "stateExpired":"Tato interakce již není platná nebo vypršela. Místo toho použijte příkaz `{0}`. Je normální obdržet tuto chybu po velké aktualizaci Open Ticket.", + "panelStateExpired":"Tento panel již není platný nebo vypršel. Vytvořte nový panel pomocí `{0}`, abyste problém vyřešili. Je normální obdržet tuto chybu po velké aktualizaci Open Ticket." }, "optionInvalidReasons":{ "stringRegex":"Hodnota neodpovídá vzoru!", @@ -387,6 +399,8 @@ "syntax":"Syntaxe", "originalName":"Původní jméno", "newName":"Nové jméno", + "originalCategory":"Původní Kategorie", + "newCategory":"Nová Kategorie", "until":"Do", "validOptions":"Platné možnosti", "validPanels":"Platné panely", @@ -408,6 +422,8 @@ "participants":"ÚČASTNÍCI", "yes":"Ano", "no":"Ne", + "accept":"Přijmout", + "cancel":"Zrušit", "option":"Možnost", "topic":"Téma", "uptime":"Doba provozu systému", @@ -439,6 +455,7 @@ "panelAutoUpdate":"Chceš, aby se tento panel automaticky aktualizoval při úpravách?", "ticket":"Okamžitě vytvoř ticket.", "ticketId":"Identifikátor ticketu, který chceš vytvořit.", + "ticketOtherUser":"Vytvořit ticket pro jiného uživatele.", "close":"Uzavři ticket.", "delete":"Smaž ticket.", "deleteNoTranscript":"Smaž tento ticket bez vytvoření přepisu.", @@ -504,7 +521,9 @@ "priorityGet":"Získejte prioritu tiketu.", "priorityList":"Získejte seznam všech tiketů s jejich prioritou.", "transfer":"Přeneste vlastnictví tiketu od jednoho uživatele k druhému.", - "transferUser":"Uživatel, na kterého se přenáší." + "transferUser":"Uživatel, na kterého se přenáší.", + "transcripts":"Zobrazit historii přepisů ticketů uživatele.", + "transcriptsUser":"Uživatel k zobrazení." }, "helpMenu":{ "help":"Získej seznam všech dostupných příkazů.", @@ -594,7 +613,8 @@ "panel":{ "selectTicket":"Vyberte svůj tiket", "selectRole":"Vyberte svou roli", - "selectOption":"Vyberte svou možnost" + "selectOption":"Vyberte svou možnost", + "selectPriorityLevel":"Vyberte úroveň priority" }, "priorities":{ "urgent":"Naléhavé", diff --git a/languages/danish.json b/languages/danish.json index 4f93fed..5f9d180 100644 --- a/languages/danish.json +++ b/languages/danish.json @@ -1,8 +1,8 @@ { "_TRANSLATION":{ - "otversion":"v4.1.3", + "otversion":"v4.2.0", "translators":["the_gamer"], - "lastedited":"16/02/2026", + "lastedited":"25/05/2026", "language":"Danish", "automated":false }, @@ -99,7 +99,7 @@ "invalidButton":"Denne knap skal have mindst en {0} eller {1}!", "unusedOption":"Muligheden {0} bruges ikke nogen steder!", "unusedQuestion":"Spørgsmålet {0} bruges ikke nogen steder!", - "dropdownOption":"Et panel med dropdown aktiveret kan kun indeholde muligheder af typen 'ticket'!", + "dropdownOption":"Et panel med rullemenu kan kun indeholde muligheder af typerne: 'ticket', 'role' eller 'sub-panel'.", "customInvalidVersion":"Den version, der er angivet i din konfiguration, matcher ikke! Sørg for, at du har opdateret konfigurationen til den nyeste version!" } }, @@ -118,7 +118,9 @@ "helpSwitchText":"Vis Tekst Kommandoer", "helpPage":"Side {0}", "withReason":"Med Årsag", - "withoutTranscript":"Uden Transkript" + "withoutTranscript":"Uden Transkript", + "blacklistAdd":"Blacklist Bruger", + "blacklistRemove":"Frigiv Bruger" }, "titles":{ "created":"Ticket Oprettet", @@ -156,7 +158,8 @@ "topicSet":"Emne Ændret", "prioritySet":"Prioritet Ændret", "priorityGet":"Ticket Prioritet", - "transfer":"Ticket Overført" + "transfer":"Ticket Overført", + "transcripts":"Transskript Historik" }, "descriptions":{ "create":"Din ticket er blevet oprettet. Klik på knappen nedenfor for at få adgang til den!", @@ -249,7 +252,9 @@ "prioritySetLog":"Prioriteten af denne ticket er blevet ændret til {0} af {1}!", "prioritySetDm":"Prioriteten af din ticket er blevet ændret til {0} på vores server!", "roleUpdateLog":"{0} har opdateret deres roller!", - "roleUpdateDm":"Dine roller på vores server er blevet opdateret!" + "roleUpdateDm":"Dine roller på vores server er blevet opdateret!", + "topicSetLog":"Prioriteten for denne ticket er blevet sat til {0} af {1}.", + "topicSetDm":"Prioriteten for din ticket er blevet sat til {0}." } }, "transcripts":{ @@ -271,7 +276,9 @@ "continue":"Slet uden transkript", "backup":"Opret backup-transkript", "error":"Noget gik galt, da vi forsøgte at oprette transkriptet.\nHvad vil du gerne gøre?\n\nDenne ticket bliver ikke slettet, før du klikker på en af disse knapper.", - "title":"Transkriptfejl" + "title":"Transkriptfejl", + "noHistory":"Denne bruger har endnu ingen transskripter.", + "historyNotSupported":"Transskript historik understøttes i øjeblikket kun med HTML Transcripts.\nTeksttransskript historik vil være tilgængelig i fremtidige versioner." }, "text":{ "messagesTitle":"BESKEDER", @@ -298,6 +305,7 @@ "unknownPanel":"Ukendt panel", "notInGuild":"Ikke på server", "channelRename":"Kan ikke omdøbe kanalen", + "channelCategory":"Kan Ikke Ændre Kategori", "busy":"Ticket er optaget", "permissionError":"Tilladelsesfejl" }, @@ -321,11 +329,15 @@ "deprecatedTicket":"Den aktuelle kanal er ikke en gyldig ticket! Det kan være en ticket fra en ældre version af Open Ticket!", "notInGuild":"Denne {0} virker ikke i DM! Prøv igen på en server!", "channelRename":"På grund af Discord-ratelimits er det i øjeblikket umuligt for botten at omdøbe kanalen. Kanalen vil automatisk blive omdøbt inden for 10 minutter, hvis botten ikke genstartes.", + "channelCategory":"På grund af Discord rate limits kunne kanalens kategori ikke ændres med det samme. Den vil automatisk blive ændret inden for 10 minutter, hvis botten forbliver online.", "channelRenameSource":"Kilden til denne fejl er: {0}", "busy":"Kan ikke bruge denne {0}!\nTicketen bliver i øjeblikket behandlet af botten.\n\nPrøv igen om et par sekunder!", "closeBeforeMessage":"Denne ticket kan ikke lukkes/slettes, før en bruger har sendt en besked.", "closeBeforeAdminMessage":"Denne ticket kan ikke lukkes/slettes, før en ticket-admin eller supportmedlem har sendt en besked.", - "unableToCreateTicket":"Du kan ikke oprette en ticket." + "unableToCreateTicket":"Du kan ikke oprette en ticket.", + "messageMissing":"Kunne ikke finde interaktionsbeskeden. Brug kommandoen `{0}` i stedet.", + "stateExpired":"Denne interaktion er ikke længere gyldig eller er udløbet. Brug kommandoen `{0}` i stedet. Det er normalt at modtage denne fejl efter en større Open Ticket-opdatering.", + "panelStateExpired":"Dette panel er ikke længere gyldigt eller er udløbet. Opret et nyt panel med `{0}` for at løse problemet. Det er normalt at modtage denne fejl efter en større Open Ticket-opdatering." }, "optionInvalidReasons":{ "stringRegex":"Værdien matcher ikke mønsteret!", @@ -387,6 +399,8 @@ "syntax":"Syntaks", "originalName":"Originalt navn", "newName":"Nyt navn", + "originalCategory":"Original Kategori", + "newCategory":"Ny Kategori", "until":"Indtil", "validOptions":"Gyldige indstillinger", "validPanels":"Gyldige paneler", @@ -408,6 +422,8 @@ "participants":"DELTAGERE", "yes":"Ja", "no":"Nej", + "accept":"Accepter", + "cancel":"Annuller", "option":"Mulighed", "topic":"Emne", "uptime":"System Oppetid", @@ -439,6 +455,7 @@ "panelAutoUpdate":"Vil du have, at dette panel automatisk opdateres, når det redigeres?", "ticket":"Opret en ticket med det samme.", "ticketId":"Identifikatoren for ticketen, som du vil oprette.", + "ticketOtherUser":"Opret en ticket for en anden bruger.", "close":"Luk en ticket.", "delete":"Slet en ticket.", "deleteNoTranscript":"Slet denne ticket uden at oprette et transkript.", @@ -504,7 +521,9 @@ "priorityGet":"Få prioritet for ticketen.", "priorityList":"Få en liste over alle tickets med deres prioritet.", "transfer":"Overfør ticket-ejerskab fra en bruger til en anden.", - "transferUser":"Brugeren der skal overføres til." + "transferUser":"Brugeren der skal overføres til.", + "transcripts":"Se en brugers ticket transskript historik.", + "transcriptsUser":"Brugeren der skal vises." }, "helpMenu":{ "help":"Få en liste over alle de tilgængelige kommandoer.", @@ -594,7 +613,8 @@ "panel":{ "selectTicket":"Vælg din ticket", "selectRole":"Vælg din rolle", - "selectOption":"Vælg dit valg" + "selectOption":"Vælg dit valg", + "selectPriorityLevel":"Vælg prioritetsniveau" }, "priorities":{ "urgent":"Haster", diff --git a/languages/dutch.json b/languages/dutch.json index f13ddfc..3824d14 100644 --- a/languages/dutch.json +++ b/languages/dutch.json @@ -1,8 +1,8 @@ { "_TRANSLATION":{ - "otversion":"v4.1.3", + "otversion":"v4.2.0", "translators":["DJj123dj"], - "lastedited":"16/02/2026", + "lastedited":"25/05/2026", "language":"Dutch", "automated":false }, @@ -99,7 +99,7 @@ "invalidButton":"Deze knop moet een {0} of {1} hebben!", "unusedOption":"De optie {0} wordt nergens gebruikt!", "unusedQuestion":"The vraag {0} wordt nergens gebruikt!", - "dropdownOption":"Een paneel met dropdown mag alleen opties bevaten van het 'ticket' type!", + "dropdownOption":"Een paneel met dropdown kan alleen opties bevatten van de types: 'ticket', 'role' of 'sub-panel'.", "customInvalidVersion":"De versie gespecificeerd in u configbestand komt niet overeen! Zorg dat u het configbestand geüpdatet heeft naar de laatste versie!" } }, @@ -118,7 +118,9 @@ "helpSwitchText":"Bekijk Text Commands", "helpPage":"Pagina {0}", "withReason":"Met Reden", - "withoutTranscript":"Zonder Transcript" + "withoutTranscript":"Zonder Transcript", + "blacklistAdd":"Gebruiker Blacklisten", + "blacklistRemove":"Gebruiker Vrijgeven" }, "titles":{ "created":"Ticket Gecreëerd", @@ -156,7 +158,8 @@ "topicSet":"Onderwerp Veranderd", "prioritySet":"Prioriteit Veranderd", "priorityGet":"Ticket Prioriteit", - "transfer":"Ticket Overgedragen" + "transfer":"Ticket Overgedragen", + "transcripts":"Transcript Geschiedenis" }, "descriptions":{ "create":"Je ticket is aangemaakt. Klik op de knop hier onder om er naar toe te gaan!", @@ -249,7 +252,9 @@ "prioritySetLog":"De prioriteit van dit ticket is veranderd naar {0} door {1}!", "prioritySetDm":"De prioriteit van u ticket is veranderd naar {0} in onze server!", "roleUpdateLog":"{0} heeft zijn/haar rollen bewerkt!", - "roleUpdateDm":"U rollen in onze server zijn veranderd!" + "roleUpdateDm":"U rollen in onze server zijn veranderd!", + "topicSetLog":"De prioriteit van dit ticket is ingesteld op {0} door {1}.", + "topicSetDm":"De prioriteit van je ticket is ingesteld op {0}." } }, "transcripts":{ @@ -271,7 +276,9 @@ "continue":"Verwijder Zonder Transcript", "backup":"Maak Backup Transcript", "error":"Er is iets misgegaan bij het maken van het transcript.\nWat wil je doen?\n\nDit ticket wordt niet meer verwijderd to je een van de onderstaande knoppen gebruikt.", - "title":"Transcript Error" + "title":"Transcript Error", + "noHistory":"Deze gebruiker heeft nog geen transcripts.", + "historyNotSupported":"Transcriptgeschiedenis wordt momenteel alleen ondersteund met HTML Transcripts.\nGeschiedenis van teksttranscripts zal beschikbaar zijn in toekomstige versies." }, "text":{ "messagesTitle":"BERICHTEN", @@ -298,6 +305,7 @@ "unknownPanel":"Onbekend Paneel", "notInGuild":"Niet In Server", "channelRename":"Kan Kanaal Niet Hernoemen", + "channelCategory":"Kan Categorie Niet Wijzigen", "busy":"Ticket Is Bezig", "permissionError":"Permission Error" }, @@ -321,11 +329,15 @@ "deprecatedTicket":"Het huidige kanaal is geen geldig ticket! Het is misschien een ticket van een oudere Open Ticket versie!", "notInGuild":"Deze {0} werkt niet in DM! Probeer het opnieuw in een server!", "channelRename":"Door discord ratelimits is het op dit moment onmogelijk voor de bot om het kanaal te hernoemen. Het kanaal zal automatisch hernoemd worden over 10 min als de bot niet herstart wordt.", + "channelCategory":"Vanwege Discord rate limits kon de kanaalcategorie niet onmiddellijk worden gewijzigd. Deze wordt automatisch binnen 10 minuten gewijzigd als de bot online blijft.", "channelRenameSource":"De bron van deze error is: {0}", "busy":"Kan deze {0} niet gebruiken!\nHet ticket wordt op dit moment verwerkt door de bot.\n\nProbeer het opnieuw binnen een paar seconden!", "closeBeforeMessage":"Dit ticket kan niet gesloten/verwijderd worden vooraleer er een bericht door een gebruiker verstuurd is.", "closeBeforeAdminMessage":"Dit ticket kan niet gesloten/verwijderd worden vooraleer er een bericht door een ticket admin of supportlid verstuurd is.", - "unableToCreateTicket":"U kunt geen ticket aanmaken." + "unableToCreateTicket":"U kunt geen ticket aanmaken.", + "messageMissing":"Kan het bericht van de interactie niet vinden. Gebruik in plaats daarvan het commando `{0}`.", + "stateExpired":"Deze interactie is niet langer geldig of is verlopen. Gebruik in plaats daarvan het commando `{0}`. Het is normaal om deze fout te ontvangen na een grote Open Ticket-update.", + "panelStateExpired":"Dit paneel is niet langer geldig of is verlopen. Maak een nieuw paneel met `{0}` om het probleem op te lossen. Het is normaal om deze fout te ontvangen na een grote Open Ticket-update." }, "optionInvalidReasons":{ "stringRegex":"Waarde is niet gelijk aan het patroon!", @@ -387,6 +399,8 @@ "syntax":"Syntax", "originalName":"Originele Naam", "newName":"Nieuwe Naam", + "originalCategory":"Originele Categorie", + "newCategory":"Nieuwe Categorie", "until":"Tot", "validOptions":"Geldige Opties", "validPanels":"Geldige Panelen", @@ -408,6 +422,8 @@ "participants":"Deelnemers", "yes":"Ja", "no":"Nee", + "accept":"Accepteren", + "cancel":"Annuleren", "option":"Optie", "topic":"Onderwerp", "uptime":"Systeem Uptime", @@ -439,6 +455,7 @@ "panelAutoUpdate":"Wil je dat dit paneel automatisch bijwerkt wanneer het aangepast wordt?", "ticket":"Maak een instant ticket.", "ticketId":"Het id van het ticket dat je wilt aanmaken.", + "ticketOtherUser":"Maak een ticket aan voor een andere gebruiker.", "close":"Sluit een ticket, dit schakelt schrijven in het kanaal uit.", "delete":"Verwijder een ticket, dit maakt een transcript waneer ingeschakeld.", "deleteNoTranscript":"Verwijder dit ticket zonder een transcript aan te maken.", @@ -504,7 +521,9 @@ "priorityGet":"Bekijk de prioriteit van een ticket.", "priorityList":"Bekijk een lijst van alle tickets met hun prioriteit.", "transfer":"Draag het ticket eigendom over van de ene naar de andere gebruiker.", - "transferUser":"De user om naar over te dragen." + "transferUser":"De user om naar over te dragen.", + "transcripts":"Bekijk de ticket transcriptgeschiedenis van een gebruiker.", + "transcriptsUser":"De gebruiker om te bekijken." }, "helpMenu":{ "help":"Krijg een lijst van alle beschikbare commands.", @@ -594,7 +613,8 @@ "panel":{ "selectTicket":"Selecteer u ticket", "selectRole":"Selecteer u rol", - "selectOption":"Selecteer u optie" + "selectOption":"Selecteer u optie", + "selectPriorityLevel":"Selecteer prioriteitsniveau" }, "priorities":{ "urgent":"Urgent", diff --git a/languages/english.json b/languages/english.json index a833cf8..8e73d7c 100644 --- a/languages/english.json +++ b/languages/english.json @@ -1,8 +1,8 @@ { "_TRANSLATION":{ - "otversion":"v4.1.3", + "otversion":"v4.2.0", "translators":["DJj123dj"], - "lastedited":"16/02/2026", + "lastedited":"25/05/2026", "language":"English", "automated":false }, @@ -13,94 +13,94 @@ "typeWarning":"[WARNING]", "typeInfo":"[INFO]", "headerConfigChecker":"CONFIG CHECKER", - "headerDescription":"check for errors in your config files!", - "footerError":"the bot won't start until all {0}'s are fixed!", - "footerWarning":"it's recommended to fix all {0}'s before starting!", + "headerDescription":"Validating config files...", + "footerError":"The bot will not start until all {0}'s are resolved.", + "footerWarning":"The bot may behave unexpectedly until all {0}'s are resolved.", "footerSupport":"SUPPORT: {0} - DOCS: {1}", - "compactInformation":"use {0} for more information!", + "compactInformation":"Use {0} for a detailed config report.", "dataPath":"path", "dataDocs":"docs", "dataMessages":"message" }, "messages":{ - "stringTooShort":"This string can't be shorter than {0} characters!", - "stringTooLong":"This string can't be longer than {0} characters!", - "stringLengthInvalid":"This string needs to be {0} characters long!", - "stringStartsWith":"This string needs to start with {0}!", - "stringEndsWith":"This string needs to end with {0}!", - "stringContains":"This string needs to contain {0}!", - "stringChoices":"This string can only be one of the following values: {0}!", - "stringRegex":"This string is invalid!", - "stringInvertedContains":"This string is not allowed to contain {0}!", - "stringLowercase":"This string must be written in lowercase only!", - "stringUppercase":"This string must be written in uppercase only!", - "stringSpecialCharacters":"This string is not allowed to contain any special characters! (a-z, 0-9 & space only)", - "stringNoSpaces":"This string is not allowed to contain spaces!", - "stringCapitalWord":"It's recommended that each word in this string starts with a capital letter!", - "stringCapitalSentence":"It looks like some sentences in this string don't start with a capital letter!", - "stringPunctuation":"It looks like the sentence in this string doesn't end with a punctuation mark!", + "stringTooShort":"Text must be at least {0} characters long", + "stringTooLong":"Text must be no longer than {0} characters", + "stringLengthInvalid":"Text must be exactly {0} characters long", + "stringStartsWith":"Text must start with {0}", + "stringEndsWith":"Text must end with {0}", + "stringContains":"Text must contain {0}", + "stringChoices":"Text must be one of the following: {0}", + "stringRegex":"Text does not match the required format", + "stringInvertedContains":"Text must not contain {0}", + "stringLowercase":"Text must be entirely lowercase", + "stringUppercase":"Text must be entirely uppercase", + "stringSpecialCharacters":"Text must only contain letters (a–z), numbers (0–9), and spaces", + "stringNoSpaces":"Text must not contain spaces", + "stringCapitalWord":"Each word in this value should start with a capital letter", + "stringCapitalSentence":"One or more sentences in this value do not start with a capital letter", + "stringPunctuation":"The sentence in this value does not end with a punctuation mark", - "numberTooShort":"This number can't be shorter than {0} characters!", - "numberTooLong":"This number can't be longer than {0} characters!", - "numberLengthInvalid":"This number needs to be {0} characters long!", - "numberTooSmall":"This number needs to be at least {0}!", - "numberTooLarge":"This number needs to be at most {0}!", - "numberNotEqual":"This number needs to be {0}!", - "numberStep":"This number needs to be a multiple of {0}!", - "numberStepOffset":"This number needs to be a multiple of {0} starting with {1}!", - "numberStartsWith":"This number needs to start with {0}!", - "numberEndsWith":"This number needs to end with {0}!", - "numberContains":"This number needs to contain {0}!", - "numberChoices":"This number can only be one of the following values: {0}!", - "numberFloat":"This number can't be a decimal!", - "numberNegative":"This number can't be negative!", - "numberPositive":"This number can't be positive!", - "numberZero":"This number can't be zero!", - "numberNan":"This number can't be NaN (Not A Number)!", - "numberInvertedContains":"This number is not allowed to contain {0}!", + "numberTooShort":"Number must be at least {0} digits long", + "numberTooLong":"Number must be no longer than {0} digits", + "numberLengthInvalid":"Number must be exactly {0} digits long", + "numberTooSmall":"Number must be at least {0}", + "numberTooLarge":"Number must be at most {0}", + "numberNotEqual":"Number must be exactly {0}", + "numberStep":"Number must be a multiple of {0}", + "numberStepOffset":"Number must be a multiple of {0}, starting from {1}", + "numberStartsWith":"Number must start with {0}", + "numberEndsWith":"Number must end with {0}", + "numberContains":"Number must contain {0}", + "numberChoices":"Number must be one of the following: {0}", + "numberFloat":"Number must be a whole number", + "numberNegative":"Number must be a positive number", + "numberPositive":"Number must be a negative number", + "numberZero":"Number must not be zero", + "numberNan":"Number must be a valid number", + "numberInvertedContains":"Number must not contain {0}", - "booleanTrue":"This boolean can't be true!", - "booleanFalse":"This boolean can't be false!", + "booleanTrue":"Boolean must be false", + "booleanFalse":"Boolean must be true", - "arrayEmptyDisabled":"This array isn't allowed to be empty!", - "arrayEmptyRequired":"This array is required to be empty!", - "arrayTooShort":"This array needs to have a length of at least {0}!", - "arrayTooLong":"This array needs to have a length of at most {0}!", - "arrayLengthInvalid":"This array needs to have a length of {0}!", - "arrayInvalidTypes":"This array can only contain the following types: {0}!", - "arrayDouble":"This array doesn't allow the same value twice!", + "arrayEmptyDisabled":"List must not be empty", + "arrayEmptyRequired":"List must be empty", + "arrayTooShort":"List must have at least {0} items", + "arrayTooLong":"List must have at most {0} items", + "arrayLengthInvalid":"List must have exactly {0} items", + "arrayInvalidTypes":"List may only contain the following types: {0}", + "arrayDouble":"List must not contain duplicate values", - "discordInvalidId":"This is an invalid discord {0} id!", - "discordInvalidIdOptions":"This is an invalid discord {0} id! You can also use one of these: {1}!", - "discordInvalidToken":"This is an invalid discord token (syntactically)!", - "colorInvalid":"This is an invalid hex color!", - "emojiTooShort":"This string needs to have at least {0} emoji's!", - "emojiTooLong":"This string needs to have at most {0} emoji's!", - "emojiCustom":"This emoji can't be a custom discord emoji!", - "emojiInvalid":"This is an invalid emoji!", - "urlInvalid":"This url is invalid!", - "urlInvalidHttp":"This url can only use the https:// protocol!", - "urlInvalidProtocol":"This url can only use the http:// & https:// protocols!", - "urlInvalidHostname":"This url has a disallowed hostname!", - "urlInvalidExtension":"This url has an invalid extension! Choose between: {0}!", - "urlInvalidPath":"This url has an invalid path!", - "idNotUnique":"This id isn't unique, use another id instead!", - "idNonExistent":"The id {0} doesn't exist!", + "discordInvalidId":"Invalid Discord {0} ID", + "discordInvalidIdOptions":"Invalid Discord {0} ID. Alternatively, use one of the following: {1}", + "discordInvalidToken":"Invalid Discord token", + "colorInvalid":"Invalid hex color", + "emojiTooShort":"Value must contain at least {0} emoji", + "emojiTooLong":"Value must contain at most {0} emoji", + "emojiCustom":"Custom Discord emojis are not allowed here", + "emojiInvalid":"Invalid emoji", + "urlInvalid":"Invalid URL", + "urlInvalidHttp":"URL must use the https:// protocol", + "urlInvalidProtocol":"URL must use the http:// or https:// protocol", + "urlInvalidHostname":"URL hostname is not allowed", + "urlInvalidExtension":"Invalid URL extension. Allowed extensions: {0}", + "urlInvalidPath":"Invalid URL path", + "idNotUnique":"This ID is already in use. Please choose a unique ID", + "idNonExistent":"ID {0} does not exist", - "invalidType":"This property needs to be the type: {0}!", - "propertyMissing":"The property {0} is missing from this object!", - "propertyOptional":"The property {0} is optional in this object!", - "objectDisabled":"This object is disabled, enable it using {0}!", - "nullInvalid":"This property can't be null!", - "switchInvalidType":"This needs to be one of the following types: {0}!", - "objectSwitchInvalid":"This object needs to be one of the following types: {0}!", + "invalidType":"Property must be of type: {0}", + "propertyMissing":"Required property {0} is missing from the object", + "propertyOptional":"Property {0} is optional in the object", + "objectDisabled":"This object is disabled. Enable it using {0}", + "nullInvalid":"Property must not be null", + "switchInvalidType":"Value must be one of the following types: {0}", + "objectSwitchInvalid":"Object must be one of the following types: {0}", - "invalidLanguage":"This is an invalid language!", - "invalidButton":"This button needs to have at least an {0} or {1}!", - "unusedOption":"The option {0} isn't used anywhere!", - "unusedQuestion":"The question {0} isn't used anywhere!", - "dropdownOption":"A panel with dropdown enabled can only contain options of the 'ticket' type!", - "customInvalidVersion":"The version specified in your config does not match! Make sure you have updated the config to the latest version!" + "invalidLanguage":"Invalid language", + "invalidButton":"Button must have at least an {0} or {1}", + "unusedOption":"Option {0} is not used anywhere", + "unusedQuestion":"Question {0} is not used anywhere", + "dropdownOption":"A panel with dropdown can only contain options of the types: 'ticket', 'role' or 'sub-panel'.", + "customInvalidVersion":"Config version mismatch. Make sure to update your config to the latest version" } }, "actions":{ @@ -118,7 +118,9 @@ "helpSwitchText":"View Text Commands", "helpPage":"Page {0}", "withReason":"With Reason", - "withoutTranscript":"Without Transcript" + "withoutTranscript":"Without Transcript", + "blacklistAdd":"Blacklist User", + "blacklistRemove":"Release User" }, "titles":{ "created":"Ticket Created", @@ -156,122 +158,127 @@ "topicSet":"Topic Changed", "prioritySet":"Priority Changed", "priorityGet":"Ticket Priority", - "transfer":"Ticket Transferred" + "transfer":"Ticket Transferred", + "transcripts":"Transcript History" }, "descriptions":{ - "create":"Your ticket has been created. Click the button below to access it!", - "close":"The ticket has been closed successfully!", - "delete":"The ticket has been deleted successfully!", - "reopen":"The ticket has been reopened successfully!", - "claim":"The ticket has been claimed successfully!", - "unclaim":"The ticket has been unclaimed successfully!", - "pin":"The ticket has been pinned successfully!", - "unpin":"The ticket has been unpinned successfully!", - "rename":"The ticket has been renamed to {0} successfully!", - "move":"The ticket has been moved to {0} successfully!", - "add":"{0} has been added to the ticket successfully!", - "remove":"{0} has been removed from the ticket successfully!", + "create":"Your ticket is ready. Click the button below to view and continue.", + "close":"The ticket has been closed.", + "delete":"The ticket has been deleted.", + "reopen":"The ticket has been reopened.", + "claim":"The ticket has been claimed.", + "unclaim":"The ticket has been unclaimed.", + "pin":"The ticket has been pinned.", + "unpin":"The ticket has been unpinned.", + "rename":"The ticket has been renamed to {0}.", + "move":"The ticket has been moved to {0}.", + "add":"{0} has been added to the ticket.", + "remove":"{0} has been removed from the ticket.", "helpExplanation":"`` => required parameter\n`[name]` => optional parameter", - "statsReset":"The bot stats have been reset successfully!", - "statsError":"Unable to view ticket stats!\n{0} is not a ticket!", - "blacklistAdd":"{0} has been blacklisted successfully!", - "blacklistRemove":"{0} has been released successfully!", - "blacklistGetSuccess":"{0} is currently blacklisted!", - "blacklistGetEmpty":"{0} is currently not blacklisted!", - "blacklistViewEmpty":"No-one has been blacklisted yet!", - "blacklistViewTip":"Use \"/blacklist add\" to blacklist a user!", - "clearVerify":"Are you sure you want to delete multiple tickets?\nThis action can't be undone!", - "clearReady":"{0} tickets have been deleted successfully!", - "rolesEmpty":"No roles have been updated!", + "statsReset":"The bot statistics have been reset.", + "statsError":"Unable to retrieve ticket statistics.\n{0} is not a valid ticket.", + "blacklistAdd":"{0} has been blacklisted.", + "blacklistRemove":"{0} has been released.", + "blacklistGetSuccess":"{0} is blacklisted!", + "blacklistGetEmpty":"{0} is not blacklisted!", + "blacklistViewEmpty":"No users have been blacklisted yet.", + "blacklistViewTip":"Use \"/blacklist add\" to add a user to the blacklist.", + "clearVerify":"Are you sure you want to delete multiple tickets?\nThis action cannot be undone.", + "clearReady":"{0} ticket(s) have been deleted.", + "rolesEmpty":"No roles were modified.", - "autocloseLeave":"This ticket has been autoclosed because the creator left the server!", - "autocloseTimeout":"This ticket has been autoclosed because it has been inactive for more than `{0}h`!", - "autodeleteLeave":"This ticket has been autodeleted because the creator left the server!", - "autodeleteTimeout":"This ticket has been autodeleted because it has been inactive for more than `{0} days`!", - "autocloseEnabled":"Autoclose has been enabled in this ticket!\nIt will be closed when it is inactive for more than `{0}h`!", - "autocloseDisabled":"Autoclose has been disabled in this ticket!\nIt won't be closed automatically anymore!", - "autodeleteEnabled":"Autodelete has been enabled in this ticket!\nIt will be deleted when it is inactive for more than `{0} days`!", - "autodeleteDisabled":"Autodelete has been disabled in this ticket!\nIt won't be deleted automatically anymore!", + "autocloseLeave":"This ticket was automatically closed because its creator left the server.", + "autocloseTimeout":"This ticket was automatically closed due to inactivity exceeding `{0}h`.", + "autodeleteLeave":"This ticket was automatically deleted because its creator left the server.", + "autodeleteTimeout":"This ticket was automatically deleted due to inactivity exceeding `{0} days`.", + "autocloseEnabled":"Autoclose has been enabled for this ticket.\nIt will close after `{0}h` of inactivity.", + "autocloseDisabled":"Autoclose has been disabled for this ticket.\nThis ticket will no longer close automatically.", + "autodeleteEnabled":"Autodelete has been enabled for this ticket.\nIt will be deleted after `{0} days` of inactivity.", + "autodeleteDisabled":"Autodelete has been disabled for this ticket.\nThis ticket will no longer be deleted automatically.", - "ticketMessageLimit":"You can only create {0} ticket(s) at the same time!", - "ticketMessageAutoclose":"This ticket will be autoclosed when inactive for {0}h!", - "ticketMessageAutodelete":"This ticket will be autodeleted when inactive for {0} days!", - "panelReady":"The panel is available in the followup message!\nThis message can now be deleted!", + "ticketMessageLimit":"You can only have {0} active ticket(s) at a time.", + "ticketMessageAutoclose":"This ticket will automatically close after `{0}h` of inactivity.", + "ticketMessageAutodelete":"This ticket will automatically be deleted after `{0} days` of inactivity.", + "panelReady":"The panel has been sent in the follow-up message.\nYou may now delete this message.", - "topicSet":"The channel topic has been changed by {0} successfully!", - "prioritySet":"The ticket priority has been changed to {0} by {1} successfully!", - "priorityGet":"The current priority of this ticket is {0}.", - "transfer":"The ticket ownership has been transferred from {0} to {1} by {2} successfully!" + "topicSet":"The channel topic has been changed by {0}.", + "prioritySet":"The ticket priority has been changed to {0} by {1}.", + "priorityGet":"The priority of this ticket is {0}.", + "transfer":"The ticket ownership has been transferred from {0} to {1} by {2}." }, "modal":{ - "closePlaceholder":"Why did you close this ticket?", - "deletePlaceholder":"Why did you delete this ticket?", - "reopenPlaceholder":"Why did you reopen this ticket?", - "claimPlaceholder":"Why did you claim this ticket?", - "unclaimPlaceholder":"Why did you unclaim this ticket?", - "pinPlaceholder":"Why did you pin this ticket?", - "unpinPlaceholder":"Why did you unpin this ticket?" + "closePlaceholder":"Why would you like to close this ticket?", + "deletePlaceholder":"Why would you like to delete this ticket?", + "reopenPlaceholder":"Why would you like to reopen this ticket?", + "claimPlaceholder":"Why would you like to claim this ticket?", + "unclaimPlaceholder":"Why would you like to unclaim this ticket?", + "pinPlaceholder":"Why would you like to pin this ticket?", + "unpinPlaceholder":"Why would you like to unpin this ticket?" }, "logs":{ - "createLog":"A new ticket got created by {0}!", - "closeLog":"This ticket has been closed by {0}!", - "closeDm":"Your ticket has been closed in our server!", - "deleteLog":"This ticket has been deleted by {0}!", - "deleteDm":"Your ticket has been deleted in our server!", - "reopenLog":"This ticket has been reopened by {0}!", - "reopenDm":"Your ticket has been reopened in our server!", - "claimLog":"This ticket has been claimed by {0}!", - "claimDm":"Your ticket has been claimed in our server!", - "unclaimLog":"This ticket has been unclaimed by {0}!", - "unclaimDm":"Your ticket has been unclaimed in our server!", - "pinLog":"This ticket has been pinned by {0}!", - "pinDm":"Your ticket has been pinned in our server!", - "unpinLog":"This ticket has been unpinned by {0}!", - "unpinDm":"Your ticket has been unpinned in our server!", - "renameLog":"This ticket has been renamed to {0} by {1}!", - "renameDm":"Your ticket has been renamed to {0} in our server!", - "moveLog":"This ticket has been moved to {0} by {1}!", - "moveDm":"Your ticket has been moved to {0} in our server!", - "addLog":"{0} has been added to this ticket by {1}!", - "addDm":"{0} has been added to your ticket in our server!", - "removeLog":"{0} has been removed from this ticket by {1}!", - "removeDm":"{0} has been removed from your ticket in our server!", + "createLog":"A new ticket got created by {0}.", + "closeLog":"This ticket has been closed by {0}.", + "closeDm":"Your ticket has been closed.", + "deleteLog":"This ticket has been deleted by {0}.", + "deleteDm":"Your ticket has been deleted.", + "reopenLog":"This ticket has been reopened by {0}.", + "reopenDm":"Your ticket has been reopened.", + "claimLog":"This ticket has been claimed by {0}.", + "claimDm":"Your ticket has been claimed.", + "unclaimLog":"This ticket has been unclaimed by {0}.", + "unclaimDm":"Your ticket has been unclaimed.", + "pinLog":"This ticket has been pinned by {0}.", + "pinDm":"Your ticket has been pinned.", + "unpinLog":"This ticket has been unpinned by {0}.", + "unpinDm":"Your ticket has been unpinned.", + "renameLog":"This ticket has been renamed to {0} by {1}.", + "renameDm":"Your ticket has been renamed to {0}.", + "moveLog":"This ticket has been moved to {0} by {1}.", + "moveDm":"Your ticket has been moved to {0}.", + "addLog":"{0} has been added to this ticket by {1}.", + "addDm":"{0} has been added to your ticket.", + "removeLog":"{0} has been removed from this ticket by {1}.", + "removeDm":"{0} has been removed from your ticket.", - "blacklistAddLog":"{0} was blacklisted by {1}!", - "blacklistRemoveLog":"{0} was removed from the blacklist by {1}!", - "blacklistAddDm":"You have been blacklisted in our server!\nFrom now on, you are unable to create a ticket!", - "blacklistRemoveDm":"You have been removed from the blacklist in our server!\nNow you can create tickets again!", - "clearLog":"{0} tickets have been deleted by {1}!", + "blacklistAddLog":"{0} has been blacklisted by {1}.", + "blacklistRemoveLog":"{0} has been removed from the blacklist by {1}.", + "blacklistAddDm":"You have been blacklisted from this server.\nYou are no longer able to create tickets.", + "blacklistRemoveDm":"You have been removed from the blacklist.\nYou can now create tickets again.", + "clearLog":"{0} ticket(s) have been deleted by {1}.", - "transferLog":"The ownership of this ticket has been transferred from {0} to {1} by {2}!", - "transferDm":"The ownership of your ticket has been transferred from {0} to {1} in our server!", - "prioritySetLog":"The priority of this ticket has been changed to {0} by {1}!", - "prioritySetDm":"The priority of your ticket has been changed to {0} in our server!", - "roleUpdateLog":"{0} has updated their roles!", - "roleUpdateDm":"Your roles in our server have been updated!" + "transferLog":"Ticket ownership has been transferred from {0} to {1} by {2}.", + "transferDm":"Ownership of your ticket has been transferred from {0} to {1}.", + "prioritySetLog":"The priority of this ticket has been set to {0} by {1}.", + "prioritySetDm":"The priority of your ticket has been set to {0}.", + "roleUpdateLog":"{0} has modified their roles.", + "roleUpdateDm":"Your roles have been modified.", + "topicSetLog":"The priority of this ticket has been set to {0} by {1}.", + "topicSetDm":"The priority of your ticket has been set to {0}." } }, "transcripts":{ "success":{ "visit":"Visit Transcript", "ready":"Transcript Created", - "textFileDescription":"This is the text transcript of a deleted ticket!", - "htmlProgress":"Please wait while this html transcript is getting processed...", + "textFileDescription":"This is a text transcript of a deleted ticket.", + "htmlProgress":"Please wait while the HTML transcript is being generated...", - "createdChannel":"A new {0} transcript has been created in the server!", - "createdCreator":"A new {0} transcript has been created for one of your tickets!", - "createdParticipant":"A new {0} transcript has been created in one of the tickets you participated in!", - "createdActiveAdmin":"A new {0} transcript has been created in one of the tickets you participated as admin!", - "createdEveryAdmin":"A new {0} transcript has been created in one of the tickets you were admin in!", - "createdOther":"A new {0} transcript has been created!" + "createdChannel":"A new {0} transcript has been created in the server.", + "createdCreator":"A new {0} transcript has been created for one of your tickets.", + "createdParticipant":"A new {0} transcript has been created for a ticket you participated in.", + "createdActiveAdmin":"A new {0} transcript has been created for a ticket you participated in as an admin.", + "createdEveryAdmin":"A new {0} transcript has been created for a ticket you managed as an admin.", + "createdOther":"A new {0} transcript has been created." }, "errors":{ "retry":"Retry", "continue":"Delete Without Transcript", "backup":"Create Backup Transcript", - "error":"Something went wrong while trying to create the transcript.\nWhat would you like to do?\n\nThis ticket won't be deleted until you click one of these buttons.", - "title":"Transcript Error" + "error":"Something went wrong while creating the transcript.\nWhat would you like to do?\n\nThis ticket will not be deleted until you select one of the options below.", + "title":"Transcript Error", + "noHistory":"This user does not yet have any transcripts.", + "historyNotSupported":"Transcript history is currently only supported with HTML Transcripts.\nText transcript history will be available in future versions." }, "text":{ "messagesTitle":"MESSAGES", @@ -279,7 +286,7 @@ "fileTitle":"FILE", "fieldsTitle":"FIELDS", "reactionsTitle":"REACTIONS", - "statsTitle":"STATS", + "statsTitle":"STATISTICS", "emptyContent":"", "noTitle":"", "noDesc":"" @@ -298,72 +305,77 @@ "unknownPanel":"Unknown Panel", "notInGuild":"Not In Server", "channelRename":"Unable To Rename Channel", + "channelCategory":"Unable To Change Category", "busy":"Ticket Is Busy", "permissionError":"Permission Error" }, "descriptions":{ - "askForInfo":"Contact the owner of this bot for more info!", - "askForInfoResolve":"Contact the bot owner of this bot if this issue doesn't resolve after a few tries.", - "internalError":"Failed to respond to this {0} due to an internal error!", - "optionMissing":"A required parameter is missing in this command!", - "optionInvalid":"A parameter in this command is invalid!", - "optionInvalidChoose":"Choose between", - "unknownCommand":"Try visiting the help menu for more info!", - "noPermissions":"You are not allowed to use this {0}!", - "noPermissionsList":"Required Permissions: (one of them)", - "noPermissionsCooldown":"You are not allowed to use this {0} because you have a cooldown!", - "noPermissionsBlacklist":"You are not allowed to use this {0} because you have been blacklisted!", - "noPermissionsLimitGlobal":"You are not allowed to create a ticket because the server reached the max tickets limit!", - "noPermissionsLimitGlobalUser":"You are not allowed to create a ticket because you reached the max tickets limit!", - "noPermissionsLimitOption":"You are not allowed to create a ticket because the server reached the max tickets limit for this option!", - "noPermissionsLimitOptionUser":"You are not allowed to create a ticket because you reached the max tickets limit for this option!", - "unknownTicket":"Try this command again in a valid ticket!", - "deprecatedTicket":"The current channel is not a valid ticket! It might have been a ticket from an old Open Ticket version!", - "notInGuild":"This {0} doesn't work in DM! Please try it again in a server!", - "channelRename":"Due to discord ratelimits, it's currently impossible for the bot to rename the channel. The channel will automatically be renamed over 10 minutes if the bot isn't rebooted.", - "channelRenameSource":"The source of this error is: {0}", - "busy":"Unable to use this {0}!\nThe ticket is currently being processed by the bot.\n\nPlease try again in a few seconds!", - "closeBeforeMessage":"This ticket cannot be closed/deleted before a message has been sent by a user.", - "closeBeforeAdminMessage":"This ticket cannot be closed/deleted before a message has been sent by a ticket admin or support member.", - "unableToCreateTicket":"You are unable to create a ticket." + "askForInfo":"Please contact the bot owner for more information.", + "askForInfoResolve":"If the issue persists after a few attempts, please contact the bot owner.", + "internalError":"An internal error occurred while processing this {0}.", + "optionMissing":"A required parameter is missing for this command.", + "optionInvalid":"One or more parameters provided for this command are invalid.", + "optionInvalidChoose":"Please choose between", + "unknownCommand":"Please use the help menu for more information.", + "noPermissions":"You are not permitted to use this {0}.", + "noPermissionsList":"Required permissions (one of the following):", + "noPermissionsCooldown":"You cannot use this {0} while on cooldown.", + "noPermissionsBlacklist":"You cannot use this {0} because you are blacklisted.", + "noPermissionsLimitGlobal":"You cannot create a ticket because the server has reached its maximum ticket limit.", + "noPermissionsLimitGlobalUser":"You cannot create a ticket because you have reached your maximum ticket limit.", + "noPermissionsLimitOption":"You cannot create a ticket because the server has reached the maximum ticket limit for this option.", + "noPermissionsLimitOptionUser":"You cannot create a ticket because you have reached the maximum ticket limit for this option.", + "unknownTicket":"Please run this command inside a valid ticket channel.", + "deprecatedTicket":"This channel is not recognized as a valid ticket. It may have been created with an older version of the bot.", + "notInGuild":"This {0} cannot be used in direct messages. Please use it in a server.", + "channelRename":"Due to Discord rate limits, the channel could not be renamed immediately. It will be renamed automatically within 10 minutes if the bot remains online.", + "channelCategory":"Due to Discord rate limits, the channel category could not be changed immediately. It will be changed automatically within 10 minutes if the bot remains online.", + "channelRenameSource":"Error source: {0}", + "busy":"This {0} is currently unavailable.\nThe ticket is being processed by the bot.\n\nPlease try again in a few seconds.", + "closeBeforeMessage":"This ticket cannot be closed or deleted before a user has sent a message.", + "closeBeforeAdminMessage":"This ticket cannot be closed or deleted before a support member or admin has sent a message.", + "unableToCreateTicket":"You are currently unable to create a ticket.", + "messageMissing":"Unable to locate message of interaction. Use the command `{0}` instead.", + "stateExpired":"This interaction is no longer valid or has expired. Use the command `{0}` instead. It is normal to receive this error after a major Open Ticket update.", + "panelStateExpired":"This panel is no longer valid or has expired. Create a new panel using `{0}` to solve the issue. It is normal to receive this error after a major Open Ticket update." }, "optionInvalidReasons":{ - "stringRegex":"Value doesn't match pattern!", - "stringMinLength":"Value needs to be at least {0} characters!", - "stringMaxLength":"Value needs to be at most {0} characters!", - "numberInvalid":"Invalid number!", - "numberMin":"Number needs to be at least {0}!", - "numberMax":"Number needs to be at most {0}!", - "numberDecimal":"Number is not allowed to be a decimal!", - "numberNegative":"Number is not allowed to be negative!", - "numberPositive":"Number is not allowed to be positive!", - "numberZero":"Number is not allowed to be zero!", - "channelNotFound":"Unable to find channel!", - "userNotFound":"Unable to find user!", - "roleNotFound":"Unable to find role!", - "memberNotFound":"Unable to find user!", - "mentionableNotFound":"Unable to find user or role!", - "channelType":"Invalid channel type!", - "notInGuild":"This option requires you to be in a server!" + "stringRegex":"The value does not match the required pattern.", + "stringMinLength":"The value must be at least {0} characters long.", + "stringMaxLength":"The value must be at most {0} characters long.", + "numberInvalid":"Invalid number provided.", + "numberMin":"The number must be at least {0}.", + "numberMax":"The number must be at most {0}.", + "numberDecimal":"Decimals are not allowed.", + "numberNegative":"Negative numbers are not allowed.", + "numberPositive":"Positive numbers are not allowed.", + "numberZero":"Zero is not allowed.", + "channelNotFound":"Channel not found.", + "userNotFound":"User not found.", + "roleNotFound":"Role not found.", + "memberNotFound":"Member not found.", + "mentionableNotFound":"User or role not found.", + "channelType":"Invalid channel type.", + "notInGuild":"This option can only be used in a server." }, "permissions":{ - "developer":"You need to be the developer of the bot.", - "owner":"You need to be the server owner.", - "admin":"You need to be a server admin.", - "moderator":"You need to be a moderator.", - "support":"You need to be in the support team.", - "member":"You need to be a member.", - "discord-administrator":"You need to have the `ADMINISTRATOR` permission." + "developer":"This action is restricted to the bot developer.", + "owner":"You must be the server owner to use this.", + "admin":"You must have administrator permissions to use this.", + "moderator":"You must be a moderator to use this.", + "support":"You must be part of the support team to use this.", + "member":"You must be a server member to use this.", + "discord-administrator":"You must have the `ADMINISTRATOR` permission." }, "actionInvalid":{ - "close":"Ticket is already closed!", - "reopen":"Ticket is not closed yet!", - "claim":"Ticket is already claimed!", - "unclaim":"Ticket is not claimed yet!", - "pin":"Ticket is already pinned!", - "unpin":"Ticket is not pinned yet!", - "add":"This user is already able to access the ticket!", - "remove":"Unable to remove this user from the ticket!" + "close":"This ticket is already closed.", + "reopen":"This ticket is not closed.", + "claim":"This ticket is already claimed.", + "unclaim":"This ticket is not claimed.", + "pin":"This ticket is already pinned.", + "unpin":"This ticket is not pinned.", + "add":"This user already has access to this ticket.", + "remove":"This user does not have access to this ticket." } }, "params":{ @@ -387,6 +399,8 @@ "syntax":"Syntax", "originalName":"Original Name", "newName":"New Name", + "originalCategory":"Original Category", + "newCategory":"New Category", "until":"Until", "validOptions":"Valid Options", "validPanels":"Valid Panels", @@ -408,6 +422,8 @@ "participants":"Participants", "yes":"Yes", "no":"No", + "accept":"Accept", + "cancel":"Cancel", "option":"Option", "topic":"Topic", "uptime":"System Uptime", @@ -432,107 +448,110 @@ } }, "commands":{ - "reason":"Specify an optional reason that will be visible in logs.", - "help":"Get a list of all the available commands.", - "panel":"Spawn a message with a dropdown or buttons (for ticket creation).", - "panelId":"The identifier of the panel that you want to spawn.", - "panelAutoUpdate":"Do you want this panel to automatically update when edited?", - "ticket":"Instantly create a ticket.", - "ticketId":"The identifier of the ticket that you want to create.", - "close":"Close a ticket.", - "delete":"Delete a ticket.", - "deleteNoTranscript":"Delete this ticket without creating a transcript.", - "reopen":"Reopen a ticket.", - "claim":"Claim a ticket.", - "claimUser":"Claim this ticket to someone else instead of yourself.", - "unclaim":"Unclaim a ticket.", - "pin":"Pin a ticket.", - "unpin":"Unpin a ticket.", + "reason":"Optional reason shown in logs.", + "help":"Display all available commands.", + "panel":"Create a ticket panel (buttons or dropdown).", + "panelId":"ID of the panel to spawn.", + "panelAutoUpdate":"Automatically update this panel when edited.", + "ticket":"Create a ticket instantly.", + "ticketId":"ID of the ticket to create.", + "ticketOtherUser":"Create a ticket for another user.", + "close":"Close this ticket.", + "delete":"Delete this ticket.", + "deleteNoTranscript":"Delete ticket without saving a transcript.", + "reopen":"Reopen a closed ticket.", + "claim":"Claim this ticket.", + "claimUser":"Claim the ticket for another user.", + "unclaim":"Unclaim this ticket.", + "pin":"Pin this ticket.", + "unpin":"Unpin this ticket.", - "move":"Move a ticket.", - "moveId":"The identifier of the option that you want to move to.", - "rename":"Rename a ticket.", - "renameName":"The new name for this ticket.", - "add":"Add a user to a ticket.", - "addUser":"The user to add.", - "remove":"Remove a user from a ticket.", - "removeUser":"The user to remove.", + "move":"Move ticket to another option.", + "moveId":"Target option ID.", + "rename":"Rename this ticket.", + "renameName":"New name for the ticket.", + "add":"Add a user to this ticket.", + "addUser":"User to add.", + "remove":"Remove a user from this ticket.", + "removeUser":"User to remove.", - "blacklist":"Manage the ticket blacklist.", - "blacklistView":"View a list of the current blacklist.", + "blacklist":"Manage the blacklist.", + "blacklistView":"View all blacklisted users.", "blacklistAdd":"Add a user to the blacklist.", "blacklistRemove":"Remove a user from the blacklist.", - "blacklistGet":"Get the details from a blacklisted user.", - "blacklistGetUser":"The user to get details from.", - "stats":"View statistics from the bot, a member or a ticket.", - "statsReset":"Reset all the stats of the bot (and start counting from zero).", - "statsGlobal":"View the global stats.", - "statsUser":"View the stats from a user in the server.", - "statsUserUser":"The user to view.", - "statsTicket":"View the stats of a ticket in the server.", - "statsTicketTicket":"The ticket to view.", + "blacklistGet":"View blacklist entry details.", + "blacklistGetUser":"User to look up.", + "stats":"View bot, user or ticket statistics.", + "statsReset":"Reset all bot statistics.", + "statsGlobal":"View global bot statistics.", + "statsUser":"View a user's statistics.", + "statsUserUser":"User to view.", + "statsTicket":"View ticket statistics.", + "statsTicketTicket":"Ticket to view.", - "clear":"Delete multiple tickets at the same time.", - "clearFilter":"The filter for clearing tickets.", + "clear":"Delete multiple tickets at once.", + "clearFilter":"Filter used when clearing tickets.", "clearFilters":{ - "all":"All", - "open":"Open", - "close":"Closed", - "claim":"Claimed", - "unclaim":"Unclaimed", - "pin":"Pinned", - "unpin":"Unpinned", - "autoclose":"Autoclosed" + "all":"All tickets", + "open":"Open tickets", + "close":"Closed tickets", + "claim":"Claimed tickets", + "unclaim":"Unclaimed tickets", + "pin":"Pinned tickets", + "unpin":"Unpinned tickets", + "autoclose":"Autoclosed tickets" }, - "autoclose":"Manage autoclose in a ticket.", - "autocloseDisable":"Disable autoclose in this ticket.", - "autocloseEnable":"Enable autoclose in this ticket.", - "autocloseEnableTime":"The amount of hours this ticket needs to be inactive to close it.", - "autodelete":"Manage autodelete in a ticket.", - "autodeleteDisable":"Disable autodelete in this ticket.", - "autodeleteEnable":"Enable autodelete in this ticket.", - "autodeleteEnableTime":"The amount of days this ticket needs to be inactive to delete it.", + "autoclose":"Manage ticket autoclose.", + "autocloseDisable":"Disable autoclose.", + "autocloseEnable":"Enable autoclose.", + "autocloseEnableTime":"Hours of inactivity before closing.", + "autodelete":"Manage ticket autodelete.", + "autodeleteDisable":"Disable autodelete.", + "autodeleteEnable":"Enable autodelete.", + "autodeleteEnableTime":"Days of inactivity before deletion.", - "topic":"Manage the topic of the ticket channel.", - "topicSet":"Set the topic of the ticket channel.", - "topicValue":"The new topic of the channel.", - "topicList":"Get a list of all tickets with their topic and stats.", - "priority":"Manage the priority of the ticket.", - "prioritySet":"Set the priority of the ticket.", - "priorityValue":"The priority of the channel.", - "priorityGet":"Get the priority of the ticket.", - "priorityList":"Get a list of all tickets with their priority status.", - "transfer":"Transfer the ticket ownership from one user to another.", - "transferUser":"The user to transfer to." + "topic":"Manage ticket channel topic.", + "topicSet":"Set the ticket channel topic.", + "topicValue":"New channel topic text.", + "topicList":"List all ticket channel topics.", + "priority":"Manage ticket priority.", + "prioritySet":"Set ticket priority.", + "priorityValue":"Priority level.", + "priorityGet":"View ticket priority.", + "priorityList":"List all ticket priorities.", + "transfer":"Transfer ticket ownership.", + "transferUser":"User to transfer to.", + "transcripts":"View ticket transcript history of a user.", + "transcriptsUser":"The user to view." }, "helpMenu":{ - "help":"Get a list of all the available commands.", - "ticket":"Instantly create a ticket.", - "close":"Close a ticket, this disables writing in this channel.", - "delete":"Delete a ticket, this creates a transcript when enabled.", - "reopen":"Reopen a ticket, this enables writing in this channel again.", - "pin":"Pin a ticket. This will move the ticket to the top and will add a '📌' emoij to the name.", - "unpin":"Unpin a ticket. The ticket will stay on it's position but will lose the '📌' emoij.", - "move":"Move a ticket. This will change the type of this ticket.", - "rename":"Rename a ticket. This will change the channel name of this ticket.", - "claim":"Claim a ticket. With this, you can let your team know you are handling this ticket.", - "unclaim":"Unclaim a ticket. With this, you can let your team know that this ticket is free again.", - "add":"Add a user to a ticket. This will allow the user to read & write in this ticket.", - "remove":"Remove a user from a ticket. This will remove the ability to read & write for a user in this ticket.", - "panel":"Spawn a message with a dropdown or buttons (for ticket creation).", - "blacklistView":"View a list of the current blacklist.", + "help":"View all available commands.", + "ticket":"Create a ticket instantly.", + "close":"Close this ticket and disable messaging in the channel.", + "delete":"Delete this ticket (creates a transcript if enabled).", + "reopen":"Reopen a closed ticket and restore messaging.", + "pin":"Pin this ticket and move it to the top with '📌'.", + "unpin":"Unpin this ticket and remove the '📌' emoji.", + "move":"Move this ticket to a different option or ticket type.", + "rename":"Rename this ticket channel.", + "claim":"Claim this ticket to show you are handling it.", + "unclaim":"Unclaim this ticket to mark it as available.", + "add":"Add a user to this ticket.", + "remove":"Remove a user's access from this ticket.", + "panel":"Create a ticket panel with buttons or a dropdown.", + "blacklistView":"View all blacklisted users.", "blacklistAdd":"Add a user to the blacklist.", "blacklistRemove":"Remove a user from the blacklist.", - "blacklistGet":"Get the details from a blacklisted user.", - "statsGlobal":"View the global stats.", - "statsTicket":"View the stats of a ticket in the server.", - "statsUser":"View the stats from a user in the server.", - "statsReset":"Reset all the stats of the bot (and start counting from zero).", - "autocloseDisable":"Disable autoclose in this ticket.", - "autocloseEnable":"Enable autoclose in this ticket.", - "autodeleteDisable":"Disable autodelete in this ticket.", - "autodeleteEnable":"Enable autodelete in this ticket.", + "blacklistGet":"View details of a blacklisted user.", + "statsGlobal":"View global bot statistics.", + "statsTicket":"View statistics for a ticket.", + "statsUser":"View statistics for a user.", + "statsReset":"Reset all bot statistics.", + "autocloseDisable":"Disable autoclose for this ticket.", + "autocloseEnable":"Enable autoclose for this ticket.", + "autodeleteDisable":"Disable autodelete for this ticket.", + "autodeleteEnable":"Enable autodelete for this ticket.", "categories":{ "general":"General Commands", "basicTicket":"Basic Ticket Commands", @@ -545,10 +564,10 @@ }, "stats":{ "scopes":{ - "global":"Global Stats", - "system":"System Stats", - "user":"User Stats", - "ticket":"Ticket Stats", + "global":"Global Statistics", + "system":"System Statistics", + "user":"User Statistics", + "ticket":"Ticket Statistics", "participants":"Participants", "messages":"Messages" }, @@ -592,9 +611,10 @@ } }, "panel":{ - "selectTicket":"Select your ticket", - "selectRole":"Select your role", - "selectOption":"Select your option" + "selectTicket":"Select a ticket", + "selectRole":"Select a role", + "selectOption":"Select an option", + "selectPriorityLevel":"Select priority level" }, "priorities":{ "urgent":"Urgent", diff --git a/languages/estonian.json b/languages/estonian.json index 241bfec..2196086 100644 --- a/languages/estonian.json +++ b/languages/estonian.json @@ -1,8 +1,8 @@ { "_TRANSLATION":{ - "otversion":"v4.1.3", + "otversion":"v4.2.0", "translators":["iamnotmega","ChatGPT"], - "lastedited":"16/02/2026", + "lastedited":"25/05/2026", "language":"Estonian", "automated":true }, @@ -99,7 +99,7 @@ "invalidButton":"Sellel nupul peab olema vähemalt {0} või {1}!", "unusedOption":"Valik {0} ei ole kusagil kasutusel!", "unusedQuestion":"Küsimust {0} ei kasutata kuskil!", - "dropdownOption":"Lubatud rippmenüüga paneel võib sisaldada ainult \"pileti\" tüüpi valikuid!", + "dropdownOption":"Rippmenüüga paneel võib sisaldada ainult järgmisi valikuid: 'ticket', 'role' või 'sub-panel'.", "customInvalidVersion":"Teie konfiguratsioonis määratud versioon ei ühti! Veenduge, et olete konfiguratsiooni uuendanud uusimale versioonile!" } }, @@ -118,7 +118,9 @@ "helpSwitchText":"Vaadake tekstikäske", "helpPage":"Leht {0}", "withReason":"Põhjusega", - "withoutTranscript":"Ilma ärakirjata" + "withoutTranscript":"Ilma ärakirjata", + "blacklistAdd":"Lisa kasutaja musta nimekirja", + "blacklistRemove":"Vabasta kasutaja" }, "titles":{ "created":"Pilet loodud", @@ -156,7 +158,8 @@ "topicSet":"Teema Muudetud", "prioritySet":"Prioriteet Muudetud", "priorityGet":"Pileti Prioriteet", - "transfer":"Pilet Üle Antud" + "transfer":"Pilet Üle Antud", + "transcripts":"Transkripti ajalugu" }, "descriptions":{ "create":"Teie pilet on loodud. Sellele juurdepääsuks klõpsake alloleval nupul!", @@ -249,7 +252,9 @@ "prioritySetLog":"Selle pileti prioriteet on muudetud väärtuseks {0} kasutaja {1} poolt!", "prioritySetDm":"Teie pileti prioriteet on muudetud väärtuseks {0} meie serveris!", "roleUpdateLog":"{0} uuendas oma rolle!", - "roleUpdateDm":"Teie rolle meie serveris on uuendatud!" + "roleUpdateDm":"Teie rolle meie serveris on uuendatud!", + "topicSetLog":"Selle pileti prioriteedi määras {1} väärtusele {0}.", + "topicSetDm":"Sinu pileti prioriteet on seatud väärtusele {0}." } }, "transcripts":{ @@ -271,7 +276,9 @@ "continue":"Kustuta ilma ärakirjata", "backup":"Loo varukoopia ärakirjast", "error":"Midagi läks ärakirja loomisel valesti.\nMida sa teha tahaksid?\n\nSeda piletit ei kustutata enne, kui klõpsate ühel neist nuppudest.", - "title":"Transkriptsiooniviga" + "title":"Transkriptsiooniviga", + "noHistory":"Sellel kasutajal ei ole veel ühtegi transkripti.", + "historyNotSupported":"Transkripti ajalugu on praegu toetatud ainult HTML Transcriptsiga.\nTekstiliste transkriptide ajalugu on saadaval tulevastes versioonides." }, "text":{ "messagesTitle":"SÕNUMID", @@ -298,6 +305,7 @@ "unknownPanel":"Tundmatu paneel", "notInGuild":"Pole serveris", "channelRename":"Kanalit ei saa ümber nimetada", + "channelCategory":"Kategooriat ei saa muuta", "busy":"Pilet on kinni", "permissionError":"Õiguste Viga" }, @@ -321,11 +329,15 @@ "deprecatedTicket":"Praegune kanal ei ole kehtiv pilet! See võis olla pilet vanast Open Ticketi versioonist!", "notInGuild":"See {0} ei tööta DM-is! Palun proovi uuesti serveris!", "channelRename":"Ebakõlaliste kiiruspiirangute tõttu on robotil praegu võimatu kanalit ümber nimetada. Kui robotit ei taaskäivitata, nimetatakse kanal 10 minuti jooksul automaatselt ümber.", + "channelCategory":"Discordi kiiruspiirangute tõttu ei saanud kanali kategooriat kohe muuta. Kui bot jääb võrgus, muudetakse see automaatselt 10 minuti jooksul.", "channelRenameSource":"Selle vea allikas on: {0}", "busy":"Seda {0} ei saa kasutada!\nPiletit töötleb praegu bot.\n\nPalun proovige mõne sekundi pärast uuesti!", "closeBeforeMessage":"Seda piletit ei saa sulgeda/kustutada enne, kui kasutaja on sõnumi saatnud.", "closeBeforeAdminMessage":"Seda piletit ei saa sulgeda/kustutada enne, kui piletihaldur või tugiliige on sõnumi saatnud.", - "unableToCreateTicket":"Te ei saa piletit luua." + "unableToCreateTicket":"Te ei saa piletit luua.", + "messageMissing":"Interaktsiooni sõnumit ei leitud. Kasuta selle asemel käsku `{0}`.", + "stateExpired":"See interaktsioon ei ole enam kehtiv või on aegunud. Kasuta selle asemel käsku `{0}`. See on tavaline pärast Open Ticket suurt uuendust.", + "panelStateExpired":"See paneel ei ole enam kehtiv või on aegunud. Loo uus paneel kasutades `{0}`, et probleem lahendada. See on tavaline pärast Open Ticket suurt uuendust." }, "optionInvalidReasons":{ "stringRegex":"Väärtus ei vasta mustrile!", @@ -387,6 +399,8 @@ "syntax":"Süntaks", "originalName":"Algne nimi", "newName":"Uus nimi", + "originalCategory":"Algne kategooria", + "newCategory":"Uus kategooria", "until":"Kuni", "validOptions":"Kehtivad valikud", "validPanels":"Kehtivad paneelid", @@ -408,6 +422,8 @@ "participants":"Osalejad", "yes":"Jah", "no":"Ei", + "accept":"Nõustu", + "cancel":"Tühista", "option":"Valik", "topic":"Teema", "uptime":"Süsteemi Töötamise Aeg", @@ -439,6 +455,7 @@ "panelAutoUpdate":"Kas soovite seda paneeli redigeerimisel automaatselt värskendada?", "ticket":"Looge pilet koheselt.", "ticketId":"Pileti identifikaator, mille soovite luua.", + "ticketOtherUser":"Loo pilet teisele kasutajale.", "close":"Sulgege pilet.", "delete":"Kustuta pilet.", "deleteNoTranscript":"Kustutage see pilet ilma ärakirja loomata.", @@ -504,7 +521,9 @@ "priorityGet":"Hangi pileti prioriteet.", "priorityList":"Hangi nimekiri kõigist piletitest ja nende prioriteedi olekust.", "transfer":"Anna pileti omand üle ühelt kasutajalt teisele.", - "transferUser":"Kasutaja, kellele omand üle anda." + "transferUser":"Kasutaja, kellele omand üle anda.", + "transcripts":"Vaata kasutaja piletite transkripti ajalugu.", + "transcriptsUser":"Kasutaja, keda vaadata." }, "helpMenu":{ "help":"Hankige kõigi saadaolevate käskude loend.", @@ -594,7 +613,8 @@ "panel":{ "selectTicket":"Vali oma pilet", "selectRole":"Vali oma roll", - "selectOption":"Vali oma valik" + "selectOption":"Vali oma valik", + "selectPriorityLevel":"Vali prioriteeditase" }, "priorities":{ "urgent":"Kiireloomuline", diff --git a/languages/finnish.json b/languages/finnish.json index d922108..4dc1692 100644 --- a/languages/finnish.json +++ b/languages/finnish.json @@ -1,8 +1,8 @@ { "_TRANSLATION":{ - "otversion":"v4.1.3", + "otversion":"v4.2.0", "translators":["iamnotmega","ChatGPT"], - "lastedited":"16/02/2026", + "lastedited":"25/05/2026", "language":"Finnish", "automated":true }, @@ -99,7 +99,7 @@ "invalidButton":"Tässä painikkeessa on oltava vähintään {0} tai {1}!", "unusedOption":"Vaihtoehtoa {0} ei käytetä missään!", "unusedQuestion":"Kysymystä {0} ei käytetä missään!", - "dropdownOption":"Paneeli, jossa pudotusvalikko on käytössä, voi sisältää vain \"lippu\"-tyyppisiä vaihtoehtoja!", + "dropdownOption":"Pudotusvalikollinen paneeli voi sisältää vain seuraavia vaihtoehtoja: 'ticket', 'role' tai 'sub-panel'.", "customInvalidVersion":"Määritetty versio kokoonpanossasi ei vastaa! Varmista, että olet päivittänyt kokoonpanon uusimpaan versioon!" } }, @@ -118,7 +118,9 @@ "helpSwitchText":"Näytä tekstikomennot", "helpPage":"Sivu {0}", "withReason":"Syyllä", - "withoutTranscript":"Ilman transkriptiota" + "withoutTranscript":"Ilman transkriptiota", + "blacklistAdd":"Lisää käyttäjä mustalle listalle", + "blacklistRemove":"Vapauta käyttäjä" }, "titles":{ "created":"Lippu luotu", @@ -156,7 +158,8 @@ "topicSet":"Aihe Muutettu", "prioritySet":"Prioriteetti Muutettu", "priorityGet":"Tiketin Prioriteetti", - "transfer":"Tiketti Siirretty" + "transfer":"Tiketti Siirretty", + "transcripts":"Transkriptien historia" }, "descriptions":{ "create":"Lippusi on luotu. Napsauta alla olevaa painiketta päästäksesi siihen!", @@ -249,7 +252,9 @@ "prioritySetLog":"Tämän tiketin prioriteetti on vaihdettu arvoksi {0} käyttäjän {1} toimesta!", "prioritySetDm":"Tiketin prioriteetti on vaihdettu arvoksi {0} palvelimellamme!", "roleUpdateLog":"{0} on päivittänyt roolinsa!", - "roleUpdateDm":"Roolisi palvelimellamme on päivitetty!" + "roleUpdateDm":"Roolisi palvelimellamme on päivitetty!", + "topicSetLog":"Tämän tiketin prioriteetti asetettiin arvoon {0} käyttäjän {1} toimesta.", + "topicSetDm":"Tikettisi prioriteetti on asetettu arvoon {0}." } }, "transcripts":{ @@ -271,7 +276,9 @@ "continue":"Poista ilman transkriptiota", "backup":"Luo varmuuskopio", "error":"Jotain meni pieleen, kun yritettiin luoda transkriptiota. Mitä haluaisit tehdä? Tätä lippua ei poisteta, ennen kuin napsautat jotakin näistä painikkeista. ", - "title":"Transkriptiovirhe" + "title":"Transkriptiovirhe", + "noHistory":"Tällä käyttäjällä ei ole vielä transkriptejä.", + "historyNotSupported":"Transkriptien historiaa tuetaan tällä hetkellä vain HTML Transcriptsilla.\nTekstimuotoinen transkriptien historia tulee saataville tulevissa versioissa." }, "text":{ "messagesTitle":"VIESTIT", @@ -298,6 +305,7 @@ "unknownPanel":"Tuntematon paneeli", "notInGuild":"Ei Palvelimessa", "channelRename":"Kanavaa ei voi nimetä uudelleen", + "channelCategory":"Luokan Vaihto Ei Onnistu", "busy":"Lippu on varattu", "permissionError":"Käyttöoikeusvirhe" }, @@ -321,11 +329,15 @@ "deprecatedTicket":"Nykyinen kanava ei ole kelvollinen lippu! Se saattoi olla lippu vanhasta Open Ticket -versiosta!", "notInGuild":"Tämä {0} ei toimi DM:ssä! Ole hyvä ja yritä uudelleen palvelimella!", "channelRename":"Discordin nopeusrajoitusten vuoksi botin on tällä hetkellä mahdotonta nimetä kanavaa uudelleen. Kanava nimetään automaattisesti uudelleen 10 minuutin kuluttua, jos bottia ei käynnistetä uudelleen.", + "channelCategory":"Discordin nopeusrajoitusten vuoksi kanavan luokkaa ei voitu muuttaa heti. Se päivitetään automaattisesti 10 minuutin kuluessa, jos botti pysyy online-tilassa.", "channelRenameSource":"Tämän virheen lähde on: {0}", "busy":"Tätä {0} ei voi käyttää! Botti käsittelee lippua parhaillaan. Yritä uudelleen muutaman sekunnin kuluttua!", "closeBeforeMessage":"Tätä tikettiä ei voi sulkea/poistaa ennen kuin käyttäjä on lähettänyt viestin.", "closeBeforeAdminMessage":"Tätä tikettiä ei voi sulkea/poistaa ennen kuin tikettien ylläpitäjä tai tukihenkilö on lähettänyt viestin.", - "unableToCreateTicket":"Et voi luoda tikettiä." + "unableToCreateTicket":"Et voi luoda tikettiä.", + "messageMissing":"Vuorovaikutuksen viestiä ei löytynyt. Käytä sen sijaan komentoa `{0}`.", + "stateExpired":"Tämä vuorovaikutus ei ole enää voimassa tai on vanhentunut. Käytä sen sijaan komentoa `{0}`. Tämä on normaalia suuren Open Ticket -päivityksen jälkeen.", + "panelStateExpired":"Tämä paneeli ei ole enää voimassa tai on vanhentunut. Luo uusi paneeli käyttämällä `{0}` ongelman ratkaisemiseksi. Tämä on normaalia suuren Open Ticket -päivityksen jälkeen." }, "optionInvalidReasons":{ "stringRegex":"Arvo ei vastaa mallia!", @@ -387,6 +399,8 @@ "syntax":"Syntaksi", "originalName":"Alkuperäinen Nimi", "newName":"Alkuperäinen Nimi", + "originalCategory":"Alkuperäinen Kategoria", + "newCategory":"Uusi Kategoria", "until":"Kunnes", "validOptions":"Kelvolliset vaihtoehdot", "validPanels":"Kelvolliset paneelit", @@ -408,6 +422,8 @@ "participants":"Osallistujat", "yes":"Kyllä", "no":"Ei", + "accept":"Hyväksy", + "cancel":"Peruuta", "option":"Vaihtoehto", "topic":"Aihe", "uptime":"Järjestelmän Käyttöaika", @@ -439,6 +455,7 @@ "panelAutoUpdate":"Haluatko tämän paneelin päivittyvän automaattisesti, kun sitä muokataan?", "ticket":"Luo lippu välittömästi.", "ticketId":"Sen lipun tunniste, jonka haluat luoda.", + "ticketOtherUser":"Luo tiketti toiselle käyttäjälle.", "close":"Sulje lippu.", "delete":"Poista lippu.", "deleteNoTranscript":"Poista tämä lippu luomatta transkriptiota.", @@ -504,7 +521,9 @@ "priorityGet":"Hae tiketin prioriteetti.", "priorityList":"Hae lista kaikista tiketeistä ja niiden prioriteettitilasta.", "transfer":"Siirrä tiketin omistus käyttäjältä toiselle.", - "transferUser":"Käyttäjä, jolle omistus siirretään." + "transferUser":"Käyttäjä, jolle omistus siirretään.", + "transcripts":"Näytä käyttäjän tikettien transkriptien historia.", + "transcriptsUser":"Käyttäjä, jota tarkastellaan." }, "helpMenu":{ "help":"Hanki luettelo kaikista käytettävissä olevista komennoista.", @@ -594,7 +613,8 @@ "panel":{ "selectTicket":"Valitse tikettisi", "selectRole":"Valitse roolisi", - "selectOption":"Valitse vaihtoehtosi" + "selectOption":"Valitse vaihtoehtosi", + "selectPriorityLevel":"Valitse prioriteettitaso" }, "priorities":{ "urgent":"Kiireellinen", diff --git a/languages/french.json b/languages/french.json index d50f971..3a7a49c 100644 --- a/languages/french.json +++ b/languages/french.json @@ -1,8 +1,8 @@ { "_TRANSLATION":{ - "otversion":"v4.1.3", + "otversion":"v4.2.0", "translators":["guillee3"], - "lastedited":"16/02/2026", + "lastedited":"25/05/2026", "language":"French", "automated":false }, @@ -99,7 +99,7 @@ "invalidButton":"Ce bouton doit avoir au moins un {0} ou {1}!", "unusedOption":"L'option {0} n'est utilisée nulle part!", "unusedQuestion":"La question {0} n'est utilisée nulle part!", - "dropdownOption":"Un panneau avec un menu déroulant activé ne peut contenir que des options du type 'ticket'!", + "dropdownOption":"Un panneau avec menu déroulant peut uniquement contenir des options des types : 'ticket', 'role' ou 'sub-panel'.", "customInvalidVersion":"La version spécifiée dans votre config ne correspond pas! Assurez-vous d'avoir mis à jour la config vers la dernière version!" } }, @@ -118,7 +118,9 @@ "helpSwitchText":"Voir les Commandes Textuelles", "helpPage":"Page {0}", "withReason":"Avec Raison", - "withoutTranscript":"Sans Transcription" + "withoutTranscript":"Sans Transcription", + "blacklistAdd":"Mettre l'Utilisateur en Liste Noire", + "blacklistRemove":"Retirer l'Utilisateur" }, "titles":{ "created":"Ticket Créé", @@ -156,7 +158,8 @@ "topicSet":"Sujet modifié", "prioritySet":"Priorité modifiée", "priorityGet":"Priorité du ticket", - "transfer":"Ticket transféré" + "transfer":"Ticket transféré", + "transcripts":"Historique des Transcriptions" }, "descriptions":{ "create":"Votre ticket a été créé. Cliquez sur le bouton ci-dessous pour y accéder!", @@ -249,7 +252,9 @@ "prioritySetLog":"La priorité de ce ticket a été modifiée en {0} par {1}!", "prioritySetDm":"La priorité de votre ticket a été modifiée en {0} sur notre serveur!", "roleUpdateLog":"{0} a mis à jour ses rôles!", - "roleUpdateDm":"Vos rôles sur notre serveur ont été mis à jour!" + "roleUpdateDm":"Vos rôles sur notre serveur ont été mis à jour!", + "topicSetLog":"La priorité de ce ticket a été définie sur {0} par {1}.", + "topicSetDm":"La priorité de votre ticket a été définie sur {0}." } }, "transcripts":{ @@ -271,7 +276,9 @@ "continue":"Supprimer Sans Transcription", "backup":"Créer une Transcription de Sauvegarde", "error":"Une erreur s'est produite lors de la création de la transcription.\nQue souhaitez-vous faire?\n\nCe ticket ne sera pas supprimé tant que vous n'aurez pas cliqué sur l'un de ces boutons.", - "title":"Erreur de transcription" + "title":"Erreur de transcription", + "noHistory":"Cet utilisateur n'a pas encore de transcriptions.", + "historyNotSupported":"L'historique des transcriptions est actuellement uniquement pris en charge avec HTML Transcripts.\nL'historique des transcriptions texte sera disponible dans les prochaines versions." }, "text":{ "messagesTitle":"MESSAGES", @@ -298,6 +305,7 @@ "unknownPanel":"Panneau Inconnu", "notInGuild":"Pas Dans le Serveur", "channelRename":"Impossible de Renommer le Canal", + "channelCategory":"Impossible de Modifier la Catégorie", "busy":"Ticket Occupé", "permissionError":"Erreur de permission" }, @@ -321,11 +329,15 @@ "deprecatedTicket":"Le canal actuel n'est pas un ticket valide! Il pourrait s'agir d'un ticket provenant d'une ancienne version d'Open Ticket!", "notInGuild":"Ce {0} ne fonctionne pas en DM! Veuillez réessayer dans un serveur!", "channelRename":"En raison des limitations de taux de Discord, il est actuellement impossible pour le bot de renommer le canal. Le canal sera automatiquement renommé après 10 minutes si le bot n'est pas redémarré.", + "channelCategory":"En raison des limites de taux de Discord, la catégorie du salon n'a pas pu être modifiée immédiatement. Elle sera modifiée automatiquement dans les 10 minutes si le bot reste en ligne.", "channelRenameSource":"La source de cette erreur est : {0}", "busy":"Impossible d'utiliser ce {0}!\nLe ticket est actuellement en cours de traitement par le bot.\n\nVeuillez réessayer dans quelques secondes!", "closeBeforeMessage":"Ce ticket ne peut pas être fermé/supprimé avant qu'un utilisateur ait envoyé un message.", "closeBeforeAdminMessage":"Ce ticket ne peut pas être fermé/supprimé avant qu'un admin de ticket ou un membre du support ait envoyé un message.", - "unableToCreateTicket":"Vous ne pouvez pas créer de ticket." + "unableToCreateTicket":"Vous ne pouvez pas créer de ticket.", + "messageMissing":"Impossible de localiser le message de l'interaction. Utilisez la commande `{0}` à la place.", + "stateExpired":"Cette interaction n'est plus valide ou a expiré. Utilisez la commande `{0}` à la place. Il est normal de recevoir cette erreur après une mise à jour majeure d'Open Ticket.", + "panelStateExpired":"Ce panneau n'est plus valide ou a expiré. Créez un nouveau panneau avec `{0}` pour résoudre le problème. Il est normal de recevoir cette erreur après une mise à jour majeure d'Open Ticket." }, "optionInvalidReasons":{ "stringRegex":"La valeur ne correspond pas au modèle!", @@ -387,6 +399,8 @@ "syntax":"Syntaxe", "originalName":"Nom Original", "newName":"Nouveau Nom", + "originalCategory":"Catégorie d'Origine", + "newCategory":"Nouvelle Catégorie", "until":"Jusqu'à", "validOptions":"Options Valides", "validPanels":"Panneaux Valides", @@ -408,6 +422,8 @@ "participants":"Participants", "yes":"Oui", "no":"Non", + "accept":"Accepter", + "cancel":"Annuler", "option":"Option", "topic":"Sujet", "uptime":"Temps de fonctionnement", @@ -439,6 +455,7 @@ "panelAutoUpdate":"Voulez-vous que ce panneau se mette à jour automatiquement lorsqu'il est modifié?", "ticket":"Créez instantanément un ticket.", "ticketId":"L'identifiant du ticket que vous souhaitez créer.", + "ticketOtherUser":"Créer un ticket pour un autre utilisateur.", "close":"Fermez un ticket.", "delete":"Supprimez un ticket.", "deleteNoTranscript":"Supprimez ce ticket sans créer de transcription.", @@ -504,7 +521,9 @@ "priorityGet":"Obtenir la priorité du ticket.", "priorityList":"Obtenir une liste de tous les tickets avec leur statut de priorité.", "transfer":"Transférer la propriété du ticket d'un utilisateur à un autre.", - "transferUser":"L'utilisateur vers lequel transférer." + "transferUser":"L'utilisateur vers lequel transférer.", + "transcripts":"Voir l'historique des transcriptions de tickets d'un utilisateur.", + "transcriptsUser":"L'utilisateur à afficher." }, "helpMenu":{ "help":"Obtenez une liste de toutes les commandes disponibles.", @@ -594,7 +613,8 @@ "panel":{ "selectTicket":"Sélectionnez votre ticket", "selectRole":"Sélectionnez votre rôle", - "selectOption":"Sélectionnez votre option" + "selectOption":"Sélectionnez votre option", + "selectPriorityLevel":"Sélectionner le niveau de priorité" }, "priorities":{ "urgent":"Urgent", diff --git a/languages/german.json b/languages/german.json index 5ab33e5..65ee436 100644 --- a/languages/german.json +++ b/languages/german.json @@ -1,8 +1,8 @@ { "_TRANSLATION":{ - "otversion":"v4.1.3", + "otversion":"v4.2.0", "translators":["benzorich"], - "lastedited":"16/02/2026", + "lastedited":"25/05/2026", "language":"German", "automated":false }, @@ -14,8 +14,8 @@ "typeInfo":"[INFO]", "headerConfigChecker":"CONFIG CHECKER", "headerDescription":"Prüfen Sie auf Fehler in Ihrer Konfigurationsdateien!", - "footerError":"Der Bot wird nicht starten, bis alle {0}'s repariert sind!", - "footerWarning":"Es wird empfohlen, vor dem Start alle {0}'s zu korrigieren!", + "footerError":"Der Bot wird nicht starten, bis alle {0}en repariert sind!", + "footerWarning":"Es wird empfohlen, vor dem Start alle {0}en zu korrigieren!", "footerSupport":"SUPPORT: {0} - DOCS: {1}", "compactInformation":"Verwenden Sie {0} für weitere Informationen!", "dataPath":"Pfad", @@ -99,7 +99,7 @@ "invalidButton":"Dieser Button muss mindestens eine {0} oder {1} enthalten!", "unusedOption":"Die Option {0} wird nirgends verwendet!", "unusedQuestion":"Die Frage {0} wird nirgends verwendet!", - "dropdownOption":"Ein Panel mit aktiviertem Dropdown kann nur Optionen vom Typ 'Ticket' enthalten!", + "dropdownOption":"Ein Panel mit Dropdown kann nur Optionen der Typen 'ticket', 'role' oder 'sub-panel' enthalten.", "customInvalidVersion":"Die in deiner Konfiguration angegebene Version stimmt nicht überein! Stelle sicher, dass du die Konfiguration auf die neueste Version aktualisiert hast!" } }, @@ -118,7 +118,9 @@ "helpSwitchText":"Textbefehle anzeigen", "helpPage":"Seite {0}", "withReason":"Mit Grund", - "withoutTranscript":"Ohne Transkription" + "withoutTranscript":"Ohne Transkription", + "blacklistAdd":"Benutzer Blacklisten", + "blacklistRemove":"Benutzer Freigeben" }, "titles":{ "created":"Ticket erstellt", @@ -156,7 +158,8 @@ "topicSet":"Thema Geändert", "prioritySet":"Priorität Geändert", "priorityGet":"Ticket-Priorität", - "transfer":"Ticket Übertragen" + "transfer":"Ticket Übertragen", + "transcripts":"Transkriptverlauf" }, "descriptions":{ "create":"Ihr Ticket wurde bereits erstellt. Klicken Sie auf die Button unten, um es zu öffnen!", @@ -249,7 +252,9 @@ "prioritySetLog":"Die Priorität dieses Tickets wurde von {1} auf {0} geändert!", "prioritySetDm":"Die Priorität deines Tickets wurde auf unserem Server auf {0} geändert!", "roleUpdateLog":"{0} hat seine Rollen aktualisiert!", - "roleUpdateDm":"Deine Rollen auf unserem Server wurden aktualisiert!" + "roleUpdateDm":"Deine Rollen auf unserem Server wurden aktualisiert!", + "topicSetLog":"Die Priorität dieses Tickets wurde von {1} auf {0} gesetzt.", + "topicSetDm":"Die Priorität deines Tickets wurde auf {0} gesetzt." } }, "transcripts":{ @@ -271,7 +276,9 @@ "continue":"Löschen ohne Transkript", "backup":"Sicherungstranskript erstellen", "error":"Beim Versuch, das Transkript zu erstellen, ist etwas schief gelaufen.\nWas möchten Sie tun?\n\nDieses Ticket wird erst gelöscht, wenn Sie auf eine der folgenden Buttons klicken.", - "title":"Transkript-Fehler" + "title":"Transkript-Fehler", + "noHistory":"Dieser Benutzer hat noch keine Transkripte.", + "historyNotSupported":"Der Transkriptverlauf wird derzeit nur mit HTML Transcripts unterstützt.\nDer Verlauf von Texttranskripten wird in zukünftigen Versionen verfügbar sein." }, "text":{ "messagesTitle":"NACHRICHTEN", @@ -298,6 +305,7 @@ "unknownPanel":"Unbekanntes Panel", "notInGuild":"Nicht auf dem Server", "channelRename":"Kanal kann nicht umbenannt werden", + "channelCategory":"Kategorie Kann Nicht Geändert Werden", "busy":"Ticket ist besetzt", "permissionError":"Berechtigungsfehler" }, @@ -321,11 +329,15 @@ "deprecatedTicket":"Der aktuelle Kanal ist kein gültiges Ticket! Es könnte sich um ein Ticket aus einer alten Open Ticket Version handeln!", "notInGuild":"{0} funktioniert nicht in DM! Bitte versuchen Sie es erneut auf einem Server!", "channelRename":"Aufgrund von Discord-Ratelimits ist es dem Bot derzeit nicht möglich, den Channel umzubenennen. Der Channel wird nach 10 Minuten automatisch umbenannt, wenn der Bot nicht neu gestartet wird.", + "channelCategory":"Aufgrund von Discord-Rate-Limits konnte die Kanalkategorie nicht sofort geändert werden. Sie wird automatisch innerhalb von 10 Minuten geändert, sofern der Bot online bleibt.", "channelRenameSource":"Die Quelle dieses Fehlers ist: {0}", "busy":"{0} kann nicht verwendet werden!\nDas Ticket wird derzeit vom Bot bearbeitet.\n\nBitte versuchen Sie es in ein paar Sekunden erneut!", "closeBeforeMessage":"Dieses Ticket kann nicht geschlossen/gelöscht werden, bevor eine Nachricht von einem Benutzer gesendet wurde.", "closeBeforeAdminMessage":"Dieses Ticket kann nicht geschlossen/gelöscht werden, bevor eine Nachricht von einem Ticket-Admin oder Support-Mitglied gesendet wurde.", - "unableToCreateTicket":"Du kannst kein Ticket erstellen." + "unableToCreateTicket":"Du kannst kein Ticket erstellen.", + "messageMissing":"Die Nachricht der Interaktion konnte nicht gefunden werden. Verwende stattdessen den Befehl `{0}`.", + "stateExpired":"Diese Interaktion ist nicht mehr gültig oder abgelaufen. Verwende stattdessen den Befehl `{0}`. Es ist normal, diesen Fehler nach einem größeren Open Ticket-Update zu erhalten.", + "panelStateExpired":"Dieses Panel ist nicht mehr gültig oder abgelaufen. Erstelle ein neues Panel mit `{0}`, um das Problem zu beheben. Es ist normal, diesen Fehler nach einem größeren Open Ticket-Update zu erhalten." }, "optionInvalidReasons":{ "stringRegex":"Wert stimmt nicht mit Muster überein!", @@ -387,6 +399,8 @@ "syntax":"Syntax", "originalName":"Ursprünglicher Name", "newName":"Neuer Name", + "originalCategory":"Originalkategorie", + "newCategory":"Neue Kategorie", "until":"Bis", "validOptions":"Gültige Optionen", "validPanels":"Gültige Panels", @@ -408,6 +422,8 @@ "participants":"Teilnehmer", "yes":"Ja", "no":"Nein", + "accept":"Akzeptieren", + "cancel":"Abbrechen", "option":"Option", "topic":"Thema", "uptime":"System-Laufzeit", @@ -439,6 +455,7 @@ "panelAutoUpdate":"Möchten Sie, dass dieses Feld bei der Bearbeitung automatisch aktualisiert wird?", "ticket":"Erstellen Sie sofort ein Ticket.", "ticketId":"Der Kennung des Panels, das Sie erzeugen wollen.", + "ticketOtherUser":"Ein Ticket für einen anderen Benutzer erstellen.", "close":"Schließen Sie ein Ticket.", "delete":"Ein Ticket löschen.", "deleteNoTranscript":"Löschen Sie dieses Ticket, ohne ein Transkript zu erstellen.", @@ -504,7 +521,9 @@ "priorityGet":"Zeige die Priorität des Tickets an.", "priorityList":"Zeige eine Liste aller Tickets mit ihrem Prioritätsstatus an.", "transfer":"Übertrage das Ticket von einem Benutzer an einen anderen.", - "transferUser":"Der Benutzer, an den übertragen werden soll." + "transferUser":"Der Benutzer, an den übertragen werden soll.", + "transcripts":"Den Ticket-Transkriptverlauf eines Benutzers anzeigen.", + "transcriptsUser":"Der anzuzeigende Benutzer." }, "helpMenu":{ "help":"Erhalten Sie eine Liste mit allen verfügbaren Befehlen.", @@ -594,7 +613,8 @@ "panel":{ "selectTicket":"Wähle dein Ticket", "selectRole":"Wähle deine Rolle", - "selectOption":"Wähle deine Option" + "selectOption":"Wähle deine Option", + "selectPriorityLevel":"Prioritätsstufe auswählen" }, "priorities":{ "urgent":"Dringend", diff --git a/languages/greek.json b/languages/greek.json index 998e304..d71c53a 100644 --- a/languages/greek.json +++ b/languages/greek.json @@ -1,8 +1,8 @@ { "_TRANSLATION":{ - "otversion":"v4.1.3", + "otversion":"v4.2.0", "translators":["HanumeshGupta","ChatGPT"], - "lastedited":"16/02/2026", + "lastedited":"25/05/2026", "language":"Greek", "automated":true }, @@ -99,7 +99,7 @@ "invalidButton":"Αυτό το κουμπί πρέπει να έχει τουλάχιστον ένα {0} ή {1}!", "unusedOption":"Η επιλογή {0} δεν χρησιμοποιείται πουθενά!", "unusedQuestion":"Η ερώτηση {0} δεν χρησιμοποιείται πουθενά!", - "dropdownOption":"Ένα πάνελ με ενεργοποιημένο dropdown μπορεί να περιέχει μόνο επιλογές τύπου 'ticket'!", + "dropdownOption":"Ένα πάνελ με αναπτυσσόμενο μενού μπορεί να περιέχει μόνο επιλογές των τύπων: 'ticket', 'role' ή 'sub-panel'.", "customInvalidVersion":"Η έκδοση που καθορίστηκε στο config σας δεν ταιριάζει! Βεβαιωθείτε ότι έχετε ενημερώσει το config στην πιο πρόσφατη έκδοση!" } }, @@ -118,7 +118,9 @@ "helpSwitchText":"Προβολή Εντολών Κειμένου", "helpPage":"Σελίδα {0}", "withReason":"Με Λόγο", - "withoutTranscript":"Χωρίς Αντίγραφο" + "withoutTranscript":"Χωρίς Αντίγραφο", + "blacklistAdd":"Προσθήκη Χρήστη στη Μαύρη Λίστα", + "blacklistRemove":"Απελευθέρωση Χρήστη" }, "titles":{ "created":"Δημιουργήθηκε Εισιτήριο", @@ -156,7 +158,8 @@ "topicSet":"Το Θέμα Αλλάχθηκε", "prioritySet":"Η Προτεραιότητα Αλλάχθηκε", "priorityGet":"Προτεραιότητα Ticket", - "transfer":"Το Ticket Μεταφέρθηκε" + "transfer":"Το Ticket Μεταφέρθηκε", + "transcripts":"Ιστορικό Μεταγραφών" }, "descriptions":{ "create":"Το εισιτήριό σας δημιουργήθηκε. Κάντε κλικ στο παρακάτω κουμπί για να το προσπελάσετε!", @@ -249,7 +252,9 @@ "prioritySetLog":"Η προτεραιότητα αυτού του ticket άλλαξε σε {0} από τον/την {1}!", "prioritySetDm":"Η προτεραιότητα του ticket σας άλλαξε σε {0} στον server μας!", "roleUpdateLog":"Ο/Η {0} ενημέρωσε τους ρόλους του/της!", - "roleUpdateDm":"Οι ρόλοι σας στον server μας έχουν ενημερωθεί!" + "roleUpdateDm":"Οι ρόλοι σας στον server μας έχουν ενημερωθεί!", + "topicSetLog":"Η προτεραιότητα αυτού του ticket ορίστηκε σε {0} από τον/την {1}.", + "topicSetDm":"Η προτεραιότητα του ticket σας ορίστηκε σε {0}." } }, "transcripts":{ @@ -271,7 +276,9 @@ "continue":"Διαγραφή Χωρίς Αντίγραφο", "backup":"Δημιουργία Αντιγράφου Ασφαλείας", "error":"Κάτι πήγε στραβά κατά τη δημιουργία του αντιγράφου.\nΤι θα θέλατε να κάνετε;\n\nΑυτό το εισιτήριο δεν θα διαγραφεί μέχρι να κάνετε κλικ σε ένα από αυτά τα κουμπιά.", - "title":"Σφάλμα Transcript" + "title":"Σφάλμα Transcript", + "noHistory":"Αυτός ο χρήστης δεν έχει ακόμη κανένα ιστορικό μεταγραφών.", + "historyNotSupported":"Το ιστορικό μεταγραφών υποστηρίζεται προς το παρόν μόνο με HTML Transcripts.\nΤο ιστορικό κειμενικών μεταγραφών θα είναι διαθέσιμο σε μελλοντικές εκδόσεις." }, "text":{ "messagesTitle":"ΜΗΝΥΜΑΤΑ", @@ -298,6 +305,7 @@ "unknownPanel":"Άγνωστο Πάνελ", "notInGuild":"Δεν Είστε σε Server", "channelRename":"Αδυναμία Μετονομασίας Κανάλι", + "channelCategory":"Αδυναμία Αλλαγής Κατηγορίας", "busy":"Το Εισιτήριο Είναι Απασχολημένο", "permissionError":"Σφάλμα Δικαιωμάτων" }, @@ -321,11 +329,15 @@ "deprecatedTicket":"Το τρέχον κανάλι δεν είναι έγκυρο εισιτήριο! Μπορεί να ήταν εισιτήριο από παλιά έκδοση του Open Ticket!", "notInGuild":"Αυτό το {0} δεν λειτουργεί σε DM! Παρακαλώ δοκιμάστε ξανά σε έναν server!", "channelRename":"Λόγω discord ratelimits, είναι αδύνατο για το bot να μετονομάσει το κανάλι. Το κανάλι θα μετονομαστεί αυτόματα σε 10 λεπτά αν το bot δεν επανεκκινηθεί.", + "channelCategory":"Λόγω περιορισμών ρυθμού του Discord, η κατηγορία καναλιού δεν μπορεί να αλλάξει άμεσα. Θα αλλάξει αυτόματα μέσα σε 10 λεπτά αν το bot παραμείνει online.", "channelRenameSource":"Η πηγή αυτού του σφάλματος είναι: {0}", "busy":"Αδυναμία χρήσης αυτού του {0}!\nΤο εισιτήριο είναι αυτή τη στιγμή σε επεξεργασία από το bot.\n\nΠαρακαλώ δοκιμάστε ξανά σε λίγα δευτερόλεπτα!", "closeBeforeMessage":"Αυτό το ticket δεν μπορεί να κλείσει/διαγραφεί πριν σταλεί ένα μήνυμα από έναν χρήστη.", "closeBeforeAdminMessage":"Αυτό το ticket δεν μπορεί να κλείσει/διαγραφεί πριν σταλεί μήνυμα από έναν ticket admin ή μέλος υποστήριξης.", - "unableToCreateTicket":"Δεν μπορείτε να δημιουργήσετε ticket." + "unableToCreateTicket":"Δεν μπορείτε να δημιουργήσετε ticket.", + "messageMissing":"Δεν ήταν δυνατός ο εντοπισμός του μηνύματος αλληλεπίδρασης. Χρησιμοποιήστε την εντολή `{0}` αντί αυτού.", + "stateExpired":"Αυτή η αλληλεπίδραση δεν είναι πλέον έγκυρη ή έχει λήξει. Χρησιμοποιήστε την εντολή `{0}` αντί αυτού. Είναι φυσιολογικό μετά από μεγάλη ενημέρωση του Open Ticket.", + "panelStateExpired":"Αυτό το πάνελ δεν είναι πλέον έγκυρο ή έχει λήξει. Δημιουργήστε νέο πάνελ χρησιμοποιώντας `{0}` για να επιλύσετε το πρόβλημα. Είναι φυσιολογικό μετά από μεγάλη ενημέρωση του Open Ticket." }, "optionInvalidReasons":{ "stringRegex":"Η τιμή δεν ταιριάζει με το μοτίβο!", @@ -387,6 +399,8 @@ "syntax":"Σύνταξη", "originalName":"Αρχικό Όνομα", "newName":"Νέο Όνομα", + "originalCategory":"Αρχική Κατηγορία", + "newCategory":"Νέα Κατηγορία", "until":"Μέχρι", "validOptions":"Έγκυρες Επιλογές", "validPanels":"Έγκυρα Πάνελ", @@ -408,6 +422,8 @@ "participants":"Συμμετέχοντες", "yes":"Ναι", "no":"Όχι", + "accept":"Αποδοχή", + "cancel":"Ακύρωση", "option":"Επιλογή", "topic":"Θέμα", "uptime":"Χρόνος Λειτουργίας Συστήματος", @@ -439,6 +455,7 @@ "panelAutoUpdate":"Θέλετε αυτό το πάνελ να ενημερώνεται αυτόματα όταν επεξεργαστεί;", "ticket":"Δημιουργία εισιτηρίου αμέσως.", "ticketId":"Το αναγνωριστικό του εισιτηρίου που θέλετε να δημιουργήσετε.", + "ticketOtherUser":"Δημιουργήστε ένα ticket για άλλον χρήστη.", "close":"Κλείσιμο εισιτηρίου.", "delete":"Διαγραφή εισιτηρίου.", "deleteNoTranscript":"Διαγραφή αυτού του εισιτηρίου χωρίς δημιουργία μεταγραφής.", @@ -504,7 +521,9 @@ "priorityGet":"Λάβετε την προτεραιότητα του ticket.", "priorityList":"Λάβετε μια λίστα όλων των tickets με την κατάσταση προτεραιότητάς τους.", "transfer":"Μεταφέρετε την ιδιοκτησία του ticket από έναν χρήστη σε άλλον.", - "transferUser":"Ο χρήστης στον οποίο θα γίνει η μεταφορά." + "transferUser":"Ο χρήστης στον οποίο θα γίνει η μεταφορά.", + "transcripts":"Προβολή ιστορικού μεταγραφών ticket ενός χρήστη.", + "transcriptsUser":"Ο χρήστης προς προβολή." }, "helpMenu":{ "help":"Λήψη λίστας όλων των διαθέσιμων εντολών.", @@ -594,7 +613,8 @@ "panel":{ "selectTicket":"Επιλέξτε το ticket σας", "selectRole":"Επιλέξτε τον ρόλο σας", - "selectOption":"Επιλέξτε την επιλογή σας" + "selectOption":"Επιλέξτε την επιλογή σας", + "selectPriorityLevel":"Επιλέξτε επίπεδο προτεραιότητας" }, "priorities":{ "urgent":"Επείγον", diff --git a/languages/hindi.json b/languages/hindi.json index 30dd621..9a4bc31 100644 --- a/languages/hindi.json +++ b/languages/hindi.json @@ -1,8 +1,8 @@ { "_TRANSLATION":{ - "otversion":"v4.1.3", + "otversion":"v4.2.0", "translators":["challenger_nova"], - "lastedited":"16/02/2026", + "lastedited":"25/05/2026", "language":"Hindi", "automated":false }, @@ -99,7 +99,7 @@ "invalidButton":"इस बटन पर कम से कम {0} या {1} होना आवश्यक है!", "unusedOption":"विकल्प {0} का उपयोग कहीं भी नहीं किया जाता है!", "unusedQuestion":"प्रश्न {0} का प्रयोग कहीं भी नहीं किया गया है!", - "dropdownOption":"ड्रॉपडाउन सक्षम पैनल में केवल 'टिकट' प्रकार के विकल्प हो सकते हैं!", + "dropdownOption":"ड्रॉपडाउन वाला पैनल केवल इन प्रकारों के विकल्प रख सकता है: 'ticket', 'role' या 'sub-panel'.", "customInvalidVersion":"आपके कॉन्फ़िग में निर्दिष्ट संस्करण मेल नहीं खाता! कृपया सुनिश्चित करें कि आपने कॉन्फ़िग को नवीनतम संस्करण में अपडेट किया है!" } }, @@ -118,7 +118,9 @@ "helpSwitchText":"टेक्स्ट कमांड देखें", "helpPage":"पेज {0}", "withReason":"कारण के साथ", - "withoutTranscript":"प्रतिलेख के बिना" + "withoutTranscript":"प्रतिलेख के बिना", + "blacklistAdd":"उपयोगकर्ता को ब्लैकलिस्ट करें", + "blacklistRemove":"उपयोगकर्ता को रिलीज़ करें" }, "titles":{ "created":"टिकट बनाया गया", @@ -156,7 +158,8 @@ "topicSet":"विषय बदला गया", "prioritySet":"प्राथमिकता बदली गई", "priorityGet":"टिकट प्राथमिकता", - "transfer":"टिकट स्थानांतरित किया गया" + "transfer":"टिकट स्थानांतरित किया गया", + "transcripts":"ट्रांसक्रिप्ट इतिहास" }, "descriptions":{ "create":"आपका टिकट बन गया है. ", @@ -249,7 +252,9 @@ "prioritySetLog":"इस टिकट की प्राथमिकता {1} द्वारा {0} में बदल दी गई है!", "prioritySetDm":"आपके टिकट की प्राथमिकता हमारे सर्वर में {0} में बदल दी गई है!", "roleUpdateLog":"{0} ने अपनी भूमिकाएँ अपडेट की हैं!", - "roleUpdateDm":"आपकी भूमिकाएँ हमारे सर्वर में अपडेट कर दी गई हैं!" + "roleUpdateDm":"आपकी भूमिकाएँ हमारे सर्वर में अपडेट कर दी गई हैं!", + "topicSetLog":"इस टिकट की प्राथमिकता {1} द्वारा {0} पर सेट की गई है।", + "topicSetDm":"आपके टिकट की प्राथमिकता {0} पर सेट की गई है।" } }, "transcripts":{ @@ -271,7 +276,9 @@ "continue":"प्रतिलेख के बिना हटाएँ", "backup":"बैकअप ट्रांस्क्रिप्ट बनाएं", "error":"प्रतिलेख बनाने का प्रयास करते समय कुछ गलत हो गया।\n", - "title":"ट्रांसक्रिप्ट त्रुटि" + "title":"ट्रांसक्रिप्ट त्रुटि", + "noHistory":"इस उपयोगकर्ता के पास अभी तक कोई ट्रांसक्रिप्ट नहीं है।", + "historyNotSupported":"ट्रांसक्रिप्ट इतिहास वर्तमान में केवल HTML Transcripts के साथ समर्थित है।\nटेक्स्ट ट्रांसक्रिप्ट इतिहास भविष्य के संस्करणों में उपलब्ध होगा।" }, "text":{ "messagesTitle":"संदेश", @@ -298,6 +305,7 @@ "unknownPanel":"अज्ञात पैनल", "notInGuild":"सर्वर में नहीं", "channelRename":"चैनल का नाम बदलने में असमर्थ", + "channelCategory":"श्रेणी बदलने में असमर्थ", "busy":"टिकट व्यस्त है", "permissionError":"अनुमति त्रुटि" }, @@ -321,11 +329,15 @@ "deprecatedTicket":"वर्तमान चैनल वैध टिकट नहीं है! ", "notInGuild":"यह {0} डीएम में काम नहीं करता! ", "channelRename":"विवाद की दर सीमा के कारण, वर्तमान में बॉट के लिए चैनल का नाम बदलना असंभव है। ", + "channelCategory":"Discord rate limits के कारण चैनल श्रेणी तुरंत नहीं बदली जा सकी। यदि बॉट ऑनलाइन रहता है तो यह 10 मिनट के भीतर स्वतः बदल दी जाएगी।", "channelRenameSource":"इस त्रुटि का स्रोत है: {0}", "busy":"इस {0} का उपयोग करने में असमर्थ!\n", "closeBeforeMessage":"किसी उपयोगकर्ता द्वारा संदेश भेजे जाने से पहले इस टिकट को बंद/हटाया नहीं जा सकता।", "closeBeforeAdminMessage":"टिकट एडमिन या सपोर्ट सदस्य द्वारा संदेश भेजे जाने से पहले इस टिकट को बंद/हटाया नहीं जा सकता।", - "unableToCreateTicket":"आप टिकट बनाने में असमर्थ हैं।" + "unableToCreateTicket":"आप टिकट बनाने में असमर्थ हैं।", + "messageMissing":"इंटरैक्शन का संदेश नहीं मिल सका। इसके बजाय `{0}` कमांड का उपयोग करें।", + "stateExpired":"यह इंटरैक्शन अब मान्य नहीं है या समाप्त हो चुका है। इसके बजाय `{0}` कमांड का उपयोग करें। Open Ticket के बड़े अपडेट के बाद यह त्रुटि मिलना सामान्य है।", + "panelStateExpired":"यह पैनल अब मान्य नहीं है या समाप्त हो चुका है। समस्या हल करने के लिए `{0}` का उपयोग करके नया पैनल बनाएं। Open Ticket के बड़े अपडेट के बाद यह त्रुटि मिलना सामान्य है।" }, "optionInvalidReasons":{ "stringRegex":"मान पैटर्न से मेल नहीं खाता!", @@ -387,6 +399,8 @@ "syntax":"सिंटेक्स", "originalName":"मूल नाम", "newName":"नया नाम", + "originalCategory":"मूल श्रेणी", + "newCategory":"नई श्रेणी", "until":"जब तक", "validOptions":"वैध विकल्प", "validPanels":"वैध पैनल", @@ -408,6 +422,8 @@ "participants":"प्रतिभागी", "yes":"हाँ", "no":"नहीं", + "accept":"स्वीकार करें", + "cancel":"रद्द करें", "option":"विकल्प", "topic":"विषय", "uptime":"सिस्टम अपटाइम", @@ -439,6 +455,7 @@ "panelAutoUpdate":"क्या आप चाहते हैं कि संपादित होने पर यह पैनल स्वचालित रूप से अपडेट हो जाए?", "ticket":"तुरंत टिकट बनाएं.", "ticketId":"उस टिकट का पहचानकर्ता जिसे आप बनाना चाहते हैं.", + "ticketOtherUser":"किसी अन्य उपयोगकर्ता के लिए टिकट बनाएं।", "close":"एक टिकट बंद करें.", "delete":"एक टिकट हटाएँ.", "deleteNoTranscript":"प्रतिलेख बनाए बिना इस टिकट को हटा दें।", @@ -504,7 +521,9 @@ "priorityGet":"टिकट की प्राथमिकता प्राप्त करें।", "priorityList":"सभी टिकटों की प्राथमिकता स्थिति की सूची प्राप्त करें।", "transfer":"टिकट का स्वामित्व एक उपयोगकर्ता से दूसरे को स्थानांतरित करें।", - "transferUser":"जिस उपयोगकर्ता को स्थानांतरित करना है।" + "transferUser":"जिस उपयोगकर्ता को स्थानांतरित करना है।", + "transcripts":"किसी उपयोगकर्ता का टिकट ट्रांसक्रिप्ट इतिहास देखें।", + "transcriptsUser":"देखने के लिए उपयोगकर्ता।" }, "helpMenu":{ "help":"सभी उपलब्ध आदेशों की सूची प्राप्त करें.", @@ -594,7 +613,8 @@ "panel":{ "selectTicket":"अपना टिकट चुनें", "selectRole":"अपनी भूमिका चुनें", - "selectOption":"अपना विकल्प चुनें" + "selectOption":"अपना विकल्प चुनें", + "selectPriorityLevel":"प्राथमिकता स्तर चुनें" }, "priorities":{ "urgent":"अत्यावश्यक", diff --git a/languages/hungarian.json b/languages/hungarian.json index bf989b3..20ce2dc 100644 --- a/languages/hungarian.json +++ b/languages/hungarian.json @@ -1,8 +1,8 @@ { "_TRANSLATION":{ - "otversion":"v4.1.3", + "otversion":"v4.2.0", "translators":["Kornel0706"], - "lastedited":"16/02/2026", + "lastedited":"25/05/2026", "language":"Hungarian", "automated":false }, @@ -99,7 +99,7 @@ "invalidButton":"Ennek a gombnak legalább egy {0} vagy {1} kell, hogy tartalmazzon!", "unusedOption":"Az opció {0} nincs használatban sehol!", "unusedQuestion":"A kérdés {0} nincs használatban sehol!", - "dropdownOption":"Egy legördülő menüvel rendelkező panel csak a 'jegy' típusú opciókat tartalmazhat!", + "dropdownOption":"A legördülő menüvel rendelkező panel csak a következő típusú opciókat tartalmazhatja: 'ticket', 'role' vagy 'sub-panel'.", "customInvalidVersion":"A konfigurációban megadott verzió nem egyezik! Győződjön meg róla, hogy a konfiguráció a legfrissebb verzióra van frissítve!" } }, @@ -118,7 +118,9 @@ "helpSwitchText":"Szöveges parancsok megtekintése", "helpPage":"Oldal {0}", "withReason":"Indoklással", - "withoutTranscript":"Átirat nélkül" + "withoutTranscript":"Átirat nélkül", + "blacklistAdd":"Felhasználó Tiltólistára Helyezése", + "blacklistRemove":"Felhasználó Feloldása" }, "titles":{ "created":"Jegy létrehozva", @@ -156,7 +158,8 @@ "topicSet":"Téma megváltoztatva", "prioritySet":"Prioritás megváltoztatva", "priorityGet":"Jegy prioritása", - "transfer":"Jegy átvitele" + "transfer":"Jegy átvitele", + "transcripts":"Átirat Előzmények" }, "descriptions":{ "create":"A jegyed létrejött. Kattints az alábbi gombra, hogy hozzáférj!", @@ -249,7 +252,9 @@ "prioritySetLog":"A jegy prioritása {1} által {0}-re változott!", "prioritySetDm":"A jegy prioritása a szerverünkön {0}-re változott!", "roleUpdateLog":"{0} frissítette a szerepköreit!", - "roleUpdateDm":"A szerepköreit frissítették a szerverünkön!" + "roleUpdateDm":"A szerepköreit frissítették a szerverünkön!", + "topicSetLog":"A jegy prioritását {1} {0} értékre állította.", + "topicSetDm":"A jegyed prioritása {0} értékre lett állítva." } }, "transcripts":{ @@ -271,7 +276,9 @@ "continue":"Törlés átirat nélkül", "backup":"Biztonsági átirat létrehozása", "error":"Valami hiba történt az átirat létrehozása közben.\nMit szeretnél tenni?\n\nEz a jegy nem kerül törlésre, amíg nem kattintasz az egyik gombra.", - "title":"Átirat hiba" + "title":"Átirat hiba", + "noHistory":"Ennek a felhasználónak még nincsenek átiratai.", + "historyNotSupported":"Az átirat előzmények jelenleg csak HTML Transcripts használatával támogatottak.\nA szöveges átirat előzmények a jövőbeli verziókban lesznek elérhetők." }, "text":{ "messagesTitle":"ÜZENETEK", @@ -298,6 +305,7 @@ "unknownPanel":"Ismeretlen Panel", "notInGuild":"Nem a Szerveren", "channelRename":"Nem sikerült átnevezni a csatornát", + "channelCategory":"A Kategória Nem Módosítható", "busy":"A Jegy Elfoglalt", "permissionError":"Engedély hiba" }, @@ -321,11 +329,15 @@ "deprecatedTicket":"Az aktuális csatorna nem egy érvényes jegy! Lehet, hogy ez egy régi Open Ticket verzióból származó jegy volt!", "notInGuild":"Ez a {0} nem működik DM-ben! Próbáld újra egy szerveren!", "channelRename":"A discord korlátai miatt jelenleg nem lehet átnevezni a csatornát. A csatorna automatikusan átnevezésre kerül 10 perc múlva, ha a bot nincs újraindítva.", + "channelCategory":"A Discord sebességkorlátai miatt a csatorna kategóriája nem módosítható azonnal. Automatikusan módosul 10 percen belül, ha a bot online marad.", "channelRenameSource":"A hiba forrása: {0}", "busy":"Nem lehet használni ezt a {0}-t!\nA jegy jelenleg a bot által van feldolgozás alatt.\n\nPróbáld meg néhány másodperc múlva!", "closeBeforeMessage":"Ezt a jegyet nem lehet lezárni/törölni, amíg egy felhasználó nem küldött üzenetet.", "closeBeforeAdminMessage":"Ezt a jegyet nem lehet lezárni/törölni, amíg a jegy adminja vagy a támogatói csapat tagja nem küldött üzenetet.", - "unableToCreateTicket":"Nem tud jegyet létrehozni." + "unableToCreateTicket":"Nem tud jegyet létrehozni.", + "messageMissing":"Nem található az interakció üzenete. Használd helyette a(z) `{0}` parancsot.", + "stateExpired":"Ez az interakció már nem érvényes vagy lejárt. Használd helyette a(z) `{0}` parancsot. Ez a hiba normális egy nagyobb Open Ticket frissítés után.", + "panelStateExpired":"Ez a panel már nem érvényes vagy lejárt. Hozz létre egy új panelt a(z) `{0}` használatával a probléma megoldásához. Ez a hiba normális egy nagyobb Open Ticket frissítés után." }, "optionInvalidReasons":{ "stringRegex":"Az érték nem felel meg a mintának!", @@ -387,6 +399,8 @@ "syntax":"Szintaxis", "originalName":"Eredeti Név", "newName":"Új Név", + "originalCategory":"Eredeti Kategória", + "newCategory":"Új Kategória", "until":"Amíg", "validOptions":"Érvényes Opciók", "validPanels":"Érvényes Panelek", @@ -408,6 +422,8 @@ "participants":"Résztvevők", "yes":"Igen", "no":"Nem", + "accept":"Elfogadás", + "cancel":"Mégse", "option":"Opció", "topic":"Téma", "uptime":"Rendszer üzemidő", @@ -439,6 +455,7 @@ "panelAutoUpdate":"Szeretnéd, hogy ez a panel automatikusan frissüljön szerkesztéskor?", "ticket":"Azonnal hozz létre egy jegyet.", "ticketId":"Az azonosítója annak a jegynek, amelyet létre szeretnél hozni.", + "ticketOtherUser":"Jegy létrehozása egy másik felhasználó számára.", "close":"Zárjon be egy jegyet.", "delete":"Jegy törlése.", "deleteNoTranscript":"Töröld ezt a jegyet átirat nélkül.", @@ -504,7 +521,9 @@ "priorityGet":"Kapja meg a jegy prioritását.", "priorityList":"Az összes jegy listája a prioritásukkal.", "transfer":"Átadja a jegy tulajdonjogát egyik felhasználótól a másikhoz.", - "transferUser":"A felhasználó, akinek át kell adni." + "transferUser":"A felhasználó, akinek át kell adni.", + "transcripts":"Egy felhasználó jegy átirat előzményeinek megtekintése.", + "transcriptsUser":"A megtekintendő felhasználó." }, "helpMenu":{ "help":"Parancsok listájának megtekintése.", @@ -594,7 +613,8 @@ "panel":{ "selectTicket":"Válassza ki a jegyét", "selectRole":"Válassza ki a szerepkörét", - "selectOption":"Válassza ki a lehetőségét" + "selectOption":"Válassza ki a lehetőségét", + "selectPriorityLevel":"Prioritási szint kiválasztása" }, "priorities":{ "urgent":"Sürgős", diff --git a/languages/indonesian.json b/languages/indonesian.json index 66c1483..7765f92 100644 --- a/languages/indonesian.json +++ b/languages/indonesian.json @@ -1,8 +1,8 @@ { "_TRANSLATION":{ - "otversion":"v4.1.3", + "otversion":"v4.2.0", "translators":["erxg"], - "lastedited":"16/02/2026", + "lastedited":"25/05/2026", "language":"Indonesian", "automated":false }, @@ -99,7 +99,7 @@ "invalidButton":"Tombol ini harus memiliki setidaknya {0} atau {1}!", "unusedOption":"Opsi {0} tidak digunakan di mana pun!", "unusedQuestion":"Pertanyaan {0} tidak digunakan di mana pun!", - "dropdownOption":"Panel dengan menu tarik-ulur yang diaktifkan hanya dapat berisi opsi dengan tipe 'tiket'!", + "dropdownOption":"Panel dengan dropdown hanya dapat berisi opsi dengan tipe: 'ticket', 'role' atau 'sub-panel'.", "customInvalidVersion":"Versi yang ditetapkan dalam konfigurasi tidak sesuai! Pastikan konfigurasi Anda telah diperbarui ke versi terbaru!" } }, @@ -118,7 +118,9 @@ "helpSwitchText":"Lihat Perintah Teks", "helpPage":"Halaman {0}", "withReason":"Dengan Alasan", - "withoutTranscript":"Tanpa Transkrip" + "withoutTranscript":"Tanpa Transkrip", + "blacklistAdd":"Masukkan Pengguna ke Daftar Hitam", + "blacklistRemove":"Lepaskan Pengguna" }, "titles":{ "created":"Tiket Dibuat", @@ -156,7 +158,8 @@ "topicSet":"Topik Diganti", "prioritySet":"Prioritas Berganti", "priorityGet":"Prioritas Tiket", - "transfer":"Tiket Ditransfer" + "transfer":"Tiket Ditransfer", + "transcripts":"Riwayat Transkrip" }, "descriptions":{ "create":"Tiket kamu telah dibuat. Klik tombol di bawah ini untuk mengaksesnya!", @@ -249,7 +252,9 @@ "prioritySetLog":"Prioritas tiket ini telah diubah menjadi {0} oleh {1}!", "prioritySetDm":"Prioritas tiket kamu telah diubah menjadi {0} di server kami!", "roleUpdateLog":"{0} telah memperbarui status mereka!", - "roleUpdateDm":"Status kamu di server kami telah diperbarui!" + "roleUpdateDm":"Status kamu di server kami telah diperbarui!", + "topicSetLog":"Prioritas tiket ini telah diatur ke {0} oleh {1}.", + "topicSetDm":"Prioritas tiket Anda telah diatur ke {0}." } }, "transcripts":{ @@ -271,7 +276,9 @@ "continue":"Hapus Tanpa Transkrip", "backup":"Membuat Transkrip Cadangan", "error":"Terjadi kesalahan saat mencoba membuat transkrip.\nApa yang ingin Anda lakukan?\n\nTiket ini tidak akan dihapus hingga Anda mengeklik salah satu tombol ini.", - "title":"Kesalahan Dalam Pembuatan Transkrip" + "title":"Kesalahan Dalam Pembuatan Transkrip", + "noHistory":"Pengguna ini belum memiliki transkrip.", + "historyNotSupported":"Riwayat transkrip saat ini hanya didukung dengan HTML Transcripts.\nRiwayat transkrip teks akan tersedia di versi mendatang." }, "text":{ "messagesTitle":"PESAN", @@ -298,6 +305,7 @@ "unknownPanel":"Panel Tidak Diketahui", "notInGuild":"Tidak Berada Di Dalam Server", "channelRename":"Tidak Dapat Mengganti Nama Saluran", + "channelCategory":"Tidak Dapat Mengubah Kategori", "busy":"Tidak Sedang Sibuk", "permissionError":"Terjadi Kesalahan Hak Akses" }, @@ -321,11 +329,15 @@ "deprecatedTicket":"Saluran saat ini bukanlah tiket yang valid! Ini mungkin merupakan tiket dari versi Open Tiket yang lama!", "notInGuild":"Ini {0} tidak berfungsi di DM! Silakan coba lagi di server!", "channelRename":"Karena batasan rasio Discord, saat ini bot tidak mungkin mengganti nama saluran. Saluran akan secara otomatis diganti namanya dalam waktu 10 menit jika bot tidak di-boot ulang.", + "channelCategory":"Karena batas rate Discord, kategori channel tidak dapat diubah segera. Kategori akan diubah secara otomatis dalam 10 menit jika bot tetap online.", "channelRenameSource":"Sumber dari masalah: {0}", "busy":"Dapat dapat menggunakan {0}!\nTiket sedang diproses oleh bot.\n\nMohon coba beberapa saat lagi!", "closeBeforeMessage":"Tiket ini tidak dapat ditutup ataupun dihapus sebelum pesan dikirim oleh seseorang.", "closeBeforeAdminMessage":"Tiket ini tidak dapat ditutup ataupun dihapus sebelum admin tiket atau anggota tim dukungan mengirimkan pesan.", - "unableToCreateTicket":"Anda tidak dapat membuat tiket.." + "unableToCreateTicket":"Anda tidak dapat membuat tiket..", + "messageMissing":"Tidak dapat menemukan pesan interaksi. Gunakan perintah `{0}` sebagai gantinya.", + "stateExpired":"Interaksi ini sudah tidak valid atau telah kedaluwarsa. Gunakan perintah `{0}` sebagai gantinya. Kesalahan ini normal terjadi setelah pembaruan besar Open Ticket.", + "panelStateExpired":"Panel ini sudah tidak valid atau telah kedaluwarsa. Buat panel baru menggunakan `{0}` untuk menyelesaikan masalah. Kesalahan ini normal terjadi setelah pembaruan besar Open Ticket." }, "optionInvalidReasons":{ "stringRegex":"Nilai tidak sesuai pola!", @@ -387,6 +399,8 @@ "syntax":"Sintaks", "originalName":"Nama asli", "newName":"Nama Yang Baru", + "originalCategory":"Kategori Asli", + "newCategory":"Kategori Baru", "until":"Hingga", "validOptions":"Opsi Yang Valid", "validPanels":"Panel Yang Valid", @@ -408,6 +422,8 @@ "participants":"Para Partisipan", "yes":"Ya", "no":"Tidak", + "accept":"Terima", + "cancel":"Batal", "option":"Opsi", "topic":"Topik", "uptime":"Waktu Aktif Sistem", @@ -439,6 +455,7 @@ "panelAutoUpdate":"Apa Anda ingin panel ini diperbarui secara otomatis ketika diubah?", "ticket":"Buat tiket secara instan.", "ticketId":"Penanda tiket yang ingin anda buat.", + "ticketOtherUser":"Buat tiket untuk pengguna lain.", "close":"Tutup tiket.", "delete":"Hapus tiket.", "deleteNoTranscript":"Hapus tiket tanpa membuat transkrip.", @@ -504,7 +521,9 @@ "priorityGet":"Dapatkan prioritas tiket.", "priorityList":"Dapatkan daftar semua tiket beserta status prioritasnya.", "transfer":"Alihkan kepemilikan tiket dari satu pengguna ke pengguna lain..", - "transferUser":"Pengguna yang akan ditransferkan." + "transferUser":"Pengguna yang akan ditransferkan.", + "transcripts":"Lihat riwayat transkrip tiket pengguna.", + "transcriptsUser":"Pengguna yang ingin dilihat." }, "helpMenu":{ "help":"Dapatkan daftar perintah yang tersedia.", @@ -594,7 +613,8 @@ "panel":{ "selectTicket":"Pilih tiket kamu", "selectRole":"Pilih status kamu", - "selectOption":"Pilih opsi kamu" + "selectOption":"Pilih opsi kamu", + "selectPriorityLevel":"Pilih tingkat prioritas" }, "priorities":{ "urgent":"Sangat Penting", diff --git a/languages/italian.json b/languages/italian.json index 28a6723..09a15c3 100644 --- a/languages/italian.json +++ b/languages/italian.json @@ -1,8 +1,8 @@ { "_TRANSLATION":{ - "otversion":"v4.1.3", + "otversion":"v4.2.0", "translators":["fraden1mvp.","imperatorix_17"], - "lastedited":"16/02/2026", + "lastedited":"25/05/2026", "language":"Italian", "automated":false }, @@ -99,7 +99,7 @@ "invalidButton":"Questo pulsante deve avere almeno un {0} o {1}!", "unusedOption":"L'opzione {0} non è utilizzata da nessuna parte!", "unusedQuestion":"La domanda {0} non è utilizzata da nessuna parte!", - "dropdownOption":"Un pannello con il dropdown attivato può contenere solo opzioni di tipo 'ticket'!", + "dropdownOption":"Un pannello con menu a discesa può contenere solo opzioni dei tipi: 'ticket', 'role' o 'sub-panel'.", "customInvalidVersion":"La versione specificata nella tua configurazione non corrisponde! Assicurati di aver aggiornato la configurazione all'ultima versione!" } }, @@ -118,7 +118,9 @@ "helpSwitchText":"Visualizza Comandi Testo", "helpPage":"Pagina {0}", "withReason":"Con Motivo", - "withoutTranscript":"Senza Trascrizione" + "withoutTranscript":"Senza Trascrizione", + "blacklistAdd":"Metti Utente in Blacklist", + "blacklistRemove":"Rilascia Utente" }, "titles":{ "created":"Ticket Creato", @@ -156,7 +158,8 @@ "topicSet":"Argomento Cambiato", "prioritySet":"Priorità Cambiata", "priorityGet":"Priorità Ticket", - "transfer":"Ticket Trasferito" + "transfer":"Ticket Trasferito", + "transcripts":"Cronologia Trascrizioni" }, "descriptions":{ "create":"Il tuo ticket è stato creato. Clicca il pulsante qui sotto per accedervi!", @@ -249,7 +252,9 @@ "prioritySetLog":"La priorità di questo ticket è stata cambiata in {0} da {1}!", "prioritySetDm":"La priorità del tuo ticket è stata cambiata in {0} nel nostro server!", "roleUpdateLog":"{0} ha aggiornato i propri ruoli!", - "roleUpdateDm":"I tuoi ruoli nel nostro server sono stati aggiornati!" + "roleUpdateDm":"I tuoi ruoli nel nostro server sono stati aggiornati!", + "topicSetLog":"La priorità di questo ticket è stata impostata su {0} da {1}.", + "topicSetDm":"La priorità del tuo ticket è stata impostata su {0}." } }, "transcripts":{ @@ -271,7 +276,9 @@ "continue":"Elimina Senza Trascrizione", "backup":"Crea Trascrizione di Backup", "error":"Qualcosa è andato storto durante la creazione della trascrizione.\nCosa vuoi fare?\n\nQuesto ticket non sarà eliminato finché non clicchi su uno di questi pulsanti.", - "title":"Errore nel Transcript" + "title":"Errore nel Transcript", + "noHistory":"Questo utente non ha ancora alcuna trascrizione.", + "historyNotSupported":"La cronologia delle trascrizioni è attualmente supportata solo con HTML Transcripts.\nLa cronologia delle trascrizioni testuali sarà disponibile nelle versioni future." }, "text":{ "messagesTitle":"MESSAGGI", @@ -298,6 +305,7 @@ "unknownPanel":"Pannello Sconosciuto", "notInGuild":"Non nel Server", "channelRename":"Impossibile Rinominare il Canale", + "channelCategory":"Impossibile Cambiare Categoria", "busy":"Ticket Occupato", "permissionError":"Errore di Permesso" }, @@ -321,11 +329,15 @@ "deprecatedTicket":"Il canale attuale non è un ticket valido! Potrebbe essere un ticket di una vecchia versione di Open Ticket!", "notInGuild":"Questo {0} non funziona in DM! Riprova in un server!", "channelRename":"A causa dei limiti di Discord, al momento non è possibile rinominare il canale. Il canale verrà automaticamente rinominato in oltre 10 minuti se il bot non viene riavviato.", + "channelCategory":"A causa dei limiti di frequenza di Discord, la categoria del canale non può essere modificata immediatamente. Verrà modificata automaticamente entro 10 minuti se il bot rimane online.", "channelRenameSource":"La fonte di questo errore è: {0}", "busy":"Impossibile utilizzare questo {0}!\nIl ticket è attualmente in fase di elaborazione dal bot.\n\nPer favore riprova tra pochi secondi!", "closeBeforeMessage":"Questo ticket non può essere chiuso/eliminato prima che un messaggio sia stato inviato da un utente.", "closeBeforeAdminMessage":"Questo ticket non può essere chiuso/eliminato prima che un messaggio sia stato inviato da un admin del ticket o da un membro del supporto.", - "unableToCreateTicket":"Non puoi creare un ticket." + "unableToCreateTicket":"Non puoi creare un ticket.", + "messageMissing":"Impossibile trovare il messaggio dell'interazione. Usa invece il comando `{0}`.", + "stateExpired":"Questa interazione non è più valida o è scaduta. Usa invece il comando `{0}`. È normale ricevere questo errore dopo un importante aggiornamento di Open Ticket.", + "panelStateExpired":"Questo pannello non è più valido o è scaduto. Crea un nuovo pannello usando `{0}` per risolvere il problema. È normale ricevere questo errore dopo un importante aggiornamento di Open Ticket." }, "optionInvalidReasons":{ "stringRegex":"Il valore non corrisponde al modello!", @@ -387,6 +399,8 @@ "syntax":"Sintassi", "originalName":"Nome Originale", "newName":"Nuovo Nome", + "originalCategory":"Categoria Originale", + "newCategory":"Nuova Categoria", "until":"Fino a", "validOptions":"Opzioni Valide", "validPanels":"Pannelli Validi", @@ -408,6 +422,8 @@ "participants":"Partecipanti", "yes":"Si", "no":"No", + "accept":"Accetta", + "cancel":"Annulla", "option":"Opzione", "topic":"Argomento", "uptime":"Uptime Sistema", @@ -439,6 +455,7 @@ "panelAutoUpdate":"Vuoi che questo pannello si aggiorni automaticamente quando viene modificato?", "ticket":"Crea istantaneamente un ticket.", "ticketId":"L'identificatore del ticket che vuoi creare.", + "ticketOtherUser":"Crea un ticket per un altro utente.", "close":"Chiudi un ticket.", "delete":"Elimina un ticket.", "deleteNoTranscript":"Elimina questo ticket senza creare una trascrizione.", @@ -504,7 +521,9 @@ "priorityGet":"Ottieni la priorità del ticket.", "priorityList":"Ottieni una lista di tutti i ticket con il loro stato di priorità.", "transfer":"Trasferisci la proprietà da un utente ad un'altro.", - "transferUser":"L'utente al quale trasferire." + "transferUser":"L'utente al quale trasferire.", + "transcripts":"Visualizza la cronologia delle trascrizioni dei ticket di un utente.", + "transcriptsUser":"L'utente da visualizzare." }, "helpMenu":{ "help":"Ottieni un elenco di tutti i comandi disponibili.", @@ -594,7 +613,8 @@ "panel":{ "selectTicket":"Seleziona il tuo Ticket", "selectRole":"Seleziona il tuo ruolo", - "selectOption":"Seleziona la tua opzione" + "selectOption":"Seleziona la tua opzione", + "selectPriorityLevel":"Seleziona livello di priorità" }, "priorities":{ "urgent":"Urgente", diff --git a/languages/japanese.json b/languages/japanese.json index f528a9a..0367a36 100644 --- a/languages/japanese.json +++ b/languages/japanese.json @@ -1,8 +1,8 @@ { "_TRANSLATION":{ - "otversion":"v4.1.3", + "otversion":"v4.2.0", "translators":["HanumeshGupta","ChatGPT"], - "lastedited":"16/02/2026", + "lastedited":"25/05/2026", "language":"Japanese", "automated":true }, @@ -99,7 +99,7 @@ "invalidButton":"このボタンには少なくとも「{0}」または「{1}」が必要です!", "unusedOption":"オプション「{0}」はどこでも使用されていません!", "unusedQuestion":"質問「{0}」はどこでも使用されていません!", - "dropdownOption":"ドロップダウンが有効なパネルには「ticket」タイプのオプションのみ含めることができます!", + "dropdownOption":"ドロップダウン付きパネルには次の種類のオプションのみ含めることができます:'ticket'、'role'、または'sub-panel'。", "customInvalidVersion":"設定ファイルに指定されたバージョンが一致しません! 設定を最新バージョンに更新してください!" } }, @@ -118,7 +118,9 @@ "helpSwitchText":"テキストコマンドを表示", "helpPage":"ページ {0}", "withReason":"理由を付ける", - "withoutTranscript":"トランスクリプトなし" + "withoutTranscript":"トランスクリプトなし", + "blacklistAdd":"ユーザーをブラックリストに追加", + "blacklistRemove":"ユーザーのブラックリスト解除" }, "titles":{ "created":"チケットが作成されました", @@ -156,7 +158,8 @@ "topicSet":"トピックが変更されました", "prioritySet":"優先度が変更されました", "priorityGet":"チケットの優先度", - "transfer":"チケットが転送されました" + "transfer":"チケットが転送されました", + "transcripts":"トランスクリプト履歴" }, "descriptions":{ "create":"チケットが作成されました。下のボタンをクリックしてアクセスしてください!", @@ -249,7 +252,9 @@ "prioritySetLog":"このチケットの優先度は {1} により {0} に変更されました!", "prioritySetDm":"あなたのチケットの優先度はサーバー内で {0} に変更されました!", "roleUpdateLog":"{0} がロールを更新しました!", - "roleUpdateDm":"サーバー内のあなたのロールが更新されました!" + "roleUpdateDm":"サーバー内のあなたのロールが更新されました!", + "topicSetLog":"このチケットの優先度は {1} により {0} に設定されました。", + "topicSetDm":"あなたのチケットの優先度は {0} に設定されました。" } }, "transcripts":{ @@ -271,7 +276,9 @@ "continue":"トランスクリプトなしで削除", "backup":"バックアップトランスクリプトを作成", "error":"トランスクリプトの作成中にエラーが発生しました。\nどうしますか?\n\nいずれかのボタンをクリックするまでチケットは削除されません。", - "title":"トランスクリプトエラー" + "title":"トランスクリプトエラー", + "noHistory":"このユーザーにはまだトランスクリプトがありません。", + "historyNotSupported":"トランスクリプト履歴は現在HTML Transcriptsのみ対応しています。\nテキストトランスクリプト履歴は今後のバージョンで利用可能になります。" }, "text":{ "messagesTitle":"メッセージ", @@ -298,6 +305,7 @@ "unknownPanel":"不明なパネル", "notInGuild":"サーバー内にいません", "channelRename":"チャンネル名を変更できません", + "channelCategory":"カテゴリを変更できません", "busy":"チケットは使用中です", "permissionError":"権限エラー" }, @@ -321,11 +329,15 @@ "deprecatedTicket":"現在のチャンネルは有効なチケットではありません!旧バージョンのチケットかもしれません!", "notInGuild":"この{0}はDMでは動作しません!サーバー内で再試行してください!", "channelRename":"Discordのレートリミットにより、現在チャンネル名を変更できません。10分以内に自動的に変更されます。", + "channelCategory":"Discordのレート制限のため、チャンネルカテゴリをすぐに変更できません。ボットがオンラインであれば10分以内に自動で変更されます。", "channelRenameSource":"エラーの原因:{0}", "busy":"この{0}は使用できません!\nチケットは現在処理中です。\n\n数秒後にもう一度お試しください!", "closeBeforeMessage":"ユーザーがメッセージを送信する前に、このチケットを閉じたり削除したりすることはできません。", "closeBeforeAdminMessage":"チケット管理者またはサポートメンバーがメッセージを送信する前に、このチケットを閉じたり削除したりすることはできません。", - "unableToCreateTicket":"チケットを作成することはできません。" + "unableToCreateTicket":"チケットを作成することはできません。", + "messageMissing":"インタラクションのメッセージを見つけられませんでした。代わりにコマンド `{0}` を使用してください。", + "stateExpired":"このインタラクションは無効または期限切れです。代わりにコマンド `{0}` を使用してください。Open Ticketの大規模アップデート後にはこのエラーは正常です。", + "panelStateExpired":"このパネルは無効または期限切れです。問題を解決するには `{0}` を使用して新しいパネルを作成してください。Open Ticketの大規模アップデート後にはこのエラーは正常です。" }, "optionInvalidReasons":{ "stringRegex":"値がパターンに一致しません!", @@ -387,6 +399,8 @@ "syntax":"構文", "originalName":"元の名前", "newName":"新しい名前", + "originalCategory":"元のカテゴリ", + "newCategory":"新しいカテゴリ", "until":"期限", "validOptions":"有効なオプション", "validPanels":"有効なパネル", @@ -408,6 +422,8 @@ "participants":"参加者", "yes":"はい", "no":"いいえ", + "accept":"承認", + "cancel":"キャンセル", "option":"オプション", "topic":"トピック", "uptime":"システム稼働時間", @@ -439,6 +455,7 @@ "panelAutoUpdate":"編集時にこのパネルを自動更新しますか?", "ticket":"すぐにチケットを作成します。", "ticketId":"作成したいチケットの識別子。", + "ticketOtherUser":"別のユーザーのチケットを作成します。", "close":"チケットを閉じます。", "delete":"チケットを削除します。", "deleteNoTranscript":"トランスクリプトを作成せずにチケットを削除します。", @@ -504,7 +521,9 @@ "priorityGet":"チケットの優先度を取得します。", "priorityList":"すべてのチケットを優先度ステータス付きで一覧表示します。", "transfer":"チケットの所有権を別のユーザーに転送します。", - "transferUser":"転送先のユーザー。" + "transferUser":"転送先のユーザー。", + "transcripts":"ユーザーのチケットトランスクリプト履歴を表示します。", + "transcriptsUser":"表示するユーザー。" }, "helpMenu":{ "help":"利用可能なすべてのコマンドのリストを表示します。", @@ -594,7 +613,8 @@ "panel":{ "selectTicket":"チケットを選択してください", "selectRole":"ロールを選択してください", - "selectOption":"オプションを選択してください" + "selectOption":"オプションを選択してください", + "selectPriorityLevel":"優先度レベルを選択" }, "priorities":{ "urgent":"緊急", diff --git a/languages/khmer.json b/languages/khmer.json new file mode 100644 index 0000000..c547dbd --- /dev/null +++ b/languages/khmer.json @@ -0,0 +1,628 @@ +{ + "_TRANSLATION":{ + "otversion":"v4.2.0", + "translators":["yuuslokrobjakkroval"], + "lastedited":"25/05/2026", + "language":"Khmer", + "automated":false + }, + "checker":{ + "system":{ + "typeError":"[កំហុស]", + "headerOpenTicket":"OPEN TICKET", + "typeWarning":"[ព្រមាន]", + "typeInfo":"[ព័ត៌មាន]", + "headerConfigChecker":"CONFIG CHECKER", + "headerDescription":"ពិនិត្យមើលកំហុសក្នុងឯកសារកំណត់រចនាសម្ព័ន្ធ!", + "footerError":"បូតនឹងមិនចាប់ផ្តើមទេ រហូតដល់ {0} ទាំងអស់ត្រូវបានជួសជុល!", + "footerWarning":"វាត្រូវបានណែនាំឱ្យជួសជុល {0} ទាំងអស់មុនពេលចាប់ផ្តើម!", + "footerSupport":"ជំនួយ: {0} - ឯកសារ: {1}", + "compactInformation":"ប្រើ {0} សម្រាប់ព័ត៌មានបន្ថែម!", + "dataPath":"ផ្លូវ", + "dataDocs":"ឯកសារ", + "dataMessages":"សារ" + }, + "messages":{ + "stringTooShort":"ខ្សែអក្សរនេះមិនអាចខ្លីជាង {0} តួអក្សរ!", + "stringTooLong":"ខ្សែអក្សរនេះមិនអាចវែងជាង {0} តួអក្សរ!", + "stringLengthInvalid":"ខ្សែអក្សរនេះត្រូវតែមាន {0} តួអក្សរ!", + "stringStartsWith":"ខ្សែអក្សរនេះត្រូវតែចាប់ផ្តើមដោយ {0}!", + "stringEndsWith":"ខ្សែអក្សរនេះត្រូវតែបញ្ចប់ដោយ {0}!", + "stringContains":"ខ្សែអក្សរនេះត្រូវតែមាន {0}!", + "stringChoices":"ខ្សែអក្សរនេះអាចជាតម្លៃដូចខាងក្រោមប៉ុណ្ណោះ: {0}!", + "stringRegex":"ខ្សែអក្សរនេះមិនត្រឹមត្រូវ!", + "stringInvertedContains":"ខ្សែអក្សរនេះមិនត្រូវបានអនុញ្ញាតឱ្យមាន {0}!", + "stringLowercase":"ខ្សែអក្សរនេះត្រូវតែសរសេរជាអក្សរតូចប៉ុណ្ណោះ!", + "stringUppercase":"ខ្សែអក្សរនេះត្រូវតែសរសេរជាអក្សរធំប៉ុណ្ណោះ!", + "stringSpecialCharacters":"ខ្សែអក្សរនេះមិនត្រូវបានអនុញ្ញាតឱ្យមានតួអក្សរពិសេស! (a-z, 0-9 និងដកឃ្លាប៉ុណ្ណោះ)", + "stringNoSpaces":"ខ្សែអក្សរនេះមិនត្រូវបានអនុញ្ញាតឱ្យមានដកឃ្លា!", + "stringCapitalWord":"វាត្រូវបានណែនាំឱ្យពាក្យនីមួយៗក្នុងខ្សែអក្សរនេះចាប់ផ្តើមដោយអក្សរធំ!", + "stringCapitalSentence":"វាមើលទៅថាប្រយោគខ្លះក្នុងខ្សែអក្សរនេះមិនចាប់ផ្តើមដោយអក្សរធំ!", + "stringPunctuation":"វាមើលទៅថាប្រយោគក្នុងខ្សែអក្សរនេះមិនបញ្ចប់ដោយសញ្ញាវណ្ណយុត្តិ!", + + "numberTooShort":"លេខនេះមិនអាចខ្លីជាង {0} តួអក្សរ!", + "numberTooLong":"លេខនេះមិនអាចវែងជាង {0} តួអក្សរ!", + "numberLengthInvalid":"លេខនេះត្រូវតែមាន {0} តួអក្សរ!", + "numberTooSmall":"លេខនេះត្រូវតែយ៉ាងហោចណាស់ {0}!", + "numberTooLarge":"លេខនេះត្រូវតែច្រើនបំផុត {0}!", + "numberNotEqual":"លេខនេះត្រូវតែជា {0}!", + "numberStep":"លេខនេះត្រូវតែជាច្រើនដង {0}!", + "numberStepOffset":"លេខនេះត្រូវតែជាច្រើនដង {0} ចាប់ផ្តើមពី {1}!", + "numberStartsWith":"លេខនេះត្រូវតែចាប់ផ្តើមដោយ {0}!", + "numberEndsWith":"លេខនេះត្រូវតែបញ្ចប់ដោយ {0}!", + "numberContains":"លេខនេះត្រូវតែមាន {0}!", + "numberChoices":"លេខនេះអាចជាតម្លៃដូចខាងក្រោមប៉ុណ្ណោះ: {0}!", + "numberFloat":"លេខនេះមិនអាចជាទសភាគ!", + "numberNegative":"លេខនេះមិនអាចជាអវិជ្ជមាន!", + "numberPositive":"លេខនេះមិនអាចជាវិជ្ជមាន!", + "numberZero":"លេខនេះមិនអាចជាសូន្យ!", + "numberNan":"លេខនេះមិនអាចជា NaN (មិនមែនជាលេខ)!", + "numberInvertedContains":"លេខនេះមិនត្រូវបានអនុញ្ញាតឱ្យមាន {0}!", + + "booleanTrue":"Boolean នេះមិនអាចជា true!", + "booleanFalse":"Boolean នេះមិនអាចជា false!", + + "arrayEmptyDisabled":"អារ៉េនេះមិនត្រូវបានអនុញ្ញាតឱ្យទទេ!", + "arrayEmptyRequired":"អារ៉េនេះត្រូវបានទាមទារឱ្យទទេ!", + "arrayTooShort":"អារ៉េនេះត្រូវការប្រវែងយ៉ាងហោចណាស់ {0}!", + "arrayTooLong":"អារ៉េនេះត្រូវការប្រវែងច្រើនបំផុត {0}!", + "arrayLengthInvalid":"អារ៉េនេះត្រូវការប្រវែង {0}!", + "arrayInvalidTypes":"អារ៉េនេះអាចមានប្រភេទដូចខាងក្រោមប៉ុណ្ណោះ: {0}!", + "arrayDouble":"អារ៉េនេះមិនអនុញ្ញាតឱ្យតម្លៃដូចគ្នា!", + + "discordInvalidId":"នេះជា discord {0} id មិនត្រឹមត្រូវ!", + "discordInvalidIdOptions":"នេះជា discord {0} id មិនត្រឹមត្រូវ! អ្នកក៏អាចប្រើ: {1}!", + "discordInvalidToken":"នេះជា discord token មិនត្រឹមត្រូវ (ក្នុងទម្រង់)!", + "colorInvalid":"នេះជាពណ៌ hex មិនត្រឹមត្រូវ!", + "emojiTooShort":"ខ្សែអក្សរនេះត្រូវការ emoji យ៉ាងហោចណាស់ {0}!", + "emojiTooLong":"ខ្សែអក្សរនេះត្រូវការ emoji ច្រើនបំផុត {0}!", + "emojiCustom":"Emoji នេះមិនអាចជា custom discord emoji!", + "emojiInvalid":"នេះជា emoji មិនត្រឹមត្រូវ!", + "urlInvalid":"URL នេះមិនត្រឹមត្រូវ!", + "urlInvalidHttp":"URL នេះអាចប្រើតែ https:// protocol ប៉ុណ្ណោះ!", + "urlInvalidProtocol":"URL នេះអាចប្រើតែ http:// និង https:// protocols ប៉ុណ្ណោះ!", + "urlInvalidHostname":"URL នេះមាន hostname មិនត្រូវបានអនុញ្ញាត!", + "urlInvalidExtension":"URL នេះមានផ្នែកបន្ថែមមិនត្រឹមត្រូវ! ជ្រើសរើស: {0}!", + "urlInvalidPath":"URL នេះមានផ្លូវមិនត្រឹមត្រូវ!", + "idNotUnique":"id នេះមិនតែមួយ, ប្រើ id ផ្សេងទៀត!", + "idNonExistent":"id {0} មិនមាន!", + + "invalidType":"លក្ខណៈសម្បត្តិនេះត្រូវជាប្រភេទ: {0}!", + "propertyMissing":"លក្ខណៈសម្បត្តិ {0} បាត់ពីវត្ថុនេះ!", + "propertyOptional":"លក្ខណៈសម្បត្តិ {0} ជាជម្រើសសម្រាប់វត្ថុនេះ!", + "objectDisabled":"វត្ថុនេះត្រូវបានបិទ, បើកវាដោយប្រើ {0}!", + "nullInvalid":"លក្ខណៈសម្បត្តិនេះមិនអាចជា null!", + "switchInvalidType":"នេះត្រូវជាប្រភេទដូចខាងក្រោម: {0}!", + "objectSwitchInvalid":"វត្ថុនេះត្រូវជាប្រភេទដូចខាងក្រោម: {0}!", + + "invalidLanguage":"នេះជាភាសាមិនត្រឹមត្រូវ!", + "invalidButton":"ប៊ូតុននេះត្រូវការ {0} ឬ {1} យ៉ាងតិចណាស់!", + "unusedOption":"ជម្រើស {0} មិនត្រូវបានប្រើ!", + "unusedQuestion":"សំណួរ {0} មិនត្រូវបានប្រើ!", + "dropdownOption":"ផ្ទាំងដែលមានម៉ឺនុយទម្លាក់ចុះអាចមានតែជម្រើសប្រភេទ៖ 'ticket', 'role' ឬ 'sub-panel' ប៉ុណ្ណោះ។", + "customInvalidVersion":"កំណែដែលបានបញ្ជាក់ក្នុង config របស់អ្នកមិនត្រូវគ្នា! សូមប្រាកដថាអ្នកបានធ្វើបច្ចុប្បន្នភាព config ទៅកំណែថ្មីបំផុត!" + } + }, + "actions":{ + "buttons":{ + "create":"ចូលមើលសំបុត្រ", + "close":"បិទសំបុត្រ", + "delete":"លុបសំបុត្រ", + "reopen":"បើកសំបុត្រឡើងវិញ", + "claim":"ទទួលសំបុត្រ", + "unclaim":"លែងទទួលសំបុត្រ", + "pin":"ដាក់ម្ជុលសំបុត្រ", + "unpin":"ដកម្ជុលសំបុត្រ", + "clear":"លុបសំបុត្រទាំងអស់", + "helpSwitchSlash":"មើលពាក្យបញ្ជា Slash", + "helpSwitchText":"មើលពាក្យបញ្ជាអក្សរ", + "helpPage":"ទំព័រ {0}", + "withReason":"ជាមួយហេតុផល", + "withoutTranscript":"គ្មាន Transcript", + "blacklistAdd":"បញ្ចូលអ្នកប្រើទៅក្នុងបញ្ជីខ្មៅ", + "blacklistRemove":"ដោះលែងអ្នកប្រើ" + }, + "titles":{ + "created":"បានបង្កើតសំបុត្រ", + "close":"បានបិទសំបុត្រ", + "delete":"បានលុបសំបុត្រ", + "reopen":"បានបើកសំបុត្រឡើងវិញ", + "claim":"បានទទួលសំបុត្រ", + "unclaim":"បានលែងទទួលសំបុត្រ", + "pin":"បានដាក់ម្ជុលសំបុត្រ", + "unpin":"បានដកម្ជុលសំបុត្រ", + "rename":"បានប្តូរឈ្មោះសំបុត្រ", + "move":"បានផ្លាស់ប្តូរសំបុត្រ", + "add":"បានបន្ថែមអ្នកប្រើប្រាស់ទៅសំបុត្រ", + "remove":"បានដកអ្នកប្រើប្រាស់ពីសំបុត្រ", + + "help":"ពាក្យបញ្ជាដែលមាន", + "statsReset":"កំណត់ស្ថិតិឡើងវិញ", + "blacklistAdd":"អ្នកប្រើប្រាស់ត្រូវបានដាក់ក្នុងបញ្ជីខ្មៅ", + "blacklistRemove":"អ្នកប្រើប្រាស់ត្រូវបានដោះលែង", + "blacklistGet":"អ្នកប្រើប្រាស់ក្នុងបញ្ជីខ្មៅ", + "blacklistView":"បញ្ជីខ្មៅបច្ចុប្បន្ន", + "blacklistAddDm":"ត្រូវបានបន្ថែមទៅបញ្ជីខ្មៅ", + "blacklistRemoveDm":"ត្រូវបានដកចេញពីបញ្ជីខ្មៅ", + "clear":"បានសម្អាតសំបុត្រ", + "clearTickets":"សម្អាតសំបុត្រ", + "roles":"តួនាទីបានធ្វើបច្ចុប្បន្នភាព", + + "autoclose":"សំបុត្របានបិទដោយស្វ័យប្រវត្តិ", + "autocloseEnabled":"បានបើក Autoclose", + "autocloseDisabled":"បានបិទ Autoclose", + "autodelete":"សំបុត្របានលុបដោយស្វ័យប្រវត្តិ", + "autodeleteEnabled":"បានបើក Autodelete", + "autodeleteDisabled":"បានបិទ Autodelete", + + "topicSet":"បានប្តូរប្រធានបទ", + "prioritySet":"បានប្តូរអាទិភាព", + "priorityGet":"អាទិភាពសំបុត្រ", + "transfer":"បានផ្ទេរសំបុត្រ", + "transcripts":"ប្រវត្តិ Transcript" + }, + "descriptions":{ + "create":"សំបុត្ររបស់អ្នកត្រូវបានបង្កើត។ ចុចប៊ូតុងខាងក្រោមដើម្បីចូលប្រើ!", + "close":"សំបុត្រត្រូវបានបិទដោយជោគជ័យ!", + "delete":"សំបុត្រត្រូវបានលុបដោយជោគជ័យ!", + "reopen":"សំបុត្រត្រូវបានបើកឡើងវិញដោយជោគជ័យ!", + "claim":"សំបុត្រត្រូវបានទទួលដោយជោគជ័យ!", + "unclaim":"សំបុត្រត្រូវបានលែងទទួលដោយជោគជ័យ!", + "pin":"សំបុត្រត្រូវបានដាក់ម្ជុលដោយជោគជ័យ!", + "unpin":"សំបុត្រត្រូវបានដកម្ជុលដោយជោគជ័យ!", + "rename":"សំបុត្រត្រូវបានប្តូរឈ្មោះទៅ {0} ដោយជោគជ័យ!", + "move":"សំបុត្រត្រូវបានផ្លាស់ប្តូរទៅ {0} ដោយជោគជ័យ!", + "add":"{0} ត្រូវបានបន្ថែមទៅសំបុត្រដោយជោគជ័យ!", + "remove":"{0} ត្រូវបានដកចេញពីសំបុត្រដោយជោគជ័យ!", + + "helpExplanation":"`` => ប៉ារ៉ាម៉ែត្រចាំបាច់\n`[name]` => ប៉ារ៉ាម៉ែត្រស្រេចចិត្ត", + "statsReset":"ស្ថិតិបូតត្រូវបានកំណត់ឡើងវិញដោយជោគជ័យ!", + "statsError":"មិនអាចមើលស្ថិតិសំបុត្រ!\n{0} មិនមែនជាសំបុត្រ!", + "blacklistAdd":"{0} ត្រូវបានដាក់ក្នុងបញ្ជីខ្មៅដោយជោគជ័យ!", + "blacklistRemove":"{0} ត្រូវបានដោះលែងដោយជោគជ័យ!", + "blacklistGetSuccess":"{0} ស្ថិតក្នុងបញ្ជីខ្មៅបច្ចុប្បន្ន!", + "blacklistGetEmpty":"{0} មិនស្ថិតក្នុងបញ្ជីខ្មៅបច្ចុប្បន្ន!", + "blacklistViewEmpty":"មិនទាន់មានអ្នកណាត្រូវបានដាក់ក្នុងបញ្ជីខ្មៅ!", + "blacklistViewTip":"ប្រើ \"/blacklist add\" ដើម្បីដាក់អ្នកប្រើក្នុងបញ្ជីខ្មៅ!", + "clearVerify":"តើអ្នកប្រាកដថាចង់លុបសំបុត្រច្រើន?\nសកម្មភាពនេះមិនអាចត្រឡប់មកវិញ!", + "clearReady":"សំបុត្រ {0} ត្រូវបានលុបដោយជោគជ័យ!", + "rolesEmpty":"មិនមានតួនាទីណាត្រូវបានធ្វើបច្ចុប្បន្នភាព!", + + "autocloseLeave":"សំបុត្រនេះត្រូវបានបិទដោយស្វ័យប្រវត្តិ ដោយសារអ្នកបង្កើតបានចាកចេញពីម៉ាស៊ីនបម្រើ!", + "autocloseTimeout":"សំបុត្រនេះត្រូវបានបិទដោយស្វ័យប្រវត្តិ ដោយសារវាមិនសកម្មលើសពី `{0}h`!", + "autodeleteLeave":"សំបុត្រនេះត្រូវបានលុបដោយស្វ័យប្រវត្តិ ដោយសារអ្នកបង្កើតបានចាកចេញពីម៉ាស៊ីនបម្រើ!", + "autodeleteTimeout":"សំបុត្រនេះត្រូវបានលុបដោយស្វ័យប្រវត្តិ ដោយសារវាមិនសកម្មលើសពី `{0} ថ្ងៃ`!", + "autocloseEnabled":"Autoclose ត្រូវបានបើកក្នុងសំបុត្រនេះ!\nវានឹងត្រូវបានបិទនៅពេលមិនសកម្មលើសពី `{0}h`!", + "autocloseDisabled":"Autoclose ត្រូវបានបិទក្នុងសំបុត្រនេះ!\nវានឹងមិនត្រូវបានបិទដោយស្វ័យប្រវត្តិទៀតទេ!", + "autodeleteEnabled":"Autodelete ត្រូវបានបើកក្នុងសំបុត្រនេះ!\nវានឹងត្រូវបានលុបនៅពេលមិនសកម្មលើសពី `{0} ថ្ងៃ`!", + "autodeleteDisabled":"Autodelete ត្រូវបានបិទក្នុងសំបុត្រនេះ!\nវានឹងមិនត្រូវបានលុបដោយស្វ័យប្រវត្តិទៀតទេ!", + + "ticketMessageLimit":"អ្នកអាចបង្កើតសំបុត្រ {0} ក្នុងពេលតែមួយ!", + "ticketMessageAutoclose":"សំបុត្រនេះនឹងត្រូវបានបិទដោយស្វ័យប្រវត្តិ នៅពេលមិនសកម្ម {0}h!", + "ticketMessageAutodelete":"សំបុត្រនេះនឹងត្រូវបានលុបដោយស្វ័យប្រវត្តិ នៅពេលមិនសកម្ម {0} ថ្ងៃ!", + "panelReady":"Panel មាននៅក្នុងសារបន្ត!\nសារនេះអាចលុបបានហើយ!", + + "topicSet":"ប្រធានបទបណ្តាញត្រូវបានប្តូរដោយ {0} ដោយជោគជ័យ!", + "prioritySet":"អាទិភាពសំបុត្រត្រូវបានប្តូរទៅ {0} ដោយ {1} ដោយជោគជ័យ!", + "priorityGet":"អាទិភាពបច្ចុប្បន្នរបស់សំបុត្រនេះគឺ {0}។", + "transfer":"ភាពជាម្ចាស់សំបុត្រត្រូវបានផ្ទេរពី {0} ទៅ {1} ដោយ {2} ដោយជោគជ័យ!" + }, + "modal":{ + "closePlaceholder":"ហេតុអ្វីអ្នកបិទសំបុត្រនេះ?", + "deletePlaceholder":"ហេតុអ្វីអ្នកលុបសំបុត្រនេះ?", + "reopenPlaceholder":"ហេតុអ្វីអ្នកបើកសំបុត្រនេះឡើងវិញ?", + "claimPlaceholder":"ហេតុអ្វីអ្នកទទួលសំបុត្រនេះ?", + "unclaimPlaceholder":"ហេតុអ្វីអ្នកលែងទទួលសំបុត្រនេះ?", + "pinPlaceholder":"ហេតុអ្វីអ្នកដាក់ម្ជុលសំបុត្រនេះ?", + "unpinPlaceholder":"ហេតុអ្វីអ្នកដកម្ជុលសំបុត្រនេះ?" + }, + "logs":{ + "createLog":"សំបុត្រថ្មីត្រូវបានបង្កើតដោយ {0}!", + "closeLog":"សំបុត្រនេះត្រូវបានបិទដោយ {0}!", + "closeDm":"សំបុត្ររបស់អ្នកត្រូវបានបិទនៅក្នុងម៉ាស៊ីនបម្រើរបស់យើង!", + "deleteLog":"សំបុត្រនេះត្រូវបានលុបដោយ {0}!", + "deleteDm":"សំបុត្ររបស់អ្នកត្រូវបានលុបនៅក្នុងម៉ាស៊ីនបម្រើរបស់យើង!", + "reopenLog":"សំបុត្រនេះត្រូវបានបើកឡើងវិញដោយ {0}!", + "reopenDm":"សំបុត្ររបស់អ្នកត្រូវបានបើកឡើងវិញនៅក្នុងម៉ាស៊ីនបម្រើរបស់យើង!", + "claimLog":"សំបុត្រនេះត្រូវបានទទួលដោយ {0}!", + "claimDm":"សំបុត្ររបស់អ្នកត្រូវបានទទួលនៅក្នុងម៉ាស៊ីនបម្រើរបស់យើង!", + "unclaimLog":"សំបុត្រនេះត្រូវបានលែងទទួលដោយ {0}!", + "unclaimDm":"សំបុត្ររបស់អ្នកត្រូវបានលែងទទួលនៅក្នុងម៉ាស៊ីនបម្រើរបស់យើង!", + "pinLog":"សំបុត្រនេះត្រូវបានដាក់ម្ជុលដោយ {0}!", + "pinDm":"សំបុត្ររបស់អ្នកត្រូវបានដាក់ម្ជុលនៅក្នុងម៉ាស៊ីនបម្រើរបស់យើង!", + "unpinLog":"សំបុត្រនេះត្រូវបានដកម្ជុលដោយ {0}!", + "unpinDm":"សំបុត្ររបស់អ្នកត្រូវបានដកម្ជុលនៅក្នុងម៉ាស៊ីនបម្រើរបស់យើង!", + "renameLog":"សំបុត្រនេះត្រូវបានប្តូរឈ្មោះទៅ {0} ដោយ {1}!", + "renameDm":"សំបុត្ររបស់អ្នកត្រូវបានប្តូរឈ្មោះទៅ {0} នៅក្នុងម៉ាស៊ីនបម្រើរបស់យើង!", + "moveLog":"សំបុត្រនេះត្រូវបានផ្លាស់ប្តូរទៅ {0} ដោយ {1}!", + "moveDm":"សំបុត្ររបស់អ្នកត្រូវបានផ្លាស់ប្តូរទៅ {0} នៅក្នុងម៉ាស៊ីនបម្រើរបស់យើង!", + "addLog":"{0} ត្រូវបានបន្ថែមទៅសំបុត្រនេះដោយ {1}!", + "addDm":"{0} ត្រូវបានបន្ថែមទៅសំបុត្ររបស់អ្នកនៅក្នុងម៉ាស៊ីនបម្រើរបស់យើង!", + "removeLog":"{0} ត្រូវបានដកចេញពីសំបុត្រនេះដោយ {1}!", + "removeDm":"{0} ត្រូវបានដកចេញពីសំបុត្ររបស់អ្នកនៅក្នុងម៉ាស៊ីនបម្រើរបស់យើង!", + + "blacklistAddLog":"{0} ត្រូវបានដាក់ក្នុងបញ្ជីខ្មៅដោយ {1}!", + "blacklistRemoveLog":"{0} ត្រូវបានដកចេញពីបញ្ជីខ្មៅដោយ {1}!", + "blacklistAddDm":"អ្នកត្រូវបានដាក់ក្នុងបញ្ជីខ្មៅក្នុងម៉ាស៊ីនបម្រើរបស់យើង!\nចាប់ពីពេលនេះ អ្នកមិនអាចបង្កើតសំបុត្រ!", + "blacklistRemoveDm":"អ្នកត្រូវបានដកចេញពីបញ្ជីខ្មៅក្នុងម៉ាស៊ីនបម្រើរបស់យើង!\nឥឡូវអ្នកអាចបង្កើតសំបុត្របានម្តងទៀត!", + "clearLog":"សំបុត្រ {0} ត្រូវបានលុបដោយ {1}!", + + "transferLog":"ភាពជាម្ចាស់សំបុត្រនេះត្រូវបានផ្ទេរពី {0} ទៅ {1} ដោយ {2}!", + "transferDm":"ភាពជាម្ចាស់សំបុត្ររបស់អ្នកត្រូវបានផ្ទេរពី {0} ទៅ {1} នៅក្នុងម៉ាស៊ីនបម្រើរបស់យើង!", + "prioritySetLog":"អាទិភាពសំបុត្រនេះត្រូវបានប្តូរទៅ {0} ដោយ {1}!", + "prioritySetDm":"អាទិភាពសំបុត្ររបស់អ្នកត្រូវបានប្តូរទៅ {0} នៅក្នុងម៉ាស៊ីនបម្រើរបស់យើង!", + "roleUpdateLog":"{0} បានធ្វើបច្ចុប្បន្នភាពតួនាទីរបស់ខ្លួន!", + "roleUpdateDm":"តួនាទីរបស់អ្នកក្នុងម៉ាស៊ីនបម្រើរបស់យើងត្រូវបានធ្វើបច្ចុប្បន្នភាព!", + "topicSetLog":"អាទិភាពសំបុត្រនេះត្រូវបានកំណត់ទៅ {0} ដោយ {1}។", + "topicSetDm":"អាទិភាពសំបុត្ររបស់អ្នកត្រូវបានកំណត់ទៅ {0}។" + } + }, + "transcripts":{ + "success":{ + "visit":"ចូលមើល Transcript", + "ready":"Transcript ត្រូវបានបង្កើត", + "textFileDescription":"នេះជា transcript អក្សររបស់សំបុត្រដែលបានលុប!", + "htmlProgress":"សូមរង់ចាំ ខណៈ html transcript កំពុងត្រូវបានដំណើរការ...", + + "createdChannel":"transcript {0} ថ្មីត្រូវបានបង្កើតក្នុងម៉ាស៊ីនបម្រើ!", + "createdCreator":"transcript {0} ថ្មីត្រូវបានបង្កើតសម្រាប់សំបុត្ររបស់អ្នក!", + "createdParticipant":"transcript {0} ថ្មីត្រូវបានបង្កើតក្នុងសំបុត្រដែលអ្នកបានចូលរួម!", + "createdActiveAdmin":"transcript {0} ថ្មីត្រូវបានបង្កើតក្នុងសំបុត្រដែលអ្នកបានចូលរួមជាអ្នកគ្រប់គ្រង!", + "createdEveryAdmin":"transcript {0} ថ្មីត្រូវបានបង្កើតក្នុងសំបុត្រដែលអ្នកជាអ្នកគ្រប់គ្រង!", + "createdOther":"transcript {0} ថ្មីត្រូវបានបង្កើត!" + }, + "errors":{ + "retry":"ព្យាយាមម្តងទៀត", + "continue":"លុបដោយគ្មាន Transcript", + "backup":"បង្កើត Transcript បម្រុង", + "error":"មានបញ្ហាខ្លះក្នុងការបង្កើត transcript។\nអ្នកចង់ធ្វើអ្វី?\n\nសំបុត្រនេះនឹងមិនត្រូវបានលុបទេ រហូតអ្នកចុចប៊ូតុងណាមួយ។", + "title":"កំហុស Transcript", + "noHistory":"អ្នកប្រើនេះមិនទាន់មាន transcript ទេ។", + "historyNotSupported":"ប្រវត្តិ transcript បច្ចុប្បន្នគាំទ្រតែ HTML Transcripts ប៉ុណ្ណោះ។\nប្រវត្តិ transcript អត្ថបទនឹងមាននៅក្នុងកំណែអនាគត។" + }, + "text":{ + "messagesTitle":"សារ", + "embedTitle":"EMBED", + "fileTitle":"ឯកសារ", + "fieldsTitle":"វាល", + "reactionsTitle":"ប្រតិកម្ម", + "statsTitle":"ស្ថិតិ", + "emptyContent":"<មាតិកាទទេ>", + "noTitle":"<គ្មានចំណងជើង>", + "noDesc":"<គ្មានការពិពណ៌នា>" + } + }, + "errors":{ + "titles":{ + "internalError":"កំហុសផ្ទៃក្នុង", + "optionMissing":"ជម្រើសពាក្យបញ្ជាបាត់", + "optionInvalid":"ជម្រើសពាក្យបញ្ជាមិនត្រឹមត្រូវ", + "unknownCommand":"ពាក្យបញ្ជាមិនស្គាល់", + "noPermissions":"គ្មានសិទ្ធិ", + "unknownTicket":"សំបុត្រមិនស្គាល់", + "deprecatedTicket":"សំបុត្រហួសសម័យ", + "unknownOption":"ជម្រើសមិនស្គាល់", + "unknownPanel":"Panel មិនស្គាល់", + "notInGuild":"មិននៅក្នុងម៉ាស៊ីនបម្រើ", + "channelRename":"មិនអាចប្តូរឈ្មោះបណ្តាញ", + "channelCategory":"មិនអាចប្តូរប្រភេទបាន", + "busy":"សំបុត្រកំពុងដំណើរការ", + "permissionError":"កំហុសសិទ្ធិ" + }, + "descriptions":{ + "askForInfo":"ទំនាក់ទំនងម្ចាស់បូតនេះសម្រាប់ព័ត៌មានបន្ថែម!", + "askForInfoResolve":"ទំនាក់ទំនងម្ចាស់បូតនេះ ប្រសិនបើបញ្ហានេះមិនដោះស្រាយបន្ទាប់ពីព្យាយាមមួយចំនួន។", + "internalError":"បរាជ័យក្នុងការឆ្លើយតប {0} ដោយសារកំហុសផ្ទៃក្នុង!", + "optionMissing":"ប៉ារ៉ាម៉ែត្រចាំបាច់ខ្វះក្នុងពាក្យបញ្ជានេះ!", + "optionInvalid":"ប៉ារ៉ាម៉ែត្រក្នុងពាក្យបញ្ជានេះមិនត្រឹមត្រូវ!", + "optionInvalidChoose":"ជ្រើសរើសរវាង", + "unknownCommand":"សូមចូលមើលម៉ឺនុយជំនួយសម្រាប់ព័ត៌មានបន្ថែម!", + "noPermissions":"អ្នកមិនត្រូវបានអនុញ្ញាតឱ្យប្រើ {0} នេះ!", + "noPermissionsList":"សិទ្ធិដែលត្រូវការ: (មួយក្នុងចំណោម)", + "noPermissionsCooldown":"អ្នកមិនត្រូវបានអនុញ្ញាតឱ្យប្រើ {0} នេះ ដោយសារអ្នកមានរយៈពេលត្រជាក់!", + "noPermissionsBlacklist":"អ្នកមិនត្រូវបានអនុញ្ញាតឱ្យប្រើ {0} នេះ ដោយសារអ្នកត្រូវបានដាក់ក្នុងបញ្ជីខ្មៅ!", + "noPermissionsLimitGlobal":"អ្នកមិនអាចបង្កើតសំបុត្រ ដោយសារម៉ាស៊ីនបម្រើឈានដល់ដែនកំណត់សំបុត្រច្រើនបំផុត!", + "noPermissionsLimitGlobalUser":"អ្នកមិនអាចបង្កើតសំបុត្រ ដោយសារអ្នកឈានដល់ដែនកំណត់សំបុត្រច្រើនបំផុត!", + "noPermissionsLimitOption":"អ្នកមិនអាចបង្កើតសំបុត្រ ដោយសារម៉ាស៊ីនបម្រើឈានដល់ដែនកំណត់សំបុត្រច្រើនបំផុតសម្រាប់ជម្រើសនេះ!", + "noPermissionsLimitOptionUser":"អ្នកមិនអាចបង្កើតសំបុត្រ ដោយសារអ្នកឈានដល់ដែនកំណត់សំបុត្រច្រើនបំផុតសម្រាប់ជម្រើសនេះ!", + "unknownTicket":"សូមសាកល្បងពាក្យបញ្ជានេះម្តងទៀតក្នុងសំបុត្រត្រឹមត្រូវ!", + "deprecatedTicket":"បណ្តាញបច្ចុប្បន្នមិនមែនជាសំបុត្រត្រឹមត្រូវ! វាប្រហែលជាសំបុត្រពី Open Ticket កំណែចាស់!", + "notInGuild":"{0} នេះមិនដំណើរការក្នុង DM! សូមសាកល្បងម្តងទៀតក្នុងម៉ាស៊ីនបម្រើ!", + "channelRename":"ដោយសារ discord ratelimits, វាមិនអាចទៅបានសម្រាប់បូតក្នុងការប្តូរឈ្មោះបណ្តាញ។ បណ្តាញនឹងត្រូវបានប្តូរឈ្មោះដោយស្វ័យប្រវត្តិ ក្នុងរយៈពេល 10 នាទី ប្រសិនបើបូតមិនបានចាប់ផ្តើមឡើងវិញ។", + "channelCategory":"ដោយសារកំណត់អត្រារបស់ Discord មិនអាចប្តូរប្រភេទឆានែលភ្លាមៗបានទេ។ វានឹងត្រូវបានប្តូរដោយស្វ័យប្រវត្តិក្នុងរយៈពេល 10 នាទី ប្រសិនបើ bot នៅតែអនឡាញ។", + "channelRenameSource":"ប្រភពកំហុសនេះគឺ: {0}", + "busy":"មិនអាចប្រើ {0} នេះ!\nសំបុត្រកំពុងត្រូវបានដំណើរការដោយបូត។\n\nសូមព្យាយាមម្តងទៀតក្នុងពីរបីវិនាទី!", + "closeBeforeMessage":"សំបុត្រនេះមិនអាចបិទ/លុបមុនមានសារពីអ្នកប្រើ។", + "closeBeforeAdminMessage":"សំបុត្រនេះមិនអាចបិទ/លុបមុនមានសារពីអ្នកគ្រប់គ្រងសំបុត្រ ឬសមាជិកជំនួយ។", + "unableToCreateTicket":"អ្នកមិនអាចបង្កើតសំបុត្រ។", + "messageMissing":"មិនអាចរកសារ interaction បានទេ។ សូមប្រើពាក្យបញ្ជា `{0}` ជំនួស។", + "stateExpired":"Interaction នេះមិនមានសុពលភាពទៀតទេ ឬបានផុតកំណត់។ សូមប្រើពាក្យបញ្ជា `{0}` ជំនួស។ វាជារឿងធម្មតាក្រោយការអាប់ដេតធំរបស់ Open Ticket។", + "panelStateExpired":"ផ្ទាំងនេះមិនមានសុពលភាពទៀតទេ ឬបានផុតកំណត់។ សូមបង្កើតផ្ទាំងថ្មីដោយប្រើ `{0}` ដើម្បីដោះស្រាយបញ្ហា។ វាជារឿងធម្មតាក្រោយការអាប់ដេតធំរបស់ Open Ticket។" + }, + "optionInvalidReasons":{ + "stringRegex":"តម្លៃមិនត្រូវនឹងលំនាំ!", + "stringMinLength":"តម្លៃត្រូវការយ៉ាងហោចណាស់ {0} តួអក្សរ!", + "stringMaxLength":"តម្លៃត្រូវការច្រើនបំផុត {0} តួអក្សរ!", + "numberInvalid":"លេខមិនត្រឹមត្រូវ!", + "numberMin":"លេខត្រូវការយ៉ាងហោចណាស់ {0}!", + "numberMax":"លេខត្រូវការច្រើនបំផុត {0}!", + "numberDecimal":"លេខមិនត្រូវបានអនុញ្ញាតជាទសភាគ!", + "numberNegative":"លេខមិនត្រូវបានអនុញ្ញាតជាអវិជ្ជមាន!", + "numberPositive":"លេខមិនត្រូវបានអនុញ្ញាតជាវិជ្ជមាន!", + "numberZero":"លេខមិនត្រូវបានអនុញ្ញាតជាសូន្យ!", + "channelNotFound":"រកបណ្តាញមិនឃើញ!", + "userNotFound":"រកអ្នកប្រើប្រាស់មិនឃើញ!", + "roleNotFound":"រកតួនាទីមិនឃើញ!", + "memberNotFound":"រកអ្នកប្រើប្រាស់មិនឃើញ!", + "mentionableNotFound":"រកអ្នកប្រើប្រាស់ ឬតួនាទីមិនឃើញ!", + "channelType":"ប្រភេទបណ្តាញមិនត្រឹមត្រូវ!", + "notInGuild":"ជម្រើសនេះទាមទារឱ្យអ្នកនៅក្នុងម៉ាស៊ីនបម្រើ!" + }, + "permissions":{ + "developer":"អ្នកត្រូវជា developer របស់បូត។", + "owner":"អ្នកត្រូវជាម្ចាស់ម៉ាស៊ីនបម្រើ។", + "admin":"អ្នកត្រូវជា admin ម៉ាស៊ីនបម្រើ។", + "moderator":"អ្នកត្រូវជាអ្នកសម្រុះសម្រួល។", + "support":"អ្នកត្រូវនៅក្នុងក្រុមជំនួយ។", + "member":"អ្នកត្រូវជាសមាជិក។", + "discord-administrator":"អ្នកត្រូវមានសិទ្ធិ `ADMINISTRATOR`។" + }, + "actionInvalid":{ + "close":"សំបុត្របានបិទហើយ!", + "reopen":"សំបុត្រមិនទាន់បិទ!", + "claim":"សំបុត្របានទទួលហើយ!", + "unclaim":"សំបុត្រមិនទាន់ត្រូវបានទទួល!", + "pin":"សំបុត្របានដាក់ម្ជុលហើយ!", + "unpin":"សំបុត្រមិនទាន់ត្រូវបានដាក់ម្ជុល!", + "add":"អ្នកប្រើប្រាស់នេះអាចចូលប្រើសំបុត្រហើយ!", + "remove":"មិនអាចដកអ្នកប្រើប្រាស់នេះចេញពីសំបុត្រ!" + } + }, + "params":{ + "uppercase":{ + "ticket":"សំបុត្រ", + "tickets":"សំបុត្រ", + "reason":"មូលហេតុ", + "creator":"អ្នកបង្កើត", + "remaining":"ពេលវេលានៅសល់", + "added":"បន្ថែម", + "removed":"ដក", + "filter":"តម្រង", + "method":"វិធីសាស្ត្រ", + "type":"ប្រភេទ", + "blacklisted":"ក្នុងបញ្ជីខ្មៅ", + "panel":"Panel", + "command":"ពាក្យបញ្ជា", + "system":"ប្រព័ន្ធ", + "true":"ពិត", + "false":"មិនពិត", + "syntax":"វាក្យសម្ព័ន្ធ", + "originalName":"ឈ្មោះដើម", + "newName":"ឈ្មោះថ្មី", + "originalCategory":"ប្រភេទដើម", + "newCategory":"ប្រភេទថ្មី", + "until":"រហូតដល់", + "validOptions":"ជម្រើសត្រឹមត្រូវ", + "validPanels":"Panel ត្រឹមត្រូវ", + "autoclose":"Autoclose", + "autodelete":"Autodelete", + "startupDate":"កាលបរិច្ឆេទចាប់ផ្តើម", + "version":"កំណែ", + "name":"ឈ្មោះ", + "role":"តួនាទី", + "status":"ស្ថានភាព", + "claimed":"ទទួលហើយ", + "pinned":"ដាក់ម្ជុលហើយ", + "creationDate":"កាលបរិច្ឆេទបង្កើត", + + "noone":"គ្មាននរណា", + "open":"បើក", + "closed":"បិទ", + "priority":"អាទិភាព", + "participants":"អ្នកចូលរួម", + "yes":"បាទ/ចាស", + "no":"ទេ", + "accept":"យល់ព្រម", + "cancel":"បោះបង់", + "option":"ជម្រើស", + "topic":"ប្រធានបទ", + "uptime":"ពេលដំណើរការប្រព័ន្ធ", + "messages":"សារ", + "embeds":"Embeds", + "files":"ឯកសារ", + "components":"ធាតុ", + "cooldown":"រយៈពេលត្រជាក់", + "maxTickets":"សំបុត្រច្រើនបំផុត", + "admins":"អ្នកគ្រប់គ្រង", + "roles":"តួនាទី", + "size":"ទំហំ" + }, + "lowercase":{ + "text":"អក្សរ", + "html":"html", + "command":"ពាក្យបញ្ជា", + "modal":"modal", + "button":"ប៊ូតុង", + "dropdown":"dropdown", + "method":"វិធីសាស្ត្រ" + } + }, + "commands":{ + "reason":"បញ្ជាក់មូលហេតុស្រេចចិត្តដែលនឹងបង្ហាញក្នុង logs។", + "help":"ទទួលបានបញ្ជីពាក្យបញ្ជាដែលមាន។", + "panel":"បង្ហាញសារជាមួយ dropdown ឬ buttons (សម្រាប់ការបង្កើតសំបុត្រ)។", + "panelId":"អ្នកកំណត់អត្តសញ្ញាណ panel ដែលអ្នកចង់បង្ហាញ។", + "panelAutoUpdate":"តើអ្នកចង់ panel នេះធ្វើបច្ចុប្បន្នភាពដោយស្វ័យប្រវត្តិ នៅពេលកែប្រែ?", + "ticket":"បង្កើតសំបុត្រភ្លាមៗ។", + "ticketId":"អ្នកកំណត់អត្តសញ្ញាណសំបុត្រដែលអ្នកចង់បង្កើត។", + "ticketOtherUser":"បង្កើតសំបុត្រសម្រាប់អ្នកប្រើផ្សេងទៀត។", + "close":"បិទសំបុត្រ។", + "delete":"លុបសំបុត្រ។", + "deleteNoTranscript":"លុបសំបុត្រនេះដោយគ្មានការបង្កើត transcript។", + "reopen":"បើកសំបុត្រឡើងវិញ។", + "claim":"ទទួលសំបុត្រ។", + "claimUser":"ទទួលសំបុត្រនេះទៅឱ្យនរណាម្នាក់ផ្សេងទៀត ជំនួសអ្នក។", + "unclaim":"លែងទទួលសំបុត្រ។", + "pin":"ដាក់ម្ជុលសំបុត្រ។", + "unpin":"ដកម្ជុលសំបុត្រ។", + + "move":"ផ្លាស់ប្តូរសំបុត្រ។", + "moveId":"អ្នកកំណត់អត្តសញ្ញាណជម្រើសដែលអ្នកចង់ផ្លាស់ប្តូរទៅ។", + "rename":"ប្តូរឈ្មោះសំបុត្រ។", + "renameName":"ឈ្មោះថ្មីសម្រាប់សំបុត្រនេះ។", + "add":"បន្ថែមអ្នកប្រើប្រាស់ទៅសំបុត្រ។", + "addUser":"អ្នកប្រើប្រាស់ដែលត្រូវបន្ថែម។", + "remove":"ដកអ្នកប្រើប្រាស់ចេញពីសំបុត្រ។", + "removeUser":"អ្នកប្រើប្រាស់ដែលត្រូវដក។", + + "blacklist":"គ្រប់គ្រងបញ្ជីខ្មៅសំបុត្រ។", + "blacklistView":"មើលបញ្ជីបញ្ជីខ្មៅបច្ចុប្បន្ន។", + "blacklistAdd":"បន្ថែមអ្នកប្រើប្រាស់ទៅបញ្ជីខ្មៅ។", + "blacklistRemove":"ដកអ្នកប្រើប្រាស់ចេញពីបញ្ជីខ្មៅ។", + "blacklistGet":"ទទួលបានព័ត៌មានលម្អិតពីអ្នកប្រើប្រាស់ក្នុងបញ្ជីខ្មៅ។", + "blacklistGetUser":"អ្នកប្រើប្រាស់ដែលត្រូវទទួលព័ត៌មានលម្អិត។", + "stats":"មើលស្ថិតិពីបូត, សមាជិក ឬសំបុត្រ។", + "statsReset":"កំណត់ស្ថិតិបូតទាំងអស់ឡើងវិញ (ចាប់ផ្តើមរាប់ពីសូន្យ)។", + "statsGlobal":"មើលស្ថិតិសរុប។", + "statsUser":"មើលស្ថិតិពីអ្នកប្រើប្រាស់ក្នុងម៉ាស៊ីនបម្រើ។", + "statsUserUser":"អ្នកប្រើប្រាស់ដែលត្រូវមើល។", + "statsTicket":"មើលស្ថិតិពីសំបុត្រក្នុងម៉ាស៊ីនបម្រើ។", + "statsTicketTicket":"សំបុត្រដែលត្រូវមើល។", + + "clear":"លុបសំបុត្រច្រើនក្នុងពេលតែមួយ។", + "clearFilter":"តម្រងសម្រាប់ការសម្អាតសំបុត្រ។", + "clearFilters":{ + "all":"ទាំងអស់", + "open":"បើក", + "close":"បិទ", + "claim":"ទទួលហើយ", + "unclaim":"មិនទាន់ទទួល", + "pin":"ដាក់ម្ជុលហើយ", + "unpin":"មិនទាន់ដាក់ម្ជុល", + "autoclose":"បិទដោយស្វ័យប្រវត្តិ" + }, + + "autoclose":"គ្រប់គ្រង autoclose ក្នុងសំបុត្រ។", + "autocloseDisable":"បិទ autoclose ក្នុងសំបុត្រនេះ។", + "autocloseEnable":"បើក autoclose ក្នុងសំបុត្រនេះ។", + "autocloseEnableTime":"ចំនួនម៉ោងដែលសំបុត្រនេះត្រូវមិនសកម្ម ដើម្បីបិទ។", + "autodelete":"គ្រប់គ្រង autodelete ក្នុងសំបុត្រ។", + "autodeleteDisable":"បិទ autodelete ក្នុងសំបុត្រនេះ។", + "autodeleteEnable":"បើក autodelete ក្នុងសំបុត្រនេះ។", + "autodeleteEnableTime":"ចំនួនថ្ងៃដែលសំបុត្រនេះត្រូវមិនសកម្ម ដើម្បីលុប។", + + "topic":"គ្រប់គ្រងប្រធានបទបណ្តាញសំបុត្រ។", + "topicSet":"កំណត់ប្រធានបទបណ្តាញសំបុត្រ។", + "topicValue":"ប្រធានបទថ្មីរបស់បណ្តាញ។", + "topicList":"ទទួលបានបញ្ជីសំបុត្រទាំងអស់ ជាមួយប្រធានបទ និងស្ថិតិ។", + "priority":"គ្រប់គ្រងអាទិភាពសំបុត្រ។", + "prioritySet":"កំណត់អាទិភាពសំបុត្រ។", + "priorityValue":"អាទិភាពរបស់បណ្តាញ។", + "priorityGet":"ទទួលបានអាទិភាពសំបុត្រ។", + "priorityList":"ទទួលបានបញ្ជីសំបុត្រទាំងអស់ ជាមួយស្ថានភាពអាទិភាព។", + "transfer":"ផ្ទេរភាពជាម្ចាស់សំបុត្រពីអ្នកប្រើប្រាស់ម្នាក់ទៅម្នាក់ទៀត។", + "transferUser":"អ្នកប្រើប្រាស់ដែលត្រូវផ្ទេរទៅ។", + "transcripts":"មើលប្រវត្តិ transcript សំបុត្ររបស់អ្នកប្រើ។", + "transcriptsUser":"អ្នកប្រើដែលត្រូវមើល។" + }, + "helpMenu":{ + "help":"ទទួលបានបញ្ជីពាក្យបញ្ជាដែលមាន។", + "ticket":"បង្កើតសំបុត្រភ្លាមៗ។", + "close":"បិទសំបុត្រ, វិធីនេះបិទការសរសេរក្នុងបណ្តាញ។", + "delete":"លុបសំបុត្រ, វិធីនេះបង្កើត transcript នៅពេលបើក។", + "reopen":"បើកសំបុត្រឡើងវិញ, វិធីនេះបើកការសរសេរក្នុងបណ្តាញម្តងទៀត។", + "pin":"ដាក់ម្ជុលសំបុត្រ។ វានឹងផ្លាស់សំបុត្រទៅខាងលើ ហើយបន្ថែម '📌' ទៅឈ្មោះ។", + "unpin":"ដកម្ជុលសំបុត្រ។ សំបុត្រនឹងនៅតំណែងដដែល ប៉ុន្តែនឹងបាត់ '📌'។", + "move":"ផ្លាស់ប្តូរសំបុត្រ។ វានឹងប្តូរប្រភេទរបស់សំបុត្រ។", + "rename":"ប្តូរឈ្មោះសំបុត្រ។ វានឹងប្តូរឈ្មោះបណ្តាញរបស់សំបុត្រ។", + "claim":"ទទួលសំបុត្រ។ ដោយវិធីនេះ អ្នកអាចប្រាប់ក្រុមរបស់អ្នកថាអ្នកកំពុងដោះស្រាយសំបុត្រនេះ។", + "unclaim":"លែងទទួលសំបុត្រ។ ដោយវិធីនេះ អ្នកអាចប្រាប់ក្រុមរបស់អ្នកថាសំបុត្រនេះទំនេរ។", + "add":"បន្ថែមអ្នកប្រើប្រាស់ទៅសំបុត្រ។ វានឹងអនុញ្ញាតឱ្យអ្នកប្រើប្រាស់អាននិងសរសេរក្នុងសំបុត្រ។", + "remove":"ដកអ្នកប្រើប្រាស់ចេញពីសំបុត្រ។ វានឹងដកសិទ្ធិអាននិងសរសេររបស់អ្នកប្រើប្រាស់ក្នុងសំបុត្រ។", + "panel":"បង្ហាញសារជាមួយ dropdown ឬ buttons (សម្រាប់ការបង្កើតសំបុត្រ)។", + "blacklistView":"មើលបញ្ជីបញ្ជីខ្មៅបច្ចុប្បន្ន។", + "blacklistAdd":"បន្ថែមអ្នកប្រើប្រាស់ទៅបញ្ជីខ្មៅ។", + "blacklistRemove":"ដកអ្នកប្រើប្រាស់ចេញពីបញ្ជីខ្មៅ។", + "blacklistGet":"ទទួលបានព័ត៌មានលម្អិតពីអ្នកប្រើប្រាស់ក្នុងបញ្ជីខ្មៅ។", + "statsGlobal":"មើលស្ថិតិសរុប។", + "statsTicket":"មើលស្ថិតិពីសំបុត្រក្នុងម៉ាស៊ីនបម្រើ។", + "statsUser":"មើលស្ថិតិពីអ្នកប្រើប្រាស់ក្នុងម៉ាស៊ីនបម្រើ។", + "statsReset":"កំណត់ស្ថិតិបូតទាំងអស់ឡើងវិញ (ចាប់ផ្តើមរាប់ពីសូន្យ)។", + "autocloseDisable":"បិទ autoclose ក្នុងសំបុត្រនេះ។", + "autocloseEnable":"បើក autoclose ក្នុងសំបុត្រនេះ។", + "autodeleteDisable":"បិទ autodelete ក្នុងសំបុត្រនេះ។", + "autodeleteEnable":"បើក autodelete ក្នុងសំបុត្រនេះ។", + "categories":{ + "general":"ពាក្យបញ្ជាទូទៅ", + "basicTicket":"ពាក្យបញ្ជាសំបុត្រមូលដ្ឋាន", + "advancedTicket":"ពាក្យបញ្ជាសំបុត្រកម្រិតខ្ពស់", + "userTicket":"ពាក្យបញ្ជាសំបុត្រអ្នកប្រើប្រាស់", + "admin":"ពាក្យបញ្ជា Admin", + "advanced":"ពាក្យបញ្ជាកម្រិតខ្ពស់", + "extra":"ពាក្យបញ្ជាបន្ថែម" + } + }, + "stats":{ + "scopes":{ + "global":"ស្ថិតិសរុប", + "system":"ស្ថិតិប្រព័ន្ធ", + "user":"ស្ថិតិអ្នកប្រើប្រាស់", + "ticket":"ស្ថិតិសំបុត្រ", + "participants":"អ្នកចូលរួម", + "messages":"សារ" + }, + "properties":{ + "ticketsCreated":"សំបុត្របានបង្កើត", + "ticketsClosed":"សំបុត្របានបិទ", + "ticketsDeleted":"សំបុត្របានលុប", + "ticketsReopened":"សំបុត្របានបើកឡើងវិញ", + "ticketsAutoclosed":"សំបុត្របានបិទដោយស្វ័យប្រវត្តិ", + "ticketsClaimed":"សំបុត្របានទទួល", + "ticketsPinned":"សំបុត្របានដាក់ម្ជុល", + "ticketsMoved":"សំបុត្របានផ្លាស់ប្តូរ", + "usersBlacklisted":"អ្នកប្រើប្រាស់ក្នុងបញ្ជីខ្មៅ", + "transcriptsCreated":"Transcript បានបង្កើត", + "ticketsAutodeleted":"សំបុត្របានលុបដោយស្វ័យប្រវត្តិ", + "ticketsTransferred":"សំបុត្របានផ្ទេរ", + "ticketVolume":"ចំនួនសំបុត្រ", + "averageTickets":"ចំនួនសំបុត្រជាមធ្យម/អ្នកប្រើ", + "currentTickets":"សំបុត្របច្ចុប្បន្ន", + "age":"អាយុសំបុត្រ", + "responseTime":"ពេលឆ្លើយតប", + "resolutionTime":"ពេលដោះស្រាយ", + "createdOn":"បង្កើតនៅ", + "createdBy":"បង្កើតដោយ", + "closedOn":"បិទនៅ", + "closedBy":"បិទដោយ", + "claimedOn":"ទទួលនៅ", + "claimedBy":"ទទួលដោយ", + "pinnedOn":"ដាក់ម្ជុលនៅ", + "pinnedBy":"ដាក់ម្ជុលដោយ", + "deletedOn":"លុបនៅ", + "deletedBy":"លុបដោយ" + }, + "roles":{ + "developer":"Developer", + "serverOwner":"ម្ចាស់ម៉ាស៊ីនបម្រើ", + "serverAdmin":"Admin ម៉ាស៊ីនបម្រើ", + "moderator":"ក្រុមអ្នកសម្រុះសម្រួល", + "support":"ក្រុមជំនួយ", + "member":"សមាជិក" + } + }, + "panel":{ + "selectTicket":"ជ្រើសរើសសំបុត្ររបស់អ្នក", + "selectRole":"ជ្រើសរើសតួនាទីរបស់អ្នក", + "selectOption":"ជ្រើសរើសជម្រើសរបស់អ្នក", + "selectPriorityLevel":"ជ្រើសរើសកម្រិតអាទិភាព" + }, + "priorities":{ + "urgent":"បន្ទាន់ខ្លាំង", + "veryHigh":"ខ្ពស់ណាស់", + "high":"ខ្ពស់", + "normal":"ធម្មតា", + "low":"ទាប", + "veryLow":"ទាបណាស់", + "none":"គ្មាន" + } +} \ No newline at end of file diff --git a/languages/korean.json b/languages/korean.json index 00e2d6b..6293826 100644 --- a/languages/korean.json +++ b/languages/korean.json @@ -1,8 +1,8 @@ { "_TRANSLATION":{ - "otversion":"v4.1.3", + "otversion":"v4.2.0", "translators":["HanumeshGupta","ChatGPT"], - "lastedited":"16/02/2026", + "lastedited":"25/05/2026", "language":"Korean", "automated":true }, @@ -99,7 +99,7 @@ "invalidButton":"이 버튼은 최소한 {0} 또는 {1}을(를) 가지고 있어야 합니다!", "unusedOption":"옵션 {0}은(는) 어디에서도 사용되지 않습니다!", "unusedQuestion":"질문 {0}은(는) 어디에서도 사용되지 않습니다!", - "dropdownOption":"드롭다운이 활성화된 패널은 'ticket' 유형의 옵션만 포함할 수 있습니다!", + "dropdownOption":"드롭다운이 있는 패널에는 'ticket', 'role' 또는 'sub-panel' 유형의 옵션만 포함할 수 있습니다.", "customInvalidVersion":"구성 파일에 지정된 버전이 일치하지 않습니다! 구성 파일을 최신 버전으로 업데이트했는지 확인하세요!" } }, @@ -118,7 +118,9 @@ "helpSwitchText":"텍스트 명령어 보기", "helpPage":"페이지 {0}", "withReason":"이유와 함께", - "withoutTranscript":"기록 없이" + "withoutTranscript":"기록 없이", + "blacklistAdd":"사용자 블랙리스트 추가", + "blacklistRemove":"사용자 해제" }, "titles":{ "created":"티켓 생성됨", @@ -156,7 +158,8 @@ "topicSet":"주제가 변경됨", "prioritySet":"우선순위 변경됨", "priorityGet":"티켓 우선순위", - "transfer":"티켓이 전송됨" + "transfer":"티켓이 전송됨", + "transcripts":"전사 기록" }, "descriptions":{ "create":"티켓이 생성되었습니다. 아래 버튼을 클릭하여 접근하세요!", @@ -249,7 +252,9 @@ "prioritySetLog":"이 티켓의 우선순위가 {1}에 의해 {0}(으)로 변경되었습니다!", "prioritySetDm":"귀하의 티켓 우선순위가 서버에서 {0}(으)로 변경되었습니다!", "roleUpdateLog":"{0}님이 자신의 역할을 업데이트했습니다!", - "roleUpdateDm":"서버에서 귀하의 역할이 업데이트되었습니다!" + "roleUpdateDm":"서버에서 귀하의 역할이 업데이트되었습니다!", + "topicSetLog":"이 티켓의 우선순위가 {1}에 의해 {0}으로 설정되었습니다.", + "topicSetDm":"귀하의 티켓 우선순위가 {0}으로 설정되었습니다." } }, "transcripts":{ @@ -271,7 +276,9 @@ "continue":"기록 없이 삭제", "backup":"백업 기록 생성", "error":"기록 생성 중 오류가 발생했습니다.\n어떻게 하시겠습니까?\n\n아래 버튼 중 하나를 클릭할 때까지 이 티켓은 삭제되지 않습니다.", - "title":"기록 오류" + "title":"기록 오류", + "noHistory":"이 사용자는 아직 전사 기록이 없습니다.", + "historyNotSupported":"전사 기록은 현재 HTML Transcripts에서만 지원됩니다.\n텍스트 전사 기록은 향후 버전에서 제공될 예정입니다." }, "text":{ "messagesTitle":"메시지", @@ -298,6 +305,7 @@ "unknownPanel":"알 수 없는 패널", "notInGuild":"서버에 없음", "channelRename":"채널 이름 변경 불가", + "channelCategory":"카테고리 변경 불가", "busy":"티켓 사용 중", "permissionError":"권한 오류" }, @@ -321,11 +329,15 @@ "deprecatedTicket":"현재 채널은 유효한 티켓이 아닙니다! 이전 Open Ticket 버전의 티켓일 수 있습니다!", "notInGuild":"이 {0}은(는) DM에서 작동하지 않습니다! 서버에서 다시 시도하세요!", "channelRename":"디스코드 속도 제한으로 인해 현재 채널 이름을 변경할 수 없습니다. 봇이 재시작되지 않으면 10분 내에 자동으로 변경됩니다.", + "channelCategory":"Discord 속도 제한으로 인해 채널 카테고리를 즉시 변경할 수 없습니다. 봇이 온라인 상태를 유지하면 10분 이내에 자동으로 변경됩니다.", "channelRenameSource":"오류 원인: {0}", "busy":"이 {0}을(를) 사용할 수 없습니다!\n티켓이 현재 봇에 의해 처리 중입니다.\n\n몇 초 후에 다시 시도하세요!", "closeBeforeMessage":"사용자가 메시지를 보내기 전에는 이 티켓을 닫거나 삭제할 수 없습니다.", "closeBeforeAdminMessage":"티켓 관리자 또는 지원 팀원이 메시지를 보내기 전에는 이 티켓을 닫거나 삭제할 수 없습니다.", - "unableToCreateTicket":"티켓을 생성할 수 없습니다." + "unableToCreateTicket":"티켓을 생성할 수 없습니다.", + "messageMissing":"상호작용 메시지를 찾을 수 없습니다. 대신 `{0}` 명령을 사용하세요.", + "stateExpired":"이 상호작용은 더 이상 유효하지 않거나 만료되었습니다. 대신 `{0}` 명령을 사용하세요. Open Ticket의 주요 업데이트 이후 이 오류는 정상입니다.", + "panelStateExpired":"이 패널은 더 이상 유효하지 않거나 만료되었습니다. 문제를 해결하려면 `{0}`을 사용하여 새 패널을 생성하세요. Open Ticket의 주요 업데이트 이후 이 오류는 정상입니다." }, "optionInvalidReasons":{ "stringRegex":"값이 패턴과 일치하지 않습니다!", @@ -387,6 +399,8 @@ "syntax":"구문", "originalName":"원래 이름", "newName":"새 이름", + "originalCategory":"원래 카테고리", + "newCategory":"새 카테고리", "until":"까지", "validOptions":"유효한 옵션", "validPanels":"유효한 패널", @@ -408,6 +422,8 @@ "participants":"참가자", "yes":"예", "no":"아니오", + "accept":"수락", + "cancel":"취소", "option":"옵션", "topic":"주제", "uptime":"시스템 가동 시간", @@ -439,6 +455,7 @@ "panelAutoUpdate":"이 패널을 편집 시 자동으로 업데이트하시겠습니까?", "ticket":"즉시 티켓을 생성하세요.", "ticketId":"생성하려는 티켓의 식별자입니다.", + "ticketOtherUser":"다른 사용자에게 티켓 생성", "close":"티켓을 닫으세요.", "delete":"티켓을 삭제하세요.", "deleteNoTranscript":"기록 없이 이 티켓을 삭제하세요.", @@ -504,7 +521,9 @@ "priorityGet":"티켓의 우선순위를 가져옵니다.", "priorityList":"모든 티켓의 우선순위 상태를 목록으로 가져옵니다.", "transfer":"티켓 소유권을 다른 사용자에게 전송합니다.", - "transferUser":"전송할 사용자입니다." + "transferUser":"전송할 사용자입니다.", + "transcripts":"사용자의 티켓 전사 기록 보기", + "transcriptsUser":"조회할 사용자" }, "helpMenu":{ "help":"사용 가능한 모든 명령어 목록을 확인하세요.", @@ -594,7 +613,8 @@ "panel":{ "selectTicket":"티켓을 선택하세요", "selectRole":"역할을 선택하세요", - "selectOption":"옵션을 선택하세요" + "selectOption":"옵션을 선택하세요", + "selectPriorityLevel":"우선순위 수준 선택" }, "priorities":{ "urgent":"긴급", diff --git a/languages/kurdish.json b/languages/kurdish.json index 0b14b78..6581302 100644 --- a/languages/kurdish.json +++ b/languages/kurdish.json @@ -1,8 +1,8 @@ { "_TRANSLATION":{ - "otversion":"v4.1.3", + "otversion":"v4.2.0", "translators":["HanumeshGupta","ChatGPT"], - "lastedited":"16/02/2026", + "lastedited":"25/05/2026", "language":"Kurdish", "automated":false }, @@ -99,7 +99,7 @@ "invalidButton":"Ev bişkok divê bi qasî {0} an {1} hebe!", "unusedOption":"Vebijarka {0} li ku derê nayê bikar anîn!", "unusedQuestion":"Pirsiyara {0} li ku derê nayê bikar anîn!", - "dropdownOption":"Panelê ku bi dropdownê çalak e tenê dikare vebijarkên ji tîpa 'ticket' dihewîne!", + "dropdownOption":"Panela ku drop-down heye tenê dikare vebijêrkên celebên 'ticket', 'role' an 'sub-panel' hebe.", "customInvalidVersion":"Wersiyona ku di configê te de hate qeyd kirin ne hevrû ye! Pêwîste ku configê xwe bi dawîna nûvekirî nûve bikin!" } }, @@ -118,7 +118,9 @@ "helpSwitchText":"Fermana Nivîsê Bibînin", "helpPage":"Rûpel {0}", "withReason":"Bi Sedem", - "withoutTranscript":"Bê Transcript" + "withoutTranscript":"Bê Transcript", + "blacklistAdd":"Bikarhênerê Lîsteya Reş Zêde Bike", + "blacklistRemove":"Bikarhêner Azad Bike" }, "titles":{ "created":"Tiket Hate Afirandin", @@ -156,7 +158,8 @@ "topicSet":"Mijar Guherî", "prioritySet":"Pêşî Guherî", "priorityGet":"Pêşîya Ticket", - "transfer":"Ticket Veguhastin" + "transfer":"Ticket Veguhastin", + "transcripts":"Dîroka Transkriptan" }, "descriptions":{ "create":"Tiketa we hate afirandin. Ji bo têketinê bişkoka jêrîn bikar bînin!", @@ -249,7 +252,9 @@ "prioritySetLog":"Pêşîya vê ticketê ji {1} bo {0} hate guhertin!", "prioritySetDm":"Pêşîya ticketê te di serverê me de hate guhertin bo {0}!", "roleUpdateLog":"{0} rolên xwe nûve kir!", - "roleUpdateDm":"Rolên te di serverê me de nûve kirin!" + "roleUpdateDm":"Rolên te di serverê me de nûve kirin!", + "topicSetLog":"Pêşengiya vê ticketê ji hêla {1} ve wek {0} hate danîn.", + "topicSetDm":"Pêşengiya ticketê te wek {0} hate danîn." } }, "transcripts":{ @@ -271,7 +276,9 @@ "continue":"Bê Transcript Jê Bike", "backup":"Transcripta Backupê Afirîne", "error":"Tiştek çewt çû dema ku transcript hate afirandin.\nHûn çi dixwazin bikin?\n\nEv tiket dê neyê jêkirin heta ku hûn yek ji van bişkokan bikar bînin.", - "title":"Xeletiya Transkriptê" + "title":"Xeletiya Transkriptê", + "noHistory":"Ev bikarhêner hêj tu transkriptên tune ye.", + "historyNotSupported":"Dîroka transkriptan niha tenê bi HTML Transcripts tê piştgirî kirin.\nDîroka transkriptên nivîsê di guhertoyên pêşerojê de dê hebin." }, "text":{ "messagesTitle":"PEYAMAN", @@ -298,6 +305,7 @@ "unknownPanel":"Panela Nenas", "notInGuild":"Ne Di Serverê De", "channelRename":"Nikare Qenalê Nav Lê Bike", + "channelCategory":"Nekare Kategorî Biguherîne", "busy":"Tiketê Şixul E", "permissionError":"Xeletiya Destûrê" }, @@ -321,11 +329,15 @@ "deprecatedTicket":"Qenala niha tiketeke derbasdar nîne! Dibe ku ji versiyonek kevn a Open Ticketê be!", "notInGuild":"Ev {0} di DM de naxebite! Ji kerema xwe dîsa di serverekê de biceribînin!", "channelRename":"Ji ber sînorên discordê, niha ne mimkûn e ku bot qenalê nav lê bike. Qenal dê bixweber ji bo zêdetirî 10 deqîqan were nav lê kirin ger bot neyê nûvekirin.", + "channelCategory":"Ji ber sînorkirinên leza Discord, kategorîya kanalê nayê guhertin bi rastî. Heke bot online bimîne, di 10 deqîqan de wê bixweber were guhertin.", "channelRenameSource":"Çavkaniya vê xetê: {0}", "busy":"Nikare vê {0} bikar bîne!\nTiket niha ji aliyê botê ve tê pêvajokirin.\n\nJi kerema xwe di çend saniyeyan de dîsa biceribînin!", "closeBeforeMessage":"Ev ticket nikare were girtin/jêbirin berî ku bikarhêner peyamê şandibe.", "closeBeforeAdminMessage":"Ev ticket nikare were girtin/jêbirin berî ku adminê ticket an endamê piştgirî peyamê şandibe.", - "unableToCreateTicket":"Tu nikarî ticket çê bikî." + "unableToCreateTicket":"Tu nikarî ticket çê bikî.", + "messageMissing":"Nikarî mesajê têkildarê peywendiyê bibîne. Li şûna wê fermanê `{0}` bikar bîne.", + "stateExpired":"Ev peywendî êdî ne derbasdar e an qediya ye. Li şûna wê fermanê `{0}` bikar bîne. Ev çewtî piştî nûvekirina mezin a Open Ticket normal e.", + "panelStateExpired":"Ev panel êdî ne derbasdar e an qediya ye. Ji bo çareserkirina pirsgirêkê panelê nû bi `{0}` biafirîne. Ev çewtî piştî nûvekirina mezin a Open Ticket normal e." }, "optionInvalidReasons":{ "stringRegex":"Nirx li şablonê nagire!", @@ -387,6 +399,8 @@ "syntax":"Sîntaks", "originalName":"Navê Orjînal", "newName":"Navê Nû", + "originalCategory":"Kategoriya Bingehîn", + "newCategory":"Kategoriya Nû", "until":"Heta", "validOptions":"Vebijarkên Derbasdar", "validPanels":"Panelên Derbasdar", @@ -408,6 +422,8 @@ "participants":"Beşdar", "yes":"Erê", "no":"Na", + "accept":"Qebûl Bike", + "cancel":"Betal Bike", "option":"Vebijêrk", "topic":"Mijar", "uptime":"Demjimêra Sistêmê", @@ -439,6 +455,7 @@ "panelAutoUpdate":"Ma hûn dixwazin ev panel xweber were nûvekirin dema ku hate guhertin?", "ticket":"Bilez tîketek çêbike.", "ticketId":"Nasnameya tîketê ku hûn dixwazin çêbikin.", + "ticketOtherUser":"Ji bo bikarhênerek din ticket çêbike.", "close":"Tîketek bigire.", "delete":"Tîketek jê bike.", "deleteNoTranscript":"Vê tîketê bêyî çêkirina transcriptê jê bike.", @@ -504,7 +521,9 @@ "priorityGet":"Pêşîya ticketê bistînin.", "priorityList":"Lîsteya hemû ticketan bi rewşa pêşîyan bistînin.", "transfer":"Mulkê ticketê ji yek bikarhêner bo yê din veguherîn.", - "transferUser":"Bikarhêner ku divê veguherîn." + "transferUser":"Bikarhêner ku divê veguherîn.", + "transcripts":"Dîroka transkriptên ticketên bikarhêner bibîne.", + "transcriptsUser":"Bikarhênerê ku were nîşandan." }, "helpMenu":{ "help":"Lîsteyê hemû fermanên berdest bistînin.", @@ -594,7 +613,8 @@ "panel":{ "selectTicket":"Ticketê xwe hilbijêrin", "selectRole":"Rolê xwe hilbijêrin", - "selectOption":"Vebijêrka xwe hilbijêrin" + "selectOption":"Vebijêrka xwe hilbijêrin", + "selectPriorityLevel":"Asta pêşengiyê hilbijêre" }, "priorities":{ "urgent":"Zû", diff --git a/languages/latvian.json b/languages/latvian.json index 859e8ce..d60618a 100644 --- a/languages/latvian.json +++ b/languages/latvian.json @@ -1,8 +1,8 @@ { "_TRANSLATION":{ - "otversion":"v4.1.3", + "otversion":"v4.2.0", "translators":["NoOneNook"], - "lastedited":"16/02/2026", + "lastedited":"25/05/2026", "language":"Latvian", "automated":false }, @@ -99,7 +99,7 @@ "invalidButton":"Šai pogai jābūt vismaz {0} vai {1}!", "unusedOption":"Opcija {0} nekur netiek izmantota!", "unusedQuestion":"Jautājums {0} nekur netiek izmantots!", - "dropdownOption":"Panelim ar iespējotu izvēlni var būt tikai 'ticket' tipa opcijas!", + "dropdownOption":"Panelis ar nolaižamo izvēlni var saturēt tikai šāda tipa opcijas: 'ticket', 'role' vai 'sub-panel'.", "customInvalidVersion":"Konfigurācijā norādītā versija nesakrīt! Pārliecinieties, ka esat atjauninājis konfigurāciju uz jaunāko versiju!" } }, @@ -118,7 +118,9 @@ "helpSwitchText":"Skatīt teksta komandas", "helpPage":"Lapa {0}", "withReason":"Ar iemeslu", - "withoutTranscript":"Bez transkripta" + "withoutTranscript":"Bez transkripta", + "blacklistAdd":"Pievienot lietotāju melnajam sarakstam", + "blacklistRemove":"Atbrīvot lietotāju" }, "titles":{ "created":"Biļete izveidota", @@ -156,7 +158,8 @@ "topicSet":"Tēma mainīta", "prioritySet":"Prioritāte mainīta", "priorityGet":"Biļetes prioritāte", - "transfer":"Biļete pārsūtīta" + "transfer":"Biļete pārsūtīta", + "transcripts":"Transkriptu vēsture" }, "descriptions":{ "create":"Jūsu biļete ir izveidota. Noklikšķiniet uz pogas zemāk, lai tai piekļūtu!", @@ -249,7 +252,9 @@ "prioritySetLog":"Šīs biļetes prioritāte tika mainīta uz {0} lietotāja {1} dēļ!", "prioritySetDm":"Jūsu biļetes prioritāte tika mainīta uz {0} mūsu serverī!", "roleUpdateLog":"{0} ir atjauninājis savas lomas!", - "roleUpdateDm":"Jūsu lomas mūsu serverī ir atjauninātas!" + "roleUpdateDm":"Jūsu lomas mūsu serverī ir atjauninātas!", + "topicSetLog":"Šī biļete prioritāte tika iestatīta uz {0} no {1}.", + "topicSetDm":"Jūsu biļetes prioritāte tika iestatīta uz {0}." } }, "transcripts":{ @@ -271,7 +276,9 @@ "continue":"Dzēst bez transkripta", "backup":"Izveidot rezerves transkriptu", "error":"Kaut kas nogāja greizi, mēģinot izveidot transkriptu.\nKo jūs vēlaties darīt?\n\nŠī biļete netiks dzēsta, kamēr jūs nenoklikšķināsiet uz vienas no šīm pogām.", - "title":"Transkripta kļūda" + "title":"Transkripta kļūda", + "noHistory":"Šim lietotājam vēl nav transkriptu.", + "historyNotSupported":"Transkriptu vēsture pašlaik tiek atbalstīta tikai ar HTML Transcripts.\nTeksta transkriptu vēsture būs pieejama nākamajās versijās." }, "text":{ "messagesTitle":"ZIŅOJUMI", @@ -298,6 +305,7 @@ "unknownPanel":"Nezināms panelis", "notInGuild":"Nav serverī", "channelRename":"Nevar pārdēvēt kanālu", + "channelCategory":"Nevar mainīt kategoriju", "busy":"Biļete ir aizņemta", "permissionError":"Atļauju kļūda" }, @@ -321,11 +329,15 @@ "deprecatedTicket":"Pašreizējais kanāls nav derīga biļete! Tas varētu būt biļete no vecas Open Ticket versijas!", "notInGuild":"Šis {0} nedarbojas DM! Lūdzu, mēģiniet vēlreiz serverī!", "channelRename":"Discord ierobežojumu dēļ bots pašlaik nevar pārdēvēt kanālu. Kanāls tiks automātiski pārdēvēts 10 minūšu laikā, ja bots netiks restartēts.", + "channelCategory":"Discord ātruma ierobežojumu dēļ kanāla kategoriju nevar uzreiz mainīt. Tā tiks automātiski mainīta 10 minūšu laikā, ja bots paliks tiešsaistē.", "channelRenameSource":"Šīs kļūdas avots ir: {0}", "busy":"Nevar izmantot šo {0}!\nBiļete pašlaik tiek apstrādāta ar botu.\n\nLūdzu, mēģiniet vēlreiz pēc dažām sekundēm!", "closeBeforeMessage":"Šo biļeti nevar aizvērt/dzēst, pirms lietotājs ir nosūtījis ziņojumu.", "closeBeforeAdminMessage":"Šo biļeti nevar aizvērt/dzēst, pirms biļetes administrators vai atbalsta dalībnieks ir nosūtījis ziņojumu.", - "unableToCreateTicket":"Jūs nevarat izveidot biļeti." + "unableToCreateTicket":"Jūs nevarat izveidot biļeti.", + "messageMissing":"Neizdevās atrast mijiedarbības ziņojumu. Tā vietā izmantojiet komandu `{0}`.", + "stateExpired":"Šī mijiedarbība vairs nav derīga vai ir beigusies. Tā vietā izmantojiet komandu `{0}`. Tas ir normāli pēc lieliem Open Ticket atjauninājumiem.", + "panelStateExpired":"Šis panelis vairs nav derīgs vai ir beidzies. Izveidojiet jaunu paneli, izmantojot `{0}`, lai atrisinātu problēmu. Tas ir normāli pēc lieliem Open Ticket atjauninājumiem." }, "optionInvalidReasons":{ "stringRegex":"Vērtība neatbilst šablonam!", @@ -387,6 +399,8 @@ "syntax":"Sintakse", "originalName":"Sākotnējais nosaukums", "newName":"Jaunais nosaukums", + "originalCategory":"Sākotnējā kategorija", + "newCategory":"Jaunā kategorija", "until":"Līdz", "validOptions":"Derīgas opcijas", "validPanels":"Derīgi paneļi", @@ -408,6 +422,8 @@ "participants":"Dalībnieki", "yes":"Jā", "no":"Nē", + "accept":"Pieņemt", + "cancel":"Atcelt", "option":"Opcija", "topic":"Tēma", "uptime":"Sistēmas darbības laiks", @@ -439,6 +455,7 @@ "panelAutoUpdate":"Vai vēlaties, lai šis panelis automātiski atjauninātos, kad tas tiek rediģēts?", "ticket":"Nekavējoties izveidojiet biļeti.", "ticketId":"Biļetes identifikators, kuru vēlaties izveidot.", + "ticketOtherUser":"Izveidot biļeti citam lietotājam.", "close":"Aizveriet biļeti.", "delete":"Dzēsiet biļeti.", "deleteNoTranscript":"Dzēsiet šo biļeti, neizveidojot transkriptu.", @@ -504,7 +521,9 @@ "priorityGet":"Iegūt biļetes prioritāti.", "priorityList":"Iegūt visu biļešu sarakstu ar to prioritātes statusu.", "transfer":"Pārsūtīt biļetes īpašumtiesības no viena lietotāja citam.", - "transferUser":"Lietotājs, kuram pārsūtīt." + "transferUser":"Lietotājs, kuram pārsūtīt.", + "transcripts":"Skatīt lietotāja biļešu transkriptu vēsturi.", + "transcriptsUser":"Lietotājs apskatei." }, "helpMenu":{ "help":"Iegūstiet visu pieejamo komandu sarakstu.", @@ -594,7 +613,8 @@ "panel":{ "selectTicket":"Izvēlieties savu biļeti", "selectRole":"Izvēlieties savu lomu", - "selectOption":"Izvēlieties savu opciju" + "selectOption":"Izvēlieties savu opciju", + "selectPriorityLevel":"Izvēlieties prioritātes līmeni" }, "priorities":{ "urgent":"Steidzama", diff --git a/languages/lithuanian.json b/languages/lithuanian.json index ba21919..1d487d6 100644 --- a/languages/lithuanian.json +++ b/languages/lithuanian.json @@ -1,8 +1,8 @@ { "_TRANSLATION":{ - "otversion":"v4.1.3", + "otversion":"v4.2.0", "translators":["TsgIndrius"], - "lastedited":"16/02/2026", + "lastedited":"25/05/2026", "language":"Lithuanian", "automated":false }, @@ -99,7 +99,7 @@ "invalidButton":"Šis mygtukas turi turėti bent {0} arba {1}!", "unusedOption":"Šis pasirinkimas {0} nėra naudojamas!", "unusedQuestion":"Klausimas {0} nėra naudojamas niekur!", - "dropdownOption":"Pasirinkimas su įjungtu išskleidžiamuoju meniu gali būti tik „bilieto“ tipo parinktys!", + "dropdownOption":"Skydelis su išskleidžiamuoju meniu gali turėti tik šių tipų parinktis: 'ticket', 'role' arba 'sub-panel'.", "customInvalidVersion":"Jūsų nustatymuose nurodyta versija nesutampa! Įsitikinkite, kad atnaujinote nustatymus į naujausią versiją.!" } }, @@ -118,7 +118,9 @@ "helpSwitchText":"Peržiūrėti Teksto Komandas ", "helpPage":"Puslapis {0}", "withReason":"Su Priežastimi", - "withoutTranscript":"Be Išrašo" + "withoutTranscript":"Be Išrašo", + "blacklistAdd":"Įtraukti Vartotoją į Juodąjį Sąrašą", + "blacklistRemove":"Atlaisvinti Vartotoją" }, "titles":{ "created":"Bilietas Sukurtas", @@ -156,7 +158,8 @@ "topicSet":"Tema pakeista", "prioritySet":"Prilioritetas pakeistas", "priorityGet":"Bilieto prilioritetas", - "transfer":"Bilietas perkeltas" + "transfer":"Bilietas perkeltas", + "transcripts":"Transkriptų Istorija" }, "descriptions":{ "create":"Jūsų bilietas sukurtas. Spustelėkite toliau esantį mygtuką, kad jį pasiektumėte!", @@ -249,7 +252,9 @@ "prioritySetLog":"Šio bilieto prioritetas buvo pakeistas į {0}, autorius {1}!", "prioritySetDm":"Jūsų bilieto prioritetas mūsų serveryje pakeistas į {0}!", "roleUpdateLog":"{0} atnaujino savo roles!", - "roleUpdateDm":"Jūsų rolės mūsų serveryje buvo atnaujinti!" + "roleUpdateDm":"Jūsų rolės mūsų serveryje buvo atnaujinti!", + "topicSetLog":"Šio bilieto prioritetas buvo nustatytas į {0} naudotojo {1}.", + "topicSetDm":"Jūsų bilieto prioritetas buvo nustatytas į {0}." } }, "transcripts":{ @@ -271,7 +276,9 @@ "continue":"Ištrinti be išrašo", "backup":"Sukurti Atsarginį išraša", "error":"Kažkas nepavyko bandant sukurti išraša.\nKą norėtumėte daryti?\n\nŠis bilietas nebus ištrintas, kol spustelėsite vieną iš šių mygtukų.", - "title":"Nuorašo klaida" + "title":"Nuorašo klaida", + "noHistory":"Šis vartotojas dar neturi jokių transkriptų.", + "historyNotSupported":"Transkriptų istorija šiuo metu palaikoma tik su HTML Transcripts.\nTekstinių transkriptų istorija bus prieinama ateities versijose." }, "text":{ "messagesTitle":"ŽINUTĖS", @@ -298,6 +305,7 @@ "unknownPanel":"Nepažystama skydelis", "notInGuild":"Nera serveryje", "channelRename":"Negaliu pervadyti kanalo", + "channelCategory":"Nepavyko Pakeisti Kategorijos", "busy":"Bilietas yra užimtas", "permissionError":"Leidimų klaida" }, @@ -321,11 +329,15 @@ "deprecatedTicket":"Dabartinis kanalas negalioja! Tai galėjo būti bilietas iš senos Open Ticket versijos!", "notInGuild":"Šis {0} neveikia DM! Bandykite dar kartą serveryje!", "channelRename":"Dėl discord'o greičio apribojimų, šiuo metu robotas negali pervardyti kanalo. Kanalas bus automatiškai pervardytas per 10 minučių, jei robotas nebus paleistas iš naujo.", + "channelCategory":"Dėl Discord dažnio apribojimų kanalo kategorijos nepavyko pakeisti iš karto. Ji bus automatiškai pakeista per 10 minučių, jei botas liks prisijungęs.", "channelRenameSource":"Šios klaidos šaltinis yra: {0}", "busy":"Neįmanoma naudoti šio {0}!\nBilietą šiuo metu apdoroja botas.\n\nBandykite dar kartą po kelių sekundžių!", "closeBeforeMessage":"Šios užklausos negalima uždaryti / ištrinti, kol vartotojas neišsiuntė pranešimo..", "closeBeforeAdminMessage":"Šios užklausos negalima uždaryti / ištrinti, kol užklausos administratorius arba palaikymo komandos narys neišsiuntė pranešimo..", - "unableToCreateTicket":"Kažkas atsitiko jūs negalite sukurti bilieto." + "unableToCreateTicket":"Kažkas atsitiko jūs negalite sukurti bilieto.", + "messageMissing":"Nepavyko rasti sąveikos žinutės. Vietoje to naudokite komandą `{0}`.", + "stateExpired":"Ši sąveika nebegalioja arba jos galiojimas baigėsi. Vietoje to naudokite komandą `{0}`. Normalu gauti šią klaidą po didelio Open Ticket atnaujinimo.", + "panelStateExpired":"Šis skydelis nebegalioja arba jo galiojimas baigėsi. Sukurkite naują skydelį naudodami `{0}`, kad išspręstumėte problemą. Normalu gauti šią klaidą po didelio Open Ticket atnaujinimo." }, "optionInvalidReasons":{ "stringRegex":"Vertė neatitinka šablono!", @@ -387,6 +399,8 @@ "syntax":"Sintaksė", "originalName":"Orginalus Pavadinimas", "newName":"Naujas pavadinimas", + "originalCategory":"Originali Kategorija", + "newCategory":"Nauja Kategorija", "until":"Iki", "validOptions":"Galiojančios parinktys", "validPanels":"Galiojančios Skydeliai", @@ -408,6 +422,8 @@ "participants":"Dalyviai", "yes":"Taip", "no":"Ne", + "accept":"Priimti", + "cancel":"Atšaukti", "option":"Pasirinkimai", "topic":"Tema", "uptime":"Sistemos Buvimo laikas", @@ -439,6 +455,7 @@ "panelAutoUpdate":"Ar norite, kad šis skydelis būtų automatiškai atnaujintas redaguojant?", "ticket":"Iškart sukurkite bilietą.", "ticketId":"Bilieto, kurį norite sukurti, identifikatorius.", + "ticketOtherUser":"Sukurti bilietą kitam vartotojui.", "close":"Uždaryti bilieta.", "delete":"Ištrinti bilieta.", "deleteNoTranscript":"Ištrinti šita bilieta nesukuriant išrašo.", @@ -504,7 +521,9 @@ "priorityGet":"Gaukite bilieto prioritetą.", "priorityList":"Gaukite visų bilietų sąrašą su jų prioriteto būsena.", "transfer":"Perduoti bilieto nuosavybę iš vieno vartotojo kitam.", - "transferUser":"Vartotojas, kuriam reikia perkelti." + "transferUser":"Vartotojas, kuriam reikia perkelti.", + "transcripts":"Peržiūrėti vartotojo bilietų transkriptų istoriją.", + "transcriptsUser":"Vartotojas peržiūrai." }, "helpMenu":{ "help":"Gauk saraša galimų komandų.", @@ -594,7 +613,8 @@ "panel":{ "selectTicket":"Pasirink Bilieta", "selectRole":"Pasirink Role", - "selectOption":"Pasirinkite savo parinktį" + "selectOption":"Pasirinkite savo parinktį", + "selectPriorityLevel":"Pasirinkite prioriteto lygį" }, "priorities":{ "urgent":"Skubiai", diff --git a/languages/norwegian.json b/languages/norwegian.json index 42b6ff5..22cd4cd 100644 --- a/languages/norwegian.json +++ b/languages/norwegian.json @@ -1,8 +1,8 @@ { "_TRANSLATION":{ - "otversion":"v4.1.3", + "otversion":"v4.2.0", "translators":["NoOneNook"], - "lastedited":"16/02/2026", + "lastedited":"25/05/2026", "language":"Norwegian", "automated":false }, @@ -99,7 +99,7 @@ "invalidButton":"Denne knappen skal ha minst en {0} eller {1}!", "unusedOption":"Alternativet {0} brukes ikke noe sted!", "unusedQuestion":"Spørsmålet {0} brukes ikke noe sted!", - "dropdownOption":"Et panel med nedtrekksmeny aktivert kan kun inneholde alternativer av typen 'sak'!", + "dropdownOption":"Et panel med rullegardinmeny kan kun inneholde alternativer av typene: 'ticket', 'role' eller 'sub-panel'.", "customInvalidVersion":"Versjonen spesifisert i konfigurasjonen stemmer ikke! Sørg for at du har oppdatert konfigurasjonen til siste versjon!" } }, @@ -118,7 +118,9 @@ "helpSwitchText":"Vis Tekstkommandoer", "helpPage":"Side {0}", "withReason":"Med Årsak", - "withoutTranscript":"Uten Utskrift" + "withoutTranscript":"Uten Utskrift", + "blacklistAdd":"Svartelist Bruker", + "blacklistRemove":"Frigi Bruker" }, "titles":{ "created":"Sak Opprettet", @@ -156,7 +158,8 @@ "topicSet":"Emne Endret", "prioritySet":"Prioritet Endret", "priorityGet":"Ticket Prioritet", - "transfer":"Ticket Overført" + "transfer":"Ticket Overført", + "transcripts":"Transkripsjonshistorikk" }, "descriptions":{ "create":"Din sak er blitt opprettet. Klikk på knappen nedenfor for å få tilgang til den!", @@ -249,7 +252,9 @@ "prioritySetLog":"Prioriteten til denne ticketen har blitt endret til {0} av {1}!", "prioritySetDm":"Prioriteten til din ticket har blitt endret til {0} på vår server!", "roleUpdateLog":"{0} har oppdatert sine roller!", - "roleUpdateDm":"Dine roller på vår server har blitt oppdatert!" + "roleUpdateDm":"Dine roller på vår server har blitt oppdatert!", + "topicSetLog":"Prioriteten til denne saken har blitt satt til {0} av {1}.", + "topicSetDm":"Prioriteten til saken din har blitt satt til {0}." } }, "transcripts":{ @@ -271,7 +276,9 @@ "continue":"Slett Uten Utskrift", "backup":"Opprett Sikkerhetskopiutskrift", "error":"Noe gikk galt under forsøket på å opprette utskriften.\nHva vil du gjøre?\n\nDenne saken vil ikke bli slettet før du klikker på en av disse knappene.", - "title":"Transkripsjonsfeil" + "title":"Transkripsjonsfeil", + "noHistory":"Denne brukeren har ingen transkripsjoner ennå.", + "historyNotSupported":"Transkripsjonshistorikk støttes for øyeblikket kun med HTML Transcripts.\nHistorikk for teksttranskripsjoner vil bli tilgjengelig i fremtidige versjoner." }, "text":{ "messagesTitle":"MELDINGER", @@ -298,6 +305,7 @@ "unknownPanel":"Ukjent Panel", "notInGuild":"Ikke På Server", "channelRename":"Kan Ikke Omdøpe Kanal", + "channelCategory":"Kunne Ikke Endre Kategori", "busy":"Saken Er Opptatt", "permissionError":"Tillatelsesfeil" }, @@ -321,11 +329,15 @@ "deprecatedTicket":"Den nåværende kanalen er ikke en gyldig sak! Den kan ha vært en sak fra en eldre versjon av Open Ticket!", "notInGuild":"Denne {0} fungerer ikke i DM! Prøv igjen på en server!", "channelRename":"På grunn av Discords hastighetsbegrensninger er det for øyeblikket umulig for boten å omdøpe kanalen. Kanalen vil automatisk bli omdøpt innen 10 minutter hvis boten ikke startes på nytt.", + "channelCategory":"På grunn av Discords rategrenser kunne ikke kanalens kategori endres umiddelbart. Den vil bli endret automatisk innen 10 minutter dersom boten forblir online.", "channelRenameSource":"Kilden til denne feilen er: {0}", "busy":"Kan ikke bruke denne {0}!\nSaken behandles for øyeblikket av boten.\n\nPrøv igjen om noen sekunder!", "closeBeforeMessage":"Denne ticketen kan ikke lukkes/slettes før en bruker har sendt en melding.", "closeBeforeAdminMessage":"Denne ticketen kan ikke lukkes/slettes før en ticket-admin eller support-medlem har sendt en melding.", - "unableToCreateTicket":"Du kan ikke opprette en ticket." + "unableToCreateTicket":"Du kan ikke opprette en ticket.", + "messageMissing":"Kunne ikke finne meldingen for interaksjonen. Bruk kommandoen `{0}` i stedet.", + "stateExpired":"Denne interaksjonen er ikke lenger gyldig eller har utløpt. Bruk kommandoen `{0}` i stedet. Det er normalt å motta denne feilen etter en større Open Ticket-oppdatering.", + "panelStateExpired":"Dette panelet er ikke lenger gyldig eller har utløpt. Opprett et nytt panel med `{0}` for å løse problemet. Det er normalt å motta denne feilen etter en større Open Ticket-oppdatering." }, "optionInvalidReasons":{ "stringRegex":"Verdien samsvarer ikke med mønsteret!", @@ -387,6 +399,8 @@ "syntax":"Syntaks", "originalName":"Opprinnelig Navn", "newName":"Nytt Navn", + "originalCategory":"Opprinnelig Kategori", + "newCategory":"Ny Kategori", "until":"Til", "validOptions":"Gyldige Alternativer", "validPanels":"Gyldige Paneler", @@ -408,6 +422,8 @@ "participants":"DELTAGERE", "yes":"Ja", "no":"Nei", + "accept":"Godta", + "cancel":"Avbryt", "option":"Alternativ", "topic":"Emne", "uptime":"System oppetid", @@ -439,6 +455,7 @@ "panelAutoUpdate":"Ønsker du at dette panelet skal oppdatere seg automatisk når det redigeres?", "ticket":"Opprett en sak med det samme.", "ticketId":"Identifikatoren for saken du vil opprette.", + "ticketOtherUser":"Opprett en sak for en annen bruker.", "close":"Lukk en sak.", "delete":"Slett en sak.", "deleteNoTranscript":"Slett denne saken uten å opprette en utskrift.", @@ -504,7 +521,9 @@ "priorityGet":"Få prioritet for ticketen.", "priorityList":"Få en liste over alle tickets med deres prioritet.", "transfer":"Overfør eierskap av ticket fra en bruker til en annen.", - "transferUser":"Brukeren som skal overføres til." + "transferUser":"Brukeren som skal overføres til.", + "transcripts":"Vis en brukers sakstranskripsjonshistorikk.", + "transcriptsUser":"Brukeren som skal vises." }, "helpMenu":{ "help":"Få en liste over alle tilgjengelige kommandoer.", @@ -594,7 +613,8 @@ "panel":{ "selectTicket":"Velg din ticket", "selectRole":"Velg din rolle", - "selectOption":"Velg ditt alternativ" + "selectOption":"Velg ditt alternativ", + "selectPriorityLevel":"Velg prioritetsnivå" }, "priorities":{ "urgent":"Haster", diff --git a/languages/persian.json b/languages/persian.json index f262ff9..638154a 100644 --- a/languages/persian.json +++ b/languages/persian.json @@ -1,8 +1,8 @@ { "_TRANSLATION":{ - "otversion":"v4.1.3", + "otversion":"v4.2.0", "translators":["dysashop","zhavis"], - "lastedited":"16/02/2026", + "lastedited":"25/05/2026", "language":"Persian", "automated":false }, @@ -99,7 +99,7 @@ "invalidButton":"این دکمه باید حداقل شامل یک {0} یا {1} باشد!", "unusedOption":"گزینه {0} در هیچ جایی استفاده نشده است!", "unusedQuestion":"سوال {0} در هیچ جایی استفاده نشده است!", - "dropdownOption":"پنلی که منوی کشویی آن فعال است، فقط می‌تواند شامل گزینه‌هایی از نوع 'تیکت' باشد!", + "dropdownOption":"یک پنل با منوی کشویی فقط می‌تواند شامل گزینه‌هایی از نوع: 'ticket'، 'role' یا 'sub-panel' باشد.", "customInvalidVersion":"نسخه مشخص شده در پیکربندی شما مطابقت ندارد! اطمینان حاصل کنید که پیکربندی به آخرین نسخه به‌روزرسانی شده است!" } }, @@ -118,7 +118,9 @@ "helpSwitchText":"مشاهده دستورات متنی", "helpPage":"صفحه {0}", "withReason":"با دلیل", - "withoutTranscript":"بدون رونوشت" + "withoutTranscript":"بدون رونوشت", + "blacklistAdd":"افزودن کاربر به لیست سیاه", + "blacklistRemove":"آزادسازی کاربر" }, "titles":{ "created":"تیکت ایجاد شد", @@ -156,7 +158,8 @@ "topicSet":"موضوع تغییر یافت", "prioritySet":"اولویت تغییر یافت", "priorityGet":"اولویت تیکت", - "transfer":"تیکت منتقل شد" + "transfer":"تیکت منتقل شد", + "transcripts":"تاریخچه رونوشت‌ها" }, "descriptions":{ "create":"تیکت شما با موفقیت ایجاد شد. برای دسترسی به آن، روی دکمه زیر کلیک کنید!", @@ -249,7 +252,9 @@ "prioritySetLog":"اولویت این تیکت توسط {1} به {0} تغییر یافت!", "prioritySetDm":"اولویت تیکت شما در سرور ما به {0} تغییر یافت!", "roleUpdateLog":"{0} نقش‌های خود را به‌روزرسانی کرد!", - "roleUpdateDm":"نقش‌های شما در سرور ما به‌روزرسانی شد!" + "roleUpdateDm":"نقش‌های شما در سرور ما به‌روزرسانی شد!", + "topicSetLog":"اولویت این تیکت توسط {1} به {0} تنظیم شد.", + "topicSetDm":"اولویت تیکت شما به {0} تنظیم شد." } }, "transcripts":{ @@ -271,7 +276,9 @@ "continue":"حذف بدون رونوشت", "backup":"ایجاد رونوشت پشتیبان", "error":"مشکلی در هنگام ایجاد رونوشت رخ داد.\nمی‌خواهید چه کاری انجام دهید؟\n\nاین تیکت تا زمانی که یکی از این دکمه‌ها را کلیک نکنید حذف نخواهد شد.", - "title":"خطای رونوشت" + "title":"خطای رونوشت", + "noHistory":"این کاربر هنوز هیچ رونوشت (transcript) ندارد.", + "historyNotSupported":"تاریخچه رونوشت‌ها در حال حاضر فقط با HTML Transcripts پشتیبانی می‌شود.\nتاریخچه رونوشت متنی در نسخه‌های آینده در دسترس خواهد بود." }, "text":{ "messagesTitle":"پیام‌ها", @@ -298,6 +305,7 @@ "unknownPanel":"پنل ناشناخته", "notInGuild":"خارج از سرور", "channelRename":"عدم امکان تغییر نام کانال", + "channelCategory":"تغییر دسته‌بندی ممکن نیست", "busy":"تیکت مشغول است", "permissionError":"خطای دسترسی" }, @@ -321,11 +329,15 @@ "deprecatedTicket":"کانال کنونی یک تیکت معتبر نیست! ممکن است این تیکت از نسخه قدیمی Open Ticket باشد!", "notInGuild":"این {0} در پیام‌های مستقیم (DM) کار نمی‌کند! لطفاً دوباره در یک سرور امتحان کنید!", "channelRename":"به دلیل محدودیت‌های نرخ دیسکورد، ربات در حال حاضر نمی‌تواند نام کانال را تغییر دهد. اگر ربات ری‌استارت نشود، نام کانال ظرف 10 دقیقه به‌طور خودکار تغییر خواهد کرد.", + "channelCategory":"به دلیل محدودیت‌های نرخ Discord، دسته‌بندی کانال فوراً قابل تغییر نیست. اگر ربات آنلاین بماند، طی 10 دقیقه به‌صورت خودکار تغییر خواهد کرد.", "channelRenameSource":"منبع این خطا: {0}", "busy":"امکان استفاده از این {0} وجود ندارد!\nتیکت در حال پردازش توسط ربات است.\n\nلطفاً چند ثانیه دیگر دوباره امتحان کنید!", "closeBeforeMessage":"این تیکت نمی‌تواند قبل از ارسال پیام توسط یک کاربر بسته/حذف شود.", "closeBeforeAdminMessage":"این تیکت نمی‌تواند قبل از ارسال پیام توسط مدیر تیکت یا عضو پشتیبانی بسته/حذف شود.", - "unableToCreateTicket":"شما نمی‌توانید تیکت ایجاد کنید." + "unableToCreateTicket":"شما نمی‌توانید تیکت ایجاد کنید.", + "messageMissing":"امکان یافتن پیام تعامل وجود ندارد. به‌جای آن از دستور `{0}` استفاده کنید.", + "stateExpired":"این تعامل دیگر معتبر نیست یا منقضی شده است. به‌جای آن از دستور `{0}` استفاده کنید. دریافت این خطا پس از به‌روزرسانی بزرگ Open Ticket طبیعی است.", + "panelStateExpired":"این پنل دیگر معتبر نیست یا منقضی شده است. برای رفع مشکل، یک پنل جدید با استفاده از `{0}` ایجاد کنید. دریافت این خطا پس از به‌روزرسانی بزرگ Open Ticket طبیعی است." }, "optionInvalidReasons":{ "stringRegex":"مقدار با الگوی مورد نظر مطابقت ندارد!", @@ -387,6 +399,8 @@ "syntax":"نحوه نگارش", "originalName":"نام اصلی", "newName":"نام جدید", + "originalCategory":"دسته‌بندی اصلی", + "newCategory":"دسته‌بندی جدید", "until":"تا", "validOptions":"گزینه‌های معتبر", "validPanels":"پنل‌های معتبر", @@ -408,6 +422,8 @@ "participants":"شرکت‌کنندگان", "yes":"بله", "no":"خیر", + "accept":"پذیرفتن", + "cancel":"لغو", "option":"گزینه", "topic":"موضوع", "uptime":"زمان کار سیستم", @@ -439,6 +455,7 @@ "panelAutoUpdate":"آیا می‌خواهید این پنل هنگام ویرایش به‌صورت خودکار به‌روزرسانی شود؟", "ticket":"فوراً یک تیکت ایجاد کنید.", "ticketId":"شناسه تیکتی که می‌خواهید ایجاد کنید.", + "ticketOtherUser":"برای کاربر دیگری یک تیکت ایجاد کنید.", "close":"یک تیکت را ببندید.", "delete":"یک تیکت را حذف کنید.", "deleteNoTranscript":"این تیکت را بدون ایجاد رونوشت حذف کنید.", @@ -504,7 +521,9 @@ "priorityGet":"دریافت اولویت تیکت.", "priorityList":"دریافت لیست تمام تیکت‌ها همراه با وضعیت اولویت آن‌ها.", "transfer":"انتقال مالکیت تیکت از یک کاربر به کاربر دیگر.", - "transferUser":"کاربری که باید به او منتقل شود." + "transferUser":"کاربری که باید به او منتقل شود.", + "transcripts":"مشاهده تاریخچه رونوشت تیکت‌های یک کاربر.", + "transcriptsUser":"کاربر برای مشاهده." }, "helpMenu":{ "help":"لیستی از تمام دستورات موجود را دریافت کنید.", @@ -594,7 +613,8 @@ "panel":{ "selectTicket":"تیکت خود را انتخاب کنید", "selectRole":"نقش خود را انتخاب کنید", - "selectOption":"گزینه خود را انتخاب کنید" + "selectOption":"گزینه خود را انتخاب کنید", + "selectPriorityLevel":"انتخاب سطح اولویت" }, "priorities":{ "urgent":"فوری", diff --git a/languages/polish.json b/languages/polish.json index ae3bcc9..3194630 100644 --- a/languages/polish.json +++ b/languages/polish.json @@ -1,8 +1,8 @@ { "_TRANSLATION":{ - "otversion":"v4.1.3", + "otversion":"v4.2.0", "translators":["DanoGlez"], - "lastedited":"16/02/2026", + "lastedited":"25/05/2026", "language":"Polish", "automated":false }, @@ -99,7 +99,7 @@ "invalidButton":"Ten przycisk musi zawierać co najmniej {0} lub {1}!", "unusedOption":"Opcja {0} nie jest nigdzie używana!", "unusedQuestion":"Pytanie {0} nie jest nigdzie używane!", - "dropdownOption":"Panel z włączonym rozwijanym menu może zawierać tylko opcje typu 'ticket'!", + "dropdownOption":"Panel z rozwijanym menu może zawierać tylko opcje typu: 'ticket', 'role' lub 'sub-panel'.", "customInvalidVersion":"Wersja określona w konfiguracji nie pasuje! Upewnij się, że zaktualizowałeś konfigurację do najnowszej wersji!" } }, @@ -118,7 +118,9 @@ "helpSwitchText":"Pokaż Komendy Tekstowe", "helpPage":"Strona {0}", "withReason":"Z Powodem", - "withoutTranscript":"Bez Transkrypcji" + "withoutTranscript":"Bez Transkrypcji", + "blacklistAdd":"Dodaj Użytkownika do Czarnej Listy", + "blacklistRemove":"Zwolnij Użytkownika" }, "titles":{ "created":"Ticket Utworzony", @@ -156,7 +158,8 @@ "topicSet":"Temat Zmieniony", "prioritySet":"Priorytet Zmieniony", "priorityGet":"Priorytet Tiku", - "transfer":"Tik przeniesiony" + "transfer":"Tik przeniesiony", + "transcripts":"Historia Transkrypcji" }, "descriptions":{ "create":"Twój ticket został utworzony. Kliknij poniższy przycisk, aby go otworzyć!", @@ -249,7 +252,9 @@ "prioritySetLog":"Priorytet tego tiku został zmieniony na {0} przez {1}!", "prioritySetDm":"Priorytet twojego tiku został zmieniony na {0} na naszym serwerze!", "roleUpdateLog":"{0} zaktualizował swoje role!", - "roleUpdateDm":"Twoje role na naszym serwerze zostały zaktualizowane!" + "roleUpdateDm":"Twoje role na naszym serwerze zostały zaktualizowane!", + "topicSetLog":"Priorytet tego zgłoszenia został ustawiony na {0} przez {1}.", + "topicSetDm":"Priorytet Twojego zgłoszenia został ustawiony na {0}." } }, "transcripts":{ @@ -271,7 +276,9 @@ "continue":"Usuń bez transkryptu", "backup":"Utwórz kopię zapasową transkryptu", "error":"Coś poszło nie tak podczas próby utworzenia transkryptu.\nCo chciałbyś zrobić?\n\nTen ticket nie zostanie usunięty, dopóki nie klikniesz jednego z tych przycisków.", - "title":"Błąd Transkrypcji" + "title":"Błąd Transkrypcji", + "noHistory":"Ten użytkownik nie ma jeszcze żadnych transkrypcji.", + "historyNotSupported":"Historia transkrypcji jest obecnie obsługiwana tylko z HTML Transcripts.\nHistoria tekstowych transkrypcji będzie dostępna w przyszłych wersjach." }, "text":{ "messagesTitle":"WIADOMOŚCI", @@ -298,6 +305,7 @@ "unknownPanel":"Nieznany panel", "notInGuild":"Nie na serwerze", "channelRename":"Nie można zmienić nazwy kanału", + "channelCategory":"Nie Można Zmienić Kategorii", "busy":"Ticket jest zajęty", "permissionError":"Błąd uprawnień" }, @@ -321,11 +329,15 @@ "deprecatedTicket":"Obecny kanał nie jest prawidłowym ticketem! Może to być ticket z starej wersji Open Ticket!", "notInGuild":"To {0} nie działa w DM! Spróbuj ponownie na serwerze!", "channelRename":"Z powodu ograniczeń Discorda obecnie niemożliwe jest, aby bot zmienił nazwę kanału. Kanał zostanie automatycznie przemianowany w ciągu 10 minut, jeśli bot nie zostanie zrestartowany.", + "channelCategory":"Z powodu limitów szybkości Discorda nie można było natychmiast zmienić kategorii kanału. Zostanie ona zmieniona automatycznie w ciągu 10 minut, jeśli bot pozostanie online.", "channelRenameSource":"Źródło tego błędu: {0}", "busy":"Nie można użyć tego {0}!\nTicket jest obecnie przetwarzany przez bota.\n\nSpróbuj ponownie za kilka sekund!", "closeBeforeMessage":"Nie można zamknąć/usunąć tego tiku zanim użytkownik wyśle wiadomość.", "closeBeforeAdminMessage":"Nie można zamknąć/usunąć tego tiku zanim administrator lub członek wsparcia wyśle wiadomość.", - "unableToCreateTicket":"Nie możesz utworzyć tiku." + "unableToCreateTicket":"Nie możesz utworzyć tiku.", + "messageMissing":"Nie można odnaleźć wiadomości interakcji. Zamiast tego użyj komendy `{0}`.", + "stateExpired":"Ta interakcja nie jest już ważna lub wygasła. Zamiast tego użyj komendy `{0}`. Otrzymanie tego błędu po dużej aktualizacji Open Ticket jest normalne.", + "panelStateExpired":"Ten panel nie jest już ważny lub wygasł. Utwórz nowy panel za pomocą `{0}`, aby rozwiązać problem. Otrzymanie tego błędu po dużej aktualizacji Open Ticket jest normalne." }, "optionInvalidReasons":{ "stringRegex":"Wartość nie pasuje do wzorca!", @@ -387,6 +399,8 @@ "syntax":"Składnia", "originalName":"Oryginalna nazwa", "newName":"Nowa nazwa", + "originalCategory":"Oryginalna Kategoria", + "newCategory":"Nowa Kategoria", "until":"Do", "validOptions":"Prawidłowe opcje", "validPanels":"Prawidłowe panele", @@ -408,6 +422,8 @@ "participants":"UCZESTNICY", "yes":"Tak", "no":"Nie", + "accept":"Akceptuj", + "cancel":"Anuluj", "option":"Opcja", "topic":"Temat", "uptime":"Czas działania systemu", @@ -439,6 +455,7 @@ "panelAutoUpdate":"Czy chcesz, aby ten panel automatycznie aktualizował się po edycji?", "ticket":"Natychmiast utwórz ticket.", "ticketId":"Identyfikator ticketa, który chcesz utworzyć.", + "ticketOtherUser":"Utwórz zgłoszenie dla innego użytkownika.", "close":"Zamknij ticket.", "delete":"Usuń ticket.", "deleteNoTranscript":"Usuń ten ticket bez tworzenia transkryptu.", @@ -504,7 +521,9 @@ "priorityGet":"Pobierz priorytet tiku.", "priorityList":"Pobierz listę wszystkich tików z ich priorytetem.", "transfer":"Przenieś własność tiku od jednego użytkownika do drugiego.", - "transferUser":"Użytkownik, do którego przenieść." + "transferUser":"Użytkownik, do którego przenieść.", + "transcripts":"Wyświetl historię transkrypcji zgłoszeń użytkownika.", + "transcriptsUser":"Użytkownik do wyświetlenia." }, "helpMenu":{ "help":"Uzyskaj listę wszystkich dostępnych poleceń.", @@ -594,7 +613,8 @@ "panel":{ "selectTicket":"Wybierz swój tik", "selectRole":"Wybierz swoją rolę", - "selectOption":"Wybierz opcję" + "selectOption":"Wybierz opcję", + "selectPriorityLevel":"Wybierz poziom priorytetu" }, "priorities":{ "urgent":"Pilny", diff --git a/languages/portuguese.json b/languages/portuguese.json index 182a73d..a8c44c1 100644 --- a/languages/portuguese.json +++ b/languages/portuguese.json @@ -1,8 +1,8 @@ { "_TRANSLATION":{ - "otversion":"v4.1.3", + "otversion":"v4.2.0", "translators":["quiradon"], - "lastedited":"16/02/2026", + "lastedited":"25/05/2026", "language":"Portuguese", "automated":false }, @@ -99,7 +99,7 @@ "invalidButton":"Este botão precisa ter pelo menos um {0} ou {1}!", "unusedOption":"A opção {0} não é usada em nenhum lugar!", "unusedQuestion":"A pergunta {0} não é usada em nenhum lugar!", - "dropdownOption":"Um painel com dropdown habilitado só pode conter opções do tipo 'ticket'!", + "dropdownOption":"Um painel com menu suspenso só pode conter opções dos tipos: 'ticket', 'role' ou 'sub-panel'.", "customInvalidVersion":"A versão especificada na sua configuração não corresponde! Certifique-se de ter atualizado a configuração para a versão mais recente!" } }, @@ -118,7 +118,9 @@ "helpSwitchText":"Ver Comandos de Texto", "helpPage":"Página {0}", "withReason":"Com Motivo", - "withoutTranscript":"Sem Transcrição" + "withoutTranscript":"Sem Transcrição", + "blacklistAdd":"Colocar Usuário na Lista Negra", + "blacklistRemove":"Liberar Usuário" }, "titles":{ "created":"Ticket Criado", @@ -156,7 +158,8 @@ "topicSet":"Tópico Alterado", "prioritySet":"Prioridade Alterada", "priorityGet":"Prioridade do Ticket", - "transfer":"Ticket Transferido" + "transfer":"Ticket Transferido", + "transcripts":"Histórico de Transcrições" }, "descriptions":{ "create":"Seu ticket foi criado. Clique no botão abaixo para acessá-lo!", @@ -249,7 +252,9 @@ "prioritySetLog":"A prioridade deste ticket foi alterada para {0} por {1}!", "prioritySetDm":"A prioridade do seu ticket foi alterada para {0} no nosso servidor!", "roleUpdateLog":"{0} atualizou suas funções!", - "roleUpdateDm":"Suas funções no nosso servidor foram atualizadas!" + "roleUpdateDm":"Suas funções no nosso servidor foram atualizadas!", + "topicSetLog":"A prioridade deste ticket foi definida para {0} por {1}.", + "topicSetDm":"A prioridade do seu ticket foi definida para {0}." } }, "transcripts":{ @@ -271,7 +276,9 @@ "continue":"Excluir Sem Transcrição", "backup":"Criar Transcrição de Backup", "error":"Algo deu errado ao tentar criar a transcrição.\nO que você gostaria de fazer?\n\nEste ticket não será excluído até que você clique em um desses botões.", - "title":"Erro de Transcrição" + "title":"Erro de Transcrição", + "noHistory":"Este usuário ainda não possui transcrições.", + "historyNotSupported":"O histórico de transcrições atualmente é suportado apenas com HTML Transcripts.\nO histórico de transcrições em texto estará disponível em versões futuras." }, "text":{ "messagesTitle":"MENSAGENS", @@ -298,6 +305,7 @@ "unknownPanel":"Painel Desconhecido", "notInGuild":"Não Está no Servidor", "channelRename":"Incapaz de Renomear Canal", + "channelCategory":"Não Foi Possível Alterar a Categoria", "busy":"Ticket Está Ocupado", "permissionError":"Erro de Permissão" }, @@ -321,11 +329,15 @@ "deprecatedTicket":"O canal atual não é um ticket válido! Pode ter sido um ticket de uma versão antiga do Open Ticket!", "notInGuild":"Este {0} não funciona em DM! Por favor, tente novamente em um servidor!", "channelRename":"Devido aos limites de taxa do Discord, atualmente é impossível para o bot renomear o canal. O canal será renomeado automaticamente em 10 minutos se o bot não for reiniciado.", + "channelCategory":"Devido aos limites de taxa do Discord, a categoria do canal não pôde ser alterada imediatamente. Ela será alterada automaticamente dentro de 10 minutos se o bot permanecer online.", "channelRenameSource":"A fonte deste erro é: {0}", "busy":"Não é possível usar este {0}!\nO ticket está sendo processado pelo bot no momento.\n\nPor favor, tente novamente em alguns segundos!", "closeBeforeMessage":"Este ticket não pode ser fechado/excluído antes de uma mensagem ser enviada por um usuário.", "closeBeforeAdminMessage":"Este ticket não pode ser fechado/excluído antes de uma mensagem ser enviada por um administrador ou membro do suporte.", - "unableToCreateTicket":"Você não pode criar um ticket." + "unableToCreateTicket":"Você não pode criar um ticket.", + "messageMissing":"Não foi possível localizar a mensagem da interação. Use o comando `{0}` em vez disso.", + "stateExpired":"Esta interação não é mais válida ou expirou. Use o comando `{0}` em vez disso. É normal receber este erro após uma grande atualização do Open Ticket.", + "panelStateExpired":"Este painel não é mais válido ou expirou. Crie um novo painel usando `{0}` para resolver o problema. É normal receber este erro após uma grande atualização do Open Ticket." }, "optionInvalidReasons":{ "stringRegex":"O valor não corresponde ao padrão!", @@ -387,6 +399,8 @@ "syntax":"Sintaxe", "originalName":"Nome Original", "newName":"Novo Nome", + "originalCategory":"Categoria Original", + "newCategory":"Nova Categoria", "until":"Até", "validOptions":"Opções Válidas", "validPanels":"Painéis Válidos", @@ -408,6 +422,8 @@ "participants":"PARTICIPANTES", "yes":"Sim", "no":"Não", + "accept":"Aceitar", + "cancel":"Cancelar", "option":"Opção", "topic":"Tópico", "uptime":"Tempo de Atividade do Sistema", @@ -439,6 +455,7 @@ "panelAutoUpdate":"Você quer que este painel atualize automaticamente quando editado?", "ticket":"Crie um ticket instantaneamente.", "ticketId":"O identificador do ticket que você deseja criar.", + "ticketOtherUser":"Criar um ticket para outro usuário.", "close":"Feche um ticket.", "delete":"Exclua um ticket.", "deleteNoTranscript":"Exclua este ticket sem criar uma transcrição.", @@ -504,7 +521,9 @@ "priorityGet":"Obter a prioridade do ticket.", "priorityList":"Obter uma lista de todos os tickets com seu status de prioridade.", "transfer":"Transferir a propriedade do ticket de um usuário para outro.", - "transferUser":"O usuário para transferir." + "transferUser":"O usuário para transferir.", + "transcripts":"Ver o histórico de transcrições de tickets de um usuário.", + "transcriptsUser":"O usuário para visualizar." }, "helpMenu":{ "help":"Obtenha uma lista de todos os comandos disponíveis.", @@ -594,7 +613,8 @@ "panel":{ "selectTicket":"Selecione seu ticket", "selectRole":"Selecione sua função", - "selectOption":"Selecione sua opção" + "selectOption":"Selecione sua opção", + "selectPriorityLevel":"Selecionar nível de prioridade" }, "priorities":{ "urgent":"Urgente", diff --git a/languages/romanian.json b/languages/romanian.json index 36e26ee..890b268 100644 --- a/languages/romanian.json +++ b/languages/romanian.json @@ -1,8 +1,8 @@ { "_TRANSLATION":{ - "otversion":"v4.1.3", + "otversion":"v4.2.0", "translators":["SankeDev"], - "lastedited":"16/02/2026", + "lastedited":"25/05/2026", "language":"Romanian", "automated":false }, @@ -99,7 +99,7 @@ "invalidButton":"Acest buton trebuie sa aiba cel putin un {0} sau {1}!", "unusedOption":"Optiunea {0} nu este folosita nicaieri!", "unusedQuestion":"Intrebarea {0} nu este folosita nicaieri!", - "dropdownOption":"Un meniul cu dropdown activat poate contine doar valori din categoria 'ticket'!", + "dropdownOption":"Un panou cu meniu derulant poate conține doar opțiuni de tipurile: 'ticket', 'role' sau 'sub-panel'.", "customInvalidVersion":"Versiunea specificată în configurația ta nu corespunde! Asigură-te că ai actualizat configurația la cea mai recentă versiune!" } }, @@ -118,7 +118,9 @@ "helpSwitchText":"Vezi Text Commands", "helpPage":"Pagina {0}", "withReason":"Cu motiv", - "withoutTranscript":"Fara Transcript" + "withoutTranscript":"Fara Transcript", + "blacklistAdd":"Adaugă Utilizatorul pe Lista Neagră", + "blacklistRemove":"Eliberează Utilizatorul" }, "titles":{ "created":"Ticket Creat", @@ -156,7 +158,8 @@ "topicSet":"Subiect Modificat", "prioritySet":"Prioritate Modificată", "priorityGet":"Prioritate Tichet", - "transfer":"Tichet Transferat" + "transfer":"Tichet Transferat", + "transcripts":"Istoric Transcrieri" }, "descriptions":{ "create":"Ticket-ul tau a fost creat cu succes! Apasa pe butonul de mai jos pentru a-l vizualiza!", @@ -249,7 +252,9 @@ "prioritySetLog":"Prioritatea acestui tichet a fost modificată la {0} de către {1}!", "prioritySetDm":"Prioritatea tichetului tău a fost modificată la {0} pe serverul nostru!", "roleUpdateLog":"{0} și-a actualizat rolurile!", - "roleUpdateDm":"Rolurile tale pe serverul nostru au fost actualizate!" + "roleUpdateDm":"Rolurile tale pe serverul nostru au fost actualizate!", + "topicSetLog":"Prioritatea acestui tichet a fost setată la {0} de către {1}.", + "topicSetDm":"Prioritatea tichetului tău a fost setată la {0}." } }, "transcripts":{ @@ -271,7 +276,9 @@ "continue":"Sterge fara Transcript", "backup":"Creeaza un Transcript de Backup", "error":"Ceva nu a mers bine cand s-a incercat creearea transcriptului.\nCum doresti sa continui?\n\nAcest ticket nu va fi modificat pana cand nu apesi pe unul dintre butoane.", - "title":"Eroare Transcriere" + "title":"Eroare Transcriere", + "noHistory":"Acest utilizator nu are încă nicio transcriere.", + "historyNotSupported":"Istoricul transcrierilor este momentan suportat doar cu HTML Transcripts.\nIstoricul transcrierilor text va fi disponibil în versiunile viitoare." }, "text":{ "messagesTitle":"MESAJE", @@ -298,6 +305,7 @@ "unknownPanel":"Panel Necunoscut", "notInGuild":"Nu este in server", "channelRename":"Nu se poate redenumi canalul", + "channelCategory":"Categoria Nu Poate Fi Schimbată", "busy":"Ticket ocupat", "permissionError":"Eroare de Permisiune" }, @@ -321,11 +329,15 @@ "deprecatedTicket":"Acest ticket este deprecat si nu mai poate fi folosit! Posibil sa fie dintr-o versiune veche Open Ticket!", "notInGuild":"Aceasta {0} poate fi folosita doar in server!", "channelRename":"Nu s-a putut redenumi canalul din cauza unor limitari Discord! Canalul va fi redenumit in 10 minute daca botul nu va fi repornit.", + "channelCategory":"Din cauza limitelor de rată Discord, categoria canalului nu a putut fi schimbată imediat. Va fi schimbată automat în 10 minute dacă botul rămâne online.", "channelRenameSource":"Sursa acestei erori este: {0}", "busy":"Nu se poate executa {0}!\nTicketul este in curs de procesare.\n\nTe rugam sa incerci din nou in cateva minute!", "closeBeforeMessage":"Acest tichet nu poate fi închis/șters înainte ca un mesaj să fi fost trimis de un utilizator.", "closeBeforeAdminMessage":"Acest tichet nu poate fi închis/șters înainte ca un mesaj să fi fost trimis de un administrator de tichete sau un membru al echipei de suport.", - "unableToCreateTicket":"Nu poți crea un tichet." + "unableToCreateTicket":"Nu poți crea un tichet.", + "messageMissing":"Nu s-a putut găsi mesajul interacțiunii. Folosește comanda `{0}` în schimb.", + "stateExpired":"Această interacțiune nu mai este validă sau a expirat. Folosește comanda `{0}` în schimb. Este normal să primești această eroare după o actualizare majoră Open Ticket.", + "panelStateExpired":"Acest panou nu mai este valid sau a expirat. Creează un nou panou folosind `{0}` pentru a rezolva problema. Este normal să primești această eroare după o actualizare majoră Open Ticket." }, "optionInvalidReasons":{ "stringRegex":"Valoarea nu respecta formatul necesar!", @@ -387,6 +399,8 @@ "syntax":"Sintaxa", "originalName":"Nume Original", "newName":"Nume Nou", + "originalCategory":"Categoria Originală", + "newCategory":"Categorie Nouă", "until":"Pana", "validOptions":"Optiuni Valide", "validPanels":"Panels Valide", @@ -408,6 +422,8 @@ "participants":"PARTICIPANȚI", "yes":"DA", "no":"NU", + "accept":"Acceptă", + "cancel":"Anulează", "option":"OPȚIUNE", "topic":"SUBIECT", "uptime":"TIM P FUNCȚIONARE SISTEM", @@ -439,6 +455,7 @@ "panelAutoUpdate":"Activeaza sau dezactiveaza actualizarea automata a panelului.", "ticket":"Creeaza instant un ticket.", "ticketId":"Id-ul ticketului pe care vrei sa il folosesti.", + "ticketOtherUser":"Creează un tichet pentru alt utilizator.", "close":"Inchide un ticket.", "delete":"Sterge un ticket.", "deleteNoTranscript":"Sterge un ticket fara a crea un transcript.", @@ -504,7 +521,9 @@ "priorityGet":"Obține prioritatea tichetului.", "priorityList":"Obține o listă cu toate tichetele și starea priorității lor.", "transfer":"Transferă proprietatea tichetului de la un utilizator la altul.", - "transferUser":"Utilizatorul către care se face transferul." + "transferUser":"Utilizatorul către care se face transferul.", + "transcripts":"Vezi istoricul transcrierilor tichetelor unui utilizator.", + "transcriptsUser":"Utilizatorul de vizualizat." }, "helpMenu":{ "help":"Afiseaza o lista cu toate comenzile disponibile.", @@ -594,7 +613,8 @@ "panel":{ "selectTicket":"Selectează tichetul tău", "selectRole":"Selectează rolul tău", - "selectOption":"Selectează opțiunea ta" + "selectOption":"Selectează opțiunea ta", + "selectPriorityLevel":"Selectează nivelul de prioritate" }, "priorities":{ "urgent":"Urgent", diff --git a/languages/russian.json b/languages/russian.json index adc1081..988ecb8 100644 --- a/languages/russian.json +++ b/languages/russian.json @@ -1,8 +1,8 @@ { "_TRANSLATION":{ - "otversion":"v4.1.3", + "otversion":"v4.2.0", "translators":["NoOneNook"], - "lastedited":"16/02/2026", + "lastedited":"25/05/2026", "language":"Russian", "automated":true }, @@ -99,7 +99,7 @@ "invalidButton":"Эта кнопка должна иметь хотя бы {0} или {1}!", "unusedOption":"Опция {0} нигде не используется!", "unusedQuestion":"Вопрос {0} нигде не используется!", - "dropdownOption":"Панель с включенным выпадающим списком может содержать только опции типа 'ticket'!", + "dropdownOption":"Панель с выпадающим списком может содержать только варианты типов: 'ticket', 'role' или 'sub-panel'.", "customInvalidVersion":"Указанная в конфигурации версия не совпадает! Убедитесь, что вы обновили конфигурацию до последней версии!" } }, @@ -118,7 +118,9 @@ "helpSwitchText":"Просмотреть текстовые команды", "helpPage":"Страница {0}", "withReason":"С причиной", - "withoutTranscript":"Без стенограммы" + "withoutTranscript":"Без стенограммы", + "blacklistAdd":"Добавить пользователя в чёрный список", + "blacklistRemove":"Разблокировать пользователя" }, "titles":{ "created":"Тикет создан", @@ -156,7 +158,8 @@ "topicSet":"Тема изменена", "prioritySet":"Приоритет изменён", "priorityGet":"Приоритет тикета", - "transfer":"Тикет передан" + "transfer":"Тикет передан", + "transcripts":"История транскриптов" }, "descriptions":{ "create":"Ваш тикет был создан. Нажмите кнопку ниже, чтобы получить к нему доступ!", @@ -249,7 +252,9 @@ "prioritySetLog":"Приоритет этого тикета был изменён на {0} пользователем {1}!", "prioritySetDm":"Приоритет вашего тикета был изменён на {0} на нашем сервере!", "roleUpdateLog":"{0} обновил(а) свои роли!", - "roleUpdateDm":"Ваши роли на нашем сервере были обновлены!" + "roleUpdateDm":"Ваши роли на нашем сервере были обновлены!", + "topicSetLog":"Приоритет этого тикета был установлен на {0} пользователем {1}.", + "topicSetDm":"Приоритет вашего тикета был установлен на {0}." } }, "transcripts":{ @@ -271,7 +276,9 @@ "continue":"Удалить без стенограммы", "backup":"Создать резервную стенограмму", "error":"Что-то пошло не так при попытке создать стенограмму.\nЧто вы хотите сделать?\n\nЭтот тикет не будет удален, пока вы не нажмете одну из этих кнопок.", - "title":"Ошибка транскрипта" + "title":"Ошибка транскрипта", + "noHistory":"У этого пользователя пока нет транскриптов.", + "historyNotSupported":"История транскриптов сейчас поддерживается только с HTML Transcripts.\nТекстовая история будет доступна в будущих версиях." }, "text":{ "messagesTitle":"СООБЩЕНИЯ", @@ -298,6 +305,7 @@ "unknownPanel":"Неизвестная панель", "notInGuild":"Не на сервере", "channelRename":"Невозможно переименовать канал", + "channelCategory":"Не удалось изменить категорию", "busy":"Тикет занят", "permissionError":"Ошибка доступа" }, @@ -321,11 +329,15 @@ "deprecatedTicket":"Текущий канал не является действительным тикетом! Возможно, это был тикет из старой версии Open Ticket!", "notInGuild":"Это {0} не работает в ЛС! Пожалуйста, попробуйте еще раз на сервере!", "channelRename":"Из-за ограничений Discord на частоту запросов, боту сейчас невозможно переименовать канал. Канал будет автоматически переименован через 10 минут, если бот не будет перезагружен.", + "channelCategory":"Из-за ограничений Discord по частоте запросов категория канала не может быть изменена сразу. Она будет автоматически изменена в течение 10 минут, если бот останется онлайн.", "channelRenameSource":"Источник этой ошибки: {0}", "busy":"Невозможно использовать это {0}!\nТикет в настоящее время обрабатывается ботом.\n\nПожалуйста, попробуйте еще раз через несколько секунд!", "closeBeforeMessage":"Этот тикет нельзя закрыть/удалить, пока пользователь не отправит сообщение.", "closeBeforeAdminMessage":"Этот тикет нельзя закрыть/удалить, пока администратор или сотрудник поддержки не отправит сообщение.", - "unableToCreateTicket":"Вы не можете создать тикет." + "unableToCreateTicket":"Вы не можете создать тикет.", + "messageMissing":"Не удалось найти сообщение взаимодействия. Используйте команду `{0}` вместо этого.", + "stateExpired":"Это взаимодействие больше не действительно или истекло. Используйте команду `{0}` вместо этого. Это нормально после крупного обновления Open Ticket.", + "panelStateExpired":"Эта панель больше не действительна или истекла. Создайте новую панель с помощью `{0}`, чтобы решить проблему. Это нормально после крупного обновления Open Ticket." }, "optionInvalidReasons":{ "stringRegex":"Значение не соответствует шаблону!", @@ -387,6 +399,8 @@ "syntax":"Синтаксис", "originalName":"Исходное имя", "newName":"Новое имя", + "originalCategory":"Исходная категория", + "newCategory":"Новая категория", "until":"До", "validOptions":"Действительные опции", "validPanels":"Действительные панели", @@ -408,6 +422,8 @@ "participants":"Участники", "yes":"Да", "no":"Нет", + "accept":"Принять", + "cancel":"Отмена", "option":"Опция", "topic":"Тема", "uptime":"Время работы системы", @@ -439,6 +455,7 @@ "panelAutoUpdate":"Хотите ли вы, чтобы эта панель автоматически обновлялась при редактировании?", "ticket":"Мгновенно создать тикет.", "ticketId":"Идентификатор тикета, который вы хотите создать.", + "ticketOtherUser":"Создать тикет для другого пользователя.", "close":"Закрыть тикет.", "delete":"Удалить тикет.", "deleteNoTranscript":"Удалить этот тикет без создания стенограммы.", @@ -504,7 +521,9 @@ "priorityGet":"Получить приоритет тикета.", "priorityList":"Получить список всех тикетов с их приоритетом.", "transfer":"Передать владение тикетом от одного пользователя другому.", - "transferUser":"Пользователь, которому передать тикет." + "transferUser":"Пользователь, которому передать тикет.", + "transcripts":"Просмотреть историю транскриптов тикетов пользователя.", + "transcriptsUser":"Пользователь для просмотра." }, "helpMenu":{ "help":"Получите список всех доступных команд.", @@ -594,7 +613,8 @@ "panel":{ "selectTicket":"Выберите ваш тикет", "selectRole":"Выберите вашу роль", - "selectOption":"Выберите ваш вариант" + "selectOption":"Выберите ваш вариант", + "selectPriorityLevel":"Выберите уровень приоритета" }, "priorities":{ "urgent":"Срочно", diff --git a/languages/simplified-chinese.json b/languages/simplified-chinese.json index 07457d7..3f64ccd 100644 --- a/languages/simplified-chinese.json +++ b/languages/simplified-chinese.json @@ -1,8 +1,8 @@ { "_TRANSLATION":{ - "otversion":"v4.1.3", + "otversion":"v4.2.0", "translators":["HanumeshGupta","ChatGPT"], - "lastedited":"16/02/2026", + "lastedited":"25/05/2026", "language":"Simplified Chainese", "automated":true }, @@ -99,7 +99,7 @@ "invalidButton":"此按钮必须至少有一个{0}或{1}!", "unusedOption":"选项{0}未在任何地方使用!", "unusedQuestion":"问题{0}未在任何地方使用!", - "dropdownOption":"启用下拉菜单的面板只能包含“ticket”类型的选项!", + "dropdownOption":"带下拉菜单的面板只能包含以下类型的选项:'ticket'、'role' 或 'sub-panel'。", "customInvalidVersion":"配置文件中指定的版本不匹配!请确保您已将配置更新至最新版本!" } }, @@ -118,7 +118,9 @@ "helpSwitchText":"查看文本命令", "helpPage":"第{0}页", "withReason":"带原因", - "withoutTranscript":"不带记录" + "withoutTranscript":"不带记录", + "blacklistAdd":"添加用户至黑名单", + "blacklistRemove":"释放用户" }, "titles":{ "created":"工单已创建", @@ -156,7 +158,8 @@ "topicSet":"主题已更改", "prioritySet":"优先级已更改", "priorityGet":"工单优先级", - "transfer":"工单已转移" + "transfer":"工单已转移", + "transcripts":"记录历史" }, "descriptions":{ "create":"您的工单已创建。点击下方按钮访问!", @@ -249,7 +252,9 @@ "prioritySetLog":"此工单的优先级已由 {1} 更改为 {0}!", "prioritySetDm":"您在服务器中的工单优先级已更改为 {0}!", "roleUpdateLog":"{0} 已更新其角色!", - "roleUpdateDm":"您在服务器中的角色已更新!" + "roleUpdateDm":"您在服务器中的角色已更新!", + "topicSetLog":"此工单的优先级已由 {1} 设置为 {0}。", + "topicSetDm":"你的工单优先级已设置为 {0}。" } }, "transcripts":{ @@ -271,7 +276,9 @@ "continue":"删除无记录", "backup":"创建备份记录", "error":"创建记录时出错。\n您想怎么做?\n\n在您点击以下按钮之前,此工单不会被删除。", - "title":"记录错误" + "title":"记录错误", + "noHistory":"该用户目前没有任何记录。", + "historyNotSupported":"目前仅支持 HTML Transcripts 的记录历史。\n文本记录历史将在未来版本中提供。" }, "text":{ "messagesTitle":"消息", @@ -298,6 +305,7 @@ "unknownPanel":"未知面板", "notInGuild":"不在服务器中", "channelRename":"无法重命名频道", + "channelCategory":"无法更改分类", "busy":"工单繁忙", "permissionError":"权限错误" }, @@ -321,11 +329,15 @@ "deprecatedTicket":"当前频道不是有效工单!可能是旧版Open Ticket的工单!", "notInGuild":"此{0}在私信中无效!请在服务器中重试!", "channelRename":"由于Discord速率限制,机器人目前无法重命名频道。如果机器人未重启,频道将在10分钟后自动重命名。", + "channelCategory":"由于 Discord 速率限制,频道分类无法立即更改。如果机器人保持在线,将在 10 分钟内自动更改。", "channelRenameSource":"此错误的来源是:{0}", "busy":"无法使用此{0}!\n工单当前正在被机器人处理。\n\n请几秒后重试!", "closeBeforeMessage":"用户发送消息之前,无法关闭或删除此工单。", "closeBeforeAdminMessage":"工单管理员或支持成员发送消息之前,无法关闭或删除此工单。", - "unableToCreateTicket":"您无法创建工单。" + "unableToCreateTicket":"您无法创建工单。", + "messageMissing":"无法找到交互消息。请改用命令 `{0}`。", + "stateExpired":"此交互已无效或已过期。请改用命令 `{0}`。在 Open Ticket 大版本更新后出现此错误是正常的。", + "panelStateExpired":"此面板已无效或已过期。请使用 `{0}` 创建新面板以解决问题。在 Open Ticket 大版本更新后出现此错误是正常的。" }, "optionInvalidReasons":{ "stringRegex":"值不符合模式!", @@ -387,6 +399,8 @@ "syntax":"语法", "originalName":"原名称", "newName":"新名称", + "originalCategory":"原始分类", + "newCategory":"新分类", "until":"直到", "validOptions":"有效选项", "validPanels":"有效面板", @@ -408,6 +422,8 @@ "participants":"参与者", "yes":"是", "no":"否", + "accept":"接受", + "cancel":"取消", "option":"选项", "topic":"主题", "uptime":"系统运行时间", @@ -439,6 +455,7 @@ "panelAutoUpdate":"您希望此面板在编辑时自动更新吗?", "ticket":"立即创建一个工单。", "ticketId":"您想要创建的工单的标识符。", + "ticketOtherUser":"为其他用户创建工单。", "close":"关闭一个工单。", "delete":"删除一个工单。", "deleteNoTranscript":"删除此工单而不创建记录。", @@ -504,7 +521,9 @@ "priorityGet":"获取工单的优先级。", "priorityList":"获取所有工单的优先级状态列表。", "transfer":"将工单所有权从一位用户转移至另一位用户。", - "transferUser":"要转移给的用户。" + "transferUser":"要转移给的用户。", + "transcripts":"查看用户的工单记录历史。", + "transcriptsUser":"要查看的用户。" }, "helpMenu":{ "help":"获取所有可用命令的列表。", @@ -594,7 +613,8 @@ "panel":{ "selectTicket":"选择您的工单", "selectRole":"选择您的角色", - "selectOption":"选择您的选项" + "selectOption":"选择您的选项", + "selectPriorityLevel":"选择优先级" }, "priorities":{ "urgent":"紧急", diff --git a/languages/slovenian.json b/languages/slovenian.json index 6ec6988..bcf03d8 100644 --- a/languages/slovenian.json +++ b/languages/slovenian.json @@ -1,8 +1,8 @@ { "_TRANSLATION":{ - "otversion":"v4.1.3", + "otversion":"v4.2.0", "translators":["HanumeshGupta","ChatGPT"], - "lastedited":"16/02/2026", + "lastedited":"25/05/2026", "language":"Solvenian", "automated":true }, @@ -99,7 +99,7 @@ "invalidButton":"Ta gumb mora imeti vsaj {0} ali {1}!", "unusedOption":"Možnost {0} se ne uporablja nikjer!", "unusedQuestion":"Vprašanje {0} se ne uporablja nikjer!", - "dropdownOption":"Panel z omogočenim spustnim menijem lahko vsebuje samo možnosti tipa 'ticket'!", + "dropdownOption":"Plošča z spustnim menijem lahko vsebuje samo možnosti tipov: 'ticket', 'role' ali 'sub-panel'.", "customInvalidVersion":"Različica, navedena v vaši konfiguraciji, se ne ujema! Prepričajte se, da ste konfiguracijo posodobili na najnovejšo različico!" } }, @@ -118,7 +118,9 @@ "helpSwitchText":"Prikaži Besedilne Ukaze", "helpPage":"Stran {0}", "withReason":"Z Razlogom", - "withoutTranscript":"Brez Prepisa" + "withoutTranscript":"Brez Prepisa", + "blacklistAdd":"Dodaj uporabnika na črno listo", + "blacklistRemove":"Sprosti uporabnika" }, "titles":{ "created":"Vstopnica Ustvarjena", @@ -156,7 +158,8 @@ "topicSet":"Tema spremenjena", "prioritySet":"Prioriteta spremenjena", "priorityGet":"Prioriteta zahtevka", - "transfer":"Zahtevek prenesen" + "transfer":"Zahtevek prenesen", + "transcripts":"Zgodovina prepisov" }, "descriptions":{ "create":"Vaša vstopnica je bila ustvarjena. Kliknite spodnji gumb za dostop do nje!", @@ -249,7 +252,9 @@ "prioritySetLog":"Prioriteta tega zahtevka je bila spremenjena v {0} s strani {1}!", "prioritySetDm":"Prioriteta vašega zahtevka je bila spremenjena v {0} v našem strežniku!", "roleUpdateLog":"{0} je posodobil svoje vloge!", - "roleUpdateDm":"Vaše vloge v našem strežniku so bile posodobljene!" + "roleUpdateDm":"Vaše vloge v našem strežniku so bile posodobljene!", + "topicSetLog":"Prioriteta tega zahtevka je bila nastavljena na {0} s strani {1}.", + "topicSetDm":"Prioriteta vašega zahtevka je bila nastavljena na {0}." } }, "transcripts":{ @@ -271,7 +276,9 @@ "continue":"Izbriši Brez Prepisa", "backup":"Ustvarili Varnostni Prepis", "error":"Prišlo je do napake med ustvarjanjem prepisa.\nKaj bi radi naredili?\n\nTa vstopnica ne bo izbrisana, dokler ne kliknete enega od teh gumbov.", - "title":"Napaka v prepisu" + "title":"Napaka v prepisu", + "noHistory":"Ta uporabnik še nima nobenih prepisov.", + "historyNotSupported":"Zgodovina prepisov je trenutno podprta samo z HTML Transcripts.\nZgodovina besedilnih prepisov bo na voljo v prihodnjih različicah." }, "text":{ "messagesTitle":"SPOROČILA", @@ -298,6 +305,7 @@ "unknownPanel":"Neznan Panel", "notInGuild":"Ni na Strežniku", "channelRename":"Ni mogoče Preimenovati Kanala", + "channelCategory":"Ni mogoče spremeniti kategorije", "busy":"Vstopnica je Zasedena", "permissionError":"Napaka dovoljenj" }, @@ -321,11 +329,15 @@ "deprecatedTicket":"Trenutni kanal ni veljavna vstopnica! Morda je vstopnica iz starejše različice Open Ticket!", "notInGuild":"Ta {0} ne deluje v zasebnih sporočilih! Poskusite znova na strežniku!", "channelRename":"Zaradi omejitev Discord-a trenutno ni mogoče preimenovati kanala. Kanal bo samodejno preimenovan v več kot 10 minutah, če bot ne bo ponovno zagnan.", + "channelCategory":"Zaradi Discord omejitev hitrosti kategorije kanala ni bilo mogoče takoj spremeniti. Če bot ostane na spletu, bo samodejno spremenjena v 10 minutah.", "channelRenameSource":"Vir te napake je: {0}", "busy":"Ni mogoče uporabiti tega {0}!\nVstopnica je trenutno v obdelavi.\n\nPoskusite znova čez nekaj sekund!", "closeBeforeMessage":"Tega zahtevka ni mogoče zapreti/izbrisati, preden uporabnik ne pošlje sporočila.", "closeBeforeAdminMessage":"Tega zahtevka ni mogoče zapreti/izbrisati, preden skrbnik zahtevkov ali član podpore ne pošlje sporočila.", - "unableToCreateTicket":"Ne morete ustvariti zahtevka." + "unableToCreateTicket":"Ne morete ustvariti zahtevka.", + "messageMissing":"Sporočila interakcije ni bilo mogoče najti. Namesto tega uporabite ukaz `{0}`.", + "stateExpired":"Ta interakcija ni več veljavna ali je potekla. Namesto tega uporabite ukaz `{0}`. To je normalno po večji posodobitvi Open Ticket.", + "panelStateExpired":"Ta plošča ni več veljavna ali je potekla. Ustvarite novo ploščo z uporabo `{0}` za rešitev težave. To je normalno po večji posodobitvi Open Ticket." }, "optionInvalidReasons":{ "stringRegex":"Vrednost se ne ujema z vzorcem!", @@ -387,6 +399,8 @@ "syntax":"Sintaksa", "originalName":"Prvotno ime", "newName":"Novo ime", + "originalCategory":"Izvirna kategorija", + "newCategory":"Nova kategorija", "until":"Do", "validOptions":"Veljavne možnosti", "validPanels":"Veljavni paneli", @@ -408,6 +422,8 @@ "participants":"Udeleženci", "yes":"Da", "no":"Ne", + "accept":"Sprejmi", + "cancel":"Prekliči", "option":"Možnost", "topic":"Tema", "uptime":"Čas delovanja sistema", @@ -439,6 +455,7 @@ "panelAutoUpdate":"Ali želite, da se ta panel samodejno posodobi, ko ga uredite?", "ticket":"Takoj ustvari vstopnico.", "ticketId":"Identifikator vstopnice, ki jo želite ustvariti.", + "ticketOtherUser":"Ustvari zahtevek za drugega uporabnika.", "close":"Zapri vstopnico.", "delete":"Izbriši vstopnico.", "deleteNoTranscript":"Izbriši to vstopnico brez ustvarjanja prepisa.", @@ -504,7 +521,9 @@ "priorityGet":"Pridobite prioriteto zahtevka.", "priorityList":"Pridobite seznam vseh zahtevkov z njihovim prioritetnim statusom.", "transfer":"Prenesite lastništvo zahtevka z enega uporabnika na drugega.", - "transferUser":"Uporabnik, na katerega želite prenesti." + "transferUser":"Uporabnik, na katerega želite prenesti.", + "transcripts":"Prikaži zgodovino prepisov zahtevkov uporabnika.", + "transcriptsUser":"Uporabnik za prikaz." }, "helpMenu":{ "help":"Pridobite seznam vseh razpoložljivih ukazov.", @@ -594,7 +613,8 @@ "panel":{ "selectTicket":"Izberite svoj zahtevek", "selectRole":"Izberite svojo vlogo", - "selectOption":"Izberite svojo možnost" + "selectOption":"Izberite svojo možnost", + "selectPriorityLevel":"Izberi raven prioritete" }, "priorities":{ "urgent":"Nujno", diff --git a/languages/spanish.json b/languages/spanish.json index f9d7335..ad905b7 100644 --- a/languages/spanish.json +++ b/languages/spanish.json @@ -1,8 +1,8 @@ { "_TRANSLATION":{ - "otversion":"v4.1.3", + "otversion":"v4.2.0", "translators":["Redactado","Josuens"], - "lastedited":"16/02/2026", + "lastedited":"25/05/2026", "language":"Spanish", "automated":false }, @@ -99,7 +99,7 @@ "invalidButton":"¡Este botón necesita al menos un {0} o un {1}!", "unusedOption":"¡La opción {0} no se usa en ningún lugar!", "unusedQuestion":"¡La pregunta {0} no se usa en ningún lugar!", - "dropdownOption":"¡Este panel de botóns solo puede contener opciones de tipo 'ticket'!", + "dropdownOption":"Un panel con menú desplegable solo puede contener opciones de los tipos: 'ticket', 'role' o 'sub-panel'.", "customInvalidVersion":"¡La versión especificada en tu configuración no coincide! ¡Asegúrate de haber actualizado la configuración a la versión más reciente!" } }, @@ -118,7 +118,9 @@ "helpSwitchText":"Ver los Comandos de Texto", "helpPage":"Página {0}", "withReason":"Con razón", - "withoutTranscript":"Sin transcripción" + "withoutTranscript":"Sin transcripción", + "blacklistAdd":"Añadir Usuario a la Lista Negra", + "blacklistRemove":"Liberar Usuario" }, "titles":{ "created":"Ticket Creado", @@ -156,7 +158,8 @@ "topicSet":"Tema Cambiado", "prioritySet":"Prioridad Cambiada", "priorityGet":"Prioridad del Ticket", - "transfer":"Ticket Transferido" + "transfer":"Ticket Transferido", + "transcripts":"Historial de Transcripciones" }, "descriptions":{ "create":"¡Tu ticket ha sido creado! Haz click en el botón abajo para verlo!", @@ -249,7 +252,9 @@ "prioritySetLog":"¡La prioridad de este ticket ha sido cambiada a {0} por {1}!", "prioritySetDm":"¡La prioridad de tu ticket ha sido cambiada a {0} en nuestro servidor!", "roleUpdateLog":"¡{0} ha actualizado sus roles!", - "roleUpdateDm":"¡Tus roles en nuestro servidor han sido actualizados!" + "roleUpdateDm":"¡Tus roles en nuestro servidor han sido actualizados!", + "topicSetLog":"La prioridad de este ticket ha sido establecida en {0} por {1}.", + "topicSetDm":"La prioridad de tu ticket ha sido establecida en {0}." } }, "transcripts":{ @@ -271,7 +276,9 @@ "continue":"Eliminar sin transcripción", "backup":"Crear respaldo de la transcripción", "error":"Algo salió mal al intentar subir la transcripción.\n¿Qué desearías hacer?\n\nEste ticket no será eliminado hasta que presiones uno de los botones.", - "title":"Error de Transcripción" + "title":"Error de Transcripción", + "noHistory":"Este usuario todavía no tiene transcripciones.", + "historyNotSupported":"El historial de transcripciones actualmente solo es compatible con HTML Transcripts.\nEl historial de transcripciones de texto estará disponible en futuras versiones." }, "text":{ "messagesTitle":"MENSAJES", @@ -298,6 +305,7 @@ "unknownPanel":"Panel Desconocido", "notInGuild":"No está en el Servidor", "channelRename":"No es posible renombrar el canal", + "channelCategory":"No Se Puede Cambiar la Categoría", "busy":"El ticket está siendo procesado", "permissionError":"Error de Permisos" }, @@ -321,11 +329,15 @@ "deprecatedTicket":"¡El canal actual no es un ticket válido! ¡Podría haber sido un ticket de una versión antigua de Open Ticket!", "notInGuild":"¡Este {0} no funciona en DM! ¡Por favor, inténtalo de nuevo en un servidor!", "channelRename":"Debido a los límites de velocidad de discord, actualmente es imposible para el bot renombrar el canal. El canal se renombrará automáticamente en 10 minutos si el bot no se reinicia.", + "channelCategory":"Debido a los límites de tasa de Discord, la categoría del canal no pudo cambiarse inmediatamente. Se cambiará automáticamente dentro de 10 minutos si el bot permanece en línea.", "channelRenameSource":"La fuente de este error es: {0}", "busy":"¡No se puede usar este {0}!\nEl ticket está siendo procesado actualmente por el bot.\n\n¡Por favor, inténtalo de nuevo en unos segundos!", "closeBeforeMessage":"Este ticket no puede cerrarse/eliminarse antes de que un usuario haya enviado un mensaje.", "closeBeforeAdminMessage":"Este ticket no puede cerrarse/eliminarse antes de que un administrador de tickets o miembro del equipo de soporte haya enviado un mensaje.", - "unableToCreateTicket":"No puedes crear un ticket." + "unableToCreateTicket":"No puedes crear un ticket.", + "messageMissing":"No se pudo localizar el mensaje de la interacción. Usa el comando `{0}` en su lugar.", + "stateExpired":"Esta interacción ya no es válida o ha expirado. Usa el comando `{0}` en su lugar. Es normal recibir este error después de una actualización importante de Open Ticket.", + "panelStateExpired":"Este panel ya no es válido o ha expirado. Crea un nuevo panel usando `{0}` para solucionar el problema. Es normal recibir este error después de una actualización importante de Open Ticket." }, "optionInvalidReasons":{ "stringRegex":"¡El valor no coincide con el patrón!", @@ -387,6 +399,8 @@ "syntax":"Sintaxis", "originalName":"Nombre Original", "newName":"Nuevo Nombre", + "originalCategory":"Categoría Original", + "newCategory":"Nueva Categoría", "until":"Hasta", "validOptions":"Operaciones Válidas", "validPanels":"Paneles Válidos", @@ -408,6 +422,8 @@ "participants":"PARTICIPANTES", "yes":"SÍ", "no":"NO", + "accept":"Aceptar", + "cancel":"Cancelar", "option":"OPCIÓN", "topic":"TEMA", "uptime":"TIEMPO DE ACTIVIDAD DEL SISTEMA", @@ -439,6 +455,7 @@ "panelAutoUpdate":"¿Quieres que este panel se actualice automáticamente cuando se edite?", "ticket":"Crea un ticket instantáneamente.", "ticketId":"El identificador del ticket que quieres crear.", + "ticketOtherUser":"Crear un ticket para otro usuario.", "close":"Cierra un ticket.", "delete":"Elimina un ticket.", "deleteNoTranscript":"Elimina este ticket sin crear una transcripción.", @@ -504,7 +521,9 @@ "priorityGet":"Obtén la prioridad del ticket.", "priorityList":"Obtén una lista de todos los tickets con su estado de prioridad.", "transfer":"Transfiere la propiedad del ticket de un usuario a otro.", - "transferUser":"El usuario al que transferir." + "transferUser":"El usuario al que transferir.", + "transcripts":"Ver el historial de transcripciones de tickets de un usuario.", + "transcriptsUser":"El usuario a visualizar." }, "helpMenu":{ "help":"Obtén una lista de todos los comandos disponibles.", @@ -594,7 +613,8 @@ "panel":{ "selectTicket":"Selecciona tu ticket", "selectRole":"Selecciona tu rol", - "selectOption":"Selecciona tu opción" + "selectOption":"Selecciona tu opción", + "selectPriorityLevel":"Seleccionar nivel de prioridad" }, "priorities":{ "urgent":"Urgente", diff --git a/languages/swedish.json b/languages/swedish.json index d71f506..4a11ce6 100644 --- a/languages/swedish.json +++ b/languages/swedish.json @@ -1,8 +1,8 @@ { "_TRANSLATION":{ - "otversion":"v4.1.3", + "otversion":"v4.2.0", "translators":["NoOneNook"], - "lastedited":"16/02/2026", + "lastedited":"25/05/2026", "language":"Svenska", "automated":false }, @@ -99,7 +99,7 @@ "invalidButton":"Denna knapp ska ha minst en {0} eller {1}!", "unusedOption":"Alternativet {0} används inte någonstans!", "unusedQuestion":"Frågan {0} används inte någonstans!", - "dropdownOption":"Ett panel med dropdown aktiverad kan endast innehålla alternativ av typen 'ärende'!", + "dropdownOption":"En panel med rullgardinsmeny kan endast innehålla alternativ av typerna: 'ticket', 'role' eller 'sub-panel'.", "customInvalidVersion":"Versionen som anges i din konfiguration matchar inte! Se till att du har uppdaterat konfigurationen till den senaste versionen!" } }, @@ -118,7 +118,9 @@ "helpSwitchText":"Visa Textkommandon", "helpPage":"Sida {0}", "withReason":"Med Orsak", - "withoutTranscript":"Utan Utskrift" + "withoutTranscript":"Utan Utskrift", + "blacklistAdd":"Svartlista Användare", + "blacklistRemove":"Frigör Användare" }, "titles":{ "created":"Ärende Skapat", @@ -156,7 +158,8 @@ "topicSet":"Ämne Ändrat", "prioritySet":"Prioritet Ändrad", "priorityGet":"Ticketprioritet", - "transfer":"Ticket Överfört" + "transfer":"Ticket Överfört", + "transcripts":"Transkriptshistorik" }, "descriptions":{ "create":"Ditt ärende har skapats. Klicka på knappen nedan för att få tillgång till det!", @@ -249,7 +252,9 @@ "prioritySetLog":"Prioriteten för denna ticket har ändrats till {0} av {1}!", "prioritySetDm":"Prioriteten för din ticket har ändrats till {0} på vår server!", "roleUpdateLog":"{0} har uppdaterat sina roller!", - "roleUpdateDm":"Dina roller på vår server har uppdaterats!" + "roleUpdateDm":"Dina roller på vår server har uppdaterats!", + "topicSetLog":"Prioriteten för detta ärende har satts till {0} av {1}.", + "topicSetDm":"Prioriteten för ditt ärende har satts till {0}." } }, "transcripts":{ @@ -271,7 +276,9 @@ "continue":"Radera Utan Utskrift", "backup":"Skapa Säkerhetskopieringsutskrift", "error":"Något gick fel vid försöket att skapa utskriften.\nVad vill du göra?\n\nDetta ärende kommer inte att raderas förrän du klickar på en av dessa knappar.", - "title":"Transkriptionsfel" + "title":"Transkriptionsfel", + "noHistory":"Denna användare har ännu inga transkript.", + "historyNotSupported":"Transkriptshistorik stöds för närvarande endast med HTML Transcripts.\nTexttranskriptshistorik kommer att finnas tillgänglig i framtida versioner." }, "text":{ "messagesTitle":"MEDDELANDEN", @@ -298,6 +305,7 @@ "unknownPanel":"Okänt Panel", "notInGuild":"Inte På Server", "channelRename":"Kan Inte Döpa Om Kanal", + "channelCategory":"Kunde Inte Ändra Kategori", "busy":"Ärendet Är Upptaget", "permissionError":"Behörighetsfel" }, @@ -321,11 +329,15 @@ "deprecatedTicket":"Den nuvarande kanalen är inte ett giltigt ärende! Det kan ha varit ett ärende från en äldre version av Open Ticket!", "notInGuild":"Denna {0} fungerar inte i DM! Försök igen på en server!", "channelRename":"På grund av Discords hastighetsbegränsningar är det för närvarande omöjligt för botten att döpa om kanalen. Kanalen kommer automatiskt att döpas om inom 10 minuter om botten inte startas om.", + "channelCategory":"På grund av Discords hastighetsbegränsningar kunde kanalens kategori inte ändras omedelbart. Den kommer att ändras automatiskt inom 10 minuter om botten förblir online.", "channelRenameSource":"Källan till detta fel är: {0}", "busy":"Kan inte använda denna {0}!\nÄrendet behandlas för närvarande av botten.\n\nFörsök igen om några sekunder!", "closeBeforeMessage":"Denna ticket kan inte stängas/raderas innan ett meddelande har skickats av en användare.", "closeBeforeAdminMessage":"Denna ticket kan inte stängas/raderas innan ett meddelande har skickats av en ticketadministratör eller supportmedlem.", - "unableToCreateTicket":"Du kan inte skapa en ticket." + "unableToCreateTicket":"Du kan inte skapa en ticket.", + "messageMissing":"Kunde inte hitta meddelandet för interaktionen. Använd kommandot `{0}` istället.", + "stateExpired":"Denna interaktion är inte längre giltig eller har gått ut. Använd kommandot `{0}` istället. Det är normalt att få detta fel efter en större Open Ticket-uppdatering.", + "panelStateExpired":"Denna panel är inte längre giltig eller har gått ut. Skapa en ny panel med `{0}` för att lösa problemet. Det är normalt att få detta fel efter en större Open Ticket-uppdatering." }, "optionInvalidReasons":{ "stringRegex":"Värdet matchar inte mönstret!", @@ -387,6 +399,8 @@ "syntax":"Syntax", "originalName":"Ursprungligt Namn", "newName":"Nytt Namn", + "originalCategory":"Originalkategori", + "newCategory":"Ny Kategori", "until":"Till", "validOptions":"Giltiga Alternativ", "validPanels":"Giltiga Paneler", @@ -408,6 +422,8 @@ "participants":"DELTAGARE", "yes":"JA", "no":"NEJ", + "accept":"Acceptera", + "cancel":"Avbryt", "option":"ALTERNATIV", "topic":"ÄMNE", "uptime":"SYSTEMETS DRIFTTID", @@ -439,6 +455,7 @@ "panelAutoUpdate":"Vill du att denna panel ska uppdateras automatiskt när den redigeras?", "ticket":"Skapa ett ärende direkt.", "ticketId":"Identifieraren för ärendet du vill skapa.", + "ticketOtherUser":"Skapa ett ärende för en annan användare.", "close":"Stäng ett ärende.", "delete":"Radera ett ärende.", "deleteNoTranscript":"Radera detta ärende utan att skapa en utskrift.", @@ -504,7 +521,9 @@ "priorityGet":"Hämta prioriteten för ticketen.", "priorityList":"Hämta en lista över alla tickets med deras prioritetsstatus.", "transfer":"Överför ticketens ägarskap från en användare till en annan.", - "transferUser":"Användaren att överföra till." + "transferUser":"Användaren att överföra till.", + "transcripts":"Visa en användares ärendetranskriptshistorik.", + "transcriptsUser":"Användaren att visa." }, "helpMenu":{ "help":"Få en lista över alla tillgängliga kommandon.", @@ -594,7 +613,8 @@ "panel":{ "selectTicket":"Välj din ticket", "selectRole":"Välj din roll", - "selectOption":"Välj ditt alternativ" + "selectOption":"Välj ditt alternativ", + "selectPriorityLevel":"Välj prioritetsnivå" }, "priorities":{ "urgent":"Brådskande", diff --git a/languages/tamil.json b/languages/tamil.json index 0a52857..1a9b1f4 100644 --- a/languages/tamil.json +++ b/languages/tamil.json @@ -1,8 +1,8 @@ { "_TRANSLATION":{ - "otversion":"v4.1.3", + "otversion":"v4.2.0", "translators":["HanumeshGupta","ChatGPT"], - "lastedited":"16/02/2026", + "lastedited":"25/05/2026", "language":"Tamil", "automated":true }, @@ -99,7 +99,7 @@ "invalidButton":"இந்த பொத்தானில் குறைந்தது {0} அல்லது {1} இருக்க வேண்டும்!", "unusedOption":"{0} விருப்பம் எங்கும் பயன்படுத்தப்படவில்லை!", "unusedQuestion":"{0} கேள்வி எங்கும் பயன்படுத்தப்படவில்லை!", - "dropdownOption":"டிராப்புடவ்ன் இயக்கப்பட்ட பேனலில் 'டிக்கெட்' வகையின் விருப்பங்கள் மட்டுமே இருக்க முடியும்!", + "dropdownOption":"டிராப்-டவுன் கொண்ட பேனலில் 'ticket', 'role' அல்லது 'sub-panel' வகை விருப்பங்களே இருக்க முடியும்.", "customInvalidVersion":"உங்கள் கான்பிக் குறிப்பிடும் பதிப்பு பொருந்தவில்லை! கான்பிகை புதிய பதிப்புக்கு புதுப்பித்தீர்களா என்பதை சரிபார்க்கவும்!" } }, @@ -118,7 +118,9 @@ "helpSwitchText":"உரை கட்டளைகள் பார்க்க", "helpPage":"பக்கம் {0}", "withReason":"காரணத்துடன்", - "withoutTranscript":"டிரான்ஸ்கிரிப்ட் இல்லாமல்" + "withoutTranscript":"டிரான்ஸ்கிரிப்ட் இல்லாமல்", + "blacklistAdd":"பயனரை கருப்புப் பட்டியலில் சேர்க்கவும்", + "blacklistRemove":"பயனரை விடுவிக்கவும்" }, "titles":{ "created":"டிக்கெட் உருவாக்கப்பட்டது", @@ -156,7 +158,8 @@ "topicSet":"தலைப்பு மாற்றப்பட்டது", "prioritySet":"முன்னுரிமை மாற்றப்பட்டது", "priorityGet":"டிக்கெட் முன்னுரிமை", - "transfer":"டிக்கெட் மாற்றப்பட்டது" + "transfer":"டிக்கெட் மாற்றப்பட்டது", + "transcripts":"பதிவுகள் வரலாறு" }, "descriptions":{ "create":"உங்கள் டிக்கெட் உருவாக்கப்பட்டது. அதை அணுக கீழே உள்ள பொத்தானை கிளிக் செய்யவும்!", @@ -249,7 +252,9 @@ "prioritySetLog":"இந்த டிக்கெட் முன்னுரிமை {1} மூலம் {0} ஆக மாற்றப்பட்டது!", "prioritySetDm":"உங்கள் டிக்கெட் முன்னுரிமை நமது சர்வரில் {0} ஆக மாற்றப்பட்டது!", "roleUpdateLog":"{0} அவர்களது பங்களிப்புகளை புதுப்பித்துள்ளார்!", - "roleUpdateDm":"நமது சர்வரில் உங்கள் பங்களிப்புகள் புதுப்பிக்கப்பட்டுள்ளன!" + "roleUpdateDm":"நமது சர்வரில் உங்கள் பங்களிப்புகள் புதுப்பிக்கப்பட்டுள்ளன!", + "topicSetLog":"இந்த டிக்கெட்டின் முன்னுரிமை {1} ஆல் {0} ஆக அமைக்கப்பட்டது.", + "topicSetDm":"உங்கள் டிக்கெட்டின் முன்னுரிமை {0} ஆக அமைக்கப்பட்டது." } }, "transcripts":{ @@ -271,7 +276,9 @@ "continue":"டிரான்ஸ்கிரிப்ட் இல்லாமல் நீக்கு", "backup":"காப்பு டிரான்ஸ்கிரிப்ட் உருவாக்கு", "error":"டிரான்ஸ்கிரிப்ட் உருவாக்க முயற்சிக்கும் போது ஏதோ தவறு நடந்தது.\nநீங்கள் என்ன செய்ய விரும்புகிறீர்கள்?\n\nஇந்த டிக்கெட் நீங்கள் இந்த பொத்தான்களில் ஒன்றை கிளிக் செய்யும் வரை நீக்கப்படாது.", - "title":"மொழிப்பெயர்ப்பு பிழை" + "title":"மொழிப்பெயர்ப்பு பிழை", + "noHistory":"இந்த பயனருக்கு இன்னும் எந்த பதிவுகளும் இல்லை.", + "historyNotSupported":"பதிவுகள் வரலாறு தற்போது HTML Transcripts உடன் மட்டுமே ஆதரிக்கப்படுகிறது.\nஉரை பதிவுகள் வரலாறு எதிர்கால பதிப்புகளில் கிடைக்கும்." }, "text":{ "messagesTitle":"செய்திகள்", @@ -298,6 +305,7 @@ "unknownPanel":"தெரியாத பேனல்", "notInGuild":"சேவையில் இல்லை", "channelRename":"சேனலை மறுபெயரிட முடியவில்லை", + "channelCategory":"வகையை மாற்ற முடியவில்லை", "busy":"டிக்கெட் பிஸியாக உள்ளது", "permissionError":"அனுமதி பிழை" }, @@ -321,11 +329,15 @@ "deprecatedTicket":"தற்போதைய சேனல் ஒரு சரியான டிக்கெட் அல்ல! இது பழைய ஓபன் டிக்கெட் பதிப்பிலிருந்து ஒரு டிக்கெட் ஆக இருக்கலாம்!", "notInGuild":"இந்த {0} DM இல் வேலை செய்யாது! தயவு செய்து ஒரு சேவையில் மீண்டும் முயற்சிக்கவும்!", "channelRename":"டிஸ்கார்ட் ரேட்லிமிட்கள் காரணமாக, போட் சேனலை மறுபெயரிடுவது தற்போது சாத்தியமில்லை. போட் ரீபூட் செய்யப்படாவிட்டால் சேனல் 10 நிமிடங்களுக்குள் தானாக மறுபெயரிடப்படும்.", + "channelCategory":"Discord வீத வரம்புகளால் சேனல் வகையை உடனடியாக மாற்ற முடியவில்லை. பாட்டு ஆன்லைனில் இருந்தால் 10 நிமிடங்களில் தானாக மாற்றப்படும்.", "channelRenameSource":"இந்த பிழையின் மூலம்: {0}", "busy":"இந்த {0} ஐ பயன்படுத்த முடியவில்லை!\nடிக்கெட் தற்போது போட் மூலம் செயலாக்கப்படுகிறது.\n\nதயவு செய்து சில விநாடிகள் காத்திருக்கவும்!", "closeBeforeMessage":"பயனர் ஒரு செய்தியை அனுப்பும் முன் இந்த டிக்கெட் மூடப்பட முடியாது/நீக்கப்பட முடியாது.", "closeBeforeAdminMessage":"டிக்கெட் நிர்வாகி அல்லது ஆதரவு உறுப்பினர் ஒரு செய்தி அனுப்பும் முன் இந்த டிக்கெட் மூடப்பட முடியாது/நீக்கப்பட முடியாது.", - "unableToCreateTicket":"நீங்கள் டிக்கெட் உருவாக்க முடியாது." + "unableToCreateTicket":"நீங்கள் டிக்கெட் உருவாக்க முடியாது.", + "messageMissing":"இணைப்பு செய்தியை கண்டுபிடிக்க முடியவில்லை. பதிலாக `{0}` கட்டளையை பயன்படுத்தவும்.", + "stateExpired":"இந்த இணைப்பு இனி செல்லுபடியாகாது அல்லது காலாவதியானது. பதிலாக `{0}` கட்டளையை பயன்படுத்தவும். Open Ticket பெரிய புதுப்பிப்புக்குப் பிறகு இது சாதாரணம்.", + "panelStateExpired":"இந்த பேனல் இனி செல்லுபடியாகாது அல்லது காலாவதியானது. பிரச்சினையை தீர்க்க `{0}` பயன்படுத்தி புதிய பேனலை உருவாக்கவும். Open Ticket பெரிய புதுப்பிப்புக்குப் பிறகு இது சாதாரணம்." }, "optionInvalidReasons":{ "stringRegex":"மதிப்பு மாதிரியுடன் பொருந்தவில்லை!", @@ -387,6 +399,8 @@ "syntax":"தொடரியல்", "originalName":"அசல் பெயர்", "newName":"புதிய பெயர்", + "originalCategory":"மூல வகை", + "newCategory":"புதிய வகை", "until":"வரை", "validOptions":"சரியான விருப்பங்கள்", "validPanels":"சரியான பேனல்கள்", @@ -408,6 +422,8 @@ "participants":"பங்கேற்பாளர்கள்", "yes":"ஆம்", "no":"இல்லை", + "accept":"ஏற்கவும்", + "cancel":"ரத்து செய்", "option":"விருப்பம்", "topic":"தலைப்பு", "uptime":"சிஸ்டம் இயங்கும் நேரம்", @@ -439,6 +455,7 @@ "panelAutoUpdate":"இந்த பேனல் திருத்தப்படும் போது தானாகவே புதுப்பிக்க விரும்புகிறீர்களா?", "ticket":"உடனடியாக ஒரு டிக்கெட்டை உருவாக்கவும்.", "ticketId":"நீங்கள் உருவாக்க விரும்பும் டிக்கெட்டின் அடையாளம்.", + "ticketOtherUser":"மற்றொரு பயனருக்காக டிக்கெட் உருவாக்கவும்.", "close":"ஒரு டிக்கெட்டை மூடவும்.", "delete":"ஒரு டிக்கெட்டை நீக்கவும்.", "deleteNoTranscript":"பதிவு இல்லாமல் இந்த டிக்கெட்டை நீக்கவும்.", @@ -504,7 +521,9 @@ "priorityGet":"டிக்கெட் முன்னுரிமையைப் பெறவும்.", "priorityList":"அனைத்து டிக்கெட்டுகளின் முன்னுரிமை நிலையைப் பெறுங்கள்.", "transfer":"ஒரு பயனரிடமிருந்து மற்றொரு பயனருக்கு டிக்கெட் உரிமையை மாற்றவும்.", - "transferUser":"மாற்ற வேண்டிய பயனர்." + "transferUser":"மாற்ற வேண்டிய பயனர்.", + "transcripts":"பயனரின் டிக்கெட் பதிவுகள் வரலாற்றைக் காண்க.", + "transcriptsUser":"காண வேண்டிய பயனர்." }, "helpMenu":{ "help":"கிடைக்கக்கூடிய அனைத்து கட்டளைகளின் பட்டியலைப் பெறவும்.", @@ -594,7 +613,8 @@ "panel":{ "selectTicket":"உங்கள் டிக்கெட்டை தேர்ந்தெடுக்கவும்", "selectRole":"உங்கள் பங்கைக் தேர்ந்தெடுக்கவும்", - "selectOption":"உங்கள் விருப்பத்தை தேர்ந்தெடுக்கவும்" + "selectOption":"உங்கள் விருப்பத்தை தேர்ந்தெடுக்கவும்", + "selectPriorityLevel":"முன்னுரிமை நிலையைத் தேர்ந்தெடுக்கவும்" }, "priorities":{ "urgent":"அவசரம்", diff --git a/languages/thai.json b/languages/thai.json index 71f1294..0210fdc 100644 --- a/languages/thai.json +++ b/languages/thai.json @@ -1,8 +1,8 @@ { "_TRANSLATION":{ - "otversion":"v4.1.3", + "otversion":"v4.2.0", "translators":["modshd"], - "lastedited":"16/02/2026", + "lastedited":"25/05/2026", "language":"Thai", "automated":false }, @@ -99,7 +99,7 @@ "invalidButton":"ปุ่มนี้ต้องมีอย่างน้อย {0} หรือ {1}", "unusedOption":"ตัวเลือก {0} ไม่ได้ใช้งานที่ไหนเลย", "unusedQuestion":"คำถาม {0} ไม่ได้ใช้งานที่ไหนเลย", - "dropdownOption":"แผงที่เปิดใช้งานตัวเลือกแบบเลื่อนลงสามารถประกอบด้วยตัวเลือกประเภท 'ticket' เท่านั้น", + "dropdownOption":"แผงที่มีเมนูดรอปดาวน์สามารถมีตัวเลือกประเภท: 'ticket', 'role' หรือ 'sub-panel' เท่านั้น", "customInvalidVersion":"เวอร์ชันที่ระบุในไฟล์ config ของคุณไม่ตรงกัน ตรวจสอบให้แน่ใจว่าคุณได้อัปเดตไฟล์ config เป็นเวอร์ชันล่าสุดแล้ว" } }, @@ -118,7 +118,9 @@ "helpSwitchText":"ดูคำสั่งแบบข้อความ", "helpPage":"หน้า {0}", "withReason":"ด้วยเหตุผล", - "withoutTranscript":"ไม่มีบันทึกการสนทนา" + "withoutTranscript":"ไม่มีบันทึกการสนทนา", + "blacklistAdd":"เพิ่มผู้ใช้ในบัญชีดำ", + "blacklistRemove":"ปลดผู้ใช้ออกจากบัญชีดำ" }, "titles":{ "created":"สร้างห้องติดต่อแล้ว", @@ -156,7 +158,8 @@ "topicSet":"เปลี่ยนหัวข้อแล้ว", "prioritySet":"เปลี่ยนลำดับความสำคัญแล้ว", "priorityGet":"ลำดับความสำคัญของห้องติดต่อ", - "transfer":"โอนห้องติดต่อแล้ว" + "transfer":"โอนห้องติดต่อแล้ว", + "transcripts":"ประวัติทรานสคริปต์" }, "descriptions":{ "create":"ห้องติดต่อของคุณถูกสร้างแล้ว คลิกปุ่มด้านล่างเพื่อไปยังห้องติดต่อ", @@ -249,7 +252,9 @@ "prioritySetLog":"ลำดับความสำคัญของห้องติดต่อนี้ถูกเปลี่ยนเป็น {0} โดย {1}", "prioritySetDm":"ลำดับความสำคัญของห้องติดต่อของคุณถูกเปลี่ยนเป็น {0} ในเซิร์ฟเวอร์ของเรา", "roleUpdateLog":"{0} ได้อัปเดตบทบาทของพวกเขาแล้ว", - "roleUpdateDm":"บทบาทของคุณในเซิร์ฟเวอร์ของเราได้รับการอัปเดตแล้ว" + "roleUpdateDm":"บทบาทของคุณในเซิร์ฟเวอร์ของเราได้รับการอัปเดตแล้ว", + "topicSetLog":"ลำดับความสำคัญของตั๋วนี้ถูกตั้งเป็น {0} โดย {1}", + "topicSetDm":"ลำดับความสำคัญของตั๋วของคุณถูกตั้งเป็น {0}" } }, "transcripts":{ @@ -271,7 +276,9 @@ "continue":"ลบโดยไม่สร้างตัวเก็บประวัติข้อความ", "backup":"สร้างสำรองตัวเก็บประวัติข้อความ", "error":"เกิดข้อผิดพลาดขณะพยายามสร้างตัวเก็บประวัติข้อความ\nคุณต้องการทำอย่างไรต่อไป?\n\nห้องติดต่อนี้จะไม่ถูกลบจนกว่าคุณจะคลิกปุ่มใดปุ่มหนึ่ง", - "title":"ข้อผิดพลาดในการบันทึกบทสนทนา" + "title":"ข้อผิดพลาดในการบันทึกบทสนทนา", + "noHistory":"ผู้ใช้นี้ยังไม่มีทรานสคริปต์", + "historyNotSupported":"ขณะนี้ประวัติทรานสคริปต์รองรับเฉพาะ HTML Transcripts เท่านั้น\nประวัติทรานสคริปต์แบบข้อความจะพร้อมใช้งานในเวอร์ชันถัดไป" }, "text":{ "messagesTitle":"ข้อความ", @@ -298,6 +305,7 @@ "unknownPanel":"แผงควบคุมที่ไม่รู้จัก", "notInGuild":"ไม่ได้อยู่ในเซิร์ฟเวอร์", "channelRename":"ไม่สามารถเปลี่ยนชื่อช่องได้", + "channelCategory":"ไม่สามารถเปลี่ยนหมวดหมู่ได้", "busy":"ห้องติดต่อยุ่งอยู่", "permissionError":"ข้อผิดพลาดด้านสิทธิ์" }, @@ -321,11 +329,15 @@ "deprecatedTicket":"ช่องนี้ไม่ใช่ห้องติดต่อที่ถูกต้อง อาจเป็นห้องติดต่อจากเวอร์ชันเก่าของ Open Ticket", "notInGuild":"คำสั่ง {0} นี้ใช้ไม่ได้ใน DM กรุณาลองใหม่ในเซิร์ฟเวอร์", "channelRename":"เนื่องจากข้อจำกัดของ Discord การเปลี่ยนชื่อช่องไม่สามารถทำได้ในขณะนี้ ช่องจะถูกเปลี่ยนชื่อโดยอัตโนมัติในเวลา 10 นาทีหากบอทไม่ถูกรีบูต", + "channelCategory":"เนื่องจากข้อจำกัดอัตราการใช้งานของ Discord หมวดหมู่ของช่องจึงไม่สามารถเปลี่ยนได้ทันที ระบบจะเปลี่ยนให้อัตโนมัติภายใน 10 นาทีหากบอทยังคงออนไลน์อยู่", "channelRenameSource":"แหล่งที่มาของข้อผิดพลาดนี้คือ: {0}", "busy":"ไม่สามารถใช้ {0} นี้ได้\nห้องติดต่อกกำลังถูกประมวลผลโดยบอท\n\nกรุณาลองใหม่ในอีกไม่กี่วินาที", "closeBeforeMessage":"ไม่สามารถปิด/ลบห้องติดต่อนี้ได้ก่อนที่ผู้ใช้จะส่งข้อความ", "closeBeforeAdminMessage":"ไม่สามารถปิด/ลบห้องติดต่อนี้ได้ก่อนที่ผู้ดูแลห้องติดต่อหรือสมาชิกฝ่ายสนับสนุนจะส่งข้อความ", - "unableToCreateTicket":"คุณไม่สามารถสร้างห้องติดต่อได้" + "unableToCreateTicket":"คุณไม่สามารถสร้างห้องติดต่อได้", + "messageMissing":"ไม่สามารถค้นหาข้อความของการโต้ตอบได้ โปรดใช้คำสั่ง `{0}` แทน", + "stateExpired":"การโต้ตอบนี้ไม่ถูกต้องอีกต่อไปหรือหมดอายุแล้ว โปรดใช้คำสั่ง `{0}` แทน เป็นเรื่องปกติที่จะพบข้อผิดพลาดนี้หลังจากการอัปเดต Open Ticket ครั้งใหญ่", + "panelStateExpired":"แผงนี้ไม่ถูกต้องอีกต่อไปหรือหมดอายุแล้ว สร้างแผงใหม่โดยใช้ `{0}` เพื่อแก้ไขปัญหา เป็นเรื่องปกติที่จะพบข้อผิดพลาดนี้หลังจากการอัปเดต Open Ticket ครั้งใหญ่" }, "optionInvalidReasons":{ "stringRegex":"ค่าที่ระบุไม่ตรงกับรูปแบบ", @@ -387,6 +399,8 @@ "syntax":"ไวยากรณ์", "originalName":"ชื่อเดิม", "newName":"ชื่อใหม่", + "originalCategory":"หมวดหมู่เดิม", + "newCategory":"หมวดหมู่ใหม่", "until":"จนถึง", "validOptions":"ตัวเลือกที่ถูกต้อง", "validPanels":"แผงควบคุมที่ถูกต้อง", @@ -408,6 +422,8 @@ "participants":"ผู้มีส่วนร่วม", "yes":"ใช่", "no":"ไม่", + "accept":"ยอมรับ", + "cancel":"ยกเลิก", "option":"ตัวเลือก", "topic":"หัวข้อ", "uptime":"เวลาทำงานของระบบ", @@ -439,6 +455,7 @@ "panelAutoUpdate":"คุณต้องการให้แผงนี้อัปเดตอัตโนมัติเมื่อมีการแก้ไขหรือไม่?", "ticket":"สร้างห้องติดต่อทันที", "ticketId":"ตัวระบุของห้องติดต่อที่คุณต้องการสร้าง", + "ticketOtherUser":"สร้างตั๋วสำหรับผู้ใช้อื่น", "close":"ปิดห้องติดต่อ", "delete":"ลบห้องติดต่อ", "deleteNoTranscript":"ลบห้องติดต่อนี้โดยไม่สร้างตัวเก็บข้อความ", @@ -504,7 +521,9 @@ "priorityGet":"รับลำดับความสำคัญของห้องติดต่อ", "priorityList":"รายการห้องติดต่อทั้งหมดพร้อมสถานะลำดับความสำคัญ", "transfer":"โอนความเป็นเจ้าของห้องติดต่อจากผู้ใช้หนึ่งไปยังอีกคนหนึ่ง", - "transferUser":"ผู้ใช้ที่จะโอนไป" + "transferUser":"ผู้ใช้ที่จะโอนไป", + "transcripts":"ดูประวัติทรานสคริปต์ตั๋วของผู้ใช้", + "transcriptsUser":"ผู้ใช้ที่ต้องการดู" }, "helpMenu":{ "help":"รับรายชื่อคำสั่งทั้งหมดที่มี", @@ -594,7 +613,8 @@ "panel":{ "selectTicket":"เลือกห้องติดต่อของคุณ", "selectRole":"เลือกบทบาทของคุณ", - "selectOption":"เลือกตัวเลือกของคุณ" + "selectOption":"เลือกตัวเลือกของคุณ", + "selectPriorityLevel":"เลือกระดับความสำคัญ" }, "priorities":{ "urgent":"เร่งด่วน", diff --git a/languages/traditional-chinese.json b/languages/traditional-chinese.json new file mode 100644 index 0000000..2292b2e --- /dev/null +++ b/languages/traditional-chinese.json @@ -0,0 +1,628 @@ +{ + "_TRANSLATION":{ + "otversion":"v4.2.0", + "translators":["me.october"], + "lastedited":"25/05/2026", + "language":"Traditional Chinese", + "automated":false + }, + "checker":{ + "system":{ + "typeError":"[錯誤]", + "headerOpenTicket":"開啟工單", + "typeWarning":"[警告]", + "typeInfo":"[信息]", + "headerConfigChecker":"配置檢查器", + "headerDescription":"檢查您的配置文件中的錯誤!", + "footerError":"機器人將無法啟動,直到所有{0}錯誤被修複!", + "footerWarning":"建議在啟動前修複所有{0}警告!", + "footerSupport":"支援:{0} - 文檔:{1}", + "compactInformation":"使用{0}獲取更多信息!", + "dataPath":"路徑", + "dataDocs":"文檔", + "dataMessages":"消息" + }, + "messages":{ + "stringTooShort":"此字符串不能少於{0}個字符!", + "stringTooLong":"此字符串不能超過{0}個字符!", + "stringLengthInvalid":"此字符串長度必須為{0}個字符!", + "stringStartsWith":"此字符串必須以{0}開頭!", + "stringEndsWith":"此字符串必須以{0}結尾!", + "stringContains":"此字符串必須包含{0}!", + "stringChoices":"此字符串隻能是以下值之一:{0}!", + "stringRegex":"此字符串無效!", + "stringInvertedContains":"此字符串不能包含 {0}!", + "stringLowercase":"此字符串必須全部使用小寫字母!", + "stringUppercase":"此字符串必須全部使用大寫字母!", + "stringSpecialCharacters":"此字符串不能包含任何特殊字符!(僅允許 a-z、0-9 和空格)", + "stringNoSpaces":"此字符串不能包含空格!", + "stringCapitalWord":"建議此字符串中的每個單詞都以大寫字母開頭!", + "stringCapitalSentence":"此字符串中的某些句子似乎冇有以大寫字母開頭!", + "stringPunctuation":"此字符串中的句子似乎冇有以標點符號結尾!", + + "numberTooShort":"此數字不能少於{0}位!", + "numberTooLong":"此數字不能超過{0}位!", + "numberLengthInvalid":"此數字長度必須為{0}位!", + "numberTooSmall":"此數字至少為{0}!", + "numberTooLarge":"此數字最多為{0}!", + "numberNotEqual":"此數字必須為{0}!", + "numberStep":"此數字必須是{0}的倍數!", + "numberStepOffset":"此數字必須是{0}的倍數,起始值為{1}!", + "numberStartsWith":"此數字必須以{0}開頭!", + "numberEndsWith":"此數字必須以{0}結尾!", + "numberContains":"此數字必須包含{0}!", + "numberChoices":"此數字隻能是以下值之一:{0}!", + "numberFloat":"此數字不能為小數!", + "numberNegative":"此數字不能為負數!", + "numberPositive":"此數字不能為正數!", + "numberZero":"此數字不能為零!", + "numberNan":"此數字不能為 NaN(非數字)!", + "numberInvertedContains":"此數字不能包含 {0}!", + + "booleanTrue":"此佈爾值不能為真!", + "booleanFalse":"此佈爾值不能為假!", + + "arrayEmptyDisabled":"此數組不允許為空!", + "arrayEmptyRequired":"此數組必須為空!", + "arrayTooShort":"此數組長度至少為{0}!", + "arrayTooLong":"此數組長度最多為{0}!", + "arrayLengthInvalid":"此數組長度必須為{0}!", + "arrayInvalidTypes":"此數組隻能包含以下類型:{0}!", + "arrayDouble":"此數組不允許重複值!", + + "discordInvalidId":"這是無效的Discord {0} ID!", + "discordInvalidIdOptions":"這是無效的Discord {0} ID!您還可以使用以下之一:{1}!", + "discordInvalidToken":"這是無效的Discord令牌(文法上)!", + "colorInvalid":"這是無效的十六進製顔色!", + "emojiTooShort":"此字符串至少需要{0}個錶情符號!", + "emojiTooLong":"此字符串最多隻能有{0}個錶情符號!", + "emojiCustom":"此錶情符號不能是自定義Discord錶情符號!", + "emojiInvalid":"這是無效的錶情符號!", + "urlInvalid":"此URL無效!", + "urlInvalidHttp":"This url can only use the https:// protocol!", + "urlInvalidProtocol":"This url can only use the http:// & https:// protocols!", + "urlInvalidHostname":"此URL的主機名不被允許!", + "urlInvalidExtension":"此URL的擴展名無效!請選擇:{0}!", + "urlInvalidPath":"此URL的路徑無效!", + "idNotUnique":"此ID不唯一,請使用其他ID!", + "idNonExistent":"ID {0}不存在!", + + "invalidType":"此屬性必須為類型:{0}!", + "propertyMissing":"此對象缺少屬性{0}!", + "propertyOptional":"此對象中的屬性{0}是可選的!", + "objectDisabled":"此對象已禁用,請使用{0}啟用!", + "nullInvalid":"此屬性不能為null!", + "switchInvalidType":"此屬性必須是以下類型之一:{0}!", + "objectSwitchInvalid":"此對象必須是以下類型之一:{0}!", + + "invalidLanguage":"這是無效的語言!", + "invalidButton":"此按鈕必須至少有一個{0}或{1}!", + "unusedOption":"選項{0}未在任何地方使用!", + "unusedQuestion":"問題{0}未在任何地方使用!", + "dropdownOption":"帶有下拉選單的面板只能包含以下類型的選項:'ticket'、'role' 或 'sub-panel'。", + "customInvalidVersion":"配置文件中指定的版本不匹配!請確保您已將配置更新至最新版本!" + } + }, + "actions":{ + "buttons":{ + "create":"訪問工單", + "close":"關閉工單", + "delete":"刪除工單", + "reopen":"重新開啟工單", + "claim":"認領工單", + "unclaim":"取消認領工單", + "pin":"置頂工單", + "unpin":"取消置頂工單", + "clear":"刪除工單", + "helpSwitchSlash":"檢視斜杠命令", + "helpSwitchText":"檢視文本命令", + "helpPage":"第{0}頁", + "withReason":"帶原因", + "withoutTranscript":"不帶記錄", + "blacklistAdd":"加入黑名單使用者", + "blacklistRemove":"解除使用者封鎖" + }, + "titles":{ + "created":"工單已創建", + "close":"工單已關閉", + "delete":"工單已刪除", + "reopen":"工單已重新開啟", + "claim":"工單已認領", + "unclaim":"工單已取消認領", + "pin":"工單已置頂", + "unpin":"工單已取消置頂", + "rename":"工單已重命名", + "move":"工單已移動", + "add":"工單用戶已添加", + "remove":"工單用戶已移除", + + "help":"可用命令", + "statsReset":"重置統計", + "blacklistAdd":"用戶已拉黑", + "blacklistRemove":"用戶已解禁", + "blacklistGet":"已拉黑用戶", + "blacklistView":"當前黑名單", + "blacklistAddDm":"已加入黑名單", + "blacklistRemoveDm":"已從黑名單移除", + "clear":"工單已清除", + "clearTickets":"清除工單", + "roles":"角色已更新", + + "autoclose":"工單自動關閉", + "autocloseEnabled":"自動關閉已啟用", + "autocloseDisabled":"自動關閉已禁用", + "autodelete":"工單自動刪除", + "autodeleteEnabled":"自動刪除已啟用", + "autodeleteDisabled":"自動刪除已禁用", + + "topicSet":"主題已更改", + "prioritySet":"優先級已更改", + "priorityGet":"工單優先級", + "transfer":"工單已轉移", + "transcripts":"轉錄歷史" + }, + "descriptions":{ + "create":"您的工單已創建。點選下方按鈕訪問!", + "close":"工單已成功關閉!", + "delete":"工單已成功刪除!", + "reopen":"工單已成功重新開啟!", + "claim":"工單已成功認領!", + "unclaim":"工單已成功取消認領!", + "pin":"工單已成功置頂!", + "unpin":"工單已成功取消置頂!", + "rename":"工單已成功重命名為{0}!", + "move":"工單已成功移動到{0}!", + "add":"{0}已成功添加到工單!", + "remove":"{0}已成功從工單移除!", + + "helpExplanation":"`<名稱>` => 必填參數\n`[名稱]` => 可選參數", + "statsReset":"機器人統計已成功重置!", + "statsError":"無法檢視工單統計!\n{0}不是工單!", + "blacklistAdd":"{0}已成功拉黑!", + "blacklistRemove":"{0}已成功解禁!", + "blacklistGetSuccess":"{0}當前已被拉黑!", + "blacklistGetEmpty":"{0}當前未被拉黑!", + "blacklistViewEmpty":"尚未有人被拉黑!", + "blacklistViewTip":"使用“/blacklist add”拉黑用戶!", + "clearVerify":"確定要刪除多個工單嗎?\n此操作無法撤銷!", + "clearReady":"{0}個工單已成功刪除!", + "rolesEmpty":"未更新任何角色!", + + "autocloseLeave":"此工單已自動關閉,因為創建者離開了服務器!", + "autocloseTimeout":"此工單已自動關閉,因為它已超過`{0}小時`未活動!", + "autodeleteLeave":"此工單已自動刪除,因為創建者離開了服務器!", + "autodeleteTimeout":"此工單已自動刪除,因為它已超過`{0}天`未活動!", + "autocloseEnabled":"此工單已啟用自動關閉!\n超過`{0}小時`未活動後將自動關閉!", + "autocloseDisabled":"此工單已禁用自動關閉!\n不再自動關閉!", + "autodeleteEnabled":"此工單已啟用自動刪除!\n超過`{0}天`未活動後將自動刪除!", + "autodeleteDisabled":"此工單已禁用自動刪除!\n不再自動刪除!", + + "ticketMessageLimit":"您隻能同時創建{0}個工單!", + "ticketMessageAutoclose":"此工單將在超過{0}小時未活動後自動關閉!", + "ticketMessageAutodelete":"此工單將在超過{0}天未活動後自動刪除!", + "panelReady":"麵闆已在後續消息中可用!\n此消息現在可以刪除!", + + "topicSet":"頻道主題已由 {0} 成功更改!", + "prioritySet":"工單優先級已由 {1} 成功更改為 {0}!", + "priorityGet":"此工單當前的優先級為 {0}。", + "transfer":"工單所有權已由 {2} 成功從 {0} 轉移至 {1}!" + }, + "modal":{ + "closePlaceholder":"您為什麼關閉此工單?", + "deletePlaceholder":"您為什麼刪除此工單?", + "reopenPlaceholder":"您為什麼重新開啟此工單?", + "claimPlaceholder":"您為什麼認領此工單?", + "unclaimPlaceholder":"您為什麼取消認領此工單?", + "pinPlaceholder":"您為什麼置頂此工單?", + "unpinPlaceholder":"您為什麼取消置頂此工單?" + }, + "logs":{ + "createLog":"{0}創建了一個新工單!", + "closeLog":"此工單已被{0}關閉!", + "closeDm":"您的工單已在我們的服務器中關閉!", + "deleteLog":"此工單已被{0}刪除!", + "deleteDm":"您的工單已在我們的服務器中刪除!", + "reopenLog":"此工單已被{0}重新開啟!", + "reopenDm":"您的工單已在我們的服務器中重新開啟!", + "claimLog":"此工單已被{0}認領!", + "claimDm":"您的工單已在我們的服務器中被認領!", + "unclaimLog":"此工單已被{0}取消認領!", + "unclaimDm":"您的工單已在我們的服務器中取消認領!", + "pinLog":"此工單已被{0}置頂!", + "pinDm":"您的工單已在我們的服務器中置頂!", + "unpinLog":"此工單已被{0}取消置頂!", + "unpinDm":"您的工單已在我們的服務器中取消置頂!", + "renameLog":"此工單已被{1}重命名為{0}!", + "renameDm":"您的工單已在我們的服務器中重命名為{0}!", + "moveLog":"此工單已被{1}移動到{0}!", + "moveDm":"您的工單已在我們的服務器中移動到{0}!", + "addLog":"{0}已被{1}添加到此工單!", + "addDm":"{0}已被添加到您的工單中!", + "removeLog":"{0}已被{1}從此工單移除!", + "removeDm":"{0}已被從您的工單中移除!", + + "blacklistAddLog":"{0}被{1}拉黑!", + "blacklistRemoveLog":"{0}被{1}從黑名單移除!", + "blacklistAddDm":"您已被我們的服務器拉黑!\n從現在起,您無法創建工單!", + "blacklistRemoveDm":"您已被我們的服務器解禁!\n現在您可以再次創建工單!", + "clearLog":"{0}個工單已被{1}刪除!", + + "transferLog":"此工單的所有權已由 {2} 從 {0} 轉移至 {1}!", + "transferDm":"您在服務器中的工單所有權已從 {0} 轉移至 {1}!", + "prioritySetLog":"此工單的優先級已由 {1} 更改為 {0}!", + "prioritySetDm":"您在服務器中的工單優先級已更改為 {0}!", + "roleUpdateLog":"{0} 已更新其角色!", + "roleUpdateDm":"您在服務器中的角色已更新!", + "topicSetLog":"此工單的優先級已由 {1} 設為 {0}。", + "topicSetDm":"你的工單優先級已設為 {0}。" + } + }, + "transcripts":{ + "success":{ + "visit":"檢視記錄", + "ready":"記錄已創建", + "textFileDescription":"這是已刪除工單的文本記錄!", + "htmlProgress":"請等待此HTML記錄處理完成...", + + "createdChannel":"服務器中已創建一個新的{0}記錄!", + "createdCreator":"已為您的工單創建一個新的{0}記錄!", + "createdParticipant":"您參與的工單中已創建一個新的{0}記錄!", + "createdActiveAdmin":"您作為管理員參與的工單中已創建一個新的{0}記錄!", + "createdEveryAdmin":"您曾擔任管理員的工單中已創建一個新的{0}記錄!", + "createdOther":"已創建一個新的{0}記錄!" + }, + "errors":{ + "retry":"重試", + "continue":"刪除無記錄", + "backup":"創建備份記錄", + "error":"創建記錄時出錯。\n您想怎麼做?\n\n在您點選以下按鈕之前,此工單不會被刪除。", + "title":"記錄錯誤", + "noHistory":"此使用者目前沒有任何轉錄記錄。", + "historyNotSupported":"目前僅支援 HTML Transcripts 的轉錄歷史。\n文字轉錄歷史將在未來版本提供。" + }, + "text":{ + "messagesTitle":"消息", + "embedTitle":"EMBED", + "fileTitle":"文件", + "fieldsTitle":"字段", + "reactionsTitle":"反應", + "statsTitle":"統計", + "emptyContent":"<內容為空>", + "noTitle":"<無標題>", + "noDesc":"<無描述>" + } + }, + "errors":{ + "titles":{ + "internalError":"內部錯誤", + "optionMissing":"命令選項缺失", + "optionInvalid":"命令選項無效", + "unknownCommand":"未知命令", + "noPermissions":"無權限", + "unknownTicket":"未知工單", + "deprecatedTicket":"過時工單", + "unknownOption":"未知選項", + "unknownPanel":"未知麵闆", + "notInGuild":"不在服務器中", + "channelRename":"無法重命名頻道", + "channelCategory":"無法更改分類", + "busy":"工單繁忙", + "permissionError":"權限錯誤" + }, + "descriptions":{ + "askForInfo":"請聯係此機器人的所有者獲取更多信息!", + "askForInfoResolve":"如果此問題在多次嘗試後仍未解決,請聯係此機器人的所有者。", + "internalError":"由於內部錯誤,無法回響此{0}!", + "optionMissing":"此命令缺少必填參數!", + "optionInvalid":"此命令中的參數無效!", + "optionInvalidChoose":"請選擇", + "unknownCommand":"請訪問幫助菜單獲取更多信息!", + "noPermissions":"您無權使用此{0}!", + "noPermissionsList":"所需權限:(其中之一)", + "noPermissionsCooldown":"您因冷卻時間無法使用此{0}!", + "noPermissionsBlacklist":"您因被拉黑無法使用此{0}!", + "noPermissionsLimitGlobal":"您無法創建工單,因為服務器已達到最大工單限製!", + "noPermissionsLimitGlobalUser":"您無法創建工單,因為您已達到最大工單限製!", + "noPermissionsLimitOption":"您無法創建工單,因為服務器已達到此選項的最大工單限製!", + "noPermissionsLimitOptionUser":"您無法創建工單,因為您已達到此選項的最大工單限製!", + "unknownTicket":"請在有效工單中重試此命令!", + "deprecatedTicket":"當前頻道不是有效工單!可能是舊版Open Ticket的工單!", + "notInGuild":"此{0}在私信中無效!請在服務器中重試!", + "channelRename":"由於Discord速率限製,機器人目前無法重命名頻道。如果機器人未重啟,頻道將在10分鍾後自動重命名。", + "channelCategory":"由於 Discord 速率限制,頻道分類無法立即更改。如果機器人保持在線,將在10分鐘內自動更新。", + "channelRenameSource":"此錯誤的來源是:{0}", + "busy":"無法使用此{0}!\n工單當前正在被機器人處理。\n\n請幾秒後重試!", + "closeBeforeMessage":"用戶發送消息之前,無法關閉或刪除此工單。", + "closeBeforeAdminMessage":"工單管理員或支援成員發送消息之前,無法關閉或刪除此工單。", + "unableToCreateTicket":"您無法創建工單。", + "messageMissing":"無法找到互動訊息。請改用指令 `{0}`。", + "stateExpired":"此互動已無效或已過期。請改用指令 `{0}`。在 Open Ticket 大型更新後出現此錯誤是正常的。", + "panelStateExpired":"此面板已無效或已過期。請使用 `{0}` 建立新面板以解決問題。在 Open Ticket 大型更新後出現此錯誤是正常的。" + }, + "optionInvalidReasons":{ + "stringRegex":"值不符合模式!", + "stringMinLength":"值至少需要{0}個字符!", + "stringMaxLength":"值最多需要{0}個字符!", + "numberInvalid":"無效數字!", + "numberMin":"數字至少為{0}!", + "numberMax":"數字最多為{0}!", + "numberDecimal":"數字不能為小數!", + "numberNegative":"數字不能為負數!", + "numberPositive":"數字不能為正數!", + "numberZero":"數字不能為零!", + "channelNotFound":"無法找到頻道!", + "userNotFound":"無法找到用戶!", + "roleNotFound":"無法找到角色!", + "memberNotFound":"無法找到用戶!", + "mentionableNotFound":"無法找到用戶或角色!", + "channelType":"無效的頻道類型!", + "notInGuild":"此選項需要您在服務器中!" + }, + "permissions":{ + "developer":"您需要是機器人的開發者。", + "owner":"您需要是服務器所有者。", + "admin":"您需要是服務器管理員。", + "moderator":"您需要是版主。", + "support":"您需要是支援團隊成員。", + "member":"您需要是成員。", + "discord-administrator":"您需要擁有`ADMINISTRATOR`權限。" + }, + "actionInvalid":{ + "close":"工單已關閉!", + "reopen":"工單未關閉!", + "claim":"工單已認領!", + "unclaim":"工單未認領!", + "pin":"工單已置頂!", + "unpin":"工單未置頂!", + "add":"此用戶已可訪問工單!", + "remove":"無法從此工單移除此用戶!" + } + }, + "params":{ + "uppercase":{ + "ticket":"工單", + "tickets":"工單", + "reason":"原因", + "creator":"創建者", + "remaining":"剩餘時間", + "added":"已添加", + "removed":"已移除", + "filter":"篩選", + "method":"方式", + "type":"類型", + "blacklisted":"已拉黑", + "panel":"麵闆", + "command":"命令", + "system":"係統", + "true":"是", + "false":"否", + "syntax":"文法", + "originalName":"原名稱", + "newName":"新名稱", + "originalCategory":"原始分類", + "newCategory":"新分類", + "until":"直到", + "validOptions":"有效選項", + "validPanels":"有效麵闆", + "autoclose":"自動關閉", + "autodelete":"自動刪除", + "startupDate":"啟動日期", + "version":"版本", + "name":"名稱", + "role":"角色", + "status":"狀態", + "claimed":"已認領", + "pinned":"已置頂", + "creationDate":"創建日期", + + "noone":"無人", + "open":"開啟", + "closed":"關閉", + "priority":"優先級", + "participants":"參與者", + "yes":"是", + "no":"否", + "accept":"接受", + "cancel":"取消", + "option":"選項", + "topic":"主題", + "uptime":"係統運行時間", + "messages":"消息", + "embeds":"嵌入", + "files":"文件", + "components":"組件", + "cooldown":"冷卻時間", + "maxTickets":"最大工單數", + "admins":"管理員", + "roles":"角色", + "size":"大小" + }, + "lowercase":{ + "text":"文本", + "html":"HTML", + "command":"命令", + "modal":"模態框", + "button":"按鈕", + "dropdown":"下拉菜單", + "method":"方式" + } + }, + "commands":{ + "reason":"指定一個可選原因,該原因將在日誌中可見。", + "help":"獲取所有可用命令的列錶。", + "panel":"生成帶有下拉菜單或按鈕的消息(用於工單創建)。", + "panelId":"您想要生成的麵闆的標識符。", + "panelAutoUpdate":"您希望此麵闆在編輯時自動更新嗎?", + "ticket":"立即創建一個工單。", + "ticketId":"您想要創建的工單的標識符。", + "ticketOtherUser":"為其他使用者建立工單。", + "close":"關閉一個工單。", + "delete":"刪除一個工單。", + "deleteNoTranscript":"刪除此工單而不創建記錄。", + "reopen":"重新開啟一個工單。", + "claim":"認領一個工單。", + "claimUser":"將此工單認領給其他人而不是您自己。", + "unclaim":"取消認領一個工單。", + "pin":"置頂一個工單。", + "unpin":"取消置頂一個工單。", + + "move":"移動一個工單。", + "moveId":"您想要移動到的選項的標識符。", + "rename":"重命名一個工單。", + "renameName":"此工單的新名稱。", + "add":"將用戶添加到工單。", + "addUser":"要添加的用戶。", + "remove":"從工單中移除用戶。", + "removeUser":"要移除的用戶。", + + "blacklist":"管理工單黑名單。", + "blacklistView":"檢視當前黑名單的列錶。", + "blacklistAdd":"將用戶添加到黑名單。", + "blacklistRemove":"從黑名單中移除用戶。", + "blacklistGet":"獲取被拉黑用戶的詳細信息。", + "blacklistGetUser":"要獲取詳細信息的用戶。", + "stats":"檢視機器人、成員或工單的統計信息。", + "statsReset":"重置機器人的所有統計信息(並從零開始計數)。", + "statsGlobal":"檢視全局統計信息。", + "statsUser":"檢視服務器中用戶的統計信息。", + "statsUserUser":"要檢視的用戶。", + "statsTicket":"檢視服務器中工單的統計信息。", + "statsTicketTicket":"要檢視的工單。", + + "clear":"同時刪除多個工單。", + "clearFilter":"清除工單的篩選條件。", + "clearFilters":{ + "all":"全部", + "open":"開啟", + "close":"關閉", + "claim":"已認領", + "unclaim":"未認領", + "pin":"已置頂", + "unpin":"未置頂", + "autoclose":"自動關閉" + }, + + "autoclose":"管理工單中的自動關閉。", + "autocloseDisable":"禁用此工單的自動關閉。", + "autocloseEnable":"啟用此工單的自動關閉。", + "autocloseEnableTime":"工單需要多少小時不活動才能自動關閉。", + "autodelete":"管理工單中的自動刪除。", + "autodeleteDisable":"禁用此工單的自動刪除。", + "autodeleteEnable":"啟用此工單的自動刪除。", + "autodeleteEnableTime":"工單需要多少天不活動才能自動刪除。", + + "topic":"管理工單頻道的主題。", + "topicSet":"設定工單頻道的主題。", + "topicValue":"頻道的新主題。", + "topicList":"獲取所有工單及其主題和統計信息的列錶。", + "priority":"管理工單的優先級。", + "prioritySet":"設定工單的優先級。", + "priorityValue":"頻道的優先級。", + "priorityGet":"獲取工單的優先級。", + "priorityList":"獲取所有工單的優先級狀態列錶。", + "transfer":"將工單所有權從一位用戶轉移至另一位用戶。", + "transferUser":"要轉移給的用戶。", + "transcripts":"查看使用者的工單轉錄歷史。", + "transcriptsUser":"要查看的使用者。" + }, + "helpMenu":{ + "help":"獲取所有可用命令的列錶。", + "ticket":"立即創建一個工單。", + "close":"關閉一個工單,這將禁用此頻道的寫入權限。", + "delete":"刪除一個工單,啟用時會創建記錄。", + "reopen":"重新開啟一個工單,這將重新啟用此頻道的寫入權限。", + "pin":"置頂一個工單。這將把工單移動到頂部,並在名稱前添加'📌'錶情符號。", + "unpin":"取消置頂一個工單。工單將保持當前位置,但會移除'📌'錶情符號。", + "move":"移動一個工單。這將更改此工單的類型。", + "rename":"重命名一個工單。這將更改此工單的頻道名稱。", + "claim":"認領一個工單。通過此操作,您可以告知團隊您正在處理此工單。", + "unclaim":"取消認領一個工單。通過此操作,您可以告知團隊此工單已空閒。", + "add":"將用戶添加到工單。這將允許用戶在此工單中讀取和寫入。", + "remove":"從工單中移除用戶。這將移除用戶在此工單中讀取和寫入的權限。", + "panel":"生成帶有下拉菜單或按鈕的消息(用於工單創建)。", + "blacklistView":"檢視當前黑名單的列錶。", + "blacklistAdd":"將用戶添加到黑名單。", + "blacklistRemove":"從黑名單中移除用戶。", + "blacklistGet":"獲取被拉黑用戶的詳細信息。", + "statsGlobal":"檢視全局統計信息。", + "statsTicket":"檢視服務器中工單的統計信息。", + "statsUser":"檢視服務器中用戶的統計信息。", + "statsReset":"重置機器人的所有統計信息(並從零開始計數)。", + "autocloseDisable":"禁用此工單的自動關閉。", + "autocloseEnable":"啟用此工單的自動關閉。", + "autodeleteDisable":"禁用此工單的自動刪除。", + "autodeleteEnable":"啟用此工單的自動刪除。", + "categories":{ + "general":"通用命令", + "basicTicket":"基礎工單命令", + "advancedTicket":"高級工單命令", + "userTicket":"用戶工單命令", + "admin":"管理員命令", + "advanced":"高級命令", + "extra":"額外命令" + } + }, + "stats":{ + "scopes":{ + "global":"全局統計", + "system":"係統統計", + "user":"用戶統計", + "ticket":"工單統計", + "participants":"參與者", + "messages":"消息" + }, + "properties":{ + "ticketsCreated":"工單已創建", + "ticketsClosed":"工單已關閉", + "ticketsDeleted":"工單已刪除", + "ticketsReopened":"工單已重新開啟", + "ticketsAutoclosed":"工單已自動關閉", + "ticketsClaimed":"工單已認領", + "ticketsPinned":"工單已置頂", + "ticketsMoved":"工單已移動", + "usersBlacklisted":"用戶已拉黑", + "transcriptsCreated":"記錄已創建", + "ticketsAutodeleted":"自動刪除的工單", + "ticketsTransferred":"已轉移的工單", + "ticketVolume":"工單量", + "averageTickets":"平均工單數/用戶", + "currentTickets":"當前工單", + "age":"工單時長", + "responseTime":"回響時間", + "resolutionTime":"解決時間", + "createdOn":"創建於", + "createdBy":"創建者", + "closedOn":"關閉於", + "closedBy":"關閉者", + "claimedOn":"認領於", + "claimedBy":"認領者", + "pinnedOn":"固定於", + "pinnedBy":"固定者", + "deletedOn":"刪除於", + "deletedBy":"刪除者" + }, + "roles":{ + "developer":"開發者", + "serverOwner":"服務器所有者", + "serverAdmin":"服務器管理員", + "moderator":"管理團隊", + "support":"支援團隊", + "member":"成員" + } + }, + "panel":{ + "selectTicket":"選擇您的工單", + "selectRole":"選擇您的角色", + "selectOption":"選擇您的選項", + "selectPriorityLevel":"選擇優先級" + }, + "priorities":{ + "urgent":"緊急", + "veryHigh":"非常高", + "high":"高", + "normal":"普通", + "low":"低", + "veryLow":"非常低", + "none":"無" + } +} \ No newline at end of file diff --git a/languages/turkish.json b/languages/turkish.json index ee66fc5..1e8dc15 100644 --- a/languages/turkish.json +++ b/languages/turkish.json @@ -1,8 +1,8 @@ { "_TRANSLATION":{ - "otversion":"v4.1.3", + "otversion":"v4.2.0", "translators":["palestinian"], - "lastedited":"16/02/2026", + "lastedited":"25/05/2026", "language":"Turkish", "automated":false }, @@ -99,7 +99,7 @@ "invalidButton":"Bu buton en az bir {0} veya {1} içermelidir!", "unusedOption":"Seçenek {0} hiçbir yerde kullanılmıyor!", "unusedQuestion":"Soru {0} hiçbir yerde kullanılmıyor!", - "dropdownOption":"Dropdown etkin olan bir panel yalnızca 'ticket' türündeki seçenekleri içerebilir!", + "dropdownOption":"Açılır menülü bir panel yalnızca şu türlerde seçenekler içerebilir: 'ticket', 'role' veya 'sub-panel'.", "customInvalidVersion":"Yapılandırmanızda belirtilen sürüm eşleşmiyor! Yapılandırmayı en son sürüme güncellediğinizden emin olun!" } }, @@ -118,7 +118,9 @@ "helpSwitchText":"Metin Komutlarını Görüntüle", "helpPage":"Sayfa {0}", "withReason":"Sebep ile", - "withoutTranscript":"Transkript Olmadan" + "withoutTranscript":"Transkript Olmadan", + "blacklistAdd":"Kullanıcıyı Kara Listeye Al", + "blacklistRemove":"Kullanıcıyı Serbest Bırak" }, "titles":{ "created":"Ticket Oluşturuldu", @@ -156,7 +158,8 @@ "topicSet":"Konu Değiştirildi", "prioritySet":"Öncelik Değiştirildi", "priorityGet":"Ticket Önceliği", - "transfer":"Ticket Aktarıldı" + "transfer":"Ticket Aktarıldı", + "transcripts":"Transkript Geçmişi" }, "descriptions":{ "create":"Ticket'iniz oluşturuldu. Aşağıdaki düğmeye tıklayarak erişebilirsiniz!", @@ -249,7 +252,9 @@ "prioritySetLog":"Bu ticketin önceliği {1} tarafından {0} olarak değiştirildi!", "prioritySetDm":"Ticketinizin önceliği sunucumuzda {0} olarak değiştirildi!", "roleUpdateLog":"{0} rollerini güncelledi!", - "roleUpdateDm":"Sunucumuzdaki rolleriniz güncellendi!" + "roleUpdateDm":"Sunucumuzdaki rolleriniz güncellendi!", + "topicSetLog":"Bu ticket'ın önceliği {1} tarafından {0} olarak ayarlandı.", + "topicSetDm":"Ticket'ınızın önceliği {0} olarak ayarlandı." } }, "transcripts":{ @@ -271,7 +276,9 @@ "continue":"Transkript Olmadan Sil", "backup":"Yedek Transkript Oluştur", "error":"Transkript oluşturulurken bir sorun oluştu.\nNe yapmak istersiniz?\n\nBu ticket silinmeyecek, bu butonlardan birine tıklamadan önce.", - "title":"Transkript Hatası" + "title":"Transkript Hatası", + "noHistory":"Bu kullanıcının henüz hiçbir transkripti yok.", + "historyNotSupported":"Transkript geçmişi şu anda yalnızca HTML Transcripts ile desteklenmektedir.\nMetin transkript geçmişi gelecekteki sürümlerde kullanılabilir olacaktır." }, "text":{ "messagesTitle":"MESAJLAR", @@ -298,6 +305,7 @@ "unknownPanel":"Bilinmeyen Panel", "notInGuild":"Sunucuda Değil", "channelRename":"Kanal Yeniden Adlandırılamadı", + "channelCategory":"Kategori Değiştirilemiyor", "busy":"Ticket Meşgul", "permissionError":"Yetki Hatası" }, @@ -321,11 +329,15 @@ "deprecatedTicket":"Mevcut kanal geçerli bir ticket değil! Bu, eski bir Open Ticket sürümünden kalmış olabilir!", "notInGuild":"Bu {0} DM'de çalışmaz! Lütfen bunu bir sunucuda tekrar deneyin!", "channelRename":"Discord hız limitleri nedeniyle, botun kanal adını değiştirmesi şu anda mümkün değil. Bot yeniden başlatılmazsa kanal 10 dakika içinde otomatik olarak yeniden adlandırılacaktır.", + "channelCategory":"Discord oran sınırları nedeniyle kanal kategorisi hemen değiştirilemedi. Bot çevrimiçi kalırsa 10 dakika içinde otomatik olarak değiştirilecektir.", "channelRenameSource":"Bu hatanın kaynağı: {0}", "busy":"Bu {0} işlemini kullanamıyorsunuz!\nTicket şu anda bot tarafından işleniyor.\n\nLütfen birkaç saniye içinde tekrar deneyin!", "closeBeforeMessage":"Bir kullanıcı tarafından mesaj gönderilmeden bu ticket kapatılamaz/silinemez.", "closeBeforeAdminMessage":"Bir ticket yöneticisi veya destek üyesi tarafından mesaj gönderilmeden bu ticket kapatılamaz/silinemez.", - "unableToCreateTicket":"Ticket oluşturamazsınız." + "unableToCreateTicket":"Ticket oluşturamazsınız.", + "messageMissing":"Etkileşim mesajı bulunamadı. Bunun yerine `{0}` komutunu kullanın.", + "stateExpired":"Bu etkileşim artık geçerli değil veya süresi doldu. Bunun yerine `{0}` komutunu kullanın. Büyük bir Open Ticket güncellemesinden sonra bu hatayı almak normaldir.", + "panelStateExpired":"Bu panel artık geçerli değil veya süresi doldu. Sorunu çözmek için `{0}` kullanarak yeni bir panel oluşturun. Büyük bir Open Ticket güncellemesinden sonra bu hatayı almak normaldir." }, "optionInvalidReasons":{ "stringRegex":"Değer desenle eşleşmiyor!", @@ -387,6 +399,8 @@ "syntax":"Sözdizimi", "originalName":"Orijinal İsim", "newName":"Yeni İsim", + "originalCategory":"Orijinal Kategori", + "newCategory":"Yeni Kategori", "until":"Kadar", "validOptions":"Geçerli Seçenekler", "validPanels":"Geçerli Paneller", @@ -408,6 +422,8 @@ "participants":"KATILIMCILAR", "yes":"EVET", "no":"HAYIR", + "accept":"Kabul Et", + "cancel":"İptal Et", "option":"SEÇENEK", "topic":"KONU", "uptime":"SİSTEM ÇALIŞMA SÜRESİ", @@ -439,6 +455,7 @@ "panelAutoUpdate":"Bu panelin düzenlendiğinde otomatik olarak güncellenmesini ister misiniz?", "ticket":"Anında bir ticket oluştur.", "ticketId":"Oluşturmak istediğiniz ticket'ın kimliği.", + "ticketOtherUser":"Başka bir kullanıcı için ticket oluştur.", "close":"Bir ticket'ı kapat.", "delete":"Bir ticket'ı sil.", "deleteNoTranscript":"Bu ticket'ı transkript oluşturmadan sil.", @@ -504,7 +521,9 @@ "priorityGet":"Ticket önceliğini al.", "priorityList":"Tüm ticketleri öncelik durumlarıyla birlikte listele.", "transfer":"Ticket sahipliğini bir kullanıcıdan diğerine aktar.", - "transferUser":"Aktarılacak kullanıcı." + "transferUser":"Aktarılacak kullanıcı.", + "transcripts":"Bir kullanıcının ticket transkript geçmişini görüntüle.", + "transcriptsUser":"Görüntülenecek kullanıcı." }, "helpMenu":{ "help":"Tüm mevcut komutların bir listesini alın.", @@ -594,7 +613,8 @@ "panel":{ "selectTicket":"Ticketinizi seçin", "selectRole":"Rolünüzü seçin", - "selectOption":"Seçeneğinizi seçin" + "selectOption":"Seçeneğinizi seçin", + "selectPriorityLevel":"Öncelik seviyesini seçin" }, "priorities":{ "urgent":"Acil", diff --git a/languages/ukrainian.json b/languages/ukrainian.json index 416da30..20a502e 100644 --- a/languages/ukrainian.json +++ b/languages/ukrainian.json @@ -1,8 +1,8 @@ { "_TRANSLATION":{ - "otversion":"v4.1.3", + "otversion":"v4.2.0", "translators":["Anderskiy"], - "lastedited":"16/02/2026", + "lastedited":"25/05/2026", "language":"Ukrainian", "automated":false }, @@ -99,7 +99,7 @@ "invalidButton":"Ця кнопка повинна мати принаймні {0} або {1}!", "unusedOption":"Опція {0} ніде не використовується!", "unusedQuestion":"Питання {0} ніде не використовується!", - "dropdownOption":"Панель з увімкненим випадаючим списком може містити лише опції типу «тікет»!", + "dropdownOption":"Панель із випадаючим списком може містити лише опції типів: 'ticket', 'role' або 'sub-panel'.", "customInvalidVersion":"Вказана версія у вашій конфігурації не збігається! Переконайтеся, що ви оновили конфігурацію до останньої версії!" } }, @@ -118,7 +118,9 @@ "helpSwitchText":"Перевірити текстові команди", "helpPage":"Cторінка {0}", "withReason":"З причиною", - "withoutTranscript":"Без транскрипції" + "withoutTranscript":"Без транскрипції", + "blacklistAdd":"Додати Користувача до Чорного Списку", + "blacklistRemove":"Звільнити Користувача" }, "titles":{ "created":"Тікет створений", @@ -156,7 +158,8 @@ "topicSet":"Тему змінено", "prioritySet":"Пріоритет змінено", "priorityGet":"Пріоритет Тікету", - "transfer":"Тікет передано" + "transfer":"Тікет передано", + "transcripts":"Історія Транскриптів" }, "descriptions":{ "create":"Ваш тікет був створений. Натисніть кнопку нижче, щоб отримати доступ до нього!", @@ -249,7 +252,9 @@ "prioritySetLog":"Пріоритет цього тікету змінено на {0} користувачем {1}!", "prioritySetDm":"Пріоритет вашого тікету змінено на {0} на нашому сервері!", "roleUpdateLog":"{0} оновив свої ролі!", - "roleUpdateDm":"Ваші ролі на нашому сервері оновлено!" + "roleUpdateDm":"Ваші ролі на нашому сервері оновлено!", + "topicSetLog":"Пріоритет цього тікета було встановлено на {0} користувачем {1}.", + "topicSetDm":"Пріоритет вашого тікета було встановлено на {0}." } }, "transcripts":{ @@ -271,7 +276,9 @@ "continue":"Видалення без транскрипції", "backup":"Створити резервну копію транскрипту", "error":"Під час спроби створити транскрипт щось пішло не так.\n\nЩо ви хочете зробити?\n\nЦей тікет не буде видалено, доки ви не натиснете одну з цих кнопок.", - "title":"Помилка транскрипта" + "title":"Помилка транскрипта", + "noHistory":"Цей користувач ще не має жодних транскриптів.", + "historyNotSupported":"Історія транскриптів наразі підтримується лише з HTML Transcripts.\nІсторія текстових транскриптів буде доступна в майбутніх версіях." }, "text":{ "messagesTitle":"ПОВІДОМЛЕННЯ", @@ -298,6 +305,7 @@ "unknownPanel":"Невідома панель", "notInGuild":"Відсутний на сервері", "channelRename":"Неможливо перейменовати канал", + "channelCategory":"Неможливо Змінити Категорію", "busy":"Тікет зайнято", "permissionError":"Помилка дозволу" }, @@ -321,11 +329,15 @@ "deprecatedTicket":"Поточний канал не є дійсним тікетом! Можливо, це тікет зі старої версії Open Ticket!", "notInGuild":"Це {0} не працює у ПП! Будь ласка, спробуйте ще раз на сервері!", "channelRename":"Через обмеження швидкості дискорду бот наразі не може перейменувати канал. Канал буде автоматично перейменовано через 10 хвилин, якщо бота не буде перезавантажено.", + "channelCategory":"Через обмеження Discord на частоту запитів категорію каналу не вдалося змінити одразу. Вона буде автоматично змінена протягом 10 хвилин, якщо бот залишатиметься онлайн.", "channelRenameSource":"Джерело цієї помилки: {0}", "busy":"Неможливо використати цей {0}!\nТікет наразі обробляється ботом.\n\nБудь ласка, повторіть спробу через кілька секунд!", "closeBeforeMessage":"Цей тікет не можна закрити/видалити до того, як користувач надішле повідомлення.", "closeBeforeAdminMessage":"Цей тікет не можна закрити/видалити до того, як адміністратор тікетів або член підтримки надішле повідомлення.", - "unableToCreateTicket":"Ви не можете створити тікет." + "unableToCreateTicket":"Ви не можете створити тікет.", + "messageMissing":"Не вдалося знайти повідомлення взаємодії. Використайте команду `{0}` замість цього.", + "stateExpired":"Ця взаємодія більше не є дійсною або термін її дії закінчився. Використайте команду `{0}` замість цього. Нормально отримувати цю помилку після великого оновлення Open Ticket.", + "panelStateExpired":"Ця панель більше не є дійсною або термін її дії закінчився. Створіть нову панель за допомогою `{0}`, щоб вирішити проблему. Нормально отримувати цю помилку після великого оновлення Open Ticket." }, "optionInvalidReasons":{ "stringRegex":"Значення не відповідає шаблону!", @@ -387,6 +399,8 @@ "syntax":"Синтаксіс", "originalName":"Оригінальне ім'я", "newName":"Нове ім'я", + "originalCategory":"Оригінальна Категорія", + "newCategory":"Нова Категорія", "until":"Доки", "validOptions":"Дійсні параметри", "validPanels":"Дійсні панелі", @@ -408,6 +422,8 @@ "participants":"УЧАСНИКИ", "yes":"Так", "no":"Ні", + "accept":"Прийняти", + "cancel":"Скасувати", "option":"ВАРІАНТ", "topic":"ТЕМА", "uptime":"Час роботи системи", @@ -439,6 +455,7 @@ "panelAutoUpdate":"Ви хочете, щоб ця панель автоматично оновлювалася при редагуванні?", "ticket":"Миттєве створення тікета.", "ticketId":"Ідентифікатор тікету, який ви хочете створити.", + "ticketOtherUser":"Створити тікет для іншого користувача.", "close":"Закрийте тікет.", "delete":"Видалити квиток.", "deleteNoTranscript":"Видаліть цей тікет без створення стенограми.", @@ -504,7 +521,9 @@ "priorityGet":"Отримати пріоритет тікету.", "priorityList":"Отримати список усіх тікетів з їхнім пріоритетом.", "transfer":"Передати власність тікету від одного користувача іншому.", - "transferUser":"Користувач, якому передати." + "transferUser":"Користувач, якому передати.", + "transcripts":"Переглянути історію транскриптів тікетів користувача.", + "transcriptsUser":"Користувач для перегляду." }, "helpMenu":{ "help":"Отримайте список усіх доступних команд.", @@ -594,7 +613,8 @@ "panel":{ "selectTicket":"Виберіть ваш тікет", "selectRole":"Виберіть вашу роль", - "selectOption":"Виберіть ваш варіант" + "selectOption":"Виберіть ваш варіант", + "selectPriorityLevel":"Виберіть рівень пріоритету" }, "priorities":{ "urgent":"Терміново", diff --git a/languages/vietnamese.json b/languages/vietnamese.json index 12d6318..29dbe26 100644 --- a/languages/vietnamese.json +++ b/languages/vietnamese.json @@ -1,8 +1,8 @@ { "_TRANSLATION":{ - "otversion":"v4.1.3", + "otversion":"v4.2.0", "translators":["ngocdiep2006"], - "lastedited":"16/02/2026", + "lastedited":"25/05/2026", "language":"Vietnamese", "automated":true }, @@ -99,7 +99,7 @@ "invalidButton":"Nút này cần có ít nhất một {0} hoặc {1}!", "unusedOption":"Tùy chọn {0} không được sử dụng ở bất kỳ đâu!", "unusedQuestion":"Câu hỏi {0} không được sử dụng ở bất kỳ đâu!", - "dropdownOption":"Bảng điều khiển có bật dropdown chỉ có thể chứa các tùy chọn thuộc loại 'ticket'!", + "dropdownOption":"Một bảng có menu thả xuống chỉ có thể chứa các tùy chọn loại: 'ticket', 'role' hoặc 'sub-panel'.", "customInvalidVersion":"Phiên bản được chỉ định trong cấu hình của bạn không khớp! Hãy đảm bảo bạn đã cập nhật cấu hình lên phiên bản mới nhất!" } }, @@ -118,7 +118,9 @@ "helpSwitchText":"Xem Lệnh Văn Bản", "helpPage":"Trang {0}", "withReason":"Có Lý Do", - "withoutTranscript":"Không Có Bản Ghi" + "withoutTranscript":"Không Có Bản Ghi", + "blacklistAdd":"Thêm người dùng vào danh sách đen", + "blacklistRemove":"Giải phóng người dùng" }, "titles":{ "created":"Ticket Đã Tạo", @@ -156,7 +158,8 @@ "topicSet":"Đã thay đổi chủ đề", "prioritySet":"Đã thay đổi mức ưu tiên", "priorityGet":"Mức ưu tiên của vé", - "transfer":"Đã chuyển vé" + "transfer":"Đã chuyển vé", + "transcripts":"Lịch sử bản ghi" }, "descriptions":{ "create":"Ticket của bạn đã được tạo. Nhấp vào nút bên dưới để truy cập!", @@ -249,7 +252,9 @@ "prioritySetLog":"Mức ưu tiên của vé này đã được {1} thay đổi thành {0}!", "prioritySetDm":"Mức ưu tiên của vé của bạn đã được thay đổi thành {0} trong máy chủ của chúng tôi!", "roleUpdateLog":"{0} đã cập nhật vai trò của họ!", - "roleUpdateDm":"Vai trò của bạn trong máy chủ của chúng tôi đã được cập nhật!" + "roleUpdateDm":"Vai trò của bạn trong máy chủ của chúng tôi đã được cập nhật!", + "topicSetLog":"Ưu tiên của ticket này đã được đặt thành {0} bởi {1}.", + "topicSetDm":"Ưu tiên ticket của bạn đã được đặt thành {0}." } }, "transcripts":{ @@ -271,7 +276,9 @@ "continue":"Xóa Không Có Bản Ghi", "backup":"Tạo Bản Ghi Dự Phòng", "error":"Đã xảy ra lỗi khi cố gắng tạo bản ghi.\nBạn muốn làm gì?\n\nTicket này sẽ không bị xóa cho đến khi bạn nhấp vào một trong các nút này.", - "title":"Lỗi bản ghi" + "title":"Lỗi bản ghi", + "noHistory":"Người dùng này chưa có bản ghi nào.", + "historyNotSupported":"Lịch sử bản ghi hiện chỉ được hỗ trợ với HTML Transcripts.\nLịch sử bản ghi văn bản sẽ có trong các phiên bản tương lai." }, "text":{ "messagesTitle":"TIN NHẮN", @@ -298,6 +305,7 @@ "unknownPanel":"Bảng Điều Khiển Không Xác Định", "notInGuild":"Không Ở Trong Máy Chủ", "channelRename":"Không Thể Đổi Tên Kênh", + "channelCategory":"Không thể thay đổi danh mục", "busy":"Ticket Đang Bận", "permissionError":"Lỗi quyền hạn" }, @@ -321,11 +329,15 @@ "deprecatedTicket":"Kênh hiện tại không phải là ticket hợp lệ! Nó có thể là một ticket từ phiên bản Open Ticket cũ!", "notInGuild":"{0} này không hoạt động trong DM! Vui lòng thử lại trong máy chủ!", "channelRename":"Do giới hạn tốc độ của discord, hiện tại bot không thể đổi tên kênh. Kênh sẽ tự động được đổi tên trong 10 phút nếu bot không được khởi động lại.", + "channelCategory":"Do giới hạn tốc độ của Discord, danh mục kênh không thể thay đổi ngay lập tức. Nó sẽ tự động được thay đổi trong vòng 10 phút nếu bot vẫn trực tuyến.", "channelRenameSource":"Nguồn gốc của lỗi này là: {0}", "busy":"Không thể sử dụng {0} này!\nTicket hiện đang được bot xử lý.\n\nVui lòng thử lại sau vài giây!", "closeBeforeMessage":"Không thể đóng/xóa vé này trước khi người dùng gửi tin nhắn.", "closeBeforeAdminMessage":"Không thể đóng/xóa vé này trước khi quản trị viên vé hoặc thành viên hỗ trợ gửi tin nhắn.", - "unableToCreateTicket":"Bạn không thể tạo vé." + "unableToCreateTicket":"Bạn không thể tạo vé.", + "messageMissing":"Không thể tìm thấy tin nhắn tương tác. Hãy sử dụng lệnh `{0}` thay thế.", + "stateExpired":"Tương tác này không còn hợp lệ hoặc đã hết hạn. Hãy sử dụng lệnh `{0}` thay thế. Điều này là bình thường sau bản cập nhật lớn của Open Ticket.", + "panelStateExpired":"Bảng này không còn hợp lệ hoặc đã hết hạn. Hãy tạo bảng mới bằng `{0}` để khắc phục sự cố. Điều này là bình thường sau bản cập nhật lớn của Open Ticket." }, "optionInvalidReasons":{ "stringRegex":"Giá trị không khớp với mẫu!", @@ -387,6 +399,8 @@ "syntax":"Cú Pháp", "originalName":"Tên Gốc", "newName":"Tên Mới", + "originalCategory":"Danh mục gốc", + "newCategory":"Danh mục mới", "until":"Đến", "validOptions":"Tùy Chọn Hợp Lệ", "validPanels":"Bảng Điều Khiển Hợp Lệ", @@ -408,6 +422,8 @@ "participants":"Người tham gia", "yes":"Có", "no":"Không", + "accept":"Chấp nhận", + "cancel":"Hủy", "option":"Tùy chọn", "topic":"Chủ đề", "uptime":"Thời gian hoạt động hệ thống", @@ -439,6 +455,7 @@ "panelAutoUpdate":"Bạn có muốn bảng điều khiển này tự động cập nhật khi được chỉnh sửa không?", "ticket":"Tạo ticket ngay lập tức.", "ticketId":"Mã định danh của ticket bạn muốn tạo.", + "ticketOtherUser":"Tạo ticket cho người dùng khác.", "close":"Đóng ticket.", "delete":"Xóa ticket.", "deleteNoTranscript":"Xóa ticket này mà không tạo bản ghi.", @@ -504,7 +521,9 @@ "priorityGet":"Lấy mức ưu tiên của vé.", "priorityList":"Lấy danh sách tất cả vé cùng với trạng thái ưu tiên.", "transfer":"Chuyển quyền sở hữu vé từ người dùng này sang người dùng khác.", - "transferUser":"Người dùng để chuyển đến." + "transferUser":"Người dùng để chuyển đến.", + "transcripts":"Xem lịch sử bản ghi ticket của người dùng.", + "transcriptsUser":"Người dùng cần xem." }, "helpMenu":{ "help":"Nhận danh sách tất cả các lệnh có sẵn.", @@ -594,7 +613,8 @@ "panel":{ "selectTicket":"Chọn vé của bạn", "selectRole":"Chọn vai trò của bạn", - "selectOption":"Chọn tùy chọn của bạn" + "selectOption":"Chọn tùy chọn của bạn", + "selectPriorityLevel":"Chọn mức độ ưu tiên" }, "priorities":{ "urgent":"Khẩn cấp", diff --git a/package.json b/package.json index 087e7b3..e80641c 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "open-ticket", "author": "DJdj Development", - "version": "4.1.3", + "version": "4.2.0", "description": "The most advanced open-source discord ticket bot with HTML transcripts, plugins, questions, claiming, pinning & more! Using discord.js v14 & JSON database! ", "keywords": [ "ticket-bot", @@ -19,20 +19,23 @@ "test": "node index.js --dev-config --dev-database --soft-plugins", "testsetup": "node index.js --cli --dev-config --dev-database --soft-plugins", "testnc": "node index.js --no-compile --dev-config --dev-database --soft-plugins", - "docs": "npx typedoc --options .docs/typedoc-config.json && node .docs/createDocs.js", - "mergelang": "node .docs/mergeTranslations.js" + "tools:mergelang": "bun run .tools/mergeTranslations.js", + "tools:sponsors": "bun run .tools/createSponsors.ts", + "tools:contributors": "bun run .tools/createContributors.ts", + "docker:build": ".tools/build-docker.sh djj123dj/open-ticket --no-push" }, - "type": "commonjs", + "type": "module", "license": "GPL-3.0-only", "dependencies": { - "@discordjs/rest": "^2.6.0", + "@discordjs/rest": "^2.6.1", + "@open-discord-bots/framework": "^1.0.0", "@types/node": "^22.5.0", "@types/terminal-kit": "^2.5.7", "ansis": "^4.2.0", - "discord.js": "^14.24.2", - "formatted-json-stringify": "^1.2.1", + "discord.js": "^14.26.4", + "formatted-json-stringify": "^1.3.2", "terminal-kit": "^3.1.2", - "typescript": "^5.9.3" + "typescript": "^6.0.3" }, "repository": { "type": "git", @@ -45,7 +48,7 @@ "homepage": "https://openticket.dj-dj.be", "imports": { "#opendiscord": "./dist/src/index.js", - "#opendiscord-types": "./dist/src/core/api/api.js" + "#opendiscord-types": "./dist/src/core/api.js" }, "funding": { "type": "individual", diff --git a/plugins/example-plugin/index.ts b/plugins/example-plugin/index.ts index 5441b05..68b5b6a 100644 --- a/plugins/example-plugin/index.ts +++ b/plugins/example-plugin/index.ts @@ -6,15 +6,15 @@ import * as discord from "discord.js" //// Enable it in the plugin.json file! //// ///////////////////////////////////////////// -if (utilities.project != "openticket") throw new api.ODPluginError("This plugin only works in Open Ticket!") +if (opendiscord.project != "openticket") throw new api.ODPluginError("This plugin only works in Open Ticket!") //Add Typescript autocomplete support for plugin data. (!!!OPTIONAL!!!) declare module "#opendiscord-types" { - export interface ODPluginManagerIds_Default { + export interface ODPluginManagerIdMappings { "example-plugin":api.ODPlugin } - export interface ODConfigManagerIds_Default { - "example-plugin:config":api.ODJsonConfig + export interface ODConfigManagerIdMappings { + "example-plugin:config":api.ODJsonConfig<{testVariable1:boolean,testVariable2:number,testVariable3:string}> } } @@ -30,7 +30,7 @@ opendiscord.events.get("onConfigLoad").listen((configManager) => { //Let's also log it to the console to let us know it worked! const ourConfig = configManager.get("example-plugin:config") opendiscord.log("The example config loaded successfully!","plugin",[ - {key:"var-1",value:ourConfig.data.testVariable1}, + {key:"var-1",value:ourConfig.data.testVariable1.toString()}, {key:"var-2",value:ourConfig.data.testVariable2.toString()}, {key:"var-3",value:ourConfig.data.testVariable3.toString()} ]) diff --git a/src/actions/addTicketUser.ts b/src/actions/addTicketUser.ts index d595597..27da7ce 100644 --- a/src/actions/addTicketUser.ts +++ b/src/actions/addTicketUser.ts @@ -1,15 +1,15 @@ /////////////////////////////////////// //TICKET ADD USER SYSTEM /////////////////////////////////////// -import {opendiscord, api, utilities} from "../index" +import {opendiscord, api, utilities, openticketUtils} from "../index.js" import * as discord from "discord.js" const generalConfig = opendiscord.configs.get("opendiscord:general") -export const registerActions = async () => { +export async function registerActions(){ opendiscord.actions.add(new api.ODAction("opendiscord:add-ticket-user")) opendiscord.actions.get("opendiscord:add-ticket-user").workers.add([ - new api.ODWorker("opendiscord:add-ticket-user",2,async (instance,params,source,cancel) => { + new api.ODWorker("opendiscord:add-ticket-user",2,async (instance,params,origin,cancel) => { const {guild,channel,user,ticket,reason,data} = params if (channel.isThread()) throw new api.ODSystemError("Unable to add user to ticket! Open Ticket doesn't support threads!") @@ -34,44 +34,31 @@ export const registerActions = async () => { opendiscord.log("Failed to add channel permission overwrites on add-ticket-user","error") } - //update ticket message - const ticketMessage = await opendiscord.tickets.getTicketMessage(ticket) - if (ticketMessage){ - try{ - ticketMessage.edit((await opendiscord.builders.messages.getSafe("opendiscord:ticket-message").build("other",{guild,channel,user,ticket})).message) - }catch(e){ - opendiscord.log("Unable to edit ticket message on ticket user adding!","error",[ - {key:"channel",value:"#"+channel.name}, - {key:"channelid",value:channel.id,hidden:true}, - {key:"messageid",value:ticketMessage.id}, - {key:"option",value:ticket.option.id.value,hidden:true} - ]) - opendiscord.debugfile.writeErrorMessage(new api.ODError(e,"uncaughtException")) - } - } + //update ticket message (no await) + openticketUtils.updateTicketMessage(guild,channel,user,ticket) //reply with new message - if (params.sendMessage) await channel.send((await opendiscord.builders.messages.getSafe("opendiscord:add-message").build(source,{guild,channel,user,ticket,reason,data})).message) + if (params.sendMessage) await channel.send((await opendiscord.builders.messages.getSafe("opendiscord:add-message").build(origin,{guild,channel,user,ticket,reason,data})).message) ticket.get("opendiscord:busy").value = false await opendiscord.events.get("afterTicketUserAdded").emit([ticket,user,data,channel,reason]) //update channel topic await opendiscord.actions.get("opendiscord:update-ticket-topic").run("ticket-action",{guild,channel,user,ticket,sendMessage:false,newTopic:null}) }), - new api.ODWorker("opendiscord:discord-logs",1,async (instance,params,source,cancel) => { + new api.ODWorker("opendiscord:discord-logs",1,async (instance,params,origin,cancel) => { const {guild,channel,user,ticket,reason,data} = params //to logs - if (generalConfig.data.system.logs.enabled && generalConfig.data.system.messages.adding.logs){ + if (generalConfig.data.logs.enabled && generalConfig.data.logs.logMessages.adding.logs){ const logChannel = opendiscord.posts.get("opendiscord:logs") - if (logChannel) logChannel.send(await opendiscord.builders.messages.getSafe("opendiscord:ticket-action-logs").build(source,{guild,channel,user,ticket,mode:"add",reason,additionalData:data})) + if (logChannel) logChannel.send(await opendiscord.builders.messages.getSafe("opendiscord:ticket-action-logs").build(origin,{guild,channel,user,ticket,mode:"add",reason,additionalData:data})) } //to dm const creator = await opendiscord.tickets.getTicketUser(ticket,"creator") - if (creator && generalConfig.data.system.messages.adding.dm) await opendiscord.client.sendUserDm(creator,await opendiscord.builders.messages.getSafe("opendiscord:ticket-action-dm").build(source,{guild,channel,user,ticket,mode:"add",reason,additionalData:data})) + if (creator && generalConfig.data.logs.logMessages.adding.dm) await opendiscord.client.sendUserDm(creator,await opendiscord.builders.messages.getSafe("opendiscord:ticket-action-dm").build(origin,{guild,channel,user,ticket,mode:"add",reason,additionalData:data})) }), - new api.ODWorker("opendiscord:logs",0,(instance,params,source,cancel) => { + new api.ODWorker("opendiscord:logs",0,(instance,params,origin,cancel) => { const {guild,channel,user,ticket,data} = params opendiscord.log(user.displayName+" added "+data.displayName+" to a ticket!","info",[ @@ -80,7 +67,7 @@ export const registerActions = async () => { {key:"channel",value:"#"+channel.name}, {key:"channelid",value:channel.id,hidden:true}, {key:"reason",value:params.reason ?? "/"}, - {key:"method",value:source} + {key:"method",value:origin} ]) }) ]) diff --git a/src/actions/calculateTicketCategory.ts b/src/actions/calculateTicketCategory.ts new file mode 100644 index 0000000..dcc2c49 --- /dev/null +++ b/src/actions/calculateTicketCategory.ts @@ -0,0 +1,129 @@ +/////////////////////////////////////// +//CALCULATE TICKET CATEGORY SYSTEM +/////////////////////////////////////// +import {opendiscord, api, utilities, openticketUtils} from "../index.js" +import * as discord from "discord.js" + +const generalConfig = opendiscord.configs.get("opendiscord:general") + +export async function registerActions(){ + opendiscord.actions.add(new api.ODAction("opendiscord:calculate-ticket-category")) + opendiscord.actions.get("opendiscord:calculate-ticket-category").workers.add([ + new api.ODWorker("opendiscord:default-category",2,async (instance,params,origin,cancel) => { + //handle default category + const {guild,user,channel,option,ticket,currentCategoryId} = params + + const defaultCategoryId = option.get("opendiscord:channel-category").value + if (!defaultCategoryId){ + //default category is disabled + instance.newCategoryId = null + instance.newCategoryMode = null + instance.newCategory = null + instance.shouldChangeCategory = (instance.newCategoryId !== currentCategoryId) + }else{ + const defaultCategory = await opendiscord.client.fetchGuildCategoryChannel(guild,defaultCategoryId) + if (defaultCategory){ + //default category is enabled + instance.newCategoryId = defaultCategoryId + instance.newCategoryMode = "default" + instance.newCategory = defaultCategory + instance.shouldChangeCategory = (instance.newCategoryId !== currentCategoryId) + }else{ + //default category is not found (do not switch categories) + opendiscord.log("Unable to find ticket category '"+defaultCategoryId+"' #1","error",[ + {key:"categoryid",value:defaultCategoryId}, + {key:"type",value:"default"} + ]) + instance.newCategoryId = null + instance.newCategoryMode = null + instance.newCategory = null + instance.shouldChangeCategory = false + } + } + }), + new api.ODWorker("opendiscord:close-category",1,async (instance,params,origin,cancel) => { + //handle close category + const {guild,user,channel,option,ticket,currentCategoryId} = params + if (!ticket) return + if (!ticket.get("opendiscord:closed").value) return + if (!generalConfig.data.ticketSystem.closedCategory.enabled) return + + const closeCategoryId = generalConfig.data.ticketSystem.closedCategory.categoryId + if (!closeCategoryId) return + const closeCategory = await opendiscord.client.fetchGuildCategoryChannel(guild,closeCategoryId) + if (closeCategory){ + //close category is enabled + instance.newCategoryId = closeCategoryId + instance.newCategoryMode = "close" + instance.newCategory = closeCategory + instance.shouldChangeCategory = (instance.newCategoryId !== currentCategoryId) + }else{ + //close category is not found (do not switch categories) + opendiscord.log("Unable to find ticket category '"+closeCategoryId+"' #2","error",[ + {key:"categoryid",value:closeCategoryId}, + {key:"type",value:"close"} + ]) + instance.newCategoryId = null + instance.newCategoryMode = null + instance.newCategory = null + instance.shouldChangeCategory = false + } + }), + new api.ODWorker("opendiscord:claim-category",0,async (instance,params,origin,cancel) => { + //handle claim category + const {guild,user,channel,option,ticket,currentCategoryId} = params + if (!ticket) return + if (!ticket.get("opendiscord:claimed").value) return + + const claimedCategoryIds = generalConfig.data.ticketSystem.claimedCategories + const claimCategoryId = claimedCategoryIds.find((c) => c.user == user.id)?.category + if (!claimCategoryId) return + const claimCategory = await opendiscord.client.fetchGuildCategoryChannel(guild,claimCategoryId) + if (claimCategory){ + //claim category is enabled + instance.newCategoryId = claimCategoryId + instance.newCategoryMode = "claim" + instance.newCategory = claimCategory + instance.shouldChangeCategory = (instance.newCategoryId !== currentCategoryId) + }else{ + //claim category is not found (do not switch categories) + opendiscord.log("Unable to find ticket category '"+claimCategoryId+"' #3","error",[ + {key:"categoryid",value:claimCategoryId}, + {key:"type",value:"claim"} + ]) + instance.newCategoryId = null + instance.newCategoryMode = null + instance.newCategory = null + instance.shouldChangeCategory = false + } + }), + new api.ODWorker("opendiscord:backup-category",-100,async (instance,params,origin,cancel) => { + //handle backup category + const {guild,user,channel,option,ticket,currentCategoryId} = params + if (!instance.newCategory || !instance.newCategoryId || !instance.shouldChangeCategory) return + if (instance.newCategory.children.cache.size < 50) return + if (!generalConfig.data.ticketSystem.backupCategory.enabled) return + + const backupCategoryId = generalConfig.data.ticketSystem.backupCategory.categoryId + if (!backupCategoryId) return + const backupCategory = await opendiscord.client.fetchGuildCategoryChannel(guild,backupCategoryId) + if (backupCategory){ + //backup category is enabled + instance.newCategoryId = backupCategoryId + instance.newCategoryMode = "backup" + instance.newCategory = backupCategory + instance.shouldChangeCategory = (instance.newCategoryId !== currentCategoryId) + }else{ + //backup category is not found (do not switch categories) + opendiscord.log("Unable to find ticket category '"+backupCategoryId+"' #4","error",[ + {key:"categoryid",value:backupCategoryId}, + {key:"type",value:"backup"} + ]) + instance.newCategoryId = null + instance.newCategoryMode = null + instance.newCategory = null + instance.shouldChangeCategory = false + } + }) + ]) +} \ No newline at end of file diff --git a/src/actions/calculateTicketName.ts b/src/actions/calculateTicketName.ts new file mode 100644 index 0000000..57930fc --- /dev/null +++ b/src/actions/calculateTicketName.ts @@ -0,0 +1,31 @@ +/////////////////////////////////////// +//CALCULATE TICKET NAME SYSTEM +/////////////////////////////////////// +import {opendiscord, api, utilities, openticketUtils} from "../index.js" +import * as discord from "discord.js" + +const generalConfig = opendiscord.configs.get("opendiscord:general") + +export async function registerActions(){ + opendiscord.actions.add(new api.ODAction("opendiscord:calculate-ticket-name")) + opendiscord.actions.get("opendiscord:calculate-ticket-name").workers.add([ + new api.ODWorker("opendiscord:calculate-ticket-name",0,async (instance,params,origin,cancel) => { + const {guild,user,channel,option,ticket,currentChannelName} = params + + //calculate base channel name + const channelPrefix = option.get("opendiscord:channel-prefix").value + const channelSuffix = (ticket) ? ticket.get("opendiscord:channel-suffix").value : (await opendiscord.options.suffix.getSuffixFromOption(option,user,guild) ?? "unknown") + const channelRenamed = (ticket && ticket.exists("opendiscord:channel-renamed")) ? ticket.get("opendiscord:channel-renamed").value : null + const baseChannelName = (channelRenamed) ? channelRenamed : channelPrefix+channelSuffix + + //calculate status emojis + const pinEmoji = (ticket && ticket.get("opendiscord:pinned").value) ? generalConfig.data.ticketSystem.pinEmoji : "" + const closeEmoji = (ticket && ticket.get("opendiscord:closed").value) ? generalConfig.data.ticketSystem.closeEmoji : "" + const priorityEmoji = (ticket) ? (opendiscord.priorities.getFromPriorityLevel(ticket.get("opendiscord:priority").value).channelEmoji ?? "") : "" + + instance.newChannelName = pinEmoji+closeEmoji+priorityEmoji+baseChannelName + instance.newChannelSuffix = channelSuffix + instance.shouldChangeName = (instance.newChannelName !== currentChannelName) + }) + ]) +} \ No newline at end of file diff --git a/src/actions/claimTicket.ts b/src/actions/claimTicket.ts index 502db01..e6a8c79 100644 --- a/src/actions/claimTicket.ts +++ b/src/actions/claimTicket.ts @@ -1,15 +1,16 @@ /////////////////////////////////////// //TICKET CLAIMING SYSTEM /////////////////////////////////////// -import {opendiscord, api, utilities} from "../index" +import {opendiscord, api, utilities, openticketUtils} from "../index.js" import * as discord from "discord.js" const generalConfig = opendiscord.configs.get("opendiscord:general") +const interactiveMsgState = opendiscord.states.get("opendiscord:interactive-message") -export const registerActions = async () => { +export async function registerActions(){ opendiscord.actions.add(new api.ODAction("opendiscord:claim-ticket")) opendiscord.actions.get("opendiscord:claim-ticket").workers.add([ - new api.ODWorker("opendiscord:claim-ticket",2,async (instance,params,source,cancel) => { + new api.ODWorker("opendiscord:claim-ticket",2,async (instance,params,origin,cancel) => { const {guild,channel,user,ticket,reason} = params if (channel.isThread()) throw new api.ODSystemError("Unable to claim ticket! Open Ticket doesn't support threads!") @@ -22,67 +23,66 @@ export const registerActions = async () => { ticket.get("opendiscord:busy").value = true //update stats - await opendiscord.stats.get("opendiscord:global").setStat("opendiscord:tickets-claimed",1,"increase") - await opendiscord.stats.get("opendiscord:user").setStat("opendiscord:tickets-claimed",user.id,1,"increase") + await opendiscord.statistics.get("opendiscord:global").setStat("opendiscord:tickets-claimed",1,"increase") + await opendiscord.statistics.get("opendiscord:user").setStat("opendiscord:tickets-claimed",user.id,1,"increase") - //update category + //calculate & update category if (typeof params.allowCategoryChange == "boolean" ? params.allowCategoryChange : true){ - const rawClaimCategory = ticket.option.get("opendiscord:channel-categories-claimed").value.find((c) => c.user == user.id) - const claimCategory = (rawClaimCategory) ? rawClaimCategory.category : null - if (claimCategory){ - try { - channel.setParent(claimCategory,{lockPermissions:false}) - ticket.get("opendiscord:category-mode").value = "claimed" - ticket.get("opendiscord:category").value = claimCategory - }catch(e){ - opendiscord.log("Unable to move ticket to 'claimed category'!","error",[ + const categoryResult = await opendiscord.actions.get("opendiscord:calculate-ticket-category").run("claim-ticket",{guild,user,option:ticket.option,channel,ticket,currentCategoryId:channel.parentId}) + if (categoryResult && categoryResult.shouldChangeCategory && typeof categoryResult.newCategoryId !== "undefined" && typeof categoryResult.newCategoryMode !== "undefined" && typeof categoryResult.newCategory !== "undefined"){ + const originalCategoryName = channel.parent?.name ?? "" + const newCategoryName = categoryResult.newCategory?.name ?? "" + try{ + await utilities.timedAwait(channel.setParent(categoryResult.newCategoryId,{lockPermissions:false}),3000,(err) => { + process.emit("uncaughtException",new Error("Error: Unable to change channel parent: "+err)) + }) + ticket.get("opendiscord:category-mode").value = categoryResult.newCategoryMode + ticket.get("opendiscord:category").value = categoryResult.newCategoryId + }catch(err){ + const sentMsg = await channel.send((await opendiscord.builders.messages.getSafe("opendiscord:error-channel-category").build("ticket-claim",{guild,channel,user,originalCategory:originalCategoryName,newCategory:newCategoryName})).message) + setTimeout(() => {if (sentMsg.deletable) sentMsg.delete()},7000) //autodelete error message + opendiscord.log("Unable to move ticket to claimed category.","error",[ {key:"channel",value:"#"+channel.name}, {key:"channelid",value:channel.id,hidden:true}, - {key:"categoryid",value:claimCategory} + {key:"categoryid",value:categoryResult.newCategoryId ?? "/"} ]) - opendiscord.debugfile.writeErrorMessage(new api.ODError(e,"uncaughtException")) } } } - //update ticket message - const ticketMessage = await opendiscord.tickets.getTicketMessage(ticket) - if (ticketMessage){ - try{ - ticketMessage.edit((await opendiscord.builders.messages.getSafe("opendiscord:ticket-message").build("other",{guild,channel,user,ticket})).message) - }catch(e){ - opendiscord.log("Unable to edit ticket message on ticket claiming!","error",[ - {key:"channel",value:"#"+channel.name}, - {key:"channelid",value:channel.id,hidden:true}, - {key:"messageid",value:ticketMessage.id}, - {key:"option",value:ticket.option.id.value} - ]) - opendiscord.debugfile.writeErrorMessage(new api.ODError(e,"uncaughtException")) - } - } + //update ticket message (no await) + openticketUtils.updateTicketMessage(guild,channel,user,ticket) //reply with new message - if (params.sendMessage) await channel.send((await opendiscord.builders.messages.getSafe("opendiscord:claim-message").build(source,{guild,channel,user,ticket,reason})).message) + if (params.sendMessage){ + const sentMsg = await channel.send((await opendiscord.builders.messages.getSafe("opendiscord:claim-message").build(origin,{guild,channel,user,ticket,reason})).message) + if (sentMsg) await interactiveMsgState.setMsgState({channel,message:sentMsg},{ + messageType:"claim-message", + messageOrigin:"other", + messageAuthor:user.id, + messageReason:reason + },false) + } ticket.get("opendiscord:busy").value = false await opendiscord.events.get("afterTicketClaimed").emit([ticket,user,channel,reason]) //update channel topic await opendiscord.actions.get("opendiscord:update-ticket-topic").run("ticket-action",{guild,channel,user,ticket,sendMessage:false,newTopic:null}) }), - new api.ODWorker("opendiscord:discord-logs",1,async (instance,params,source,cancel) => { + new api.ODWorker("opendiscord:discord-logs",1,async (instance,params,origin,cancel) => { const {guild,channel,user,ticket,reason} = params //to logs - if (generalConfig.data.system.logs.enabled && generalConfig.data.system.messages.claiming.logs){ + if (generalConfig.data.logs.enabled && generalConfig.data.logs.logMessages.claiming.logs){ const logChannel = opendiscord.posts.get("opendiscord:logs") - if (logChannel) logChannel.send(await opendiscord.builders.messages.getSafe("opendiscord:ticket-action-logs").build(source,{guild,channel,user,ticket,mode:"claim",reason,additionalData:null})) + if (logChannel) logChannel.send(await opendiscord.builders.messages.getSafe("opendiscord:ticket-action-logs").build(origin,{guild,channel,user,ticket,mode:"claim",reason,additionalData:null})) } //to dm const creator = await opendiscord.tickets.getTicketUser(ticket,"creator") - if (creator && generalConfig.data.system.messages.claiming.dm) await opendiscord.client.sendUserDm(creator,await opendiscord.builders.messages.getSafe("opendiscord:ticket-action-dm").build(source,{guild,channel,user,ticket,mode:"claim",reason,additionalData:null})) + if (creator && generalConfig.data.logs.logMessages.claiming.dm) await opendiscord.client.sendUserDm(creator,await opendiscord.builders.messages.getSafe("opendiscord:ticket-action-dm").build(origin,{guild,channel,user,ticket,mode:"claim",reason,additionalData:null})) }), - new api.ODWorker("opendiscord:logs",0,(instance,params,source,cancel) => { + new api.ODWorker("opendiscord:logs",0,(instance,params,origin,cancel) => { const {guild,channel,user,ticket} = params opendiscord.log(user.displayName+" claimed a ticket!","info",[ @@ -91,7 +91,7 @@ export const registerActions = async () => { {key:"channel",value:"#"+channel.name}, {key:"channelid",value:channel.id,hidden:true}, {key:"reason",value:params.reason ?? "/"}, - {key:"method",value:source} + {key:"method",value:origin} ]) }) ]) @@ -99,184 +99,4 @@ export const registerActions = async () => { //set busy to false in case of crash or cancel params.ticket.get("opendiscord:busy").value = false }) -} - -export const registerVerifyBars = async () => { - //CLAIM TICKET TICKET MESSAGE - opendiscord.verifybars.add(new api.ODVerifyBar("opendiscord:claim-ticket-ticket-message",opendiscord.builders.messages.getSafe("opendiscord:verifybar-ticket-message"),!generalConfig.data.system.disableVerifyBars)) - opendiscord.verifybars.get("opendiscord:claim-ticket-ticket-message").success.add([ - new api.ODWorker("opendiscord:permissions",1,async (instance,params,source,cancel) => { - const permissionMode = generalConfig.data.system.permissions.claim - - if (permissionMode == "none"){ - //no permissions - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-no-permissions").build("button",{guild:instance.guild,channel:instance.channel,user:instance.user,permissions:[]})) - return cancel() - }else if (permissionMode == "everyone") return - else if (permissionMode == "admin"){ - if (!opendiscord.permissions.hasPermissions("support",await opendiscord.permissions.getPermissions(instance.user,instance.channel,instance.guild))){ - //no permissions - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-no-permissions").build("button",{guild:instance.guild,channel:instance.channel,user:instance.user,permissions:["support"]})) - return cancel() - }else return - }else{ - if (!instance.guild || !instance.member){ - //error - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error").build("button",{guild:instance.guild,channel:instance.channel,user:instance.user,error:"Permission Error: Not in Server #1",layout:"advanced"})) - return cancel() - } - const role = await opendiscord.client.fetchGuildRole(instance.guild,permissionMode) - if (!role){ - //error - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error").build("button",{guild:instance.guild,channel:instance.channel,user:instance.user,error:"Permission Error: Not in Server #2",layout:"advanced"})) - return cancel() - } - if (!role.members.has(instance.member.id)){ - //no permissions - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-no-permissions").build("button",{guild:instance.guild,channel:instance.channel,user:instance.user,permissions:[]})) - return cancel() - }else return - } - }), - new api.ODWorker("opendiscord:claim-ticket",0,async (instance,params,source,cancel) => { - const {guild,channel,user} = instance - if (!guild){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-not-in-guild").build("button",{channel,user})) - return cancel() - } - const ticket = opendiscord.tickets.get(channel.id) - if (!ticket || channel.isDMBased()){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-ticket-unknown").build("button",{guild,channel,user})) - return cancel() - } - //return when already claimed - if (ticket.get("opendiscord:claimed").value){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error").build("button",{guild,channel,user,error:opendiscord.languages.getTranslation("errors.actionInvalid.claim"),layout:"simple"})) - return cancel() - } - //return when busy - if (ticket.get("opendiscord:busy").value){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-ticket-busy").build("button",{guild,channel,user})) - return cancel() - } - - //start claiming ticket - if (params.data == "reason"){ - //claim with reason - instance.modal(await opendiscord.builders.modals.getSafe("opendiscord:claim-ticket-reason").build("ticket-message",{guild,channel,user,ticket})) - }else{ - //claim without reason - await instance.defer("update",false) - await opendiscord.actions.get("opendiscord:claim-ticket").run("ticket-message",{guild,channel,user,ticket,reason:null,sendMessage:true}) - await instance.update(await opendiscord.builders.messages.getSafe("opendiscord:ticket-message").build("other",{guild,channel,user,ticket})) - } - }) - ]) - opendiscord.verifybars.get("opendiscord:claim-ticket-ticket-message").failure.add([ - new api.ODWorker("opendiscord:back-to-ticket-message",0,async (instance,params,source,cancel) => { - const {guild,channel,user} = instance - if (!guild){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-not-in-guild").build("button",{channel,user})) - return cancel() - } - const ticket = opendiscord.tickets.get(channel.id) - if (!ticket || channel.isDMBased()){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-ticket-unknown").build("button",{guild,channel,user})) - return cancel() - } - - await instance.update(await opendiscord.builders.messages.getSafe("opendiscord:ticket-message").build("other",{guild,channel,user,ticket})) - }) - ]) - - //CLAIM TICKET UNCLAIM MESSAGE - opendiscord.verifybars.add(new api.ODVerifyBar("opendiscord:claim-ticket-unclaim-message",opendiscord.builders.messages.getSafe("opendiscord:verifybar-unclaim-message"),!generalConfig.data.system.disableVerifyBars)) - opendiscord.verifybars.get("opendiscord:claim-ticket-unclaim-message").success.add([ - new api.ODWorker("opendiscord:permissions",1,async (instance,params,source,cancel) => { - const permissionMode = generalConfig.data.system.permissions.claim - - if (permissionMode == "none"){ - //no permissions - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-no-permissions").build("button",{guild:instance.guild,channel:instance.channel,user:instance.user,permissions:[]})) - return cancel() - }else if (permissionMode == "everyone") return - else if (permissionMode == "admin"){ - if (!opendiscord.permissions.hasPermissions("support",await opendiscord.permissions.getPermissions(instance.user,instance.channel,instance.guild))){ - //no permissions - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-no-permissions").build("button",{guild:instance.guild,channel:instance.channel,user:instance.user,permissions:["support"]})) - return cancel() - }else return - }else{ - if (!instance.guild || !instance.member){ - //error - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error").build("button",{guild:instance.guild,channel:instance.channel,user:instance.user,error:"Permission Error: Not in Server #1",layout:"advanced"})) - return cancel() - } - const role = await opendiscord.client.fetchGuildRole(instance.guild,permissionMode) - if (!role){ - //error - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error").build("button",{guild:instance.guild,channel:instance.channel,user:instance.user,error:"Permission Error: Not in Server #2",layout:"advanced"})) - return cancel() - } - if (!role.members.has(instance.member.id)){ - //no permissions - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-no-permissions").build("button",{guild:instance.guild,channel:instance.channel,user:instance.user,permissions:[]})) - return cancel() - }else return - } - }), - new api.ODWorker("opendiscord:claim-ticket",0,async (instance,params,source,cancel) => { - const {guild,channel,user} = instance - if (!guild){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-not-in-guild").build("button",{channel,user})) - return cancel() - } - const ticket = opendiscord.tickets.get(channel.id) - if (!ticket || channel.isDMBased()){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-ticket-unknown").build("button",{guild,channel,user})) - return cancel() - } - //return when already claimed - if (ticket.get("opendiscord:claimed").value){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error").build("button",{guild,channel,user,error:opendiscord.languages.getTranslation("errors.actionInvalid.claim"),layout:"simple"})) - return cancel() - } - //return when busy - if (ticket.get("opendiscord:busy").value){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-ticket-busy").build("button",{guild,channel,user})) - return cancel() - } - - //start claiming ticket - if (params.data == "reason"){ - //claim with reason - instance.modal(await opendiscord.builders.modals.getSafe("opendiscord:claim-ticket-reason").build("unclaim-message",{guild,channel,user,ticket})) - }else{ - //claim without reason - await instance.defer("update",false) - await opendiscord.actions.get("opendiscord:claim-ticket").run("unclaim-message",{guild,channel,user,ticket,reason:null,sendMessage:false}) - await instance.update(await opendiscord.builders.messages.getSafe("opendiscord:claim-message").build("unclaim-message",{guild,channel,user,ticket,reason:null})) - } - }) - ]) - opendiscord.verifybars.get("opendiscord:claim-ticket-unclaim-message").failure.add([ - new api.ODWorker("opendiscord:back-to-unclaim-message",0,async (instance,params,source,cancel) => { - const {guild,channel,user} = instance - const {verifybarMessage} = params - if (!guild){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-not-in-guild").build("button",{channel,user})) - return cancel() - } - const ticket = opendiscord.tickets.get(channel.id) - if (!ticket || channel.isDMBased()){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-ticket-unknown").build("button",{guild,channel,user})) - return cancel() - } - - const rawReason = (verifybarMessage && verifybarMessage.embeds[0] && verifybarMessage.embeds[0].fields[0]) ? verifybarMessage.embeds[0].fields[0].value : null - const reason = (rawReason == null) ? null : rawReason.substring(3,rawReason.length-3) - - await instance.update(await opendiscord.builders.messages.getSafe("opendiscord:unclaim-message").build("other",{guild,channel,user,ticket,reason})) - }) - ]) } \ No newline at end of file diff --git a/src/actions/clearTickets.ts b/src/actions/clearTickets.ts index f2fac57..3c15e2b 100644 --- a/src/actions/clearTickets.ts +++ b/src/actions/clearTickets.ts @@ -1,15 +1,15 @@ /////////////////////////////////////// //CLEAR TICKETS SYSTEM /////////////////////////////////////// -import {opendiscord, api, utilities} from "../index" +import {opendiscord, api, utilities, openticketUtils} from "../index.js" import * as discord from "discord.js" const generalConfig = opendiscord.configs.get("opendiscord:general") -export const registerActions = async () => { +export async function registerActions(){ opendiscord.actions.add(new api.ODAction("opendiscord:clear-tickets")) opendiscord.actions.get("opendiscord:clear-tickets").workers.add([ - new api.ODWorker("opendiscord:clear-tickets",2,async (instance,params,source,cancel) => { + new api.ODWorker("opendiscord:clear-tickets",2,async (instance,params,origin,cancel) => { const {guild,channel,user,filter,list} = params await opendiscord.events.get("onTicketsClear").emit([list,user,channel,filter]) @@ -43,21 +43,21 @@ export const registerActions = async () => { instance.list = nameList await opendiscord.events.get("afterTicketsCleared").emit([list,user,channel,filter]) }), - new api.ODWorker("opendiscord:discord-logs",1,async (instance,params,source,cancel) => { + new api.ODWorker("opendiscord:discord-logs",1,async (instance,params,origin,cancel) => { const {guild,channel,user,filter,list} = params //to logs - if (generalConfig.data.system.logs.enabled && generalConfig.data.system.messages.deleting.logs){ + if (generalConfig.data.logs.enabled && generalConfig.data.logs.logMessages.deleting.logs){ const logChannel = opendiscord.posts.get("opendiscord:logs") - if (logChannel) logChannel.send(await opendiscord.builders.messages.getSafe("opendiscord:clear-logs").build(source,{guild,channel,user,filter,list:instance.list ?? []})) + if (logChannel) logChannel.send(await opendiscord.builders.messages.getSafe("opendiscord:clear-logs").build(origin,{guild,channel,user,filter,list:instance.list ?? []})) } }), - new api.ODWorker("opendiscord:logs",0,(instance,params,source,cancel) => { + new api.ODWorker("opendiscord:logs",0,(instance,params,origin,cancel) => { const {guild,user,filter,list} = params opendiscord.log(user.displayName+" cleared "+list.length+" tickets!","info",[ {key:"user",value:user.username}, {key:"userid",value:user.id,hidden:true}, - {key:"method",value:source}, + {key:"method",value:origin}, {key:"filter",value:filter} ]) }) diff --git a/src/actions/closeTicket.ts b/src/actions/closeTicket.ts index e09bb2c..a151a2f 100644 --- a/src/actions/closeTicket.ts +++ b/src/actions/closeTicket.ts @@ -1,16 +1,17 @@ /////////////////////////////////////// //TICKET CLOSING SYSTEM /////////////////////////////////////// -import {opendiscord, api, utilities} from "../index" +import {opendiscord, api, utilities, openticketUtils} from "../index.js" import * as discord from "discord.js" const generalConfig = opendiscord.configs.get("opendiscord:general") const lang = opendiscord.languages +const interactiveMsgState = opendiscord.states.get("opendiscord:interactive-message") -export const registerActions = async () => { +export async function registerActions(){ opendiscord.actions.add(new api.ODAction("opendiscord:close-ticket")) opendiscord.actions.get("opendiscord:close-ticket").workers.add([ - new api.ODWorker("opendiscord:close-ticket",2,async (instance,params,source,cancel) => { + new api.ODWorker("opendiscord:close-ticket",2,async (instance,params,origin,cancel) => { const {guild,channel,user,ticket,reason} = params if (channel.isThread()) throw new api.ODSystemError("Unable to close ticket! Open Ticket doesn't support threads!") @@ -25,33 +26,57 @@ export const registerActions = async () => { ticket.get("opendiscord:reopened-by").value = null ticket.get("opendiscord:reopened-on").value = null - if (source == "autoclose") ticket.get("opendiscord:autoclosed").value = true + if (origin == "autoclose") ticket.get("opendiscord:autoclosed").value = true ticket.get("opendiscord:open").value = false ticket.get("opendiscord:busy").value = true //update stats - await opendiscord.stats.get("opendiscord:global").setStat("opendiscord:tickets-closed",1,"increase") - await opendiscord.stats.get("opendiscord:user").setStat("opendiscord:tickets-closed",user.id,1,"increase") + await opendiscord.statistics.get("opendiscord:global").setStat("opendiscord:tickets-closed",1,"increase") + await opendiscord.statistics.get("opendiscord:user").setStat("opendiscord:tickets-closed",user.id,1,"increase") - //update category + //calculate & update category if (typeof params.allowCategoryChange == "boolean" ? params.allowCategoryChange : true){ - const closeCategory = ticket.option.get("opendiscord:channel-category-closed").value - if (closeCategory !== ""){ - try { - channel.setParent(closeCategory,{lockPermissions:false}) - ticket.get("opendiscord:category-mode").value = "closed" - ticket.get("opendiscord:category").value = closeCategory - }catch(e){ - opendiscord.log("Unable to move ticket to 'closed category'!","error",[ + const categoryResult = await opendiscord.actions.get("opendiscord:calculate-ticket-category").run("close-ticket",{guild,user,option:ticket.option,channel,ticket,currentCategoryId:channel.parentId}) + if (categoryResult && categoryResult.shouldChangeCategory && typeof categoryResult.newCategoryId !== "undefined" && typeof categoryResult.newCategoryMode !== "undefined" && typeof categoryResult.newCategory !== "undefined"){ + const originalCategoryName = channel.parent?.name ?? "" + const newCategoryName = categoryResult.newCategory?.name ?? "" + try{ + await utilities.timedAwait(channel.setParent(categoryResult.newCategoryId,{lockPermissions:false}),3000,(err) => { + process.emit("uncaughtException",new Error("Error: Unable to change channel parent: "+err)) + }) + ticket.get("opendiscord:category-mode").value = categoryResult.newCategoryMode + ticket.get("opendiscord:category").value = categoryResult.newCategoryId + }catch(err){ + const sentMsg = await channel.send((await opendiscord.builders.messages.getSafe("opendiscord:error-channel-category").build("ticket-close",{guild,channel,user,originalCategory:originalCategoryName,newCategory:newCategoryName})).message) + setTimeout(() => {if (sentMsg.deletable) sentMsg.delete()},7000) //autodelete error message + opendiscord.log("Unable to move ticket to closed category.","error",[ {key:"channel",value:"#"+channel.name}, {key:"channelid",value:channel.id,hidden:true}, - {key:"categoryid",value:closeCategory} + {key:"categoryid",value:categoryResult.newCategoryId ?? "/"} ]) - opendiscord.debugfile.writeErrorMessage(new api.ODError(e,"uncaughtException")) } } } + //calculate channel name + const channelNameResult = await opendiscord.actions.get("opendiscord:calculate-ticket-name").run("close-ticket",{guild,user,option:ticket.option,channel,ticket,currentChannelName:channel.name}) + if (channelNameResult && channelNameResult.shouldChangeName && typeof channelNameResult.newChannelName !== "undefined"){ + const originalName = channel.name + const newName = channelNameResult.newChannelName + try{ + await utilities.timedAwait(channel.setName(newName),2500,(err) => { + opendiscord.log("Failed to rename channel on ticket close","error") + }) + }catch(err){ + opendiscord.log("Unable to rename channel while closing ticket! Waiting until ratelimit expires...","warning",[ + {key:"oldName",value:originalName}, + {key:"newName",value:newName} + ]) + const sentMsg = await channel.send((await opendiscord.builders.messages.getSafe("opendiscord:error-channel-rename").build("ticket-close",{guild,channel,user,originalName,newName})).message) + setTimeout(() => {if (sentMsg.deletable) sentMsg.delete()},7000) //autodelete error message + } + } + //update permissions (non-staff => readonly) const permissions: discord.OverwriteResolvable[] = [{ type:discord.OverwriteType.Role, @@ -93,7 +118,7 @@ export const registerActions = async () => { ticket.get("opendiscord:participants").value.forEach((participant) => { //all participants that aren't roles/admins => readonly (OR non-viewable when enabled) if (participant.type == "user"){ - if (generalConfig.data.system.removeParticipantsOnClose) permissions.push({ + if (generalConfig.data.ticketSystem.removeParticipantsOnClose) permissions.push({ type:discord.OverwriteType.Member, id:participant.id, allow:[], @@ -109,44 +134,39 @@ export const registerActions = async () => { }) channel.permissionOverwrites.set(permissions) - //update ticket message - const ticketMessage = await opendiscord.tickets.getTicketMessage(ticket) - if (ticketMessage){ - try{ - ticketMessage.edit((await opendiscord.builders.messages.getSafe("opendiscord:ticket-message").build("other",{guild,channel,user,ticket})).message) - }catch(e){ - opendiscord.log("Unable to edit ticket message on ticket closing!","error",[ - {key:"channel",value:"#"+channel.name}, - {key:"channelid",value:channel.id,hidden:true}, - {key:"messageid",value:ticketMessage.id}, - {key:"option",value:ticket.option.id.value} - ]) - opendiscord.debugfile.writeErrorMessage(new api.ODError(e,"uncaughtException")) - } - } + //update ticket message (no await) + openticketUtils.updateTicketMessage(guild,channel,user,ticket) //reply with new message - if (params.sendMessage) await channel.send((await opendiscord.builders.messages.getSafe("opendiscord:close-message").build(source,{guild,channel,user,ticket,reason})).message) + if (params.sendMessage){ + const sentMsg = await channel.send((await opendiscord.builders.messages.getSafe("opendiscord:close-message").build(origin,{guild,channel,user,ticket,reason})).message) + if (sentMsg) await interactiveMsgState.setMsgState({channel,message:sentMsg},{ + messageType:"close-message", + messageOrigin:"other", + messageAuthor:user.id, + messageReason:reason + },false) + } ticket.get("opendiscord:busy").value = false await opendiscord.events.get("afterTicketClosed").emit([ticket,user,channel,reason]) //update channel topic await opendiscord.actions.get("opendiscord:update-ticket-topic").run("ticket-action",{guild,channel,user,ticket,sendMessage:false,newTopic:null}) }), - new api.ODWorker("opendiscord:discord-logs",1,async (instance,params,source,cancel) => { + new api.ODWorker("opendiscord:discord-logs",1,async (instance,params,origin,cancel) => { const {guild,channel,user,ticket,reason} = params //to logs - if (generalConfig.data.system.logs.enabled && generalConfig.data.system.messages.closing.logs){ + if (generalConfig.data.logs.enabled && generalConfig.data.logs.logMessages.closing.logs){ const logChannel = opendiscord.posts.get("opendiscord:logs") - if (logChannel) logChannel.send(await opendiscord.builders.messages.getSafe("opendiscord:ticket-action-logs").build(source,{guild,channel,user,ticket,mode:"close",reason,additionalData:null})) + if (logChannel) logChannel.send(await opendiscord.builders.messages.getSafe("opendiscord:ticket-action-logs").build(origin,{guild,channel,user,ticket,mode:"close",reason,additionalData:null})) } //to dm const creator = await opendiscord.tickets.getTicketUser(ticket,"creator") - if (creator && generalConfig.data.system.messages.closing.dm) await opendiscord.client.sendUserDm(creator,await opendiscord.builders.messages.getSafe("opendiscord:ticket-action-dm").build(source,{guild,channel,user,ticket,mode:"close",reason,additionalData:null})) + if (creator && generalConfig.data.logs.logMessages.closing.dm) await opendiscord.client.sendUserDm(creator,await opendiscord.builders.messages.getSafe("opendiscord:ticket-action-dm").build(origin,{guild,channel,user,ticket,mode:"close",reason,additionalData:null})) }), - new api.ODWorker("opendiscord:logs",0,(instance,params,source,cancel) => { + new api.ODWorker("opendiscord:logs",0,(instance,params,origin,cancel) => { const {guild,channel,user,ticket} = params opendiscord.log(user.displayName+" closed a ticket!","info",[ @@ -155,7 +175,7 @@ export const registerActions = async () => { {key:"channel",value:"#"+channel.name}, {key:"channelid",value:channel.id,hidden:true}, {key:"reason",value:params.reason ?? "/"}, - {key:"method",value:source} + {key:"method",value:origin} ]) }) ]) @@ -163,172 +183,4 @@ export const registerActions = async () => { //set busy to false in case of crash or cancel params.ticket.get("opendiscord:busy").value = false }) -} - -export const registerVerifyBars = async () => { - //CLOSE TICKET TICKET MESSAGE - opendiscord.verifybars.add(new api.ODVerifyBar("opendiscord:close-ticket-ticket-message",opendiscord.builders.messages.getSafe("opendiscord:verifybar-ticket-message"),!generalConfig.data.system.disableVerifyBars)) - opendiscord.verifybars.get("opendiscord:close-ticket-ticket-message").success.add([ - new api.ODWorker("opendiscord:close-ticket",0,async (instance,params,source,cancel) => { - const {user,member,channel,guild} = instance - - //check permissions - const permsResult = await opendiscord.permissions.checkCommandPerms(generalConfig.data.system.permissions.close,"support",user,member,channel,guild) - if (!permsResult.hasPerms){ - if (permsResult.reason == "not-in-server") await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-not-in-guild").build("button",{channel,user})) - else await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-no-permissions").build("button",{guild,channel,user,permissions:["support"]})) - return cancel() - } - - //check is in guild/server - if (!guild){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-not-in-guild").build("button",{channel,user})) - return cancel() - } - - //check if ticket exists - const ticket = opendiscord.tickets.get(channel.id) - if (!ticket || channel.isDMBased()){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-ticket-unknown").build("button",{guild,channel,user})) - return cancel() - } - - //return when already closed - if (ticket.get("opendiscord:closed").value){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error").build("button",{guild,channel,user,error:lang.getTranslation("errors.actionInvalid.close"),layout:"simple"})) - return cancel() - } - - //return when busy - if (ticket.get("opendiscord:busy").value){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-ticket-busy").build("button",{guild,channel,user})) - return cancel() - } - - //return when not allowed because of missing messages - if (!permsResult.isAdmin && (!generalConfig.data.system.allowCloseBeforeMessage || !generalConfig.data.system.allowCloseBeforeAdminMessage)){ - const analysis = await opendiscord.transcripts.collector.ticketUserMessagesAnalysis(ticket,guild,channel) - if (analysis && !generalConfig.data.system.allowCloseBeforeMessage && analysis.totalMessages < 1){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error").build("button",{guild,channel,user,layout:"simple",error:lang.getTranslation("errors.descriptions.closeBeforeMessage"),customTitle:lang.getTranslation("errors.titles.noPermissions")})) - return cancel() - } - if (analysis && !generalConfig.data.system.allowCloseBeforeAdminMessage && analysis.adminMessages < 1){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error").build("button",{guild,channel,user,layout:"simple",error:lang.getTranslation("errors.descriptions.closeBeforeAdminMessage"),customTitle:lang.getTranslation("errors.titles.noPermissions")})) - return cancel() - } - } - - //start closing ticket - if (params.data == "reason"){ - //close with reason - instance.modal(await opendiscord.builders.modals.getSafe("opendiscord:close-ticket-reason").build("ticket-message",{guild,channel,user,ticket})) - }else{ - //close without reason - await instance.defer("update",false) - await opendiscord.actions.get("opendiscord:close-ticket").run("ticket-message",{guild,channel,user,ticket,reason:null,sendMessage:true}) - await instance.update(await opendiscord.builders.messages.getSafe("opendiscord:ticket-message").build("other",{guild,channel,user,ticket})) - } - }) - ]) - opendiscord.verifybars.get("opendiscord:close-ticket-ticket-message").failure.add([ - new api.ODWorker("opendiscord:back-to-ticket-message",0,async (instance,params,source,cancel) => { - const {guild,channel,user} = instance - if (!guild){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-not-in-guild").build("button",{channel,user})) - return cancel() - } - const ticket = opendiscord.tickets.get(channel.id) - if (!ticket || channel.isDMBased()){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-ticket-unknown").build("button",{guild,channel,user})) - return cancel() - } - - await instance.update(await opendiscord.builders.messages.getSafe("opendiscord:ticket-message").build("other",{guild,channel,user,ticket})) - }) - ]) - - //CLOSE TICKET REOPEN MESSAGE - opendiscord.verifybars.add(new api.ODVerifyBar("opendiscord:close-ticket-reopen-message",opendiscord.builders.messages.getSafe("opendiscord:verifybar-reopen-message"),!generalConfig.data.system.disableVerifyBars)) - opendiscord.verifybars.get("opendiscord:close-ticket-reopen-message").success.add([ - new api.ODWorker("opendiscord:close-ticket",0,async (instance,params,source,cancel) => { - const {user,member,channel,guild} = instance - - //check permissions - const permsResult = await opendiscord.permissions.checkCommandPerms(generalConfig.data.system.permissions.close,"support",user,member,channel,guild) - if (!permsResult.hasPerms){ - if (permsResult.reason == "not-in-server") await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-not-in-guild").build("button",{channel,user})) - else await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-no-permissions").build("button",{guild,channel,user,permissions:["support"]})) - return cancel() - } - - //check is in guild/server - if (!guild){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-not-in-guild").build("button",{channel,user})) - return cancel() - } - - //check if ticket exists - const ticket = opendiscord.tickets.get(channel.id) - if (!ticket || channel.isDMBased()){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-ticket-unknown").build("button",{guild,channel,user})) - return cancel() - } - - //return when already closed - if (ticket.get("opendiscord:closed").value){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error").build("button",{guild,channel,user,error:lang.getTranslation("errors.actionInvalid.close"),layout:"simple"})) - return cancel() - } - - //return when busy - if (ticket.get("opendiscord:busy").value){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-ticket-busy").build("button",{guild,channel,user})) - return cancel() - } - - //return when not allowed because of missing messages - if (!permsResult.isAdmin && (!generalConfig.data.system.allowCloseBeforeMessage || !generalConfig.data.system.allowCloseBeforeAdminMessage)){ - const analysis = await opendiscord.transcripts.collector.ticketUserMessagesAnalysis(ticket,guild,channel) - if (analysis && !generalConfig.data.system.allowCloseBeforeMessage && analysis.totalMessages < 1){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error").build("button",{guild,channel,user,layout:"simple",error:lang.getTranslation("errors.descriptions.closeBeforeMessage"),customTitle:lang.getTranslation("errors.titles.noPermissions")})) - return cancel() - } - if (analysis && !generalConfig.data.system.allowCloseBeforeAdminMessage && analysis.adminMessages < 1){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error").build("button",{guild,channel,user,layout:"simple",error:lang.getTranslation("errors.descriptions.closeBeforeAdminMessage"),customTitle:lang.getTranslation("errors.titles.noPermissions")})) - return cancel() - } - } - - //start closing ticket - if (params.data == "reason"){ - //close with reason - instance.modal(await opendiscord.builders.modals.getSafe("opendiscord:close-ticket-reason").build("reopen-message",{guild,channel,user,ticket})) - }else{ - //close without reason - await instance.defer("update",false) - await opendiscord.actions.get("opendiscord:close-ticket").run("reopen-message",{guild,channel,user,ticket,reason:null,sendMessage:false}) - await instance.update(await opendiscord.builders.messages.getSafe("opendiscord:close-message").build("reopen-message",{guild,channel,user,ticket,reason:null})) - } - }) - ]) - opendiscord.verifybars.get("opendiscord:close-ticket-reopen-message").failure.add([ - new api.ODWorker("opendiscord:back-to-reopen-message",0,async (instance,params,source,cancel) => { - const {guild,channel,user} = instance - const {verifybarMessage} = params - if (!guild){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-not-in-guild").build("button",{channel,user})) - return cancel() - } - const ticket = opendiscord.tickets.get(channel.id) - if (!ticket || channel.isDMBased()){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-ticket-unknown").build("button",{guild,channel,user})) - return cancel() - } - - const rawReason = (verifybarMessage && verifybarMessage.embeds[0] && verifybarMessage.embeds[0].fields[0]) ? verifybarMessage.embeds[0].fields[0].value : null - const reason = (rawReason == null) ? null : rawReason.substring(3,rawReason.length-3) - - await instance.update(await opendiscord.builders.messages.getSafe("opendiscord:reopen-message").build("other",{guild,channel,user,ticket,reason})) - }) - ]) } \ No newline at end of file diff --git a/src/actions/createTicket.ts b/src/actions/createTicket.ts index 2e62699..c8ff9ca 100644 --- a/src/actions/createTicket.ts +++ b/src/actions/createTicket.ts @@ -1,63 +1,36 @@ /////////////////////////////////////// //TICKET CREATION SYSTEM /////////////////////////////////////// -import {opendiscord, api, utilities} from "../index" +import {opendiscord, api, utilities, openticketUtils} from "../index.js" import * as discord from "discord.js" const generalConfig = opendiscord.configs.get("opendiscord:general") const lang = opendiscord.languages +const interactiveMsgState = opendiscord.states.get("opendiscord:interactive-message") -export const registerActions = async () => { +export async function registerActions(){ opendiscord.actions.add(new api.ODAction("opendiscord:create-ticket")) opendiscord.actions.get("opendiscord:create-ticket").workers.add([ - new api.ODWorker("opendiscord:create-ticket",3,async (instance,params,source,cancel) => { + new api.ODWorker("opendiscord:create-ticket",3,async (instance,params,origin,cancel) => { const {guild,user,answers,option} = params await opendiscord.events.get("onTicketCreate").emit([user]) await opendiscord.events.get("onTicketChannelCreation").emit([option,user]) //get channel properties - const channelPrefix = option.get("opendiscord:channel-prefix").value - const channelCategory = option.get("opendiscord:channel-category").value - const channelBackupCategory = option.get("opendiscord:channel-category-backup").value const channelTopicText = option.get("opendiscord:channel-topic").value - const channelSuffix = await opendiscord.options.suffix.getSuffixFromOption(option,user,guild) - const channelName = channelPrefix+channelSuffix - //handle category - let category: string|null = null - let categoryMode: "backup"|"normal"|null = null - if (channelCategory != ""){ - //category enabled - const normalCategory = await opendiscord.client.fetchGuildCategoryChannel(guild,channelCategory) - if (!normalCategory){ - //default category was not found - opendiscord.log("Ticket Creation Error: Unable to find category! #1","error",[ - {key:"categoryid",value:channelCategory}, - {key:"backup",value:"false"} - ]) - }else{ - //default category was found - if (normalCategory.children.cache.size >= 50 && channelBackupCategory != ""){ - //use backup category - const backupCategory = await opendiscord.client.fetchGuildCategoryChannel(guild,channelBackupCategory) - if (!backupCategory){ - //default category was not found - opendiscord.log("Ticket Creation Error: Unable to find category! #2","error",[ - {key:"categoryid",value:channelBackupCategory}, - {key:"backup",value:"true"} - ]) - }else{ - category = backupCategory.id - categoryMode = "backup" - } - }else{ - //use default category - category = normalCategory.id - categoryMode = "normal" - } - } - } + //calculate channel name + const channelNameResult = await opendiscord.actions.get("opendiscord:calculate-ticket-name").run("create-ticket",{guild,user,option,channel:null,ticket:null,currentChannelName:null}) + if (!channelNameResult) return opendiscord.log("Ticket Creation Error: Unable to calculate ticket name.","error") + const channelName = (channelNameResult.shouldChangeName && typeof channelNameResult.newChannelName !== "undefined") ? channelNameResult.newChannelName : "ot-unnamed-ticket" + const channelSuffix = (typeof channelNameResult.newChannelSuffix !== "undefined") ? channelNameResult.newChannelSuffix : "unknown" + + //calculate category + const categoryResult = await opendiscord.actions.get("opendiscord:calculate-ticket-category").run("create-ticket",{guild,user,option,channel:null,ticket:null,currentCategoryId:null}) + if (!categoryResult) return opendiscord.log("Ticket Creation Error: Unable to calculate ticket category.","error") + const ticketCategoryId = (categoryResult.shouldChangeCategory && typeof categoryResult.newCategoryId !== "undefined") ? categoryResult.newCategoryId : undefined + const ticketCategoryMode = (categoryResult.shouldChangeCategory && typeof categoryResult.newCategoryMode !== "undefined") ? categoryResult.newCategoryMode : undefined //handle permissions const permissions: discord.OverwriteResolvable[] = [{ @@ -118,15 +91,15 @@ export const registerActions = async () => { //handle channel topic const channelTopics: string[] = [] - if (generalConfig.data.system.channelTopic.showOptionName) channelTopics.push(option.get("opendiscord:name").value) - if (generalConfig.data.system.channelTopic.showOptionDescription) channelTopics.push(option.get("opendiscord:description").value) - if (generalConfig.data.system.channelTopic.showOptionTopic) channelTopics.push(channelTopicText) - if (generalConfig.data.system.channelTopic.showPriority) channelTopics.push("**"+lang.getTranslation("params.uppercase.priority")+":** "+opendiscord.priorities.get("opendiscord:none").renderDisplayName()) - if (generalConfig.data.system.channelTopic.showClosed) channelTopics.push("**"+lang.getTranslation("params.uppercase.status")+":** "+lang.getTranslation("params.uppercase.open")) - if (generalConfig.data.system.channelTopic.showClaimed) channelTopics.push("**"+lang.getTranslation("stats.properties.claimedBy")+":** "+lang.getTranslation("params.uppercase.noone")) - if (generalConfig.data.system.channelTopic.showPinned) channelTopics.push("**"+lang.getTranslation("params.uppercase.pinned")+":** "+lang.getTranslation("params.uppercase.no")) - if (generalConfig.data.system.channelTopic.showCreator) channelTopics.push("**"+lang.getTranslation("params.uppercase.creator")+":** "+discord.userMention(user.id)) - if (generalConfig.data.system.channelTopic.showParticipants) channelTopics.push("**"+lang.getTranslation("params.uppercase.participants")+":** "+participants.map((p) => (p.type == "user") ? discord.userMention(p.id) : discord.roleMention(p.id)).join(", ")) + if (generalConfig.data.ticketSystem.channelTopic.showOptionName) channelTopics.push(option.get("opendiscord:name").value) + if (generalConfig.data.ticketSystem.channelTopic.showOptionDescription) channelTopics.push(option.get("opendiscord:description").value) + if (generalConfig.data.ticketSystem.channelTopic.showOptionTopic) channelTopics.push(channelTopicText) + if (generalConfig.data.ticketSystem.channelTopic.showPriority) channelTopics.push("**"+lang.getTranslation("params.uppercase.priority")+":** "+opendiscord.priorities.get("opendiscord:none").renderDisplayName()) + if (generalConfig.data.ticketSystem.channelTopic.showClosed) channelTopics.push("**"+lang.getTranslation("params.uppercase.status")+":** "+lang.getTranslation("params.uppercase.open")) + if (generalConfig.data.ticketSystem.channelTopic.showClaimed) channelTopics.push("**"+lang.getTranslation("stats.properties.claimedBy")+":** "+lang.getTranslation("params.uppercase.noone")) + if (generalConfig.data.ticketSystem.channelTopic.showPinned) channelTopics.push("**"+lang.getTranslation("params.uppercase.pinned")+":** "+lang.getTranslation("params.uppercase.no")) + if (generalConfig.data.ticketSystem.channelTopic.showCreator) channelTopics.push("**"+lang.getTranslation("params.uppercase.creator")+":** "+discord.userMention(user.id)) + if (generalConfig.data.ticketSystem.channelTopic.showParticipants) channelTopics.push("**"+lang.getTranslation("params.uppercase.participants")+":** "+participants.map((p) => (p.type == "user") ? discord.userMention(p.id) : discord.roleMention(p.id)).join(", ")) //create channel const channel = await guild.channels.create({ @@ -134,7 +107,7 @@ export const registerActions = async () => { name:channelName, nsfw:false, topic:(channelTopics.length > 0) ? channelTopics.join(" • ") : undefined, - parent:category, + parent:ticketCategoryId, reason:"Ticket Created By "+user.displayName, permissionOverwrites:permissions, rateLimitPerUser:slowMode @@ -148,6 +121,7 @@ export const registerActions = async () => { new api.ODTicketData("opendiscord:ticket-message",null), new api.ODTicketData("opendiscord:participants",participants), new api.ODTicketData("opendiscord:channel-suffix",channelSuffix), + new api.ODTicketData("opendiscord:channel-renamed",null), new api.ODTicketData("opendiscord:previous-creators",[]), new api.ODTicketData("opendiscord:open",true), @@ -167,8 +141,8 @@ export const registerActions = async () => { new api.ODTicketData("opendiscord:pinned-on",null), new api.ODTicketData("opendiscord:for-deletion",false), - new api.ODTicketData("opendiscord:category",category), - new api.ODTicketData("opendiscord:category-mode",categoryMode), + new api.ODTicketData("opendiscord:category",ticketCategoryId ?? null), + new api.ODTicketData("opendiscord:category-mode",ticketCategoryMode ?? null), new api.ODTicketData("opendiscord:autoclose-enabled",option.get("opendiscord:autoclose-enable-hours").value), new api.ODTicketData("opendiscord:autoclose-hours",(option.get("opendiscord:autoclose-enable-hours").value ? option.get("opendiscord:autoclose-hours").value : 0)), @@ -184,8 +158,8 @@ export const registerActions = async () => { ]) //manage stats - await opendiscord.stats.get("opendiscord:global").setStat("opendiscord:tickets-created",1,"increase") - await opendiscord.stats.get("opendiscord:user").setStat("opendiscord:tickets-created",user.id,1,"increase") + await opendiscord.statistics.get("opendiscord:global").setStat("opendiscord:tickets-created",1,"increase") + await opendiscord.statistics.get("opendiscord:user").setStat("opendiscord:tickets-created",user.id,1,"increase") //manage bot permissions await opendiscord.events.get("onTicketPermissionsCreated").emit([option,opendiscord.permissions,channel,user]) @@ -197,7 +171,7 @@ export const registerActions = async () => { instance.ticket = ticket opendiscord.tickets.add(ticket) }), - new api.ODWorker("opendiscord:send-ticket-message",2,async (instance,params,source,cancel) => { + new api.ODWorker("opendiscord:send-ticket-message",2,async (instance,params,origin,cancel) => { const {guild,user,answers,option} = params const {ticket,channel} = instance @@ -207,17 +181,23 @@ export const registerActions = async () => { //check if ticket message is enabled if (!option.get("opendiscord:ticket-message-enabled").value) return try { - const msg = await channel.send((await opendiscord.builders.messages.getSafe("opendiscord:ticket-message").build(source,{guild,channel,user,ticket})).message) + const ticketMsg = await channel.send((await opendiscord.builders.messages.getSafe("opendiscord:ticket-message").build(origin,{guild,channel,user,ticket})).message) - ticket.get("opendiscord:ticket-message").value = msg.id + if (ticketMsg) await interactiveMsgState.setMsgState({channel,message:ticketMsg},{ + messageType:"ticket-message", + messageOrigin:"other", + messageAuthor:user.id + },false) + + ticket.get("opendiscord:ticket-message").value = ticketMsg.id //pin ticket message (if required) - if (generalConfig.data.system.pinFirstTicketMessage && msg.pinnable) await msg.pin("Ticket Message") + if (generalConfig.data.ticketSystem.pinFirstTicketMessage && ticketMsg.pinnable) await ticketMsg.pin("Ticket Message") //manage stats - await opendiscord.stats.get("opendiscord:ticket").setStat("opendiscord:messages-sent",ticket.id.value,1,"increase") + await opendiscord.statistics.get("opendiscord:ticket").setStat("opendiscord:messages-sent",ticket.id.value,1,"increase") - await opendiscord.events.get("afterTicketMainMessageCreated").emit([ticket,msg,channel,user]) + await opendiscord.events.get("afterTicketMainMessageCreated").emit([ticket,ticketMsg,channel,user]) }catch(err){ process.emit("uncaughtException",err) //something went wrong while sending the ticket message @@ -225,20 +205,22 @@ export const registerActions = async () => { } await opendiscord.events.get("afterTicketCreated").emit([ticket,user,channel]) }), - new api.ODWorker("opendiscord:discord-logs",1,async (instance,params,source,cancel) => { + new api.ODWorker("opendiscord:discord-logs",1,async (instance,params,origin,cancel) => { const {guild,user,answers,option} = params const {ticket,channel} = instance + if (!ticket || !channel) return opendiscord.log("Ticket Creation Error: Unable to send ticket message. Previous worker failed!","error") + //to logs - if (generalConfig.data.system.logs.enabled && generalConfig.data.system.messages.creation.logs){ + if (generalConfig.data.logs.enabled && generalConfig.data.logs.logMessages.creation.logs){ const logChannel = opendiscord.posts.get("opendiscord:logs") - if (logChannel) logChannel.send(await opendiscord.builders.messages.getSafe("opendiscord:ticket-created-logs").build(source,{guild,channel,user,ticket})) + if (logChannel) logChannel.send(await opendiscord.builders.messages.getSafe("opendiscord:ticket-created-logs").build(origin,{guild,channel,user,ticket})) } //to dm - if (generalConfig.data.system.messages.creation.dm) await opendiscord.client.sendUserDm(user,await opendiscord.builders.messages.getSafe("opendiscord:ticket-created-dm").build(source,{guild,channel,user,ticket})) + if (generalConfig.data.logs.logMessages.creation.dm) await opendiscord.client.sendUserDm(user,await opendiscord.builders.messages.getSafe("opendiscord:ticket-created-dm").build(origin,{guild,channel,user,ticket})) }), - new api.ODWorker("opendiscord:logs",0,(instance,params,source,cancel) => { + new api.ODWorker("opendiscord:logs",0,(instance,params,origin,cancel) => { const {guild,user,answers,option} = params const {ticket,channel} = instance @@ -249,7 +231,7 @@ export const registerActions = async () => { {key:"userid",value:user.id,hidden:true}, {key:"channel",value:"#"+channel.name}, {key:"channelid",value:channel.id,hidden:true}, - {key:"method",value:source}, + {key:"method",value:origin}, {key:"option",value:option.id.value} ]) }) diff --git a/src/actions/createTicketPermissions.ts b/src/actions/createTicketPermissions.ts index 2c1bb06..0617124 100644 --- a/src/actions/createTicketPermissions.ts +++ b/src/actions/createTicketPermissions.ts @@ -1,13 +1,13 @@ /////////////////////////////////////// //TICKET CREATION SYSTEM /////////////////////////////////////// -import {opendiscord, api, utilities} from "../index" +import {opendiscord, api, utilities, openticketUtils} from "../index.js" import * as discord from "discord.js" -export const registerActions = async () => { +export async function registerActions(){ opendiscord.actions.add(new api.ODAction("opendiscord:create-ticket-permissions")) opendiscord.actions.get("opendiscord:create-ticket-permissions").workers.add([ - new api.ODWorker("opendiscord:check-blacklist",4,(instance,params,source,cancel) => { + new api.ODWorker("opendiscord:check-blacklist",4,(instance,params,origin,cancel) => { if (!params.option.get("opendiscord:allow-blacklisted-users").value && opendiscord.blacklist.exists(params.user.id)){ instance.valid = false instance.reason = "blacklist" @@ -19,7 +19,7 @@ export const registerActions = async () => { return cancel() } }), - new api.ODWorker("opendiscord:check-cooldown",3,(instance,params,source,cancel) => { + new api.ODWorker("opendiscord:check-cooldown",3,(instance,params,origin,cancel) => { const cooldown = opendiscord.cooldowns.get("opendiscord:option-cooldown_"+params.option.id.value) if (cooldown && cooldown instanceof api.ODTimeoutCooldown && cooldown.use(params.user.id)){ instance.valid = false @@ -36,16 +36,16 @@ export const registerActions = async () => { return cancel() } }), - new api.ODWorker("opendiscord:check-global-limits",2,(instance,params,source,cancel) => { + new api.ODWorker("opendiscord:check-global-limits",2,(instance,params,origin,cancel) => { const generalConfig = opendiscord.configs.get("opendiscord:general") - if (!generalConfig.data.system.limits.enabled) return + if (!generalConfig.data.ticketSystem.limits.enabled) return const allTickets = opendiscord.tickets.getAll() const globalTicketCount = allTickets.length const userTickets = opendiscord.tickets.getFiltered((ticket) => ticket.exists("opendiscord:opened-by") && (ticket.get("opendiscord:opened-by").value == params.user.id)) const userTicketCount = userTickets.length - if (globalTicketCount >= generalConfig.data.system.limits.globalMaximum){ + if (globalTicketCount >= generalConfig.data.ticketSystem.limits.globalMaximum){ instance.valid = false instance.reason = "global-limit" opendiscord.log(params.user.displayName+" tried to create a ticket but reached the limit!","info",[ @@ -55,7 +55,7 @@ export const registerActions = async () => { {key:"limit",value:"global"} ]) return cancel() - }else if (userTicketCount >= generalConfig.data.system.limits.userMaximum){ + }else if (userTicketCount >= generalConfig.data.ticketSystem.limits.userMaximum){ instance.valid = false instance.reason = "global-user-limit" opendiscord.log(params.user.displayName+" tried to create a ticket, but reached the limit!","info",[ @@ -67,7 +67,7 @@ export const registerActions = async () => { return cancel() } }), - new api.ODWorker("opendiscord:check-option-limits",1,(instance,params,source,cancel) => { + new api.ODWorker("opendiscord:check-option-limits",1,(instance,params,origin,cancel) => { if (!params.option.exists("opendiscord:limits-enabled") || !params.option.get("opendiscord:limits-enabled").value) return const allTickets = opendiscord.tickets.getFiltered((ticket) => ticket.option.id.value == params.option.id.value) @@ -97,7 +97,7 @@ export const registerActions = async () => { return cancel() } }), - new api.ODWorker("opendiscord:valid",0,(instance,params,source,cancel) => { + new api.ODWorker("opendiscord:valid",0,(instance,params,origin,cancel) => { instance.valid = true instance.reason = null cancel() diff --git a/src/actions/createTranscript.ts b/src/actions/createTranscript.ts index 825a142..6eac994 100644 --- a/src/actions/createTranscript.ts +++ b/src/actions/createTranscript.ts @@ -1,15 +1,16 @@ /////////////////////////////////////// //TRANSCRIPT CREATION SYSTEM /////////////////////////////////////// -import {opendiscord, api, utilities} from "../index" +import {opendiscord, api, utilities, openticketUtils} from "../index.js" import * as discord from "discord.js" const transcriptConfig = opendiscord.configs.get("opendiscord:transcripts") +const transcriptDatabase = opendiscord.databases.get("opendiscord:transcripts") -export const registerActions = async () => { +export async function registerActions(){ opendiscord.actions.add(new api.ODAction("opendiscord:create-transcript")) opendiscord.actions.get("opendiscord:create-transcript").workers.add([ - new api.ODWorker("opendiscord:select-compiler",4,async (instance,params,source,cancel) => { + new api.ODWorker("opendiscord:select-compiler",4,async (instance,params,origin,cancel) => { const {channel,user,ticket} = params if (channel.type != discord.ChannelType.GuildText) return cancel() if (!transcriptConfig.data.general.enabled) return cancel() @@ -29,10 +30,15 @@ export const registerActions = async () => { if (transcriptConfig.data.general.mode == "text") instance.compiler = opendiscord.transcripts.get("opendiscord:text-compiler") else if (transcriptConfig.data.general.mode == "html") instance.compiler = opendiscord.transcripts.get("opendiscord:html-compiler") }), - new api.ODWorker("opendiscord:init-transcript",3,async (instance,params,source,cancel) => { + new api.ODWorker("opendiscord:init-transcript",3,async (instance,params,origin,cancel) => { const {channel,user,ticket} = params if (channel.type != discord.ChannelType.GuildText) return cancel() if (!transcriptConfig.data.general.enabled) return cancel() + if (!instance.compiler){ + instance.success = false + cancel() + throw new api.ODSystemError("ODAction(ot:create-transcript):ODWorker(ot:init-transcript) => Instance is missing transcript compiler!") + } //run transcript compiler init() await opendiscord.events.get("onTranscriptInit").emit([opendiscord.transcripts,ticket,channel,user]) @@ -63,7 +69,7 @@ export const registerActions = async () => { } await opendiscord.events.get("afterTranscriptInitiated").emit([opendiscord.transcripts,ticket,channel,user]) }), - new api.ODWorker("opendiscord:compile-transcript",2,async (instance,params,source,cancel) => { + new api.ODWorker("opendiscord:compile-transcript",2,async (instance,params,origin,cancel) => { const {channel,user,ticket} = params if (channel.type != discord.ChannelType.GuildText) return cancel() if (!instance.compiler){ @@ -71,6 +77,11 @@ export const registerActions = async () => { cancel() throw new api.ODSystemError("ODAction(ot:create-transcript):ODWorker(ot:compile-transcript) => Instance is missing transcript compiler!") } + if (typeof instance.initData == "undefined"){ + instance.success = false + cancel() + throw new api.ODSystemError("ODAction(ot:create-transcript):ODWorker(ot:compile-transcript) => Instance is missing transcript initData!") + } //run transcript compiler compile() await opendiscord.events.get("onTranscriptCompile").emit([opendiscord.transcripts,ticket,channel,user]) @@ -93,25 +104,54 @@ export const registerActions = async () => { } await opendiscord.events.get("afterTranscriptCompiled").emit([opendiscord.transcripts,ticket,channel,user]) }), - new api.ODWorker("opendiscord:ready-transcript",1,async (instance,params,source,cancel) => { + new api.ODWorker("opendiscord:ready-transcript",1,async (instance,params,origin,cancel) => { + if (!instance.compiler){ + instance.success = false + cancel() + throw new api.ODSystemError("ODAction(ot:create-transcript):ODWorker(ot:ready-transcript) => Instance is missing transcript compiler! (1)") + } if (!instance.result){ instance.success = false cancel() - throw new api.ODSystemError("ODAction(ot:create-transcript):ODWorker(ot:ready-transcript) => Instance is missing transcript result!") + throw new api.ODSystemError("ODAction(ot:create-transcript):ODWorker(ot:ready-transcript) => Instance is missing transcript result! (1)") } //run transcript compiler ready() utilities.runAsync(async () => { - await opendiscord.events.get("onTranscriptReady").emit([opendiscord.transcripts,instance.result.ticket,instance.result.channel,instance.result.user]) + if (!instance.compiler){ + instance.success = false + cancel() + throw new api.ODSystemError("ODAction(ot:create-transcript):ODWorker(ot:ready-transcript) => Instance is missing transcript compiler! (2)") + } + if (!instance.result){ + instance.success = false + cancel() + throw new api.ODSystemError("ODAction(ot:create-transcript):ODWorker(ot:ready-transcript) => Instance is missing transcript result! (2)") + } + const result = instance.result as api.ODTranscriptCompilerCompileResult<{url:string,availableUntil:Date}|{contents:string}> + + const historyData: api.ODTranscriptHistoryData = { + ticketId:result.channel.id, + ticketName:"#"+result.channel.name, + ticketCreatorId:result.user.id, + ticketCreatedDate:result.ticket.get("opendiscord:opened-on").value, + ticketDeletedDate:Date.now(), + transcriptType:(result.data && "contents" in result.data) ? "localContents" : "remoteUrl", + transcriptContents:(result.data && "contents" in result.data) ? result.data.contents : null, + transcriptUrl:(result.data && "url" in result.data) ? result.data.url : null, + } + transcriptDatabase.set("opendiscord:transcript","C:"+result.channel.id+",U:"+result.user.id,historyData) + + await opendiscord.events.get("onTranscriptReady").emit([opendiscord.transcripts,result.ticket,result.channel,result.user]) if (instance.compiler.ready){ try{ - const {channelMessage,creatorDmMessage,participantDmMessage,activeAdminDmMessage,everyAdminDmMessage} = await instance.compiler.ready(instance.result) + const {channelMessage,creatorDmMessage,participantDmMessage,activeAdminDmMessage,everyAdminDmMessage} = await instance.compiler.ready(result) //send channel message if (transcriptConfig.data.general.enableChannel && channelMessage){ - if (instance.pendingMessage && instance.pendingMessage.message && instance.pendingMessage.success){ + if (instance.pendingMessage && instance.pendingMessage.success){ //edit "pending" message to be the "ready" message - instance.pendingMessage.message.edit(channelMessage.message) + instance.pendingMessage.message.edit(utilities.getMessageFromBuildResult(channelMessage,"message")) }else{ //send ready message to channel const post = opendiscord.posts.get("opendiscord:transcripts") @@ -128,7 +168,7 @@ export const registerActions = async () => { }else if (p.role == "participant" && transcriptConfig.data.general.enableParticipantDM && participantDmMessage){ //send participant dm message await opendiscord.client.sendUserDm(p.user,participantDmMessage) - }else if (p.role == "admin" && transcriptConfig.data.general.enableActiveAdminDM && instance.result.success && instance.result.messages && instance.result.messages.some((msg) => msg.author.id == p.user.id) && activeAdminDmMessage){ + }else if (p.role == "admin" && transcriptConfig.data.general.enableActiveAdminDM && result.success && result.messages && result.messages.some((msg) => msg.author.id == p.user.id) && activeAdminDmMessage){ //send active admin dm message await opendiscord.client.sendUserDm(p.user,activeAdminDmMessage) }else if (p.role == "admin" && transcriptConfig.data.general.enableEveryAdminDM && everyAdminDmMessage){ @@ -145,15 +185,15 @@ export const registerActions = async () => { throw new api.ODSystemError("ODAction(ot:create-transcript) => Failed transcript compiler ready()! (see error above)") } } - await opendiscord.events.get("afterTranscriptReady").emit([opendiscord.transcripts,instance.result.ticket,instance.result.channel,instance.result.user]) + await opendiscord.events.get("afterTranscriptReady").emit([opendiscord.transcripts,result.ticket,result.channel,result.user]) }) //update stats - await opendiscord.stats.get("opendiscord:global").setStat("opendiscord:transcripts-created",1,"increase") - await opendiscord.stats.get("opendiscord:user").setStat("opendiscord:transcripts-created",params.user.id,1,"increase") + await opendiscord.statistics.get("opendiscord:global").setStat("opendiscord:transcripts-created",1,"increase") + await opendiscord.statistics.get("opendiscord:user").setStat("opendiscord:transcripts-created",params.user.id,1,"increase") await opendiscord.events.get("afterTranscriptCreated").emit([opendiscord.transcripts,instance.result.ticket,instance.result.channel,instance.result.user]) }), - new api.ODWorker("opendiscord:logs",0,(instance,params,source,cancel) => { + new api.ODWorker("opendiscord:logs",0,(instance,params,origin,cancel) => { const {user,channel,ticket} = params opendiscord.log(user.displayName+" created a transcript!","info",[ {key:"user",value:user.username}, @@ -161,8 +201,8 @@ export const registerActions = async () => { {key:"channel",value:"#"+channel.name}, {key:"channelid",value:channel.id,hidden:true}, {key:"option",value:ticket.option.id.value}, - {key:"method",value:source,hidden:true}, - {key:"compiler",value:instance.compiler.id.value}, + {key:"method",value:origin,hidden:true}, + {key:"compiler",value:instance.compiler?.id.value ?? ""}, ]) }) ]) diff --git a/src/actions/deleteTicket.ts b/src/actions/deleteTicket.ts index 68b0d16..500ab61 100644 --- a/src/actions/deleteTicket.ts +++ b/src/actions/deleteTicket.ts @@ -1,16 +1,16 @@ /////////////////////////////////////// //TICKET DELETION SYSTEM /////////////////////////////////////// -import {opendiscord, api, utilities} from "../index" +import {opendiscord, api, utilities, openticketUtils} from "../index.js" import * as discord from "discord.js" const generalConfig = opendiscord.configs.get("opendiscord:general") const lang = opendiscord.languages -export const registerActions = async () => { +export async function registerActions(){ opendiscord.actions.add(new api.ODAction("opendiscord:delete-ticket")) opendiscord.actions.get("opendiscord:delete-ticket").workers.add([ - new api.ODWorker("opendiscord:delete-ticket",3,async (instance,params,source,cancel) => { + new api.ODWorker("opendiscord:delete-ticket",3,async (instance,params,origin,cancel) => { const {guild,channel,user,ticket,reason} = params if (channel.isThread()) throw new api.ODSystemError("Unable to delete ticket! Open Ticket doesn't support threads!") @@ -20,31 +20,18 @@ export const registerActions = async () => { ticket.get("opendiscord:for-deletion").value = true ticket.get("opendiscord:busy").value = true - //update ticket message - const ticketMessage = await opendiscord.tickets.getTicketMessage(ticket) - if (ticketMessage){ - try{ - ticketMessage.edit((await opendiscord.builders.messages.getSafe("opendiscord:ticket-message").build("other",{guild,channel,user,ticket})).message) - }catch(e){ - opendiscord.log("Unable to edit ticket message on ticket deletion!","error",[ - {key:"channel",value:"#"+channel.name}, - {key:"channelid",value:channel.id,hidden:true}, - {key:"messageid",value:ticketMessage.id}, - {key:"option",value:ticket.option.id.value} - ]) - opendiscord.debugfile.writeErrorMessage(new api.ODError(e,"uncaughtException")) - } - } + //update ticket message (no await) + openticketUtils.updateTicketMessage(guild,channel,user,ticket) - if (params.sendMessage) await channel.send((await opendiscord.builders.messages.getSafe("opendiscord:delete-message").build(source,{guild,channel,user,ticket,reason})).message) + if (params.sendMessage) await channel.send((await opendiscord.builders.messages.getSafe("opendiscord:delete-message").build(origin,{guild,channel,user,ticket,reason})).message) //create transcript if (!params.withoutTranscript){ - const transcriptRes = await opendiscord.actions.get("opendiscord:create-transcript").run(source,{guild,channel,user,ticket}) + const transcriptRes = await opendiscord.actions.get("opendiscord:create-transcript").run(origin,{guild,channel,user,ticket}) //transcript failure if (typeof transcriptRes.success == "boolean" && !transcriptRes.success && transcriptRes.compiler){ const {compiler} = transcriptRes - await channel.send((await opendiscord.builders.messages.getSafe("opendiscord:transcript-error").build(source,{guild,channel,user,ticket,compiler,reason:transcriptRes.errorReason ?? null})).message) + await channel.send((await opendiscord.builders.messages.getSafe("opendiscord:transcript-error").build(origin,{guild,channel,user,ticket,compiler,reason:transcriptRes.errorReason ?? null})).message) .catch((reason) => opendiscord.log("Unable to send transcript failure to ticket channel!","error",[{key:"id",value:channel.id}])) //undo deletion @@ -59,8 +46,8 @@ export const registerActions = async () => { } //update stats - await opendiscord.stats.get("opendiscord:global").setStat("opendiscord:tickets-deleted",1,"increase") - await opendiscord.stats.get("opendiscord:user").setStat("opendiscord:tickets-deleted",user.id,1,"increase") + await opendiscord.statistics.get("opendiscord:global").setStat("opendiscord:tickets-deleted",1,"increase") + await opendiscord.statistics.get("opendiscord:user").setStat("opendiscord:tickets-deleted",user.id,1,"increase") //delete ticket from manager opendiscord.tickets.remove(ticket.id) @@ -68,21 +55,21 @@ export const registerActions = async () => { //delete permissions from manager await (await import("../data/framework/permissionLoader.js")).removeTicketPermissions(ticket) }), - new api.ODWorker("opendiscord:discord-logs",2,async (instance,params,source,cancel) => { + new api.ODWorker("opendiscord:discord-logs",2,async (instance,params,origin,cancel) => { //logs before channel deletion => channel might still be used in log embeds const {guild,channel,user,ticket,reason} = params //to logs - if (generalConfig.data.system.logs.enabled && generalConfig.data.system.messages.deleting.logs){ + if (generalConfig.data.logs.enabled && generalConfig.data.logs.logMessages.deleting.logs){ const logChannel = opendiscord.posts.get("opendiscord:logs") - if (logChannel) logChannel.send(await opendiscord.builders.messages.getSafe("opendiscord:ticket-action-logs").build(source,{guild,channel,user,ticket,mode:"delete",reason,additionalData:null})) + if (logChannel) logChannel.send(await opendiscord.builders.messages.getSafe("opendiscord:ticket-action-logs").build(origin,{guild,channel,user,ticket,mode:"delete",reason,additionalData:null})) } //to dm const creator = await opendiscord.tickets.getTicketUser(ticket,"creator") - if (creator && generalConfig.data.system.messages.deleting.dm) await opendiscord.client.sendUserDm(creator,await opendiscord.builders.messages.getSafe("opendiscord:ticket-action-dm").build(source,{guild,channel,user,ticket,mode:"delete",reason,additionalData:null})) + if (creator && generalConfig.data.logs.logMessages.deleting.dm) await opendiscord.client.sendUserDm(creator,await opendiscord.builders.messages.getSafe("opendiscord:ticket-action-dm").build(origin,{guild,channel,user,ticket,mode:"delete",reason,additionalData:null})) }), - new api.ODWorker("opendiscord:delete-channel",1,async (instance,params,source,cancel) => { + new api.ODWorker("opendiscord:delete-channel",1,async (instance,params,origin,cancel) => { const {guild,channel,user,ticket,reason} = params //delete channel & events await opendiscord.events.get("onTicketChannelDeletion").emit([ticket,channel,user]) @@ -94,7 +81,7 @@ export const registerActions = async () => { await opendiscord.events.get("afterTicketDeleted").emit([ticket,user,reason]) }), - new api.ODWorker("opendiscord:logs",0,(instance,params,source,cancel) => { + new api.ODWorker("opendiscord:logs",0,(instance,params,origin,cancel) => { const {guild,channel,user,ticket} = params opendiscord.log(user.displayName+" deleted a ticket!","info",[ @@ -103,7 +90,7 @@ export const registerActions = async () => { {key:"channel",value:"#"+channel.name}, {key:"channelid",value:channel.id,hidden:true}, {key:"reason",value:params.reason ?? "/"}, - {key:"method",value:source}, + {key:"method",value:origin}, {key:"transcript",value:(!params.withoutTranscript).toString()}, ]) }) @@ -113,357 +100,4 @@ export const registerActions = async () => { params.ticket.get("opendiscord:busy").value = false params.ticket.get("opendiscord:for-deletion").value = false }) -} - -export const registerVerifyBars = async () => { - //DELETE TICKET TICKET MESSAGE - opendiscord.verifybars.add(new api.ODVerifyBar("opendiscord:delete-ticket-ticket-message",opendiscord.builders.messages.getSafe("opendiscord:verifybar-ticket-message"),!generalConfig.data.system.disableVerifyBars)) - opendiscord.verifybars.get("opendiscord:delete-ticket-ticket-message").success.add([ - new api.ODWorker("opendiscord:delete-ticket",0,async (instance,params,source,cancel) => { - const {user,member,channel,guild} = instance - - //check permissions - const permsResult = await opendiscord.permissions.checkCommandPerms(generalConfig.data.system.permissions.delete,"support",user,member,channel,guild) - if (!permsResult.hasPerms){ - if (permsResult.reason == "not-in-server") await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-not-in-guild").build("button",{channel,user})) - else await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-no-permissions").build("button",{guild,channel,user,permissions:["support"]})) - return cancel() - } - - //don't allow deleteWithoutTranscript to non-global-admins when enabled - if (params.data == "no-transcript" && generalConfig.data.system.adminOnlyDeleteWithoutTranscript){ - if (!opendiscord.permissions.hasPermissions("support",await opendiscord.permissions.getPermissions(instance.user,instance.channel,instance.guild,{allowChannelRoleScope:false,allowChannelUserScope:false,allowGlobalRoleScope:true,allowGlobalUserScope:true}))){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-no-permissions").build("button",{guild:instance.guild,channel:instance.channel,user:instance.user,permissions:["support"]})) - return cancel() - } - } - - //check is in guild/server - if (!guild){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-not-in-guild").build("button",{channel,user})) - return cancel() - } - - //check if ticket exists - const ticket = opendiscord.tickets.get(channel.id) - if (!ticket || channel.isDMBased()){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-ticket-unknown").build("button",{guild,channel,user})) - return cancel() - } - - //return when busy - if (ticket.get("opendiscord:busy").value){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-ticket-busy").build("button",{guild,channel,user})) - return cancel() - } - - //return when not allowed because of missing messages - if (!permsResult.isAdmin && (!generalConfig.data.system.allowCloseBeforeMessage || !generalConfig.data.system.allowCloseBeforeAdminMessage)){ - const analysis = await opendiscord.transcripts.collector.ticketUserMessagesAnalysis(ticket,guild,channel) - if (analysis && !generalConfig.data.system.allowCloseBeforeMessage && analysis.totalMessages < 1){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error").build("button",{guild,channel,user,layout:"simple",error:lang.getTranslation("errors.descriptions.closeBeforeMessage"),customTitle:lang.getTranslation("errors.titles.noPermissions")})) - return cancel() - } - if (analysis && !generalConfig.data.system.allowCloseBeforeAdminMessage && analysis.adminMessages < 1){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error").build("button",{guild,channel,user,layout:"simple",error:lang.getTranslation("errors.descriptions.closeBeforeAdminMessage"),customTitle:lang.getTranslation("errors.titles.noPermissions")})) - return cancel() - } - } - - //start deleting ticket - if (params.data == "reason"){ - //delete with reason - instance.modal(await opendiscord.builders.modals.getSafe("opendiscord:delete-ticket-reason").build("ticket-message",{guild,channel,user,ticket})) - }else{ - //delete without reason - await instance.defer("update",false) - //don't await DELETE action => else it will update the message after the channel has been deleted - opendiscord.actions.get("opendiscord:delete-ticket").run("ticket-message",{guild,channel,user,ticket,reason:null,sendMessage:true,withoutTranscript:(params.data == "no-transcript")}) - //update ticket (for ticket message) => no-await doesn't wait for the action to set this variable - ticket.get("opendiscord:for-deletion").value = true - await instance.update(await opendiscord.builders.messages.getSafe("opendiscord:ticket-message").build("other",{guild,channel,user,ticket})) - } - }) - ]) - opendiscord.verifybars.get("opendiscord:delete-ticket-ticket-message").failure.add([ - new api.ODWorker("opendiscord:back-to-ticket-message",0,async (instance,params,source,cancel) => { - const {guild,channel,user} = instance - if (!guild){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-not-in-guild").build("button",{channel,user})) - return cancel() - } - const ticket = opendiscord.tickets.get(channel.id) - if (!ticket || channel.isDMBased()){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-ticket-unknown").build("button",{guild,channel,user})) - return cancel() - } - - await instance.update(await opendiscord.builders.messages.getSafe("opendiscord:ticket-message").build("other",{guild,channel,user,ticket})) - }) - ]) - - //DELETE TICKET CLOSE MESSAGE - opendiscord.verifybars.add(new api.ODVerifyBar("opendiscord:delete-ticket-close-message",opendiscord.builders.messages.getSafe("opendiscord:verifybar-close-message"),!generalConfig.data.system.disableVerifyBars)) - opendiscord.verifybars.get("opendiscord:delete-ticket-close-message").success.add([ - new api.ODWorker("opendiscord:delete-ticket",0,async (instance,params,source,cancel) => { - const {user,member,channel,guild} = instance - - //check permissions - const permsResult = await opendiscord.permissions.checkCommandPerms(generalConfig.data.system.permissions.delete,"support",user,member,channel,guild) - if (!permsResult.hasPerms){ - if (permsResult.reason == "not-in-server") await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-not-in-guild").build("button",{channel,user})) - else await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-no-permissions").build("button",{guild,channel,user,permissions:["support"]})) - return cancel() - } - - //don't allow deleteWithoutTranscript to non-global-admins when enabled - if (params.data == "no-transcript" && generalConfig.data.system.adminOnlyDeleteWithoutTranscript){ - if (!opendiscord.permissions.hasPermissions("support",await opendiscord.permissions.getPermissions(instance.user,instance.channel,instance.guild,{allowChannelRoleScope:false,allowChannelUserScope:false,allowGlobalRoleScope:true,allowGlobalUserScope:true}))){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-no-permissions").build("button",{guild:instance.guild,channel:instance.channel,user:instance.user,permissions:["support"]})) - return cancel() - } - } - - //check is in guild/server - if (!guild){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-not-in-guild").build("button",{channel,user})) - return cancel() - } - - //check if ticket exists - const ticket = opendiscord.tickets.get(channel.id) - if (!ticket || channel.isDMBased()){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-ticket-unknown").build("button",{guild,channel,user})) - return cancel() - } - - //return when busy - if (ticket.get("opendiscord:busy").value){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-ticket-busy").build("button",{guild,channel,user})) - return cancel() - } - - //return when not allowed because of missing messages - if (!permsResult.isAdmin && (!generalConfig.data.system.allowCloseBeforeMessage || !generalConfig.data.system.allowCloseBeforeAdminMessage)){ - const analysis = await opendiscord.transcripts.collector.ticketUserMessagesAnalysis(ticket,guild,channel) - if (analysis && !generalConfig.data.system.allowCloseBeforeMessage && analysis.totalMessages < 1){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error").build("button",{guild,channel,user,layout:"simple",error:lang.getTranslation("errors.descriptions.closeBeforeMessage"),customTitle:lang.getTranslation("errors.titles.noPermissions")})) - return cancel() - } - if (analysis && !generalConfig.data.system.allowCloseBeforeAdminMessage && analysis.adminMessages < 1){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error").build("button",{guild,channel,user,layout:"simple",error:lang.getTranslation("errors.descriptions.closeBeforeAdminMessage"),customTitle:lang.getTranslation("errors.titles.noPermissions")})) - return cancel() - } - } - - //start deleting ticket - if (params.data == "reason"){ - //delete with reason - instance.modal(await opendiscord.builders.modals.getSafe("opendiscord:delete-ticket-reason").build("close-message",{guild,channel,user,ticket})) - }else{ - //delete without reason - await instance.defer("update",false) - //don't await DELETE action => else it will update the message after the channel has been deleted - opendiscord.actions.get("opendiscord:delete-ticket").run("close-message",{guild,channel,user,ticket,reason:null,sendMessage:false,withoutTranscript:(params.data == "no-transcript")}) - //update ticket (for ticket message) => no-await doesn't wait for the action to set this variable - ticket.get("opendiscord:for-deletion").value = true - await instance.update(await opendiscord.builders.messages.getSafe("opendiscord:delete-message").build("close-message",{guild,channel,user,ticket,reason:null})) - } - }) - ]) - opendiscord.verifybars.get("opendiscord:delete-ticket-close-message").failure.add([ - new api.ODWorker("opendiscord:back-to-close-message",0,async (instance,params,source,cancel) => { - const {guild,channel,user} = instance - const {verifybarMessage} = params - if (!guild){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-not-in-guild").build("button",{channel,user})) - return cancel() - } - const ticket = opendiscord.tickets.get(channel.id) - if (!ticket || channel.isDMBased()){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-ticket-unknown").build("button",{guild,channel,user})) - return cancel() - } - - const rawReason = (verifybarMessage && verifybarMessage.embeds[0] && verifybarMessage.embeds[0].fields[0]) ? verifybarMessage.embeds[0].fields[0].value : null - const reason = (rawReason == null) ? null : rawReason.substring(3,rawReason.length-3) - - await instance.update(await opendiscord.builders.messages.getSafe("opendiscord:close-message").build("other",{guild,channel,user,ticket,reason})) - }) - ]) - - //DELETE TICKET REOPEN MESSAGE - opendiscord.verifybars.add(new api.ODVerifyBar("opendiscord:delete-ticket-reopen-message",opendiscord.builders.messages.getSafe("opendiscord:verifybar-reopen-message"),!generalConfig.data.system.disableVerifyBars)) - opendiscord.verifybars.get("opendiscord:delete-ticket-reopen-message").success.add([ - new api.ODWorker("opendiscord:delete-ticket",0,async (instance,params,source,cancel) => { - const {user,member,channel,guild} = instance - - //check permissions - const permsResult = await opendiscord.permissions.checkCommandPerms(generalConfig.data.system.permissions.delete,"support",user,member,channel,guild) - if (!permsResult.hasPerms){ - if (permsResult.reason == "not-in-server") await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-not-in-guild").build("button",{channel,user})) - else await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-no-permissions").build("button",{guild,channel,user,permissions:["support"]})) - return cancel() - } - - //don't allow deleteWithoutTranscript to non-global-admins when enabled - if (params.data == "no-transcript" && generalConfig.data.system.adminOnlyDeleteWithoutTranscript){ - if (!opendiscord.permissions.hasPermissions("support",await opendiscord.permissions.getPermissions(instance.user,instance.channel,instance.guild,{allowChannelRoleScope:false,allowChannelUserScope:false,allowGlobalRoleScope:true,allowGlobalUserScope:true}))){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-no-permissions").build("button",{guild:instance.guild,channel:instance.channel,user:instance.user,permissions:["support"]})) - return cancel() - } - } - - //check is in guild/server - if (!guild){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-not-in-guild").build("button",{channel,user})) - return cancel() - } - - //check if ticket exists - const ticket = opendiscord.tickets.get(channel.id) - if (!ticket || channel.isDMBased()){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-ticket-unknown").build("button",{guild,channel,user})) - return cancel() - } - - //return when busy - if (ticket.get("opendiscord:busy").value){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-ticket-busy").build("button",{guild,channel,user})) - return cancel() - } - //return when not allowed because of missing messages - if (!permsResult.isAdmin && (!generalConfig.data.system.allowCloseBeforeMessage || !generalConfig.data.system.allowCloseBeforeAdminMessage)){ - const analysis = await opendiscord.transcripts.collector.ticketUserMessagesAnalysis(ticket,guild,channel) - if (analysis && !generalConfig.data.system.allowCloseBeforeMessage && analysis.totalMessages < 1){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error").build("button",{guild,channel,user,layout:"simple",error:lang.getTranslation("errors.descriptions.closeBeforeMessage"),customTitle:lang.getTranslation("errors.titles.noPermissions")})) - return cancel() - } - if (analysis && !generalConfig.data.system.allowCloseBeforeAdminMessage && analysis.adminMessages < 1){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error").build("button",{guild,channel,user,layout:"simple",error:lang.getTranslation("errors.descriptions.closeBeforeAdminMessage"),customTitle:lang.getTranslation("errors.titles.noPermissions")})) - return cancel() - } - } - - //start deleting ticket - if (params.data == "reason"){ - //delete with reason - instance.modal(await opendiscord.builders.modals.getSafe("opendiscord:delete-ticket-reason").build("reopen-message",{guild,channel,user,ticket})) - }else{ - //delete without reason - await instance.defer("update",false) - //don't await DELETE action => else it will update the message after the channel has been deleted - opendiscord.actions.get("opendiscord:delete-ticket").run("reopen-message",{guild,channel,user,ticket,reason:null,sendMessage:false,withoutTranscript:(params.data == "no-transcript")}) - //update ticket (for ticket message) => no-await doesn't wait for the action to set this variable - ticket.get("opendiscord:for-deletion").value = true - await instance.update(await opendiscord.builders.messages.getSafe("opendiscord:delete-message").build("reopen-message",{guild,channel,user,ticket,reason:null})) - } - }) - ]) - opendiscord.verifybars.get("opendiscord:delete-ticket-reopen-message").failure.add([ - new api.ODWorker("opendiscord:back-to-reopen-message",0,async (instance,params,source,cancel) => { - const {guild,channel,user} = instance - const {verifybarMessage} = params - if (!guild){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-not-in-guild").build("button",{channel,user})) - return cancel() - } - const ticket = opendiscord.tickets.get(channel.id) - if (!ticket || channel.isDMBased()){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-ticket-unknown").build("button",{guild,channel,user})) - return cancel() - } - - const rawReason = (verifybarMessage && verifybarMessage.embeds[0] && verifybarMessage.embeds[0].fields[0]) ? verifybarMessage.embeds[0].fields[0].value : null - const reason = (rawReason == null) ? null : rawReason.substring(3,rawReason.length-3) - - await instance.update(await opendiscord.builders.messages.getSafe("opendiscord:reopen-message").build("other",{guild,channel,user,ticket,reason})) - }) - ]) - - //DELETE TICKET AUTOCLOSE MESSAGE - opendiscord.verifybars.add(new api.ODVerifyBar("opendiscord:delete-ticket-autoclose-message",opendiscord.builders.messages.getSafe("opendiscord:verifybar-autoclose-message"),!generalConfig.data.system.disableVerifyBars)) - opendiscord.verifybars.get("opendiscord:delete-ticket-autoclose-message").success.add([ - new api.ODWorker("opendiscord:delete-ticket",0,async (instance,params,source,cancel) => { - const {user,member,channel,guild} = instance - - //check permissions - const permsResult = await opendiscord.permissions.checkCommandPerms(generalConfig.data.system.permissions.delete,"support",user,member,channel,guild) - if (!permsResult.hasPerms){ - if (permsResult.reason == "not-in-server") await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-not-in-guild").build("button",{channel,user})) - else await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-no-permissions").build("button",{guild,channel,user,permissions:["support"]})) - return cancel() - } - - //don't allow deleteWithoutTranscript to non-global-admins when enabled - if (params.data == "no-transcript" && generalConfig.data.system.adminOnlyDeleteWithoutTranscript){ - if (!opendiscord.permissions.hasPermissions("support",await opendiscord.permissions.getPermissions(instance.user,instance.channel,instance.guild,{allowChannelRoleScope:false,allowChannelUserScope:false,allowGlobalRoleScope:true,allowGlobalUserScope:true}))){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-no-permissions").build("button",{guild:instance.guild,channel:instance.channel,user:instance.user,permissions:["support"]})) - return cancel() - } - } - - //check is in guild/server - if (!guild){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-not-in-guild").build("button",{channel,user})) - return cancel() - } - - //check if ticket exists - const ticket = opendiscord.tickets.get(channel.id) - if (!ticket || channel.isDMBased()){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-ticket-unknown").build("button",{guild,channel,user})) - return cancel() - } - - //return when busy - if (ticket.get("opendiscord:busy").value){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-ticket-busy").build("button",{guild,channel,user})) - return cancel() - } - //return when not allowed because of missing messages - if (!permsResult.isAdmin && (!generalConfig.data.system.allowCloseBeforeMessage || !generalConfig.data.system.allowCloseBeforeAdminMessage)){ - const analysis = await opendiscord.transcripts.collector.ticketUserMessagesAnalysis(ticket,guild,channel) - if (analysis && !generalConfig.data.system.allowCloseBeforeMessage && analysis.totalMessages < 1){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error").build("button",{guild,channel,user,layout:"simple",error:lang.getTranslation("errors.descriptions.closeBeforeMessage"),customTitle:lang.getTranslation("errors.titles.noPermissions")})) - return cancel() - } - if (analysis && !generalConfig.data.system.allowCloseBeforeAdminMessage && analysis.adminMessages < 1){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error").build("button",{guild,channel,user,layout:"simple",error:lang.getTranslation("errors.descriptions.closeBeforeAdminMessage"),customTitle:lang.getTranslation("errors.titles.noPermissions")})) - return cancel() - } - } - - //start deleting ticket - if (params.data == "reason"){ - //delete with reason - instance.modal(await opendiscord.builders.modals.getSafe("opendiscord:delete-ticket-reason").build("autoclose-message",{guild,channel,user,ticket})) - }else{ - //delete without reason - await instance.defer("update",false) - //don't await DELETE action => else it will update the message after the channel has been deleted - opendiscord.actions.get("opendiscord:delete-ticket").run("autoclose-message",{guild,channel,user,ticket,reason:null,sendMessage:false,withoutTranscript:(params.data == "no-transcript")}) - //update ticket (for ticket message) => no-await doesn't wait for the action to set this variable - ticket.get("opendiscord:for-deletion").value = true - await instance.update(await opendiscord.builders.messages.getSafe("opendiscord:delete-message").build("autoclose-message",{guild,channel,user,ticket,reason:null})) - } - }) - ]) - opendiscord.verifybars.get("opendiscord:delete-ticket-autoclose-message").failure.add([ - new api.ODWorker("opendiscord:back-to-autoclose-message",0,async (instance,params,source,cancel) => { - const {guild,channel,user} = instance - const {verifybarMessage} = params - if (!guild){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-not-in-guild").build("button",{channel,user})) - return cancel() - } - const ticket = opendiscord.tickets.get(channel.id) - if (!ticket || channel.isDMBased()){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-ticket-unknown").build("button",{guild,channel,user})) - return cancel() - } - - await instance.update(await opendiscord.builders.messages.getSafe("opendiscord:autoclose-message").build("other",{guild,channel,user,ticket})) - }) - ]) } \ No newline at end of file diff --git a/src/actions/handleTranscriptErrors.ts b/src/actions/handleTranscriptErrors.ts index bc0cf9a..d9548d0 100644 --- a/src/actions/handleTranscriptErrors.ts +++ b/src/actions/handleTranscriptErrors.ts @@ -1,76 +1,43 @@ /////////////////////////////////////// //TRANSCRIPT ERROR SYSTEM /////////////////////////////////////// -import {opendiscord, api, utilities} from "../index" +import {opendiscord, api, utilities, openticketUtils} from "../index.js" +import * as discord from "discord.js" const generalConfig = opendiscord.configs.get("opendiscord:general") -export const registerButtonResponders = async () => { +export async function registerButtonResponders(){ //TRANSCRIPT ERROR RETRY opendiscord.responders.buttons.add(new api.ODButtonResponder("opendiscord:transcript-error-retry",/^od:transcript-error-retry_([^_]+)/)) opendiscord.responders.buttons.get("opendiscord:transcript-error-retry").workers.add([ - new api.ODWorker("opendiscord:permissions",1,async (instance,params,source,cancel) => { - const permissionMode = generalConfig.data.system.permissions.delete - - if (permissionMode == "none"){ - //no permissions - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-no-permissions").build("button",{guild:instance.guild,channel:instance.channel,user:instance.user,permissions:[]})) - return cancel() - }else if (permissionMode == "everyone") return - else if (permissionMode == "admin"){ - if (!opendiscord.permissions.hasPermissions("support",await opendiscord.permissions.getPermissions(instance.user,instance.channel,instance.guild))){ - //no permissions - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-no-permissions").build("button",{guild:instance.guild,channel:instance.channel,user:instance.user,permissions:["support"]})) - return cancel() - }else return - }else{ - if (!instance.guild || !instance.member){ - //error - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error").build("button",{guild:instance.guild,channel:instance.channel,user:instance.user,error:"Permission Error: Not in Server #1",layout:"advanced"})) - return cancel() - } - const role = await opendiscord.client.fetchGuildRole(instance.guild,permissionMode) - if (!role){ - //error - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error").build("button",{guild:instance.guild,channel:instance.channel,user:instance.user,error:"Permission Error: Not in Server #2",layout:"advanced"})) - return cancel() - } - if (!role.members.has(instance.member.id)){ - //no permissions - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-no-permissions").build("button",{guild:instance.guild,channel:instance.channel,user:instance.user,permissions:[]})) - return cancel() - }else return - } - }), - new api.ODWorker("opendiscord:delete-ticket",0,async (instance,params,source,cancel) => { - const {guild,channel,user} = instance - const originalSource = instance.interaction.customId.split("_")[1] as api.ODActionManagerIds_Default["opendiscord:delete-ticket"]["source"] + new api.ODWorker("opendiscord:delete-ticket",0,async (instance,params,origin,cancel) => { + const {guild,channel,user,member} = instance - if (!guild){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-not-in-guild").build("button",{channel,user})) - return cancel() - } - const ticket = opendiscord.tickets.get(channel.id) - if (!ticket || channel.isDMBased()){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-ticket-unknown").build("button",{guild,channel,user})) - return cancel() - } - //return when busy - if (ticket.get("opendiscord:busy").value){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-ticket-busy").build("button",{guild,channel,user})) - return cancel() - } + //responder checks + const hasPerms = await openticketUtils.replyHasPermissions(instance,origin,"delete") + if (!hasPerms) return cancel() + + const isInGuild = await openticketUtils.replyIsInGuild(instance,origin) + if (!isInGuild || !guild || channel.isDMBased()) return cancel() + + const ticket = await openticketUtils.replyIsTicket(instance,origin) + if (!ticket) return cancel() + + const isAvailable = await openticketUtils.replyTicketIsAvailable(instance,origin,ticket) + if (!isAvailable) return cancel() + //fetch data + const originalOrigin = instance.interaction.customId.split("_")[1] as api.ODActionManagerIdMappings["opendiscord:delete-ticket"]["origin"] + //start deleting ticket (without reason) await instance.defer("update",false) //don't await DELETE action => else it will update the message after the channel has been deleted - opendiscord.actions.get("opendiscord:delete-ticket").run(originalSource,{guild,channel,user,ticket,reason:"Transcript Error (Retried)",sendMessage:false,withoutTranscript:false}) - //update ticket (for ticket message) => no-await doesn't wait for the action to set this variable - ticket.get("opendiscord:for-deletion").value = true + opendiscord.actions.get("opendiscord:delete-ticket").run(originalOrigin,{guild,channel,user,ticket,reason:"Transcript Error (Retried)",sendMessage:false,withoutTranscript:false}) + + ticket.get("opendiscord:for-deletion").value = true //disable ticket message buttons await instance.update(await opendiscord.builders.messages.getSafe("opendiscord:delete-message").build("other",{guild,channel,user,ticket,reason:"Transcript Error (Retried)"})) - }), - new api.ODWorker("opendiscord:logs",-1,async (instance,params,source,cancel) => { + new api.ODWorker("opendiscord:logs",-1,async (instance,params,origin,cancel) => { const {user,channel} = instance if (channel.isDMBased()) return opendiscord.log(user.displayName+" retried deleting a ticket with transcript!","info",[ @@ -78,7 +45,7 @@ export const registerButtonResponders = async () => { {key:"userid",value:user.id,hidden:true}, {key:"channel",value:"#"+channel.name}, {key:"channelid",value:channel.id,hidden:true}, - {key:"method",value:source} + {key:"method",value:origin} ]) }) ]) @@ -86,68 +53,35 @@ export const registerButtonResponders = async () => { //TRANSCRIPT ERROR CONTINUE opendiscord.responders.buttons.add(new api.ODButtonResponder("opendiscord:transcript-error-continue",/^od:transcript-error-continue_([^_]+)/)) opendiscord.responders.buttons.get("opendiscord:transcript-error-continue").workers.add([ - new api.ODWorker("opendiscord:permissions",1,async (instance,params,source,cancel) => { - const permissionMode = generalConfig.data.system.permissions.delete - - if (permissionMode == "none"){ - //no permissions - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-no-permissions").build("button",{guild:instance.guild,channel:instance.channel,user:instance.user,permissions:[]})) - return cancel() - }else if (permissionMode == "everyone") return - else if (permissionMode == "admin"){ - if (!opendiscord.permissions.hasPermissions("support",await opendiscord.permissions.getPermissions(instance.user,instance.channel,instance.guild))){ - //no permissions - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-no-permissions").build("button",{guild:instance.guild,channel:instance.channel,user:instance.user,permissions:["support"]})) - return cancel() - }else return - }else{ - if (!instance.guild || !instance.member){ - //error - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error").build("button",{guild:instance.guild,channel:instance.channel,user:instance.user,error:"Permission Error: Not in Server #1",layout:"advanced"})) - return cancel() - } - const role = await opendiscord.client.fetchGuildRole(instance.guild,permissionMode) - if (!role){ - //error - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error").build("button",{guild:instance.guild,channel:instance.channel,user:instance.user,error:"Permission Error: Not in Server #2",layout:"advanced"})) - return cancel() - } - if (!role.members.has(instance.member.id)){ - //no permissions - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-no-permissions").build("button",{guild:instance.guild,channel:instance.channel,user:instance.user,permissions:[]})) - return cancel() - }else return - } - }), - new api.ODWorker("opendiscord:delete-ticket",0,async (instance,params,source,cancel) => { + new api.ODWorker("opendiscord:delete-ticket",0,async (instance,params,origin,cancel) => { const {guild,channel,user} = instance - const originalSource = instance.interaction.customId.split("_")[1] as api.ODActionManagerIds_Default["opendiscord:delete-ticket"]["source"] - if (!guild){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-not-in-guild").build("button",{channel,user})) - return cancel() - } - const ticket = opendiscord.tickets.get(channel.id) - if (!ticket || channel.isDMBased()){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-ticket-unknown").build("button",{guild,channel,user})) - return cancel() - } - //return when busy - if (ticket.get("opendiscord:busy").value){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-ticket-busy").build("button",{guild,channel,user})) - return cancel() - } + //responder checks + const hasPerms = await openticketUtils.replyHasPermissions(instance,origin,"delete") + if (!hasPerms) return cancel() + + const isInGuild = await openticketUtils.replyIsInGuild(instance,origin) + if (!isInGuild || !guild || channel.isDMBased()) return cancel() + + const ticket = await openticketUtils.replyIsTicket(instance,origin) + if (!ticket) return cancel() + + const isAvailable = await openticketUtils.replyTicketIsAvailable(instance,origin,ticket) + if (!isAvailable) return cancel() - //start deleting ticket (without reason) + //fetch data + const originalOrigin = instance.interaction.customId.split("_")[1] as api.ODActionManagerIdMappings["opendiscord:delete-ticket"]["origin"] + + //start deleting ticket (without reason & without transcript) await instance.defer("update",false) //don't await DELETE action => else it will update the message after the channel has been deleted - opendiscord.actions.get("opendiscord:delete-ticket").run(originalSource,{guild,channel,user,ticket,reason:"Transcript Error (Continued)",sendMessage:false,withoutTranscript:true}) - //update ticket (for ticket message) => no-await doesn't wait for the action to set this variable - ticket.get("opendiscord:for-deletion").value = true + opendiscord.actions.get("opendiscord:delete-ticket").run(originalOrigin,{guild,channel,user,ticket,reason:"Transcript Error (Continued)",sendMessage:false,withoutTranscript:true}) + + ticket.get("opendiscord:for-deletion").value = true //disable ticket message buttons await instance.update(await opendiscord.builders.messages.getSafe("opendiscord:delete-message").build("other",{guild,channel,user,ticket,reason:"Transcript Error (Continued)"})) }), - new api.ODWorker("opendiscord:logs",-1,async (instance,params,source,cancel) => { + new api.ODWorker("opendiscord:logs",-1,async (instance,params,origin,cancel) => { const {user,channel} = instance if (channel.isDMBased()) return opendiscord.log(user.displayName+" continued deleting a ticket without transcript!","info",[ @@ -155,7 +89,7 @@ export const registerButtonResponders = async () => { {key:"userid",value:user.id,hidden:true}, {key:"channel",value:"#"+channel.name}, {key:"channelid",value:channel.id,hidden:true}, - {key:"method",value:source} + {key:"method",value:origin} ]) }) ]) diff --git a/src/actions/handleVerifyBar.ts b/src/actions/handleVerifyBar.ts index 2794d71..3eb6960 100644 --- a/src/actions/handleVerifyBar.ts +++ b/src/actions/handleVerifyBar.ts @@ -1,32 +1,22 @@ /////////////////////////////////////// //VERIFYBAR SYSTEM /////////////////////////////////////// -import {opendiscord, api, utilities} from "../index" +import {opendiscord, api, utilities, openticketUtils} from "../index.js" +import * as discord from "discord.js" -export const registerButtonResponders = async () => { - //VERIFYBAR SUCCESS - opendiscord.responders.buttons.add(new api.ODButtonResponder("opendiscord:verifybar-success",/^od:verifybar-success_([^_]+)/)) - opendiscord.responders.buttons.get("opendiscord:verifybar-success").workers.add( - new api.ODWorker("opendiscord:handle-verifybar",0,async (instance,params,source,cancel) => { - const id = instance.interaction.customId.split("_")[1] - const customData = instance.interaction.customId.split("_")[2] as string|undefined +export async function registerButtonResponders(){ + //HANDLE VERIFYBAR BUTTON + opendiscord.responders.buttons.add(new api.ODButtonResponder("opendiscord:verifybar-button",/^od:verifybar\|([^|]+)\|([^|]+)/)) + opendiscord.responders.buttons.get("opendiscord:verifybar-button").workers.add( + new api.ODWorker("opendiscord:handle-verifybar",0,async (instance,params,origin,cancel) => { + const match = /^od:verifybar\|([^|]+)\|([^|]+)/.exec(instance.interaction.customId) + if (!match) return cancel() + const verifyBarId = match[1] + const verifyButtonId = match[2] - const verifybar = opendiscord.verifybars.get(id) + const verifybar = opendiscord.verifybars.get(verifyBarId) if (!verifybar) return - if (verifybar.success) await verifybar.success.executeWorkers(instance,"verifybar",{data:customData ?? null,verifybarMessage:instance.message}) - }) - ) - - //VERIFYBAR FAILURE - opendiscord.responders.buttons.add(new api.ODButtonResponder("opendiscord:verifybar-failure",/^od:verifybar-failure_([^_]+)/)) - opendiscord.responders.buttons.get("opendiscord:verifybar-failure").workers.add( - new api.ODWorker("opendiscord:handle-verifybar",0,async (instance,params,source,cancel) => { - const id = instance.interaction.customId.split("_")[1] - const customData = instance.interaction.customId.split("_")[2] as string|undefined - - const verifybar = opendiscord.verifybars.get(id) - if (!verifybar) return - if (verifybar.failure) await verifybar.failure.executeWorkers(instance,"verifybar",{data:customData ?? null,verifybarMessage:instance.message}) + await verifybar.activate(instance,verifyButtonId) }) ) } \ No newline at end of file diff --git a/src/actions/moveTicket.ts b/src/actions/moveTicket.ts index 591aef6..2679368 100644 --- a/src/actions/moveTicket.ts +++ b/src/actions/moveTicket.ts @@ -1,15 +1,15 @@ /////////////////////////////////////// //TICKET MOVING SYSTEM /////////////////////////////////////// -import {opendiscord, api, utilities} from "../index" +import {opendiscord, api, utilities, openticketUtils} from "../index.js" import * as discord from "discord.js" const generalConfig = opendiscord.configs.get("opendiscord:general") -export const registerActions = async () => { +export async function registerActions(){ opendiscord.actions.add(new api.ODAction("opendiscord:move-ticket")) opendiscord.actions.get("opendiscord:move-ticket").workers.add([ - new api.ODWorker("opendiscord:move-ticket",2,async (instance,params,source,cancel) => { + new api.ODWorker("opendiscord:move-ticket",2,async (instance,params,origin,cancel) => { const {guild,channel,user,ticket,reason,data} = params if (channel.isThread()) throw new api.ODSystemError("Unable to move ticket! Open Ticket doesn't support threads!") @@ -17,78 +17,31 @@ export const registerActions = async () => { ticket.option = data //update stats - await opendiscord.stats.get("opendiscord:global").setStat("opendiscord:tickets-moved",1,"increase") - await opendiscord.stats.get("opendiscord:user").setStat("opendiscord:tickets-moved",user.id,1,"increase") + await opendiscord.statistics.get("opendiscord:global").setStat("opendiscord:tickets-moved",1,"increase") + await opendiscord.statistics.get("opendiscord:user").setStat("opendiscord:tickets-moved",user.id,1,"increase") - //get new channel properties - const channelPrefix = ticket.option.get("opendiscord:channel-prefix").value - const channelSuffix = ticket.get("opendiscord:channel-suffix").value - const channelCategory = ticket.option.get("opendiscord:channel-category").value - const channelBackupCategory = ticket.option.get("opendiscord:channel-category-backup").value - const rawClaimCategory = ticket.option.get("opendiscord:channel-categories-claimed").value.find((c) => c.user == user.id) - const claimCategory = (rawClaimCategory) ? rawClaimCategory.category : null - const closeCategory = ticket.option.get("opendiscord:channel-category-closed").value - const channelTopic = ticket.option.get("opendiscord:channel-topic").value - - //handle category - let category: string|null = null - let categoryMode: "backup"|"normal"|"closed"|"claimed"|null = null - if (claimCategory){ - //use claim category - category = claimCategory - categoryMode = "claimed" - }else if (closeCategory != "" && ticket.get("opendiscord:closed").value){ - //use close category - category = closeCategory - categoryMode = "closed" - }else if (channelCategory != ""){ - //category enabled - const normalCategory = await opendiscord.client.fetchGuildCategoryChannel(guild,channelCategory) - if (!normalCategory){ - //default category was not found - opendiscord.log("Ticket Move Error: Unable to find category! #1","error",[ - {key:"categoryid",value:channelCategory}, - {key:"backup",value:"false"} + //calculate & update category + const categoryResult = await opendiscord.actions.get("opendiscord:calculate-ticket-category").run("move-ticket",{guild,user,option:ticket.option,channel,ticket,currentCategoryId:channel.parentId}) + if (categoryResult && categoryResult.shouldChangeCategory && typeof categoryResult.newCategoryId !== "undefined" && typeof categoryResult.newCategoryMode !== "undefined" && typeof categoryResult.newCategory !== "undefined"){ + const originalCategoryName = channel.parent?.name ?? "" + const newCategoryName = categoryResult.newCategory?.name ?? "" + try{ + await utilities.timedAwait(channel.setParent(categoryResult.newCategoryId,{lockPermissions:false}),3000,(err) => { + process.emit("uncaughtException",new Error("Error: Unable to change channel parent: "+err)) + }) + ticket.get("opendiscord:category-mode").value = categoryResult.newCategoryMode + ticket.get("opendiscord:category").value = categoryResult.newCategoryId + }catch(err){ + const sentMsg = await channel.send((await opendiscord.builders.messages.getSafe("opendiscord:error-channel-category").build("ticket-move",{guild,channel,user,originalCategory:originalCategoryName,newCategory:newCategoryName})).message) + setTimeout(() => {if (sentMsg.deletable) sentMsg.delete()},7000) //autodelete error message + opendiscord.log("Unable to move ticket to moved category.","error",[ + {key:"channel",value:"#"+channel.name}, + {key:"channelid",value:channel.id,hidden:true}, + {key:"categoryid",value:categoryResult.newCategoryId ?? "/"} ]) - }else{ - //default category was found - if (normalCategory.children.cache.size >= 50 && channelBackupCategory != ""){ - //use backup category - const backupCategory = await opendiscord.client.fetchGuildCategoryChannel(guild,channelBackupCategory) - if (!backupCategory){ - //default category was not found - opendiscord.log("Ticket Move Error: Unable to find category! #2","error",[ - {key:"categoryid",value:channelBackupCategory}, - {key:"backup",value:"true"} - ]) - }else{ - category = backupCategory.id - categoryMode = "backup" - } - }else{ - //use default category - category = normalCategory.id - categoryMode = "normal" - } } } - try { - //only move category when not the same. - if (channel.parentId != category) await utilities.timedAwait(channel.setParent(category,{lockPermissions:false}),2500,(err) => { - opendiscord.log("Failed to change channel category on ticket move","error") - }) - ticket.get("opendiscord:category-mode").value = categoryMode - ticket.get("opendiscord:category").value = category - }catch(e){ - opendiscord.log("Unable to move ticket to 'moved category'!","error",[ - {key:"channel",value:"#"+channel.name}, - {key:"channelid",value:channel.id,hidden:true}, - {key:"categoryid",value:category ?? "/"} - ]) - opendiscord.debugfile.writeErrorMessage(new api.ODError(e,"uncaughtException")) - } - //handle permissions const permissions: discord.OverwriteResolvable[] = [{ type:discord.OverwriteType.Role, @@ -153,58 +106,50 @@ export const registerActions = async () => { ticket.get("opendiscord:participants").value = participants ticket.get("opendiscord:participants").refreshDatabase() - //rename channel (and give error when crashed) - const pinEmoji = ticket.get("opendiscord:pinned").value ? generalConfig.data.system.pinEmoji : "" - const priorityEmoji = opendiscord.priorities.getFromPriorityLevel(ticket.get("opendiscord:priority").value).channelEmoji ?? "" - - const originalName = channel.name - const newName = pinEmoji+priorityEmoji+utilities.trimEmojis(channelPrefix+channelSuffix) - try{ - await utilities.timedAwait(channel.setName(newName),2500,(err) => { - opendiscord.log("Failed to rename channel on ticket move","error") - }) - }catch(err){ - await channel.send((await opendiscord.builders.messages.getSafe("opendiscord:error-channel-rename").build("ticket-move",{guild,channel,user,originalName,newName:newName})).message) - } - - //update ticket message - const ticketMessage = await opendiscord.tickets.getTicketMessage(ticket) - if (ticketMessage){ + //calculate channel name + const channelNameResult = await opendiscord.actions.get("opendiscord:calculate-ticket-name").run("move-ticket",{guild,user,option:ticket.option,channel,ticket,currentChannelName:channel.name}) + if (channelNameResult && channelNameResult.shouldChangeName && typeof channelNameResult.newChannelName !== "undefined"){ + const originalName = channel.name + const newName = channelNameResult.newChannelName try{ - ticketMessage.edit((await opendiscord.builders.messages.getSafe("opendiscord:ticket-message").build("other",{guild,channel,user,ticket})).message) - }catch(e){ - opendiscord.log("Unable to edit ticket message on ticket moving!","error",[ - {key:"channel",value:"#"+channel.name}, - {key:"channelid",value:channel.id,hidden:true}, - {key:"messageid",value:ticketMessage.id}, - {key:"option",value:ticket.option.id.value} + await utilities.timedAwait(channel.setName(newName),2500,(err) => { + opendiscord.log("Failed to rename channel on ticket move","error") + }) + }catch(err){ + opendiscord.log("Unable to rename channel while moving ticket! Waiting until ratelimit expires...","warning",[ + {key:"oldName",value:originalName}, + {key:"newName",value:newName} ]) - opendiscord.debugfile.writeErrorMessage(new api.ODError(e,"uncaughtException")) + const sentMsg = await channel.send((await opendiscord.builders.messages.getSafe("opendiscord:error-channel-rename").build("ticket-move",{guild,channel,user,originalName,newName})).message) + setTimeout(() => {if (sentMsg.deletable) sentMsg.delete()},7000) //autodelete error message } } + //update ticket message (no await) + openticketUtils.updateTicketMessage(guild,channel,user,ticket) + //reply with new message - if (params.sendMessage) await channel.send((await opendiscord.builders.messages.getSafe("opendiscord:move-message").build(source,{guild,channel,user,ticket,reason,data})).message) + if (params.sendMessage) await channel.send((await opendiscord.builders.messages.getSafe("opendiscord:move-message").build(origin,{guild,channel,user,ticket,reason,data})).message) ticket.get("opendiscord:busy").value = false await opendiscord.events.get("afterTicketMoved").emit([ticket,user,channel,reason]) //update channel topic await opendiscord.actions.get("opendiscord:update-ticket-topic").run("ticket-action",{guild,channel,user,ticket,sendMessage:false,newTopic:null}) }), - new api.ODWorker("opendiscord:discord-logs",1,async (instance,params,source,cancel) => { + new api.ODWorker("opendiscord:discord-logs",1,async (instance,params,origin,cancel) => { const {guild,channel,user,ticket,reason,data} = params //to logs - if (generalConfig.data.system.logs.enabled && generalConfig.data.system.messages.moving.logs){ + if (generalConfig.data.logs.enabled && generalConfig.data.logs.logMessages.moving.logs){ const logChannel = opendiscord.posts.get("opendiscord:logs") - if (logChannel) logChannel.send(await opendiscord.builders.messages.getSafe("opendiscord:ticket-action-logs").build(source,{guild,channel,user,ticket,mode:"move",reason,additionalData:data})) + if (logChannel) logChannel.send(await opendiscord.builders.messages.getSafe("opendiscord:ticket-action-logs").build(origin,{guild,channel,user,ticket,mode:"move",reason,additionalData:data})) } //to dm const creator = await opendiscord.tickets.getTicketUser(ticket,"creator") - if (creator && generalConfig.data.system.messages.moving.dm) await opendiscord.client.sendUserDm(creator,await opendiscord.builders.messages.getSafe("opendiscord:ticket-action-dm").build(source,{guild,channel,user,ticket,mode:"move",reason,additionalData:data})) + if (creator && generalConfig.data.logs.logMessages.moving.dm) await opendiscord.client.sendUserDm(creator,await opendiscord.builders.messages.getSafe("opendiscord:ticket-action-dm").build(origin,{guild,channel,user,ticket,mode:"move",reason,additionalData:data})) }), - new api.ODWorker("opendiscord:logs",0,(instance,params,source,cancel) => { + new api.ODWorker("opendiscord:logs",0,(instance,params,origin,cancel) => { const {guild,channel,user,ticket} = params opendiscord.log(user.displayName+" moved a ticket!","info",[ @@ -213,7 +158,7 @@ export const registerActions = async () => { {key:"channel",value:"#"+channel.name}, {key:"channelid",value:channel.id,hidden:true}, {key:"reason",value:params.reason ?? "/"}, - {key:"method",value:source} + {key:"method",value:origin} ]) }) ]) diff --git a/src/actions/pinTicket.ts b/src/actions/pinTicket.ts index a388ae0..6d52953 100644 --- a/src/actions/pinTicket.ts +++ b/src/actions/pinTicket.ts @@ -1,15 +1,16 @@ /////////////////////////////////////// //TICKET PINNING SYSTEM /////////////////////////////////////// -import {opendiscord, api, utilities} from "../index" +import {opendiscord, api, utilities, openticketUtils} from "../index.js" import * as discord from "discord.js" const generalConfig = opendiscord.configs.get("opendiscord:general") +const interactiveMsgState = opendiscord.states.get("opendiscord:interactive-message") -export const registerActions = async () => { +export async function registerActions(){ opendiscord.actions.add(new api.ODAction("opendiscord:pin-ticket")) opendiscord.actions.get("opendiscord:pin-ticket").workers.add([ - new api.ODWorker("opendiscord:pin-ticket",2,async (instance,params,source,cancel) => { + new api.ODWorker("opendiscord:pin-ticket",2,async (instance,params,origin,cancel) => { const {guild,channel,user,ticket,reason} = params if (channel.isThread()) throw new api.ODSystemError("Unable to pin ticket! Open Ticket doesn't support threads!") @@ -22,66 +23,66 @@ export const registerActions = async () => { ticket.get("opendiscord:busy").value = true //update stats - await opendiscord.stats.get("opendiscord:global").setStat("opendiscord:tickets-pinned",1,"increase") - await opendiscord.stats.get("opendiscord:user").setStat("opendiscord:tickets-pinned",user.id,1,"increase") + await opendiscord.statistics.get("opendiscord:global").setStat("opendiscord:tickets-pinned",1,"increase") + await opendiscord.statistics.get("opendiscord:user").setStat("opendiscord:tickets-pinned",user.id,1,"increase") //move to top of category if (channel.parent){ await channel.setPosition(0,{reason:"Ticket Pinned!"}) } - //rename channel (and give error when crashed) - const pinEmoji = ticket.get("opendiscord:pinned").value ? generalConfig.data.system.pinEmoji : "" - const priorityEmoji = opendiscord.priorities.getFromPriorityLevel(ticket.get("opendiscord:priority").value).channelEmoji ?? "" - - const originalName = channel.name - const newName = pinEmoji+priorityEmoji+utilities.trimEmojis(channel.name) - try{ - await utilities.timedAwait(channel.setName(newName),2500,(err) => { - opendiscord.log("Failed to rename channel on ticket pin","error") - }) - }catch(err){ - await channel.send((await opendiscord.builders.messages.getSafe("opendiscord:error-channel-rename").build("ticket-pin",{guild,channel,user,originalName,newName})).message) - } - - //update ticket message - const ticketMessage = await opendiscord.tickets.getTicketMessage(ticket) - if (ticketMessage){ + //calculate channel name + const channelNameResult = await opendiscord.actions.get("opendiscord:calculate-ticket-name").run("pin-ticket",{guild,user,option:ticket.option,channel,ticket,currentChannelName:channel.name}) + if (channelNameResult && channelNameResult.shouldChangeName && typeof channelNameResult.newChannelName !== "undefined"){ + const originalName = channel.name + const newName = channelNameResult.newChannelName try{ - ticketMessage.edit((await opendiscord.builders.messages.getSafe("opendiscord:ticket-message").build("other",{guild,channel,user,ticket})).message) - }catch(e){ - opendiscord.log("Unable to edit ticket message on ticket pinning!","error",[ - {key:"channel",value:"#"+channel.name}, - {key:"channelid",value:channel.id,hidden:true}, - {key:"message",value:ticketMessage.id}, - {key:"option",value:ticket.option.id.value} + await utilities.timedAwait(channel.setName(newName),2500,(err) => { + opendiscord.log("Failed to rename channel on ticket pin","error") + }) + }catch(err){ + opendiscord.log("Unable to rename channel while pinning ticket! Waiting until ratelimit expires...","warning",[ + {key:"oldName",value:originalName}, + {key:"newName",value:newName} ]) - opendiscord.debugfile.writeErrorMessage(new api.ODError(e,"uncaughtException")) + const sentMsg = await channel.send((await opendiscord.builders.messages.getSafe("opendiscord:error-channel-rename").build("ticket-pin",{guild,channel,user,originalName,newName})).message) + setTimeout(() => {if (sentMsg.deletable) sentMsg.delete()},7000) //autodelete error message } } + //update ticket message (no await) + openticketUtils.updateTicketMessage(guild,channel,user,ticket) + //reply with new message - if (params.sendMessage) await channel.send((await opendiscord.builders.messages.getSafe("opendiscord:pin-message").build(source,{guild,channel,user,ticket,reason})).message) + if (params.sendMessage){ + const sentMsg = await channel.send((await opendiscord.builders.messages.getSafe("opendiscord:pin-message").build(origin,{guild,channel,user,ticket,reason})).message) + if (sentMsg) await interactiveMsgState.setMsgState({channel,message:sentMsg},{ + messageType:"pin-message", + messageOrigin:"other", + messageAuthor:user.id, + messageReason:reason + },false) + } ticket.get("opendiscord:busy").value = false await opendiscord.events.get("afterTicketPinned").emit([ticket,user,channel,reason]) //update channel topic await opendiscord.actions.get("opendiscord:update-ticket-topic").run("ticket-action",{guild,channel,user,ticket,sendMessage:false,newTopic:null}) }), - new api.ODWorker("opendiscord:discord-logs",1,async (instance,params,source,cancel) => { + new api.ODWorker("opendiscord:discord-logs",1,async (instance,params,origin,cancel) => { const {guild,channel,user,ticket,reason} = params //to logs - if (generalConfig.data.system.logs.enabled && generalConfig.data.system.messages.pinning.logs){ + if (generalConfig.data.logs.enabled && generalConfig.data.logs.logMessages.pinning.logs){ const logChannel = opendiscord.posts.get("opendiscord:logs") - if (logChannel) logChannel.send(await opendiscord.builders.messages.getSafe("opendiscord:ticket-action-logs").build(source,{guild,channel,user,ticket,mode:"pin",reason,additionalData:null})) + if (logChannel) logChannel.send(await opendiscord.builders.messages.getSafe("opendiscord:ticket-action-logs").build(origin,{guild,channel,user,ticket,mode:"pin",reason,additionalData:null})) } //to dm const creator = await opendiscord.tickets.getTicketUser(ticket,"creator") - if (creator && generalConfig.data.system.messages.pinning.dm) await opendiscord.client.sendUserDm(creator,await opendiscord.builders.messages.getSafe("opendiscord:ticket-action-dm").build(source,{guild,channel,user,ticket,mode:"pin",reason,additionalData:null})) + if (creator && generalConfig.data.logs.logMessages.pinning.dm) await opendiscord.client.sendUserDm(creator,await opendiscord.builders.messages.getSafe("opendiscord:ticket-action-dm").build(origin,{guild,channel,user,ticket,mode:"pin",reason,additionalData:null})) }), - new api.ODWorker("opendiscord:logs",0,(instance,params,source,cancel) => { + new api.ODWorker("opendiscord:logs",0,(instance,params,origin,cancel) => { const {guild,channel,user,ticket} = params opendiscord.log(user.displayName+" pinned a ticket!","info",[ @@ -90,192 +91,8 @@ export const registerActions = async () => { {key:"channel",value:"#"+channel.name}, {key:"channelid",value:channel.id,hidden:true}, {key:"reason",value:params.reason ?? "/"}, - {key:"method",value:source} + {key:"method",value:origin} ]) }) ]) -} - -export const registerVerifyBars = async () => { - //PIN TICKET TICKET MESSAGE - opendiscord.verifybars.add(new api.ODVerifyBar("opendiscord:pin-ticket-ticket-message",opendiscord.builders.messages.getSafe("opendiscord:verifybar-ticket-message"),!generalConfig.data.system.disableVerifyBars)) - opendiscord.verifybars.get("opendiscord:pin-ticket-ticket-message").success.add([ - new api.ODWorker("opendiscord:permissions",1,async (instance,params,source,cancel) => { - const permissionMode = generalConfig.data.system.permissions.pin - - if (permissionMode == "none"){ - //no permissions - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-no-permissions").build("button",{guild:instance.guild,channel:instance.channel,user:instance.user,permissions:[]})) - return cancel() - }else if (permissionMode == "everyone") return - else if (permissionMode == "admin"){ - if (!opendiscord.permissions.hasPermissions("support",await opendiscord.permissions.getPermissions(instance.user,instance.channel,instance.guild))){ - //no permissions - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-no-permissions").build("button",{guild:instance.guild,channel:instance.channel,user:instance.user,permissions:["support"]})) - return cancel() - }else return - }else{ - if (!instance.guild || !instance.member){ - //error - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error").build("button",{guild:instance.guild,channel:instance.channel,user:instance.user,error:"Permission Error: Not in Server #1",layout:"advanced"})) - return cancel() - } - const role = await opendiscord.client.fetchGuildRole(instance.guild,permissionMode) - if (!role){ - //error - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error").build("button",{guild:instance.guild,channel:instance.channel,user:instance.user,error:"Permission Error: Not in Server #2",layout:"advanced"})) - return cancel() - } - if (!role.members.has(instance.member.id)){ - //no permissions - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-no-permissions").build("button",{guild:instance.guild,channel:instance.channel,user:instance.user,permissions:[]})) - return cancel() - }else return - } - }), - new api.ODWorker("opendiscord:pin-ticket",0,async (instance,params,source,cancel) => { - const {guild,channel,user} = instance - if (!guild){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-not-in-guild").build("button",{channel,user})) - return cancel() - } - const ticket = opendiscord.tickets.get(channel.id) - if (!ticket || channel.isDMBased()){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-ticket-unknown").build("button",{guild,channel,user})) - return cancel() - } - //return when already pinned - if (ticket.get("opendiscord:pinned").value){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error").build("button",{guild,channel,user,error:opendiscord.languages.getTranslation("errors.actionInvalid.pin"),layout:"simple"})) - return cancel() - } - //return when busy - if (ticket.get("opendiscord:busy").value){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-ticket-busy").build("button",{guild,channel,user})) - return cancel() - } - - //start pinning ticket - if (params.data == "reason"){ - //pin with reason - instance.modal(await opendiscord.builders.modals.getSafe("opendiscord:pin-ticket-reason").build("ticket-message",{guild,channel,user,ticket})) - }else{ - //pin without reason - await instance.defer("update",false) - await opendiscord.actions.get("opendiscord:pin-ticket").run("ticket-message",{guild,channel,user,ticket,reason:null,sendMessage:true}) - await instance.update(await opendiscord.builders.messages.getSafe("opendiscord:ticket-message").build("other",{guild,channel,user,ticket})) - } - }) - ]) - opendiscord.verifybars.get("opendiscord:pin-ticket-ticket-message").failure.add([ - new api.ODWorker("opendiscord:back-to-ticket-message",0,async (instance,params,source,cancel) => { - const {guild,channel,user} = instance - if (!guild){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-not-in-guild").build("button",{channel,user})) - return cancel() - } - const ticket = opendiscord.tickets.get(channel.id) - if (!ticket || channel.isDMBased()){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-ticket-unknown").build("button",{guild,channel,user})) - return cancel() - } - - await instance.update(await opendiscord.builders.messages.getSafe("opendiscord:ticket-message").build("other",{guild,channel,user,ticket})) - }) - ]) - - //PIN TICKET UNPIN MESSAGE - opendiscord.verifybars.add(new api.ODVerifyBar("opendiscord:pin-ticket-unpin-message",opendiscord.builders.messages.getSafe("opendiscord:verifybar-unpin-message"),!generalConfig.data.system.disableVerifyBars)) - opendiscord.verifybars.get("opendiscord:pin-ticket-unpin-message").success.add([ - new api.ODWorker("opendiscord:permissions",1,async (instance,params,source,cancel) => { - const permissionMode = generalConfig.data.system.permissions.pin - - if (permissionMode == "none"){ - //no permissions - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-no-permissions").build("button",{guild:instance.guild,channel:instance.channel,user:instance.user,permissions:[]})) - return cancel() - }else if (permissionMode == "everyone") return - else if (permissionMode == "admin"){ - if (!opendiscord.permissions.hasPermissions("support",await opendiscord.permissions.getPermissions(instance.user,instance.channel,instance.guild))){ - //no permissions - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-no-permissions").build("button",{guild:instance.guild,channel:instance.channel,user:instance.user,permissions:["support"]})) - return cancel() - }else return - }else{ - if (!instance.guild || !instance.member){ - //error - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error").build("button",{guild:instance.guild,channel:instance.channel,user:instance.user,error:"Permission Error: Not in Server #1",layout:"advanced"})) - return cancel() - } - const role = await opendiscord.client.fetchGuildRole(instance.guild,permissionMode) - if (!role){ - //error - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error").build("button",{guild:instance.guild,channel:instance.channel,user:instance.user,error:"Permission Error: Not in Server #2",layout:"advanced"})) - return cancel() - } - if (!role.members.has(instance.member.id)){ - //no permissions - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-no-permissions").build("button",{guild:instance.guild,channel:instance.channel,user:instance.user,permissions:[]})) - return cancel() - }else return - } - }), - new api.ODWorker("opendiscord:pin-ticket",0,async (instance,params,source,cancel) => { - const {guild,channel,user} = instance - if (!guild){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-not-in-guild").build("button",{channel,user})) - return cancel() - } - const ticket = opendiscord.tickets.get(channel.id) - if (!ticket || channel.isDMBased()){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-ticket-unknown").build("button",{guild,channel,user})) - return cancel() - } - //return when already pinned - if (ticket.get("opendiscord:pinned").value){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error").build("button",{guild,channel,user,error:opendiscord.languages.getTranslation("errors.actionInvalid.pin"),layout:"simple"})) - return cancel() - } - //return when busy - if (ticket.get("opendiscord:busy").value){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-ticket-busy").build("button",{guild,channel,user})) - return cancel() - } - - //start pinning ticket - if (params.data == "reason"){ - //pin with reason - instance.modal(await opendiscord.builders.modals.getSafe("opendiscord:pin-ticket-reason").build("unpin-message",{guild,channel,user,ticket})) - }else{ - //pin without reason - await instance.defer("update",false) - await opendiscord.actions.get("opendiscord:pin-ticket").run("unpin-message",{guild,channel,user,ticket,reason:null,sendMessage:false}) - await instance.update(await opendiscord.builders.messages.getSafe("opendiscord:pin-message").build("unpin-message",{guild,channel,user,ticket,reason:null})) - } - }) - ]) - opendiscord.verifybars.get("opendiscord:pin-ticket-unpin-message").failure.add([ - new api.ODWorker("opendiscord:back-to-unpin-message",0,async (instance,params,source,cancel) => { - const {guild,channel,user} = instance - const {verifybarMessage} = params - if (!guild){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-not-in-guild").build("button",{channel,user})) - return cancel() - } - const ticket = opendiscord.tickets.get(channel.id) - if (!ticket || channel.isDMBased()){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-ticket-unknown").build("button",{guild,channel,user})) - return cancel() - } - - const rawReason = (verifybarMessage && verifybarMessage.embeds[0] && verifybarMessage.embeds[0].fields[0]) ? verifybarMessage.embeds[0].fields[0].value : null - const reason = (rawReason == null) ? null : rawReason.substring(3,rawReason.length-3) - - await instance.update(await opendiscord.builders.messages.getSafe("opendiscord:unpin-message").build("other",{guild,channel,user,ticket,reason})) - }) - ]) - opendiscord.actions.get("opendiscord:pin-ticket").workers.backupWorker = new api.ODWorker("opendiscord:cancel-busy",0,(instance,params) => { - //set busy to false in case of crash or cancel - params.ticket.get("opendiscord:busy").value = false - }) } \ No newline at end of file diff --git a/src/actions/reactionRole.ts b/src/actions/reactionRole.ts index 84760e1..8a0a116 100644 --- a/src/actions/reactionRole.ts +++ b/src/actions/reactionRole.ts @@ -1,15 +1,15 @@ /////////////////////////////////////// //REACTION ROLE SYSTEM /////////////////////////////////////// -import {opendiscord, api, utilities} from "../index" +import {opendiscord, api, utilities, openticketUtils} from "../index.js" import * as discord from "discord.js" const generalConfig = opendiscord.configs.get("opendiscord:general") -export const registerActions = async () => { +export async function registerActions(){ opendiscord.actions.add(new api.ODAction("opendiscord:reaction-role")) opendiscord.actions.get("opendiscord:reaction-role").workers.add([ - new api.ODWorker("opendiscord:reaction-role",2,async (instance,params,source,cancel) => { + new api.ODWorker("opendiscord:reaction-role",2,async (instance,params,origin,cancel) => { const {guild,user,option,overwriteMode} = params const role = opendiscord.roles.get(option.id) if (!role) throw new api.ODSystemError("ODAction(ot:reaction-role) => Unknown reaction role (ODRole)") @@ -83,25 +83,25 @@ export const registerActions = async () => { instance.result = result await opendiscord.events.get("afterRolesUpdated").emit([user,role]) }), - new api.ODWorker("opendiscord:discord-logs",1,async (instance,params,source,cancel) => { + new api.ODWorker("opendiscord:discord-logs",1,async (instance,params,origin,cancel) => { const {guild,user,option,overwriteMode} = params if (!instance.role || !instance.result) return //to logs - if (generalConfig.data.system.logs.enabled && (generalConfig.data.system.messages.reactionRole.logs)){ + if (generalConfig.data.logs.enabled && (generalConfig.data.logs.logMessages.reactionRole.logs)){ const logChannel = opendiscord.posts.get("opendiscord:logs") - if (logChannel) logChannel.send(await opendiscord.builders.messages.getSafe("opendiscord:reaction-role-logs").build(source,{guild,user,role:instance.role,result:instance.result})) + if (logChannel) logChannel.send(await opendiscord.builders.messages.getSafe("opendiscord:reaction-role-logs").build(origin,{guild,user,role:instance.role,result:instance.result})) } //to dm - if (generalConfig.data.system.messages.reactionRole.dm) await opendiscord.client.sendUserDm(user,await opendiscord.builders.messages.getSafe("opendiscord:reaction-role-dm").build(source,{guild,user,role:instance.role,result:instance.result})) + if (generalConfig.data.logs.logMessages.reactionRole.dm) await opendiscord.client.sendUserDm(user,await opendiscord.builders.messages.getSafe("opendiscord:reaction-role-dm").build(origin,{guild,user,role:instance.role,result:instance.result})) }), - new api.ODWorker("opendiscord:logs",0,(instance,params,source,cancel) => { + new api.ODWorker("opendiscord:logs",0,(instance,params,origin,cancel) => { const {guild,user,option} = params opendiscord.log(user.displayName+" updated his roles!","info",[ {key:"user",value:user.username}, {key:"userid",value:user.id,hidden:true}, - {key:"method",value:source}, + {key:"method",value:origin}, {key:"option",value:option.id.value} ]) }) diff --git a/src/actions/removeTicketUser.ts b/src/actions/removeTicketUser.ts index 9a8ef9f..93737fc 100644 --- a/src/actions/removeTicketUser.ts +++ b/src/actions/removeTicketUser.ts @@ -1,15 +1,15 @@ /////////////////////////////////////// //TICKET REMOVE USER SYSTEM /////////////////////////////////////// -import {opendiscord, api, utilities} from "../index" +import {opendiscord, api, utilities, openticketUtils} from "../index.js" import * as discord from "discord.js" const generalConfig = opendiscord.configs.get("opendiscord:general") -export const registerActions = async () => { +export async function registerActions(){ opendiscord.actions.add(new api.ODAction("opendiscord:remove-ticket-user")) opendiscord.actions.get("opendiscord:remove-ticket-user").workers.add([ - new api.ODWorker("opendiscord:remove-ticket-user",2,async (instance,params,source,cancel) => { + new api.ODWorker("opendiscord:remove-ticket-user",2,async (instance,params,origin,cancel) => { const {guild,channel,user,ticket,reason,data} = params if (channel.isThread()) throw new api.ODSystemError("Unable to remove user from ticket! Open Ticket doesn't support threads!") @@ -29,43 +29,31 @@ export const registerActions = async () => { opendiscord.log("Failed to remove channel permission overwrites on remove-ticket-user","error") } - //update ticket message - const ticketMessage = await opendiscord.tickets.getTicketMessage(ticket) - if (ticketMessage){ - try{ - ticketMessage.edit((await opendiscord.builders.messages.getSafe("opendiscord:ticket-message").build("other",{guild,channel,user,ticket})).message) - }catch(e){ - opendiscord.log("Unable to edit ticket message on ticket user removal!","error",[ - {key:"channel",value:channel.id}, - {key:"message",value:ticketMessage.id}, - {key:"option",value:ticket.option.id.value} - ]) - opendiscord.debugfile.writeErrorMessage(new api.ODError(e,"uncaughtException")) - } - } + //update ticket message (no await) + openticketUtils.updateTicketMessage(guild,channel,user,ticket) //reply with new message - if (params.sendMessage) await channel.send((await opendiscord.builders.messages.getSafe("opendiscord:remove-message").build(source,{guild,channel,user,ticket,reason,data})).message) + if (params.sendMessage) await channel.send((await opendiscord.builders.messages.getSafe("opendiscord:remove-message").build(origin,{guild,channel,user,ticket,reason,data})).message) ticket.get("opendiscord:busy").value = false await opendiscord.events.get("afterTicketUserRemoved").emit([ticket,user,data,channel,reason]) //update channel topic await opendiscord.actions.get("opendiscord:update-ticket-topic").run("ticket-action",{guild,channel,user,ticket,sendMessage:false,newTopic:null}) }), - new api.ODWorker("opendiscord:discord-logs",1,async (instance,params,source,cancel) => { + new api.ODWorker("opendiscord:discord-logs",1,async (instance,params,origin,cancel) => { const {guild,channel,user,ticket,reason,data} = params //to logs - if (generalConfig.data.system.logs.enabled && generalConfig.data.system.messages.removing.logs){ + if (generalConfig.data.logs.enabled && generalConfig.data.logs.logMessages.removing.logs){ const logChannel = opendiscord.posts.get("opendiscord:logs") - if (logChannel) logChannel.send(await opendiscord.builders.messages.getSafe("opendiscord:ticket-action-logs").build(source,{guild,channel,user,ticket,mode:"remove",reason,additionalData:data})) + if (logChannel) logChannel.send(await opendiscord.builders.messages.getSafe("opendiscord:ticket-action-logs").build(origin,{guild,channel,user,ticket,mode:"remove",reason,additionalData:data})) } //to dm const creator = await opendiscord.tickets.getTicketUser(ticket,"creator") - if (creator && generalConfig.data.system.messages.removing.dm) await opendiscord.client.sendUserDm(creator,await opendiscord.builders.messages.getSafe("opendiscord:ticket-action-dm").build(source,{guild,channel,user,ticket,mode:"remove",reason,additionalData:data})) + if (creator && generalConfig.data.logs.logMessages.removing.dm) await opendiscord.client.sendUserDm(creator,await opendiscord.builders.messages.getSafe("opendiscord:ticket-action-dm").build(origin,{guild,channel,user,ticket,mode:"remove",reason,additionalData:data})) }), - new api.ODWorker("opendiscord:logs",0,(instance,params,source,cancel) => { + new api.ODWorker("opendiscord:logs",0,(instance,params,origin,cancel) => { const {guild,channel,user,ticket,data} = params opendiscord.log(user.displayName+" removed "+data.displayName+" from a ticket!","info",[ @@ -74,7 +62,7 @@ export const registerActions = async () => { {key:"channel",value:"#"+channel.name}, {key:"channelid",value:channel.id,hidden:true}, {key:"reason",value:params.reason ?? "/"}, - {key:"method",value:source} + {key:"method",value:origin} ]) }) ]) diff --git a/src/actions/renameTicket.ts b/src/actions/renameTicket.ts index a110f16..7ffd071 100644 --- a/src/actions/renameTicket.ts +++ b/src/actions/renameTicket.ts @@ -1,72 +1,67 @@ /////////////////////////////////////// //TICKET RENAMING SYSTEM /////////////////////////////////////// -import {opendiscord, api, utilities} from "../index" +import {opendiscord, api, utilities, openticketUtils} from "../index.js" import * as discord from "discord.js" const generalConfig = opendiscord.configs.get("opendiscord:general") -export const registerActions = async () => { +export async function registerActions(){ opendiscord.actions.add(new api.ODAction("opendiscord:rename-ticket")) opendiscord.actions.get("opendiscord:rename-ticket").workers.add([ - new api.ODWorker("opendiscord:rename-ticket",2,async (instance,params,source,cancel) => { + new api.ODWorker("opendiscord:rename-ticket",2,async (instance,params,origin,cancel) => { const {guild,channel,user,ticket,reason,data} = params if (channel.isThread()) throw new api.ODSystemError("Unable to rename ticket! Open Ticket doesn't support threads!") await opendiscord.events.get("onTicketRename").emit([ticket,user,channel,reason]) - //rename channel (and give error when crashed) - const pinEmoji = ticket.get("opendiscord:pinned").value ? generalConfig.data.system.pinEmoji : "" - const priorityEmoji = opendiscord.priorities.getFromPriorityLevel(ticket.get("opendiscord:priority").value).channelEmoji ?? "" + //update ticket + ticket.get("opendiscord:channel-renamed").value = data - const originalName = channel.name - const newName = pinEmoji+priorityEmoji+utilities.trimEmojis(data) - try{ - await utilities.timedAwait(channel.setName(newName),2500,(err) => { - opendiscord.log("Failed to rename channel on ticket rename","error") - }) - }catch(err){ - await channel.send((await opendiscord.builders.messages.getSafe("opendiscord:error-channel-rename").build("ticket-rename",{guild,channel,user,originalName,newName:data})).message) - } - - //update ticket message - const ticketMessage = await opendiscord.tickets.getTicketMessage(ticket) - if (ticketMessage){ + //calculate channel name + const channelNameResult = await opendiscord.actions.get("opendiscord:calculate-ticket-name").run("rename-ticket",{guild,user,option:ticket.option,channel,ticket,currentChannelName:channel.name}) + if (channelNameResult && channelNameResult.shouldChangeName && typeof channelNameResult.newChannelName !== "undefined"){ + const originalName = channel.name + const newName = channelNameResult.newChannelName try{ - ticketMessage.edit((await opendiscord.builders.messages.getSafe("opendiscord:ticket-message").build("other",{guild,channel,user,ticket})).message) - }catch(e){ - opendiscord.log("Unable to edit ticket message on ticket renaming!","error",[ - {key:"channel",value:"#"+channel.name}, - {key:"channelid",value:channel.id,hidden:true}, - {key:"messageid",value:ticketMessage.id}, - {key:"option",value:ticket.option.id.value} + await utilities.timedAwait(channel.setName(newName),2500,(err) => { + opendiscord.log("Failed to rename channel on ticket rename","error") + }) + }catch(err){ + opendiscord.log("Unable to rename channel while renaming ticket! Waiting until ratelimit expires...","warning",[ + {key:"oldName",value:originalName}, + {key:"newName",value:newName} ]) - opendiscord.debugfile.writeErrorMessage(new api.ODError(e,"uncaughtException")) + const sentMsg = await channel.send((await opendiscord.builders.messages.getSafe("opendiscord:error-channel-rename").build("ticket-rename",{guild,channel,user,originalName,newName})).message) + setTimeout(() => {if (sentMsg.deletable) sentMsg.delete()},7000) //autodelete error message } } + //update ticket message (no await) + openticketUtils.updateTicketMessage(guild,channel,user,ticket) + //reply with new message - if (params.sendMessage) await channel.send((await opendiscord.builders.messages.getSafe("opendiscord:rename-message").build(source,{guild,channel,user,ticket,reason,data})).message) + if (params.sendMessage) await channel.send((await opendiscord.builders.messages.getSafe("opendiscord:rename-message").build(origin,{guild,channel,user,ticket,reason,data})).message) ticket.get("opendiscord:busy").value = false await opendiscord.events.get("afterTicketRenamed").emit([ticket,user,channel,reason]) //update channel topic await opendiscord.actions.get("opendiscord:update-ticket-topic").run("ticket-action",{guild,channel,user,ticket,sendMessage:false,newTopic:null}) }), - new api.ODWorker("opendiscord:discord-logs",1,async (instance,params,source,cancel) => { + new api.ODWorker("opendiscord:discord-logs",1,async (instance,params,origin,cancel) => { const {guild,channel,user,ticket,reason,data} = params //to logs - if (generalConfig.data.system.logs.enabled && generalConfig.data.system.messages.renaming.logs){ + if (generalConfig.data.logs.enabled && generalConfig.data.logs.logMessages.renaming.logs){ const logChannel = opendiscord.posts.get("opendiscord:logs") - if (logChannel) logChannel.send(await opendiscord.builders.messages.getSafe("opendiscord:ticket-action-logs").build(source,{guild,channel,user,ticket,mode:"rename",reason,additionalData:data})) + if (logChannel) logChannel.send(await opendiscord.builders.messages.getSafe("opendiscord:ticket-action-logs").build(origin,{guild,channel,user,ticket,mode:"rename",reason,additionalData:data})) } //to dm const creator = await opendiscord.tickets.getTicketUser(ticket,"creator") - if (creator && generalConfig.data.system.messages.renaming.dm) await opendiscord.client.sendUserDm(creator,await opendiscord.builders.messages.getSafe("opendiscord:ticket-action-dm").build(source,{guild,channel,user,ticket,mode:"rename",reason,additionalData:data})) + if (creator && generalConfig.data.logs.logMessages.renaming.dm) await opendiscord.client.sendUserDm(creator,await opendiscord.builders.messages.getSafe("opendiscord:ticket-action-dm").build(origin,{guild,channel,user,ticket,mode:"rename",reason,additionalData:data})) }), - new api.ODWorker("opendiscord:logs",0,(instance,params,source,cancel) => { + new api.ODWorker("opendiscord:logs",0,(instance,params,origin,cancel) => { const {guild,channel,user,ticket} = params opendiscord.log(user.displayName+" renamed a ticket!","info",[ @@ -75,7 +70,7 @@ export const registerActions = async () => { {key:"channel",value:"#"+channel.name}, {key:"channelid",value:channel.id,hidden:true}, {key:"reason",value:params.reason ?? "/"}, - {key:"method",value:source} + {key:"method",value:origin} ]) }) ]) diff --git a/src/actions/reopenTicket.ts b/src/actions/reopenTicket.ts index 5112f66..76c06b7 100644 --- a/src/actions/reopenTicket.ts +++ b/src/actions/reopenTicket.ts @@ -1,15 +1,16 @@ /////////////////////////////////////// //TICKET REOPENING SYSTEM /////////////////////////////////////// -import {opendiscord, api, utilities} from "../index" +import {opendiscord, api, utilities, openticketUtils} from "../index.js" import * as discord from "discord.js" const generalConfig = opendiscord.configs.get("opendiscord:general") +const interactiveMsgState = opendiscord.states.get("opendiscord:interactive-message") -export const registerActions = async () => { +export async function registerActions(){ opendiscord.actions.add(new api.ODAction("opendiscord:reopen-ticket")) opendiscord.actions.get("opendiscord:reopen-ticket").workers.add([ - new api.ODWorker("opendiscord:reopen-ticket",2,async (instance,params,source,cancel) => { + new api.ODWorker("opendiscord:reopen-ticket",2,async (instance,params,origin,cancel) => { const {guild,channel,user,ticket,reason} = params if (channel.isThread()) throw new api.ODSystemError("Unable to reopen ticket! Open Ticket doesn't support threads!") @@ -28,65 +29,56 @@ export const registerActions = async () => { ticket.get("opendiscord:open").value = true ticket.get("opendiscord:busy").value = true - if (generalConfig.data.system.disableAutocloseAfterReopen){ + if (generalConfig.data.ticketSystem.disableAutocloseAfterReopen){ //disable autoclose after reopen ticket.get("opendiscord:autoclose-enabled").value = false ticket.get("opendiscord:autoclose-hours").value = 0 } //update stats - await opendiscord.stats.get("opendiscord:global").setStat("opendiscord:tickets-reopened",1,"increase") - await opendiscord.stats.get("opendiscord:user").setStat("opendiscord:tickets-reopened",user.id,1,"increase") + await opendiscord.statistics.get("opendiscord:global").setStat("opendiscord:tickets-reopened",1,"increase") + await opendiscord.statistics.get("opendiscord:user").setStat("opendiscord:tickets-reopened",user.id,1,"increase") - //update category + //calculate & update category if (typeof params.allowCategoryChange == "boolean" ? params.allowCategoryChange : true){ - const channelCategory = ticket.option.get("opendiscord:channel-category").value - const channelBackupCategory = ticket.option.get("opendiscord:channel-category-backup").value - if (channelCategory !== ""){ - //category enabled - try { - const normalCategory = await opendiscord.client.fetchGuildCategoryChannel(guild,channelCategory) - if (!normalCategory){ - //default category was not found - opendiscord.log("Ticket Reopening Error: Unable to find category! #1","error",[ - {key:"categoryid",value:channelCategory}, - {key:"backup",value:"false"} - ]) - }else{ - //default category was found - if (normalCategory.children.cache.size >= 49 && channelBackupCategory != ""){ - //use backup category - const backupCategory = await opendiscord.client.fetchGuildCategoryChannel(guild,channelBackupCategory) - if (!backupCategory){ - //default category was not found - opendiscord.log("Ticket Reopening Error: Unable to find category! #2","error",[ - {key:"categoryid",value:channelBackupCategory}, - {key:"backup",value:"true"} - ]) - }else{ - //use backup category - channel.setParent(backupCategory,{lockPermissions:false}) - ticket.get("opendiscord:category-mode").value = "backup" - ticket.get("opendiscord:category").value = backupCategory.id - } - }else{ - //use default category - channel.setParent(normalCategory,{lockPermissions:false}) - ticket.get("opendiscord:category-mode").value = "normal" - ticket.get("opendiscord:category").value = normalCategory.id - } - } - }catch(e){ - opendiscord.log("Unable to move ticket to 'reopened category'!","error",[ + const categoryResult = await opendiscord.actions.get("opendiscord:calculate-ticket-category").run("reopen-ticket",{guild,user,option:ticket.option,channel,ticket,currentCategoryId:channel.parentId}) + if (categoryResult && categoryResult.shouldChangeCategory && typeof categoryResult.newCategoryId !== "undefined" && typeof categoryResult.newCategoryMode !== "undefined" && typeof categoryResult.newCategory !== "undefined"){ + const originalCategoryName = channel.parent?.name ?? "" + const newCategoryName = categoryResult.newCategory?.name ?? "" + try{ + await utilities.timedAwait(channel.setParent(categoryResult.newCategoryId,{lockPermissions:false}),3000,(err) => { + process.emit("uncaughtException",new Error("Error: Unable to change channel parent: "+err)) + }) + ticket.get("opendiscord:category-mode").value = categoryResult.newCategoryMode + ticket.get("opendiscord:category").value = categoryResult.newCategoryId + }catch(err){ + const sentMsg = await channel.send((await opendiscord.builders.messages.getSafe("opendiscord:error-channel-category").build("ticket-reopen",{guild,channel,user,originalCategory:originalCategoryName,newCategory:newCategoryName})).message) + setTimeout(() => {if (sentMsg.deletable) sentMsg.delete()},7000) //autodelete error message + opendiscord.log("Unable to move ticket to reopened category.","error",[ {key:"channel",value:"#"+channel.name}, {key:"channelid",value:channel.id,hidden:true}, + {key:"categoryid",value:categoryResult.newCategoryId ?? "/"} ]) - opendiscord.debugfile.writeErrorMessage(new api.ODError(e,"uncaughtException")) } - }else{ - channel.setParent(null,{lockPermissions:false}) - ticket.get("opendiscord:category-mode").value = null - ticket.get("opendiscord:category").value = null + } + } + + //calculate channel name + const channelNameResult = await opendiscord.actions.get("opendiscord:calculate-ticket-name").run("reopen-ticket",{guild,user,option:ticket.option,channel,ticket,currentChannelName:channel.name}) + if (channelNameResult && channelNameResult.shouldChangeName && typeof channelNameResult.newChannelName !== "undefined"){ + const originalName = channel.name + const newName = channelNameResult.newChannelName + try{ + await utilities.timedAwait(channel.setName(newName),2500,(err) => { + opendiscord.log("Failed to rename channel on ticket reopen","error") + }) + }catch(err){ + opendiscord.log("Unable to rename channel while reopening ticket! Waiting until ratelimit expires...","warning",[ + {key:"oldName",value:originalName}, + {key:"newName",value:newName} + ]) + const sentMsg = await channel.send((await opendiscord.builders.messages.getSafe("opendiscord:error-channel-rename").build("ticket-reopen",{guild,channel,user,originalName,newName})).message) + setTimeout(() => {if (sentMsg.deletable) sentMsg.delete()},7000) //autodelete error message } } @@ -141,43 +133,38 @@ export const registerActions = async () => { }) channel.permissionOverwrites.set(permissions) - //update ticket message - const ticketMessage = await opendiscord.tickets.getTicketMessage(ticket) - if (ticketMessage){ - try{ - ticketMessage.edit((await opendiscord.builders.messages.getSafe("opendiscord:ticket-message").build("other",{guild,channel,user,ticket})).message) - }catch(e){ - opendiscord.log("Unable to edit ticket message on ticket reopening!","error",[ - {key:"channel",value:"#"+channel.name}, - {key:"channelid",value:channel.id,hidden:true}, - {key:"messageid",value:ticketMessage.id}, - {key:"option",value:ticket.option.id.value} - ]) - opendiscord.debugfile.writeErrorMessage(new api.ODError(e,"uncaughtException")) - } - } + //update ticket message (no await) + openticketUtils.updateTicketMessage(guild,channel,user,ticket) - if (params.sendMessage) await channel.send((await opendiscord.builders.messages.getSafe("opendiscord:reopen-message").build(source,{guild,channel,user,ticket,reason})).message) + if (params.sendMessage){ + const sentMsg = await channel.send((await opendiscord.builders.messages.getSafe("opendiscord:reopen-message").build(origin,{guild,channel,user,ticket,reason})).message) + if (sentMsg) await interactiveMsgState.setMsgState({channel,message:sentMsg},{ + messageType:"reopen-message", + messageOrigin:"other", + messageAuthor:user.id, + messageReason:reason + },false) + } ticket.get("opendiscord:busy").value = false await opendiscord.events.get("afterTicketReopened").emit([ticket,user,channel,reason]) //update channel topic await opendiscord.actions.get("opendiscord:update-ticket-topic").run("ticket-action",{guild,channel,user,ticket,sendMessage:false,newTopic:null}) }), - new api.ODWorker("opendiscord:discord-logs",1,async (instance,params,source,cancel) => { + new api.ODWorker("opendiscord:discord-logs",1,async (instance,params,origin,cancel) => { const {guild,channel,user,ticket,reason} = params //to logs - if (generalConfig.data.system.logs.enabled && generalConfig.data.system.messages.reopening.logs){ + if (generalConfig.data.logs.enabled && generalConfig.data.logs.logMessages.reopening.logs){ const logChannel = opendiscord.posts.get("opendiscord:logs") - if (logChannel) logChannel.send(await opendiscord.builders.messages.getSafe("opendiscord:ticket-action-logs").build(source,{guild,channel,user,ticket,mode:"reopen",reason,additionalData:null})) + if (logChannel) logChannel.send(await opendiscord.builders.messages.getSafe("opendiscord:ticket-action-logs").build(origin,{guild,channel,user,ticket,mode:"reopen",reason,additionalData:null})) } //to dm const creator = await opendiscord.tickets.getTicketUser(ticket,"creator") - if (creator && generalConfig.data.system.messages.reopening.dm) await opendiscord.client.sendUserDm(creator,await opendiscord.builders.messages.getSafe("opendiscord:ticket-action-dm").build(source,{guild,channel,user,ticket,mode:"reopen",reason,additionalData:null})) + if (creator && generalConfig.data.logs.logMessages.reopening.dm) await opendiscord.client.sendUserDm(creator,await opendiscord.builders.messages.getSafe("opendiscord:ticket-action-dm").build(origin,{guild,channel,user,ticket,mode:"reopen",reason,additionalData:null})) }), - new api.ODWorker("opendiscord:logs",0,(instance,params,source,cancel) => { + new api.ODWorker("opendiscord:logs",0,(instance,params,origin,cancel) => { const {guild,channel,user,ticket} = params opendiscord.log(user.displayName+" reopened a ticket!","info",[ @@ -186,280 +173,8 @@ export const registerActions = async () => { {key:"channel",value:"#"+channel.name}, {key:"channelid",value:channel.id,hidden:true}, {key:"reason",value:params.reason ?? "/"}, - {key:"method",value:source} + {key:"method",value:origin} ]) }) ]) -} - -export const registerVerifyBars = async () => { - //REOPEN TICKET TICKET MESSAGE - opendiscord.verifybars.add(new api.ODVerifyBar("opendiscord:reopen-ticket-ticket-message",opendiscord.builders.messages.getSafe("opendiscord:verifybar-ticket-message"),!generalConfig.data.system.disableVerifyBars)) - opendiscord.verifybars.get("opendiscord:reopen-ticket-ticket-message").success.add([ - new api.ODWorker("opendiscord:permissions",1,async (instance,params,source,cancel) => { - const permissionMode = generalConfig.data.system.permissions.reopen - - if (permissionMode == "none"){ - //no permissions - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-no-permissions").build("button",{guild:instance.guild,channel:instance.channel,user:instance.user,permissions:[]})) - return cancel() - }else if (permissionMode == "everyone") return - else if (permissionMode == "admin"){ - if (!opendiscord.permissions.hasPermissions("support",await opendiscord.permissions.getPermissions(instance.user,instance.channel,instance.guild))){ - //no permissions - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-no-permissions").build("button",{guild:instance.guild,channel:instance.channel,user:instance.user,permissions:["support"]})) - return cancel() - }else return - }else{ - if (!instance.guild || !instance.member){ - //error - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error").build("button",{guild:instance.guild,channel:instance.channel,user:instance.user,error:"Permission Error: Not in Server #1",layout:"advanced"})) - return cancel() - } - const role = await opendiscord.client.fetchGuildRole(instance.guild,permissionMode) - if (!role){ - //error - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error").build("button",{guild:instance.guild,channel:instance.channel,user:instance.user,error:"Permission Error: Not in Server #2",layout:"advanced"})) - return cancel() - } - if (!role.members.has(instance.member.id)){ - //no permissions - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-no-permissions").build("button",{guild:instance.guild,channel:instance.channel,user:instance.user,permissions:[]})) - return cancel() - }else return - } - }), - new api.ODWorker("opendiscord:reopen-ticket",0,async (instance,params,source,cancel) => { - const {guild,channel,user} = instance - if (!guild){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-not-in-guild").build("button",{channel,user})) - return cancel() - } - const ticket = opendiscord.tickets.get(channel.id) - if (!ticket || channel.isDMBased()){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-ticket-unknown").build("button",{guild,channel,user})) - return cancel() - } - //return when not closed - if (!ticket.get("opendiscord:closed").value){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error").build("button",{guild,channel,user,error:opendiscord.languages.getTranslation("errors.actionInvalid.reopen"),layout:"simple"})) - return cancel() - } - //return when busy - if (ticket.get("opendiscord:busy").value){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-ticket-busy").build("button",{guild,channel,user})) - return cancel() - } - - //start reopening ticket - if (params.data == "reason"){ - //reopen with reason - instance.modal(await opendiscord.builders.modals.getSafe("opendiscord:reopen-ticket-reason").build("ticket-message",{guild,channel,user,ticket})) - }else{ - //reopen without reason - await instance.defer("update",false) - await opendiscord.actions.get("opendiscord:reopen-ticket").run("ticket-message",{guild,channel,user,ticket,reason:null,sendMessage:true}) - await instance.update(await opendiscord.builders.messages.getSafe("opendiscord:ticket-message").build("other",{guild,channel,user,ticket})) - } - }) - ]) - opendiscord.verifybars.get("opendiscord:reopen-ticket-ticket-message").failure.add([ - new api.ODWorker("opendiscord:back-to-ticket-message",0,async (instance,params,source,cancel) => { - const {guild,channel,user} = instance - if (!guild){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-not-in-guild").build("button",{channel,user})) - return cancel() - } - const ticket = opendiscord.tickets.get(channel.id) - if (!ticket || channel.isDMBased()){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-ticket-unknown").build("button",{guild,channel,user})) - return cancel() - } - - await instance.update(await opendiscord.builders.messages.getSafe("opendiscord:ticket-message").build("other",{guild,channel,user,ticket})) - }) - ]) - - //REOPEN TICKET CLOSE MESSAGE - opendiscord.verifybars.add(new api.ODVerifyBar("opendiscord:reopen-ticket-close-message",opendiscord.builders.messages.getSafe("opendiscord:verifybar-close-message"),!generalConfig.data.system.disableVerifyBars)) - opendiscord.verifybars.get("opendiscord:reopen-ticket-close-message").success.add([ - new api.ODWorker("opendiscord:permissions",1,async (instance,params,source,cancel) => { - const permissionMode = generalConfig.data.system.permissions.reopen - - if (permissionMode == "none"){ - //no permissions - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-no-permissions").build("button",{guild:instance.guild,channel:instance.channel,user:instance.user,permissions:[]})) - return cancel() - }else if (permissionMode == "everyone") return - else if (permissionMode == "admin"){ - if (!opendiscord.permissions.hasPermissions("support",await opendiscord.permissions.getPermissions(instance.user,instance.channel,instance.guild))){ - //no permissions - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-no-permissions").build("button",{guild:instance.guild,channel:instance.channel,user:instance.user,permissions:["support"]})) - return cancel() - }else return - }else{ - if (!instance.guild || !instance.member){ - //error - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error").build("button",{guild:instance.guild,channel:instance.channel,user:instance.user,error:"Permission Error: Not in Server #1",layout:"advanced"})) - return cancel() - } - const role = await opendiscord.client.fetchGuildRole(instance.guild,permissionMode) - if (!role){ - //error - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error").build("button",{guild:instance.guild,channel:instance.channel,user:instance.user,error:"Permission Error: Not in Server #2",layout:"advanced"})) - return cancel() - } - if (!role.members.has(instance.member.id)){ - //no permissions - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-no-permissions").build("button",{guild:instance.guild,channel:instance.channel,user:instance.user,permissions:[]})) - return cancel() - }else return - } - }), - new api.ODWorker("opendiscord:reopen-ticket",0,async (instance,params,source,cancel) => { - const {guild,channel,user} = instance - if (!guild){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-not-in-guild").build("button",{channel,user})) - return cancel() - } - const ticket = opendiscord.tickets.get(channel.id) - if (!ticket || channel.isDMBased()){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-ticket-unknown").build("button",{guild,channel,user})) - return cancel() - } - //return when not closed - if (!ticket.get("opendiscord:closed").value){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error").build("button",{guild,channel,user,error:opendiscord.languages.getTranslation("errors.actionInvalid.reopen"),layout:"simple"})) - return cancel() - } - //return when busy - if (ticket.get("opendiscord:busy").value){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-ticket-busy").build("button",{guild,channel,user})) - return cancel() - } - - //start reopening ticket - if (params.data == "reason"){ - //reopen with reason - instance.modal(await opendiscord.builders.modals.getSafe("opendiscord:reopen-ticket-reason").build("close-message",{guild,channel,user,ticket})) - }else{ - //reopen without reason - await instance.defer("update",false) - await opendiscord.actions.get("opendiscord:reopen-ticket").run("close-message",{guild,channel,user,ticket,reason:null,sendMessage:false}) - await instance.update(await opendiscord.builders.messages.getSafe("opendiscord:reopen-message").build("close-message",{guild,channel,user,ticket,reason:null})) - } - }) - ]) - opendiscord.verifybars.get("opendiscord:reopen-ticket-close-message").failure.add([ - new api.ODWorker("opendiscord:back-to-close-message",0,async (instance,params,source,cancel) => { - const {guild,channel,user} = instance - const {verifybarMessage} = params - if (!guild){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-not-in-guild").build("button",{channel,user})) - return cancel() - } - const ticket = opendiscord.tickets.get(channel.id) - if (!ticket || channel.isDMBased()){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-ticket-unknown").build("button",{guild,channel,user})) - return cancel() - } - - const rawReason = (verifybarMessage && verifybarMessage.embeds[0] && verifybarMessage.embeds[0].fields[0]) ? verifybarMessage.embeds[0].fields[0].value : null - const reason = (rawReason == null) ? null : rawReason.substring(3,rawReason.length-3) - - await instance.update(await opendiscord.builders.messages.getSafe("opendiscord:close-message").build("other",{guild,channel,user,ticket,reason})) - }) - ]) - - //REOPEN TICKET AUTOCLOSE MESSAGE - opendiscord.verifybars.add(new api.ODVerifyBar("opendiscord:reopen-ticket-autoclose-message",opendiscord.builders.messages.getSafe("opendiscord:verifybar-autoclose-message"),!generalConfig.data.system.disableVerifyBars)) - opendiscord.verifybars.get("opendiscord:reopen-ticket-autoclose-message").success.add([ - new api.ODWorker("opendiscord:permissions",1,async (instance,params,source,cancel) => { - const permissionMode = generalConfig.data.system.permissions.reopen - - if (permissionMode == "none"){ - //no permissions - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-no-permissions").build("button",{guild:instance.guild,channel:instance.channel,user:instance.user,permissions:[]})) - return cancel() - }else if (permissionMode == "everyone") return - else if (permissionMode == "admin"){ - if (!opendiscord.permissions.hasPermissions("support",await opendiscord.permissions.getPermissions(instance.user,instance.channel,instance.guild))){ - //no permissions - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-no-permissions").build("button",{guild:instance.guild,channel:instance.channel,user:instance.user,permissions:["support"]})) - return cancel() - }else return - }else{ - if (!instance.guild || !instance.member){ - //error - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error").build("button",{guild:instance.guild,channel:instance.channel,user:instance.user,error:"Permission Error: Not in Server #1",layout:"advanced"})) - return cancel() - } - const role = await opendiscord.client.fetchGuildRole(instance.guild,permissionMode) - if (!role){ - //error - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error").build("button",{guild:instance.guild,channel:instance.channel,user:instance.user,error:"Permission Error: Not in Server #2",layout:"advanced"})) - return cancel() - } - if (!role.members.has(instance.member.id)){ - //no permissions - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-no-permissions").build("button",{guild:instance.guild,channel:instance.channel,user:instance.user,permissions:[]})) - return cancel() - }else return - } - }), - new api.ODWorker("opendiscord:reopen-ticket",0,async (instance,params,source,cancel) => { - const {guild,channel,user} = instance - if (!guild){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-not-in-guild").build("button",{channel,user})) - return cancel() - } - const ticket = opendiscord.tickets.get(channel.id) - if (!ticket || channel.isDMBased()){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-ticket-unknown").build("button",{guild,channel,user})) - return cancel() - } - //return when not closed - if (!ticket.get("opendiscord:closed").value){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error").build("button",{guild,channel,user,error:opendiscord.languages.getTranslation("errors.actionInvalid.reopen"),layout:"simple"})) - return cancel() - } - //return when busy - if (ticket.get("opendiscord:busy").value){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-ticket-busy").build("button",{guild,channel,user})) - return cancel() - } - - //start reopening ticket - if (params.data == "reason"){ - //reopen with reason - instance.modal(await opendiscord.builders.modals.getSafe("opendiscord:reopen-ticket-reason").build("autoclose-message",{guild,channel,user,ticket})) - }else{ - //reopen without reason - await instance.defer("update",false) - await opendiscord.actions.get("opendiscord:reopen-ticket").run("autoclose-message",{guild,channel,user,ticket,reason:null,sendMessage:false}) - await instance.update(await opendiscord.builders.messages.getSafe("opendiscord:reopen-message").build("autoclose-message",{guild,channel,user,ticket,reason:null})) - } - }) - ]) - opendiscord.verifybars.get("opendiscord:reopen-ticket-autoclose-message").failure.add([ - new api.ODWorker("opendiscord:back-to-autoclose-message",0,async (instance,params,source,cancel) => { - const {guild,channel,user} = instance - const {verifybarMessage} = params - if (!guild){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-not-in-guild").build("button",{channel,user})) - return cancel() - } - const ticket = opendiscord.tickets.get(channel.id) - if (!ticket || channel.isDMBased()){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-ticket-unknown").build("button",{guild,channel,user})) - return cancel() - } - - await instance.update(await opendiscord.builders.messages.getSafe("opendiscord:autoclose-message").build("other",{guild,channel,user,ticket})) - }) - ]) - opendiscord.actions.get("opendiscord:reopen-ticket").workers.backupWorker = new api.ODWorker("opendiscord:cancel-busy",0,(instance,params) => { - //set busy to false in case of crash or cancel - params.ticket.get("opendiscord:busy").value = false - }) } \ No newline at end of file diff --git a/src/actions/transferTicket.ts b/src/actions/transferTicket.ts index 85887dc..b80cc4f 100644 --- a/src/actions/transferTicket.ts +++ b/src/actions/transferTicket.ts @@ -1,15 +1,15 @@ /////////////////////////////////////// //TICKET TRANSFER SYSTEM /////////////////////////////////////// -import {opendiscord, api, utilities} from "../index" +import {opendiscord, api, utilities, openticketUtils} from "../index.js" import * as discord from "discord.js" const generalConfig = opendiscord.configs.get("opendiscord:general") -export const registerActions = async () => { +export async function registerActions(){ opendiscord.actions.add(new api.ODAction("opendiscord:transfer-ticket")) opendiscord.actions.get("opendiscord:transfer-ticket").workers.add([ - new api.ODWorker("opendiscord:transfer-ticket",2,async (instance,params,source,cancel) => { + new api.ODWorker("opendiscord:transfer-ticket",2,async (instance,params,origin,cancel) => { const {guild,channel,user,ticket,reason,newCreator} = params if (channel.isThread()) throw new api.ODSystemError("Unable to transfer ticket! Open Ticket doesn't support threads!") @@ -33,12 +33,8 @@ export const registerActions = async () => { } //update stats - await opendiscord.stats.get("opendiscord:global").setStat("opendiscord:tickets-transferred",1,"increase") - await opendiscord.stats.get("opendiscord:user").setStat("opendiscord:tickets-transferred",user.id,1,"increase") - - //get new channel properties - const channelPrefix = ticket.option.get("opendiscord:channel-prefix").value - const channelSuffix = ticket.get("opendiscord:channel-suffix").value + await opendiscord.statistics.get("opendiscord:global").setStat("opendiscord:tickets-transferred",1,"increase") + await opendiscord.statistics.get("opendiscord:user").setStat("opendiscord:tickets-transferred",user.id,1,"increase") //handle permissions const permissions: discord.OverwriteResolvable[] = [{ @@ -93,48 +89,56 @@ export const registerActions = async () => { opendiscord.log("Failed to reset channel permissions on ticket transfer!","error") } - //rename channel (and give error when crashed) - const pinEmoji = ticket.get("opendiscord:pinned").value ? generalConfig.data.system.pinEmoji : "" - const priorityEmoji = opendiscord.priorities.getFromPriorityLevel(ticket.get("opendiscord:priority").value).channelEmoji ?? "" - - const originalName = channel.name - const newName = pinEmoji+priorityEmoji+utilities.trimEmojis(channelPrefix+channelSuffix) - try{ - await utilities.timedAwait(channel.setName(newName),2500,(err) => { - opendiscord.log("Failed to rename channel on ticket transfer","error") - }) - }catch(err){ - await channel.send((await opendiscord.builders.messages.getSafe("opendiscord:error-channel-rename").build("ticket-transfer",{guild,channel,user,originalName,newName:newName})).message) - } - - //update ticket message - const ticketMessage = await opendiscord.tickets.getTicketMessage(ticket) - if (ticketMessage){ + //calculate channel name + const channelNameResult = await opendiscord.actions.get("opendiscord:calculate-ticket-name").run("transfer-ticket",{guild,user,option:ticket.option,channel,ticket,currentChannelName:channel.name}) + if (channelNameResult && channelNameResult.shouldChangeName && typeof channelNameResult.newChannelName !== "undefined"){ + const originalName = channel.name + const newName = channelNameResult.newChannelName try{ - ticketMessage.edit((await opendiscord.builders.messages.getSafe("opendiscord:ticket-message").build("other",{guild,channel,user,ticket})).message) - }catch(e){ - opendiscord.log("Unable to edit ticket message on ticket transferring!","error",[ - {key:"channel",value:"#"+channel.name}, - {key:"channelid",value:channel.id,hidden:true}, - {key:"messageid",value:ticketMessage.id}, - {key:"option",value:ticket.option.id.value} + await utilities.timedAwait(channel.setName(newName),2500,(err) => { + opendiscord.log("Failed to rename channel on ticket transfer","error") + }) + }catch(err){ + opendiscord.log("Unable to rename channel while transferring ticket! Waiting until ratelimit expires...","warning",[ + {key:"oldName",value:originalName}, + {key:"newName",value:newName} ]) - opendiscord.debugfile.writeErrorMessage(new api.ODError(e,"uncaughtException")) + const sentMsg = await channel.send((await opendiscord.builders.messages.getSafe("opendiscord:error-channel-rename").build("ticket-transfer",{guild,channel,user,originalName,newName})).message) + setTimeout(() => {if (sentMsg.deletable) sentMsg.delete()},7000) //autodelete error message } } + //update ticket message (no await) + openticketUtils.updateTicketMessage(guild,channel,user,ticket) + //reply with new message - if (params.sendMessage) await channel.send((await opendiscord.builders.messages.getSafe("opendiscord:transfer-message").build(source,{guild,channel,user,ticket,oldCreator,newCreator,reason})).message) + if (params.sendMessage) await channel.send((await opendiscord.builders.messages.getSafe("opendiscord:transfer-message").build(origin,{guild,channel,user,ticket,oldCreator,newCreator,reason})).message) ticket.get("opendiscord:busy").value = false await opendiscord.events.get("afterTicketTransferred").emit([ticket,user,channel,oldCreator,newCreator,reason]) //update channel topic await opendiscord.actions.get("opendiscord:update-ticket-topic").run("ticket-action",{guild,channel,user,ticket,sendMessage:false,newTopic:null}) }), - new api.ODWorker("opendiscord:discord-logs",1,async (instance,params,source,cancel) => { + new api.ODWorker("opendiscord:discord-logs",1,async (instance,params,origin,cancel) => { const {guild,channel,user,ticket,newCreator,reason} = params + + const lastCreatorId = ticket.get("opendiscord:previous-creators").value.at(-1) + if (!lastCreatorId) return + const lastCreator = await opendiscord.client.fetchUser(lastCreatorId) + if (!lastCreator) return + + //to logs + if (generalConfig.data.logs.enabled && generalConfig.data.logs.logMessages.transferring.logs){ + const logChannel = opendiscord.posts.get("opendiscord:logs") + if (logChannel) logChannel.send(await opendiscord.builders.messages.getSafe("opendiscord:ticket-action-logs").build(origin,{guild,channel,user,ticket,mode:"transfer",reason,additionalData:lastCreator,additionalData2:newCreator})) + } + + //to dm + const creator = await opendiscord.tickets.getTicketUser(ticket,"creator") + if (creator && generalConfig.data.logs.logMessages.transferring.dm) await opendiscord.client.sendUserDm(creator,await opendiscord.builders.messages.getSafe("opendiscord:ticket-action-dm").build(origin,{guild,channel,user,ticket,mode:"transfer",reason,additionalData:lastCreator,additionalData2:newCreator})) + }), - new api.ODWorker("opendiscord:logs",0,(instance,params,source,cancel) => { + new api.ODWorker("opendiscord:logs",0,(instance,params,origin,cancel) => { const {guild,channel,user,ticket,newCreator} = params opendiscord.log(user.displayName+" transferred a ticket to '"+newCreator.displayName+"'!","info",[ @@ -143,7 +147,7 @@ export const registerActions = async () => { {key:"channel",value:"#"+channel.name}, {key:"channelid",value:channel.id,hidden:true}, {key:"reason",value:params.reason ?? "/"}, - {key:"method",value:source} + {key:"method",value:origin} ]) }) ]) diff --git a/src/actions/unclaimTicket.ts b/src/actions/unclaimTicket.ts index 6a04c1e..e91b4ef 100644 --- a/src/actions/unclaimTicket.ts +++ b/src/actions/unclaimTicket.ts @@ -1,15 +1,16 @@ /////////////////////////////////////// //TICKET UNCLAIMING SYSTEM /////////////////////////////////////// -import {opendiscord, api, utilities} from "../index" +import {opendiscord, api, utilities, openticketUtils} from "../index.js" import * as discord from "discord.js" const generalConfig = opendiscord.configs.get("opendiscord:general") +const interactiveMsgState = opendiscord.states.get("opendiscord:interactive-message") -export const registerActions = async () => { +export async function registerActions(){ opendiscord.actions.add(new api.ODAction("opendiscord:unclaim-ticket")) opendiscord.actions.get("opendiscord:unclaim-ticket").workers.add([ - new api.ODWorker("opendiscord:unclaim-ticket",2,async (instance,params,source,cancel) => { + new api.ODWorker("opendiscord:unclaim-ticket",2,async (instance,params,origin,cancel) => { const {guild,channel,user,ticket,reason} = params if (channel.isThread()) throw new api.ODSystemError("Unable to unclaim ticket! Open Ticket doesn't support threads!") @@ -21,97 +22,63 @@ export const registerActions = async () => { ticket.get("opendiscord:claimed-on").value = null ticket.get("opendiscord:busy").value = true - //update category + //calculate & update category if (typeof params.allowCategoryChange == "boolean" ? params.allowCategoryChange : true){ - const channelCategory = ticket.option.get("opendiscord:channel-category").value - const channelBackupCategory = ticket.option.get("opendiscord:channel-category-backup").value - if (channelCategory !== ""){ - //category enabled - try { - const normalCategory = await opendiscord.client.fetchGuildCategoryChannel(guild,channelCategory) - if (!normalCategory){ - //default category was not found - opendiscord.log("Ticket Unclaiming Error: Unable to find category! #1","error",[ - {key:"categoryid",value:channelCategory}, - {key:"backup",value:"false"} - ]) - }else{ - //default category was found - if (normalCategory.children.cache.size >= 49 && channelBackupCategory != ""){ - //use backup category - const backupCategory = await opendiscord.client.fetchGuildCategoryChannel(guild,channelBackupCategory) - if (!backupCategory){ - //default category was not found - opendiscord.log("Ticket Unclaiming Error: Unable to find category! #2","error",[ - {key:"categoryid",value:channelBackupCategory}, - {key:"backup",value:"true"} - ]) - }else{ - //use backup category - channel.setParent(backupCategory,{lockPermissions:false}) - ticket.get("opendiscord:category-mode").value = "backup" - ticket.get("opendiscord:category").value = backupCategory.id - } - }else{ - //use default category - channel.setParent(normalCategory,{lockPermissions:false}) - ticket.get("opendiscord:category-mode").value = "normal" - ticket.get("opendiscord:category").value = normalCategory.id - } - } - - }catch(e){ - opendiscord.log("Unable to move ticket to 'unclaimed category'!","error",[ + const categoryResult = await opendiscord.actions.get("opendiscord:calculate-ticket-category").run("unclaim-ticket",{guild,user,option:ticket.option,channel,ticket,currentCategoryId:channel.parentId}) + if (categoryResult && categoryResult.shouldChangeCategory && typeof categoryResult.newCategoryId !== "undefined" && typeof categoryResult.newCategoryMode !== "undefined" && typeof categoryResult.newCategory !== "undefined"){ + const originalCategoryName = channel.parent?.name ?? "" + const newCategoryName = categoryResult.newCategory?.name ?? "" + try{ + await utilities.timedAwait(channel.setParent(categoryResult.newCategoryId,{lockPermissions:false}),3000,(err) => { + process.emit("uncaughtException",new Error("Error: Unable to change channel parent: "+err)) + }) + ticket.get("opendiscord:category-mode").value = categoryResult.newCategoryMode + ticket.get("opendiscord:category").value = categoryResult.newCategoryId + }catch(err){ + const sentMsg = await channel.send((await opendiscord.builders.messages.getSafe("opendiscord:error-channel-category").build("ticket-unclaim",{guild,channel,user,originalCategory:originalCategoryName,newCategory:newCategoryName})).message) + setTimeout(() => {if (sentMsg.deletable) sentMsg.delete()},7000) //autodelete error message + opendiscord.log("Unable to move ticket to unclaimed category.","error",[ {key:"channel",value:"#"+channel.name}, - {key:"channelid",value:channel.id,hidden:true} + {key:"channelid",value:channel.id,hidden:true}, + {key:"categoryid",value:categoryResult.newCategoryId ?? "/"} ]) - opendiscord.debugfile.writeErrorMessage(new api.ODError(e,"uncaughtException")) } - }else{ - channel.setParent(null,{lockPermissions:false}) - ticket.get("opendiscord:category-mode").value = null - ticket.get("opendiscord:category").value = null } } - //update ticket message - const ticketMessage = await opendiscord.tickets.getTicketMessage(ticket) - if (ticketMessage){ - try{ - ticketMessage.edit((await opendiscord.builders.messages.getSafe("opendiscord:ticket-message").build("other",{guild,channel,user,ticket})).message) - }catch(e){ - opendiscord.log("Unable to edit ticket message on ticket unclaiming!","error",[ - {key:"channel",value:"#"+channel.name}, - {key:"channelid",value:channel.id,hidden:true}, - {key:"messageid",value:ticketMessage.id}, - {key:"option",value:ticket.option.id.value} - ]) - opendiscord.debugfile.writeErrorMessage(new api.ODError(e,"uncaughtException")) - } - } + //update ticket message (no await) + openticketUtils.updateTicketMessage(guild,channel,user,ticket) //reply with new message - if (params.sendMessage) await channel.send((await opendiscord.builders.messages.getSafe("opendiscord:unclaim-message").build(source,{guild,channel,user,ticket,reason})).message) + if (params.sendMessage){ + const sentMsg = await channel.send((await opendiscord.builders.messages.getSafe("opendiscord:unclaim-message").build(origin,{guild,channel,user,ticket,reason})).message) + if (sentMsg) await interactiveMsgState.setMsgState({channel,message:sentMsg},{ + messageType:"unclaim-message", + messageOrigin:"other", + messageAuthor:user.id, + messageReason:reason + },false) + } ticket.get("opendiscord:busy").value = false await opendiscord.events.get("afterTicketUnclaimed").emit([ticket,user,channel,reason]) //update channel topic await opendiscord.actions.get("opendiscord:update-ticket-topic").run("ticket-action",{guild,channel,user,ticket,sendMessage:false,newTopic:null}) }), - new api.ODWorker("opendiscord:discord-logs",1,async (instance,params,source,cancel) => { + new api.ODWorker("opendiscord:discord-logs",1,async (instance,params,origin,cancel) => { const {guild,channel,user,ticket,reason} = params //to logs - if (generalConfig.data.system.logs.enabled && generalConfig.data.system.messages.claiming.logs){ + if (generalConfig.data.logs.enabled && generalConfig.data.logs.logMessages.claiming.logs){ const logChannel = opendiscord.posts.get("opendiscord:logs") - if (logChannel) logChannel.send(await opendiscord.builders.messages.getSafe("opendiscord:ticket-action-logs").build(source,{guild,channel,user,ticket,mode:"unclaim",reason,additionalData:null})) + if (logChannel) logChannel.send(await opendiscord.builders.messages.getSafe("opendiscord:ticket-action-logs").build(origin,{guild,channel,user,ticket,mode:"unclaim",reason,additionalData:null})) } //to dm const creator = await opendiscord.tickets.getTicketUser(ticket,"creator") - if (creator && generalConfig.data.system.messages.claiming.dm) await opendiscord.client.sendUserDm(creator,await opendiscord.builders.messages.getSafe("opendiscord:ticket-action-dm").build(source,{guild,channel,user,ticket,mode:"unclaim",reason,additionalData:null})) + if (creator && generalConfig.data.logs.logMessages.claiming.dm) await opendiscord.client.sendUserDm(creator,await opendiscord.builders.messages.getSafe("opendiscord:ticket-action-dm").build(origin,{guild,channel,user,ticket,mode:"unclaim",reason,additionalData:null})) }), - new api.ODWorker("opendiscord:logs",0,(instance,params,source,cancel) => { + new api.ODWorker("opendiscord:logs",0,(instance,params,origin,cancel) => { const {guild,channel,user,ticket} = params opendiscord.log(user.displayName+" unclaimed a ticket!","info",[ @@ -120,192 +87,8 @@ export const registerActions = async () => { {key:"channel",value:"#"+channel.name}, {key:"channelid",value:channel.id,hidden:true}, {key:"reason",value:params.reason ?? "/"}, - {key:"method",value:source} + {key:"method",value:origin} ]) }) ]) -} - -export const registerVerifyBars = async () => { - //UNCLAIM TICKET TICKET MESSAGE - opendiscord.verifybars.add(new api.ODVerifyBar("opendiscord:unclaim-ticket-ticket-message",opendiscord.builders.messages.getSafe("opendiscord:verifybar-ticket-message"),!generalConfig.data.system.disableVerifyBars)) - opendiscord.verifybars.get("opendiscord:unclaim-ticket-ticket-message").success.add([ - new api.ODWorker("opendiscord:permissions",1,async (instance,params,source,cancel) => { - const permissionMode = generalConfig.data.system.permissions.unclaim - - if (permissionMode == "none"){ - //no permissions - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-no-permissions").build("button",{guild:instance.guild,channel:instance.channel,user:instance.user,permissions:[]})) - return cancel() - }else if (permissionMode == "everyone") return - else if (permissionMode == "admin"){ - if (!opendiscord.permissions.hasPermissions("support",await opendiscord.permissions.getPermissions(instance.user,instance.channel,instance.guild))){ - //no permissions - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-no-permissions").build("button",{guild:instance.guild,channel:instance.channel,user:instance.user,permissions:["support"]})) - return cancel() - }else return - }else{ - if (!instance.guild || !instance.member){ - //error - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error").build("button",{guild:instance.guild,channel:instance.channel,user:instance.user,error:"Permission Error: Not in Server #1",layout:"advanced"})) - return cancel() - } - const role = await opendiscord.client.fetchGuildRole(instance.guild,permissionMode) - if (!role){ - //error - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error").build("button",{guild:instance.guild,channel:instance.channel,user:instance.user,error:"Permission Error: Not in Server #2",layout:"advanced"})) - return cancel() - } - if (!role.members.has(instance.member.id)){ - //no permissions - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-no-permissions").build("button",{guild:instance.guild,channel:instance.channel,user:instance.user,permissions:[]})) - return cancel() - }else return - } - }), - new api.ODWorker("opendiscord:unclaim-ticket",0,async (instance,params,source,cancel) => { - const {guild,channel,user} = instance - if (!guild){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-not-in-guild").build("button",{channel,user})) - return cancel() - } - const ticket = opendiscord.tickets.get(channel.id) - if (!ticket || channel.isDMBased()){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-ticket-unknown").build("button",{guild,channel,user})) - return cancel() - } - //return when not claimed - if (!ticket.get("opendiscord:claimed").value){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error").build("button",{guild,channel,user,error:opendiscord.languages.getTranslation("errors.actionInvalid.unclaim"),layout:"simple"})) - return cancel() - } - //return when busy - if (ticket.get("opendiscord:busy").value){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-ticket-busy").build("button",{guild,channel,user})) - return cancel() - } - - //start unclaiming ticket - if (params.data == "reason"){ - //unclaim with reason - instance.modal(await opendiscord.builders.modals.getSafe("opendiscord:unclaim-ticket-reason").build("ticket-message",{guild,channel,user,ticket})) - }else{ - //unclaim without reason - await instance.defer("update",false) - await opendiscord.actions.get("opendiscord:unclaim-ticket").run("ticket-message",{guild,channel,user,ticket,reason:null,sendMessage:true}) - await instance.update(await opendiscord.builders.messages.getSafe("opendiscord:ticket-message").build("other",{guild,channel,user,ticket})) - } - }) - ]) - opendiscord.verifybars.get("opendiscord:unclaim-ticket-ticket-message").failure.add([ - new api.ODWorker("opendiscord:back-to-ticket-message",0,async (instance,params,source,cancel) => { - const {guild,channel,user} = instance - if (!guild){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-not-in-guild").build("button",{channel,user})) - return cancel() - } - const ticket = opendiscord.tickets.get(channel.id) - if (!ticket || channel.isDMBased()){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-ticket-unknown").build("button",{guild,channel,user})) - return cancel() - } - - await instance.update(await opendiscord.builders.messages.getSafe("opendiscord:ticket-message").build("other",{guild,channel,user,ticket})) - }) - ]) - - //UNCLAIM TICKET CLAIM MESSAGE - opendiscord.verifybars.add(new api.ODVerifyBar("opendiscord:unclaim-ticket-claim-message",opendiscord.builders.messages.getSafe("opendiscord:verifybar-claim-message"),!generalConfig.data.system.disableVerifyBars)) - opendiscord.verifybars.get("opendiscord:unclaim-ticket-claim-message").success.add([ - new api.ODWorker("opendiscord:permissions",1,async (instance,params,source,cancel) => { - const permissionMode = generalConfig.data.system.permissions.unclaim - - if (permissionMode == "none"){ - //no permissions - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-no-permissions").build("button",{guild:instance.guild,channel:instance.channel,user:instance.user,permissions:[]})) - return cancel() - }else if (permissionMode == "everyone") return - else if (permissionMode == "admin"){ - if (!opendiscord.permissions.hasPermissions("support",await opendiscord.permissions.getPermissions(instance.user,instance.channel,instance.guild))){ - //no permissions - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-no-permissions").build("button",{guild:instance.guild,channel:instance.channel,user:instance.user,permissions:["support"]})) - return cancel() - }else return - }else{ - if (!instance.guild || !instance.member){ - //error - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error").build("button",{guild:instance.guild,channel:instance.channel,user:instance.user,error:"Permission Error: Not in Server #1",layout:"advanced"})) - return cancel() - } - const role = await opendiscord.client.fetchGuildRole(instance.guild,permissionMode) - if (!role){ - //error - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error").build("button",{guild:instance.guild,channel:instance.channel,user:instance.user,error:"Permission Error: Not in Server #2",layout:"advanced"})) - return cancel() - } - if (!role.members.has(instance.member.id)){ - //no permissions - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-no-permissions").build("button",{guild:instance.guild,channel:instance.channel,user:instance.user,permissions:[]})) - return cancel() - }else return - } - }), - new api.ODWorker("opendiscord:unclaim-ticket",0,async (instance,params,source,cancel) => { - const {guild,channel,user} = instance - if (!guild){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-not-in-guild").build("button",{channel,user})) - return cancel() - } - const ticket = opendiscord.tickets.get(channel.id) - if (!ticket || channel.isDMBased()){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-ticket-unknown").build("button",{guild,channel,user})) - return cancel() - } - //return when not claimed - if (!ticket.get("opendiscord:claimed").value){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error").build("button",{guild,channel,user,error:opendiscord.languages.getTranslation("errors.actionInvalid.unclaim"),layout:"simple"})) - return cancel() - } - //return when busy - if (ticket.get("opendiscord:busy").value){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-ticket-busy").build("button",{guild,channel,user})) - return cancel() - } - - //start unclaiming ticket - if (params.data == "reason"){ - //unclaim with reason - instance.modal(await opendiscord.builders.modals.getSafe("opendiscord:unclaim-ticket-reason").build("claim-message",{guild,channel,user,ticket})) - }else{ - //unclaim without reason - await instance.defer("update",false) - await opendiscord.actions.get("opendiscord:unclaim-ticket").run("claim-message",{guild,channel,user,ticket,reason:null,sendMessage:false}) - await instance.update(await opendiscord.builders.messages.getSafe("opendiscord:unclaim-message").build("claim-message",{guild,channel,user,ticket,reason:null})) - } - }) - ]) - opendiscord.verifybars.get("opendiscord:unclaim-ticket-claim-message").failure.add([ - new api.ODWorker("opendiscord:back-to-claim-message",0,async (instance,params,source,cancel) => { - const {guild,channel,user} = instance - const {verifybarMessage} = params - if (!guild){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-not-in-guild").build("button",{channel,user})) - return cancel() - } - const ticket = opendiscord.tickets.get(channel.id) - if (!ticket || channel.isDMBased()){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-ticket-unknown").build("button",{guild,channel,user})) - return cancel() - } - - const rawReason = (verifybarMessage && verifybarMessage.embeds[0] && verifybarMessage.embeds[0].fields[0]) ? verifybarMessage.embeds[0].fields[0].value : null - const reason = (rawReason == null) ? null : rawReason.substring(3,rawReason.length-3) - - await instance.update(await opendiscord.builders.messages.getSafe("opendiscord:claim-message").build("other",{guild,channel,user,ticket,reason})) - }) - ]) - opendiscord.actions.get("opendiscord:unclaim-ticket").workers.backupWorker = new api.ODWorker("opendiscord:cancel-busy",0,(instance,params) => { - //set busy to false in case of crash or cancel - params.ticket.get("opendiscord:busy").value = false - }) } \ No newline at end of file diff --git a/src/actions/unpinTicket.ts b/src/actions/unpinTicket.ts index 931ef38..747754a 100644 --- a/src/actions/unpinTicket.ts +++ b/src/actions/unpinTicket.ts @@ -1,15 +1,16 @@ /////////////////////////////////////// //TICKET UNPINNING SYSTEM /////////////////////////////////////// -import {opendiscord, api, utilities} from "../index" +import {opendiscord, api, utilities, openticketUtils} from "../index.js" import * as discord from "discord.js" const generalConfig = opendiscord.configs.get("opendiscord:general") +const interactiveMsgState = opendiscord.states.get("opendiscord:interactive-message") -export const registerActions = async () => { +export async function registerActions(){ opendiscord.actions.add(new api.ODAction("opendiscord:unpin-ticket")) opendiscord.actions.get("opendiscord:unpin-ticket").workers.add([ - new api.ODWorker("opendiscord:unpin-ticket",2,async (instance,params,source,cancel) => { + new api.ODWorker("opendiscord:unpin-ticket",2,async (instance,params,origin,cancel) => { const {guild,channel,user,ticket,reason} = params if (channel.isThread()) throw new api.ODSystemError("Unable to unpin ticket! Open Ticket doesn't support threads!") @@ -21,58 +22,58 @@ export const registerActions = async () => { ticket.get("opendiscord:pinned-on").value = null ticket.get("opendiscord:busy").value = true - //rename channel (and give error when crashed) - const pinEmoji = ticket.get("opendiscord:pinned").value ? generalConfig.data.system.pinEmoji : "" - const priorityEmoji = opendiscord.priorities.getFromPriorityLevel(ticket.get("opendiscord:priority").value).channelEmoji ?? "" - - const originalName = channel.name - const newName = pinEmoji+priorityEmoji+utilities.trimEmojis(channel.name) - try{ - await utilities.timedAwait(channel.setName(newName),2500,(err) => { - opendiscord.log("Failed to rename channel on ticket unpin","error") - }) - }catch(err){ - await channel.send((await opendiscord.builders.messages.getSafe("opendiscord:error-channel-rename").build("ticket-unpin",{guild,channel,user,originalName,newName})).message) - } - - //update ticket message - const ticketMessage = await opendiscord.tickets.getTicketMessage(ticket) - if (ticketMessage){ + //calculate channel name + const channelNameResult = await opendiscord.actions.get("opendiscord:calculate-ticket-name").run("unpin-ticket",{guild,user,option:ticket.option,channel,ticket,currentChannelName:channel.name}) + if (channelNameResult && channelNameResult.shouldChangeName && typeof channelNameResult.newChannelName !== "undefined"){ + const originalName = channel.name + const newName = channelNameResult.newChannelName try{ - ticketMessage.edit((await opendiscord.builders.messages.getSafe("opendiscord:ticket-message").build("other",{guild,channel,user,ticket})).message) - }catch(e){ - opendiscord.log("Unable to edit ticket message on ticket unpinning!","error",[ - {key:"channel",value:"#"+channel.name}, - {key:"channelid",value:channel.id,hidden:true}, - {key:"messageid",value:ticketMessage.id}, - {key:"option",value:ticket.option.id.value} + await utilities.timedAwait(channel.setName(newName),2500,(err) => { + opendiscord.log("Failed to rename channel on ticket unpin","error") + }) + }catch(err){ + opendiscord.log("Unable to rename channel while unpinning ticket! Waiting until ratelimit expires...","warning",[ + {key:"oldName",value:originalName}, + {key:"newName",value:newName} ]) - opendiscord.debugfile.writeErrorMessage(new api.ODError(e,"uncaughtException")) + const sentMsg = await channel.send((await opendiscord.builders.messages.getSafe("opendiscord:error-channel-rename").build("ticket-unpin",{guild,channel,user,originalName,newName})).message) + setTimeout(() => {if (sentMsg.deletable) sentMsg.delete()},7000) //autodelete error message } } + //update ticket message (no await) + openticketUtils.updateTicketMessage(guild,channel,user,ticket) + //reply with new message - if (params.sendMessage) await channel.send((await opendiscord.builders.messages.getSafe("opendiscord:unpin-message").build(source,{guild,channel,user,ticket,reason})).message) + if (params.sendMessage){ + const sentMsg = await channel.send((await opendiscord.builders.messages.getSafe("opendiscord:unpin-message").build(origin,{guild,channel,user,ticket,reason})).message) + if (sentMsg) await interactiveMsgState.setMsgState({channel,message:sentMsg},{ + messageType:"unpin-message", + messageOrigin:"other", + messageAuthor:user.id, + messageReason:reason + },false) + } ticket.get("opendiscord:busy").value = false await opendiscord.events.get("afterTicketUnpinned").emit([ticket,user,channel,reason]) //update channel topic await opendiscord.actions.get("opendiscord:update-ticket-topic").run("ticket-action",{guild,channel,user,ticket,sendMessage:false,newTopic:null}) }), - new api.ODWorker("opendiscord:discord-logs",1,async (instance,params,source,cancel) => { + new api.ODWorker("opendiscord:discord-logs",1,async (instance,params,origin,cancel) => { const {guild,channel,user,ticket,reason} = params //to logs - if (generalConfig.data.system.logs.enabled && generalConfig.data.system.messages.pinning.logs){ + if (generalConfig.data.logs.enabled && generalConfig.data.logs.logMessages.pinning.logs){ const logChannel = opendiscord.posts.get("opendiscord:logs") - if (logChannel) logChannel.send(await opendiscord.builders.messages.getSafe("opendiscord:ticket-action-logs").build(source,{guild,channel,user,ticket,mode:"unpin",reason,additionalData:null})) + if (logChannel) logChannel.send(await opendiscord.builders.messages.getSafe("opendiscord:ticket-action-logs").build(origin,{guild,channel,user,ticket,mode:"unpin",reason,additionalData:null})) } //to dm const creator = await opendiscord.tickets.getTicketUser(ticket,"creator") - if (creator && generalConfig.data.system.messages.pinning.dm) await opendiscord.client.sendUserDm(creator,await opendiscord.builders.messages.getSafe("opendiscord:ticket-action-dm").build(source,{guild,channel,user,ticket,mode:"unpin",reason,additionalData:null})) + if (creator && generalConfig.data.logs.logMessages.pinning.dm) await opendiscord.client.sendUserDm(creator,await opendiscord.builders.messages.getSafe("opendiscord:ticket-action-dm").build(origin,{guild,channel,user,ticket,mode:"unpin",reason,additionalData:null})) }), - new api.ODWorker("opendiscord:logs",0,(instance,params,source,cancel) => { + new api.ODWorker("opendiscord:logs",0,(instance,params,origin,cancel) => { const {guild,channel,user,ticket} = params opendiscord.log(user.displayName+" unpinned a ticket!","info",[ @@ -81,192 +82,8 @@ export const registerActions = async () => { {key:"channel",value:"#"+channel.name}, {key:"channelid",value:channel.id,hidden:true}, {key:"reason",value:params.reason ?? "/"}, - {key:"method",value:source} + {key:"method",value:origin} ]) }) ]) -} - -export const registerVerifyBars = async () => { - //UNPIN TICKET TICKET MESSAGE - opendiscord.verifybars.add(new api.ODVerifyBar("opendiscord:unpin-ticket-ticket-message",opendiscord.builders.messages.getSafe("opendiscord:verifybar-ticket-message"),!generalConfig.data.system.disableVerifyBars)) - opendiscord.verifybars.get("opendiscord:unpin-ticket-ticket-message").success.add([ - new api.ODWorker("opendiscord:permissions",1,async (instance,params,source,cancel) => { - const permissionMode = generalConfig.data.system.permissions.unpin - - if (permissionMode == "none"){ - //no permissions - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-no-permissions").build("button",{guild:instance.guild,channel:instance.channel,user:instance.user,permissions:[]})) - return cancel() - }else if (permissionMode == "everyone") return - else if (permissionMode == "admin"){ - if (!opendiscord.permissions.hasPermissions("support",await opendiscord.permissions.getPermissions(instance.user,instance.channel,instance.guild))){ - //no permissions - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-no-permissions").build("button",{guild:instance.guild,channel:instance.channel,user:instance.user,permissions:["support"]})) - return cancel() - }else return - }else{ - if (!instance.guild || !instance.member){ - //error - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error").build("button",{guild:instance.guild,channel:instance.channel,user:instance.user,error:"Permission Error: Not in Server #1",layout:"advanced"})) - return cancel() - } - const role = await opendiscord.client.fetchGuildRole(instance.guild,permissionMode) - if (!role){ - //error - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error").build("button",{guild:instance.guild,channel:instance.channel,user:instance.user,error:"Permission Error: Not in Server #2",layout:"advanced"})) - return cancel() - } - if (!role.members.has(instance.member.id)){ - //no permissions - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-no-permissions").build("button",{guild:instance.guild,channel:instance.channel,user:instance.user,permissions:[]})) - return cancel() - }else return - } - }), - new api.ODWorker("opendiscord:unpin-ticket",0,async (instance,params,source,cancel) => { - const {guild,channel,user} = instance - if (!guild){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-not-in-guild").build("button",{channel,user})) - return cancel() - } - const ticket = opendiscord.tickets.get(channel.id) - if (!ticket || channel.isDMBased()){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-ticket-unknown").build("button",{guild,channel,user})) - return cancel() - } - //return when not pinned - if (!ticket.get("opendiscord:pinned").value){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error").build("button",{guild,channel,user,error:opendiscord.languages.getTranslation("errors.actionInvalid.unpin"),layout:"simple"})) - return cancel() - } - //return when busy - if (ticket.get("opendiscord:busy").value){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-ticket-busy").build("button",{guild,channel,user})) - return cancel() - } - - //start unpining ticket - if (params.data == "reason"){ - //unpin with reason - instance.modal(await opendiscord.builders.modals.getSafe("opendiscord:unpin-ticket-reason").build("ticket-message",{guild,channel,user,ticket})) - }else{ - //unpin without reason - await instance.defer("update",false) - await opendiscord.actions.get("opendiscord:unpin-ticket").run("ticket-message",{guild,channel,user,ticket,reason:null,sendMessage:true}) - await instance.update(await opendiscord.builders.messages.getSafe("opendiscord:ticket-message").build("other",{guild,channel,user,ticket})) - } - }) - ]) - opendiscord.verifybars.get("opendiscord:unpin-ticket-ticket-message").failure.add([ - new api.ODWorker("opendiscord:back-to-ticket-message",0,async (instance,params,source,cancel) => { - const {guild,channel,user} = instance - if (!guild){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-not-in-guild").build("button",{channel,user})) - return cancel() - } - const ticket = opendiscord.tickets.get(channel.id) - if (!ticket || channel.isDMBased()){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-ticket-unknown").build("button",{guild,channel,user})) - return cancel() - } - - await instance.update(await opendiscord.builders.messages.getSafe("opendiscord:ticket-message").build("other",{guild,channel,user,ticket})) - }) - ]) - - //UNPIN TICKET PIN MESSAGE - opendiscord.verifybars.add(new api.ODVerifyBar("opendiscord:unpin-ticket-pin-message",opendiscord.builders.messages.getSafe("opendiscord:verifybar-pin-message"),!generalConfig.data.system.disableVerifyBars)) - opendiscord.verifybars.get("opendiscord:unpin-ticket-pin-message").success.add([ - new api.ODWorker("opendiscord:permissions",1,async (instance,params,source,cancel) => { - const permissionMode = generalConfig.data.system.permissions.unpin - - if (permissionMode == "none"){ - //no permissions - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-no-permissions").build("button",{guild:instance.guild,channel:instance.channel,user:instance.user,permissions:[]})) - return cancel() - }else if (permissionMode == "everyone") return - else if (permissionMode == "admin"){ - if (!opendiscord.permissions.hasPermissions("support",await opendiscord.permissions.getPermissions(instance.user,instance.channel,instance.guild))){ - //no permissions - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-no-permissions").build("button",{guild:instance.guild,channel:instance.channel,user:instance.user,permissions:["support"]})) - return cancel() - }else return - }else{ - if (!instance.guild || !instance.member){ - //error - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error").build("button",{guild:instance.guild,channel:instance.channel,user:instance.user,error:"Permission Error: Not in Server #1",layout:"advanced"})) - return cancel() - } - const role = await opendiscord.client.fetchGuildRole(instance.guild,permissionMode) - if (!role){ - //error - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error").build("button",{guild:instance.guild,channel:instance.channel,user:instance.user,error:"Permission Error: Not in Server #2",layout:"advanced"})) - return cancel() - } - if (!role.members.has(instance.member.id)){ - //no permissions - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-no-permissions").build("button",{guild:instance.guild,channel:instance.channel,user:instance.user,permissions:[]})) - return cancel() - }else return - } - }), - new api.ODWorker("opendiscord:unpin-ticket",0,async (instance,params,source,cancel) => { - const {guild,channel,user} = instance - if (!guild){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-not-in-guild").build("button",{channel,user})) - return cancel() - } - const ticket = opendiscord.tickets.get(channel.id) - if (!ticket || channel.isDMBased()){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-ticket-unknown").build("button",{guild,channel,user})) - return cancel() - } - //return when not pinned - if (!ticket.get("opendiscord:pinned").value){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error").build("button",{guild,channel,user,error:opendiscord.languages.getTranslation("errors.actionInvalid.unpin"),layout:"simple"})) - return cancel() - } - //return when busy - if (ticket.get("opendiscord:busy").value){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-ticket-busy").build("button",{guild,channel,user})) - return cancel() - } - - //start unpinning ticket - if (params.data == "reason"){ - //unpin with reason - instance.modal(await opendiscord.builders.modals.getSafe("opendiscord:unpin-ticket-reason").build("pin-message",{guild,channel,user,ticket})) - }else{ - //unpin without reason - await instance.defer("update",false) - await opendiscord.actions.get("opendiscord:unpin-ticket").run("pin-message",{guild,channel,user,ticket,reason:null,sendMessage:false}) - await instance.update(await opendiscord.builders.messages.getSafe("opendiscord:unpin-message").build("pin-message",{guild,channel,user,ticket,reason:null})) - } - }) - ]) - opendiscord.verifybars.get("opendiscord:unpin-ticket-pin-message").failure.add([ - new api.ODWorker("opendiscord:back-to-pin-message",0,async (instance,params,source,cancel) => { - const {guild,channel,user} = instance - const {verifybarMessage} = params - if (!guild){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-not-in-guild").build("button",{channel,user})) - return cancel() - } - const ticket = opendiscord.tickets.get(channel.id) - if (!ticket || channel.isDMBased()){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-ticket-unknown").build("button",{guild,channel,user})) - return cancel() - } - - const rawReason = (verifybarMessage && verifybarMessage.embeds[0] && verifybarMessage.embeds[0].fields[0]) ? verifybarMessage.embeds[0].fields[0].value : null - const reason = (rawReason == null) ? null : rawReason.substring(3,rawReason.length-3) - - await instance.update(await opendiscord.builders.messages.getSafe("opendiscord:pin-message").build("other",{guild,channel,user,ticket,reason})) - }) - ]) - opendiscord.actions.get("opendiscord:unpin-ticket").workers.backupWorker = new api.ODWorker("opendiscord:cancel-busy",0,(instance,params) => { - //set busy to false in case of crash or cancel - params.ticket.get("opendiscord:busy").value = false - }) } \ No newline at end of file diff --git a/src/actions/updateTicketPriority.ts b/src/actions/updateTicketPriority.ts index de328a7..3112b52 100644 --- a/src/actions/updateTicketPriority.ts +++ b/src/actions/updateTicketPriority.ts @@ -1,15 +1,15 @@ /////////////////////////////////////// //TICKET TOPIC SYSTEM /////////////////////////////////////// -import {opendiscord, api, utilities} from "../index" +import {opendiscord, api, utilities, openticketUtils} from "../index.js" import * as discord from "discord.js" const generalConfig = opendiscord.configs.get("opendiscord:general") -export const registerActions = async () => { +export async function registerActions(){ opendiscord.actions.add(new api.ODAction("opendiscord:update-ticket-priority")) opendiscord.actions.get("opendiscord:update-ticket-priority").workers.add([ - new api.ODWorker("opendiscord:update-ticket-priority",2,async (instance,params,source,cancel) => { + new api.ODWorker("opendiscord:update-ticket-priority",2,async (instance,params,origin,cancel) => { const {guild,channel,user,ticket,newPriority,reason} = params if (channel.isThread() || !(channel instanceof discord.TextChannel)) throw new api.ODSystemError("Unable to set priority of ticket! Open Ticket doesn't support threads!") @@ -20,32 +20,52 @@ export const registerActions = async () => { ticket.get("opendiscord:busy").value = true if (newPriority) ticket.get("opendiscord:priority").value = newPriority.priority - //rename channel (and give error when crashed) - const pinEmoji = ticket.get("opendiscord:pinned").value ? generalConfig.data.system.pinEmoji : "" - const priorityEmoji = newPriority.channelEmoji ?? "" - - const originalName = channel.name - const newName = pinEmoji+priorityEmoji+utilities.trimEmojis(channel.name) - try{ - await utilities.timedAwait(channel.setName(newName),2500,(err) => { - opendiscord.log("Failed to rename channel on ticket priority update","error") - }) - }catch(err){ - await channel.send((await opendiscord.builders.messages.getSafe("opendiscord:error-channel-rename").build("ticket-priority",{guild,channel,user,originalName,newName})).message) + //calculate channel name + const channelNameResult = await opendiscord.actions.get("opendiscord:calculate-ticket-name").run("priority-change",{guild,user,option:ticket.option,channel,ticket,currentChannelName:channel.name}) + if (channelNameResult && channelNameResult.shouldChangeName && typeof channelNameResult.newChannelName !== "undefined"){ + const originalName = channel.name + const newName = channelNameResult.newChannelName + try{ + await utilities.timedAwait(channel.setName(newName),2500,(err) => { + opendiscord.log("Failed to rename channel on priority change","error") + }) + }catch(err){ + opendiscord.log("Unable to rename channel while updating ticket priority! Waiting until ratelimit expires...","warning",[ + {key:"oldName",value:originalName}, + {key:"newName",value:newName} + ]) + const sentMsg = await channel.send((await opendiscord.builders.messages.getSafe("opendiscord:error-channel-rename").build("ticket-priority",{guild,channel,user,originalName,newName})).message) + setTimeout(() => {if (sentMsg.deletable) sentMsg.delete()},7000) //autodelete error message + } } //reply with new message - if (params.sendMessage) await channel.send((await opendiscord.builders.messages.getSafe("opendiscord:priority-set").build(source,{guild,channel,user,ticket,priority:newPriority,reason})).message) + if (params.sendMessage) await channel.send((await opendiscord.builders.messages.getSafe("opendiscord:priority-set").build(origin,{guild,channel,user,ticket,priority:newPriority,reason})).message) ticket.get("opendiscord:busy").value = false await opendiscord.events.get("afterTicketPriorityChanged").emit([ticket,user,channel,oldPriority,newPriority,reason]) //update channel topic await opendiscord.actions.get("opendiscord:update-ticket-topic").run("ticket-action",{guild,channel,user,ticket,sendMessage:false,newTopic:null}) + + //update ticket message (no await) + openticketUtils.updateTicketMessage(guild,channel,user,ticket) }), - new api.ODWorker("opendiscord:discord-logs",1,async (instance,params,source,cancel) => { - const {guild,channel,user,ticket} = params + new api.ODWorker("opendiscord:discord-logs",1,async (instance,params,origin,cancel) => { + const {guild,channel,user,ticket,reason,newPriority} = params + + const renderedPriority = newPriority.renderDisplayName() + + //to logs + if (generalConfig.data.logs.enabled && generalConfig.data.logs.logMessages.priorityChange.logs){ + const logChannel = opendiscord.posts.get("opendiscord:logs") + if (logChannel) logChannel.send(await opendiscord.builders.messages.getSafe("opendiscord:ticket-action-logs").build(origin,{guild,channel,user,ticket,mode:"priority",reason,additionalData:renderedPriority})) + } + + //to dm + const creator = await opendiscord.tickets.getTicketUser(ticket,"creator") + if (creator && generalConfig.data.logs.logMessages.priorityChange.dm) await opendiscord.client.sendUserDm(creator,await opendiscord.builders.messages.getSafe("opendiscord:ticket-action-dm").build(origin,{guild,channel,user,ticket,mode:"priority",reason,additionalData:renderedPriority})) }), - new api.ODWorker("opendiscord:logs",0,(instance,params,source,cancel) => { + new api.ODWorker("opendiscord:logs",0,(instance,params,origin,cancel) => { const {guild,channel,user,ticket,newPriority} = params opendiscord.log(user.displayName+" changed the priority of a ticket!","info",[ @@ -54,7 +74,7 @@ export const registerActions = async () => { {key:"channel",value:"#"+channel.name}, {key:"channelid",value:channel.id,hidden:true}, {key:"priority",value:newPriority.id.value}, - {key:"method",value:source} + {key:"method",value:origin} ]) }) ]) diff --git a/src/actions/updateTicketTopic.ts b/src/actions/updateTicketTopic.ts index 1b544b0..ccacccf 100644 --- a/src/actions/updateTicketTopic.ts +++ b/src/actions/updateTicketTopic.ts @@ -1,16 +1,16 @@ /////////////////////////////////////// //TICKET TOPIC SYSTEM /////////////////////////////////////// -import {opendiscord, api, utilities} from "../index" +import {opendiscord, api, utilities, openticketUtils} from "../index.js" import * as discord from "discord.js" const generalConfig = opendiscord.configs.get("opendiscord:general") const lang = opendiscord.languages -export const registerActions = async () => { +export async function registerActions(){ opendiscord.actions.add(new api.ODAction("opendiscord:update-ticket-topic")) opendiscord.actions.get("opendiscord:update-ticket-topic").workers.add([ - new api.ODWorker("opendiscord:update-ticket-topic",2,async (instance,params,source,cancel) => { + new api.ODWorker("opendiscord:update-ticket-topic",2,async (instance,params,origin,cancel) => { const {guild,channel,user,ticket,newTopic} = params if (channel.isThread() || !(channel instanceof discord.TextChannel)) throw new api.ODSystemError("Unable to set topic of ticket! Open Ticket doesn't support threads!") @@ -29,28 +29,43 @@ export const registerActions = async () => { //handle channel topic const channelTopics: string[] = [] - if (generalConfig.data.system.channelTopic.showOptionName) channelTopics.push(ticket.option.get("opendiscord:name").value) - if (generalConfig.data.system.channelTopic.showOptionDescription) channelTopics.push(ticket.option.get("opendiscord:description").value) - if (generalConfig.data.system.channelTopic.showOptionTopic) channelTopics.push(ticket.get("opendiscord:topic").value) - if (generalConfig.data.system.channelTopic.showPriority) channelTopics.push("**"+lang.getTranslation("params.uppercase.priority")+":** "+opendiscord.priorities.getFromPriorityLevel(ticket.get("opendiscord:priority").value).renderDisplayName()) - if (generalConfig.data.system.channelTopic.showClosed) channelTopics.push("**"+lang.getTranslation("params.uppercase.status")+":** "+(closed ? lang.getTranslation("params.uppercase.closed") : lang.getTranslation("params.uppercase.open"))) - if (generalConfig.data.system.channelTopic.showClaimed) channelTopics.push("**"+lang.getTranslation("stats.properties.claimedBy")+":** "+(claimedBy ? discord.userMention(claimedBy) : lang.getTranslation("params.uppercase.noone"))) - if (generalConfig.data.system.channelTopic.showPinned) channelTopics.push("**"+lang.getTranslation("params.uppercase.pinned")+":** "+(pinned ? lang.getTranslation("params.uppercase.yes") : lang.getTranslation("params.uppercase.no"))) - if (generalConfig.data.system.channelTopic.showCreator) channelTopics.push("**"+lang.getTranslation("params.uppercase.creator")+":** "+discord.userMention(creator)) - if (generalConfig.data.system.channelTopic.showParticipants) channelTopics.push("**"+lang.getTranslation("params.uppercase.participants")+":** "+ticket.get("opendiscord:participants").value.map((p) => (p.type == "user") ? discord.userMention(p.id) : discord.roleMention(p.id)).join(", ")) + if (generalConfig.data.ticketSystem.channelTopic.showOptionName) channelTopics.push(ticket.option.get("opendiscord:name").value) + if (generalConfig.data.ticketSystem.channelTopic.showOptionDescription) channelTopics.push(ticket.option.get("opendiscord:description").value) + if (generalConfig.data.ticketSystem.channelTopic.showOptionTopic) channelTopics.push(ticket.get("opendiscord:topic").value) + if (generalConfig.data.ticketSystem.channelTopic.showPriority) channelTopics.push("**"+lang.getTranslation("params.uppercase.priority")+":** "+opendiscord.priorities.getFromPriorityLevel(ticket.get("opendiscord:priority").value).renderDisplayName()) + if (generalConfig.data.ticketSystem.channelTopic.showClosed) channelTopics.push("**"+lang.getTranslation("params.uppercase.status")+":** "+(closed ? lang.getTranslation("params.uppercase.closed") : lang.getTranslation("params.uppercase.open"))) + if (generalConfig.data.ticketSystem.channelTopic.showClaimed) channelTopics.push("**"+lang.getTranslation("stats.properties.claimedBy")+":** "+(claimedBy ? discord.userMention(claimedBy) : lang.getTranslation("params.uppercase.noone"))) + if (generalConfig.data.ticketSystem.channelTopic.showPinned) channelTopics.push("**"+lang.getTranslation("params.uppercase.pinned")+":** "+(pinned ? lang.getTranslation("params.uppercase.yes") : lang.getTranslation("params.uppercase.no"))) + if (generalConfig.data.ticketSystem.channelTopic.showCreator) channelTopics.push("**"+lang.getTranslation("params.uppercase.creator")+":** "+discord.userMention(creator)) + if (generalConfig.data.ticketSystem.channelTopic.showParticipants) channelTopics.push("**"+lang.getTranslation("params.uppercase.participants")+":** "+ticket.get("opendiscord:participants").value.map((p) => (p.type == "user") ? discord.userMention(p.id) : discord.roleMention(p.id)).join(", ")) //update channel channel.setTopic(channelTopics.join(" • "),"Topic Changed") //reply with new message - if (params.sendMessage && newTopic) await channel.send((await opendiscord.builders.messages.getSafe("opendiscord:topic-set").build(source,{guild,channel,user,ticket,topic:newTopic})).message) + if (params.sendMessage && newTopic) await channel.send((await opendiscord.builders.messages.getSafe("opendiscord:topic-set").build(origin,{guild,channel,user,ticket,topic:newTopic})).message) ticket.get("opendiscord:busy").value = false if (newTopic) await opendiscord.events.get("afterTicketTopicChanged").emit([ticket,user,channel,oldTopic,newTopic]) + + //update ticket message (no await) + openticketUtils.updateTicketMessage(guild,channel,user,ticket) }), - new api.ODWorker("opendiscord:discord-logs",1,async (instance,params,source,cancel) => { - const {guild,channel,user,ticket} = params + new api.ODWorker("opendiscord:discord-logs",1,async (instance,params,origin,cancel) => { + const {guild,channel,user,ticket,newTopic} = params + + if (!newTopic) return //only log when topic actually changed + + //to logs + if (generalConfig.data.logs.enabled && generalConfig.data.logs.logMessages.topicChange.logs){ + const logChannel = opendiscord.posts.get("opendiscord:logs") + if (logChannel) logChannel.send(await opendiscord.builders.messages.getSafe("opendiscord:ticket-action-logs").build("topic-message",{guild,channel,user,ticket,mode:"topic",reason:null,additionalData:newTopic})) + } + + //to dm + const creator = await opendiscord.tickets.getTicketUser(ticket,"creator") + if (creator && generalConfig.data.logs.logMessages.topicChange.dm) await opendiscord.client.sendUserDm(creator,await opendiscord.builders.messages.getSafe("opendiscord:ticket-action-dm").build("topic-message",{guild,channel,user,ticket,mode:"topic",reason:null,additionalData:newTopic})) }), - new api.ODWorker("opendiscord:logs",0,(instance,params,source,cancel) => { + new api.ODWorker("opendiscord:logs",0,(instance,params,origin,cancel) => { const {guild,channel,user,ticket,newTopic} = params if (newTopic) opendiscord.log(user.displayName+" changed the topic of a ticket!","info",[ @@ -59,7 +74,7 @@ export const registerActions = async () => { {key:"channel",value:"#"+channel.name}, {key:"channelid",value:channel.id,hidden:true}, {key:"topic",value:newTopic}, - {key:"method",value:source} + {key:"method",value:origin} ]) }) ]) diff --git a/src/actions/utilities.ts b/src/actions/utilities.ts new file mode 100644 index 0000000..73d7d75 --- /dev/null +++ b/src/actions/utilities.ts @@ -0,0 +1,195 @@ +/////////////////////////////////////// +//OPEN TICKET UTILITY FUNCTIONS +/////////////////////////////////////// +import {opendiscord, api, utilities} from "../index.js" +import * as discord from "discord.js" + +/**Check the ticket creation permissions like: cooldown, limits, blacklist, ... */ +export async function checkTicketCreationPerms(instance:api.ODButtonResponderInstance|api.ODDropdownResponderInstance|api.ODModalResponderInstance|api.ODCommandResponderInstance,origin:api.ODActionManagerIdMappings["opendiscord:create-ticket-permissions"]["origin"],guild:discord.Guild,user:discord.User,option:api.ODTicketOption){ + //check ticket permissions + const lang = opendiscord.languages + const permsRes = await opendiscord.actions.get("opendiscord:create-ticket-permissions").run(origin,{guild,user,option}) + if (!permsRes.valid && instance.channel){ + //error + const newOrigin = (origin === "slash" || origin === "text") ? origin : "other" + if (permsRes.reason == "blacklist") await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-no-permissions-blacklisted").build(newOrigin,{guild:instance.guild,channel:instance.channel,user:instance.user})) + else if (permsRes.reason == "cooldown") await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-no-permissions-cooldown").build(newOrigin,{guild:instance.guild,channel:instance.channel,user:instance.user,until:permsRes.cooldownUntil})) + else if (permsRes.reason == "global-limit") await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-no-permissions-limits").build(newOrigin,{guild:instance.guild,channel:instance.channel,user:instance.user,limit:"global"})) + else if (permsRes.reason == "global-user-limit") await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-no-permissions-limits").build(newOrigin,{guild:instance.guild,channel:instance.channel,user:instance.user,limit:"global-user"})) + else if (permsRes.reason == "option-limit") await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-no-permissions-limits").build(newOrigin,{guild:instance.guild,channel:instance.channel,user:instance.user,limit:"option"})) + else if (permsRes.reason == "option-user-limit") await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-no-permissions-limits").build(newOrigin,{guild:instance.guild,channel:instance.channel,user:instance.user,limit:"option-user"})) + else if (permsRes.reason == "custom") await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error").build(newOrigin,{guild:instance.guild,channel:instance.channel,user:instance.user,layout:"simple",error:permsRes.customReason ?? lang.getTranslation("errors.descriptions.unableToCreateTicket")+" `Unknown invalid_permission_reason => no reason specified by plugin`",customTitle:lang.getTranslation("errors.titles.permissionError")})) + else await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error").build(newOrigin,{guild:instance.guild,channel:instance.channel,user:instance.user,error:"Unknown invalid_permission reason => calculation failed #1",layout:"advanced"})) + return false + }else return true +} + +/**Fetch the interactive message state. If not present, auto replies with error and returns `null`. When `null` is received, the worker should be returned and canceled. */ +export async function replyInteractiveMessageState(instance:api.ODButtonResponderInstance|api.ODDropdownResponderInstance|api.ODModalResponderInstance,origin:"slash"|"text"|"button"|"dropdown"|"modal"|"other",channel:discord.TextBasedChannel,message:discord.Message|null,replacementCommandName:string){ + //check message state + const {user,member,guild} = instance + const lang = opendiscord.languages + const interactiveMsgState = opendiscord.states.get("opendiscord:interactive-message") + + if (!message){ + await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error").build(origin,{guild,channel,user,error:lang.getTranslationWithParams("errors.descriptions.messageMissing",[replacementCommandName]),layout:"simple",customTitle:"Message State Error"})) + return null + } + + const state = await interactiveMsgState.getMsgState({channel,message}) + if (!state){ + await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error").build(origin,{guild,channel,user,error:lang.getTranslationWithParams("errors.descriptions.stateExpired",[replacementCommandName]),layout:"simple",customTitle:"Message State Expired"})) + return null + }else return state +} + +/**Check the permissions for this command. If not allowed, auto replies with error and returns `false`. When `false` is received, the worker should be returned and canceled. */ +export async function replyHasPermissions(instance:api.ODButtonResponderInstance|api.ODCommandResponderInstance|api.ODDropdownResponderInstance|api.ODModalResponderInstance,origin:"slash"|"text"|"button"|"dropdown"|"modal"|"other",commandName:keyof api.ODGeneralJsonConfig_SystemPermissions,settings?:api.ODPermissionSettings) { + //check permissions + const {user,member,channel,guild} = instance + const generalConfig = opendiscord.configs.get("opendiscord:general") + const permsResult = await opendiscord.permissions.checkCommandPerms(generalConfig.data.permissions[commandName],"support",user,member,channel,guild,settings) + + if (!permsResult.hasPerms){ + if (permsResult.reason == "not-in-server" && channel) await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-not-in-guild").build("button",{channel,user})) + else if (channel) await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-no-permissions").build(origin,{guild,channel,user,permissions:["support"]})) + return false + }else return true +} + +/**Check if channel is in guild/server. If not, auto replies with error and returns `false`. When `false` is received, the worker should be returned and canceled. */ +export async function replyIsInGuild(instance:api.ODButtonResponderInstance|api.ODCommandResponderInstance|api.ODDropdownResponderInstance|api.ODModalResponderInstance,origin:"slash"|"text"|"button"|"dropdown"|"modal"|"other"){ + //check is in guild/server + const {user,member,channel,guild} = instance + + if (!guild){ + if (channel) await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-not-in-guild").build(origin,{channel,user})) + return false + }else return true +} + +/**Check if channel is valid ticket. If not, auto replies with error and returns `null`. When `null` is received, the worker should be returned and canceled. */ +export async function replyIsTicket(instance:api.ODButtonResponderInstance|api.ODCommandResponderInstance|api.ODDropdownResponderInstance|api.ODModalResponderInstance,origin:"slash"|"text"|"button"|"dropdown"|"modal"|"other") { + //check if ticket exists + const {user,member,channel,guild} = instance + if (!channel) return null + const ticket = opendiscord.tickets.get(channel.id) + + if (!ticket || channel.isDMBased()){ + await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-ticket-unknown").build(origin,{guild,channel,user})) + return null + }else return ticket +} + +/**Check if ticket is not busy and available. If not, auto replies with error and returns `false`. When `false` is received, the worker should be returned and canceled. */ +export async function replyTicketIsAvailable(instance:api.ODButtonResponderInstance|api.ODCommandResponderInstance|api.ODDropdownResponderInstance|api.ODModalResponderInstance,origin:"slash"|"text"|"button"|"dropdown"|"modal"|"other",ticket:api.ODTicket) { + //return when busy + const {user,member,channel,guild} = instance + + if (ticket.get("opendiscord:busy").value){ + if (channel) await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-ticket-busy").build(origin,{guild,channel,user})) + return false + }else return true +} + +/**Check if ticket is open. If closed, auto replies with error and returns `false`. When `false` is received, the worker should be returned and canceled. */ +export async function replyTicketMustBeOpen(instance:api.ODButtonResponderInstance|api.ODCommandResponderInstance|api.ODDropdownResponderInstance|api.ODModalResponderInstance,origin:"slash"|"text"|"button"|"dropdown"|"modal"|"other",ticket:api.ODTicket){ + //return when already closed + const {user,member,channel,guild} = instance + if (ticket.get("opendiscord:closed").value){ + if (channel) await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error").build(origin,{guild,channel,user,error:opendiscord.languages.getTranslation("errors.actionInvalid.close"),layout:"simple"})) + return false + }else return true +} + +/**Check if ticket is closed. If open, auto replies with error and returns `false`. When `false` is received, the worker should be returned and canceled. */ +export async function replyTicketMustBeClosed(instance:api.ODButtonResponderInstance|api.ODCommandResponderInstance|api.ODDropdownResponderInstance|api.ODModalResponderInstance,origin:"slash"|"text"|"button"|"dropdown"|"modal"|"other",ticket:api.ODTicket) { + //return when already open + const {user,member,channel,guild} = instance + if (!ticket.get("opendiscord:closed").value){ + if (channel) await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error").build(origin,{guild,channel,user,error:opendiscord.languages.getTranslation("errors.actionInvalid.reopen"),layout:"simple"})) + return false + }else return true +} + +/**Check if ticket is unclaimed. If claimed, auto replies with error and returns `false`. When `false` is received, the worker should be returned and canceled. */ +export async function replyTicketMustBeUnclaimed(instance:api.ODButtonResponderInstance|api.ODCommandResponderInstance|api.ODDropdownResponderInstance|api.ODModalResponderInstance,origin:"slash"|"text"|"button"|"dropdown"|"modal"|"other",ticket:api.ODTicket) { + //return when already claimed + const {user,member,channel,guild} = instance + if (ticket.get("opendiscord:claimed").value){ + if (channel) await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error").build(origin,{guild,channel,user,error:opendiscord.languages.getTranslation("errors.actionInvalid.claim"),layout:"simple"})) + return false + }else return true +} + +/**Check if ticket is claimed. If unclaimed, auto replies with error and returns `false`. When `false` is received, the worker should be returned and canceled. */ +export async function replyTicketMustBeClaimed(instance:api.ODButtonResponderInstance|api.ODCommandResponderInstance|api.ODDropdownResponderInstance|api.ODModalResponderInstance,origin:"slash"|"text"|"button"|"dropdown"|"modal"|"other",ticket:api.ODTicket) { + //return when already unclaimed + const {user,member,channel,guild} = instance + if (!ticket.get("opendiscord:claimed").value){ + if (channel) await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error").build(origin,{guild,channel,user,error:opendiscord.languages.getTranslation("errors.actionInvalid.unclaim"),layout:"simple"})) + return false + }else return true +} + +/**Check if ticket is unpinned. If pinned, auto replies with error and returns `false`. When `false` is received, the worker should be returned and canceled. */ +export async function replyTicketMustBeUnpinned(instance:api.ODButtonResponderInstance|api.ODCommandResponderInstance|api.ODDropdownResponderInstance|api.ODModalResponderInstance,origin:"slash"|"text"|"button"|"dropdown"|"modal"|"other",ticket:api.ODTicket) { + //return when already pinned + const {user,member,channel,guild} = instance + if (ticket.get("opendiscord:pinned").value){ + if (channel) await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error").build(origin,{guild,channel,user,error:opendiscord.languages.getTranslation("errors.actionInvalid.pin"),layout:"simple"})) + return false + }else return true +} + +/**Check if ticket is pinned. If unpinned, auto replies with error and returns `false`. When `false` is received, the worker should be returned and canceled. */ +export async function replyTicketMustBePinned(instance:api.ODButtonResponderInstance|api.ODCommandResponderInstance|api.ODDropdownResponderInstance|api.ODModalResponderInstance,origin:"slash"|"text"|"button"|"dropdown"|"modal"|"other",ticket:api.ODTicket) { + //return when already unpinned + const {user,member,channel,guild} = instance + if (!ticket.get("opendiscord:pinned").value){ + if (channel) await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error").build(origin,{guild,channel,user,error:opendiscord.languages.getTranslation("errors.actionInvalid.unpin"),layout:"simple"})) + return false + }else return true +} + +/**Check if the ticket can be closed/deleted before a message has been sent by an admin or user. If not passing the test, auto replies with error and returns `false`. When `false` is received, the worker should be returned and canceled. */ +export async function replyMessageMustBeSentBeforeClose(instance:api.ODButtonResponderInstance|api.ODCommandResponderInstance|api.ODDropdownResponderInstance,origin:"slash"|"text"|"button"|"dropdown"|"modal"|"other",ticket:api.ODTicket,commandName:keyof api.ODGeneralJsonConfig_SystemPermissions) { + //return when not allowed because of missing messages + const {user,member,channel,guild} = instance + const generalConfig = opendiscord.configs.get("opendiscord:general") + const lang = opendiscord.languages + const permsResult = await opendiscord.permissions.checkCommandPerms(generalConfig.data.permissions[commandName],"support",user,member,channel,guild) + + if (!permsResult.hasPerms) throw new api.ODSystemError("Please check permissions before using replyMessageMustBeSentBeforeClose()") + if (!guild || channel.isDMBased()) throw new api.ODSystemError("replyMessageMustBeSentBeforeClose() must be used in a guild and not a DM channel.") + + if (!permsResult.isAdmin && (!generalConfig.data.ticketSystem.allowCloseBeforeMessage || !generalConfig.data.ticketSystem.allowCloseBeforeAdminMessage)){ + const analysis = await opendiscord.transcripts.collector.ticketUserMessagesAnalysis(ticket,guild,channel) + if (analysis && !generalConfig.data.ticketSystem.allowCloseBeforeMessage && analysis.totalMessages < 1){ + if (channel) await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error").build(origin,{guild,channel,user,layout:"simple",error:lang.getTranslation("errors.descriptions.closeBeforeMessage"),customTitle:lang.getTranslation("errors.titles.noPermissions")})) + return false + } + if (analysis && !generalConfig.data.ticketSystem.allowCloseBeforeAdminMessage && analysis.adminMessages < 1){ + if (channel) await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error").build(origin,{guild,channel,user,layout:"simple",error:lang.getTranslation("errors.descriptions.closeBeforeAdminMessage"),customTitle:lang.getTranslation("errors.titles.noPermissions")})) + return false + } + return true + }else return true +} + +/**Update the ticket message from a given ticket channel. This should be used after every change to the `ODTicket` after an action. */ +export async function updateTicketMessage(guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,ticket:api.ODTicket){ + const ticketMessage = await opendiscord.tickets.getTicketMessage(ticket) + if (!ticketMessage || !user || !channel || !guild || channel.isDMBased()) return + try{ + await ticketMessage.edit((await opendiscord.builders.messages.getSafe("opendiscord:ticket-message").build("other",{guild,channel,user,ticket})).message) + }catch(e){ + opendiscord.log("Unable to edit ticket message!","error",[ + {key:"channel",value:"#"+channel.name}, + {key:"channelid",value:channel.id,hidden:true}, + {key:"messageid",value:ticketMessage.id}, + {key:"option",value:ticket.option.id.value} + ]) + opendiscord.debugfile.writeErrorMessage(new api.ODError(e,"uncaughtException")) + } +} \ No newline at end of file diff --git a/src/builders/buttons.ts b/src/builders/buttons.ts index cc9cba9..bb83519 100644 --- a/src/builders/buttons.ts +++ b/src/builders/buttons.ts @@ -1,15 +1,36 @@ /////////////////////////////////////// //BUTTON BUILDERS /////////////////////////////////////// -import {opendiscord, api, utilities} from "../index" +import {opendiscord, api, utilities} from "../index.js" import * as discord from "discord.js" const buttons = opendiscord.builders.buttons const lang = opendiscord.languages const generalConfig = opendiscord.configs.get("opendiscord:general") -export const registerAllButtons = async () => { - verifybarButtons() +export async function registerAllButtons(){ + //VERIFYBAR BUTTON + buttons.add(new api.ODButton("opendiscord:verifybar-button")) + buttons.get("opendiscord:verifybar-button").workers.add( + new api.ODWorker("opendiscord:verifybar-button",0,async (instance,params) => { + const {verifybar,verifyButtonId} = params + if (params.verifyButtonId.length > 40) throw new api.ODSystemError("ODButton:opendiscord:verifybar-button => verifyButtonId '"+verifyButtonId+"' exceeds 40 characters limit!") + + const color = ("customColor" in params && params.customColor) ? params.customColor : "gray" + const defaultLabel = (params.defaultButtonType == "✅") ? lang.getTranslation("params.uppercase.accept") : lang.getTranslation("params.uppercase.cancel") + const defaultEmoji = (params.defaultButtonType == "✅") ? "✅" : "❌" + + const label = ("customLabel" in params && params.customLabel) ? params.customLabel : (params.useDefaultLabels ? defaultLabel : null) + const emoji = ("customEmoji" in params && params.customEmoji) ? params.customEmoji : defaultEmoji + + instance.setCustomId("od:verifybar|"+verifybar.id.value+"|"+verifyButtonId) + instance.setMode("button") + instance.setColor(color) + if (label) instance.setLabel(label) + if (emoji) instance.setEmoji(emoji) + }) + ) + errorButtons() helpMenuButtons() panelButtons() @@ -18,50 +39,6 @@ export const registerAllButtons = async () => { clearButtons() } -const verifybarButtons = () => { - //VERIFYBAR SUCCESS - buttons.add(new api.ODButton("opendiscord:verifybar-success")) - buttons.get("opendiscord:verifybar-success").workers.add( - new api.ODWorker("opendiscord:verifybar-success",0,async (instance,params) => { - const {verifybar,customData,customColor,customLabel,customEmoji} = params - - if (customData && customData.length > 40) throw new api.ODSystemError("ODButton:opendiscord:verifybar-success => customData exceeds 40 characters limit!") - - const newData = (customData) ? "_"+customData : "" - const newColor = customColor ?? "gray" - const newLabel = customLabel ?? "" - const newEmoji = customEmoji ?? "✅" - - instance.setCustomId("od:verifybar-success_"+verifybar.id.value+newData) - instance.setMode("button") - instance.setColor(newColor) - if (newLabel) instance.setLabel(newLabel) - if (newEmoji) instance.setEmoji(newEmoji) - }) - ) - - //VERIFYBAR FAILURE - buttons.add(new api.ODButton("opendiscord:verifybar-failure")) - buttons.get("opendiscord:verifybar-failure").workers.add( - new api.ODWorker("opendiscord:verifybar-failure",0,async (instance,params) => { - const {verifybar,customData,customColor,customLabel,customEmoji} = params - - if (customData && customData.length > 40) throw new api.ODSystemError("ODButton:opendiscord:verifybar-success => customData exceeds 40 characters limit!") - - const newData = (customData) ? "_"+customData : "" - const newColor = customColor ?? "gray" - const newLabel = customLabel ?? "" - const newEmoji = customEmoji ?? "❌" - - instance.setCustomId("od:verifybar-failure_"+verifybar.id.value+newData) - instance.setMode("button") - instance.setColor(newColor) - if (newLabel) instance.setLabel(newLabel) - if (newEmoji) instance.setEmoji(newEmoji) - }) - ) -} - const errorButtons = () => { //ERROR TICKET DEPRECATED TRANSCRIPT buttons.add(new api.ODButton("opendiscord:error-ticket-deprecated-transcript")) @@ -145,7 +122,7 @@ const panelButtons = () => { new api.ODWorker("opendiscord:ticket-option",0,async (instance,params) => { const {panel,option} = params - instance.setCustomId("od:ticket-option_"+panel.id.value+"_"+option.id.value) + instance.setCustomId("od:ticket-option|"+option.id.value) instance.setMode("button") instance.setColor(option.get("opendiscord:button-color").value) if (option.get("opendiscord:button-emoji").value) instance.setEmoji(option.get("opendiscord:button-emoji").value) @@ -174,7 +151,22 @@ const panelButtons = () => { new api.ODWorker("opendiscord:role-option",0,async (instance,params) => { const {panel,option} = params - instance.setCustomId("od:role-option_"+panel.id.value+"_"+option.id.value) + instance.setCustomId("od:role-option|"+option.id.value) + instance.setMode("button") + instance.setColor(option.get("opendiscord:button-color").value) + if (option.get("opendiscord:button-emoji").value) instance.setEmoji(option.get("opendiscord:button-emoji").value) + if (option.get("opendiscord:button-label").value) instance.setLabel(option.get("opendiscord:button-label").value) + if (!option.get("opendiscord:button-emoji").value && !option.get("opendiscord:button-label").value) instance.setLabel("<"+option.id.value+">") + }) + ) + + //SUB-PANEL OPTION + buttons.add(new api.ODButton("opendiscord:subpanel-option")) + buttons.get("opendiscord:subpanel-option").workers.add( + new api.ODWorker("opendiscord:subpanel-option",0,async (instance,params) => { + const {panel,option} = params + + instance.setCustomId("od:subpanel-option|"+option.id.value) instance.setMode("button") instance.setColor(option.get("opendiscord:button-color").value) if (option.get("opendiscord:button-emoji").value) instance.setEmoji(option.get("opendiscord:button-emoji").value) @@ -201,11 +193,11 @@ const ticketButtons = () => { //CLOSE TICKET buttons.add(new api.ODButton("opendiscord:close-ticket")) buttons.get("opendiscord:close-ticket").workers.add( - new api.ODWorker("opendiscord:close-ticket",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:close-ticket",0,async (instance,params,origin) => { const {guild,channel,ticket} = params instance.setMode("button") - instance.setCustomId("od:close-ticket_"+source) + instance.setCustomId("od:close-ticket") instance.setColor("gray") instance.setEmoji("🔒") instance.setLabel(lang.getTranslation("actions.buttons.close")) @@ -215,11 +207,11 @@ const ticketButtons = () => { //DELETE TICKET buttons.add(new api.ODButton("opendiscord:delete-ticket")) buttons.get("opendiscord:delete-ticket").workers.add( - new api.ODWorker("opendiscord:delete-ticket",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:delete-ticket",0,async (instance,params,origin) => { const {guild,channel,ticket} = params instance.setMode("button") - instance.setCustomId("od:delete-ticket_"+source) + instance.setCustomId("od:delete-ticket") instance.setColor("red") instance.setEmoji("✖") instance.setLabel(lang.getTranslation("actions.buttons.delete")) @@ -229,11 +221,11 @@ const ticketButtons = () => { //REOPEN TICKET buttons.add(new api.ODButton("opendiscord:reopen-ticket")) buttons.get("opendiscord:reopen-ticket").workers.add( - new api.ODWorker("opendiscord:reopen-ticket",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:reopen-ticket",0,async (instance,params,origin) => { const {guild,channel,ticket} = params instance.setMode("button") - instance.setCustomId("od:reopen-ticket_"+source) + instance.setCustomId("od:reopen-ticket") instance.setColor("green") instance.setEmoji("🔓") instance.setLabel(lang.getTranslation("actions.buttons.reopen")) @@ -243,11 +235,11 @@ const ticketButtons = () => { //CLAIM TICKET buttons.add(new api.ODButton("opendiscord:claim-ticket")) buttons.get("opendiscord:claim-ticket").workers.add( - new api.ODWorker("opendiscord:claim-ticket",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:claim-ticket",0,async (instance,params,origin) => { const {guild,channel,ticket} = params instance.setMode("button") - instance.setCustomId("od:claim-ticket_"+source) + instance.setCustomId("od:claim-ticket") instance.setColor("green") instance.setEmoji("👋") instance.setLabel(lang.getTranslation("actions.buttons.claim")) @@ -257,11 +249,11 @@ const ticketButtons = () => { //UNCLAIM TICKET buttons.add(new api.ODButton("opendiscord:unclaim-ticket")) buttons.get("opendiscord:unclaim-ticket").workers.add( - new api.ODWorker("opendiscord:unclaim-ticket",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:unclaim-ticket",0,async (instance,params,origin) => { const {guild,channel,ticket} = params instance.setMode("button") - instance.setCustomId("od:unclaim-ticket_"+source) + instance.setCustomId("od:unclaim-ticket") instance.setColor("green") instance.setEmoji("↩️") instance.setLabel(lang.getTranslation("actions.buttons.unclaim")) @@ -271,13 +263,13 @@ const ticketButtons = () => { //PIN TICKET buttons.add(new api.ODButton("opendiscord:pin-ticket")) buttons.get("opendiscord:pin-ticket").workers.add( - new api.ODWorker("opendiscord:pin-ticket",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:pin-ticket",0,async (instance,params,origin) => { const {guild,channel,ticket} = params instance.setMode("button") - instance.setCustomId("od:pin-ticket_"+source) + instance.setCustomId("od:pin-ticket") instance.setColor("gray") - instance.setEmoji(generalConfig.data.system.pinEmoji) + instance.setEmoji(generalConfig.data.ticketSystem.pinEmoji) instance.setLabel(lang.getTranslation("actions.buttons.pin")) }) ) @@ -285,13 +277,13 @@ const ticketButtons = () => { //UNPIN TICKET buttons.add(new api.ODButton("opendiscord:unpin-ticket")) buttons.get("opendiscord:unpin-ticket").workers.add( - new api.ODWorker("opendiscord:unpin-ticket",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:unpin-ticket",0,async (instance,params,origin) => { const {guild,channel,ticket} = params instance.setMode("button") - instance.setCustomId("od:unpin-ticket_"+source) + instance.setCustomId("od:unpin-ticket") instance.setColor("gray") - instance.setEmoji(generalConfig.data.system.pinEmoji) + instance.setEmoji(generalConfig.data.ticketSystem.pinEmoji) instance.setLabel(lang.getTranslation("actions.buttons.unpin")) }) ) @@ -301,7 +293,7 @@ const transcriptButtons = () => { //TRANSCRIPT HTML VISIT buttons.add(new api.ODButton("opendiscord:transcript-html-visit")) buttons.get("opendiscord:transcript-html-visit").workers.add( - new api.ODWorker("opendiscord:transcript-html-visit",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:transcript-html-visit",0,async (instance,params,origin) => { const {result} = params instance.setMode("url") if (result.data) instance.setUrl(result.data.url) @@ -314,9 +306,9 @@ const transcriptButtons = () => { //TRANSCRIPT ERROR RETRY buttons.add(new api.ODButton("opendiscord:transcript-error-retry")) buttons.get("opendiscord:transcript-error-retry").workers.add( - new api.ODWorker("opendiscord:transcript-error-retry",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:transcript-error-retry",0,async (instance,params,origin) => { instance.setMode("button") - instance.setCustomId("od:transcript-error-retry_"+source) + instance.setCustomId("od:transcript-error-retry_"+origin) instance.setColor("gray") instance.setEmoji("🔄") instance.setLabel(lang.getTranslation("transcripts.errors.retry")) @@ -326,9 +318,9 @@ const transcriptButtons = () => { //TRANSCRIPT ERROR CONTINUE buttons.add(new api.ODButton("opendiscord:transcript-error-continue")) buttons.get("opendiscord:transcript-error-continue").workers.add( - new api.ODWorker("opendiscord:transcript-error-continue",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:transcript-error-continue",0,async (instance,params,origin) => { instance.setMode("button") - instance.setCustomId("od:transcript-error-continue_"+source) + instance.setCustomId("od:transcript-error-continue_"+origin) instance.setColor("red") instance.setEmoji("✖") instance.setLabel(lang.getTranslation("transcripts.errors.continue")) @@ -340,12 +332,13 @@ const clearButtons = () => { //CLEAR CONTINUE buttons.add(new api.ODButton("opendiscord:clear-continue")) buttons.get("opendiscord:clear-continue").workers.add( - new api.ODWorker("opendiscord:clear-continue",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:clear-continue",0,async (instance,params,origin) => { instance.setMode("button") - instance.setCustomId("od:clear-continue_"+source+"_"+params.filter) + instance.setCustomId("od:clear-continue") instance.setColor("red") instance.setEmoji("✖") instance.setLabel(lang.getTranslation("actions.buttons.clear")) + instance.setDisabled(params.inProgress) }) ) } \ No newline at end of file diff --git a/src/builders/dropdowns.ts b/src/builders/dropdowns.ts index 2f675c5..28d26ae 100644 --- a/src/builders/dropdowns.ts +++ b/src/builders/dropdowns.ts @@ -1,37 +1,64 @@ /////////////////////////////////////// //DROPDOWN BUILDERS /////////////////////////////////////// -import {opendiscord, api, utilities} from "../index" +import {opendiscord, api, utilities} from "../index.js" import * as discord from "discord.js" const dropdowns = opendiscord.builders.dropdowns +const lang = opendiscord.languages -export const registerAllDropdowns = async () => { +export async function registerAllDropdowns(){ panelDropdowns() } const panelDropdowns = () => { - //TICKET OPTION - dropdowns.add(new api.ODDropdown("opendiscord:panel-dropdown-tickets")) - dropdowns.get("opendiscord:panel-dropdown-tickets").workers.add( - new api.ODWorker("opendiscord:panel-dropdown-tickets",0,async (instance,params) => { + //PANEL DROPDOWN + dropdowns.add(new api.ODDropdown("opendiscord:panel-dropdown")) + dropdowns.get("opendiscord:panel-dropdown").workers.add( + new api.ODWorker("opendiscord:panel-dropdown",0,async (instance,params) => { const {panel,options} = params const parsedOptions: api.ODDropdownData["options"] = options.map((option) => { - const label = option.get("opendiscord:button-label").value.substring(0,100) - const desc = option.get("opendiscord:description").value.substring(0,100) - const emoji = option.get("opendiscord:button-emoji").value - - return { - label:(label.length > 0) ? label : "", - value:"od:ticket-option_"+panel.id.value+"_"+option.id.value, - emoji:(emoji.length > 0) ? emoji : undefined, - description:(desc.length > 0) ? desc : undefined, - default:false - } + if (option instanceof api.ODTicketOption){ + const label = option.get("opendiscord:button-label").value.substring(0,100) + const desc = option.get("opendiscord:description").value.substring(0,100) + const emoji = option.get("opendiscord:button-emoji").value + + return { + label:(label.length > 0) ? label : "", + value:"od:ticket-option|"+option.id.value, + emoji:(emoji.length > 0) ? emoji : undefined, + description:(desc.length > 0) ? desc : undefined, + default:false + } + }else if (option instanceof api.ODRoleOption){ + const label = option.get("opendiscord:button-label").value.substring(0,100) + const desc = option.get("opendiscord:description").value.substring(0,100) + const emoji = option.get("opendiscord:button-emoji").value + + return { + label:(label.length > 0) ? label : "", + value:"od:role-option|"+option.id.value, + emoji:(emoji.length > 0) ? emoji : undefined, + description:(desc.length > 0) ? desc : undefined, + default:false + } + }else if (option instanceof api.ODSubPanelOption){ + const label = option.get("opendiscord:button-label").value.substring(0,100) + const desc = option.get("opendiscord:description").value.substring(0,100) + const emoji = option.get("opendiscord:button-emoji").value + + return { + label:(label.length > 0) ? label : "", + value:"od:subpanel-option|"+option.id.value, + emoji:(emoji.length > 0) ? emoji : undefined, + description:(desc.length > 0) ? desc : undefined, + default:false + } + }else throw new api.ODSystemError("Unable to create panel dropdown with options that don't match: ticket, role, sub-panel!") }) - instance.setCustomId("od:panel-dropdown_"+panel.id.value) + instance.setCustomId("od:panel-dropdown") instance.setType("string") instance.setMaxValues(1) instance.setMinValues(0) @@ -39,4 +66,28 @@ const panelDropdowns = () => { instance.setOptions(parsedOptions) }) ) + + //PRIORITY DROPDOWN + dropdowns.add(new api.ODDropdown("opendiscord:priority-dropdown")) + dropdowns.get("opendiscord:priority-dropdown").workers.add( + new api.ODWorker("opendiscord:priority-dropdown",0,async (instance,params) => { + const {ticket} = params + + const parsedOptions: api.ODDropdownData["options"] = opendiscord.priorities.getAll().sort((a,b) => b.priority-a.priority).map((prio) => ({ + label:prio.displayName, + emoji:prio.displayEmoji ?? undefined, + value:"od:select-priority|"+prio.rawName + })) + + const currentPriority = opendiscord.priorities.getFromPriorityLevel(ticket.get("opendiscord:priority").value) + const placeholder = (currentPriority.priority < 0) ? lang.getTranslation("panel.selectPriorityLevel") : (lang.getTranslation("params.uppercase.priority")+": "+currentPriority.renderDisplayName()) + + instance.setCustomId("od:priority-dropdown") + instance.setType("string") + instance.setMaxValues(1) + instance.setMinValues(0) + instance.setPlaceholder(placeholder) + instance.setOptions(parsedOptions) + }) + ) } \ No newline at end of file diff --git a/src/builders/embeds.ts b/src/builders/embeds.ts index 5d7cc2c..ec88c67 100644 --- a/src/builders/embeds.ts +++ b/src/builders/embeds.ts @@ -1,7 +1,7 @@ /////////////////////////////////////// //EMBED BUILDERS /////////////////////////////////////// -import {opendiscord, api, utilities} from "../index" +import {opendiscord, api, utilities} from "../index.js" import * as discord from "discord.js" import nodepath from "path" @@ -9,7 +9,7 @@ const embeds = opendiscord.builders.embeds const lang = opendiscord.languages const generalConfig = opendiscord.configs.get("opendiscord:general") -export const registerAllEmbeds = async () => { +export async function registerAllEmbeds(){ errorEmbeds() helpMenuEmbeds() statsEmbeds() @@ -23,12 +23,12 @@ export const registerAllEmbeds = async () => { extraEmbeds() } -/**Utility function to get the translated "method" from the source. Mostly used in error embeds. */ -const getMethodFromSource = (source:"slash"|"text"|"button"|"dropdown"|"modal"|"other"): string => { - if (source == "slash" || source == "text") return lang.getTranslation("params.lowercase.command") - else if (source == "button") return lang.getTranslation("params.lowercase.button") - else if (source == "dropdown") return lang.getTranslation("params.lowercase.dropdown") - else if (source == "modal") return lang.getTranslation("params.lowercase.modal") +/**Utility function to get the translated "method" from the origin. Mostly used in error embeds. */ +const getMethodFromOrigin = (origin:"slash"|"text"|"button"|"dropdown"|"modal"|"other"): string => { + if (origin == "slash" || origin == "text") return lang.getTranslation("params.lowercase.command") + else if (origin == "button") return lang.getTranslation("params.lowercase.button") + else if (origin == "dropdown") return lang.getTranslation("params.lowercase.dropdown") + else if (origin == "modal") return lang.getTranslation("params.lowercase.modal") else return lang.getTranslation("params.lowercase.method") } //lang.getTranslation() @@ -37,12 +37,12 @@ const errorEmbeds = () => { //ERROR embeds.add(new api.ODEmbed("opendiscord:error")) embeds.get("opendiscord:error").workers.add( - new api.ODWorker("opendiscord:error",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:error",0,async (instance,params,origin) => { const {user,error,layout,customTitle} = params - const method = getMethodFromSource(source) + const method = getMethodFromOrigin(origin) - instance.setColor(generalConfig.data.system.useRedErrorEmbeds ? "Red" : generalConfig.data.mainColor) + instance.setColor(generalConfig.data.ticketSystem.useRedErrorEmbeds ? "Red" : generalConfig.data.mainColor) instance.setTitle(utilities.emojiTitle("❌",customTitle ?? lang.getTranslation("errors.titles.internalError"))) instance.setAuthor(user.displayName,user.displayAvatarURL()) instance.setDescription(lang.getTranslationWithParams("errors.descriptions.internalError",[method]) + (layout == "simple") ? "\n"+error : "") @@ -75,7 +75,7 @@ const errorEmbeds = () => { }) const commandSyntax = "**"+error.prefix+error.name+" "+optionSyntax.join(" ")+"**" - instance.setColor(generalConfig.data.system.useRedErrorEmbeds ? "Red" : generalConfig.data.mainColor) + instance.setColor(generalConfig.data.ticketSystem.useRedErrorEmbeds ? "Red" : generalConfig.data.mainColor) instance.setTitle(utilities.emojiTitle("❌",lang.getTranslation("errors.titles.optionMissing"))) instance.setAuthor(user.displayName,user.displayAvatarURL()) instance.setDescription(lang.getTranslation("errors.descriptions.optionMissing")) @@ -130,7 +130,7 @@ const errorEmbeds = () => { else if (error.reason == "channel_type" && error.option.type == "channel") reasonValue = lang.getTranslation("errors.optionInvalidReasons.channelType") else if (error.reason == "not_in_guild") reasonValue = lang.getTranslation("errors.optionInvalidReasons.notInGuild") - instance.setColor(generalConfig.data.system.useRedErrorEmbeds ? "Red" : generalConfig.data.mainColor) + instance.setColor(generalConfig.data.ticketSystem.useRedErrorEmbeds ? "Red" : generalConfig.data.mainColor) instance.setTitle(utilities.emojiTitle("❌",lang.getTranslation("errors.titles.optionInvalid"))) instance.setAuthor(user.displayName,user.displayAvatarURL()) instance.setDescription(lang.getTranslation("errors.descriptions.optionInvalid")+"\n"+reasonTitle+": `"+reasonValue+"`") @@ -144,7 +144,7 @@ const errorEmbeds = () => { new api.ODWorker("opendiscord:error-unknown-command",0,async (instance,params) => { const {user} = params - instance.setColor(generalConfig.data.system.useRedErrorEmbeds ? "Red" : generalConfig.data.mainColor) + instance.setColor(generalConfig.data.ticketSystem.useRedErrorEmbeds ? "Red" : generalConfig.data.mainColor) instance.setTitle(utilities.emojiTitle("❌",lang.getTranslation("errors.titles.unknownCommand"))) instance.setAuthor(user.displayName,user.displayAvatarURL()) instance.setDescription(lang.getTranslation("errors.descriptions.unknownCommand")) @@ -154,10 +154,10 @@ const errorEmbeds = () => { //ERROR NO PERMISSIONS embeds.add(new api.ODEmbed("opendiscord:error-no-permissions")) embeds.get("opendiscord:error-no-permissions").workers.add( - new api.ODWorker("opendiscord:error-no-permissions",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:error-no-permissions",0,async (instance,params,origin) => { const {user,permissions} = params - const method = getMethodFromSource(source) + const method = getMethodFromOrigin(origin) const renderedPerms = permissions.map((perm) => { if (perm == "developer") return "- "+lang.getTranslation("errors.permissions.developer") @@ -169,7 +169,7 @@ const errorEmbeds = () => { else if (perm == "discord-administrator") return "- "+lang.getTranslation("errors.permissions.discord-administrator") }).join("\n") - instance.setColor(generalConfig.data.system.useRedErrorEmbeds ? "Red" : generalConfig.data.mainColor) + instance.setColor(generalConfig.data.ticketSystem.useRedErrorEmbeds ? "Red" : generalConfig.data.mainColor) instance.setTitle(utilities.emojiTitle("❌",lang.getTranslation("errors.titles.noPermissions"))) instance.setAuthor(user.displayName,user.displayAvatarURL()) instance.setDescription(lang.getTranslationWithParams("errors.descriptions.noPermissions",[method])) @@ -180,12 +180,12 @@ const errorEmbeds = () => { //ERROR NO PERMISSIONS COOLDOWN embeds.add(new api.ODEmbed("opendiscord:error-no-permissions-cooldown")) embeds.get("opendiscord:error-no-permissions-cooldown").workers.add( - new api.ODWorker("opendiscord:error-no-permissions-cooldown",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:error-no-permissions-cooldown",0,async (instance,params,origin) => { const {user} = params - const method = getMethodFromSource(source) + const method = getMethodFromOrigin(origin) - instance.setColor(generalConfig.data.system.useRedErrorEmbeds ? "Red" : generalConfig.data.mainColor) + instance.setColor(generalConfig.data.ticketSystem.useRedErrorEmbeds ? "Red" : generalConfig.data.mainColor) instance.setTitle(utilities.emojiTitle("❌",lang.getTranslation("errors.titles.noPermissions"))) instance.setAuthor(user.displayName,user.displayAvatarURL()) instance.setDescription(lang.getTranslationWithParams("errors.descriptions.noPermissionsCooldown",[method])) @@ -196,12 +196,12 @@ const errorEmbeds = () => { //ERROR NO PERMISSIONS BLACKLISTED embeds.add(new api.ODEmbed("opendiscord:error-no-permissions-blacklisted")) embeds.get("opendiscord:error-no-permissions-blacklisted").workers.add( - new api.ODWorker("opendiscord:error-no-permissions-blacklisted",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:error-no-permissions-blacklisted",0,async (instance,params,origin) => { const {user} = params - const method = getMethodFromSource(source) + const method = getMethodFromOrigin(origin) - instance.setColor(generalConfig.data.system.useRedErrorEmbeds ? "Red" : generalConfig.data.mainColor) + instance.setColor(generalConfig.data.ticketSystem.useRedErrorEmbeds ? "Red" : generalConfig.data.mainColor) instance.setTitle(utilities.emojiTitle("❌",lang.getTranslation("errors.titles.noPermissions"))) instance.setAuthor(user.displayName,user.displayAvatarURL()) instance.setDescription(lang.getTranslationWithParams("errors.descriptions.noPermissionsBlacklist",[method])) @@ -211,10 +211,10 @@ const errorEmbeds = () => { //ERROR NO PERMISSIONS LIMITS embeds.add(new api.ODEmbed("opendiscord:error-no-permissions-limits")) embeds.get("opendiscord:error-no-permissions-limits").workers.add( - new api.ODWorker("opendiscord:error-no-permissions-limits",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:error-no-permissions-limits",0,async (instance,params,origin) => { const {user,limit} = params - instance.setColor(generalConfig.data.system.useRedErrorEmbeds ? "Red" : generalConfig.data.mainColor) + instance.setColor(generalConfig.data.ticketSystem.useRedErrorEmbeds ? "Red" : generalConfig.data.mainColor) instance.setTitle(utilities.emojiTitle("❌",lang.getTranslation("errors.titles.noPermissions"))) instance.setAuthor(user.displayName,user.displayAvatarURL()) if (limit == "global") instance.setDescription(lang.getTranslation("errors.descriptions.noPermissionsLimitGlobal")) @@ -227,12 +227,12 @@ const errorEmbeds = () => { //ERROR RESPONDER TIMEOUT embeds.add(new api.ODEmbed("opendiscord:error-responder-timeout")) embeds.get("opendiscord:error-responder-timeout").workers.add( - new api.ODWorker("opendiscord:error-responder-timeout",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:error-responder-timeout",0,async (instance,params,origin) => { const {user} = params - const method = getMethodFromSource(source) + const method = getMethodFromOrigin(origin) - instance.setColor(generalConfig.data.system.useRedErrorEmbeds ? "Red" : generalConfig.data.mainColor) + instance.setColor(generalConfig.data.ticketSystem.useRedErrorEmbeds ? "Red" : generalConfig.data.mainColor) instance.setTitle(utilities.emojiTitle("❌",lang.getTranslation("errors.titles.internalError"))) instance.setAuthor(user.displayName,user.displayAvatarURL()) instance.setDescription(lang.getTranslationWithParams("errors.descriptions.internalError",[method])) @@ -244,10 +244,10 @@ const errorEmbeds = () => { //ERROR TICKET UNKNOWN embeds.add(new api.ODEmbed("opendiscord:error-ticket-unknown")) embeds.get("opendiscord:error-ticket-unknown").workers.add( - new api.ODWorker("opendiscord:error-ticket-unknown",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:error-ticket-unknown",0,async (instance,params,origin) => { const {user} = params - instance.setColor(generalConfig.data.system.useRedErrorEmbeds ? "Red" : generalConfig.data.mainColor) + instance.setColor(generalConfig.data.ticketSystem.useRedErrorEmbeds ? "Red" : generalConfig.data.mainColor) instance.setTitle(utilities.emojiTitle("❌",lang.getTranslation("errors.titles.unknownTicket"))) instance.setAuthor(user.displayName,user.displayAvatarURL()) instance.setDescription(lang.getTranslation("errors.descriptions.unknownTicket")) @@ -258,10 +258,10 @@ const errorEmbeds = () => { //ERROR TICKET DEPRECATED embeds.add(new api.ODEmbed("opendiscord:error-ticket-deprecated")) embeds.get("opendiscord:error-ticket-deprecated").workers.add( - new api.ODWorker("opendiscord:error-ticket-deprecated",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:error-ticket-deprecated",0,async (instance,params,origin) => { const {user} = params - instance.setColor(generalConfig.data.system.useRedErrorEmbeds ? "Red" : generalConfig.data.mainColor) + instance.setColor(generalConfig.data.ticketSystem.useRedErrorEmbeds ? "Red" : generalConfig.data.mainColor) instance.setTitle(utilities.emojiTitle("❌",lang.getTranslation("errors.titles.deprecatedTicket"))) instance.setAuthor(user.displayName,user.displayAvatarURL()) instance.setDescription(lang.getTranslation("errors.descriptions.deprecatedTicket")) @@ -272,7 +272,7 @@ const errorEmbeds = () => { //ERROR OPTION UNKNOWN embeds.add(new api.ODEmbed("opendiscord:error-option-unknown")) embeds.get("opendiscord:error-option-unknown").workers.add( - new api.ODWorker("opendiscord:error-option-unknown",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:error-option-unknown",0,async (instance,params,origin) => { const {user} = params const renderedTicketOptions = opendiscord.options.getAll().map((option) => { @@ -281,7 +281,7 @@ const errorEmbeds = () => { }else return "- `"+option.id.value+"`" }).join("\n") - instance.setColor(generalConfig.data.system.useRedErrorEmbeds ? "Red" : generalConfig.data.mainColor) + instance.setColor(generalConfig.data.ticketSystem.useRedErrorEmbeds ? "Red" : generalConfig.data.mainColor) instance.setTitle(utilities.emojiTitle("❌",lang.getTranslation("errors.titles.unknownOption"))) instance.setAuthor(user.displayName,user.displayAvatarURL()) instance.setFooter(lang.getTranslation("errors.descriptions.askForInfo")) @@ -292,7 +292,7 @@ const errorEmbeds = () => { //ERROR PANEL UNKNOWN embeds.add(new api.ODEmbed("opendiscord:error-panel-unknown")) embeds.get("opendiscord:error-panel-unknown").workers.add( - new api.ODWorker("opendiscord:error-panel-unknown",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:error-panel-unknown",0,async (instance,params,origin) => { const {user} = params const renderedPanels = opendiscord.panels.getAll().map((panel) => { @@ -301,7 +301,7 @@ const errorEmbeds = () => { }else return "- `"+panel.id.value+"`" }).join("\n") - instance.setColor(generalConfig.data.system.useRedErrorEmbeds ? "Red" : generalConfig.data.mainColor) + instance.setColor(generalConfig.data.ticketSystem.useRedErrorEmbeds ? "Red" : generalConfig.data.mainColor) instance.setTitle(utilities.emojiTitle("❌",lang.getTranslation("errors.titles.unknownPanel"))) instance.setAuthor(user.displayName,user.displayAvatarURL()) instance.addFields({name:lang.getTranslation("params.uppercase.validPanels")+":",value:renderedPanels}) @@ -312,12 +312,12 @@ const errorEmbeds = () => { //ERROR NOT IN GUILD embeds.add(new api.ODEmbed("opendiscord:error-not-in-guild")) embeds.get("opendiscord:error-not-in-guild").workers.add( - new api.ODWorker("opendiscord:error-not-in-guild",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:error-not-in-guild",0,async (instance,params,origin) => { const {user} = params - const method = getMethodFromSource(source) + const method = getMethodFromOrigin(origin) - instance.setColor(generalConfig.data.system.useRedErrorEmbeds ? "Red" : generalConfig.data.mainColor) + instance.setColor(generalConfig.data.ticketSystem.useRedErrorEmbeds ? "Red" : generalConfig.data.mainColor) instance.setTitle(utilities.emojiTitle("❌",lang.getTranslation("errors.titles.notInGuild"))) instance.setAuthor(user.displayName,user.displayAvatarURL()) instance.setDescription(lang.getTranslationWithParams("errors.descriptions.notInGuild",[method])) @@ -327,19 +327,39 @@ const errorEmbeds = () => { //ERROR CHANNEL RENAME embeds.add(new api.ODEmbed("opendiscord:error-channel-rename")) embeds.get("opendiscord:error-channel-rename").workers.add( - new api.ODWorker("opendiscord:error-channel-rename",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:error-channel-rename",0,async (instance,params,origin) => { const {channel,user,originalName,newName} = params - const method = (source == "ticket-move" || source == "ticket-pin" || source == "ticket-rename" || source == "ticket-unpin" || source == "ticket-priority" || source == "ticket-transfer") ? source : getMethodFromSource(source) + const method = (origin == "ticket-move" || origin == "ticket-pin" || origin == "ticket-close" || origin == "ticket-reopen" || origin == "ticket-rename" || origin == "ticket-unpin" || origin == "ticket-priority" || origin == "ticket-transfer") ? origin : getMethodFromOrigin(origin) - instance.setColor(generalConfig.data.system.useRedErrorEmbeds ? "Red" : generalConfig.data.mainColor) + instance.setColor(generalConfig.data.ticketSystem.useRedErrorEmbeds ? "Red" : generalConfig.data.mainColor) instance.setTitle(utilities.emojiTitle("❌",lang.getTranslation("errors.titles.channelRename"))) instance.setAuthor(user.displayName,user.displayAvatarURL()) instance.setDescription(lang.getTranslation("errors.descriptions.channelRename")) instance.setFooter(lang.getTranslationWithParams("errors.descriptions.channelRenameSource",[method])) instance.addFields( - {name:lang.getTranslation("params.uppercase.originalName")+":",value:"```#"+originalName+"```",inline:false}, - {name:lang.getTranslation("params.uppercase.newName")+":",value:"```#"+newName+"```",inline:false} + {name:lang.getTranslation("params.uppercase.originalName")+":",value:"```#"+originalName+"```",inline:true}, + {name:lang.getTranslation("params.uppercase.newName")+":",value:"```#"+newName+"```",inline:true} + ) + }) + ) + + //ERROR CHANNEL CATEGORY + embeds.add(new api.ODEmbed("opendiscord:error-channel-category")) + embeds.get("opendiscord:error-channel-category").workers.add( + new api.ODWorker("opendiscord:error-channel-category",0,async (instance,params,origin) => { + const {channel,user,originalCategory,newCategory} = params + + const method = (origin == "ticket-create" || origin == "ticket-close" || origin == "ticket-reopen" || origin == "ticket-claim" || origin == "ticket-unclaim" || origin == "ticket-move") ? origin : getMethodFromOrigin(origin) + + instance.setColor(generalConfig.data.ticketSystem.useRedErrorEmbeds ? "Red" : generalConfig.data.mainColor) + instance.setTitle(utilities.emojiTitle("❌",lang.getTranslation("errors.titles.channelCategory"))) + instance.setAuthor(user.displayName,user.displayAvatarURL()) + instance.setDescription(lang.getTranslation("errors.descriptions.channelCategory")) + instance.setFooter(lang.getTranslationWithParams("errors.descriptions.channelRenameSource",[method])) + instance.addFields( + {name:lang.getTranslation("params.uppercase.originalCategory")+":",value:"```"+originalCategory+"```",inline:true}, + {name:lang.getTranslation("params.uppercase.newCategory")+":",value:"```"+newCategory+"```",inline:true} ) }) ) @@ -347,12 +367,12 @@ const errorEmbeds = () => { //ERROR TICKET BUSY embeds.add(new api.ODEmbed("opendiscord:error-ticket-busy")) embeds.get("opendiscord:error-ticket-busy").workers.add( - new api.ODWorker("opendiscord:error-ticket-busy",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:error-ticket-busy",0,async (instance,params,origin) => { const {user} = params - const method = getMethodFromSource(source) + const method = getMethodFromOrigin(origin) - instance.setColor(generalConfig.data.system.useRedErrorEmbeds ? "Red" : generalConfig.data.mainColor) + instance.setColor(generalConfig.data.ticketSystem.useRedErrorEmbeds ? "Red" : generalConfig.data.mainColor) instance.setTitle(utilities.emojiTitle("❌",lang.getTranslation("errors.titles.busy"))) instance.setAuthor(user.displayName,user.displayAvatarURL()) instance.setDescription(lang.getTranslationWithParams("errors.descriptions.busy",[method])) @@ -387,7 +407,7 @@ const statsEmbeds = () => { new api.ODWorker("opendiscord:stats-global",0,async (instance,params) => { const {guild,channel,user} = params - const scope = opendiscord.stats.get("opendiscord:global") + const scope = opendiscord.statistics.get("opendiscord:global") if (!scope) return const data = await scope.render("GLOBAL",guild,channel,user) @@ -397,7 +417,7 @@ const statsEmbeds = () => { if (opendiscord.permissions.hasPermissions("owner",await opendiscord.permissions.getPermissions(user,channel,guild))){ //show system data when owner or developer - const systemScope = opendiscord.stats.get("opendiscord:system") + const systemScope = opendiscord.statistics.get("opendiscord:system") if (!systemScope) return const systemData = await systemScope.render("GLOBAL",guild,channel,user) instance.addFields({name:systemScope.name,value:systemData,inline:false}) @@ -411,9 +431,9 @@ const statsEmbeds = () => { new api.ODWorker("opendiscord:stats-ticket",0,async (instance,params) => { const {guild,channel,user,scopeData} = params - const scope = opendiscord.stats.get("opendiscord:ticket") - const participantsScope = opendiscord.stats.get("opendiscord:participants") - const messagesScope = opendiscord.stats.get("opendiscord:messages") + const scope = opendiscord.statistics.get("opendiscord:ticket") + const participantsScope = opendiscord.statistics.get("opendiscord:participants") + const messagesScope = opendiscord.statistics.get("opendiscord:messages") if (!scope || !participantsScope || !messagesScope) return const data = await scope.render(scopeData.id.value,guild,channel,user) const participantsData = await participantsScope.render(scopeData.id.value,guild,channel,user) @@ -433,7 +453,7 @@ const statsEmbeds = () => { new api.ODWorker("opendiscord:stats-user",0,async (instance,params) => { const {guild,channel,user,scopeData} = params - const scope = opendiscord.stats.get("opendiscord:user") + const scope = opendiscord.statistics.get("opendiscord:user") if (!scope) return const data = await scope.render(scopeData.id,guild,channel,user) @@ -477,7 +497,7 @@ const statsEmbeds = () => { new api.ODWorker("opendiscord:stats-ticket-unknown",0,async (instance,params) => { const {user,id} = params - instance.setColor(generalConfig.data.system.useRedErrorEmbeds ? "Red" : generalConfig.data.mainColor) + instance.setColor(generalConfig.data.ticketSystem.useRedErrorEmbeds ? "Red" : generalConfig.data.mainColor) instance.setTitle(utilities.emojiTitle("❌",lang.getTranslation("errors.titles.unknownTicket"))) instance.setAuthor(user.displayName,user.displayAvatarURL()) instance.setDescription(lang.getTranslationWithParams("actions.descriptions.statsError",[discord.channelMention(id)])) @@ -510,8 +530,8 @@ const panelEmbeds = () => { instance.setDescription(embedOptions.description) } - if (panel.get("opendiscord:enable-max-tickets-warning-embed").value && generalConfig.data.system.limits.enabled){ - instance.setDescription(instance.data.description+"\n\n*"+lang.getTranslationWithParams("actions.descriptions.ticketMessageLimit",[generalConfig.data.system.limits.userMaximum.toString()])+"*") + if (panel.get("opendiscord:enable-max-tickets-warning-embed").value && generalConfig.data.ticketSystem.limits.enabled){ + instance.setDescription(instance.data.description+"\n\n*"+lang.getTranslationWithParams("actions.descriptions.ticketMessageLimit",[generalConfig.data.ticketSystem.limits.userMaximum.toString()])+"*") } if (panel.get("opendiscord:describe-options-in-embed-fields").value){ @@ -529,7 +549,7 @@ const ticketEmbeds = () => { //TICKET CREATED embeds.add(new api.ODEmbed("opendiscord:ticket-created")) embeds.get("opendiscord:ticket-created").workers.add( - new api.ODWorker("opendiscord:ticket-created",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:ticket-created",0,async (instance,params,origin) => { const {user} = params instance.setColor(generalConfig.data.mainColor) @@ -543,7 +563,7 @@ const ticketEmbeds = () => { //TICKET CREATED DM embeds.add(new api.ODEmbed("opendiscord:ticket-created-dm")) embeds.get("opendiscord:ticket-created-dm").workers.add( - new api.ODWorker("opendiscord:ticket-created-dm",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:ticket-created-dm",0,async (instance,params,origin) => { const {user,ticket} = params const embedOptions = ticket.option.get("opendiscord:dm-message-embed").value @@ -563,10 +583,10 @@ const ticketEmbeds = () => { //TICKET CREATED LOGS embeds.add(new api.ODEmbed("opendiscord:ticket-created-logs")) embeds.get("opendiscord:ticket-created-logs").workers.add( - new api.ODWorker("opendiscord:ticket-created-logs",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:ticket-created-logs",0,async (instance,params,origin) => { const {user,ticket} = params - const method = (source == "panel-button" || source == "panel-dropdown") ? lang.getTranslation("params.uppercase.panel") : (source == "slash" || source == "text") ? lang.getTranslation("params.uppercase.command") : lang.getTranslation("params.uppercase.system") + const method = (origin == "panel-button" || origin == "panel-dropdown") ? lang.getTranslation("params.uppercase.panel") : (origin == "slash" || origin == "text") ? lang.getTranslation("params.uppercase.command") : lang.getTranslation("params.uppercase.system") const blacklisted = opendiscord.blacklist.exists(user.id) ? lang.getTranslation("params.uppercase.true") : lang.getTranslation("params.uppercase.false") instance.setColor(generalConfig.data.mainColor) @@ -586,7 +606,7 @@ const ticketEmbeds = () => { //TICKET MESSAGE embeds.add(new api.ODEmbed("opendiscord:ticket-message")) embeds.get("opendiscord:ticket-message").workers.add( - new api.ODWorker("opendiscord:ticket-message",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:ticket-message",0,async (instance,params,origin) => { const {user,ticket} = params const embedOptions = ticket.option.get("opendiscord:ticket-message-embed").value @@ -599,14 +619,22 @@ const ticketEmbeds = () => { if (ticket.option.get("opendiscord:questions").value.length > 0){ //show config fields if mixing is allowed - if (generalConfig.data.system.displayFieldsWithQuestions) instance.addFields(...embedOptions.fields) + if (generalConfig.data.ticketSystem.displayFieldsWithQuestions) instance.addFields(...embedOptions.fields) const answers = ticket.get("opendiscord:answers").value - answers.forEach((answer) => { - if (!answer.value || answer.value.length == 0) return - if (generalConfig.data.system.questionFieldsInCodeBlock) instance.addFields({name:answer.name,value:"```"+answer.value+"```",inline:false}) - else instance.addFields({name:answer.name,value:answer.value,inline:false}) - }) + for (const answer of answers){ + if (answer.type == "file-upload"){ + //render file upload fields + if (!answer.files) continue + const renderedFiles = answer.files.map((file) => `- [${file.name}](${file.url})`).join("\n") + instance.addFields({name:answer.name,value:renderedFiles,inline:false}) + + }else if (typeof answer.value == "string" && answer.value.length > 0){ + //render other fields + if (generalConfig.data.ticketSystem.questionFieldsInCodeBlock) instance.addFields({name:answer.name,value:"```"+answer.value.slice(0,1024-6)+"```",inline:false}) + else instance.addFields({name:answer.name,value:answer.value,inline:false}) + } + } }else if (embedOptions.fields){ instance.setFields(embedOptions.fields) } @@ -625,162 +653,162 @@ const ticketEmbeds = () => { //TICKET CLOSED embeds.add(new api.ODEmbed("opendiscord:close-message")) embeds.get("opendiscord:close-message").workers.add( - new api.ODWorker("opendiscord:close-message",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:close-message",0,async (instance,params,origin) => { const {user,ticket,reason} = params instance.setAuthor(user.displayName,user.displayAvatarURL()) instance.setColor(generalConfig.data.mainColor) instance.setTitle(utilities.emojiTitle("🔒",lang.getTranslation("actions.titles.close"))) instance.setDescription(lang.getTranslation("actions.descriptions.close")) - if (reason || generalConfig.data.system.alwaysShowReason) instance.addFields({name:lang.getTranslation("params.uppercase.reason")+":",value:"```"+(reason ?? "/")+"```"}) + if (reason || generalConfig.data.ticketSystem.alwaysShowReason) instance.addFields({name:lang.getTranslation("params.uppercase.reason")+":",value:"```"+(reason ?? "/")+"```"}) }) ) //TICKET REOPENED embeds.add(new api.ODEmbed("opendiscord:reopen-message")) embeds.get("opendiscord:reopen-message").workers.add( - new api.ODWorker("opendiscord:reopen-message",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:reopen-message",0,async (instance,params,origin) => { const {user,ticket,reason} = params instance.setAuthor(user.displayName,user.displayAvatarURL()) instance.setColor(generalConfig.data.mainColor) instance.setTitle(utilities.emojiTitle("🔓",lang.getTranslation("actions.titles.reopen"))) instance.setDescription(lang.getTranslation("actions.descriptions.reopen")) - if (reason || generalConfig.data.system.alwaysShowReason) instance.addFields({name:lang.getTranslation("params.uppercase.reason")+":",value:"```"+(reason ?? "/")+"```"}) + if (reason || generalConfig.data.ticketSystem.alwaysShowReason) instance.addFields({name:lang.getTranslation("params.uppercase.reason")+":",value:"```"+(reason ?? "/")+"```"}) }) ) //TICKET DELETED embeds.add(new api.ODEmbed("opendiscord:delete-message")) embeds.get("opendiscord:delete-message").workers.add( - new api.ODWorker("opendiscord:delete-message",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:delete-message",0,async (instance,params,origin) => { const {user,ticket,reason} = params instance.setAuthor(user.displayName,user.displayAvatarURL()) instance.setColor(generalConfig.data.mainColor) instance.setTitle(utilities.emojiTitle("🗑️",lang.getTranslation("actions.titles.delete"))) instance.setDescription(lang.getTranslation("actions.descriptions.delete")) - if (reason || generalConfig.data.system.alwaysShowReason) instance.addFields({name:lang.getTranslation("params.uppercase.reason")+":",value:"```"+(reason ?? "/")+"```"}) + if (reason || generalConfig.data.ticketSystem.alwaysShowReason) instance.addFields({name:lang.getTranslation("params.uppercase.reason")+":",value:"```"+(reason ?? "/")+"```"}) }) ) //TICKET CLAIMED embeds.add(new api.ODEmbed("opendiscord:claim-message")) embeds.get("opendiscord:claim-message").workers.add( - new api.ODWorker("opendiscord:claim-message",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:claim-message",0,async (instance,params,origin) => { const {user,ticket,reason} = params instance.setAuthor(user.displayName,user.displayAvatarURL()) instance.setColor(generalConfig.data.mainColor) instance.setTitle(utilities.emojiTitle("👋",lang.getTranslation("actions.titles.claim"))) instance.setDescription(lang.getTranslation("actions.descriptions.claim")) - if (reason || generalConfig.data.system.alwaysShowReason) instance.addFields({name:lang.getTranslation("params.uppercase.reason")+":",value:"```"+(reason ?? "/")+"```"}) + if (reason || generalConfig.data.ticketSystem.alwaysShowReason) instance.addFields({name:lang.getTranslation("params.uppercase.reason")+":",value:"```"+(reason ?? "/")+"```"}) }) ) //TICKET UNCLAIMED embeds.add(new api.ODEmbed("opendiscord:unclaim-message")) embeds.get("opendiscord:unclaim-message").workers.add( - new api.ODWorker("opendiscord:unclaim-message",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:unclaim-message",0,async (instance,params,origin) => { const {user,ticket,reason} = params instance.setAuthor(user.displayName,user.displayAvatarURL()) instance.setColor(generalConfig.data.mainColor) instance.setTitle(utilities.emojiTitle("↩️",lang.getTranslation("actions.titles.unclaim"))) instance.setDescription(lang.getTranslation("actions.descriptions.unclaim")) - if (reason || generalConfig.data.system.alwaysShowReason) instance.addFields({name:lang.getTranslation("params.uppercase.reason")+":",value:"```"+(reason ?? "/")+"```"}) + if (reason || generalConfig.data.ticketSystem.alwaysShowReason) instance.addFields({name:lang.getTranslation("params.uppercase.reason")+":",value:"```"+(reason ?? "/")+"```"}) }) ) //TICKET PINNED embeds.add(new api.ODEmbed("opendiscord:pin-message")) embeds.get("opendiscord:pin-message").workers.add( - new api.ODWorker("opendiscord:pin-message",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:pin-message",0,async (instance,params,origin) => { const {user,ticket,reason} = params instance.setAuthor(user.displayName,user.displayAvatarURL()) instance.setColor(generalConfig.data.mainColor) - instance.setTitle(utilities.emojiTitle(generalConfig.data.system.pinEmoji,lang.getTranslation("actions.titles.pin"))) + instance.setTitle(utilities.emojiTitle(generalConfig.data.ticketSystem.pinEmoji,lang.getTranslation("actions.titles.pin"))) instance.setDescription(lang.getTranslation("actions.descriptions.pin")) - if (reason || generalConfig.data.system.alwaysShowReason) instance.addFields({name:lang.getTranslation("params.uppercase.reason")+":",value:"```"+(reason ?? "/")+"```"}) + if (reason || generalConfig.data.ticketSystem.alwaysShowReason) instance.addFields({name:lang.getTranslation("params.uppercase.reason")+":",value:"```"+(reason ?? "/")+"```"}) }) ) //TICKET UNPINNED embeds.add(new api.ODEmbed("opendiscord:unpin-message")) embeds.get("opendiscord:unpin-message").workers.add( - new api.ODWorker("opendiscord:unpin-message",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:unpin-message",0,async (instance,params,origin) => { const {user,ticket,reason} = params instance.setAuthor(user.displayName,user.displayAvatarURL()) instance.setColor(generalConfig.data.mainColor) - instance.setTitle(utilities.emojiTitle(generalConfig.data.system.pinEmoji,lang.getTranslation("actions.titles.unpin"))) + instance.setTitle(utilities.emojiTitle(generalConfig.data.ticketSystem.pinEmoji,lang.getTranslation("actions.titles.unpin"))) instance.setDescription(lang.getTranslation("actions.descriptions.unpin")) - if (reason || generalConfig.data.system.alwaysShowReason) instance.addFields({name:lang.getTranslation("params.uppercase.reason")+":",value:"```"+(reason ?? "/")+"```"}) + if (reason || generalConfig.data.ticketSystem.alwaysShowReason) instance.addFields({name:lang.getTranslation("params.uppercase.reason")+":",value:"```"+(reason ?? "/")+"```"}) }) ) //TICKET RENAMED embeds.add(new api.ODEmbed("opendiscord:rename-message")) embeds.get("opendiscord:rename-message").workers.add( - new api.ODWorker("opendiscord:rename-message",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:rename-message",0,async (instance,params,origin) => { const {user,ticket,reason,data} = params instance.setAuthor(user.displayName,user.displayAvatarURL()) instance.setColor(generalConfig.data.mainColor) instance.setTitle(utilities.emojiTitle("🔄",lang.getTranslation("actions.titles.rename"))) instance.setDescription(lang.getTranslationWithParams("actions.descriptions.rename",["`#"+data+"`"])) - if (reason || generalConfig.data.system.alwaysShowReason) instance.addFields({name:lang.getTranslation("params.uppercase.reason")+":",value:"```"+(reason ?? "/")+"```"}) + if (reason || generalConfig.data.ticketSystem.alwaysShowReason) instance.addFields({name:lang.getTranslation("params.uppercase.reason")+":",value:"```"+(reason ?? "/")+"```"}) }) ) //TICKET MOVED embeds.add(new api.ODEmbed("opendiscord:move-message")) embeds.get("opendiscord:move-message").workers.add( - new api.ODWorker("opendiscord:move-message",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:move-message",0,async (instance,params,origin) => { const {user,ticket,reason,data} = params instance.setAuthor(user.displayName,user.displayAvatarURL()) instance.setColor(generalConfig.data.mainColor) instance.setTitle(utilities.emojiTitle("🔀",lang.getTranslation("actions.titles.move"))) instance.setDescription(lang.getTranslationWithParams("actions.descriptions.move",["`"+data.get("opendiscord:name").value+"`"])) - if (reason || generalConfig.data.system.alwaysShowReason) instance.addFields({name:lang.getTranslation("params.uppercase.reason")+":",value:"```"+(reason ?? "/")+"```"}) + if (reason || generalConfig.data.ticketSystem.alwaysShowReason) instance.addFields({name:lang.getTranslation("params.uppercase.reason")+":",value:"```"+(reason ?? "/")+"```"}) }) ) //TICKET USER ADDED embeds.add(new api.ODEmbed("opendiscord:add-message")) embeds.get("opendiscord:add-message").workers.add( - new api.ODWorker("opendiscord:add-message",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:add-message",0,async (instance,params,origin) => { const {user,ticket,reason,data} = params instance.setAuthor(user.displayName,user.displayAvatarURL()) instance.setColor(generalConfig.data.mainColor) instance.setTitle(utilities.emojiTitle("👤",lang.getTranslation("actions.titles.add"))) instance.setDescription(lang.getTranslationWithParams("actions.descriptions.add",[discord.userMention(data.id)])) - if (reason || generalConfig.data.system.alwaysShowReason) instance.addFields({name:lang.getTranslation("params.uppercase.reason")+":",value:"```"+(reason ?? "/")+"```"}) + if (reason || generalConfig.data.ticketSystem.alwaysShowReason) instance.addFields({name:lang.getTranslation("params.uppercase.reason")+":",value:"```"+(reason ?? "/")+"```"}) }) ) //TICKET USER REMOVED embeds.add(new api.ODEmbed("opendiscord:remove-message")) embeds.get("opendiscord:remove-message").workers.add( - new api.ODWorker("opendiscord:remove-message",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:remove-message",0,async (instance,params,origin) => { const {user,ticket,reason,data} = params instance.setAuthor(user.displayName,user.displayAvatarURL()) instance.setColor(generalConfig.data.mainColor) instance.setTitle(utilities.emojiTitle("👤",lang.getTranslation("actions.titles.remove"))) instance.setDescription(lang.getTranslationWithParams("actions.descriptions.remove",[discord.userMention(data.id)])) - if (reason || generalConfig.data.system.alwaysShowReason) instance.addFields({name:lang.getTranslation("params.uppercase.reason")+":",value:"```"+(reason ?? "/")+"```"}) + if (reason || generalConfig.data.ticketSystem.alwaysShowReason) instance.addFields({name:lang.getTranslation("params.uppercase.reason")+":",value:"```"+(reason ?? "/")+"```"}) }) ) //TICKET ACTION DM embeds.add(new api.ODEmbed("opendiscord:ticket-action-dm")) embeds.get("opendiscord:ticket-action-dm").workers.add( - new api.ODWorker("opendiscord:ticket-action-dm",0,async (instance,params,source) => { - const {user,mode,ticket,reason,additionalData} = params + new api.ODWorker("opendiscord:ticket-action-dm",0,async (instance,params,origin) => { + const {user,mode,ticket,reason,additionalData,additionalData2} = params const channel = await opendiscord.tickets.getTicketChannel(ticket) instance.setColor(generalConfig.data.mainColor) @@ -804,10 +832,10 @@ const ticketEmbeds = () => { instance.setTitle(utilities.emojiTitle("↩️",lang.getTranslation("actions.titles.unclaim"))) instance.setDescription(lang.getTranslation("actions.logs.unclaimDm")) }else if (mode == "pin"){ - instance.setTitle(utilities.emojiTitle(generalConfig.data.system.pinEmoji,lang.getTranslation("actions.titles.pin"))) + instance.setTitle(utilities.emojiTitle(generalConfig.data.ticketSystem.pinEmoji,lang.getTranslation("actions.titles.pin"))) instance.setDescription(lang.getTranslation("actions.logs.pinDm")) }else if (mode == "unpin"){ - instance.setTitle(utilities.emojiTitle(generalConfig.data.system.pinEmoji,lang.getTranslation("actions.titles.unpin"))) + instance.setTitle(utilities.emojiTitle(generalConfig.data.ticketSystem.pinEmoji,lang.getTranslation("actions.titles.unpin"))) instance.setDescription(lang.getTranslation("actions.logs.unpinDm")) }else if (mode == "rename"){ instance.setTitle(utilities.emojiTitle("🔄",lang.getTranslation("actions.titles.rename"))) @@ -821,6 +849,15 @@ const ticketEmbeds = () => { }else if (mode == "remove"){ instance.setTitle(utilities.emojiTitle("👤",lang.getTranslation("actions.titles.remove"))) instance.setDescription(lang.getTranslationWithParams("actions.logs.removeDm",[(additionalData instanceof discord.User ? discord.userMention(additionalData.id) : "")])) + }else if (mode == "priority"){ + instance.setTitle(utilities.emojiTitle("🚨",lang.getTranslation("actions.titles.prioritySet"))) + instance.setDescription(lang.getTranslationWithParams("actions.logs.prioritySetDm",[(typeof additionalData === "string" ? "**"+additionalData+"**" : "****")])) + }else if (mode == "transfer"){ + instance.setTitle(utilities.emojiTitle("🚨",lang.getTranslation("actions.titles.transfer"))) + instance.setDescription(lang.getTranslationWithParams("actions.logs.transferDm",[(additionalData instanceof discord.User ? discord.userMention(additionalData.id) : ""),(additionalData2 instanceof discord.User ? discord.userMention(additionalData2.id) : "")])) + }else if (mode == "topic"){ + instance.setTitle(utilities.emojiTitle("ℹ️",lang.getTranslation("actions.titles.topicSet"))) + instance.setDescription(lang.getTranslationWithParams("actions.logs.topicSetDm",[(typeof additionalData === "string" ? "`"+additionalData+"`" : "``")])) } }) ) @@ -828,8 +865,8 @@ const ticketEmbeds = () => { //TICKET ACTION LOGS embeds.add(new api.ODEmbed("opendiscord:ticket-action-logs")) embeds.get("opendiscord:ticket-action-logs").workers.add( - new api.ODWorker("opendiscord:ticket-action-logs",0,async (instance,params,source) => { - const {user,mode,ticket,reason,additionalData} = params + new api.ODWorker("opendiscord:ticket-action-logs",0,async (instance,params,origin) => { + const {user,mode,ticket,reason,additionalData,additionalData2} = params const channel = await opendiscord.tickets.getTicketChannel(ticket) instance.setColor(generalConfig.data.mainColor) @@ -840,7 +877,7 @@ const ticketEmbeds = () => { {name:lang.getTranslation("params.uppercase.ticket")+":",value:"```#"+(channel ? channel.name : "")+"```",inline:false}, {name:lang.getTranslation("params.uppercase.option")+":",value:"```"+(ticket.option.get("opendiscord:name").value)+"```",inline:false}, ) - if (reason || generalConfig.data.system.alwaysShowReason) instance.addFields({name:lang.getTranslation("params.uppercase.reason")+":",value:"```"+(reason ?? "/")+"```",inline:false}) + if (reason || generalConfig.data.ticketSystem.alwaysShowReason) instance.addFields({name:lang.getTranslation("params.uppercase.reason")+":",value:"```"+(reason ?? "/")+"```",inline:false}) if (mode == "close"){ instance.setTitle(utilities.emojiTitle("🔒",lang.getTranslation("actions.titles.close"))) @@ -858,10 +895,10 @@ const ticketEmbeds = () => { instance.setTitle(utilities.emojiTitle("↩️",lang.getTranslation("actions.titles.unclaim"))) instance.setDescription(lang.getTranslationWithParams("actions.logs.unclaimLog",[discord.userMention(user.id)])) }else if (mode == "pin"){ - instance.setTitle(utilities.emojiTitle(generalConfig.data.system.pinEmoji,lang.getTranslation("actions.titles.pin"))) + instance.setTitle(utilities.emojiTitle(generalConfig.data.ticketSystem.pinEmoji,lang.getTranslation("actions.titles.pin"))) instance.setDescription(lang.getTranslationWithParams("actions.logs.pinLog",[discord.userMention(user.id)])) }else if (mode == "unpin"){ - instance.setTitle(utilities.emojiTitle(generalConfig.data.system.pinEmoji,lang.getTranslation("actions.titles.unpin"))) + instance.setTitle(utilities.emojiTitle(generalConfig.data.ticketSystem.pinEmoji,lang.getTranslation("actions.titles.unpin"))) instance.setDescription(lang.getTranslationWithParams("actions.logs.unpinLog",[discord.userMention(user.id)])) }else if (mode == "rename"){ instance.setTitle(utilities.emojiTitle("🔄",lang.getTranslation("actions.titles.rename"))) @@ -875,6 +912,15 @@ const ticketEmbeds = () => { }else if (mode == "remove"){ instance.setTitle(utilities.emojiTitle("👤",lang.getTranslation("actions.titles.remove"))) instance.setDescription(lang.getTranslationWithParams("actions.logs.removeLog",[(additionalData instanceof discord.User ? discord.userMention(additionalData.id) : ""),discord.userMention(user.id)])) + }else if (mode == "priority"){ + instance.setTitle(utilities.emojiTitle("🚨",lang.getTranslation("actions.titles.prioritySet"))) + instance.setDescription(lang.getTranslationWithParams("actions.logs.prioritySetLog",[(typeof additionalData === "string" ? "**"+additionalData+"**" : "****"),discord.userMention(user.id)])) + }else if (mode == "transfer"){ + instance.setTitle(utilities.emojiTitle("🚨",lang.getTranslation("actions.titles.transfer"))) + instance.setDescription(lang.getTranslationWithParams("actions.logs.transferLog",[(additionalData instanceof discord.User ? discord.userMention(additionalData.id) : ""),(additionalData2 instanceof discord.User ? discord.userMention(additionalData2.id) : ""),discord.userMention(user.id)])) + }else if (mode == "topic"){ + instance.setTitle(utilities.emojiTitle("ℹ️",lang.getTranslation("actions.titles.topicSet"))) + instance.setDescription(lang.getTranslationWithParams("actions.logs.topicSetLog",[(typeof additionalData === "string" ? "`"+additionalData+"`" : "``"),discord.userMention(user.id)])) } }) ) @@ -884,7 +930,7 @@ const blacklistEmbeds = () => { //BLACKLIST VIEW embeds.add(new api.ODEmbed("opendiscord:blacklist-view")) embeds.get("opendiscord:blacklist-view").workers.add( - new api.ODWorker("opendiscord:blacklist-view",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:blacklist-view",0,async (instance,params,origin) => { const {user} = params const renderedUsers: string[] = [] @@ -904,7 +950,7 @@ const blacklistEmbeds = () => { //BLACKLIST GET embeds.add(new api.ODEmbed("opendiscord:blacklist-get")) embeds.get("opendiscord:blacklist-get").workers.add( - new api.ODWorker("opendiscord:blacklist-get",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:blacklist-get",0,async (instance,params,origin) => { const {user,data} = params const blacklist = opendiscord.blacklist.get(data.id) @@ -914,7 +960,7 @@ const blacklistEmbeds = () => { if (blacklist){ instance.setDescription(lang.getTranslationWithParams("actions.descriptions.blacklistGetSuccess",[discord.userMention(data.id)])) - if (blacklist.reason || generalConfig.data.system.alwaysShowReason) instance.addFields({name:lang.getTranslation("params.uppercase.reason")+":",value:"```"+(blacklist.reason ?? "/")+"```"}) + if (blacklist.reason || generalConfig.data.ticketSystem.alwaysShowReason) instance.addFields({name:lang.getTranslation("params.uppercase.reason")+":",value:"```"+(blacklist.reason ?? "/")+"```"}) }else instance.setDescription("*"+lang.getTranslationWithParams("actions.descriptions.blacklistGetEmpty",[discord.userMention(data.id)])+"*") }) @@ -923,35 +969,35 @@ const blacklistEmbeds = () => { //BLACKLIST ADD embeds.add(new api.ODEmbed("opendiscord:blacklist-add")) embeds.get("opendiscord:blacklist-add").workers.add( - new api.ODWorker("opendiscord:blacklist-add",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:blacklist-add",0,async (instance,params,origin) => { const {user,data,reason} = params instance.setAuthor(user.displayName,user.displayAvatarURL()) instance.setColor(generalConfig.data.mainColor) instance.setTitle(utilities.emojiTitle("🚫",lang.getTranslation("actions.titles.blacklistAdd"))) instance.setDescription(lang.getTranslationWithParams("actions.descriptions.blacklistAdd",[discord.userMention(data.id)])) - if (reason || generalConfig.data.system.alwaysShowReason) instance.addFields({name:lang.getTranslation("params.uppercase.reason")+":",value:"```"+(reason ?? "/")+"```"}) + if (reason || generalConfig.data.ticketSystem.alwaysShowReason) instance.addFields({name:lang.getTranslation("params.uppercase.reason")+":",value:"```"+(reason ?? "/")+"```"}) }) ) //BLACKLIST REMOVE embeds.add(new api.ODEmbed("opendiscord:blacklist-remove")) embeds.get("opendiscord:blacklist-remove").workers.add( - new api.ODWorker("opendiscord:blacklist-remove",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:blacklist-remove",0,async (instance,params,origin) => { const {user,data,reason} = params instance.setAuthor(user.displayName,user.displayAvatarURL()) instance.setColor(generalConfig.data.mainColor) instance.setTitle(utilities.emojiTitle("🆓",lang.getTranslation("actions.titles.blacklistRemove"))) instance.setDescription(lang.getTranslationWithParams("actions.descriptions.blacklistRemove",[discord.userMention(data.id)])) - if (reason || generalConfig.data.system.alwaysShowReason) instance.addFields({name:lang.getTranslation("params.uppercase.reason")+":",value:"```"+(reason ?? "/")+"```"}) + if (reason || generalConfig.data.ticketSystem.alwaysShowReason) instance.addFields({name:lang.getTranslation("params.uppercase.reason")+":",value:"```"+(reason ?? "/")+"```"}) }) ) //BLACKLIST DM embeds.add(new api.ODEmbed("opendiscord:blacklist-dm")) embeds.get("opendiscord:blacklist-dm").workers.add( - new api.ODWorker("opendiscord:blacklist-dm",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:blacklist-dm",0,async (instance,params,origin) => { const {user,mode,data,reason} = params const title = (mode == "add") ? lang.getTranslation("actions.titles.blacklistAddDm") : lang.getTranslation("actions.titles.blacklistRemoveDm") @@ -961,14 +1007,14 @@ const blacklistEmbeds = () => { instance.setTitle(utilities.emojiTitle((mode == "add") ? "🚫" : "🆓",title)) instance.setTimestamp(new Date()) instance.setDescription(text) - if (reason || generalConfig.data.system.alwaysShowReason) instance.addFields({name:lang.getTranslation("params.uppercase.reason")+":",value:"```"+(reason ?? "/")+"```"}) + if (reason || generalConfig.data.ticketSystem.alwaysShowReason) instance.addFields({name:lang.getTranslation("params.uppercase.reason")+":",value:"```"+(reason ?? "/")+"```"}) }) ) //BLACKLIST LOGS embeds.add(new api.ODEmbed("opendiscord:blacklist-logs")) embeds.get("opendiscord:blacklist-logs").workers.add( - new api.ODWorker("opendiscord:blacklist-logs",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:blacklist-logs",0,async (instance,params,origin) => { const {user,mode,data,reason} = params const title = (mode == "add") ? lang.getTranslation("actions.titles.blacklistAdd") : lang.getTranslation("actions.titles.blacklistRemove") @@ -980,7 +1026,7 @@ const blacklistEmbeds = () => { instance.setAuthor(user.displayName,user.displayAvatarURL()) instance.setTimestamp(new Date()) instance.setDescription(text) - if (reason || generalConfig.data.system.alwaysShowReason) instance.addFields({name:lang.getTranslation("params.uppercase.reason")+":",value:"```"+(reason ?? "/")+"```"}) + if (reason || generalConfig.data.ticketSystem.alwaysShowReason) instance.addFields({name:lang.getTranslation("params.uppercase.reason")+":",value:"```"+(reason ?? "/")+"```"}) }) ) } @@ -989,7 +1035,7 @@ const transcriptEmbeds = () => { //TRANSCRIPT TEXT READY embeds.add(new api.ODEmbed("opendiscord:transcript-text-ready")) embeds.get("opendiscord:transcript-text-ready").workers.add( - new api.ODWorker("opendiscord:transcript-text-ready",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:transcript-text-ready",0,async (instance,params,origin) => { const {guild,channel,user,ticket,compiler} = params const transcriptConfig = opendiscord.configs.get("opendiscord:transcripts") @@ -1007,11 +1053,11 @@ const transcriptEmbeds = () => { }catch{} } - if (source == "channel") instance.setDescription(lang.getTranslationWithParams("transcripts.success.createdChannel",[lang.getTranslation("params.lowercase.text")])) - else if (source == "creator-dm") instance.setDescription(lang.getTranslationWithParams("transcripts.success.createdCreator",[lang.getTranslation("params.lowercase.text")])) - else if (source == "participant-dm") instance.setDescription(lang.getTranslationWithParams("transcripts.success.createdParticipant",[lang.getTranslation("params.lowercase.text")])) - else if (source == "active-admin-dm") instance.setDescription(lang.getTranslationWithParams("transcripts.success.createdActiveAdmin",[lang.getTranslation("params.lowercase.text")])) - else if (source == "every-admin-dm") instance.setDescription(lang.getTranslationWithParams("transcripts.success.createdEveryAdmin",[lang.getTranslation("params.lowercase.text")])) + if (origin == "channel") instance.setDescription(lang.getTranslationWithParams("transcripts.success.createdChannel",[lang.getTranslation("params.lowercase.text")])) + else if (origin == "creator-dm") instance.setDescription(lang.getTranslationWithParams("transcripts.success.createdCreator",[lang.getTranslation("params.lowercase.text")])) + else if (origin == "participant-dm") instance.setDescription(lang.getTranslationWithParams("transcripts.success.createdParticipant",[lang.getTranslation("params.lowercase.text")])) + else if (origin == "active-admin-dm") instance.setDescription(lang.getTranslationWithParams("transcripts.success.createdActiveAdmin",[lang.getTranslation("params.lowercase.text")])) + else if (origin == "every-admin-dm") instance.setDescription(lang.getTranslationWithParams("transcripts.success.createdEveryAdmin",[lang.getTranslation("params.lowercase.text")])) else instance.setDescription(lang.getTranslationWithParams("transcripts.success.createdOther",[lang.getTranslation("params.lowercase.text")])) }) ) @@ -1019,7 +1065,7 @@ const transcriptEmbeds = () => { //TRANSCRIPT HTML READY embeds.add(new api.ODEmbed("opendiscord:transcript-html-ready")) embeds.get("opendiscord:transcript-html-ready").workers.add( - new api.ODWorker("opendiscord:transcript-html-ready",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:transcript-html-ready",0,async (instance,params,origin) => { const {guild,channel,user,ticket,compiler,result} = params const transcriptConfig = opendiscord.configs.get("opendiscord:transcripts") @@ -1038,11 +1084,11 @@ const transcriptEmbeds = () => { }catch{} } - if (source == "channel") instance.setDescription(lang.getTranslationWithParams("transcripts.success.createdChannel",[lang.getTranslation("params.lowercase.html")])) - else if (source == "creator-dm") instance.setDescription(lang.getTranslationWithParams("transcripts.success.createdCreator",[lang.getTranslation("params.lowercase.html")])) - else if (source == "participant-dm") instance.setDescription(lang.getTranslationWithParams("transcripts.success.createdParticipant",[lang.getTranslation("params.lowercase.html")])) - else if (source == "active-admin-dm") instance.setDescription(lang.getTranslationWithParams("transcripts.success.createdActiveAdmin",[lang.getTranslation("params.lowercase.html")])) - else if (source == "every-admin-dm") instance.setDescription(lang.getTranslationWithParams("transcripts.success.createdEveryAdmin",[lang.getTranslation("params.lowercase.html")])) + if (origin == "channel") instance.setDescription(lang.getTranslationWithParams("transcripts.success.createdChannel",[lang.getTranslation("params.lowercase.html")])) + else if (origin == "creator-dm") instance.setDescription(lang.getTranslationWithParams("transcripts.success.createdCreator",[lang.getTranslation("params.lowercase.html")])) + else if (origin == "participant-dm") instance.setDescription(lang.getTranslationWithParams("transcripts.success.createdParticipant",[lang.getTranslation("params.lowercase.html")])) + else if (origin == "active-admin-dm") instance.setDescription(lang.getTranslationWithParams("transcripts.success.createdActiveAdmin",[lang.getTranslation("params.lowercase.html")])) + else if (origin == "every-admin-dm") instance.setDescription(lang.getTranslationWithParams("transcripts.success.createdEveryAdmin",[lang.getTranslation("params.lowercase.html")])) else instance.setDescription(lang.getTranslationWithParams("transcripts.success.createdOther",[lang.getTranslation("params.lowercase.html")])) }) ) @@ -1050,7 +1096,7 @@ const transcriptEmbeds = () => { //TRANSCRIPT HTML PROGRESS embeds.add(new api.ODEmbed("opendiscord:transcript-html-progress")) embeds.get("opendiscord:transcript-html-progress").workers.add( - new api.ODWorker("opendiscord:transcript-html-progress",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:transcript-html-progress",0,async (instance,params,origin) => { const {guild,channel,user,ticket,compiler,remaining} = params const remainingDate = new Date(new Date().getTime()+remaining) @@ -1076,15 +1122,34 @@ const transcriptEmbeds = () => { //TRANSCRIPT ERROR embeds.add(new api.ODEmbed("opendiscord:transcript-error")) embeds.get("opendiscord:transcript-error").workers.add( - new api.ODWorker("opendiscord:transcript-error",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:transcript-error",0,async (instance,params,origin) => { const {guild,channel,user,ticket,compiler,reason} = params - instance.setColor(generalConfig.data.system.useRedErrorEmbeds ? "Red" : generalConfig.data.mainColor) + instance.setColor(generalConfig.data.ticketSystem.useRedErrorEmbeds ? "Red" : generalConfig.data.mainColor) instance.setTitle(utilities.emojiTitle("❌",lang.getTranslation("transcripts.errors.title"))) instance.setTimestamp(new Date()) instance.setDescription(lang.getTranslation("transcripts.errors.error")) instance.setFooter(lang.getTranslation("errors.descriptions.askForInfo")) - if (reason || generalConfig.data.system.alwaysShowReason) instance.addFields({name:lang.getTranslation("params.uppercase.reason")+":",value:"```"+(reason ?? "/")+"```"}) + if (reason || generalConfig.data.ticketSystem.alwaysShowReason) instance.addFields({name:lang.getTranslation("params.uppercase.reason")+":",value:"```"+(reason ?? "/")+"```"}) + }) + ) + + //TRANSCRIPT HISTORY + embeds.add(new api.ODEmbed("opendiscord:transcript-history")) + embeds.get("opendiscord:transcript-history").workers.add( + new api.ODWorker("opendiscord:transcript-history",0,async (instance,params,origin) => { + const {transcriptUser,transcriptList} = params + + //transcript history only supports URL (html) transcripts at the moment + const filteredList = transcriptList.filter((t) => t.transcriptType == "remoteUrl") + const renderedList = filteredList.map((t) => "- ["+t.ticketName+"]("+t.transcriptUrl+") ("+discord.time(new Date(t.ticketDeletedDate ?? 0),"R")+")").join("\n") + const contents = (transcriptList.length < 1) ? lang.getTranslation("transcripts.errors.noHistory") : ((filteredList.length > 0) ? renderedList : "⚠️ "+lang.getTranslation("transcripts.errors.historyNotSupported")) + + instance.setAuthor(transcriptUser.displayName,transcriptUser.displayAvatarURL()) + instance.setThumbnail(transcriptUser.displayAvatarURL()) + instance.setColor(generalConfig.data.mainColor) + instance.setTitle(utilities.emojiTitle("📄",lang.getTranslation("actions.titles.transcripts"))) + instance.setDescription(contents) }) ) } @@ -1093,7 +1158,7 @@ const roleEmbeds = () => { //REACTION ROLE embeds.add(new api.ODEmbed("opendiscord:reaction-role")) embeds.get("opendiscord:reaction-role").workers.add( - new api.ODWorker("opendiscord:reaction-role",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:reaction-role",0,async (instance,params,origin) => { const {guild,user,role,result} = params instance.setColor(generalConfig.data.mainColor) @@ -1117,7 +1182,7 @@ const roleEmbeds = () => { //REACTION ROLE DM embeds.add(new api.ODEmbed("opendiscord:reaction-role-dm")) embeds.get("opendiscord:reaction-role-dm").workers.add( - new api.ODWorker("opendiscord:reaction-role-dm",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:reaction-role-dm",0,async (instance,params,origin) => { const {guild,user,role,result} = params instance.setColor(generalConfig.data.mainColor) @@ -1143,7 +1208,7 @@ const roleEmbeds = () => { //REACTION ROLE LOGS embeds.add(new api.ODEmbed("opendiscord:reaction-role-logs")) embeds.get("opendiscord:reaction-role-logs").workers.add( - new api.ODWorker("opendiscord:reaction-role-logs",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:reaction-role-logs",0,async (instance,params,origin) => { const {guild,user,role,result} = params instance.setColor(generalConfig.data.mainColor) @@ -1171,7 +1236,7 @@ const clearEmbeds = () => { //CLEAR VERIFY MESSAGE embeds.add(new api.ODEmbed("opendiscord:clear-verify-message")) embeds.get("opendiscord:clear-verify-message").workers.add( - new api.ODWorker("opendiscord:clear-verify-message",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:clear-verify-message",0,async (instance,params,origin) => { const {guild,channel,user,filter,list} = params instance.setColor(generalConfig.data.mainColor) @@ -1189,7 +1254,7 @@ const clearEmbeds = () => { //CLEAR MESSAGE embeds.add(new api.ODEmbed("opendiscord:clear-message")) embeds.get("opendiscord:clear-message").workers.add( - new api.ODWorker("opendiscord:clear-message",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:clear-message",0,async (instance,params,origin) => { const {guild,channel,user,filter,list} = params instance.setColor(generalConfig.data.mainColor) @@ -1206,7 +1271,7 @@ const clearEmbeds = () => { //CLEAR LOGS embeds.add(new api.ODEmbed("opendiscord:clear-logs")) embeds.get("opendiscord:clear-logs").workers.add( - new api.ODWorker("opendiscord:clear-logs",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:clear-logs",0,async (instance,params,origin) => { const {guild,channel,user,filter,list} = params instance.setColor(generalConfig.data.mainColor) @@ -1226,10 +1291,10 @@ const autoEmbeds = () => { //AUTOCLOSE MESSAGE embeds.add(new api.ODEmbed("opendiscord:autoclose-message")) embeds.get("opendiscord:autoclose-message").workers.add( - new api.ODWorker("opendiscord:autoclose-message",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:autoclose-message",0,async (instance,params,origin) => { const {user,ticket} = params const hours: number = ticket.get("opendiscord:autoclose-hours").value - const description = (source == "leave") ? lang.getTranslation("actions.descriptions.autocloseLeave") : lang.getTranslationWithParams("actions.descriptions.autocloseTimeout",[hours.toString()]) + const description = (origin == "leave") ? lang.getTranslation("actions.descriptions.autocloseLeave") : lang.getTranslationWithParams("actions.descriptions.autocloseTimeout",[hours.toString()]) instance.setAuthor(user.displayName,user.displayAvatarURL()) instance.setColor(generalConfig.data.mainColor) @@ -1242,10 +1307,10 @@ const autoEmbeds = () => { //AUTODELETE MESSAGE embeds.add(new api.ODEmbed("opendiscord:autodelete-message")) embeds.get("opendiscord:autodelete-message").workers.add( - new api.ODWorker("opendiscord:autodelete-message",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:autodelete-message",0,async (instance,params,origin) => { const {user,ticket} = params const days: number = ticket.get("opendiscord:autodelete-days").value - const description = (source == "leave") ? lang.getTranslation("actions.descriptions.autodeleteLeave") : lang.getTranslationWithParams("actions.descriptions.autodeleteTimeout",[days.toString()]) + const description = (origin == "leave") ? lang.getTranslation("actions.descriptions.autodeleteLeave") : lang.getTranslationWithParams("actions.descriptions.autodeleteTimeout",[days.toString()]) instance.setAuthor(user.displayName,user.displayAvatarURL()) instance.setColor(generalConfig.data.mainColor) @@ -1258,56 +1323,56 @@ const autoEmbeds = () => { //AUTOCLOSE ENABLE embeds.add(new api.ODEmbed("opendiscord:autoclose-enable")) embeds.get("opendiscord:autoclose-enable").workers.add( - new api.ODWorker("opendiscord:autoclose-enable",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:autoclose-enable",0,async (instance,params,origin) => { const {user,ticket,reason,time} = params instance.setAuthor(user.displayName,user.displayAvatarURL()) instance.setColor(generalConfig.data.mainColor) instance.setTitle(utilities.emojiTitle("⏱️",lang.getTranslation("actions.titles.autocloseEnabled"))) instance.setDescription(lang.getTranslationWithParams("actions.descriptions.autocloseEnabled",[time.toString()])) - if (reason || generalConfig.data.system.alwaysShowReason) instance.addFields({name:lang.getTranslation("params.uppercase.reason")+":",value:"```"+(reason ?? "/")+"```"}) + if (reason || generalConfig.data.ticketSystem.alwaysShowReason) instance.addFields({name:lang.getTranslation("params.uppercase.reason")+":",value:"```"+(reason ?? "/")+"```"}) }) ) //AUTODELETE ENABLE embeds.add(new api.ODEmbed("opendiscord:autodelete-enable")) embeds.get("opendiscord:autodelete-enable").workers.add( - new api.ODWorker("opendiscord:autodelete-enable",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:autodelete-enable",0,async (instance,params,origin) => { const {user,ticket,reason,time} = params instance.setAuthor(user.displayName,user.displayAvatarURL()) instance.setColor(generalConfig.data.mainColor) instance.setTitle(utilities.emojiTitle("⏱️",lang.getTranslation("actions.titles.autodeleteEnabled"))) instance.setDescription(lang.getTranslationWithParams("actions.descriptions.autodeleteEnabled",[time.toString()])) - if (reason || generalConfig.data.system.alwaysShowReason) instance.addFields({name:lang.getTranslation("params.uppercase.reason")+":",value:"```"+(reason ?? "/")+"```"}) + if (reason || generalConfig.data.ticketSystem.alwaysShowReason) instance.addFields({name:lang.getTranslation("params.uppercase.reason")+":",value:"```"+(reason ?? "/")+"```"}) }) ) //AUTOCLOSE DISABLE embeds.add(new api.ODEmbed("opendiscord:autoclose-disable")) embeds.get("opendiscord:autoclose-disable").workers.add( - new api.ODWorker("opendiscord:autoclose-disable",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:autoclose-disable",0,async (instance,params,origin) => { const {user,ticket,reason} = params instance.setAuthor(user.displayName,user.displayAvatarURL()) instance.setColor(generalConfig.data.mainColor) instance.setTitle(utilities.emojiTitle("⏱️",lang.getTranslation("actions.titles.autocloseDisabled"))) instance.setDescription(lang.getTranslation("actions.descriptions.autocloseDisabled")) - if (reason || generalConfig.data.system.alwaysShowReason) instance.addFields({name:lang.getTranslation("params.uppercase.reason")+":",value:"```"+(reason ?? "/")+"```"}) + if (reason || generalConfig.data.ticketSystem.alwaysShowReason) instance.addFields({name:lang.getTranslation("params.uppercase.reason")+":",value:"```"+(reason ?? "/")+"```"}) }) ) //AUTODELETE DISABLE embeds.add(new api.ODEmbed("opendiscord:autodelete-disable")) embeds.get("opendiscord:autodelete-disable").workers.add( - new api.ODWorker("opendiscord:autodelete-disable",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:autodelete-disable",0,async (instance,params,origin) => { const {user,ticket,reason} = params instance.setAuthor(user.displayName,user.displayAvatarURL()) instance.setColor(generalConfig.data.mainColor) instance.setTitle(utilities.emojiTitle("⏱️",lang.getTranslation("actions.titles.autodeleteDisabled"))) instance.setDescription(lang.getTranslation("actions.descriptions.autodeleteDisabled")) - if (reason || generalConfig.data.system.alwaysShowReason) instance.addFields({name:lang.getTranslation("params.uppercase.reason")+":",value:"```"+(reason ?? "/")+"```"}) + if (reason || generalConfig.data.ticketSystem.alwaysShowReason) instance.addFields({name:lang.getTranslation("params.uppercase.reason")+":",value:"```"+(reason ?? "/")+"```"}) }) ) } @@ -1316,7 +1381,7 @@ const extraEmbeds = () => { //TOPIC SET embeds.add(new api.ODEmbed("opendiscord:topic-set")) embeds.get("opendiscord:topic-set").workers.add( - new api.ODWorker("opendiscord:topic-set",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:topic-set",0,async (instance,params,origin) => { const {user,topic} = params instance.setAuthor(user.displayName,user.displayAvatarURL()) @@ -1330,21 +1395,21 @@ const extraEmbeds = () => { //PRIORITY SET embeds.add(new api.ODEmbed("opendiscord:priority-set")) embeds.get("opendiscord:priority-set").workers.add( - new api.ODWorker("opendiscord:priority-set",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:priority-set",0,async (instance,params,origin) => { const {user,priority,reason} = params instance.setAuthor(user.displayName,user.displayAvatarURL()) instance.setColor(generalConfig.data.mainColor) instance.setTitle(utilities.emojiTitle("🚨",lang.getTranslation("actions.titles.prioritySet"))) instance.setDescription(lang.getTranslationWithParams("actions.descriptions.prioritySet",["**"+priority.renderDisplayName()+"**",discord.userMention(user.id)])) - if (reason || generalConfig.data.system.alwaysShowReason) instance.addFields({name:lang.getTranslation("params.uppercase.reason")+":",value:"```"+(reason ?? "/")+"```"}) + if (reason || generalConfig.data.ticketSystem.alwaysShowReason) instance.addFields({name:lang.getTranslation("params.uppercase.reason")+":",value:"```"+(reason ?? "/")+"```"}) }) ) //PRIORITY GET embeds.add(new api.ODEmbed("opendiscord:priority-get")) embeds.get("opendiscord:priority-get").workers.add( - new api.ODWorker("opendiscord:priority-get",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:priority-get",0,async (instance,params,origin) => { const {user,priority} = params instance.setAuthor(user.displayName,user.displayAvatarURL()) @@ -1357,14 +1422,14 @@ const extraEmbeds = () => { //TRANSFER MESSAGE embeds.add(new api.ODEmbed("opendiscord:transfer-message")) embeds.get("opendiscord:transfer-message").workers.add( - new api.ODWorker("opendiscord:transfer-message",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:transfer-message",0,async (instance,params,origin) => { const {user,oldCreator,newCreator,reason} = params instance.setAuthor(user.displayName,user.displayAvatarURL()) instance.setColor(generalConfig.data.mainColor) instance.setTitle(utilities.emojiTitle("🔀",lang.getTranslation("actions.titles.transfer"))) instance.setDescription(lang.getTranslationWithParams("actions.descriptions.transfer",[discord.userMention(oldCreator.id),discord.userMention(newCreator.id),discord.userMention(user.id)])) - if (reason || generalConfig.data.system.alwaysShowReason) instance.addFields({name:lang.getTranslation("params.uppercase.reason")+":",value:"```"+(reason ?? "/")+"```"}) + if (reason || generalConfig.data.ticketSystem.alwaysShowReason) instance.addFields({name:lang.getTranslation("params.uppercase.reason")+":",value:"```"+(reason ?? "/")+"```"}) }) ) } \ No newline at end of file diff --git a/src/builders/files.ts b/src/builders/files.ts index 9a30548..252a719 100644 --- a/src/builders/files.ts +++ b/src/builders/files.ts @@ -1,14 +1,14 @@ /////////////////////////////////////// //FILE BUILDERS /////////////////////////////////////// -import {opendiscord, api, utilities} from "../index" +import {opendiscord, api, utilities} from "../index.js" import * as discord from "discord.js" const files = opendiscord.builders.files const lang = opendiscord.languages const transcriptConfig = opendiscord.configs.get("opendiscord:transcripts") -export const registerAllFiles = async () => { +export async function registerAllFiles(){ transcriptFiles() } @@ -17,7 +17,7 @@ const transcriptFiles = () => { //TEXT TRANSCRIPT files.add(new api.ODFile("opendiscord:text-transcript")) files.get("opendiscord:text-transcript").workers.add( - new api.ODWorker("opendiscord:text-transcript",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:text-transcript",0,async (instance,params,origin) => { const {guild,channel,user,ticket,compiler,result} = params const fileMode = transcriptConfig.data.textTranscriptStyle.fileMode diff --git a/src/builders/messages.ts b/src/builders/messages.ts index 0b89c1b..fee0f36 100644 --- a/src/builders/messages.ts +++ b/src/builders/messages.ts @@ -1,7 +1,7 @@ /////////////////////////////////////// //MESSAGE BUILDERS /////////////////////////////////////// -import {opendiscord, api, utilities} from "../index" +import {opendiscord, api, utilities} from "../index.js" import * as discord from "discord.js" const messages = opendiscord.builders.messages @@ -12,8 +12,7 @@ const embeds = opendiscord.builders.embeds const lang = opendiscord.languages const generalConfig = opendiscord.configs.get("opendiscord:general") -export const registerAllMessages = async () => { - verifyBarMessages() +export async function registerAllMessages(){ errorMessages() helpMenuMessages() statsMessages() @@ -27,314 +26,13 @@ export const registerAllMessages = async () => { extraMessages() } -const verifyBarMessages = () => { - //VERIFYBAR TICKET MESSAGE - messages.add(new api.ODMessage("opendiscord:verifybar-ticket-message")) - messages.get("opendiscord:verifybar-ticket-message").workers.add( - new api.ODWorker("opendiscord:verifybar-ticket-message",0,async (instance,params,source) => { - const {guild,channel,user,verifybar} = params - if (!guild){ - instance.setContent("ODError: Not In Guild => `opendiscord:verifybar-ticket-message`") - return - } - const ticket = opendiscord.tickets.get(channel.id) - if (!ticket){ - instance.setContent("ODError: Unknown Ticket => `opendiscord:verifybar-ticket-message`") - return - } - const option = ticket.option - - //add pings - const pingOptions = option.get("opendiscord:ticket-message-ping").value - const pings: string[] = [] - const creator = ticket.get("opendiscord:opened-by").value - if (creator) pings.push(discord.userMention(creator)) - if (pingOptions["@everyone"]) pings.push("@everyone") - if (pingOptions["@here"]) pings.push("@here") - pingOptions.custom.forEach((ping) => pings.push(discord.roleMention(ping))) - const pingText = (pings.length > 0) ? pings.join(" ")+"\n" : "" - - //add text - const text = option.get("opendiscord:ticket-message-text").value - if (text !== "") instance.setContent(pingText+text) - else instance.setContent(pingText) - - //add embed - if (option.get("opendiscord:ticket-message-embed").value.enabled) instance.addEmbed(await embeds.getSafe("opendiscord:ticket-message").build("other",{guild,channel,user,ticket})) - - //add verifybar components - if (verifybar.id.value == "opendiscord:claim-ticket-ticket-message"){ - instance.addComponent(await buttons.getSafe("opendiscord:verifybar-success").build("verifybar",{guild,channel,user,verifybar})) - instance.addComponent(await buttons.getSafe("opendiscord:verifybar-failure").build("verifybar",{guild,channel,user,verifybar})) - if (generalConfig.data.system.enableTicketActionWithReason) instance.addComponent(await buttons.getSafe("opendiscord:verifybar-success").build("verifybar",{guild,channel,user,verifybar,customData:"reason",customEmoji:"✏️",customLabel:lang.getTranslation("actions.buttons.withReason"),customColor:"blue"})) - - }else if (verifybar.id.value == "opendiscord:unclaim-ticket-ticket-message"){ - instance.addComponent(await buttons.getSafe("opendiscord:verifybar-success").build("verifybar",{guild,channel,user,verifybar})) - instance.addComponent(await buttons.getSafe("opendiscord:verifybar-failure").build("verifybar",{guild,channel,user,verifybar})) - if (generalConfig.data.system.enableTicketActionWithReason) instance.addComponent(await buttons.getSafe("opendiscord:verifybar-success").build("verifybar",{guild,channel,user,verifybar,customData:"reason",customEmoji:"✏️",customLabel:lang.getTranslation("actions.buttons.withReason"),customColor:"blue"})) - - }else if (verifybar.id.value == "opendiscord:pin-ticket-ticket-message"){ - instance.addComponent(await buttons.getSafe("opendiscord:verifybar-success").build("verifybar",{guild,channel,user,verifybar})) - instance.addComponent(await buttons.getSafe("opendiscord:verifybar-failure").build("verifybar",{guild,channel,user,verifybar})) - if (generalConfig.data.system.enableTicketActionWithReason) instance.addComponent(await buttons.getSafe("opendiscord:verifybar-success").build("verifybar",{guild,channel,user,verifybar,customData:"reason",customEmoji:"✏️",customLabel:lang.getTranslation("actions.buttons.withReason"),customColor:"blue"})) - - }else if (verifybar.id.value == "opendiscord:unpin-ticket-ticket-message"){ - instance.addComponent(await buttons.getSafe("opendiscord:verifybar-success").build("verifybar",{guild,channel,user,verifybar})) - instance.addComponent(await buttons.getSafe("opendiscord:verifybar-failure").build("verifybar",{guild,channel,user,verifybar})) - if (generalConfig.data.system.enableTicketActionWithReason) instance.addComponent(await buttons.getSafe("opendiscord:verifybar-success").build("verifybar",{guild,channel,user,verifybar,customData:"reason",customEmoji:"✏️",customLabel:lang.getTranslation("actions.buttons.withReason"),customColor:"blue"})) - - }else if (verifybar.id.value == "opendiscord:close-ticket-ticket-message"){ - instance.addComponent(await buttons.getSafe("opendiscord:verifybar-success").build("verifybar",{guild,channel,user,verifybar})) - instance.addComponent(await buttons.getSafe("opendiscord:verifybar-failure").build("verifybar",{guild,channel,user,verifybar})) - if (generalConfig.data.system.enableTicketActionWithReason) instance.addComponent(await buttons.getSafe("opendiscord:verifybar-success").build("verifybar",{guild,channel,user,verifybar,customData:"reason",customEmoji:"✏️",customLabel:lang.getTranslation("actions.buttons.withReason"),customColor:"blue"})) - - }else if (verifybar.id.value == "opendiscord:reopen-ticket-ticket-message"){ - instance.addComponent(await buttons.getSafe("opendiscord:verifybar-success").build("verifybar",{guild,channel,user,verifybar})) - instance.addComponent(await buttons.getSafe("opendiscord:verifybar-failure").build("verifybar",{guild,channel,user,verifybar})) - if (generalConfig.data.system.enableTicketActionWithReason) instance.addComponent(await buttons.getSafe("opendiscord:verifybar-success").build("verifybar",{guild,channel,user,verifybar,customData:"reason",customEmoji:"✏️",customLabel:lang.getTranslation("actions.buttons.withReason"),customColor:"blue"})) - - }else if (verifybar.id.value == "opendiscord:delete-ticket-ticket-message"){ - instance.addComponent(await buttons.getSafe("opendiscord:verifybar-success").build("verifybar",{guild,channel,user,verifybar})) - instance.addComponent(await buttons.getSafe("opendiscord:verifybar-failure").build("verifybar",{guild,channel,user,verifybar})) - if (generalConfig.data.system.enableTicketActionWithReason) instance.addComponent(await buttons.getSafe("opendiscord:verifybar-success").build("verifybar",{guild,channel,user,verifybar,customData:"reason",customEmoji:"✏️",customLabel:lang.getTranslation("actions.buttons.withReason"),customColor:"blue"})) - if (generalConfig.data.system.enableDeleteWithoutTranscript) instance.addComponent(await buttons.getSafe("opendiscord:verifybar-success").build("verifybar",{guild,channel,user,verifybar,customData:"no-transcript",customEmoji:"📄",customLabel:lang.getTranslation("actions.buttons.withoutTranscript"),customColor:"red"})) - } - }) - ) - - //TICKET CLOSED - messages.add(new api.ODMessage("opendiscord:verifybar-close-message")) - messages.get("opendiscord:verifybar-close-message").workers.add( - new api.ODWorker("opendiscord:verifybar-close-message",0,async (instance,params,source) => { - const {guild,channel,user,verifybar,originalMessage} = params - if (!guild){ - instance.setContent("ODError: Not In Guild => `opendiscord:verifybar-close-message`") - return - } - const ticket = opendiscord.tickets.get(channel.id) - if (!ticket){ - instance.setContent("ODError: Unknown Ticket => `opendiscord:verifybar-close-message`") - return - } - - const rawReason = (originalMessage.embeds[0] && originalMessage.embeds[0].fields[0]) ? originalMessage.embeds[0].fields[0].value : null - const reason = (rawReason == null) ? null : rawReason.substring(3,rawReason.length-3) - - //add embed - instance.addEmbed(await embeds.getSafe("opendiscord:close-message").build("other",{guild,channel,user,ticket,reason})) - - //add verifybar components - if (verifybar.id.value == "opendiscord:reopen-ticket-close-message"){ - instance.addComponent(await buttons.getSafe("opendiscord:verifybar-success").build("verifybar",{guild,channel,user,verifybar})) - instance.addComponent(await buttons.getSafe("opendiscord:verifybar-failure").build("verifybar",{guild,channel,user,verifybar})) - if (generalConfig.data.system.enableTicketActionWithReason) instance.addComponent(await buttons.getSafe("opendiscord:verifybar-success").build("verifybar",{guild,channel,user,verifybar,customData:"reason",customEmoji:"✏️",customLabel:lang.getTranslation("actions.buttons.withReason"),customColor:"blue"})) - - }else if (verifybar.id.value == "opendiscord:delete-ticket-close-message"){ - instance.addComponent(await buttons.getSafe("opendiscord:verifybar-success").build("verifybar",{guild,channel,user,verifybar})) - instance.addComponent(await buttons.getSafe("opendiscord:verifybar-failure").build("verifybar",{guild,channel,user,verifybar})) - if (generalConfig.data.system.enableTicketActionWithReason) instance.addComponent(await buttons.getSafe("opendiscord:verifybar-success").build("verifybar",{guild,channel,user,verifybar,customData:"reason",customEmoji:"✏️",customLabel:lang.getTranslation("actions.buttons.withReason"),customColor:"blue"})) - if (generalConfig.data.system.enableDeleteWithoutTranscript) instance.addComponent(await buttons.getSafe("opendiscord:verifybar-success").build("verifybar",{guild,channel,user,verifybar,customData:"no-transcript",customEmoji:"📄",customLabel:lang.getTranslation("actions.buttons.withoutTranscript"),customColor:"red"})) - } - }) - ) - - //TICKET REOPENED - messages.add(new api.ODMessage("opendiscord:verifybar-reopen-message")) - messages.get("opendiscord:verifybar-reopen-message").workers.add( - new api.ODWorker("opendiscord:verifybar-reopen-message",0,async (instance,params,source) => { - const {guild,channel,user,verifybar,originalMessage} = params - if (!guild){ - instance.setContent("ODError: Not In Guild => `opendiscord:verifybar-reopen-message`") - return - } - const ticket = opendiscord.tickets.get(channel.id) - if (!ticket){ - instance.setContent("ODError: Unknown Ticket => `opendiscord:verifybar-reopen-message`") - return - } - - const rawReason = (originalMessage.embeds[0] && originalMessage.embeds[0].fields[0]) ? originalMessage.embeds[0].fields[0].value : null - const reason = (rawReason == null) ? null : rawReason.substring(3,rawReason.length-3) - - //add embed - instance.addEmbed(await embeds.getSafe("opendiscord:reopen-message").build("other",{guild,channel,user,ticket,reason})) - - //add verifybar components - if (verifybar.id.value == "opendiscord:close-ticket-reopen-message"){ - instance.addComponent(await buttons.getSafe("opendiscord:verifybar-success").build("verifybar",{guild,channel,user,verifybar})) - instance.addComponent(await buttons.getSafe("opendiscord:verifybar-failure").build("verifybar",{guild,channel,user,verifybar})) - if (generalConfig.data.system.enableTicketActionWithReason) instance.addComponent(await buttons.getSafe("opendiscord:verifybar-success").build("verifybar",{guild,channel,user,verifybar,customData:"reason",customEmoji:"✏️",customLabel:lang.getTranslation("actions.buttons.withReason"),customColor:"blue"})) - - }else if (verifybar.id.value == "opendiscord:delete-ticket-reopen-message"){ - instance.addComponent(await buttons.getSafe("opendiscord:verifybar-success").build("verifybar",{guild,channel,user,verifybar})) - instance.addComponent(await buttons.getSafe("opendiscord:verifybar-failure").build("verifybar",{guild,channel,user,verifybar})) - if (generalConfig.data.system.enableTicketActionWithReason) instance.addComponent(await buttons.getSafe("opendiscord:verifybar-success").build("verifybar",{guild,channel,user,verifybar,customData:"reason",customEmoji:"✏️",customLabel:lang.getTranslation("actions.buttons.withReason"),customColor:"blue"})) - if (generalConfig.data.system.enableDeleteWithoutTranscript) instance.addComponent(await buttons.getSafe("opendiscord:verifybar-success").build("verifybar",{guild,channel,user,verifybar,customData:"no-transcript",customEmoji:"📄",customLabel:lang.getTranslation("actions.buttons.withoutTranscript"),customColor:"red"})) - } - }) - ) - - //TICKET CLAIM - messages.add(new api.ODMessage("opendiscord:verifybar-claim-message")) - messages.get("opendiscord:verifybar-claim-message").workers.add( - new api.ODWorker("opendiscord:verifybar-claim-message",0,async (instance,params,source) => { - const {guild,channel,user,verifybar,originalMessage} = params - if (!guild){ - instance.setContent("ODError: Not In Guild => `opendiscord:verifybar-claim-message`") - return - } - const ticket = opendiscord.tickets.get(channel.id) - if (!ticket){ - instance.setContent("ODError: Unknown Ticket => `opendiscord:verifybar-claim-message`") - return - } - - const rawReason = (originalMessage.embeds[0] && originalMessage.embeds[0].fields[0]) ? originalMessage.embeds[0].fields[0].value : null - const reason = (rawReason == null) ? null : rawReason.substring(3,rawReason.length-3) - - //add embed - instance.addEmbed(await embeds.getSafe("opendiscord:claim-message").build("other",{guild,channel,user,ticket,reason})) - - //add verifybar components - if (verifybar.id.value == "opendiscord:unclaim-ticket-claim-message"){ - instance.addComponent(await buttons.getSafe("opendiscord:verifybar-success").build("verifybar",{guild,channel,user,verifybar})) - instance.addComponent(await buttons.getSafe("opendiscord:verifybar-failure").build("verifybar",{guild,channel,user,verifybar})) - if (generalConfig.data.system.enableTicketActionWithReason) instance.addComponent(await buttons.getSafe("opendiscord:verifybar-success").build("verifybar",{guild,channel,user,verifybar,customData:"reason",customEmoji:"✏️",customLabel:lang.getTranslation("actions.buttons.withReason"),customColor:"blue"})) - } - }) - ) - - //TICKET UNCLAIM - messages.add(new api.ODMessage("opendiscord:verifybar-unclaim-message")) - messages.get("opendiscord:verifybar-unclaim-message").workers.add( - new api.ODWorker("opendiscord:verifybar-unclaim-message",0,async (instance,params,source) => { - const {guild,channel,user,verifybar,originalMessage} = params - if (!guild){ - instance.setContent("ODError: Not In Guild => `opendiscord:verifybar-unclaim-message`") - return - } - const ticket = opendiscord.tickets.get(channel.id) - if (!ticket){ - instance.setContent("ODError: Unknown Ticket => `opendiscord:verifybar-unclaim-message`") - return - } - - const rawReason = (originalMessage.embeds[0] && originalMessage.embeds[0].fields[0]) ? originalMessage.embeds[0].fields[0].value : null - const reason = (rawReason == null) ? null : rawReason.substring(3,rawReason.length-3) - - //add embed - instance.addEmbed(await embeds.getSafe("opendiscord:unclaim-message").build("other",{guild,channel,user,ticket,reason})) - - //add verifybar components - if (verifybar.id.value == "opendiscord:claim-ticket-unclaim-message"){ - instance.addComponent(await buttons.getSafe("opendiscord:verifybar-success").build("verifybar",{guild,channel,user,verifybar})) - instance.addComponent(await buttons.getSafe("opendiscord:verifybar-failure").build("verifybar",{guild,channel,user,verifybar})) - if (generalConfig.data.system.enableTicketActionWithReason) instance.addComponent(await buttons.getSafe("opendiscord:verifybar-success").build("verifybar",{guild,channel,user,verifybar,customData:"reason",customEmoji:"✏️",customLabel:lang.getTranslation("actions.buttons.withReason"),customColor:"blue"})) - } - }) - ) - - //TICKET PIN - messages.add(new api.ODMessage("opendiscord:verifybar-pin-message")) - messages.get("opendiscord:verifybar-pin-message").workers.add( - new api.ODWorker("opendiscord:verifybar-pin-message",0,async (instance,params,source) => { - const {guild,channel,user,verifybar,originalMessage} = params - if (!guild){ - instance.setContent("ODError: Not In Guild => `opendiscord:verifybar-pin-message`") - return - } - const ticket = opendiscord.tickets.get(channel.id) - if (!ticket){ - instance.setContent("ODError: Unknown Ticket => `opendiscord:verifybar-pin-message`") - return - } - - const rawReason = (originalMessage.embeds[0] && originalMessage.embeds[0].fields[0]) ? originalMessage.embeds[0].fields[0].value : null - const reason = (rawReason == null) ? null : rawReason.substring(3,rawReason.length-3) - - //add embed - instance.addEmbed(await embeds.getSafe("opendiscord:pin-message").build("other",{guild,channel,user,ticket,reason})) - - //add verifybar components - if (verifybar.id.value == "opendiscord:unpin-ticket-pin-message"){ - instance.addComponent(await buttons.getSafe("opendiscord:verifybar-success").build("verifybar",{guild,channel,user,verifybar})) - instance.addComponent(await buttons.getSafe("opendiscord:verifybar-failure").build("verifybar",{guild,channel,user,verifybar})) - if (generalConfig.data.system.enableTicketActionWithReason) instance.addComponent(await buttons.getSafe("opendiscord:verifybar-success").build("verifybar",{guild,channel,user,verifybar,customData:"reason",customEmoji:"✏️",customLabel:lang.getTranslation("actions.buttons.withReason"),customColor:"blue"})) - } - }) - ) - - //TICKET UNPIN - messages.add(new api.ODMessage("opendiscord:verifybar-unpin-message")) - messages.get("opendiscord:verifybar-unpin-message").workers.add( - new api.ODWorker("opendiscord:verifybar-unpin-message",0,async (instance,params,source) => { - const {guild,channel,user,verifybar,originalMessage} = params - if (!guild){ - instance.setContent("ODError: Not In Guild => `opendiscord:verifybar-unpin-message`") - return - } - const ticket = opendiscord.tickets.get(channel.id) - if (!ticket){ - instance.setContent("ODError: Unknown Ticket => `opendiscord:verifybar-unpin-message`") - return - } - - const rawReason = (originalMessage.embeds[0] && originalMessage.embeds[0].fields[0]) ? originalMessage.embeds[0].fields[0].value : null - const reason = (rawReason == null) ? null : rawReason.substring(3,rawReason.length-3) - - //add embed - instance.addEmbed(await embeds.getSafe("opendiscord:unpin-message").build("other",{guild,channel,user,ticket,reason})) - - //add verifybar components - if (verifybar.id.value == "opendiscord:pin-ticket-unpin-message"){ - instance.addComponent(await buttons.getSafe("opendiscord:verifybar-success").build("verifybar",{guild,channel,user,verifybar})) - instance.addComponent(await buttons.getSafe("opendiscord:verifybar-failure").build("verifybar",{guild,channel,user,verifybar})) - if (generalConfig.data.system.enableTicketActionWithReason) instance.addComponent(await buttons.getSafe("opendiscord:verifybar-success").build("verifybar",{guild,channel,user,verifybar,customData:"reason",customEmoji:"✏️",customLabel:lang.getTranslation("actions.buttons.withReason"),customColor:"blue"})) - } - }) - ) - - //TICKET AUTOCLOSED - messages.add(new api.ODMessage("opendiscord:verifybar-autoclose-message")) - messages.get("opendiscord:verifybar-autoclose-message").workers.add( - new api.ODWorker("opendiscord:verifybar-autoclose-message",0,async (instance,params,source) => { - const {guild,channel,user,verifybar,originalMessage} = params - if (!guild || channel.isDMBased()){ - instance.setContent("ODError: Not In Guild => `opendiscord:verifybar-autoclose-message`") - return - } - const ticket = opendiscord.tickets.get(channel.id) - if (!ticket){ - instance.setContent("ODError: Unknown Ticket => `opendiscord:verifybar-autoclose-message`") - return - } - - //add embed - instance.addEmbed(await embeds.getSafe("opendiscord:autoclose-message").build("other",{guild,channel,user,ticket})) - - //add verifybar components - if (verifybar.id.value == "opendiscord:reopen-ticket-autoclose-message"){ - instance.addComponent(await buttons.getSafe("opendiscord:verifybar-success").build("verifybar",{guild,channel,user,verifybar})) - instance.addComponent(await buttons.getSafe("opendiscord:verifybar-failure").build("verifybar",{guild,channel,user,verifybar})) - if (generalConfig.data.system.enableTicketActionWithReason) instance.addComponent(await buttons.getSafe("opendiscord:verifybar-success").build("verifybar",{guild,channel,user,verifybar,customData:"reason",customEmoji:"✏️",customLabel:lang.getTranslation("actions.buttons.withReason"),customColor:"blue"})) - - }else if (verifybar.id.value == "opendiscord:delete-ticket-autoclose-message"){ - instance.addComponent(await buttons.getSafe("opendiscord:verifybar-success").build("verifybar",{guild,channel,user,verifybar})) - instance.addComponent(await buttons.getSafe("opendiscord:verifybar-failure").build("verifybar",{guild,channel,user,verifybar})) - if (generalConfig.data.system.enableTicketActionWithReason) instance.addComponent(await buttons.getSafe("opendiscord:verifybar-success").build("verifybar",{guild,channel,user,verifybar,customData:"reason",customEmoji:"✏️",customLabel:lang.getTranslation("actions.buttons.withReason"),customColor:"blue"})) - if (generalConfig.data.system.enableDeleteWithoutTranscript) instance.addComponent(await buttons.getSafe("opendiscord:verifybar-success").build("verifybar",{guild,channel,user,verifybar,customData:"no-transcript",customEmoji:"📄",customLabel:lang.getTranslation("actions.buttons.withoutTranscript"),customColor:"red"})) - } - }) - ) -} - const errorMessages = () => { //ERROR messages.add(new api.ODMessage("opendiscord:error")) messages.get("opendiscord:error").workers.add( - new api.ODWorker("opendiscord:error",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:error",0,async (instance,params,origin) => { const {guild,channel,user,error,layout,customTitle} = params - instance.addEmbed(await embeds.getSafe("opendiscord:error").build(source,{guild,channel,user,error,layout,customTitle})) + instance.addEmbed(await embeds.getSafe("opendiscord:error").build(origin,{guild,channel,user,error,layout,customTitle})) instance.setEphemeral(true) }) ) @@ -342,9 +40,9 @@ const errorMessages = () => { //ERROR OPTION MISSING messages.add(new api.ODMessage("opendiscord:error-option-missing")) messages.get("opendiscord:error-option-missing").workers.add( - new api.ODWorker("opendiscord:error-option-missing",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:error-option-missing",0,async (instance,params,origin) => { const {guild,channel,user,error} = params - instance.addEmbed(await embeds.getSafe("opendiscord:error-option-missing").build(source,{guild,channel,user,error})) + instance.addEmbed(await embeds.getSafe("opendiscord:error-option-missing").build(origin,{guild,channel,user,error})) instance.setEphemeral(true) }) ) @@ -352,9 +50,9 @@ const errorMessages = () => { //ERROR OPTION INVALID messages.add(new api.ODMessage("opendiscord:error-option-invalid")) messages.get("opendiscord:error-option-invalid").workers.add( - new api.ODWorker("opendiscord:error-option-invalid",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:error-option-invalid",0,async (instance,params,origin) => { const {guild,channel,user,error} = params - instance.addEmbed(await embeds.getSafe("opendiscord:error-option-invalid").build(source,{guild,channel,user,error})) + instance.addEmbed(await embeds.getSafe("opendiscord:error-option-invalid").build(origin,{guild,channel,user,error})) instance.setEphemeral(true) }) ) @@ -362,9 +60,9 @@ const errorMessages = () => { //ERROR UNKNOWN COMMAND messages.add(new api.ODMessage("opendiscord:error-unknown-command")) messages.get("opendiscord:error-unknown-command").workers.add( - new api.ODWorker("opendiscord:error-unknown-command",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:error-unknown-command",0,async (instance,params,origin) => { const {guild,channel,user,error} = params - instance.addEmbed(await embeds.getSafe("opendiscord:error-unknown-command").build(source,{guild,channel,user,error})) + instance.addEmbed(await embeds.getSafe("opendiscord:error-unknown-command").build(origin,{guild,channel,user,error})) instance.setEphemeral(true) }) ) @@ -372,9 +70,9 @@ const errorMessages = () => { //ERROR NO PERMISSIONS messages.add(new api.ODMessage("opendiscord:error-no-permissions")) messages.get("opendiscord:error-no-permissions").workers.add( - new api.ODWorker("opendiscord:error-no-permissions",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:error-no-permissions",0,async (instance,params,origin) => { const {guild,channel,user,permissions} = params - instance.addEmbed(await embeds.getSafe("opendiscord:error-no-permissions").build(source,{guild,channel,user,permissions})) + instance.addEmbed(await embeds.getSafe("opendiscord:error-no-permissions").build(origin,{guild,channel,user,permissions})) instance.setEphemeral(true) }) ) @@ -382,9 +80,9 @@ const errorMessages = () => { //ERROR NO PERMISSIONS COOLDOWN messages.add(new api.ODMessage("opendiscord:error-no-permissions-cooldown")) messages.get("opendiscord:error-no-permissions-cooldown").workers.add( - new api.ODWorker("opendiscord:error-no-permissions-cooldown",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:error-no-permissions-cooldown",0,async (instance,params,origin) => { const {guild,channel,user,until} = params - instance.addEmbed(await embeds.getSafe("opendiscord:error-no-permissions-cooldown").build(source,{guild,channel,user,until})) + instance.addEmbed(await embeds.getSafe("opendiscord:error-no-permissions-cooldown").build(origin,{guild,channel,user,until})) instance.setEphemeral(true) }) ) @@ -392,9 +90,9 @@ const errorMessages = () => { //ERROR NO PERMISSIONS BLACKLISTED messages.add(new api.ODMessage("opendiscord:error-no-permissions-blacklisted")) messages.get("opendiscord:error-no-permissions-blacklisted").workers.add( - new api.ODWorker("opendiscord:error-no-permissions-blacklisted",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:error-no-permissions-blacklisted",0,async (instance,params,origin) => { const {guild,channel,user} = params - instance.addEmbed(await embeds.getSafe("opendiscord:error-no-permissions-blacklisted").build(source,{guild,channel,user})) + instance.addEmbed(await embeds.getSafe("opendiscord:error-no-permissions-blacklisted").build(origin,{guild,channel,user})) instance.setEphemeral(true) }) ) @@ -402,9 +100,9 @@ const errorMessages = () => { //ERROR NO PERMISSIONS LIMITS messages.add(new api.ODMessage("opendiscord:error-no-permissions-limits")) messages.get("opendiscord:error-no-permissions-limits").workers.add( - new api.ODWorker("opendiscord:error-no-permissions-limits",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:error-no-permissions-limits",0,async (instance,params,origin) => { const {guild,channel,user,limit} = params - instance.addEmbed(await embeds.getSafe("opendiscord:error-no-permissions-limits").build(source,{guild,channel,user,limit})) + instance.addEmbed(await embeds.getSafe("opendiscord:error-no-permissions-limits").build(origin,{guild,channel,user,limit})) instance.setEphemeral(true) }) ) @@ -412,9 +110,9 @@ const errorMessages = () => { //ERROR RESPONDER TIMEOUT messages.add(new api.ODMessage("opendiscord:error-responder-timeout")) messages.get("opendiscord:error-responder-timeout").workers.add( - new api.ODWorker("opendiscord:error-responder-timeout",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:error-responder-timeout",0,async (instance,params,origin) => { const {guild,channel,user} = params - instance.addEmbed(await embeds.getSafe("opendiscord:error-responder-timeout").build(source,{guild,channel,user})) + instance.addEmbed(await embeds.getSafe("opendiscord:error-responder-timeout").build(origin,{guild,channel,user})) instance.setEphemeral(true) }) ) @@ -422,9 +120,9 @@ const errorMessages = () => { //ERROR TICKET UNKNOWN messages.add(new api.ODMessage("opendiscord:error-ticket-unknown")) messages.get("opendiscord:error-ticket-unknown").workers.add( - new api.ODWorker("opendiscord:error-ticket-unknown",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:error-ticket-unknown",0,async (instance,params,origin) => { const {guild,channel,user} = params - instance.addEmbed(await embeds.getSafe("opendiscord:error-ticket-unknown").build(source,{guild,channel,user})) + instance.addEmbed(await embeds.getSafe("opendiscord:error-ticket-unknown").build(origin,{guild,channel,user})) instance.setEphemeral(true) }) ) @@ -432,10 +130,10 @@ const errorMessages = () => { //ERROR TICKET DEPRECATED messages.add(new api.ODMessage("opendiscord:error-ticket-deprecated")) messages.get("opendiscord:error-ticket-deprecated").workers.add( - new api.ODWorker("opendiscord:error-ticket-deprecated",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:error-ticket-deprecated",0,async (instance,params,origin) => { const {guild,channel,user} = params - instance.addEmbed(await embeds.getSafe("opendiscord:error-ticket-deprecated").build(source,{guild,channel,user})) - instance.addComponent(await buttons.getSafe("opendiscord:error-ticket-deprecated-transcript").build(source,{})) + instance.addEmbed(await embeds.getSafe("opendiscord:error-ticket-deprecated").build(origin,{guild,channel,user})) + instance.addComponent(await buttons.getSafe("opendiscord:error-ticket-deprecated-transcript").build(origin,{})) instance.setEphemeral(true) }) ) @@ -443,9 +141,9 @@ const errorMessages = () => { //ERROR OPTION UNKNOWN messages.add(new api.ODMessage("opendiscord:error-option-unknown")) messages.get("opendiscord:error-option-unknown").workers.add( - new api.ODWorker("opendiscord:error-option-unknown",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:error-option-unknown",0,async (instance,params,origin) => { const {guild,channel,user} = params - instance.addEmbed(await embeds.getSafe("opendiscord:error-option-unknown").build(source,{guild,channel,user})) + instance.addEmbed(await embeds.getSafe("opendiscord:error-option-unknown").build(origin,{guild,channel,user})) instance.setEphemeral(true) }) ) @@ -453,9 +151,9 @@ const errorMessages = () => { //ERROR PANEL UNKNOWN messages.add(new api.ODMessage("opendiscord:error-panel-unknown")) messages.get("opendiscord:error-panel-unknown").workers.add( - new api.ODWorker("opendiscord:error-panel-unknown",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:error-panel-unknown",0,async (instance,params,origin) => { const {guild,channel,user} = params - instance.addEmbed(await embeds.getSafe("opendiscord:error-panel-unknown").build(source,{guild,channel,user})) + instance.addEmbed(await embeds.getSafe("opendiscord:error-panel-unknown").build(origin,{guild,channel,user})) instance.setEphemeral(true) }) ) @@ -463,9 +161,9 @@ const errorMessages = () => { //ERROR NOT IN GUILD messages.add(new api.ODMessage("opendiscord:error-not-in-guild")) messages.get("opendiscord:error-not-in-guild").workers.add( - new api.ODWorker("opendiscord:error-not-in-guild",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:error-not-in-guild",0,async (instance,params,origin) => { const {channel,user} = params - instance.addEmbed(await embeds.getSafe("opendiscord:error-not-in-guild").build(source,{channel,user})) + instance.addEmbed(await embeds.getSafe("opendiscord:error-not-in-guild").build(origin,{channel,user})) instance.setEphemeral(true) }) ) @@ -473,9 +171,19 @@ const errorMessages = () => { //ERROR CHANNEL RENAME messages.add(new api.ODMessage("opendiscord:error-channel-rename")) messages.get("opendiscord:error-channel-rename").workers.add( - new api.ODWorker("opendiscord:error-channel-rename",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:error-channel-rename",0,async (instance,params,origin) => { const {guild,channel,user,originalName,newName} = params - instance.addEmbed(await embeds.getSafe("opendiscord:error-channel-rename").build(source,{guild,channel,user,originalName,newName})) + instance.addEmbed(await embeds.getSafe("opendiscord:error-channel-rename").build(origin,{guild,channel,user,originalName,newName})) + instance.setEphemeral(true) + }) + ) + + //ERROR CHANNEL CATEGORY + messages.add(new api.ODMessage("opendiscord:error-channel-category")) + messages.get("opendiscord:error-channel-category").workers.add( + new api.ODWorker("opendiscord:error-channel-category",0,async (instance,params,origin) => { + const {guild,channel,user,originalCategory,newCategory} = params + instance.addEmbed(await embeds.getSafe("opendiscord:error-channel-category").build(origin,{guild,channel,user,originalCategory,newCategory})) instance.setEphemeral(true) }) ) @@ -483,9 +191,9 @@ const errorMessages = () => { //ERROR TICKET BUSY messages.add(new api.ODMessage("opendiscord:error-ticket-busy")) messages.get("opendiscord:error-ticket-busy").workers.add( - new api.ODWorker("opendiscord:error-ticket-busy",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:error-ticket-busy",0,async (instance,params,origin) => { const {guild,channel,user} = params - instance.addEmbed(await embeds.getSafe("opendiscord:error-ticket-busy").build(source,{guild,channel,user})) + instance.addEmbed(await embeds.getSafe("opendiscord:error-ticket-busy").build(origin,{guild,channel,user})) instance.setEphemeral(true) }) ) @@ -495,20 +203,20 @@ const helpMenuMessages = () => { //HELP MENU messages.add(new api.ODMessage("opendiscord:help-menu")) messages.get("opendiscord:help-menu").workers.add( - new api.ODWorker("opendiscord:help-menu",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:help-menu",0,async (instance,params,origin) => { const {mode,page} = params const totalPages = (await opendiscord.helpmenu.render(mode)).length - const embed = await embeds.getSafe("opendiscord:help-menu").build(source,{mode,page}) + const embed = await embeds.getSafe("opendiscord:help-menu").build(origin,{mode,page}) instance.addEmbed(embed) if (totalPages > 1){ //when more than 1 page - instance.addComponent(await buttons.getSafe("opendiscord:help-menu-previous").build(source,{mode,page})) - instance.addComponent(await buttons.getSafe("opendiscord:help-menu-page").build(source,{mode,page})) - instance.addComponent(await buttons.getSafe("opendiscord:help-menu-next").build(source,{mode,page})) + instance.addComponent(await buttons.getSafe("opendiscord:help-menu-previous").build(origin,{mode,page})) + instance.addComponent(await buttons.getSafe("opendiscord:help-menu-page").build(origin,{mode,page})) + instance.addComponent(await buttons.getSafe("opendiscord:help-menu-next").build(origin,{mode,page})) instance.addComponent(buttons.getNewLine("opendiscord:help-menu-divider")) } - if (generalConfig.data.textCommands && generalConfig.data.slashCommands) instance.addComponent(await buttons.get("opendiscord:help-menu-switch").build(source,{mode,page})) + if (generalConfig.data.textCommands && generalConfig.data.slashCommands) instance.addComponent(await buttons.get("opendiscord:help-menu-switch").build(origin,{mode,page})) }) ) } @@ -517,45 +225,45 @@ const statsMessages = () => { //STATS GLOBAL messages.add(new api.ODMessage("opendiscord:stats-global")) messages.get("opendiscord:stats-global").workers.add( - new api.ODWorker("opendiscord:stats-global",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:stats-global",0,async (instance,params,origin) => { const {guild,channel,user} = params - instance.addEmbed(await embeds.getSafe("opendiscord:stats-global").build(source,{guild,channel,user})) + instance.addEmbed(await embeds.getSafe("opendiscord:stats-global").build(origin,{guild,channel,user})) }) ) //STATS TICKET messages.add(new api.ODMessage("opendiscord:stats-ticket")) messages.get("opendiscord:stats-ticket").workers.add( - new api.ODWorker("opendiscord:stats-ticket",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:stats-ticket",0,async (instance,params,origin) => { const {guild,channel,user,scopeData} = params - instance.addEmbed(await embeds.getSafe("opendiscord:stats-ticket").build(source,{guild,channel,user,scopeData})) + instance.addEmbed(await embeds.getSafe("opendiscord:stats-ticket").build(origin,{guild,channel,user,scopeData})) }) ) //STATS USER messages.add(new api.ODMessage("opendiscord:stats-user")) messages.get("opendiscord:stats-user").workers.add( - new api.ODWorker("opendiscord:stats-user",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:stats-user",0,async (instance,params,origin) => { const {guild,channel,user,scopeData} = params - instance.addEmbed(await embeds.getSafe("opendiscord:stats-user").build(source,{guild,channel,user,scopeData})) + instance.addEmbed(await embeds.getSafe("opendiscord:stats-user").build(origin,{guild,channel,user,scopeData})) }) ) //STATS RESET messages.add(new api.ODMessage("opendiscord:stats-reset")) messages.get("opendiscord:stats-reset").workers.add( - new api.ODWorker("opendiscord:stats-reset",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:stats-reset",0,async (instance,params,origin) => { const {guild,channel,user,reason} = params - instance.addEmbed(await embeds.getSafe("opendiscord:stats-reset").build(source,{guild,channel,user,reason})) + instance.addEmbed(await embeds.getSafe("opendiscord:stats-reset").build(origin,{guild,channel,user,reason})) }) ) //STATS TICKET UNKNOWN messages.add(new api.ODMessage("opendiscord:stats-ticket-unknown")) messages.get("opendiscord:stats-ticket-unknown").workers.add( - new api.ODWorker("opendiscord:stats-ticket-unknown",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:stats-ticket-unknown",0,async (instance,params,origin) => { const {guild,channel,user,id} = params - instance.addEmbed(await embeds.getSafe("opendiscord:stats-ticket-unknown").build(source,{guild,channel,user,id})) + instance.addEmbed(await embeds.getSafe("opendiscord:stats-ticket-unknown").build(origin,{guild,channel,user,id})) instance.setEphemeral(true) }) ) @@ -565,8 +273,11 @@ const panelMessages = () => { //PANEL messages.add(new api.ODMessage("opendiscord:panel")) messages.get("opendiscord:panel").workers.add([ - new api.ODWorker("opendiscord:panel-layout",1,async (instance,params,source) => { - const {guild,channel,user,panel} = params + new api.ODWorker("opendiscord:panel-layout",1,async (instance,params,origin) => { + const {guild,channel,user,panel,isSubPanel} = params + + //ephemeral if sub-panel + if (isSubPanel) instance.setEphemeral(true) //add text const text = panel.get("opendiscord:text").value @@ -578,15 +289,15 @@ const panelMessages = () => { instance.setContent(text) } - if (panel.get("opendiscord:enable-max-tickets-warning-text").value && generalConfig.data.system.limits.enabled){ - instance.setContent(instance.data.content+"\n\n*"+lang.getTranslationWithParams("actions.descriptions.ticketMessageLimit",[generalConfig.data.system.limits.userMaximum.toString()])+"*") + if (panel.get("opendiscord:enable-max-tickets-warning-text").value && generalConfig.data.ticketSystem.limits.enabled){ + instance.setContent(instance.data.content+"\n\n*"+lang.getTranslationWithParams("actions.descriptions.ticketMessageLimit",[generalConfig.data.ticketSystem.limits.userMaximum.toString()])+"*") } //add embed const embedOptions = panel.get("opendiscord:embed").value - if (embedOptions.enabled) instance.addEmbed(await embeds.getSafe("opendiscord:panel").build(source,{guild,channel,user,panel})) + if (embedOptions.enabled) instance.addEmbed(await embeds.getSafe("opendiscord:panel").build(origin,{guild,channel,user,panel,isSubPanel})) }), - new api.ODWorker("opendiscord:panel-components",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:panel-components",0,async (instance,params,origin) => { const {guild,channel,user,panel} = params const options: api.ODOption[] = [] panel.get("opendiscord:options").value.forEach((id) => { @@ -596,17 +307,27 @@ const panelMessages = () => { if (panel.get("opendiscord:dropdown").value){ //dropdown - const ticketOptions: api.ODTicketOption[] = [] + const ticketOptions: (api.ODTicketOption|api.ODRoleOption|api.ODSubPanelOption)[] = [] options.forEach((option) => { if (option instanceof api.ODTicketOption) ticketOptions.push(option) + if (option instanceof api.ODRoleOption) ticketOptions.push(option) + if (option instanceof api.ODSubPanelOption) ticketOptions.push(option) }) - instance.addComponent(await dropdowns.getSafe("opendiscord:panel-dropdown-tickets").build(source,{guild,channel,user,panel,options:ticketOptions})) + instance.addComponent(await dropdowns.getSafe("opendiscord:panel-dropdown").build(origin,{guild,channel,user,panel,options:ticketOptions})) }else{ //buttons + let rowButtonCount: number = 0 for (const option of options){ - if (option instanceof api.ODTicketOption) instance.addComponent(await buttons.getSafe("opendiscord:ticket-option").build(source,{guild,channel,user,panel,option})) - else if (option instanceof api.ODWebsiteOption) instance.addComponent(await buttons.getSafe("opendiscord:website-option").build(source,{guild,channel,user,panel,option})) - else if (option instanceof api.ODRoleOption) instance.addComponent(await buttons.getSafe("opendiscord:role-option").build(source,{guild,channel,user,panel,option})) + if (rowButtonCount >= panel.get("opendiscord:maximum-buttons-per-row").value){ + instance.addComponent({id:new api.ODId("opendiscord:new-row"),component:"\n"}) + rowButtonCount = 0 + } + if (option instanceof api.ODTicketOption) instance.addComponent(await buttons.getSafe("opendiscord:ticket-option").build(origin,{guild,channel,user,panel,option})) + else if (option instanceof api.ODWebsiteOption) instance.addComponent(await buttons.getSafe("opendiscord:website-option").build(origin,{guild,channel,user,panel,option})) + else if (option instanceof api.ODRoleOption) instance.addComponent(await buttons.getSafe("opendiscord:role-option").build(origin,{guild,channel,user,panel,option})) + else if (option instanceof api.ODSubPanelOption) instance.addComponent(await buttons.getSafe("opendiscord:subpanel-option").build(origin,{guild,channel,user,panel,option})) + + rowButtonCount++ } } }) @@ -615,7 +336,7 @@ const panelMessages = () => { //PANEL READY messages.add(new api.ODMessage("opendiscord:panel-ready")) messages.get("opendiscord:panel-ready").workers.add( - new api.ODWorker("opendiscord:panel-ready",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:panel-ready",0,async (instance,params,origin) => { instance.setContent("## "+lang.getTranslation("actions.descriptions.panelReady")) instance.setEphemeral(true) }) @@ -626,10 +347,10 @@ const ticketMessages = () => { //TICKET CREATED messages.add(new api.ODMessage("opendiscord:ticket-created")) messages.get("opendiscord:ticket-created").workers.add( - new api.ODWorker("opendiscord:ticket-created",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:ticket-created",0,async (instance,params,origin) => { const {guild,channel,user,ticket} = params - instance.addEmbed(await embeds.getSafe("opendiscord:ticket-created").build(source,{guild,channel,user,ticket})) + instance.addEmbed(await embeds.getSafe("opendiscord:ticket-created").build(origin,{guild,channel,user,ticket})) instance.addComponent(await buttons.getSafe("opendiscord:visit-ticket").build("ticket-created",{guild,channel,user,ticket})) instance.setEphemeral(true) }) @@ -638,7 +359,7 @@ const ticketMessages = () => { //TICKET CREATED DM messages.add(new api.ODMessage("opendiscord:ticket-created-dm")) messages.get("opendiscord:ticket-created-dm").workers.add( - new api.ODWorker("opendiscord:ticket-created-dm",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:ticket-created-dm",0,async (instance,params,origin) => { const {guild,channel,user,ticket} = params //add text @@ -646,7 +367,7 @@ const ticketMessages = () => { if (text !== "") instance.setContent(text) //add embed - if (ticket.option.get("opendiscord:dm-message-embed").value.enabled) instance.addEmbed(await embeds.getSafe("opendiscord:ticket-created-dm").build(source,{guild,channel,user,ticket})) + if (ticket.option.get("opendiscord:dm-message-embed").value.enabled) instance.addEmbed(await embeds.getSafe("opendiscord:ticket-created-dm").build(origin,{guild,channel,user,ticket})) //add components instance.addComponent(await buttons.getSafe("opendiscord:visit-ticket").build("ticket-created",{guild,channel,user,ticket})) @@ -656,10 +377,10 @@ const ticketMessages = () => { //TICKET CREATED LOGS messages.add(new api.ODMessage("opendiscord:ticket-created-logs")) messages.get("opendiscord:ticket-created-logs").workers.add( - new api.ODWorker("opendiscord:ticket-created-logs",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:ticket-created-logs",0,async (instance,params,origin) => { const {guild,channel,user,ticket} = params - instance.addEmbed(await embeds.getSafe("opendiscord:ticket-created-logs").build(source,{guild,channel,user,ticket})) + instance.addEmbed(await embeds.getSafe("opendiscord:ticket-created-logs").build(origin,{guild,channel,user,ticket})) instance.addComponent(await buttons.getSafe("opendiscord:visit-ticket").build("ticket-created",{guild,channel,user,ticket})) }) ) @@ -667,7 +388,7 @@ const ticketMessages = () => { //TICKET MESSAGE messages.add(new api.ODMessage("opendiscord:ticket-message")) messages.get("opendiscord:ticket-message").workers.add([ - new api.ODWorker("opendiscord:ticket-message-layout",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:ticket-message-layout",0,async (instance,params,origin) => { const {guild,channel,user,ticket} = params //add pings @@ -686,12 +407,12 @@ const ticketMessages = () => { else instance.setContent(pingText) //add embed - if (ticket.option.get("opendiscord:ticket-message-embed").value.enabled) instance.addEmbed(await embeds.getSafe("opendiscord:ticket-message").build(source,{guild,channel,user,ticket})) + if (ticket.option.get("opendiscord:ticket-message-embed").value.enabled) instance.addEmbed(await embeds.getSafe("opendiscord:ticket-message").build(origin,{guild,channel,user,ticket})) }), - new api.ODWorker("opendiscord:ticket-message-components",1,async (instance,params,source) => { + new api.ODWorker("opendiscord:ticket-message-components",1,async (instance,params,origin) => { const {guild,channel,user,ticket} = params //add components - if (generalConfig.data.system.enableTicketClaimButtons && !ticket.get("opendiscord:closed").value){ + if (generalConfig.data.ticketSystem.enableTicketClaimButtons && !ticket.get("opendiscord:closed").value){ //enable ticket claiming if (ticket.get("opendiscord:claimed").value){ instance.addComponent(await buttons.getSafe("opendiscord:unclaim-ticket").build("ticket-message",{guild,channel,user,ticket})) @@ -699,7 +420,7 @@ const ticketMessages = () => { instance.addComponent(await buttons.getSafe("opendiscord:claim-ticket").build("ticket-message",{guild,channel,user,ticket})) } } - if (generalConfig.data.system.enableTicketPinButtons && !ticket.get("opendiscord:closed").value){ + if (generalConfig.data.ticketSystem.enableTicketPinButtons && !ticket.get("opendiscord:closed").value){ //enable ticket pinning if (ticket.get("opendiscord:pinned").value){ instance.addComponent(await buttons.getSafe("opendiscord:unpin-ticket").build("ticket-message",{guild,channel,user,ticket})) @@ -707,7 +428,7 @@ const ticketMessages = () => { instance.addComponent(await buttons.getSafe("opendiscord:pin-ticket").build("ticket-message",{guild,channel,user,ticket})) } } - if (generalConfig.data.system.enableTicketCloseButtons){ + if (generalConfig.data.ticketSystem.enableTicketCloseButtons){ //enable ticket closing if (ticket.get("opendiscord:closed").value){ instance.addComponent(await buttons.getSafe("opendiscord:reopen-ticket").build("ticket-message",{guild,channel,user,ticket})) @@ -716,9 +437,12 @@ const ticketMessages = () => { } } //enable ticket deletion - if (generalConfig.data.system.enableTicketDeleteButtons) instance.addComponent(await buttons.getSafe("opendiscord:delete-ticket").build("ticket-message",{guild,channel,user,ticket})) + if (generalConfig.data.ticketSystem.enableTicketDeleteButtons) instance.addComponent(await buttons.getSafe("opendiscord:delete-ticket").build("ticket-message",{guild,channel,user,ticket})) + + //add priority dropdown + if (generalConfig.data.ticketSystem.askPriorityOnTicketCreation) instance.addComponent(await dropdowns.getSafe("opendiscord:priority-dropdown").build(origin,{guild,channel,user,ticket})) }), - new api.ODWorker("opendiscord:ticket-message-disable-components",2,async (instance,params,source) => { + new api.ODWorker("opendiscord:ticket-message-disable-components",2,async (instance,params,origin) => { const {ticket} = params if (ticket.get("opendiscord:for-deletion").value){ //disable all buttons when ticket is being prepared for deletion @@ -734,125 +458,125 @@ const ticketMessages = () => { //TICKET CLOSED messages.add(new api.ODMessage("opendiscord:close-message")) messages.get("opendiscord:close-message").workers.add( - new api.ODWorker("opendiscord:close-message",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:close-message",0,async (instance,params,origin) => { const {guild,channel,user,ticket,reason} = params - instance.addEmbed(await embeds.getSafe("opendiscord:close-message").build(source,{guild,channel,user,ticket,reason})) - if (generalConfig.data.system.enableTicketCloseButtons) instance.addComponent(await buttons.getSafe("opendiscord:reopen-ticket").build("close-message",{guild,channel,user,ticket})) - if (generalConfig.data.system.enableTicketDeleteButtons) instance.addComponent(await buttons.getSafe("opendiscord:delete-ticket").build("close-message",{guild,channel,user,ticket})) + instance.addEmbed(await embeds.getSafe("opendiscord:close-message").build(origin,{guild,channel,user,ticket,reason})) + if (generalConfig.data.ticketSystem.enableTicketCloseButtons) instance.addComponent(await buttons.getSafe("opendiscord:reopen-ticket").build("close-message",{guild,channel,user,ticket})) + if (generalConfig.data.ticketSystem.enableTicketDeleteButtons) instance.addComponent(await buttons.getSafe("opendiscord:delete-ticket").build("close-message",{guild,channel,user,ticket})) }) ) //TICKET REOPENED messages.add(new api.ODMessage("opendiscord:reopen-message")) messages.get("opendiscord:reopen-message").workers.add( - new api.ODWorker("opendiscord:reopen-message",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:reopen-message",0,async (instance,params,origin) => { const {guild,channel,user,ticket,reason} = params - instance.addEmbed(await embeds.getSafe("opendiscord:reopen-message").build(source,{guild,channel,user,ticket,reason})) - if (generalConfig.data.system.enableTicketCloseButtons) instance.addComponent(await buttons.getSafe("opendiscord:close-ticket").build("reopen-message",{guild,channel,user,ticket})) - if (generalConfig.data.system.enableTicketDeleteButtons) instance.addComponent(await buttons.getSafe("opendiscord:delete-ticket").build("reopen-message",{guild,channel,user,ticket})) + instance.addEmbed(await embeds.getSafe("opendiscord:reopen-message").build(origin,{guild,channel,user,ticket,reason})) + if (generalConfig.data.ticketSystem.enableTicketCloseButtons) instance.addComponent(await buttons.getSafe("opendiscord:close-ticket").build("reopen-message",{guild,channel,user,ticket})) + if (generalConfig.data.ticketSystem.enableTicketDeleteButtons) instance.addComponent(await buttons.getSafe("opendiscord:delete-ticket").build("reopen-message",{guild,channel,user,ticket})) }) ) //TICKET DELETED messages.add(new api.ODMessage("opendiscord:delete-message")) messages.get("opendiscord:delete-message").workers.add( - new api.ODWorker("opendiscord:delete-message",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:delete-message",0,async (instance,params,origin) => { const {guild,channel,user,ticket,reason} = params - instance.addEmbed(await embeds.getSafe("opendiscord:delete-message").build(source,{guild,channel,user,ticket,reason})) + instance.addEmbed(await embeds.getSafe("opendiscord:delete-message").build(origin,{guild,channel,user,ticket,reason})) }) ) //TICKET CLAIMED messages.add(new api.ODMessage("opendiscord:claim-message")) messages.get("opendiscord:claim-message").workers.add( - new api.ODWorker("opendiscord:claim-message",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:claim-message",0,async (instance,params,origin) => { const {guild,channel,user,ticket,reason} = params - instance.addEmbed(await embeds.getSafe("opendiscord:claim-message").build(source,{guild,channel,user,ticket,reason})) - if (generalConfig.data.system.enableTicketClaimButtons) instance.addComponent(await buttons.getSafe("opendiscord:unclaim-ticket").build("claim-message",{guild,channel,user,ticket})) + instance.addEmbed(await embeds.getSafe("opendiscord:claim-message").build(origin,{guild,channel,user,ticket,reason})) + if (generalConfig.data.ticketSystem.enableTicketClaimButtons) instance.addComponent(await buttons.getSafe("opendiscord:unclaim-ticket").build("claim-message",{guild,channel,user,ticket})) }) ) //TICKET UNCLAIMED messages.add(new api.ODMessage("opendiscord:unclaim-message")) messages.get("opendiscord:unclaim-message").workers.add( - new api.ODWorker("opendiscord:unclaim-message",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:unclaim-message",0,async (instance,params,origin) => { const {guild,channel,user,ticket,reason} = params - instance.addEmbed(await embeds.getSafe("opendiscord:unclaim-message").build(source,{guild,channel,user,ticket,reason})) - if (generalConfig.data.system.enableTicketClaimButtons) instance.addComponent(await buttons.getSafe("opendiscord:claim-ticket").build("unclaim-message",{guild,channel,user,ticket})) + instance.addEmbed(await embeds.getSafe("opendiscord:unclaim-message").build(origin,{guild,channel,user,ticket,reason})) + if (generalConfig.data.ticketSystem.enableTicketClaimButtons) instance.addComponent(await buttons.getSafe("opendiscord:claim-ticket").build("unclaim-message",{guild,channel,user,ticket})) }) ) //TICKET PINNED messages.add(new api.ODMessage("opendiscord:pin-message")) messages.get("opendiscord:pin-message").workers.add( - new api.ODWorker("opendiscord:pin-message",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:pin-message",0,async (instance,params,origin) => { const {guild,channel,user,ticket,reason} = params - instance.addEmbed(await embeds.getSafe("opendiscord:pin-message").build(source,{guild,channel,user,ticket,reason})) - if (generalConfig.data.system.enableTicketPinButtons) instance.addComponent(await buttons.getSafe("opendiscord:unpin-ticket").build("pin-message",{guild,channel,user,ticket})) + instance.addEmbed(await embeds.getSafe("opendiscord:pin-message").build(origin,{guild,channel,user,ticket,reason})) + if (generalConfig.data.ticketSystem.enableTicketPinButtons) instance.addComponent(await buttons.getSafe("opendiscord:unpin-ticket").build("pin-message",{guild,channel,user,ticket})) }) ) //TICKET UNPINNED messages.add(new api.ODMessage("opendiscord:unpin-message")) messages.get("opendiscord:unpin-message").workers.add( - new api.ODWorker("opendiscord:unpin-message",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:unpin-message",0,async (instance,params,origin) => { const {guild,channel,user,ticket,reason} = params - instance.addEmbed(await embeds.getSafe("opendiscord:unpin-message").build(source,{guild,channel,user,ticket,reason})) - if (generalConfig.data.system.enableTicketPinButtons) instance.addComponent(await buttons.getSafe("opendiscord:pin-ticket").build("unpin-message",{guild,channel,user,ticket})) + instance.addEmbed(await embeds.getSafe("opendiscord:unpin-message").build(origin,{guild,channel,user,ticket,reason})) + if (generalConfig.data.ticketSystem.enableTicketPinButtons) instance.addComponent(await buttons.getSafe("opendiscord:pin-ticket").build("unpin-message",{guild,channel,user,ticket})) }) ) //TICKET RENAMED messages.add(new api.ODMessage("opendiscord:rename-message")) messages.get("opendiscord:rename-message").workers.add( - new api.ODWorker("opendiscord:rename-message",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:rename-message",0,async (instance,params,origin) => { const {guild,channel,user,ticket,reason,data} = params - instance.addEmbed(await embeds.getSafe("opendiscord:rename-message").build(source,{guild,channel,user,ticket,reason,data})) + instance.addEmbed(await embeds.getSafe("opendiscord:rename-message").build(origin,{guild,channel,user,ticket,reason,data})) }) ) //TICKET MOVED messages.add(new api.ODMessage("opendiscord:move-message")) messages.get("opendiscord:move-message").workers.add( - new api.ODWorker("opendiscord:move-message",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:move-message",0,async (instance,params,origin) => { const {guild,channel,user,ticket,reason,data} = params - instance.addEmbed(await embeds.getSafe("opendiscord:move-message").build(source,{guild,channel,user,ticket,reason,data})) + instance.addEmbed(await embeds.getSafe("opendiscord:move-message").build(origin,{guild,channel,user,ticket,reason,data})) }) ) //TICKET USER ADDED messages.add(new api.ODMessage("opendiscord:add-message")) messages.get("opendiscord:add-message").workers.add( - new api.ODWorker("opendiscord:add-message",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:add-message",0,async (instance,params,origin) => { const {guild,channel,user,ticket,reason,data} = params - instance.addEmbed(await embeds.getSafe("opendiscord:add-message").build(source,{guild,channel,user,ticket,reason,data})) + instance.addEmbed(await embeds.getSafe("opendiscord:add-message").build(origin,{guild,channel,user,ticket,reason,data})) }) ) //TICKET USER REMOVED messages.add(new api.ODMessage("opendiscord:remove-message")) messages.get("opendiscord:remove-message").workers.add( - new api.ODWorker("opendiscord:remove-message",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:remove-message",0,async (instance,params,origin) => { const {guild,channel,user,ticket,reason,data} = params - instance.addEmbed(await embeds.getSafe("opendiscord:remove-message").build(source,{guild,channel,user,ticket,reason,data})) + instance.addEmbed(await embeds.getSafe("opendiscord:remove-message").build(origin,{guild,channel,user,ticket,reason,data})) }) ) //TICKET ACTION DM messages.add(new api.ODMessage("opendiscord:ticket-action-dm")) messages.get("opendiscord:ticket-action-dm").workers.add( - new api.ODWorker("opendiscord:ticket-action-dm",0,async (instance,params,source) => { - const {guild,channel,user,mode,ticket,reason,additionalData} = params - instance.addEmbed(await embeds.getSafe("opendiscord:ticket-action-dm").build(source,{guild,channel,user,mode,ticket,reason,additionalData})) + new api.ODWorker("opendiscord:ticket-action-dm",0,async (instance,params,origin) => { + const {guild,channel,user,mode,ticket,reason,additionalData,additionalData2} = params + instance.addEmbed(await embeds.getSafe("opendiscord:ticket-action-dm").build(origin,{guild,channel,user,mode,ticket,reason,additionalData,additionalData2})) }) ) //TICKET ACTION LOGS messages.add(new api.ODMessage("opendiscord:ticket-action-logs")) messages.get("opendiscord:ticket-action-logs").workers.add( - new api.ODWorker("opendiscord:ticket-action-logs",0,async (instance,params,source) => { - const {guild,channel,user,mode,ticket,reason,additionalData} = params - instance.addEmbed(await embeds.getSafe("opendiscord:ticket-action-logs").build(source,{guild,channel,user,mode,ticket,reason,additionalData})) + new api.ODWorker("opendiscord:ticket-action-logs",0,async (instance,params,origin) => { + const {guild,channel,user,mode,ticket,reason,additionalData,additionalData2} = params + instance.addEmbed(await embeds.getSafe("opendiscord:ticket-action-logs").build(origin,{guild,channel,user,mode,ticket,reason,additionalData,additionalData2})) }) ) } @@ -861,54 +585,54 @@ const blacklistMessages = () => { //BLACKLIST VIEW messages.add(new api.ODMessage("opendiscord:blacklist-view")) messages.get("opendiscord:blacklist-view").workers.add( - new api.ODWorker("opendiscord:blacklist-view",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:blacklist-view",0,async (instance,params,origin) => { const {guild,channel,user} = params - instance.addEmbed(await embeds.getSafe("opendiscord:blacklist-view").build(source,{guild,channel,user})) + instance.addEmbed(await embeds.getSafe("opendiscord:blacklist-view").build(origin,{guild,channel,user})) }) ) //BLACKLIST GET messages.add(new api.ODMessage("opendiscord:blacklist-get")) messages.get("opendiscord:blacklist-get").workers.add( - new api.ODWorker("opendiscord:blacklist-get",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:blacklist-get",0,async (instance,params,origin) => { const {guild,channel,user,data} = params - instance.addEmbed(await embeds.getSafe("opendiscord:blacklist-get").build(source,{guild,channel,user,data})) + instance.addEmbed(await embeds.getSafe("opendiscord:blacklist-get").build(origin,{guild,channel,user,data})) }) ) //BLACKLIST ADD messages.add(new api.ODMessage("opendiscord:blacklist-add")) messages.get("opendiscord:blacklist-add").workers.add( - new api.ODWorker("opendiscord:blacklist-add",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:blacklist-add",0,async (instance,params,origin) => { const {guild,channel,user,data,reason} = params - instance.addEmbed(await embeds.getSafe("opendiscord:blacklist-add").build(source,{guild,channel,user,data,reason})) + instance.addEmbed(await embeds.getSafe("opendiscord:blacklist-add").build(origin,{guild,channel,user,data,reason})) }) ) //BLACKLIST REMOVE messages.add(new api.ODMessage("opendiscord:blacklist-remove")) messages.get("opendiscord:blacklist-remove").workers.add( - new api.ODWorker("opendiscord:blacklist-remove",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:blacklist-remove",0,async (instance,params,origin) => { const {guild,channel,user,data,reason} = params - instance.addEmbed(await embeds.getSafe("opendiscord:blacklist-remove").build(source,{guild,channel,user,data,reason})) + instance.addEmbed(await embeds.getSafe("opendiscord:blacklist-remove").build(origin,{guild,channel,user,data,reason})) }) ) //BLACKLIST DM messages.add(new api.ODMessage("opendiscord:blacklist-dm")) messages.get("opendiscord:blacklist-dm").workers.add( - new api.ODWorker("opendiscord:blacklist-dm",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:blacklist-dm",0,async (instance,params,origin) => { const {guild,channel,user,mode,data,reason} = params - instance.addEmbed(await embeds.getSafe("opendiscord:blacklist-dm").build(source,{guild,channel,user,mode,data,reason})) + instance.addEmbed(await embeds.getSafe("opendiscord:blacklist-dm").build(origin,{guild,channel,user,mode,data,reason})) }) ) //BLACKLIST LOGS messages.add(new api.ODMessage("opendiscord:blacklist-logs")) messages.get("opendiscord:blacklist-logs").workers.add( - new api.ODWorker("opendiscord:blacklist-logs",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:blacklist-logs",0,async (instance,params,origin) => { const {guild,channel,user,mode,data,reason} = params - instance.addEmbed(await embeds.getSafe("opendiscord:blacklist-logs").build(source,{guild,channel,user,mode,data,reason})) + instance.addEmbed(await embeds.getSafe("opendiscord:blacklist-logs").build(origin,{guild,channel,user,mode,data,reason})) }) ) } @@ -917,40 +641,50 @@ const transcriptMessages = () => { //TRANSCRIPT TEXT READY messages.add(new api.ODMessage("opendiscord:transcript-text-ready")) messages.get("opendiscord:transcript-text-ready").workers.add( - new api.ODWorker("opendiscord:transcript-text-ready",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:transcript-text-ready",0,async (instance,params,origin) => { const {guild,channel,user,ticket,compiler,result} = params - instance.addEmbed(await embeds.getSafe("opendiscord:transcript-text-ready").build(source,{guild,channel,user,ticket,compiler,result})) - instance.addFile(await files.getSafe("opendiscord:text-transcript").build(source,{guild,channel,user,ticket,compiler,result})) + instance.addEmbed(await embeds.getSafe("opendiscord:transcript-text-ready").build(origin,{guild,channel,user,ticket,compiler,result})) + instance.addFile(await files.getSafe("opendiscord:text-transcript").build(origin,{guild,channel,user,ticket,compiler,result})) }) ) //TRANSCRIPT HTML READY messages.add(new api.ODMessage("opendiscord:transcript-html-ready")) messages.get("opendiscord:transcript-html-ready").workers.add( - new api.ODWorker("opendiscord:transcript-html-ready",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:transcript-html-ready",0,async (instance,params,origin) => { const {guild,channel,user,ticket,compiler,result} = params - instance.addEmbed(await embeds.getSafe("opendiscord:transcript-html-ready").build(source,{guild,channel,user,ticket,compiler,result})) - instance.addComponent(await buttons.getSafe("opendiscord:transcript-html-visit").build(source,{guild,channel,user,ticket,compiler,result})) + instance.addEmbed(await embeds.getSafe("opendiscord:transcript-html-ready").build(origin,{guild,channel,user,ticket,compiler,result})) + instance.addComponent(await buttons.getSafe("opendiscord:transcript-html-visit").build(origin,{guild,channel,user,ticket,compiler,result})) }) ) //TRANSCRIPT HTML PROGRESS messages.add(new api.ODMessage("opendiscord:transcript-html-progress")) messages.get("opendiscord:transcript-html-progress").workers.add( - new api.ODWorker("opendiscord:transcript-html-progress",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:transcript-html-progress",0,async (instance,params,origin) => { const {guild,channel,user,ticket,compiler,remaining} = params - instance.addEmbed(await embeds.getSafe("opendiscord:transcript-html-progress").build(source,{guild,channel,user,ticket,compiler,remaining})) + instance.addEmbed(await embeds.getSafe("opendiscord:transcript-html-progress").build(origin,{guild,channel,user,ticket,compiler,remaining})) }) ) //TRANSCRIPT ERROR messages.add(new api.ODMessage("opendiscord:transcript-error")) messages.get("opendiscord:transcript-error").workers.add( - new api.ODWorker("opendiscord:transcript-error",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:transcript-error",0,async (instance,params,origin) => { const {guild,channel,user,ticket,compiler,reason} = params - instance.addEmbed(await embeds.getSafe("opendiscord:transcript-error").build(source,{guild,channel,user,ticket,compiler,reason})) - instance.addComponent(await buttons.getSafe("opendiscord:transcript-error-retry").build(source,{guild,channel,user,ticket,compiler,reason})) - instance.addComponent(await buttons.getSafe("opendiscord:transcript-error-continue").build(source,{guild,channel,user,ticket,compiler,reason})) + instance.addEmbed(await embeds.getSafe("opendiscord:transcript-error").build(origin,{guild,channel,user,ticket,compiler,reason})) + instance.addComponent(await buttons.getSafe("opendiscord:transcript-error-retry").build(origin,{guild,channel,user,ticket,compiler,reason})) + instance.addComponent(await buttons.getSafe("opendiscord:transcript-error-continue").build(origin,{guild,channel,user,ticket,compiler,reason})) + }) + ) + + //TRANSCRIPT HISTORY + messages.add(new api.ODMessage("opendiscord:transcript-history")) + messages.get("opendiscord:transcript-history").workers.add( + new api.ODWorker("opendiscord:transcript-history",0,async (instance,params,origin) => { + const {guild,channel,user,transcriptUser,transcriptList} = params + instance.addEmbed(await embeds.getSafe("opendiscord:transcript-history").build(origin,{guild,channel,user,transcriptUser,transcriptList})) + instance.setEphemeral(true) }) ) } @@ -959,9 +693,9 @@ const roleMessages = () => { //REACTION ROLE messages.add(new api.ODMessage("opendiscord:reaction-role")) messages.get("opendiscord:reaction-role").workers.add( - new api.ODWorker("opendiscord:reaction-role",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:reaction-role",0,async (instance,params,origin) => { const {guild,user,role,result} = params - instance.addEmbed(await embeds.getSafe("opendiscord:reaction-role").build(source,{guild,user,role,result})) + instance.addEmbed(await embeds.getSafe("opendiscord:reaction-role").build(origin,{guild,user,role,result})) instance.setEphemeral(true) }) ) @@ -969,18 +703,18 @@ const roleMessages = () => { //REACTION ROLE DM messages.add(new api.ODMessage("opendiscord:reaction-role-dm")) messages.get("opendiscord:reaction-role-dm").workers.add( - new api.ODWorker("opendiscord:reaction-role-dm",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:reaction-role-dm",0,async (instance,params,origin) => { const {guild,user,role,result} = params - instance.addEmbed(await embeds.getSafe("opendiscord:reaction-role-dm").build(source,{guild,user,role,result})) + instance.addEmbed(await embeds.getSafe("opendiscord:reaction-role-dm").build(origin,{guild,user,role,result})) }) ) //REACTION ROLE LOGS messages.add(new api.ODMessage("opendiscord:reaction-role-logs")) messages.get("opendiscord:reaction-role-logs").workers.add( - new api.ODWorker("opendiscord:reaction-role-logs",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:reaction-role-logs",0,async (instance,params,origin) => { const {guild,user,role,result} = params - instance.addEmbed(await embeds.getSafe("opendiscord:reaction-role-logs").build(source,{guild,user,role,result})) + instance.addEmbed(await embeds.getSafe("opendiscord:reaction-role-logs").build(origin,{guild,user,role,result})) }) ) } @@ -989,10 +723,10 @@ const clearMessages = () => { //CLEAR VERIFY MESSAGE messages.add(new api.ODMessage("opendiscord:clear-verify-message")) messages.get("opendiscord:clear-verify-message").workers.add( - new api.ODWorker("opendiscord:clear-verify-message",0,async (instance,params,source) => { - const {guild,channel,user,filter,list} = params - instance.addEmbed(await embeds.getSafe("opendiscord:clear-verify-message").build(source,{guild,channel,user,filter,list})) - instance.addComponent(await buttons.getSafe("opendiscord:clear-continue").build(source,{guild,channel,user,filter,list})) + new api.ODWorker("opendiscord:clear-verify-message",0,async (instance,params,origin) => { + const {guild,channel,user,filter,list,inProgress} = params + instance.addEmbed(await embeds.getSafe("opendiscord:clear-verify-message").build(origin,{guild,channel,user,filter,list,inProgress})) + instance.addComponent(await buttons.getSafe("opendiscord:clear-continue").build(origin,{guild,channel,user,filter,list,inProgress})) instance.setEphemeral(true) }) ) @@ -1000,9 +734,9 @@ const clearMessages = () => { //CLEAR MESSAGE messages.add(new api.ODMessage("opendiscord:clear-message")) messages.get("opendiscord:clear-message").workers.add( - new api.ODWorker("opendiscord:clear-message",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:clear-message",0,async (instance,params,origin) => { const {guild,channel,user,filter,list} = params - instance.addEmbed(await embeds.getSafe("opendiscord:clear-message").build(source,{guild,channel,user,filter,list})) + instance.addEmbed(await embeds.getSafe("opendiscord:clear-message").build(origin,{guild,channel,user,filter,list})) instance.setEphemeral(true) }) ) @@ -1010,9 +744,9 @@ const clearMessages = () => { //CLEAR LOGS messages.add(new api.ODMessage("opendiscord:clear-logs")) messages.get("opendiscord:clear-logs").workers.add( - new api.ODWorker("opendiscord:clear-logs",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:clear-logs",0,async (instance,params,origin) => { const {guild,channel,user,filter,list} = params - instance.addEmbed(await embeds.getSafe("opendiscord:clear-logs").build(source,{guild,channel,user,filter,list})) + instance.addEmbed(await embeds.getSafe("opendiscord:clear-logs").build(origin,{guild,channel,user,filter,list})) }) ) } @@ -1021,56 +755,56 @@ const autoMessages = () => { //AUTOCLOSE MESSAGE messages.add(new api.ODMessage("opendiscord:autoclose-message")) messages.get("opendiscord:autoclose-message").workers.add( - new api.ODWorker("opendiscord:autoclose-message",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:autoclose-message",0,async (instance,params,origin) => { const {guild,channel,user,ticket} = params - instance.addEmbed(await embeds.getSafe("opendiscord:autoclose-message").build(source,{guild,channel,user,ticket})) - if (generalConfig.data.system.enableTicketCloseButtons) instance.addComponent(await buttons.getSafe("opendiscord:reopen-ticket").build("autoclose-message",{guild,channel,user,ticket})) - if (generalConfig.data.system.enableTicketDeleteButtons) instance.addComponent(await buttons.getSafe("opendiscord:delete-ticket").build("autoclose-message",{guild,channel,user,ticket})) + instance.addEmbed(await embeds.getSafe("opendiscord:autoclose-message").build(origin,{guild,channel,user,ticket})) + if (generalConfig.data.ticketSystem.enableTicketCloseButtons) instance.addComponent(await buttons.getSafe("opendiscord:reopen-ticket").build("autoclose-message",{guild,channel,user,ticket})) + if (generalConfig.data.ticketSystem.enableTicketDeleteButtons) instance.addComponent(await buttons.getSafe("opendiscord:delete-ticket").build("autoclose-message",{guild,channel,user,ticket})) }) ) //AUTODELETE MESSAGE messages.add(new api.ODMessage("opendiscord:autodelete-message")) messages.get("opendiscord:autodelete-message").workers.add( - new api.ODWorker("opendiscord:autodelete-message",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:autodelete-message",0,async (instance,params,origin) => { const {guild,channel,user,ticket} = params - instance.addEmbed(await embeds.getSafe("opendiscord:autodelete-message").build(source,{guild,channel,user,ticket})) + instance.addEmbed(await embeds.getSafe("opendiscord:autodelete-message").build(origin,{guild,channel,user,ticket})) }) ) //AUTOCLOSE ENABLE messages.add(new api.ODMessage("opendiscord:autoclose-enable")) messages.get("opendiscord:autoclose-enable").workers.add( - new api.ODWorker("opendiscord:autoclose-enable",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:autoclose-enable",0,async (instance,params,origin) => { const {guild,channel,user,ticket,reason,time} = params - instance.addEmbed(await embeds.getSafe("opendiscord:autoclose-enable").build(source,{guild,channel,user,ticket,reason,time})) + instance.addEmbed(await embeds.getSafe("opendiscord:autoclose-enable").build(origin,{guild,channel,user,ticket,reason,time})) }) ) //AUTODELETE ENABLE messages.add(new api.ODMessage("opendiscord:autodelete-enable")) messages.get("opendiscord:autodelete-enable").workers.add( - new api.ODWorker("opendiscord:autodelete-enable",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:autodelete-enable",0,async (instance,params,origin) => { const {guild,channel,user,ticket,reason,time} = params - instance.addEmbed(await embeds.getSafe("opendiscord:autodelete-enable").build(source,{guild,channel,user,ticket,reason,time})) + instance.addEmbed(await embeds.getSafe("opendiscord:autodelete-enable").build(origin,{guild,channel,user,ticket,reason,time})) }) ) //AUTOCLOSE DISABLE messages.add(new api.ODMessage("opendiscord:autoclose-disable")) messages.get("opendiscord:autoclose-disable").workers.add( - new api.ODWorker("opendiscord:autoclose-disable",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:autoclose-disable",0,async (instance,params,origin) => { const {guild,channel,user,ticket,reason} = params - instance.addEmbed(await embeds.getSafe("opendiscord:autoclose-disable").build(source,{guild,channel,user,ticket,reason})) + instance.addEmbed(await embeds.getSafe("opendiscord:autoclose-disable").build(origin,{guild,channel,user,ticket,reason})) }) ) //AUTODELETE DISABLE messages.add(new api.ODMessage("opendiscord:autodelete-disable")) messages.get("opendiscord:autodelete-disable").workers.add( - new api.ODWorker("opendiscord:autodelete-disable",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:autodelete-disable",0,async (instance,params,origin) => { const {guild,channel,user,ticket,reason} = params - instance.addEmbed(await embeds.getSafe("opendiscord:autodelete-disable").build(source,{guild,channel,user,ticket,reason})) + instance.addEmbed(await embeds.getSafe("opendiscord:autodelete-disable").build(origin,{guild,channel,user,ticket,reason})) }) ) } @@ -1079,36 +813,36 @@ const extraMessages = () => { //TOPIC SET messages.add(new api.ODMessage("opendiscord:topic-set")) messages.get("opendiscord:topic-set").workers.add( - new api.ODWorker("opendiscord:topic-set",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:topic-set",0,async (instance,params,origin) => { const {guild,channel,user,ticket,topic} = params - instance.addEmbed(await embeds.getSafe("opendiscord:topic-set").build(source,{guild,channel,user,ticket,topic})) + instance.addEmbed(await embeds.getSafe("opendiscord:topic-set").build(origin,{guild,channel,user,ticket,topic})) }) ) //PRIORITY SET messages.add(new api.ODMessage("opendiscord:priority-set")) messages.get("opendiscord:priority-set").workers.add( - new api.ODWorker("opendiscord:priority-set",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:priority-set",0,async (instance,params,origin) => { const {guild,channel,user,ticket,priority,reason} = params - instance.addEmbed(await embeds.getSafe("opendiscord:priority-set").build(source,{guild,channel,user,ticket,priority,reason})) + instance.addEmbed(await embeds.getSafe("opendiscord:priority-set").build(origin,{guild,channel,user,ticket,priority,reason})) }) ) //PRIORITY GET messages.add(new api.ODMessage("opendiscord:priority-get")) messages.get("opendiscord:priority-get").workers.add( - new api.ODWorker("opendiscord:priority-get",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:priority-get",0,async (instance,params,origin) => { const {guild,channel,user,ticket,priority} = params - instance.addEmbed(await embeds.getSafe("opendiscord:priority-get").build(source,{guild,channel,user,ticket,priority})) + instance.addEmbed(await embeds.getSafe("opendiscord:priority-get").build(origin,{guild,channel,user,ticket,priority})) }) ) //TRANSFER MESSAGE messages.add(new api.ODMessage("opendiscord:transfer-message")) messages.get("opendiscord:transfer-message").workers.add( - new api.ODWorker("opendiscord:transfer-message",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:transfer-message",0,async (instance,params,origin) => { const {guild,channel,user,ticket,oldCreator,newCreator,reason} = params - instance.addEmbed(await embeds.getSafe("opendiscord:transfer-message").build(source,{guild,channel,user,ticket,oldCreator,newCreator,reason})) + instance.addEmbed(await embeds.getSafe("opendiscord:transfer-message").build(origin,{guild,channel,user,ticket,oldCreator,newCreator,reason})) }) ) } \ No newline at end of file diff --git a/src/builders/modals.ts b/src/builders/modals.ts deleted file mode 100644 index df7d222..0000000 --- a/src/builders/modals.ts +++ /dev/null @@ -1,173 +0,0 @@ -/////////////////////////////////////// -//MODAL BUILDERS -/////////////////////////////////////// -import {opendiscord, api, utilities} from "../index" -import * as discord from "discord.js" - -const modals = opendiscord.builders.modals -const lang = opendiscord.languages - -export const registerAllModals = async () => { - ticketModals() -} -const ticketModals = () => { - //TICKET QUESTIONS - modals.add(new api.ODModal("opendiscord:ticket-questions")) - modals.get("opendiscord:ticket-questions").workers.add( - new api.ODWorker("opendiscord:ticket-questions",0,async (instance,params,source) => { - const {option} = params - - instance.setCustomId("od:ticket-questions_"+option.id.value+"_"+source) - instance.setTitle(option.exists("opendiscord:name") ? option.get("opendiscord:name").value : option.id.value) - const questionIds = option.get("opendiscord:questions").value - questionIds.forEach((id) => { - const question = opendiscord.questions.get(id) - if (!question) return - if (question instanceof api.ODShortQuestion) instance.addQuestion({ - customId:question.id.value, - label:question.get("opendiscord:name").value, - style:"short", - required:question.get("opendiscord:required").value, - placeholder:(question.get("opendiscord:placeholder").value) ? question.get("opendiscord:placeholder").value : undefined, - minLength:(question.get("opendiscord:length-enabled").value) ? question.get("opendiscord:length-min").value : undefined, - maxLength:(question.get("opendiscord:length-enabled").value) ? question.get("opendiscord:length-max").value : undefined - }) - else if (question instanceof api.ODParagraphQuestion) instance.addQuestion({ - customId:question.id.value, - label:question.get("opendiscord:name").value, - style:"paragraph", - required:question.get("opendiscord:required").value, - placeholder:(question.get("opendiscord:placeholder").value) ? question.get("opendiscord:placeholder").value : undefined, - minLength:(question.get("opendiscord:length-enabled").value) ? question.get("opendiscord:length-min").value : undefined, - maxLength:(question.get("opendiscord:length-enabled").value) ? question.get("opendiscord:length-max").value : undefined - }) - }) - }) - ) - - //CLOSE TICKET REASON - modals.add(new api.ODModal("opendiscord:close-ticket-reason")) - modals.get("opendiscord:close-ticket-reason").workers.add( - new api.ODWorker("opendiscord:close-ticket-reason",0,async (instance,params,source) => { - const {ticket} = params - - instance.setCustomId("od:close-ticket-reason_"+ticket.id.value+"_"+source) - instance.setTitle(lang.getTranslation("actions.buttons.close")) - instance.addQuestion({ - customId:"reason", - label:lang.getTranslation("params.uppercase.reason"), - style:"paragraph", - required:true, - placeholder:lang.getTranslation("actions.modal.closePlaceholder") - }) - }) - ) - - //REOPEN TICKET REASON - modals.add(new api.ODModal("opendiscord:reopen-ticket-reason")) - modals.get("opendiscord:reopen-ticket-reason").workers.add( - new api.ODWorker("opendiscord:reopen-ticket-reason",0,async (instance,params,source) => { - const {ticket} = params - - instance.setCustomId("od:reopen-ticket-reason_"+ticket.id.value+"_"+source) - instance.setTitle(lang.getTranslation("actions.buttons.reopen")) - instance.addQuestion({ - customId:"reason", - label:lang.getTranslation("params.uppercase.reason"), - style:"paragraph", - required:true, - placeholder:lang.getTranslation("actions.modal.reopenPlaceholder") - }) - }) - ) - - //DELETE TICKET REASON - modals.add(new api.ODModal("opendiscord:delete-ticket-reason")) - modals.get("opendiscord:delete-ticket-reason").workers.add( - new api.ODWorker("opendiscord:delete-ticket-reason",0,async (instance,params,source) => { - const {ticket} = params - - instance.setCustomId("od:delete-ticket-reason_"+ticket.id.value+"_"+source) - instance.setTitle(lang.getTranslation("actions.buttons.delete")) - instance.addQuestion({ - customId:"reason", - label:lang.getTranslation("params.uppercase.reason"), - style:"paragraph", - required:true, - placeholder:lang.getTranslation("actions.modal.deletePlaceholder") - }) - }) - ) - - //CLAIM TICKET REASON - modals.add(new api.ODModal("opendiscord:claim-ticket-reason")) - modals.get("opendiscord:claim-ticket-reason").workers.add( - new api.ODWorker("opendiscord:claim-ticket-reason",0,async (instance,params,source) => { - const {ticket} = params - - instance.setCustomId("od:claim-ticket-reason_"+ticket.id.value+"_"+source) - instance.setTitle(lang.getTranslation("actions.buttons.claim")) - instance.addQuestion({ - customId:"reason", - label:lang.getTranslation("params.uppercase.reason"), - style:"paragraph", - required:true, - placeholder:lang.getTranslation("actions.modal.claimPlaceholder") - }) - }) - ) - - //UNCLAIM TICKET REASON - modals.add(new api.ODModal("opendiscord:unclaim-ticket-reason")) - modals.get("opendiscord:unclaim-ticket-reason").workers.add( - new api.ODWorker("opendiscord:unclaim-ticket-reason",0,async (instance,params,source) => { - const {ticket} = params - - instance.setCustomId("od:unclaim-ticket-reason_"+ticket.id.value+"_"+source) - instance.setTitle(lang.getTranslation("actions.buttons.unclaim")) - instance.addQuestion({ - customId:"reason", - label:lang.getTranslation("params.uppercase.reason"), - style:"paragraph", - required:true, - placeholder:lang.getTranslation("actions.modal.unclaimPlaceholder") - }) - }) - ) - - //PIN TICKET REASON - modals.add(new api.ODModal("opendiscord:pin-ticket-reason")) - modals.get("opendiscord:pin-ticket-reason").workers.add( - new api.ODWorker("opendiscord:pin-ticket-reason",0,async (instance,params,source) => { - const {ticket} = params - - instance.setCustomId("od:pin-ticket-reason_"+ticket.id.value+"_"+source) - instance.setTitle(lang.getTranslation("actions.buttons.pin")) - instance.addQuestion({ - customId:"reason", - label:lang.getTranslation("params.uppercase.reason"), - style:"paragraph", - required:true, - placeholder:lang.getTranslation("actions.modal.pinPlaceholder") - }) - }) - ) - - //UNPIN TICKET REASON - modals.add(new api.ODModal("opendiscord:unpin-ticket-reason")) - modals.get("opendiscord:unpin-ticket-reason").workers.add( - new api.ODWorker("opendiscord:unpin-ticket-reason",0,async (instance,params,source) => { - const {ticket} = params - - instance.setCustomId("od:unpin-ticket-reason_"+ticket.id.value+"_"+source) - instance.setTitle(lang.getTranslation("actions.buttons.unpin")) - instance.addQuestion({ - customId:"reason", - label:lang.getTranslation("params.uppercase.reason"), - style:"paragraph", - required:true, - placeholder:lang.getTranslation("actions.modal.unpinPlaceholder") - }) - }) - ) -} \ No newline at end of file diff --git a/src/commands/add.ts b/src/commands/add.ts index 43d4b89..dd1dec6 100644 --- a/src/commands/add.ts +++ b/src/commands/add.ts @@ -1,45 +1,32 @@ /////////////////////////////////////// //ADD COMMAND /////////////////////////////////////// -import {opendiscord, api, utilities} from "../index" +import {opendiscord, api, utilities, openticketUtils} from "../index.js" import * as discord from "discord.js" const generalConfig = opendiscord.configs.get("opendiscord:general") -export const registerCommandResponders = async () => { +export async function registerCommandResponders(){ //ADD COMMAND RESPONDER opendiscord.responders.commands.add(new api.ODCommandResponder("opendiscord:add",generalConfig.data.prefix,"add")) opendiscord.responders.commands.get("opendiscord:add").workers.add([ - new api.ODWorker("opendiscord:add",0,async (instance,params,source,cancel) => { + new api.ODWorker("opendiscord:add",0,async (instance,params,origin,cancel) => { const {guild,channel,user,member} = instance - //check permissions - const permsResult = await opendiscord.permissions.checkCommandPerms(generalConfig.data.system.permissions.add,"support",user,member,channel,guild) - if (!permsResult.hasPerms){ - if (permsResult.reason == "not-in-server") await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-not-in-guild").build("button",{channel,user})) - else await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-no-permissions").build(source,{guild,channel,user,permissions:["support"]})) - return cancel() - } - - //check if in guild/Server - if (!guild){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-not-in-guild").build("button",{channel,user})) - return cancel() - } - - //check if ticket exists - const ticket = opendiscord.tickets.get(channel.id) - if (!ticket || channel.isDMBased()){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-ticket-unknown").build("button",{guild,channel,user})) - return cancel() - } - - //return when busy - if (ticket.get("opendiscord:busy").value){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-ticket-busy").build("button",{guild,channel,user})) - return cancel() - } + //responder checks + const hasPerms = await openticketUtils.replyHasPermissions(instance,origin,"add") + if (!hasPerms) return cancel() + + const isInGuild = await openticketUtils.replyIsInGuild(instance,origin) + if (!isInGuild || !guild || channel.isDMBased()) return cancel() + + const ticket = await openticketUtils.replyIsTicket(instance,origin) + if (!ticket) return cancel() + + const isAvailable = await openticketUtils.replyTicketIsAvailable(instance,origin,ticket) + if (!isAvailable) return cancel() + //fetch data const data = instance.options.getUser("user",true) const reason = instance.options.getString("reason",false) @@ -52,15 +39,15 @@ export const registerCommandResponders = async () => { //start adding user to ticket await instance.defer(false) - await opendiscord.actions.get("opendiscord:add-ticket-user").run(source,{guild,channel,user,ticket,reason,sendMessage:false,data}) - await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:add-message").build(source,{guild,channel,user,ticket,reason,data})) + await opendiscord.actions.get("opendiscord:add-ticket-user").run(origin,{guild,channel,user,ticket,reason,sendMessage:false,data}) + await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:add-message").build(origin,{guild,channel,user,ticket,reason,data})) }), - new api.ODWorker("opendiscord:logs",-1,(instance,params,source,cancel) => { + new api.ODWorker("opendiscord:logs",-1,(instance,params,origin,cancel) => { opendiscord.log(instance.user.displayName+" used the 'add' command!","info",[ {key:"user",value:instance.user.username}, {key:"userid",value:instance.user.id,hidden:true}, {key:"channelid",value:instance.channel.id,hidden:true}, - {key:"method",value:source} + {key:"method",value:origin} ]) }) ]) diff --git a/src/commands/autoclose.ts b/src/commands/autoclose.ts index bdf5a8b..ba66cf7 100644 --- a/src/commands/autoclose.ts +++ b/src/commands/autoclose.ts @@ -1,50 +1,33 @@ /////////////////////////////////////// //AUTOCLOSE COMMAND /////////////////////////////////////// -import {opendiscord, api, utilities} from "../index" +import {opendiscord, api, utilities, openticketUtils} from "../index.js" import * as discord from "discord.js" const generalConfig = opendiscord.configs.get("opendiscord:general") -export const registerCommandResponders = async () => { +export async function registerCommandResponders(){ //AUTOCLOSE COMMAND RESPONDER opendiscord.responders.commands.add(new api.ODCommandResponder("opendiscord:autoclose",generalConfig.data.prefix,/^autoclose/)) opendiscord.responders.commands.get("opendiscord:autoclose").workers.add([ - new api.ODWorker("opendiscord:autoclose",0,async (instance,params,source,cancel) => { + new api.ODWorker("opendiscord:autoclose",0,async (instance,params,origin,cancel) => { const {guild,channel,user,member} = instance - - //check permissions - const permsResult = await opendiscord.permissions.checkCommandPerms(generalConfig.data.system.permissions.autoclose,"support",user,member,channel,guild) - if (!permsResult.hasPerms){ - if (permsResult.reason == "not-in-server") await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-not-in-guild").build("button",{channel,user})) - else await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-no-permissions").build(source,{guild,channel,user,permissions:["support"]})) - return cancel() - } - //check is in guild/server - if (!guild){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-not-in-guild").build(source,{channel:instance.channel,user:instance.user})) - return cancel() - } + //responder checks + const hasPerms = await openticketUtils.replyHasPermissions(instance,origin,"autoclose") + if (!hasPerms) return cancel() + + const isInGuild = await openticketUtils.replyIsInGuild(instance,origin) + if (!isInGuild || !guild || channel.isDMBased()) return cancel() + + const ticket = await openticketUtils.replyIsTicket(instance,origin) + if (!ticket) return cancel() + + const isAvailable = await openticketUtils.replyTicketIsAvailable(instance,origin,ticket) + if (!isAvailable) return cancel() - //check if ticket exists - const ticket = opendiscord.tickets.get(channel.id) - if (!ticket || channel.isDMBased()){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-ticket-unknown").build("button",{guild,channel,user})) - return - } - - //return when already closed - if (ticket.get("opendiscord:closed").value){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error").build("button",{guild,channel,user,error:opendiscord.languages.getTranslation("errors.actionInvalid.close"),layout:"simple"})) - return cancel() - } - - //return when busy - if (ticket.get("opendiscord:busy").value){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-ticket-busy").build("button",{guild,channel,user})) - return cancel() - } + const isTicketOpen = await openticketUtils.replyTicketMustBeOpen(instance,origin,ticket) + if (!isTicketOpen) return cancel() //subcommands const scope = instance.options.getSubCommand() @@ -54,40 +37,27 @@ export const registerCommandResponders = async () => { const reason = instance.options.getString("reason",false) ticket.get("opendiscord:autoclose-enabled").value = false ticket.get("opendiscord:autoclose-hours").value = 0 - await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:autoclose-disable").build(source,{guild,channel,user,ticket,reason})) + await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:autoclose-disable").build(origin,{guild,channel,user,ticket,reason})) }else if (scope == "enable"){ const time = instance.options.getNumber("time",true) const reason = instance.options.getString("reason",false) ticket.get("opendiscord:autoclose-enabled").value = true ticket.get("opendiscord:autoclose-hours").value = time - await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:autoclose-enable").build(source,{guild,channel,user,ticket,reason,time})) + await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:autoclose-enable").build(origin,{guild,channel,user,ticket,reason,time})) } - //update ticket message - const ticketMessage = await opendiscord.tickets.getTicketMessage(ticket) - if (ticketMessage){ - try{ - ticketMessage.edit((await opendiscord.builders.messages.getSafe("opendiscord:ticket-message").build("other",{guild,channel,user,ticket})).message) - }catch(e){ - opendiscord.log("Unable to edit ticket message on autoclose "+scope+"!","error",[ - {key:"channel",value:"#"+channel.name}, - {key:"channelid",value:channel.id,hidden:true}, - {key:"messageid",value:ticketMessage.id}, - {key:"option",value:ticket.option.id.value} - ]) - opendiscord.debugfile.writeErrorMessage(new api.ODError(e,"uncaughtException")) - } - } + //update ticket message (no await) + openticketUtils.updateTicketMessage(guild,channel,user,ticket) }), - new api.ODWorker("opendiscord:logs",-1,(instance,params,source,cancel) => { + new api.ODWorker("opendiscord:logs",-1,(instance,params,origin,cancel) => { const scope = instance.options.getSubCommand() const reason = instance.options.getString("reason",false) opendiscord.log(instance.user.displayName+" used the 'autoclose "+scope+"' command!","info",[ {key:"user",value:instance.user.username}, {key:"userid",value:instance.user.id,hidden:true}, {key:"channelid",value:instance.channel.id,hidden:true}, - {key:"method",value:source}, + {key:"method",value:origin}, {key:"reason",value:reason ?? "/"}, ]) }) diff --git a/src/commands/autocomplete.ts b/src/commands/autocomplete.ts index e7cac2a..2dbd7f2 100644 --- a/src/commands/autocomplete.ts +++ b/src/commands/autocomplete.ts @@ -1,13 +1,13 @@ /////////////////////////////////////// //AUTOCOMPLETE COMMAND UTILS /////////////////////////////////////// -import {opendiscord, api, utilities} from "../index" +import {opendiscord, api, utilities} from "../index.js" import * as discord from "discord.js" -export const registerAutocompleteResponders = async () => { +export async function registerAutocompleteResponders(){ //PANEL ID AUTOCOMPLETE opendiscord.responders.autocomplete.add(new api.ODAutocompleteResponder("opendiscord:panel-id","panel","id")) - opendiscord.responders.autocomplete.get("opendiscord:panel-id").workers.add(new api.ODWorker("opendiscord:panel-id",0,async (instance,params,source,cancel) => { + opendiscord.responders.autocomplete.get("opendiscord:panel-id").workers.add(new api.ODWorker("opendiscord:panel-id",0,async (instance,params,origin,cancel) => { //create panel choices const panelChoices : {name:string, value:string}[] = [] opendiscord.configs.get("opendiscord:panels").data.forEach((panel) => { @@ -19,7 +19,7 @@ export const registerAutocompleteResponders = async () => { //OPTION ID AUTOCOMPLETE opendiscord.responders.autocomplete.add(new api.ODAutocompleteResponder("opendiscord:option-id",/ticket|move/,"id")) - opendiscord.responders.autocomplete.get("opendiscord:option-id").workers.add(new api.ODWorker("opendiscord:option-id",0,async (instance,params,source,cancel) => { + opendiscord.responders.autocomplete.get("opendiscord:option-id").workers.add(new api.ODWorker("opendiscord:option-id",0,async (instance,params,origin,cancel) => { //create ticket choices const ticketChoices : {name:string, value:string}[] = [] opendiscord.configs.get("opendiscord:options").data.forEach((option) => { diff --git a/src/commands/autodelete.ts b/src/commands/autodelete.ts index f912668..235c2cf 100644 --- a/src/commands/autodelete.ts +++ b/src/commands/autodelete.ts @@ -1,45 +1,30 @@ /////////////////////////////////////// //AUTODELETE COMMAND /////////////////////////////////////// -import {opendiscord, api, utilities} from "../index" +import {opendiscord, api, utilities, openticketUtils} from "../index.js" import * as discord from "discord.js" const generalConfig = opendiscord.configs.get("opendiscord:general") -export const registerCommandResponders = async () => { +export async function registerCommandResponders(){ //AUTODELETE COMMAND RESPONDER opendiscord.responders.commands.add(new api.ODCommandResponder("opendiscord:autodelete",generalConfig.data.prefix,/^autodelete/)) opendiscord.responders.commands.get("opendiscord:autodelete").workers.add([ - new api.ODWorker("opendiscord:autodelete",0,async (instance,params,source,cancel) => { + new api.ODWorker("opendiscord:autodelete",0,async (instance,params,origin,cancel) => { const {guild,channel,user,member} = instance - //check permissions - const permsResult = await opendiscord.permissions.checkCommandPerms(generalConfig.data.system.permissions.autodelete,"support",user,member,channel,guild) - if (!permsResult.hasPerms){ - if (permsResult.reason == "not-in-server") await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-not-in-guild").build("button",{channel,user})) - else await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-no-permissions").build(source,{guild,channel,user,permissions:["support"]})) - return cancel() - } - - //check is in guild/server - if (!guild){ - //error - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-not-in-guild").build(source,{channel:instance.channel,user:instance.user})) - return cancel() - } - - //check if ticket exists - const ticket = opendiscord.tickets.get(channel.id) - if (!ticket || channel.isDMBased()){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-ticket-unknown").build("button",{guild,channel,user})) - return - } - - //return when busy - if (ticket.get("opendiscord:busy").value){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-ticket-busy").build("button",{guild,channel,user})) - return cancel() - } + //responder checks + const hasPerms = await openticketUtils.replyHasPermissions(instance,origin,"autodelete") + if (!hasPerms) return cancel() + + const isInGuild = await openticketUtils.replyIsInGuild(instance,origin) + if (!isInGuild || !guild || channel.isDMBased()) return cancel() + + const ticket = await openticketUtils.replyIsTicket(instance,origin) + if (!ticket) return cancel() + + const isAvailable = await openticketUtils.replyTicketIsAvailable(instance,origin,ticket) + if (!isAvailable) return cancel() //subcommands const scope = instance.options.getSubCommand() @@ -49,40 +34,27 @@ export const registerCommandResponders = async () => { const reason = instance.options.getString("reason",false) ticket.get("opendiscord:autodelete-enabled").value = false ticket.get("opendiscord:autodelete-days").value = 0 - await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:autodelete-disable").build(source,{guild,channel,user,ticket,reason})) + await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:autodelete-disable").build(origin,{guild,channel,user,ticket,reason})) }else if (scope == "enable"){ const time = instance.options.getNumber("time",true) const reason = instance.options.getString("reason",false) ticket.get("opendiscord:autodelete-enabled").value = true ticket.get("opendiscord:autodelete-days").value = time - await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:autodelete-enable").build(source,{guild,channel,user,ticket,reason,time})) + await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:autodelete-enable").build(origin,{guild,channel,user,ticket,reason,time})) } - //update ticket message - const ticketMessage = await opendiscord.tickets.getTicketMessage(ticket) - if (ticketMessage){ - try{ - ticketMessage.edit((await opendiscord.builders.messages.getSafe("opendiscord:ticket-message").build("other",{guild,channel,user,ticket})).message) - }catch(e){ - opendiscord.log("Unable to edit ticket message on autodelete "+scope+"!","error",[ - {key:"channel",value:"#"+channel.name}, - {key:"channelid",value:channel.id,hidden:true}, - {key:"messageid",value:ticketMessage.id}, - {key:"option",value:ticket.option.id.value} - ]) - opendiscord.debugfile.writeErrorMessage(new api.ODError(e,"uncaughtException")) - } - } + //update ticket message (no await) + openticketUtils.updateTicketMessage(guild,channel,user,ticket) }), - new api.ODWorker("opendiscord:logs",-1,(instance,params,source,cancel) => { + new api.ODWorker("opendiscord:logs",-1,(instance,params,origin,cancel) => { const scope = instance.options.getSubCommand() const reason = instance.options.getString("reason",false) opendiscord.log(instance.user.displayName+" used the 'autodelete "+scope+"' command!","info",[ {key:"user",value:instance.user.username}, {key:"userid",value:instance.user.id,hidden:true}, {key:"channelid",value:instance.channel.id,hidden:true}, - {key:"method",value:source}, + {key:"method",value:origin}, {key:"reason",value:reason ?? "/"}, ]) }) diff --git a/src/commands/blacklist.ts b/src/commands/blacklist.ts index 16e8548..10d0d18 100644 --- a/src/commands/blacklist.ts +++ b/src/commands/blacklist.ts @@ -1,29 +1,29 @@ /////////////////////////////////////// //BLACKLIST COMMAND /////////////////////////////////////// -import {opendiscord, api, utilities} from "../index" +import {opendiscord, api, utilities, openticketUtils} from "../index.js" import * as discord from "discord.js" const generalConfig = opendiscord.configs.get("opendiscord:general") -export const registerCommandResponders = async () => { +export async function registerCommandResponders(){ //BLACKLIST COMMAND RESPONDER opendiscord.responders.commands.add(new api.ODCommandResponder("opendiscord:blacklist",generalConfig.data.prefix,/^blacklist/)) opendiscord.responders.commands.get("opendiscord:blacklist").workers.add([ - new api.ODWorker("opendiscord:blacklist",0,async (instance,params,source,cancel) => { + new api.ODWorker("opendiscord:blacklist",0,async (instance,params,origin,cancel) => { const {guild,channel,user,member} = instance //check permissions - const permsResult = await opendiscord.permissions.checkCommandPerms(generalConfig.data.system.permissions.blacklist,"support",user,member,channel,guild) + const permsResult = await opendiscord.permissions.checkCommandPerms(generalConfig.data.permissions.blacklist,"support",user,member,channel,guild) if (!permsResult.hasPerms){ if (permsResult.reason == "not-in-server") await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-not-in-guild").build("button",{channel,user})) - else await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-no-permissions").build(source,{guild,channel,user,permissions:["support"]})) + else await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-no-permissions").build(origin,{guild,channel,user,permissions:["support"]})) return cancel() } //check is in guild/server if (!guild){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-not-in-guild").build(source,{channel:instance.channel,user:instance.user})) + instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-not-in-guild").build(origin,{channel:instance.channel,user:instance.user})) return cancel() } @@ -31,11 +31,11 @@ export const registerCommandResponders = async () => { const scope = instance.options.getSubCommand() if (!scope || (scope != "add" && scope != "get" && scope != "remove" && scope != "view")) return if (scope == "view"){ - await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:blacklist-view").build(source,{guild,channel,user})) + await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:blacklist-view").build(origin,{guild,channel,user})) }else if (scope == "get"){ const data = instance.options.getUser("user",true) - await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:blacklist-get").build(source,{guild,channel,user,data})) + await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:blacklist-get").build(origin,{guild,channel,user,data})) }else if (scope == "add"){ const data = instance.options.getUser("user",true) @@ -46,15 +46,15 @@ export const registerCommandResponders = async () => { {key:"user",value:user.username}, {key:"userid",value:user.id,hidden:true}, {key:"channelid",value:channel.id,hidden:true}, - {key:"method",value:source}, + {key:"method",value:origin}, {key:"reason",value:reason ?? "/"} ]) //manage stats - await opendiscord.stats.get("opendiscord:global").setStat("opendiscord:users-blacklisted",1,"increase") - await opendiscord.stats.get("opendiscord:user").setStat("opendiscord:users-blacklisted",user.id,1,"increase") + await opendiscord.statistics.get("opendiscord:global").setStat("opendiscord:users-blacklisted",1,"increase") + await opendiscord.statistics.get("opendiscord:user").setStat("opendiscord:users-blacklisted",user.id,1,"increase") - await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:blacklist-add").build(source,{guild,channel,user,data,reason})) + await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:blacklist-add").build(origin,{guild,channel,user,data,reason})) }else if (scope == "remove"){ const data = instance.options.getUser("user",true) @@ -65,14 +65,14 @@ export const registerCommandResponders = async () => { {key:"user",value:user.username}, {key:"userid",value:user.id,hidden:true}, {key:"channelid",value:channel.id,hidden:true}, - {key:"method",value:source}, + {key:"method",value:origin}, {key:"reason",value:reason ?? "/"} ]) - await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:blacklist-remove").build(source,{guild,channel,user,data,reason})) + await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:blacklist-remove").build(origin,{guild,channel,user,data,reason})) } }), - new api.ODWorker("opendiscord:discord-logs",1,async (instance,params,source,cancel) => { + new api.ODWorker("opendiscord:discord-logs",1,async (instance,params,origin,cancel) => { const {guild,channel,user} = instance if (!guild) return @@ -83,21 +83,21 @@ export const registerCommandResponders = async () => { const reason = instance.options.getString("reason",false) //to logs - if (generalConfig.data.system.logs.enabled && generalConfig.data.system.messages.blacklisting.logs){ + if (generalConfig.data.logs.enabled && generalConfig.data.logs.logMessages.blacklisting.logs){ const logChannel = opendiscord.posts.get("opendiscord:logs") - if (logChannel) logChannel.send(await opendiscord.builders.messages.getSafe("opendiscord:blacklist-logs").build(source,{guild,channel,user,mode:scope,data,reason})) + if (logChannel) logChannel.send(await opendiscord.builders.messages.getSafe("opendiscord:blacklist-logs").build(origin,{guild,channel,user,mode:scope,data,reason})) } //to dm - if (generalConfig.data.system.messages.blacklisting.dm) await opendiscord.client.sendUserDm(user,await opendiscord.builders.messages.getSafe("opendiscord:blacklist-dm").build(source,{guild,channel,user,mode:scope,data,reason})) + if (generalConfig.data.logs.logMessages.blacklisting.dm) await opendiscord.client.sendUserDm(user,await opendiscord.builders.messages.getSafe("opendiscord:blacklist-dm").build(origin,{guild,channel,user,mode:scope,data,reason})) }), - new api.ODWorker("opendiscord:logs",-1,(instance,params,source,cancel) => { + new api.ODWorker("opendiscord:logs",-1,(instance,params,origin,cancel) => { const scope = instance.options.getSubCommand() opendiscord.log(instance.user.displayName+" used the 'blacklist "+scope+"' command!","info",[ {key:"user",value:instance.user.username}, {key:"userid",value:instance.user.id,hidden:true}, {key:"channelid",value:instance.channel.id,hidden:true}, - {key:"method",value:source} + {key:"method",value:origin} ]) }) ]) diff --git a/src/commands/claim.ts b/src/commands/claim.ts index 72c2b6c..a2eb3a9 100644 --- a/src/commands/claim.ts +++ b/src/commands/claim.ts @@ -1,119 +1,235 @@ /////////////////////////////////////// //CLAIM COMMAND /////////////////////////////////////// -import {opendiscord, api, utilities} from "../index" +import {opendiscord, api, utilities, openticketUtils} from "../index.js" import * as discord from "discord.js" const generalConfig = opendiscord.configs.get("opendiscord:general") +const lang = opendiscord.languages +const interactiveMsgState = opendiscord.states.get("opendiscord:interactive-message") -export const registerCommandResponders = async () => { +export async function registerCommandResponders(){ //CLAIM COMMAND RESPONDER opendiscord.responders.commands.add(new api.ODCommandResponder("opendiscord:claim",generalConfig.data.prefix,"claim")) opendiscord.responders.commands.get("opendiscord:claim").workers.add([ - new api.ODWorker("opendiscord:claim",0,async (instance,params,source,cancel) => { + new api.ODWorker("opendiscord:claim",0,async (instance,params,origin,cancel) => { const {guild,channel,user,member} = instance - //check permissions - const permsResult = await opendiscord.permissions.checkCommandPerms(generalConfig.data.system.permissions.claim,"support",user,member,channel,guild) - if (!permsResult.hasPerms){ - if (permsResult.reason == "not-in-server") await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-not-in-guild").build("button",{channel,user})) - else await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-no-permissions").build(source,{guild,channel,user,permissions:["support"]})) - return cancel() - } - - //check is in guild/server - if (!guild){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-not-in-guild").build("button",{channel,user})) - return cancel() - } - - //check if ticket exists - const ticket = opendiscord.tickets.get(channel.id) - if (!ticket || channel.isDMBased()){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-ticket-unknown").build("button",{guild,channel,user})) - return cancel() - } - - //return when already claimed - if (ticket.get("opendiscord:claimed").value){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error").build("button",{guild,channel,user,error:opendiscord.languages.getTranslation("errors.actionInvalid.claim"),layout:"simple"})) - return cancel() - } - - //return when busy - if (ticket.get("opendiscord:busy").value){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-ticket-busy").build("button",{guild,channel,user})) - return cancel() - } - - const claimUser = instance.options.getUser("user",false) ?? user - const reason = instance.options.getString("reason",false) - + //responder checks + const hasPerms = await openticketUtils.replyHasPermissions(instance,origin,"claim") + if (!hasPerms) return cancel() + + const isInGuild = await openticketUtils.replyIsInGuild(instance,origin) + if (!isInGuild || !guild || channel.isDMBased()) return cancel() + + const ticket = await openticketUtils.replyIsTicket(instance,origin) + if (!ticket) return cancel() + + const isAvailable = await openticketUtils.replyTicketIsAvailable(instance,origin,ticket) + if (!isAvailable) return cancel() + + const isTicketUnclaimed = await openticketUtils.replyTicketMustBeUnclaimed(instance,origin,ticket) + if (!isTicketUnclaimed) return cancel() + //start claiming ticket await instance.defer(false) - await opendiscord.actions.get("opendiscord:claim-ticket").run(source,{guild,channel,user:claimUser,ticket,reason,sendMessage:false}) - await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:claim-message").build(source,{guild,channel,user:claimUser,ticket,reason})) + const reason = instance.options.getString("reason",false) + const claimUser = instance.options.getUser("user",false) ?? user + await opendiscord.actions.get("opendiscord:claim-ticket").run(origin,{guild,channel,user:claimUser,ticket,reason,sendMessage:false}) + + //send message & set state + const sentMsg = await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:claim-message").build(origin,{guild,channel,user:claimUser,ticket,reason})) + if (sentMsg.success) await interactiveMsgState.setMsgState({channel,message:sentMsg.message},{ + messageType:"claim-message", + messageOrigin:origin, + messageAuthor:claimUser.id, + messageReason:reason + },sentMsg.ephemeral) }), - new api.ODWorker("opendiscord:logs",-1,(instance,params,source,cancel) => { + new api.ODWorker("opendiscord:logs",-1,(instance,params,origin,cancel) => { opendiscord.log(instance.user.displayName+" used the 'claim' command!","info",[ {key:"user",value:instance.user.username}, {key:"userid",value:instance.user.id,hidden:true}, {key:"channelid",value:instance.channel.id,hidden:true}, - {key:"method",value:source} + {key:"method",value:origin} ]) }) ]) } -export const registerButtonResponders = async () => { +export async function registerButtonResponders(){ //CLAIM TICKET BUTTON RESPONDER opendiscord.responders.buttons.add(new api.ODButtonResponder("opendiscord:claim-ticket",/^od:claim-ticket/)) opendiscord.responders.buttons.get("opendiscord:claim-ticket").workers.add( - new api.ODWorker("opendiscord:claim-ticket",0,async (instance,params,source,cancel) => { - const originalSource = instance.interaction.customId.split("_")[1] as Exclude + new api.ODWorker("opendiscord:claim-ticket",0,async (instance,params,origin,cancel) => { + const {guild,channel,user,message} = instance + + //responder checks + const hasPerms = await openticketUtils.replyHasPermissions(instance,origin,"claim") + if (!hasPerms) return cancel() - if (originalSource == "ticket-message") await opendiscord.verifybars.get("opendiscord:claim-ticket-ticket-message").activate(instance) - else if (originalSource == "unclaim-message") await opendiscord.verifybars.get("opendiscord:claim-ticket-unclaim-message").activate(instance) - else await instance.defer("update",false) + const isInGuild = await openticketUtils.replyIsInGuild(instance,origin) + if (!isInGuild || !guild || channel.isDMBased()) return cancel() + + const state = await openticketUtils.replyInteractiveMessageState(instance,origin,channel,message,"/claim") + if (!state) return cancel() + + const ticket = await openticketUtils.replyIsTicket(instance,origin) + if (!ticket) return cancel() + + const isAvailable = await openticketUtils.replyTicketIsAvailable(instance,origin,ticket) + if (!isAvailable) return cancel() + + const isTicketUnclaimed = await openticketUtils.replyTicketMustBeUnclaimed(instance,origin,ticket) + if (!isTicketUnclaimed) return cancel() + + //fetch state details + const verifybar = opendiscord.verifybars.get("opendiscord:claim-ticket") + const originalUser = ((state.data.messageAuthor) ? await opendiscord.client.fetchUser(state.data.messageAuthor) : user) ?? user + const originalReason = state.data.messageReason ?? null + const originalMsgType = state.data.messageType + + //send verifybar + if (generalConfig.data.ticketSystem.disableVerifyBars){ + //verifybar disabled, directly run response + await verifybar.activate(instance,"accept") + + }else if (originalMsgType == "ticket-message"){ + //ticket message verifybar + const modifiedMsg = opendiscord.components.modifiers.get("opendiscord:claim-ticket-verifybar").modify(opendiscord.builders.messages.getSafe("opendiscord:ticket-message"),originalMsgType,{guild,channel,user,verifybar}) + await instance.update(await modifiedMsg.build("verifybar",{guild,channel,user,ticket})) + }else if (originalMsgType == "unclaim-message"){ + //unclaim message verifybar + const modifiedMsg = opendiscord.components.modifiers.get("opendiscord:claim-ticket-verifybar").modify(opendiscord.builders.messages.getSafe("opendiscord:unclaim-message"),originalMsgType,{guild,channel,user,verifybar}) + await instance.update(await modifiedMsg.build("verifybar",{guild,channel,user:originalUser,ticket,reason:originalReason})) + } }) ) } -export const registerModalResponders = async () => { - //CLAIM WITH REASON MODAL RESPONDER - opendiscord.responders.modals.add(new api.ODModalResponder("opendiscord:claim-ticket-reason",/^od:claim-ticket-reason_/)) - opendiscord.responders.modals.get("opendiscord:claim-ticket-reason").workers.add([ - new api.ODWorker("opendiscord:claim-ticket-reason",0,async (instance,params,source,cancel) => { - const {guild,channel,user} = instance - if (!channel) return - if (!guild){ - //error - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-not-in-guild").build(source,{channel,user:instance.user})) - return cancel() - } - const ticket = opendiscord.tickets.get(instance.interaction.customId.split("_")[1]) - if (!ticket || channel.isDMBased()){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-ticket-unknown").build("button",{guild,channel,user})) - return - } - - const originalSource = instance.interaction.customId.split("_")[2] as Exclude - const reason = instance.values.getTextField("reason",true) - - //claim with reason - if (originalSource == "ticket-message"){ - await instance.defer("update",false) - await opendiscord.actions.get("opendiscord:claim-ticket").run(originalSource,{guild,channel,user,ticket,reason,sendMessage:true}) - await instance.update(await opendiscord.builders.messages.getSafe("opendiscord:ticket-message").build("other",{guild,channel,user,ticket})) - }else if (originalSource == "unclaim-message"){ - await instance.defer("update",false) - await opendiscord.actions.get("opendiscord:claim-ticket").run(originalSource,{guild,channel,user,ticket,reason,sendMessage:false}) - await instance.update(await opendiscord.builders.messages.getSafe("opendiscord:claim-message").build("other",{guild,channel,user,ticket,reason})) - }else{ - await instance.defer("update",false) - await opendiscord.actions.get("opendiscord:claim-ticket").run(originalSource,{guild,channel,user,ticket,reason,sendMessage:true}) - } +export async function registerVerifyBars(){ + //CLAIM TICKET + opendiscord.verifybars.add(new api.ODVerifyBar("opendiscord:claim-ticket")) + opendiscord.verifybars.get("opendiscord:claim-ticket").workers.add([ + new api.ODWorker("opendiscord:claim-ticket",0,async (instance,params,origin,cancel) => { + const {user,member,channel,guild,message} = instance + //responder checks + const hasPerms = await openticketUtils.replyHasPermissions(instance,origin,"claim") + if (!hasPerms) return cancel() + + const isInGuild = await openticketUtils.replyIsInGuild(instance,origin) + if (!isInGuild || !guild || channel.isDMBased()) return cancel() + + const state = await openticketUtils.replyInteractiveMessageState(instance,origin,channel,message,"/claim") + if (!state) return cancel() + + const ticket = await openticketUtils.replyIsTicket(instance,origin) + if (!ticket) return cancel() + + const isAvailable = await openticketUtils.replyTicketIsAvailable(instance,origin,ticket) + if (!isAvailable) return cancel() + + const isTicketUnclaimed = await openticketUtils.replyTicketMustBeUnclaimed(instance,origin,ticket) + if (!isTicketUnclaimed) return cancel() + + //fetch state details + const originalUser = ((state.data.messageAuthor) ? await opendiscord.client.fetchUser(state.data.messageAuthor) : user) ?? user + const originalReason = state.data.messageReason ?? null + const originalMsgType = state.data.messageType + + //start claiming ticket + if (params.selectedButtonId == "cancel"){ + //CANCEL + if (originalMsgType == "ticket-message"){ + //ticket message + await instance.update(await opendiscord.builders.messages.getSafe("opendiscord:ticket-message").build("verifybar",{guild,channel,user,ticket})) + }else if (originalMsgType == "unclaim-message"){ + //unclaim message + await instance.update(await opendiscord.builders.messages.getSafe("opendiscord:unclaim-message").build("verifybar",{guild,channel,user:originalUser,ticket,reason:originalReason})) + } + }else if (params.selectedButtonId == "accept"){ + //CLAIM TICKET + await instance.defer("update",false) + + if (originalMsgType == "ticket-message"){ + //ticket message + await opendiscord.actions.get("opendiscord:claim-ticket").run(originalMsgType,{guild,channel,user,ticket,reason:null,sendMessage:true}) + await instance.update(await opendiscord.builders.messages.getSafe("opendiscord:ticket-message").build("verifybar",{guild,channel,user,ticket})) + }else if (originalMsgType == "unclaim-message"){ + //converted to claim message + await opendiscord.actions.get("opendiscord:claim-ticket").run(originalMsgType,{guild,channel,user,ticket,reason:null,sendMessage:false}) + const sentMsg = await instance.update(await opendiscord.builders.messages.getSafe("opendiscord:claim-message").build("verifybar",{guild,channel,user,ticket,reason:null})) + if (sentMsg.success) await interactiveMsgState.setMsgState({channel,message:sentMsg.message},{ + messageType:"claim-message", + messageOrigin:origin, + messageAuthor:user.id, + messageReason:null + },sentMsg.ephemeral) + } + }else if (params.selectedButtonId == "accept-with-reason"){ + //CLAIM WITH REASON (MODAL) + instance.modal(await opendiscord.components.modals.get("opendiscord:claim-ticket-reason").build("other",{guild,channel,user,ticket,message})) + } + }) + ]) +} + +export async function registerModalResponders(){ + //CLAIM WITH REASON MODAL RESPONDER + opendiscord.responders.modals.add(new api.ODModalResponder("opendiscord:claim-ticket-reason",/^od:claim-ticket-reason\|([^|]+)\|([^|]+)/)) + opendiscord.responders.modals.get("opendiscord:claim-ticket-reason").workers.add([ + new api.ODWorker("opendiscord:claim-ticket-reason",0,async (instance,params,origin,cancel) => { + const {guild,user} = instance + + const match = /^od:claim-ticket-reason\|([^|]+)\|([^|]+)/.exec(instance.interaction.customId) + if (!match) return cancel() + const channel = await opendiscord.client.fetchTextChannel(match[1]) + const message = await opendiscord.client.fetchChannelMessage(match[1],match[2]) + + //responder checks + const hasPerms = await openticketUtils.replyHasPermissions(instance,origin,"claim") + if (!hasPerms) return cancel() + + const isInGuild = await openticketUtils.replyIsInGuild(instance,origin) + if (!isInGuild || !guild || !channel || channel.isDMBased()) return cancel() + + const state = await openticketUtils.replyInteractiveMessageState(instance,origin,channel,message,"/claim") + if (!state) return cancel() + + const ticket = await openticketUtils.replyIsTicket(instance,origin) + if (!ticket) return cancel() + + const isAvailable = await openticketUtils.replyTicketIsAvailable(instance,origin,ticket) + if (!isAvailable) return cancel() + + const isTicketUnclaimed = await openticketUtils.replyTicketMustBeUnclaimed(instance,origin,ticket) + if (!isTicketUnclaimed) return cancel() + + //fetch state details + const reason = instance.values.getTextField("reason",true) + const originalMsgOrigin = state.data.messageOrigin + const originalMsgType = state.data.messageType + + //start claiming ticket + await instance.defer("update",false) + + if (originalMsgType == "ticket-message"){ + //ticket message + await opendiscord.actions.get("opendiscord:claim-ticket").run(originalMsgType,{guild,channel,user,ticket,reason:null,sendMessage:true}) + await instance.update(await opendiscord.builders.messages.getSafe("opendiscord:ticket-message").build("other",{guild,channel,user,ticket})) + + }else if (originalMsgType == "unclaim-message"){ + //converted to claim message + await opendiscord.actions.get("opendiscord:claim-ticket").run(originalMsgType,{guild,channel,user,ticket,reason:null,sendMessage:false}) + const sentMsg = await instance.update(await opendiscord.builders.messages.getSafe("opendiscord:claim-message").build(originalMsgType,{guild,channel,user,ticket,reason})) + if (sentMsg.success) await interactiveMsgState.setMsgState({channel,message:sentMsg.message},{ + messageType:"claim-message", + messageOrigin:originalMsgOrigin, + messageAuthor:user.id, + messageReason:reason + },sentMsg.ephemeral) + } }) ]) } \ No newline at end of file diff --git a/src/commands/clear.ts b/src/commands/clear.ts index 1de6e25..30bb9c9 100644 --- a/src/commands/clear.ts +++ b/src/commands/clear.ts @@ -1,35 +1,32 @@ /////////////////////////////////////// //CLEAR COMMAND /////////////////////////////////////// -import {opendiscord, api, utilities} from "../index" +import {opendiscord, api, utilities, openticketUtils} from "../index.js" import * as discord from "discord.js" const generalConfig = opendiscord.configs.get("opendiscord:general") +const clearMsgState = opendiscord.states.get("opendiscord:clear-message") +const lang = opendiscord.languages -export const registerCommandResponders = async () => { +export async function registerCommandResponders(){ //CLEAR COMMAND RESPONDER opendiscord.responders.commands.add(new api.ODCommandResponder("opendiscord:clear",generalConfig.data.prefix,"clear")) opendiscord.responders.commands.get("opendiscord:clear").workers.add([ - new api.ODWorker("opendiscord:clear",0,async (instance,params,source,cancel) => { + new api.ODWorker("opendiscord:clear",0,async (instance,params,origin,cancel) => { const {user,member,channel,guild} = instance - + + //responder checks //check permissions (only allow global admins: ticket admins aren't allowed to clear tickets) - const permsResult = await opendiscord.permissions.checkCommandPerms(generalConfig.data.system.permissions.clear,"support",user,member,channel,guild,{allowChannelUserScope:false,allowChannelRoleScope:false}) - if (!permsResult.hasPerms){ - if (permsResult.reason == "not-in-server") await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-not-in-guild").build("button",{channel,user})) - else await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-no-permissions").build(source,{guild,channel,user,permissions:["support"]})) - return cancel() - } - - //check is in guild/server - if (!guild || channel.isDMBased()){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-not-in-guild").build("button",{channel,user})) - return cancel() - } + const hasPerms = await openticketUtils.replyHasPermissions(instance,origin,"clear",{allowChannelUserScope:false,allowChannelRoleScope:false}) + if (!hasPerms) return cancel() + const isInGuild = await openticketUtils.replyIsInGuild(instance,origin) + if (!isInGuild || !guild || channel.isDMBased()) return cancel() + + //fetch data const tempFilter = instance.options.getString("filter",false) const filter = (tempFilter) ? tempFilter.toLowerCase() as api.ODTicketClearFilter : "all" - const list: string[] = [] + const channelNameList: string[] = [] const ticketList = opendiscord.tickets.getAll().filter((ticket) => { if (filter == "all") return true else if (filter == "open" && ticket.get("opendiscord:open").value) return true @@ -43,46 +40,69 @@ export const registerCommandResponders = async () => { }) for (const ticket of ticketList){ const ticketChannel = await opendiscord.tickets.getTicketChannel(ticket) - if (ticketChannel) list.push("#"+ticketChannel.name) + if (ticketChannel) channelNameList.push("#"+ticketChannel.name) } //reply with clear verify await instance.defer(true) - await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:clear-verify-message").build(source,{guild,channel,user,filter,list})) + const sentMsg = await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:clear-verify-message").build(origin,{guild,channel,user,filter,list:channelNameList,inProgress:false})) + if (sentMsg.success) await clearMsgState.setMsgState({channel,message:sentMsg.message,user},{ + messageOrigin:origin, + clearFilter:filter, + clearChannelNameList:channelNameList + },sentMsg.ephemeral) }), - new api.ODWorker("opendiscord:logs",-1,(instance,params,source,cancel) => { + new api.ODWorker("opendiscord:logs",-1,(instance,params,origin,cancel) => { opendiscord.log(instance.user.displayName+" used the 'clear' command!","info",[ {key:"user",value:instance.user.username}, {key:"userid",value:instance.user.id,hidden:true}, {key:"channelid",value:instance.channel.id,hidden:true}, - {key:"method",value:source} + {key:"method",value:origin} ]) }) ]) } -export const registerButtonResponders = async () => { +export async function registerButtonResponders(){ //CLEAR CONTINUE BUTTON RESPONDER - opendiscord.responders.buttons.add(new api.ODButtonResponder("opendiscord:clear-continue",/^od:clear-continue_/)) + opendiscord.responders.buttons.add(new api.ODButtonResponder("opendiscord:clear-continue","od:clear-continue")) opendiscord.responders.buttons.get("opendiscord:clear-continue").workers.add( - new api.ODWorker("opendiscord:clear-continue",0,async (instance,params,source,cancel) => { - const {guild,channel,user} = instance - if (!guild || channel.isDMBased()) return - const originalSource = instance.interaction.customId.split("_")[1] as api.ODActionManagerIds_Default["opendiscord:clear-tickets"]["source"] - const filter = instance.interaction.customId.split("_")[2] as api.ODTicketClearFilter + new api.ODWorker("opendiscord:clear-continue",0,async (instance,params,origin,cancel) => { + const {guild,channel,user,message} = instance + + //check message state + const state = await clearMsgState.getMsgState({channel,message,user}) + if (!state){ + await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error").build(origin,{guild,channel,user,error:lang.getTranslationWithParams("errors.descriptions.stateExpired",["/clear"]),layout:"simple",customTitle:"Message State Expired"})) + return cancel() + } + + //responder checks + const hasPerms = await openticketUtils.replyHasPermissions(instance,origin,"clear") + if (!hasPerms) return cancel() + + const isInGuild = await openticketUtils.replyIsInGuild(instance,origin) + if (!isInGuild || !guild || !channel || channel.isDMBased()) return cancel() + + //fetch state details + const originalUser = ((state.userId) ? await opendiscord.client.fetchUser(state.userId) : user) ?? user + const originalOrigin = state.data.messageOrigin + const originalClearFilter = state.data.clearFilter + const originalClearChannelNameList = state.data.clearChannelNameList //start ticket clear - await instance.defer("update",true) + await instance.update(await opendiscord.builders.messages.getSafe("opendiscord:clear-verify-message").build(originalOrigin,{guild,channel,user:originalUser,filter:originalClearFilter,list:originalClearChannelNameList,inProgress:true})) + const list: string[] = [] const ticketList = opendiscord.tickets.getAll().filter((ticket) => { - if (filter == "all") return true - else if (filter == "open" && ticket.get("opendiscord:open").value) return true - else if (filter == "closed" && ticket.get("opendiscord:closed").value) return true - else if (filter == "claimed" && ticket.get("opendiscord:claimed").value) return true - else if (filter == "pinned" && ticket.get("opendiscord:pinned").value) return true - else if (filter == "unclaimed" && !ticket.get("opendiscord:claimed").value) return true - else if (filter == "unpinned" && !ticket.get("opendiscord:pinned").value) return true - else if (filter == "autoclosed" && ticket.get("opendiscord:closed").value) return true + if (originalClearFilter == "all") return true + else if (originalClearFilter == "open" && ticket.get("opendiscord:open").value) return true + else if (originalClearFilter == "closed" && ticket.get("opendiscord:closed").value) return true + else if (originalClearFilter == "claimed" && ticket.get("opendiscord:claimed").value) return true + else if (originalClearFilter == "pinned" && ticket.get("opendiscord:pinned").value) return true + else if (originalClearFilter == "unclaimed" && !ticket.get("opendiscord:claimed").value) return true + else if (originalClearFilter == "unpinned" && !ticket.get("opendiscord:pinned").value) return true + else if (originalClearFilter == "autoclosed" && ticket.get("opendiscord:closed").value) return true else return false }) for (const ticket of ticketList){ @@ -90,8 +110,8 @@ export const registerButtonResponders = async () => { if (ticketChannel) list.push("#"+ticketChannel.name) } - await opendiscord.actions.get("opendiscord:clear-tickets").run(originalSource,{guild,channel,user,filter,list:ticketList}) - await instance.update(await opendiscord.builders.messages.getSafe("opendiscord:clear-message").build(originalSource,{guild,channel,user,filter,list})) + await opendiscord.actions.get("opendiscord:clear-tickets").run(originalOrigin,{guild,channel,user,filter:originalClearFilter,list:ticketList}) + await instance.update(await opendiscord.builders.messages.getSafe("opendiscord:clear-message").build(originalOrigin,{guild,channel,user,filter:originalClearFilter,list})) }) ) } \ No newline at end of file diff --git a/src/commands/close.ts b/src/commands/close.ts index 8f2c01b..ce0526d 100644 --- a/src/commands/close.ts +++ b/src/commands/close.ts @@ -1,131 +1,243 @@ /////////////////////////////////////// //CLOSE COMMAND /////////////////////////////////////// -import {opendiscord, api, utilities} from "../index" +import {opendiscord, api, utilities, openticketUtils} from "../index.js" import * as discord from "discord.js" const generalConfig = opendiscord.configs.get("opendiscord:general") const lang = opendiscord.languages +const interactiveMsgState = opendiscord.states.get("opendiscord:interactive-message") -export const registerCommandResponders = async () => { +export async function registerCommandResponders(){ //CLOSE COMMAND RESPONDER opendiscord.responders.commands.add(new api.ODCommandResponder("opendiscord:close",generalConfig.data.prefix,"close")) opendiscord.responders.commands.get("opendiscord:close").workers.add([ - new api.ODWorker("opendiscord:close",0,async (instance,params,source,cancel) => { + new api.ODWorker("opendiscord:close",0,async (instance,params,origin,cancel) => { const {user,member,channel,guild} = instance - //check permissions - const permsResult = await opendiscord.permissions.checkCommandPerms(generalConfig.data.system.permissions.close,"support",user,member,channel,guild) - if (!permsResult.hasPerms){ - if (permsResult.reason == "not-in-server") await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-not-in-guild").build("button",{channel,user})) - else await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-no-permissions").build(source,{guild,channel,user,permissions:["support"]})) - return cancel() - } - - //check is in guild/server - if (!guild){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-not-in-guild").build("button",{channel,user})) - return cancel() - } - - //check if ticket exists - const ticket = opendiscord.tickets.get(channel.id) - if (!ticket || channel.isDMBased()){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-ticket-unknown").build("button",{guild,channel,user})) - return cancel() - } - - //return when already closed - if (ticket.get("opendiscord:closed").value){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error").build("button",{guild,channel,user,error:opendiscord.languages.getTranslation("errors.actionInvalid.close"),layout:"simple"})) - return cancel() - } - - //return when busy - if (ticket.get("opendiscord:busy").value){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-ticket-busy").build("button",{guild,channel,user})) - return cancel() - } - - //return when not allowed because of missing messages - if (!permsResult.isAdmin && (!generalConfig.data.system.allowCloseBeforeMessage || !generalConfig.data.system.allowCloseBeforeAdminMessage)){ - const analysis = await opendiscord.transcripts.collector.ticketUserMessagesAnalysis(ticket,guild,channel) - if (analysis && !generalConfig.data.system.allowCloseBeforeMessage && analysis.totalMessages < 1){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error").build("button",{guild,channel,user,layout:"simple",error:lang.getTranslation("errors.descriptions.closeBeforeMessage"),customTitle:lang.getTranslation("errors.titles.noPermissions")})) - return cancel() - } - if (analysis && !generalConfig.data.system.allowCloseBeforeAdminMessage && analysis.adminMessages < 1){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error").build("button",{guild,channel,user,layout:"simple",error:lang.getTranslation("errors.descriptions.closeBeforeAdminMessage"),customTitle:lang.getTranslation("errors.titles.noPermissions")})) - return cancel() - } - } + //responder checks + const hasPerms = await openticketUtils.replyHasPermissions(instance,origin,"close") + if (!hasPerms) return cancel() + + const isInGuild = await openticketUtils.replyIsInGuild(instance,origin) + if (!isInGuild || !guild || channel.isDMBased()) return cancel() + + const ticket = await openticketUtils.replyIsTicket(instance,origin) + if (!ticket) return cancel() + + const isAvailable = await openticketUtils.replyTicketIsAvailable(instance,origin,ticket) + if (!isAvailable) return cancel() + + const isTicketOpen = await openticketUtils.replyTicketMustBeOpen(instance,origin,ticket) + if (!isTicketOpen) return cancel() + + const messagesHaveBeenSent = await openticketUtils.replyMessageMustBeSentBeforeClose(instance,origin,ticket,"close") + if (!messagesHaveBeenSent) return cancel() //start closing ticket await instance.defer(false) const reason = instance.options.getString("reason",false) - await opendiscord.actions.get("opendiscord:close-ticket").run(source,{guild,channel,user,ticket,reason,sendMessage:false}) - await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:close-message").build(source,{guild,channel,user,ticket,reason})) + await opendiscord.actions.get("opendiscord:close-ticket").run(origin,{guild,channel,user,ticket,reason,sendMessage:false}) + + //send message & set state + const sentMsg = await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:close-message").build(origin,{guild,channel,user,ticket,reason})) + if (sentMsg.success) await interactiveMsgState.setMsgState({channel,message:sentMsg.message},{ + messageType:"close-message", + messageOrigin:origin, + messageAuthor:user.id, + messageReason:reason + },sentMsg.ephemeral) }), - new api.ODWorker("opendiscord:logs",-1,(instance,params,source,cancel) => { + new api.ODWorker("opendiscord:logs",-1,(instance,params,origin,cancel) => { opendiscord.log(instance.user.displayName+" used the 'close' command!","info",[ {key:"user",value:instance.user.username}, {key:"userid",value:instance.user.id,hidden:true}, {key:"channelid",value:instance.channel.id,hidden:true}, - {key:"method",value:source} + {key:"method",value:origin} ]) }) ]) } -export const registerButtonResponders = async () => { +export async function registerButtonResponders(){ //CLOSE TICKET BUTTON RESPONDER - opendiscord.responders.buttons.add(new api.ODButtonResponder("opendiscord:close-ticket",/^od:close-ticket_/)) + opendiscord.responders.buttons.add(new api.ODButtonResponder("opendiscord:close-ticket",/^od:close-ticket/)) opendiscord.responders.buttons.get("opendiscord:close-ticket").workers.add( - new api.ODWorker("opendiscord:close-ticket",0,async (instance,params,source,cancel) => { - const originalSource = instance.interaction.customId.split("_")[1] as Exclude + new api.ODWorker("opendiscord:close-ticket",0,async (instance,params,origin,cancel) => { + const {guild,channel,message,user,member} = instance + + //responder checks + const hasPerms = await openticketUtils.replyHasPermissions(instance,origin,"close") + if (!hasPerms) return cancel() - if (originalSource == "ticket-message") await opendiscord.verifybars.get("opendiscord:close-ticket-ticket-message").activate(instance) - else if (originalSource == "reopen-message") await opendiscord.verifybars.get("opendiscord:close-ticket-reopen-message").activate(instance) - else await instance.defer("update",false) + const isInGuild = await openticketUtils.replyIsInGuild(instance,origin) + if (!isInGuild || !guild || channel.isDMBased()) return cancel() + + const state = await openticketUtils.replyInteractiveMessageState(instance,origin,channel,message,"/close") + if (!state) return cancel() + + const ticket = await openticketUtils.replyIsTicket(instance,origin) + if (!ticket) return cancel() + + const isAvailable = await openticketUtils.replyTicketIsAvailable(instance,origin,ticket) + if (!isAvailable) return cancel() + + const isTicketOpen = await openticketUtils.replyTicketMustBeOpen(instance,origin,ticket) + if (!isTicketOpen) return cancel() + + const messagesHaveBeenSent = await openticketUtils.replyMessageMustBeSentBeforeClose(instance,origin,ticket,"close") + if (!messagesHaveBeenSent) return cancel() + + //fetch state details + const verifybar = opendiscord.verifybars.get("opendiscord:close-ticket") + const originalUser = ((state.data.messageAuthor) ? await opendiscord.client.fetchUser(state.data.messageAuthor) : user) ?? user + const originalReason = state.data.messageReason ?? null + const originalMsgType = state.data.messageType + + //send verifybar + if (generalConfig.data.ticketSystem.disableVerifyBars){ + //verifybar disabled, directly run response + await verifybar.activate(instance,"accept") + + }else if (originalMsgType == "ticket-message"){ + //ticket message verifybar + const modifiedMsg = opendiscord.components.modifiers.get("opendiscord:close-ticket-verifybar").modify(opendiscord.builders.messages.getSafe("opendiscord:ticket-message"),originalMsgType,{guild,channel,user,verifybar}) + await instance.update(await modifiedMsg.build("verifybar",{guild,channel,user,ticket})) + }else if (originalMsgType == "reopen-message"){ + //reopen message verifybar + const modifiedMsg = opendiscord.components.modifiers.get("opendiscord:close-ticket-verifybar").modify(opendiscord.builders.messages.getSafe("opendiscord:reopen-message"),originalMsgType,{guild,channel,user,verifybar}) + await instance.update(await modifiedMsg.build("verifybar",{guild,channel,user:originalUser,ticket,reason:originalReason})) + } }) ) } -export const registerModalResponders = async () => { - //CLOSE WITH REASON MODAL RESPONDER - opendiscord.responders.modals.add(new api.ODModalResponder("opendiscord:close-ticket-reason",/^od:close-ticket-reason_/)) - opendiscord.responders.modals.get("opendiscord:close-ticket-reason").workers.add([ - new api.ODWorker("opendiscord:close-ticket-reason",0,async (instance,params,source,cancel) => { - const {guild,channel,user} = instance - if (!channel) return - if (!guild){ - //error - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-not-in-guild").build(source,{channel,user:instance.user})) - return cancel() - } - const ticket = opendiscord.tickets.get(instance.interaction.customId.split("_")[1]) - if (!ticket || channel.isDMBased()){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-ticket-unknown").build("button",{guild,channel,user})) - return - } +export async function registerVerifyBars(){ + //CLOSE TICKET VERIFYBAR + opendiscord.verifybars.add(new api.ODVerifyBar("opendiscord:close-ticket")) + opendiscord.verifybars.get("opendiscord:close-ticket").workers.add([ + new api.ODWorker("opendiscord:close-ticket",0,async (instance,params,origin,cancel) => { + const {user,member,channel,guild,message} = instance - const originalSource = instance.interaction.customId.split("_")[2] as Exclude - const reason = instance.values.getTextField("reason",true) - - //close with reason - if (originalSource == "ticket-message"){ - await instance.defer("update",false) - await opendiscord.actions.get("opendiscord:close-ticket").run(originalSource,{guild,channel,user,ticket,reason,sendMessage:true}) - await instance.update(await opendiscord.builders.messages.getSafe("opendiscord:ticket-message").build("other",{guild,channel,user,ticket})) - }else if (originalSource == "reopen-message"){ - await instance.defer("update",false) - await opendiscord.actions.get("opendiscord:close-ticket").run(originalSource,{guild,channel,user,ticket,reason,sendMessage:false}) - await instance.update(await opendiscord.builders.messages.getSafe("opendiscord:close-message").build("other",{guild,channel,user,ticket,reason})) - }else{ - await instance.defer("update",false) - await opendiscord.actions.get("opendiscord:close-ticket").run(originalSource,{guild,channel,user,ticket,reason,sendMessage:true}) - } + //responder checks + const hasPerms = await openticketUtils.replyHasPermissions(instance,origin,"close") + if (!hasPerms) return cancel() + const isInGuild = await openticketUtils.replyIsInGuild(instance,origin) + if (!isInGuild || !guild || channel.isDMBased()) return cancel() + + const state = await openticketUtils.replyInteractiveMessageState(instance,origin,channel,message,"/close") + if (!state) return cancel() + + const ticket = await openticketUtils.replyIsTicket(instance,origin) + if (!ticket) return cancel() + + const isAvailable = await openticketUtils.replyTicketIsAvailable(instance,origin,ticket) + if (!isAvailable) return cancel() + + const isTicketOpen = await openticketUtils.replyTicketMustBeOpen(instance,origin,ticket) + if (!isTicketOpen) return cancel() + + const messagesHaveBeenSent = await openticketUtils.replyMessageMustBeSentBeforeClose(instance,origin,ticket,"close") + if (!messagesHaveBeenSent) return cancel() + + //fetch state details + const originalUser = ((state.data.messageAuthor) ? await opendiscord.client.fetchUser(state.data.messageAuthor) : user) ?? user + const originalReason = state.data.messageReason ?? null + const originalMsgType = state.data.messageType + + //start closing ticket + if (params.selectedButtonId == "cancel"){ + //CANCEL + if (originalMsgType == "ticket-message"){ + //ticket message + await instance.update(await opendiscord.builders.messages.getSafe("opendiscord:ticket-message").build("verifybar",{guild,channel,user,ticket})) + }else if (originalMsgType == "reopen-message"){ + //reopen message + await instance.update(await opendiscord.builders.messages.getSafe("opendiscord:reopen-message").build("verifybar",{guild,channel,user:originalUser,ticket,reason:originalReason})) + } + }else if (params.selectedButtonId == "accept"){ + //CLOSE TICKET + await instance.defer("update",false) + + if (originalMsgType == "ticket-message"){ + //ticket message + await opendiscord.actions.get("opendiscord:close-ticket").run(originalMsgType,{guild,channel,user,ticket,reason:null,sendMessage:true}) + await instance.update(await opendiscord.builders.messages.getSafe("opendiscord:ticket-message").build("verifybar",{guild,channel,user,ticket})) + }else if (originalMsgType == "reopen-message"){ + //converted to close message + await opendiscord.actions.get("opendiscord:close-ticket").run(originalMsgType,{guild,channel,user,ticket,reason:null,sendMessage:false}) + const sentMsg = await instance.update(await opendiscord.builders.messages.getSafe("opendiscord:close-message").build("verifybar",{guild,channel,user,ticket,reason:null})) + if (sentMsg.success) await interactiveMsgState.setMsgState({channel,message:sentMsg.message},{ + messageType:"close-message", + messageOrigin:origin, + messageAuthor:user.id, + messageReason:null + },sentMsg.ephemeral) + } + }else if (params.selectedButtonId == "accept-with-reason"){ + //CLOSE WITH REASON (MODAL) + instance.modal(await opendiscord.components.modals.get("opendiscord:close-ticket-reason").build("other",{guild,channel,user,ticket,message})) + } + }) + ]) +} + +export async function registerModalResponders(){ + //CLOSE WITH REASON MODAL RESPONDER + opendiscord.responders.modals.add(new api.ODModalResponder("opendiscord:close-ticket-reason",/^od:close-ticket-reason\|([^|]+)\|([^|]+)/)) + opendiscord.responders.modals.get("opendiscord:close-ticket-reason").workers.add([ + new api.ODWorker("opendiscord:close-ticket-reason",0,async (instance,params,origin,cancel) => { + const {guild,user} = instance + + const match = /^od:close-ticket-reason\|([^|]+)\|([^|]+)/.exec(instance.interaction.customId) + if (!match) return cancel() + const channel = await opendiscord.client.fetchTextChannel(match[1]) + const message = await opendiscord.client.fetchChannelMessage(match[1],match[2]) + + //responder checks + const hasPerms = await openticketUtils.replyHasPermissions(instance,origin,"close") + if (!hasPerms) return cancel() + + const isInGuild = await openticketUtils.replyIsInGuild(instance,origin) + if (!isInGuild || !guild || !channel || channel.isDMBased()) return cancel() + + const state = await openticketUtils.replyInteractiveMessageState(instance,origin,channel,message,"/close") + if (!state) return cancel() + + const ticket = await openticketUtils.replyIsTicket(instance,origin) + if (!ticket) return cancel() + + const isAvailable = await openticketUtils.replyTicketIsAvailable(instance,origin,ticket) + if (!isAvailable) return cancel() + + const isTicketOpen = await openticketUtils.replyTicketMustBeOpen(instance,origin,ticket) + if (!isTicketOpen) return cancel() + + //fetch state details + const reason = instance.values.getTextField("reason",true) + const originalMsgOrigin = state.data.messageOrigin + const originalMsgType = state.data.messageType + + //start closing ticket + await instance.defer("update",false) + + if (originalMsgType == "ticket-message"){ + //ticket message + await opendiscord.actions.get("opendiscord:close-ticket").run(originalMsgType,{guild,channel,user,ticket,reason:null,sendMessage:true}) + await instance.update(await opendiscord.builders.messages.getSafe("opendiscord:ticket-message").build("other",{guild,channel,user,ticket})) + + }else if (originalMsgType == "reopen-message"){ + //converted to close message + await opendiscord.actions.get("opendiscord:close-ticket").run(originalMsgType,{guild,channel,user,ticket,reason:null,sendMessage:false}) + const sentMsg = await instance.update(await opendiscord.builders.messages.getSafe("opendiscord:close-message").build(originalMsgType,{guild,channel,user,ticket,reason})) + if (sentMsg.success) await interactiveMsgState.setMsgState({channel,message:sentMsg.message},{ + messageType:"close-message", + messageOrigin:originalMsgOrigin, + messageAuthor:user.id, + messageReason:reason + },sentMsg.ephemeral) + } }) ]) } \ No newline at end of file diff --git a/src/commands/delete.ts b/src/commands/delete.ts index ebe4c32..400fdf3 100644 --- a/src/commands/delete.ts +++ b/src/commands/delete.ts @@ -1,64 +1,42 @@ /////////////////////////////////////// //DELETE COMMAND /////////////////////////////////////// -import {opendiscord, api, utilities} from "../index" +import {opendiscord, api, utilities, openticketUtils} from "../index.js" import * as discord from "discord.js" const generalConfig = opendiscord.configs.get("opendiscord:general") const lang = opendiscord.languages +const interactiveMsgState = opendiscord.states.get("opendiscord:interactive-message") -export const registerCommandResponders = async () => { +export async function registerCommandResponders(){ //DELETE COMMAND RESPONDER opendiscord.responders.commands.add(new api.ODCommandResponder("opendiscord:delete",generalConfig.data.prefix,"delete")) opendiscord.responders.commands.get("opendiscord:delete").workers.add([ - new api.ODWorker("opendiscord:delete",0,async (instance,params,source,cancel) => { + new api.ODWorker("opendiscord:delete",0,async (instance,params,origin,cancel) => { const {user,member,channel,guild} = instance - //check permissions - const permsResult = await opendiscord.permissions.checkCommandPerms(generalConfig.data.system.permissions.delete,"support",user,member,channel,guild) - if (!permsResult.hasPerms){ - if (permsResult.reason == "not-in-server") await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-not-in-guild").build("button",{channel,user})) - else await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-no-permissions").build(source,{guild,channel,user,permissions:["support"]})) - return cancel() - } - - //check is in guild/server - if (!guild){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-not-in-guild").build("button",{channel,user})) - return cancel() - } - - //check if ticket exists - const ticket = opendiscord.tickets.get(channel.id) - if (!ticket || channel.isDMBased()){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-ticket-unknown").build("button",{guild,channel,user})) - return cancel() - } - - //return when busy - if (ticket.get("opendiscord:busy").value){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-ticket-busy").build("button",{guild,channel,user})) - return cancel() - } + //responder checks + const hasPerms = await openticketUtils.replyHasPermissions(instance,origin,"delete") + if (!hasPerms) return cancel() - //return when not allowed because of missing messages - if (!permsResult.isAdmin && (!generalConfig.data.system.allowCloseBeforeMessage || !generalConfig.data.system.allowCloseBeforeAdminMessage)){ - const analysis = await opendiscord.transcripts.collector.ticketUserMessagesAnalysis(ticket,guild,channel) - if (analysis && !generalConfig.data.system.allowCloseBeforeMessage && analysis.totalMessages < 1){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error").build("button",{guild,channel,user,layout:"simple",error:lang.getTranslation("errors.descriptions.closeBeforeMessage"),customTitle:lang.getTranslation("errors.titles.noPermissions")})) - return cancel() - } - if (analysis && !generalConfig.data.system.allowCloseBeforeAdminMessage && analysis.adminMessages < 1){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error").build("button",{guild,channel,user,layout:"simple",error:lang.getTranslation("errors.descriptions.closeBeforeAdminMessage"),customTitle:lang.getTranslation("errors.titles.noPermissions")})) - return cancel() - } - } + const isInGuild = await openticketUtils.replyIsInGuild(instance,origin) + if (!isInGuild || !guild || channel.isDMBased()) return cancel() + + const ticket = await openticketUtils.replyIsTicket(instance,origin) + if (!ticket) return cancel() + + const isAvailable = await openticketUtils.replyTicketIsAvailable(instance,origin,ticket) + if (!isAvailable) return cancel() + + const isTicketOpen = await openticketUtils.replyTicketMustBeOpen(instance,origin,ticket) + if (!isTicketOpen) return cancel() - const reason = instance.options.getString("reason",false) - const withoutTranscript = instance.options.getBoolean("notranscript",false) ?? false + const messagesHaveBeenSent = await openticketUtils.replyMessageMustBeSentBeforeClose(instance,origin,ticket,"delete") + if (!messagesHaveBeenSent) return cancel() //don't allow deleteWithoutTranscript to non-global-admins when enabled - if (withoutTranscript && generalConfig.data.system.adminOnlyDeleteWithoutTranscript){ + const withoutTranscript = instance.options.getBoolean("notranscript",false) ?? false + if (withoutTranscript && generalConfig.data.ticketSystem.adminOnlyDeleteWithoutTranscript){ if (!opendiscord.permissions.hasPermissions("support",await opendiscord.permissions.getPermissions(instance.user,instance.channel,instance.guild,{allowChannelRoleScope:false,allowChannelUserScope:false,allowGlobalRoleScope:true,allowGlobalUserScope:true}))){ instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-no-permissions").build("button",{guild:instance.guild,channel:instance.channel,user:instance.user,permissions:["support"]})) return cancel() @@ -67,84 +45,227 @@ export const registerCommandResponders = async () => { //start deleting ticket await instance.defer(false) - await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:delete-message").build(source,{guild,channel,user,ticket,reason})) - await opendiscord.actions.get("opendiscord:delete-ticket").run(source,{guild,channel,user,ticket,reason,sendMessage:false,withoutTranscript}) + const reason = instance.options.getString("reason",false) + await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:delete-message").build(origin,{guild,channel,user,ticket,reason})) + await opendiscord.actions.get("opendiscord:delete-ticket").run(origin,{guild,channel,user,ticket,reason,sendMessage:false,withoutTranscript}) }), - new api.ODWorker("opendiscord:logs",-1,(instance,params,source,cancel) => { + new api.ODWorker("opendiscord:logs",-1,(instance,params,origin,cancel) => { opendiscord.log(instance.user.displayName+" used the 'delete' command!","info",[ {key:"user",value:instance.user.username}, {key:"userid",value:instance.user.id,hidden:true}, {key:"channelid",value:instance.channel.id,hidden:true}, - {key:"method",value:source} + {key:"method",value:origin} ]) }) ]) } -export const registerButtonResponders = async () => { +export async function registerButtonResponders(){ //DELETE TICKET BUTTON RESPONDER opendiscord.responders.buttons.add(new api.ODButtonResponder("opendiscord:delete-ticket",/^od:delete-ticket/)) opendiscord.responders.buttons.get("opendiscord:delete-ticket").workers.add( - new api.ODWorker("opendiscord:delete-ticket",0,async (instance,params,source,cancel) => { - const originalSource = instance.interaction.customId.split("_")[1] as Exclude + new api.ODWorker("opendiscord:delete-ticket",0,async (instance,params,origin,cancel) => { + const {guild,channel,message,user,member} = instance - if (originalSource == "ticket-message") await opendiscord.verifybars.get("opendiscord:delete-ticket-ticket-message").activate(instance) - else if (originalSource == "close-message") await opendiscord.verifybars.get("opendiscord:delete-ticket-close-message").activate(instance) - else if (originalSource == "reopen-message") await opendiscord.verifybars.get("opendiscord:delete-ticket-reopen-message").activate(instance) - else if (originalSource == "autoclose-message") await opendiscord.verifybars.get("opendiscord:delete-ticket-autoclose-message").activate(instance) - else await instance.defer("update",false) + //responder checks + const hasPerms = await openticketUtils.replyHasPermissions(instance,origin,"delete") + if (!hasPerms) return cancel() + + const isInGuild = await openticketUtils.replyIsInGuild(instance,origin) + if (!isInGuild || !guild || channel.isDMBased()) return cancel() + + const state = await openticketUtils.replyInteractiveMessageState(instance,origin,channel,message,"/delete") + if (!state) return cancel() + + const ticket = await openticketUtils.replyIsTicket(instance,origin) + if (!ticket) return cancel() + + const isAvailable = await openticketUtils.replyTicketIsAvailable(instance,origin,ticket) + if (!isAvailable) return cancel() + + const messagesHaveBeenSent = await openticketUtils.replyMessageMustBeSentBeforeClose(instance,origin,ticket,"delete") + if (!messagesHaveBeenSent) return cancel() + + //fetch state details + const verifybar = opendiscord.verifybars.get("opendiscord:delete-ticket") + const originalUser = ((state.data.messageAuthor) ? await opendiscord.client.fetchUser(state.data.messageAuthor) : user) ?? user + const originalReason = state.data.messageReason ?? null + const originalMsgType = state.data.messageType + + //send verifybar + if (generalConfig.data.ticketSystem.disableVerifyBars){ + //verifybar disabled, directly run response + await verifybar.activate(instance,"accept") + + }else if (originalMsgType == "ticket-message"){ + //ticket message verifybar + const modifiedMsg = opendiscord.components.modifiers.get("opendiscord:delete-ticket-verifybar").modify(opendiscord.builders.messages.getSafe("opendiscord:ticket-message"),originalMsgType,{guild,channel,user,verifybar}) + await instance.update(await modifiedMsg.build("verifybar",{guild,channel,user,ticket})) + }else if (originalMsgType == "close-message"){ + //close message verifybar + const modifiedMsg = opendiscord.components.modifiers.get("opendiscord:delete-ticket-verifybar").modify(opendiscord.builders.messages.getSafe("opendiscord:close-message"),originalMsgType,{guild,channel,user,verifybar}) + await instance.update(await modifiedMsg.build("verifybar",{guild,channel,user:originalUser,ticket,reason:originalReason})) + }else if (originalMsgType == "reopen-message"){ + //reopen message verifybar + const modifiedMsg = opendiscord.components.modifiers.get("opendiscord:delete-ticket-verifybar").modify(opendiscord.builders.messages.getSafe("opendiscord:reopen-message"),originalMsgType,{guild,channel,user,verifybar}) + await instance.update(await modifiedMsg.build("verifybar",{guild,channel,user:originalUser,ticket,reason:originalReason})) + }else if (originalMsgType == "autoclose-message"){ + //autoclose message verifybar + const modifiedMsg = opendiscord.components.modifiers.get("opendiscord:delete-ticket-verifybar").modify(opendiscord.builders.messages.getSafe("opendiscord:autoclose-message"),originalMsgType,{guild,channel,user,verifybar}) + await instance.update(await modifiedMsg.build("verifybar",{guild,channel,user:originalUser,ticket})) + } }) ) } -export const registerModalResponders = async () => { - //REOPEN WITH REASON MODAL RESPONDER - opendiscord.responders.modals.add(new api.ODModalResponder("opendiscord:delete-ticket-reason",/^od:delete-ticket-reason_/)) +export async function registerVerifyBars(){ + //DELETE TICKET + opendiscord.verifybars.add(new api.ODVerifyBar("opendiscord:delete-ticket")) + opendiscord.verifybars.get("opendiscord:delete-ticket").workers.add([ + new api.ODWorker("opendiscord:delete-ticket",0,async (instance,params,origin,cancel) => { + const {user,member,channel,guild,message} = instance + + //responder checks + const hasPerms = await openticketUtils.replyHasPermissions(instance,origin,"delete") + if (!hasPerms) return cancel() + + const isInGuild = await openticketUtils.replyIsInGuild(instance,origin) + if (!isInGuild || !guild || channel.isDMBased()) return cancel() + + const state = await openticketUtils.replyInteractiveMessageState(instance,origin,channel,message,"/delete") + if (!state) return cancel() + + const ticket = await openticketUtils.replyIsTicket(instance,origin) + if (!ticket) return cancel() + + const isAvailable = await openticketUtils.replyTicketIsAvailable(instance,origin,ticket) + if (!isAvailable) return cancel() + + const messagesHaveBeenSent = await openticketUtils.replyMessageMustBeSentBeforeClose(instance,origin,ticket,"delete") + if (!messagesHaveBeenSent) return cancel() + + //don't allow deleteWithoutTranscript to non-global-admins when enabled + const withoutTranscript = (params.selectedButtonId == "accept-without-transcript") + if (withoutTranscript && generalConfig.data.ticketSystem.adminOnlyDeleteWithoutTranscript){ + if (!opendiscord.permissions.hasPermissions("support",await opendiscord.permissions.getPermissions(instance.user,instance.channel,instance.guild,{allowChannelRoleScope:false,allowChannelUserScope:false,allowGlobalRoleScope:true,allowGlobalUserScope:true}))){ + instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-no-permissions").build("button",{guild:instance.guild,channel:instance.channel,user:instance.user,permissions:["support"]})) + return cancel() + } + } + + //fetch state details + const originalUser = ((state.data.messageAuthor) ? await opendiscord.client.fetchUser(state.data.messageAuthor) : user) ?? user + const originalReason = state.data.messageReason ?? null + const originalMsgType = state.data.messageType + + //start deleting ticket + if (params.selectedButtonId == "cancel"){ + //CANCEL + if (originalMsgType == "ticket-message"){ + //ticket message + await instance.update(await opendiscord.builders.messages.getSafe("opendiscord:ticket-message").build("verifybar",{guild,channel,user,ticket})) + }else if (originalMsgType == "close-message"){ + //close message + await instance.update(await opendiscord.builders.messages.getSafe("opendiscord:close-message").build("verifybar",{guild,channel,user:originalUser,ticket,reason:originalReason})) + }else if (originalMsgType == "reopen-message"){ + //reopen message + await instance.update(await opendiscord.builders.messages.getSafe("opendiscord:reopen-message").build("verifybar",{guild,channel,user:originalUser,ticket,reason:originalReason})) + }else if (originalMsgType == "autoclose-message"){ + //autoclose message + await instance.update(await opendiscord.builders.messages.getSafe("opendiscord:autoclose-message").build("verifybar",{guild,channel,user:originalUser,ticket})) + } + }else if (params.selectedButtonId == "accept" || params.selectedButtonId == "accept-without-transcript"){ + //DELETE TICKET + await instance.defer("update",false) + + if (originalMsgType == "ticket-message"){ + //ticket message + opendiscord.actions.get("opendiscord:delete-ticket").run(originalMsgType,{guild,channel,user,ticket,reason:null,sendMessage:true,withoutTranscript}) + ticket.get("opendiscord:for-deletion").value = true //disable ticket message buttons + await instance.update(await opendiscord.builders.messages.getSafe("opendiscord:ticket-message").build("other",{guild,channel,user,ticket})) + + }else if (originalMsgType == "close-message"){ + //converted to delete message + opendiscord.actions.get("opendiscord:delete-ticket").run(originalMsgType,{guild,channel,user,ticket,reason:null,sendMessage:false,withoutTranscript}) + await instance.update(await opendiscord.builders.messages.getSafe("opendiscord:delete-message").build(originalMsgType,{guild,channel,user,ticket,reason:null})) + + }else if (originalMsgType == "reopen-message"){ + //converted to delete message + opendiscord.actions.get("opendiscord:delete-ticket").run(originalMsgType,{guild,channel,user,ticket,reason:null,sendMessage:false,withoutTranscript}) + await instance.update(await opendiscord.builders.messages.getSafe("opendiscord:delete-message").build(originalMsgType,{guild,channel,user,ticket,reason:null})) + + }else if (originalMsgType == "autoclose-message"){ + //converted to delete message + opendiscord.actions.get("opendiscord:delete-ticket").run(originalMsgType,{guild,channel,user,ticket,reason:null,sendMessage:false,withoutTranscript}) + await instance.update(await opendiscord.builders.messages.getSafe("opendiscord:delete-message").build(originalMsgType,{guild,channel,user,ticket,reason:null})) + + } + }else if (params.selectedButtonId == "accept-with-reason"){ + //DELETE WITH REASON (MODAL) + instance.modal(await opendiscord.components.modals.get("opendiscord:delete-ticket-reason").build("other",{guild,channel,user,ticket,message})) + } + }) + ]) +} + +export async function registerModalResponders(){ + //DELETE WITH REASON MODAL RESPONDER + opendiscord.responders.modals.add(new api.ODModalResponder("opendiscord:delete-ticket-reason",/^od:delete-ticket-reason\|([^|]+)\|([^|]+)/)) opendiscord.responders.modals.get("opendiscord:delete-ticket-reason").workers.add([ - new api.ODWorker("opendiscord:delete-ticket-reason",0,async (instance,params,source,cancel) => { - const {guild,channel,user} = instance - if (!channel) return - if (!guild){ - //error - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-not-in-guild").build(source,{channel,user:instance.user})) - return cancel() - } - const ticket = opendiscord.tickets.get(instance.interaction.customId.split("_")[1]) - if (!ticket || channel.isDMBased()){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-ticket-unknown").build("button",{guild,channel,user})) - return - } + new api.ODWorker("opendiscord:delete-ticket-reason",0,async (instance,params,origin,cancel) => { + const {guild,user} = instance - const originalSource = instance.interaction.customId.split("_")[2] as Exclude + const match = /^od:delete-ticket-reason\|([^|]+)\|([^|]+)/.exec(instance.interaction.customId) + if (!match) return cancel() + const channel = await opendiscord.client.fetchTextChannel(match[1]) + const message = await opendiscord.client.fetchChannelMessage(match[1],match[2]) + + //responder checks + const hasPerms = await openticketUtils.replyHasPermissions(instance,origin,"delete") + if (!hasPerms) return cancel() + + const isInGuild = await openticketUtils.replyIsInGuild(instance,origin) + if (!isInGuild || !guild || !channel || channel.isDMBased()) return cancel() + + const state = await openticketUtils.replyInteractiveMessageState(instance,origin,channel,message,"/delete") + if (!state) return cancel() + + const ticket = await openticketUtils.replyIsTicket(instance,origin) + if (!ticket) return cancel() + + const isAvailable = await openticketUtils.replyTicketIsAvailable(instance,origin,ticket) + if (!isAvailable) return cancel() + + //fetch state details const reason = instance.values.getTextField("reason",true) + const originalMsgOrigin = state.data.messageOrigin + const originalMsgType = state.data.messageType - //delete with reason - if (originalSource == "ticket-message"){ - await instance.defer("update",false) - //don't await DELETE action => else it will update the message after the channel has been deleted - opendiscord.actions.get("opendiscord:delete-ticket").run(originalSource,{guild,channel,user,ticket,reason,sendMessage:true,withoutTranscript:false}) - //update ticket (for ticket message) => no-await doesn't wait for the action to set this variable - ticket.get("opendiscord:for-deletion").value = true + //start deleting ticket + //don't await DELETE action => else it will update the message after the channel has been deleted + await instance.defer("update",false) + + if (originalMsgType == "ticket-message"){ + //ticket message + opendiscord.actions.get("opendiscord:delete-ticket").run(originalMsgType,{guild,channel,user,ticket,reason,sendMessage:true,withoutTranscript:false}) + ticket.get("opendiscord:for-deletion").value = true //disable ticket message buttons await instance.update(await opendiscord.builders.messages.getSafe("opendiscord:ticket-message").build("other",{guild,channel,user,ticket})) - }else if (originalSource == "close-message"){ - await instance.defer("update",false) - //don't await DELETE action => else it will update the message after the channel has been deleted - opendiscord.actions.get("opendiscord:delete-ticket").run(originalSource,{guild,channel,user,ticket,reason,sendMessage:false,withoutTranscript:false}) - await instance.update(await opendiscord.builders.messages.getSafe("opendiscord:delete-message").build("other",{guild,channel,user,ticket,reason})) - }else if (originalSource == "reopen-message"){ - await instance.defer("update",false) - //don't await DELETE action => else it will update the message after the channel has been deleted - opendiscord.actions.get("opendiscord:delete-ticket").run(originalSource,{guild,channel,user,ticket,reason,sendMessage:false,withoutTranscript:false}) - await instance.update(await opendiscord.builders.messages.getSafe("opendiscord:delete-message").build("other",{guild,channel,user,ticket,reason})) - }else if (originalSource == "autoclose-message"){ - await instance.defer("update",false) - //don't await DELETE action => else it will update the message after the channel has been deleted - opendiscord.actions.get("opendiscord:delete-ticket").run(originalSource,{guild,channel,user,ticket,reason,sendMessage:false,withoutTranscript:false}) - await instance.update(await opendiscord.builders.messages.getSafe("opendiscord:delete-message").build("other",{guild,channel,user,ticket,reason})) - }else{ - await instance.defer("update",false) - //don't await DELETE action => else it will update the message after the channel has been deleted - opendiscord.actions.get("opendiscord:delete-ticket").run(originalSource,{guild,channel,user,ticket,reason,sendMessage:true,withoutTranscript:false}) + + }else if (originalMsgType == "close-message"){ + //converted to delete message + opendiscord.actions.get("opendiscord:delete-ticket").run(originalMsgType,{guild,channel,user,ticket,reason,sendMessage:false,withoutTranscript:false}) + await instance.update(await opendiscord.builders.messages.getSafe("opendiscord:delete-message").build(originalMsgType,{guild,channel,user,ticket,reason})) + + }else if (originalMsgType == "reopen-message"){ + //converted to delete message + opendiscord.actions.get("opendiscord:delete-ticket").run(originalMsgType,{guild,channel,user,ticket,reason,sendMessage:false,withoutTranscript:false}) + await instance.update(await opendiscord.builders.messages.getSafe("opendiscord:delete-message").build(originalMsgType,{guild,channel,user,ticket,reason})) + + }else if (originalMsgType == "autoclose-message"){ + //converted to delete message + opendiscord.actions.get("opendiscord:delete-ticket").run(originalMsgType,{guild,channel,user,ticket,reason,sendMessage:false,withoutTranscript:false}) + await instance.update(await opendiscord.builders.messages.getSafe("opendiscord:delete-message").build(originalMsgType,{guild,channel,user,ticket,reason})) + } }) ]) diff --git a/src/commands/help.ts b/src/commands/help.ts index a8eb799..1ad49a2 100644 --- a/src/commands/help.ts +++ b/src/commands/help.ts @@ -1,50 +1,46 @@ /////////////////////////////////////// //HELP COMMAND /////////////////////////////////////// -import {opendiscord, api, utilities} from "../index" +import {opendiscord, api, utilities, openticketUtils} from "../index.js" import * as discord from "discord.js" const generalConfig = opendiscord.configs.get("opendiscord:general") -export const registerCommandResponders = async () => { +export async function registerCommandResponders(){ //HELP COMMAND RESPONDER opendiscord.responders.commands.add(new api.ODCommandResponder("opendiscord:help",generalConfig.data.prefix,"help")) opendiscord.responders.commands.get("opendiscord:help").workers.add([ - new api.ODWorker("opendiscord:help",0,async (instance,params,source,cancel) => { + new api.ODWorker("opendiscord:help",0,async (instance,params,origin,cancel) => { const {guild,channel,user,member} = instance - //check permissions - const permsResult = await opendiscord.permissions.checkCommandPerms(generalConfig.data.system.permissions.help,"support",user,member,channel,guild) - if (!permsResult.hasPerms){ - if (permsResult.reason == "not-in-server") await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-not-in-guild").build("button",{channel,user})) - else await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-no-permissions").build(source,{guild,channel,user,permissions:["support"]})) - return cancel() - } + //responder checks + const hasPerms = await openticketUtils.replyHasPermissions(instance,origin,"help") + if (!hasPerms) return cancel() //calculate slash/text mode for help menu let mode: "slash"|"text" - if (generalConfig.data.slashCommands && generalConfig.data.textCommands) mode = (generalConfig.data.system.preferSlashOverText) ? "slash" : "text" + if (generalConfig.data.slashCommands && generalConfig.data.textCommands) mode = (generalConfig.data.ticketSystem.preferSlashOverText) ? "slash" : "text" else if (!generalConfig.data.slashCommands) mode = "text" else mode = "slash" - await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:help-menu").build(source,{mode,page:0})) + await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:help-menu").build(origin,{mode,page:0})) }), - new api.ODWorker("opendiscord:logs",-1,(instance,params,source,cancel) => { + new api.ODWorker("opendiscord:logs",-1,(instance,params,origin,cancel) => { opendiscord.log(instance.user.displayName+" used the 'help' command!","info",[ {key:"user",value:instance.user.username}, {key:"userid",value:instance.user.id,hidden:true}, {key:"channelid",value:instance.channel.id,hidden:true}, - {key:"method",value:source} + {key:"method",value:origin} ]) }) ]) } -export const registerButtonResponders = async () => { +export async function registerButtonResponders(){ //HELP MENU SWITCH BUTTON RESPONDER opendiscord.responders.buttons.add(new api.ODButtonResponder("opendiscord:help-menu-switch",/^od:help-menu-switch_(slash|text)/)) opendiscord.responders.buttons.get("opendiscord:help-menu-switch").workers.add( - new api.ODWorker("opendiscord:update-help-menu",0,async (instance,params,source,cancel) => { + new api.ODWorker("opendiscord:update-help-menu",0,async (instance,params,origin,cancel) => { const mode = instance.interaction.customId.split("_")[1] as "slash"|"text" const pageButton = instance.getMessageComponent("button",/^od:help-menu-page_([0-9]+)/) const currentPage = (pageButton && pageButton.customId) ? Number(pageButton.customId.split("_")[1]) : 0 @@ -59,7 +55,7 @@ export const registerButtonResponders = async () => { //HELP MENU PREVIOUS BUTTON RESPONDER opendiscord.responders.buttons.add(new api.ODButtonResponder("opendiscord:help-menu-previous",/^od:help-menu-previous/)) opendiscord.responders.buttons.get("opendiscord:help-menu-previous").workers.add( - new api.ODWorker("opendiscord:update-help-menu",0,async (instance,params,source,cancel) => { + new api.ODWorker("opendiscord:update-help-menu",0,async (instance,params,origin,cancel) => { const switchButton = instance.getMessageComponent("button",/^od:help-menu-switch_(slash|text)/) const pageButton = instance.getMessageComponent("button",/^od:help-menu-page_([0-9]+)/) const currentPage = (pageButton && pageButton.customId) ? Number(pageButton.customId.split("_")[1]) : 0 @@ -73,7 +69,7 @@ export const registerButtonResponders = async () => { //HELP MENU NEXT BUTTON RESPONDER opendiscord.responders.buttons.add(new api.ODButtonResponder("opendiscord:help-menu-next",/^od:help-menu-next/)) opendiscord.responders.buttons.get("opendiscord:help-menu-next").workers.add( - new api.ODWorker("opendiscord:update-help-menu",0,async (instance,params,source,cancel) => { + new api.ODWorker("opendiscord:update-help-menu",0,async (instance,params,origin,cancel) => { const switchButton = instance.getMessageComponent("button",/^od:help-menu-switch_(slash|text)/) const pageButton = instance.getMessageComponent("button",/^od:help-menu-page_([0-9]+)/) const currentPage = (pageButton && pageButton.customId) ? Number(pageButton.customId.split("_")[1]) : 0 diff --git a/src/commands/move.ts b/src/commands/move.ts index 93ad8ed..3ec418f 100644 --- a/src/commands/move.ts +++ b/src/commands/move.ts @@ -1,48 +1,36 @@ /////////////////////////////////////// //MOVE COMMAND /////////////////////////////////////// -import {opendiscord, api, utilities} from "../index" +import {opendiscord, api, utilities, openticketUtils} from "../index.js" import * as discord from "discord.js" const generalConfig = opendiscord.configs.get("opendiscord:general") -export const registerCommandResponders = async () => { +export async function registerCommandResponders(){ //MOVE COMMAND RESPONDER opendiscord.responders.commands.add(new api.ODCommandResponder("opendiscord:move",generalConfig.data.prefix,"move")) opendiscord.responders.commands.get("opendiscord:move").workers.add([ - new api.ODWorker("opendiscord:move",0,async (instance,params,source,cancel) => { + new api.ODWorker("opendiscord:move",0,async (instance,params,origin,cancel) => { const {guild,channel,user,member} = instance - - //check permissions - const permsResult = await opendiscord.permissions.checkCommandPerms(generalConfig.data.system.permissions.move,"support",user,member,channel,guild) - if (!permsResult.hasPerms){ - if (permsResult.reason == "not-in-server") await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-not-in-guild").build("button",{channel,user})) - else await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-no-permissions").build(source,{guild,channel,user,permissions:["support"]})) - return cancel() - } - - //check if in guild/server - if (!guild){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-not-in-guild").build("button",{channel,user})) - return cancel() - } - - //check if ticket exists - const ticket = opendiscord.tickets.get(channel.id) - if (!ticket || channel.isDMBased()){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-ticket-unknown").build("button",{guild,channel,user})) - return cancel() - } - //return when busy - if (ticket.get("opendiscord:busy").value){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-ticket-busy").build("button",{guild,channel,user})) - return cancel() - } + + //responder checks + const hasPerms = await openticketUtils.replyHasPermissions(instance,origin,"move") + if (!hasPerms) return cancel() + + const isInGuild = await openticketUtils.replyIsInGuild(instance,origin) + if (!isInGuild || !guild || channel.isDMBased()) return cancel() + + const ticket = await openticketUtils.replyIsTicket(instance,origin) + if (!ticket) return cancel() + + const isAvailable = await openticketUtils.replyTicketIsAvailable(instance,origin,ticket) + if (!isAvailable) return cancel() + //fetch data const id = instance.options.getString("id",true) const reason = instance.options.getString("reason",false) - const option = opendiscord.options.get(id) + //return if unknown option if (!option || !(option instanceof api.ODTicketOption)){ instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error").build("button",{guild,channel,user,error:opendiscord.languages.getTranslation("errors.titles.unknownOption"),layout:"simple"})) @@ -56,15 +44,15 @@ export const registerCommandResponders = async () => { //start moving ticket await instance.defer(false) - await opendiscord.actions.get("opendiscord:move-ticket").run(source,{guild,channel,user,ticket,reason,sendMessage:false,data:option}) - await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:move-message").build(source,{guild,channel,user,ticket,reason,data:option})) + await opendiscord.actions.get("opendiscord:move-ticket").run(origin,{guild,channel,user,ticket,reason,sendMessage:false,data:option}) + await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:move-message").build(origin,{guild,channel,user,ticket,reason,data:option})) }), - new api.ODWorker("opendiscord:logs",-1,(instance,params,source,cancel) => { + new api.ODWorker("opendiscord:logs",-1,(instance,params,origin,cancel) => { opendiscord.log(instance.user.displayName+" used the 'move' command!","info",[ {key:"user",value:instance.user.username}, {key:"userid",value:instance.user.id,hidden:true}, {key:"channelid",value:instance.channel.id,hidden:true}, - {key:"method",value:source} + {key:"method",value:origin} ]) }) ]) diff --git a/src/commands/panel.ts b/src/commands/panel.ts index cb84407..d6607e7 100644 --- a/src/commands/panel.ts +++ b/src/commands/panel.ts @@ -1,59 +1,297 @@ /////////////////////////////////////// //STATS COMMAND /////////////////////////////////////// -import {opendiscord, api, utilities} from "../index" +import {opendiscord, api, utilities, openticketUtils} from "../index.js" import * as discord from "discord.js" const generalConfig = opendiscord.configs.get("opendiscord:general") +const panelMsgState = opendiscord.states.get("opendiscord:panel-message") +const lang = opendiscord.languages -export const registerCommandResponders = async () => { +export async function registerCommandResponders(){ //PANEL COMMAND RESPONDER opendiscord.responders.commands.add(new api.ODCommandResponder("opendiscord:panel",generalConfig.data.prefix,/^panel/)) opendiscord.responders.commands.get("opendiscord:panel").workers.add([ - new api.ODWorker("opendiscord:panel",0,async (instance,params,source,cancel) => { + new api.ODWorker("opendiscord:panel",0,async (instance,params,origin,cancel) => { const {guild,channel,user,member} = instance - - //check permissions - const permsResult = await opendiscord.permissions.checkCommandPerms(generalConfig.data.system.permissions.panel,"support",user,member,channel,guild) - if (!permsResult.hasPerms){ - if (permsResult.reason == "not-in-server") await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-not-in-guild").build("button",{channel,user})) - else await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-no-permissions").build(source,{guild,channel,user,permissions:["support"]})) - return cancel() - } - - //check is in guild/server - if (!guild || instance.channel.type == discord.ChannelType.GroupDM){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-not-in-guild").build(source,{channel:instance.channel,user:instance.user})) - return cancel() - } - //get panel data + //responder checks + const hasPerms = await openticketUtils.replyHasPermissions(instance,origin,"panel") + if (!hasPerms) return cancel() + + const isInGuild = await openticketUtils.replyIsInGuild(instance,origin) + if (!isInGuild || !guild || channel.isDMBased()) return cancel() + + //fetch data const id = instance.options.getString("id",true) const panel = opendiscord.panels.get(id) if (!panel){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-panel-unknown").build(source,{guild,channel,user})) + instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-panel-unknown").build(origin,{guild,channel,user})) return cancel() } - await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:panel-ready").build(source,{guild,channel,user,panel})) - const panelMessage = await instance.channel.send((await opendiscord.builders.messages.getSafe("opendiscord:panel").build(source,{guild,channel,user,panel})).message) - - //add panel to database (this way, the bot knows where all panels are located) - const globalDatabase = opendiscord.databases.get("opendiscord:global") - await globalDatabase.set("opendiscord:panel-message",panelMessage.channel.id+"_"+panelMessage.id,panel.id.value) - - //add panel to database for auto-update - if (instance.options.getBoolean("auto-update",false)){ - await globalDatabase.set("opendiscord:panel-update",panelMessage.channel.id+"_"+panelMessage.id,panel.id.value) - } + await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:panel-ready").build(origin,{guild,channel,user,panel})) + const panelMessage = await channel.send((await opendiscord.builders.messages.getSafe("opendiscord:panel").build(origin,{guild,channel,user,panel,isSubPanel:false})).message) + if (panelMessage) await panelMsgState.setMsgState({channel,message:panelMessage},{ + messageOrigin:origin, + panelId:panel.id.value, + panelOptionIds:panel.get("opendiscord:options").value, + panelAutoUpdate:(instance.options.getBoolean("auto-update",false) ?? false), + isSubPanel:false + },panelMessage.flags.has("Ephemeral")) }), - new api.ODWorker("opendiscord:logs",-1,(instance,params,source,cancel) => { + new api.ODWorker("opendiscord:logs",-1,(instance,params,origin,cancel) => { opendiscord.log(instance.user.displayName+" used the 'panel' command!","info",[ {key:"user",value:instance.user.username}, {key:"userid",value:instance.user.id,hidden:true}, {key:"channelid",value:instance.channel.id,hidden:true}, - {key:"method",value:source} + {key:"method",value:origin} ]) }) ]) +} + +export async function registerButtonResponders(){ + //SUBPANEL OPTION BUTTON RESPONDER + opendiscord.responders.buttons.add(new api.ODButtonResponder("opendiscord:subpanel-option",/^od:subpanel-option\|([^|]+)/)) + opendiscord.responders.buttons.get("opendiscord:subpanel-option").workers.add( + new api.ODWorker("opendiscord:subpanel-option",0,async (instance,params,origin,cancel) => { + const {guild,channel,user,message} = instance + + const match = /^od:subpanel-option\|([^|]+)/.exec(instance.interaction.customId) + if (!match) return cancel() + const optionId = match[1] + + //check message state + const state = await panelMsgState.getMsgState({channel,message}) + if (!state){ + await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error").build(origin,{guild,channel,user,error:lang.getTranslationWithParams("errors.descriptions.panelStateExpired",["/panel"]),layout:"simple",customTitle:"Message State Expired"})) + return cancel() + } + + //responder checks + const isInGuild = await openticketUtils.replyIsInGuild(instance,origin) + if (!isInGuild || !guild || channel.isDMBased()) return cancel() + + //get option data + const option = opendiscord.options.get(optionId) + if (!option || !(option instanceof api.ODSubPanelOption)){ + instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-option-unknown").build(origin,{guild:instance.guild,channel:instance.channel,user:instance.user})) + return cancel() + } + + //get panel data + const subPanel = opendiscord.panels.get(option.get("opendiscord:panel-id").value) + if (!subPanel){ + instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-panel-unknown").build(origin,{guild,channel,user})) + return cancel() + } + + //send sub-panel message (ephemeral) + const panelMessage = await instance.reply((await opendiscord.builders.messages.getSafe("opendiscord:panel").build("sub-panel",{guild,channel,user,panel:subPanel,isSubPanel:true}))) + if (panelMessage.success) await panelMsgState.setMsgState({channel,message:panelMessage.message},{ + messageOrigin:"sub-panel", + panelId:subPanel.id.value, + panelOptionIds:subPanel.get("opendiscord:options").value, + panelAutoUpdate:false, + isSubPanel:true + },panelMessage.ephemeral) + + opendiscord.log(instance.user.displayName+" created a sub-panel!","info",[ + {key:"user",value:instance.user.username}, + {key:"userid",value:instance.user.id,hidden:true}, + {key:"channelid",value:instance.channel.id,hidden:true}, + {key:"method",value:origin} + ]) + }) + ) +} + +export async function registerDropdownResponders(){ + opendiscord.responders.dropdowns.add(new api.ODDropdownResponder("opendiscord:panel-dropdown",/^od:panel-dropdown/)) + + //TICKET DROPDOWN RESPONDER + opendiscord.responders.dropdowns.get("opendiscord:panel-dropdown").workers.add( + new api.ODWorker("opendiscord:dropdown-ticket",2,async (instance,params,origin,cancel) => { + const {guild,channel,user,message} = instance + + //RETURN WHEN NOT OF TYPE TICKET (role or sub-panel responders might catch it) + const match = /^od:ticket-option\|([^|]+)/.exec(instance.values.getStringValues()[0]) + if (!match) return + const optionId = match[1] + + //check message state + const state = await panelMsgState.getMsgState({channel,message}) + if (!state){ + await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error").build(origin,{guild,channel,user,error:lang.getTranslationWithParams("errors.descriptions.panelStateExpired",["/panel"]),layout:"simple",customTitle:"Message State Expired"})) + return cancel() + } + + //responder checks + const isInGuild = await openticketUtils.replyIsInGuild(instance,origin) + if (!isInGuild || !guild || channel.isDMBased()) return cancel() + + //get option data + + const option = opendiscord.options.get(optionId) + if (!option || !(option instanceof api.ODTicketOption)){ + instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-option-unknown").build(origin,{guild:instance.guild,channel:instance.channel,user:instance.user})) + return cancel() + } + + //start ticket creation + if (option.exists("opendiscord:questions") && option.get("opendiscord:questions").value.length > 0){ + //SEND MODAL + instance.modal(await opendiscord.components.modals.get("opendiscord:ticket-questions").build("panel-dropdown",{guild,channel,user,option})) + }else{ + //check ticket permissions (modals need check after submit) + if (!(await openticketUtils.checkTicketCreationPerms(instance,"panel-dropdown",guild,user,option))) return cancel() + + //CREATE TICKET + await instance.defer((generalConfig.data.ticketSystem.replyOnTicketCreation) ? "reply" : "update",true) + + const res = await opendiscord.actions.get("opendiscord:create-ticket").run("panel-dropdown",{guild,user,answers:[],option}) + if (!res.channel || !res.ticket){ + //error + await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error").build(origin,{guild,channel:instance.channel,user,error:"Unable to receive ticket or channel from callback! #1",layout:"advanced"})) + return cancel() + } + if (generalConfig.data.ticketSystem.replyOnTicketCreation) await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:ticket-created").build("panel-dropdown",{guild,channel:res.channel,user,ticket:res.ticket})) + } + + //update panel after dropdown usage (reset panel choice) + const panel = opendiscord.panels.get(state.data.panelId) + if (panel){ + const panelMessage = await instance.message.edit((await opendiscord.builders.messages.getSafe("opendiscord:panel").build("auto-update",{guild,channel,user,panel,isSubPanel:state.data.isSubPanel})).message) + if (panelMessage) await panelMsgState.setMsgState({channel,message:panelMessage},{ + messageOrigin:"auto-update", + panelId:panel.id.value, + panelOptionIds:panel.get("opendiscord:options").value, + panelAutoUpdate:state.data.panelAutoUpdate, //same value + isSubPanel:state.data.isSubPanel //same value + },panelMessage.flags.has("Ephemeral")) + } + }) + ) + + //ROLE DROPDOWN RESPONDER + opendiscord.responders.dropdowns.get("opendiscord:panel-dropdown").workers.add( + new api.ODWorker("opendiscord:dropdown-role",1,async (instance,params,origin,cancel) => { + const {guild,channel,user,message} = instance + + //RETURN WHEN NOT OF TYPE ROLE (ticket or sub-panel responders might catch it) + const match = /^od:role-option\|([^|]+)/.exec(instance.values.getStringValues()[0]) + if (!match) return + const optionId = match[1] + + //check message state + const state = await panelMsgState.getMsgState({channel,message}) + if (!state){ + await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error").build(origin,{guild,channel,user,error:lang.getTranslationWithParams("errors.descriptions.panelStateExpired",["/panel"]),layout:"simple",customTitle:"Message State Expired"})) + return cancel() + } + + //responder checks + const isInGuild = await openticketUtils.replyIsInGuild(instance,origin) + if (!isInGuild || !guild || channel.isDMBased()) return cancel() + + //get option data + const option = opendiscord.options.get(optionId) + if (!option || !(option instanceof api.ODRoleOption)){ + //error + instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-option-unknown").build(origin,{guild:instance.guild,channel:instance.channel,user:instance.user})) + return cancel() + } + + //reaction role + await instance.defer(generalConfig.data.ticketSystem.replyOnReactionRole ? "reply" : "update",true) + const res = await opendiscord.actions.get("opendiscord:reaction-role").run("panel-button",{guild,user,option,overwriteMode:null}) + if (!res.result || !res.role){ + //error + await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error").build(origin,{guild,channel:instance.channel,user,error:"Unable to receive role update data from worker!",layout:"advanced"})) + return cancel() + } + if (generalConfig.data.ticketSystem.replyOnReactionRole) await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:reaction-role").build("panel-button",{guild,user,role:res.role,result:res.result})) + + //update panel after dropdown usage (reset panel choice) + const panel = opendiscord.panels.get(state.data.panelId) + if (panel){ + const panelMessage = await instance.message.edit((await opendiscord.builders.messages.getSafe("opendiscord:panel").build("auto-update",{guild,channel,user,panel,isSubPanel:state.data.isSubPanel})).message) + if (panelMessage) await panelMsgState.setMsgState({channel,message:panelMessage},{ + messageOrigin:"auto-update", + panelId:panel.id.value, + panelOptionIds:panel.get("opendiscord:options").value, + panelAutoUpdate:state.data.panelAutoUpdate, //same value + isSubPanel:state.data.isSubPanel //same value + },panelMessage.flags.has("Ephemeral")) + } + }) + ) + + //SUB-PANEL DROPDOWN RESPONDER + opendiscord.responders.dropdowns.get("opendiscord:panel-dropdown").workers.add( + new api.ODWorker("opendiscord:dropdown-subpanel",0,async (instance,params,origin,cancel) => { + const {guild,channel,user,message} = instance + + //RETURN WHEN NOT OF TYPE SUB-PANEL (ticket or role responders might catch it) + const match = /^od:subpanel-option\|([^|]+)/.exec(instance.values.getStringValues()[0]) + if (!match) return + const optionId = match[1] + + //check message state + const state = await panelMsgState.getMsgState({channel,message}) + if (!state){ + await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error").build(origin,{guild,channel,user,error:lang.getTranslationWithParams("errors.descriptions.panelStateExpired",["/panel"]),layout:"simple",customTitle:"Message State Expired"})) + return cancel() + } + + //responder checks + const isInGuild = await openticketUtils.replyIsInGuild(instance,origin) + if (!isInGuild || !guild || channel.isDMBased()) return cancel() + + //get option data + const option = opendiscord.options.get(optionId) + if (!option || !(option instanceof api.ODSubPanelOption)){ + instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-option-unknown").build(origin,{guild:instance.guild,channel:instance.channel,user:instance.user})) + return cancel() + } + + //get panel data + const subPanel = opendiscord.panels.get(option.get("opendiscord:panel-id").value) + if (!subPanel){ + instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-panel-unknown").build(origin,{guild,channel,user})) + return cancel() + } + + //send sub-panel message (ephemeral) + const panelMessage = await instance.reply((await opendiscord.builders.messages.getSafe("opendiscord:panel").build("sub-panel",{guild,channel,user,panel:subPanel,isSubPanel:true}))) + if (panelMessage.success) await panelMsgState.setMsgState({channel,message:panelMessage.message},{ + messageOrigin:"sub-panel", + panelId:subPanel.id.value, + panelOptionIds:subPanel.get("opendiscord:options").value, + panelAutoUpdate:false, + isSubPanel:true + },panelMessage.ephemeral) + + opendiscord.log(instance.user.displayName+" created a sub-panel!","info",[ + {key:"user",value:instance.user.username}, + {key:"userid",value:instance.user.id,hidden:true}, + {key:"channelid",value:instance.channel.id,hidden:true}, + {key:"method",value:origin} + ]) + + //update panel after dropdown usage (reset panel choice) + const panel = opendiscord.panels.get(state.data.panelId) + if (panel){ + const panelMessage = await instance.message.edit((await opendiscord.builders.messages.getSafe("opendiscord:panel").build("auto-update",{guild,channel,user,panel,isSubPanel:state.data.isSubPanel})).message) + if (panelMessage) await panelMsgState.setMsgState({channel,message:panelMessage},{ + messageOrigin:"auto-update", + panelId:panel.id.value, + panelOptionIds:panel.get("opendiscord:options").value, + panelAutoUpdate:state.data.panelAutoUpdate, //same value + isSubPanel:state.data.isSubPanel //same value + },panelMessage.flags.has("Ephemeral")) + } + }) + ) } \ No newline at end of file diff --git a/src/commands/pin.ts b/src/commands/pin.ts index f1a8499..413be79 100644 --- a/src/commands/pin.ts +++ b/src/commands/pin.ts @@ -1,151 +1,235 @@ /////////////////////////////////////// //PIN COMMAND /////////////////////////////////////// -import {opendiscord, api, utilities} from "../index" +import {opendiscord, api, utilities, openticketUtils} from "../index.js" import * as discord from "discord.js" const generalConfig = opendiscord.configs.get("opendiscord:general") +const lang = opendiscord.languages +const interactiveMsgState = opendiscord.states.get("opendiscord:interactive-message") -export const registerCommandResponders = async () => { +export async function registerCommandResponders(){ //PIN COMMAND RESPONDER opendiscord.responders.commands.add(new api.ODCommandResponder("opendiscord:pin",generalConfig.data.prefix,"pin")) opendiscord.responders.commands.get("opendiscord:pin").workers.add([ - new api.ODWorker("opendiscord:permissions",1,async (instance,params,source,cancel) => { - const permissionMode = generalConfig.data.system.permissions.pin - - if (permissionMode == "none"){ - //no permissions - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-no-permissions").build("button",{guild:instance.guild,channel:instance.channel,user:instance.user,permissions:[]})) - return cancel() - }else if (permissionMode == "everyone") return - else if (permissionMode == "admin"){ - if (!opendiscord.permissions.hasPermissions("support",await opendiscord.permissions.getPermissions(instance.user,instance.channel,instance.guild))){ - //no permissions - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-no-permissions").build(source,{guild:instance.guild,channel:instance.channel,user:instance.user,permissions:["support"]})) - return cancel() - }else return - }else{ - if (!instance.guild || !instance.member){ - //error - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error").build(source,{guild:instance.guild,channel:instance.channel,user:instance.user,error:"Permission Error: Not in Server #1",layout:"advanced"})) - return cancel() - } - const role = await opendiscord.client.fetchGuildRole(instance.guild,permissionMode) - if (!role){ - //error - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error").build(source,{guild:instance.guild,channel:instance.channel,user:instance.user,error:"Permission Error: Not in Server #2",layout:"advanced"})) - return cancel() - } - if (!role.members.has(instance.member.id)){ - //no permissions - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-no-permissions").build(source,{guild:instance.guild,channel:instance.channel,user:instance.user,permissions:[]})) - return cancel() - }else return - } - }), - new api.ODWorker("opendiscord:pin",0,async (instance,params,source,cancel) => { + new api.ODWorker("opendiscord:pin",0,async (instance,params,origin,cancel) => { const {guild,channel,user,member} = instance - - //check permissions - const permsResult = await opendiscord.permissions.checkCommandPerms(generalConfig.data.system.permissions.pin,"support",user,member,channel,guild) - if (!permsResult.hasPerms){ - if (permsResult.reason == "not-in-server") await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-not-in-guild").build("button",{channel,user})) - else await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-no-permissions").build(source,{guild,channel,user,permissions:["support"]})) - return cancel() - } - //check is in guild/server - if (!guild){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-not-in-guild").build("button",{channel,user})) - return cancel() - } - - //check if ticket exists - const ticket = opendiscord.tickets.get(channel.id) - if (!ticket || channel.isDMBased()){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-ticket-unknown").build("button",{guild,channel,user})) - return cancel() - } - - //return when already pinned - if (ticket.get("opendiscord:pinned").value){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error").build("button",{guild,channel,user,error:opendiscord.languages.getTranslation("errors.actionInvalid.pin"),layout:"simple"})) - return cancel() - } + //responder checks + const hasPerms = await openticketUtils.replyHasPermissions(instance,origin,"pin") + if (!hasPerms) return cancel() + + const isInGuild = await openticketUtils.replyIsInGuild(instance,origin) + if (!isInGuild || !guild || channel.isDMBased()) return cancel() + + const ticket = await openticketUtils.replyIsTicket(instance,origin) + if (!ticket) return cancel() + + const isAvailable = await openticketUtils.replyTicketIsAvailable(instance,origin,ticket) + if (!isAvailable) return cancel() + + const isTicketUnpinned = await openticketUtils.replyTicketMustBeUnpinned(instance,origin,ticket) + if (!isTicketUnpinned) return cancel() - //return when busy - if (ticket.get("opendiscord:busy").value){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-ticket-busy").build("button",{guild,channel,user})) - return cancel() - } - - const reason = instance.options.getString("reason",false) - //start pinning ticket await instance.defer(false) - await opendiscord.actions.get("opendiscord:pin-ticket").run(source,{guild,channel,user,ticket,reason,sendMessage:false}) - await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:pin-message").build(source,{guild,channel,user,ticket,reason})) + const reason = instance.options.getString("reason",false) + await opendiscord.actions.get("opendiscord:pin-ticket").run(origin,{guild,channel,user,ticket,reason,sendMessage:false}) + + //send message & set state + const sentMsg = await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:pin-message").build(origin,{guild,channel,user,ticket,reason})) + if (sentMsg.success) await interactiveMsgState.setMsgState({channel,message:sentMsg.message},{ + messageType:"pin-message", + messageOrigin:origin, + messageAuthor:user.id, + messageReason:reason + },sentMsg.ephemeral) }), - new api.ODWorker("opendiscord:logs",-1,(instance,params,source,cancel) => { + new api.ODWorker("opendiscord:logs",-1,(instance,params,origin,cancel) => { opendiscord.log(instance.user.displayName+" used the 'pin' command!","info",[ {key:"user",value:instance.user.username}, {key:"userid",value:instance.user.id,hidden:true}, {key:"channelid",value:instance.channel.id,hidden:true}, - {key:"method",value:source} + {key:"method",value:origin} ]) }) ]) } -export const registerButtonResponders = async () => { +export async function registerButtonResponders(){ //PIN TICKET BUTTON RESPONDER opendiscord.responders.buttons.add(new api.ODButtonResponder("opendiscord:pin-ticket",/^od:pin-ticket/)) opendiscord.responders.buttons.get("opendiscord:pin-ticket").workers.add( - new api.ODWorker("opendiscord:pin-ticket",0,async (instance,params,source,cancel) => { - const originalSource = instance.interaction.customId.split("_")[1] as Exclude + new api.ODWorker("opendiscord:pin-ticket",0,async (instance,params,origin,cancel) => { + const {guild,channel,user,message} = instance + + //responder checks + const hasPerms = await openticketUtils.replyHasPermissions(instance,origin,"pin") + if (!hasPerms) return cancel() + + const isInGuild = await openticketUtils.replyIsInGuild(instance,origin) + if (!isInGuild || !guild || channel.isDMBased()) return cancel() - if (originalSource == "ticket-message") await opendiscord.verifybars.get("opendiscord:pin-ticket-ticket-message").activate(instance) - else if (originalSource == "unpin-message") await opendiscord.verifybars.get("opendiscord:pin-ticket-unpin-message").activate(instance) - else await instance.defer("update",false) + const state = await openticketUtils.replyInteractiveMessageState(instance,origin,channel,message,"/pin") + if (!state) return cancel() + + const ticket = await openticketUtils.replyIsTicket(instance,origin) + if (!ticket) return cancel() + + const isAvailable = await openticketUtils.replyTicketIsAvailable(instance,origin,ticket) + if (!isAvailable) return cancel() + + const isTicketUnpinned = await openticketUtils.replyTicketMustBeUnpinned(instance,origin,ticket) + if (!isTicketUnpinned) return cancel() + + //fetch state details + const verifybar = opendiscord.verifybars.get("opendiscord:pin-ticket") + const originalUser = ((state.data.messageAuthor) ? await opendiscord.client.fetchUser(state.data.messageAuthor) : user) ?? user + const originalReason = state.data.messageReason ?? null + const originalMsgType = state.data.messageType + + //send verifybar + if (generalConfig.data.ticketSystem.disableVerifyBars){ + //verifybar disabled, directly run response + await verifybar.activate(instance,"accept") + + }else if (originalMsgType == "ticket-message"){ + //ticket message verifybar + const modifiedMsg = opendiscord.components.modifiers.get("opendiscord:pin-ticket-verifybar").modify(opendiscord.builders.messages.getSafe("opendiscord:ticket-message"),originalMsgType,{guild,channel,user,verifybar}) + await instance.update(await modifiedMsg.build("verifybar",{guild,channel,user,ticket})) + }else if (originalMsgType == "unpin-message"){ + //unpin message verifybar + const modifiedMsg = opendiscord.components.modifiers.get("opendiscord:pin-ticket-verifybar").modify(opendiscord.builders.messages.getSafe("opendiscord:unpin-message"),originalMsgType,{guild,channel,user,verifybar}) + await instance.update(await modifiedMsg.build("verifybar",{guild,channel,user:originalUser,ticket,reason:originalReason})) + } }) ) } -export const registerModalResponders = async () => { - //PIN WITH REASON MODAL RESPONDER - opendiscord.responders.modals.add(new api.ODModalResponder("opendiscord:pin-ticket-reason",/^od:pin-ticket-reason_/)) - opendiscord.responders.modals.get("opendiscord:pin-ticket-reason").workers.add([ - new api.ODWorker("opendiscord:pin-ticket-reason",0,async (instance,params,source,cancel) => { - const {guild,channel,user} = instance - if (!channel) return - if (!guild){ - //error - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-not-in-guild").build(source,{channel,user:instance.user})) - return cancel() - } - const ticket = opendiscord.tickets.get(instance.interaction.customId.split("_")[1]) - if (!ticket || channel.isDMBased()){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-ticket-unknown").build("button",{guild,channel,user})) - return - } - const originalSource = instance.interaction.customId.split("_")[2] as Exclude - const reason = instance.values.getTextField("reason",true) - - //pin with reason - if (originalSource == "ticket-message"){ - await instance.defer("update",false) - await opendiscord.actions.get("opendiscord:pin-ticket").run(originalSource,{guild,channel,user,ticket,reason,sendMessage:true}) - await instance.update(await opendiscord.builders.messages.getSafe("opendiscord:ticket-message").build("other",{guild,channel,user,ticket})) - }else if (originalSource == "unpin-message"){ - await instance.defer("update",false) - await opendiscord.actions.get("opendiscord:pin-ticket").run(originalSource,{guild,channel,user,ticket,reason,sendMessage:false}) - await instance.update(await opendiscord.builders.messages.getSafe("opendiscord:pin-message").build("other",{guild,channel,user,ticket,reason})) - }else{ - await instance.defer("update",false) - await opendiscord.actions.get("opendiscord:pin-ticket").run(originalSource,{guild,channel,user,ticket,reason,sendMessage:true}) - } +export async function registerVerifyBars(){ + //PIN TICKET + opendiscord.verifybars.add(new api.ODVerifyBar("opendiscord:pin-ticket")) + opendiscord.verifybars.get("opendiscord:pin-ticket").workers.add([ + new api.ODWorker("opendiscord:pin-ticket",0,async (instance,params,origin,cancel) => { + const {user,member,channel,guild,message} = instance + + //responder checks + const hasPerms = await openticketUtils.replyHasPermissions(instance,origin,"pin") + if (!hasPerms) return cancel() + const isInGuild = await openticketUtils.replyIsInGuild(instance,origin) + if (!isInGuild || !guild || channel.isDMBased()) return cancel() + + const state = await openticketUtils.replyInteractiveMessageState(instance,origin,channel,message,"/pin") + if (!state) return cancel() + + const ticket = await openticketUtils.replyIsTicket(instance,origin) + if (!ticket) return cancel() + + const isAvailable = await openticketUtils.replyTicketIsAvailable(instance,origin,ticket) + if (!isAvailable) return cancel() + + const isTicketUnpinned = await openticketUtils.replyTicketMustBeUnpinned(instance,origin,ticket) + if (!isTicketUnpinned) return cancel() + + //fetch state details + const originalUser = ((state.data.messageAuthor) ? await opendiscord.client.fetchUser(state.data.messageAuthor) : user) ?? user + const originalReason = state.data.messageReason ?? null + const originalMsgType = state.data.messageType + + //start pinning ticket + if (params.selectedButtonId == "cancel"){ + //CANCEL + if (originalMsgType == "ticket-message"){ + //ticket message + await instance.update(await opendiscord.builders.messages.getSafe("opendiscord:ticket-message").build("verifybar",{guild,channel,user,ticket})) + }else if (originalMsgType == "unpin-message"){ + //unpin message + await instance.update(await opendiscord.builders.messages.getSafe("opendiscord:unpin-message").build("verifybar",{guild,channel,user:originalUser,ticket,reason:originalReason})) + } + }else if (params.selectedButtonId == "accept"){ + //PIN TICKET + await instance.defer("update",false) + + if (originalMsgType == "ticket-message"){ + //ticket message + await opendiscord.actions.get("opendiscord:pin-ticket").run(originalMsgType,{guild,channel,user,ticket,reason:null,sendMessage:true}) + await instance.update(await opendiscord.builders.messages.getSafe("opendiscord:ticket-message").build("verifybar",{guild,channel,user,ticket})) + }else if (originalMsgType == "unpin-message"){ + //converted to pin message + await opendiscord.actions.get("opendiscord:pin-ticket").run(originalMsgType,{guild,channel,user,ticket,reason:null,sendMessage:false}) + const sentMsg = await instance.update(await opendiscord.builders.messages.getSafe("opendiscord:pin-message").build("verifybar",{guild,channel,user,ticket,reason:null})) + if (sentMsg.success) await interactiveMsgState.setMsgState({channel,message:sentMsg.message},{ + messageType:"pin-message", + messageOrigin:origin, + messageAuthor:user.id, + messageReason:null + },sentMsg.ephemeral) + } + }else if (params.selectedButtonId == "accept-with-reason"){ + //PIN WITH REASON (MODAL) + instance.modal(await opendiscord.components.modals.get("opendiscord:pin-ticket-reason").build("other",{guild,channel,user,ticket,message})) + } + }) + ]) +} + +export async function registerModalResponders(){ + //PIN WITH REASON MODAL RESPONDER + opendiscord.responders.modals.add(new api.ODModalResponder("opendiscord:pin-ticket-reason",/^od:pin-ticket-reason\|([^|]+)\|([^|]+)/)) + opendiscord.responders.modals.get("opendiscord:pin-ticket-reason").workers.add([ + new api.ODWorker("opendiscord:pin-ticket-reason",0,async (instance,params,origin,cancel) => { + const {guild,user} = instance + + const match = /^od:pin-ticket-reason\|([^|]+)\|([^|]+)/.exec(instance.interaction.customId) + if (!match) return cancel() + const channel = await opendiscord.client.fetchTextChannel(match[1]) + const message = await opendiscord.client.fetchChannelMessage(match[1],match[2]) + + //responder checks + const hasPerms = await openticketUtils.replyHasPermissions(instance,origin,"pin") + if (!hasPerms) return cancel() + + const isInGuild = await openticketUtils.replyIsInGuild(instance,origin) + if (!isInGuild || !guild || !channel || channel.isDMBased()) return cancel() + + const state = await openticketUtils.replyInteractiveMessageState(instance,origin,channel,message,"/pin") + if (!state) return cancel() + + const ticket = await openticketUtils.replyIsTicket(instance,origin) + if (!ticket) return cancel() + + const isAvailable = await openticketUtils.replyTicketIsAvailable(instance,origin,ticket) + if (!isAvailable) return cancel() + + const isTicketUnpinned = await openticketUtils.replyTicketMustBeUnpinned(instance,origin,ticket) + if (!isTicketUnpinned) return cancel() + + //fetch state details + const reason = instance.values.getTextField("reason",true) + const originalMsgOrigin = state.data.messageOrigin + const originalMsgType = state.data.messageType + + //start pinning ticket + await instance.defer("update",false) + + if (originalMsgType == "ticket-message"){ + //ticket message + await opendiscord.actions.get("opendiscord:pin-ticket").run(originalMsgType,{guild,channel,user,ticket,reason:null,sendMessage:true}) + await instance.update(await opendiscord.builders.messages.getSafe("opendiscord:ticket-message").build("other",{guild,channel,user,ticket})) + + }else if (originalMsgType == "unpin-message"){ + //converted to pin message + await opendiscord.actions.get("opendiscord:pin-ticket").run(originalMsgType,{guild,channel,user,ticket,reason:null,sendMessage:false}) + const sentMsg = await instance.update(await opendiscord.builders.messages.getSafe("opendiscord:pin-message").build(originalMsgType,{guild,channel,user,ticket,reason})) + if (sentMsg.success) await interactiveMsgState.setMsgState({channel,message:sentMsg.message},{ + messageType:"pin-message", + messageOrigin:originalMsgOrigin, + messageAuthor:user.id, + messageReason:reason + },sentMsg.ephemeral) + } }) ]) } \ No newline at end of file diff --git a/src/commands/priority.ts b/src/commands/priority.ts index 39812ca..771c3c5 100644 --- a/src/commands/priority.ts +++ b/src/commands/priority.ts @@ -1,44 +1,30 @@ /////////////////////////////////////// //PRIORITY COMMAND /////////////////////////////////////// -import {opendiscord, api, utilities} from "../index" +import {opendiscord, api, utilities, openticketUtils} from "../index.js" import * as discord from "discord.js" const generalConfig = opendiscord.configs.get("opendiscord:general") -export const registerCommandResponders = async () => { +export async function registerCommandResponders(){ //PRIORITY COMMAND RESPONDER opendiscord.responders.commands.add(new api.ODCommandResponder("opendiscord:priority",generalConfig.data.prefix,"priority")) opendiscord.responders.commands.get("opendiscord:priority").workers.add([ - new api.ODWorker("opendiscord:priority",0,async (instance,params,source,cancel) => { + new api.ODWorker("opendiscord:priority",0,async (instance,params,origin,cancel) => { const {guild,channel,user,member} = instance - - //check permissions - const permsResult = await opendiscord.permissions.checkCommandPerms(generalConfig.data.system.permissions.priority,"support",user,member,channel,guild) - if (!permsResult.hasPerms){ - if (permsResult.reason == "not-in-server") await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-not-in-guild").build("button",{channel,user})) - else await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-no-permissions").build(source,{guild,channel,user,permissions:["support"]})) - return cancel() - } - - //check is in guild/server - if (!guild){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-not-in-guild").build("button",{channel,user})) - return cancel() - } - - //check if ticket exists - const ticket = opendiscord.tickets.get(channel.id) - if (!ticket || channel.isDMBased()){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-ticket-unknown").build("button",{guild,channel,user})) - return cancel() - } - - //return when busy - if (ticket.get("opendiscord:busy").value){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-ticket-busy").build("button",{guild,channel,user})) - return cancel() - } + + //responder checks + const hasPerms = await openticketUtils.replyHasPermissions(instance,origin,"priority") + if (!hasPerms) return cancel() + + const isInGuild = await openticketUtils.replyIsInGuild(instance,origin) + if (!isInGuild || !guild || channel.isDMBased()) return cancel() + + const ticket = await openticketUtils.replyIsTicket(instance,origin) + if (!ticket) return cancel() + + const isAvailable = await openticketUtils.replyTicketIsAvailable(instance,origin,ticket) + if (!isAvailable) return cancel() //subcommands const scope = instance.options.getSubCommand() @@ -56,22 +42,67 @@ export const registerCommandResponders = async () => { //start changing ticket priority await instance.defer(false) - await opendiscord.actions.get("opendiscord:update-ticket-priority").run(source,{guild,channel,user,ticket,newPriority:priority,sendMessage:false,reason}) - await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:priority-set").build(source,{guild,channel,user,ticket,priority,reason})) + await opendiscord.actions.get("opendiscord:update-ticket-priority").run(origin,{guild,channel,user,ticket,newPriority:priority,sendMessage:false,reason}) + await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:priority-set").build(origin,{guild,channel,user,ticket,priority,reason})) }else if (scope == "get"){ const priority = opendiscord.priorities.getFromPriorityLevel(ticket.get("opendiscord:priority").value) - await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:priority-get").build(source,{guild,channel,user,ticket,priority})) + await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:priority-get").build(origin,{guild,channel,user,ticket,priority})) } }), - new api.ODWorker("opendiscord:logs",-1,(instance,params,source,cancel) => { + new api.ODWorker("opendiscord:logs",-1,(instance,params,origin,cancel) => { const scope = instance.options.getSubCommand() opendiscord.log(instance.user.displayName+" used the 'priority "+scope+"' command!","info",[ {key:"user",value:instance.user.username}, {key:"userid",value:instance.user.id,hidden:true}, {key:"channelid",value:instance.channel.id,hidden:true}, - {key:"method",value:source} + {key:"method",value:origin} ]) }) ]) +} + +export async function registerDropdownResponders(){ + //PRIORITY DROPDOWN RESPONDER + opendiscord.responders.dropdowns.add(new api.ODDropdownResponder("opendiscord:priority-dropdown",/^od:priority-dropdown/)) + opendiscord.responders.dropdowns.get("opendiscord:priority-dropdown").workers.add( + new api.ODWorker("opendiscord:priority-dropdown",0,async (instance,params,origin,cancel) => { + const {guild,channel,user,message} = instance + + const match = /^od:select-priority\|([^|]+)/.exec(instance.values.getStringValues()[0]) + if (!match) return + const priorityName = match[1] + + const priority = opendiscord.priorities.getAll().find((lvl) => lvl.rawName === priorityName) ?? null + if (!priority){ + instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error").build("button",{guild,channel,user,layout:"simple",error:"Please select a valid priority level.",customTitle:"Unknown Priority Level"})) + return cancel() + } + + //responder checks + const hasPerms = await openticketUtils.replyHasPermissions(instance,origin,"priority") + if (!hasPerms) return cancel() + + const isInGuild = await openticketUtils.replyIsInGuild(instance,origin) + if (!isInGuild || !guild || channel.isDMBased()) return cancel() + + const state = await openticketUtils.replyInteractiveMessageState(instance,origin,channel,message,"/priority") + if (!state) return cancel() + + const ticket = await openticketUtils.replyIsTicket(instance,origin) + if (!ticket) return cancel() + + const isAvailable = await openticketUtils.replyTicketIsAvailable(instance,origin,ticket) + if (!isAvailable) return cancel() + + //fetch state details + const originalMsgOrigin = state.data.messageOrigin + const originalMsgType = state.data.messageType + + //start changing ticket priority + await instance.defer("reply",false) + await opendiscord.actions.get("opendiscord:update-ticket-priority").run("other",{guild,channel,user,ticket,newPriority:priority,sendMessage:false,reason:null}) + await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:priority-set").build("other",{guild,channel,user,ticket,priority,reason:null})) + }) + ) } \ No newline at end of file diff --git a/src/commands/remove.ts b/src/commands/remove.ts index c6ec472..554178d 100644 --- a/src/commands/remove.ts +++ b/src/commands/remove.ts @@ -1,45 +1,32 @@ /////////////////////////////////////// //REMOVE COMMAND /////////////////////////////////////// -import {opendiscord, api, utilities} from "../index" +import {opendiscord, api, utilities, openticketUtils} from "../index.js" import * as discord from "discord.js" const generalConfig = opendiscord.configs.get("opendiscord:general") -export const registerCommandResponders = async () => { +export async function registerCommandResponders(){ //REMOVE COMMAND RESPONDER opendiscord.responders.commands.add(new api.ODCommandResponder("opendiscord:remove",generalConfig.data.prefix,"remove")) opendiscord.responders.commands.get("opendiscord:remove").workers.add([ - new api.ODWorker("opendiscord:remove",0,async (instance,params,source,cancel) => { + new api.ODWorker("opendiscord:remove",0,async (instance,params,origin,cancel) => { const {guild,channel,user,member} = instance - - //check permissions - const permsResult = await opendiscord.permissions.checkCommandPerms(generalConfig.data.system.permissions.remove,"support",user,member,channel,guild) - if (!permsResult.hasPerms){ - if (permsResult.reason == "not-in-server") await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-not-in-guild").build("button",{channel,user})) - else await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-no-permissions").build(source,{guild,channel,user,permissions:["support"]})) - return cancel() - } - //check is in guild/server - if (!guild){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-not-in-guild").build("button",{channel,user})) - return cancel() - } - - //check if ticket exists - const ticket = opendiscord.tickets.get(channel.id) - if (!ticket || channel.isDMBased()){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-ticket-unknown").build("button",{guild,channel,user})) - return cancel() - } + //responder checks + const hasPerms = await openticketUtils.replyHasPermissions(instance,origin,"remove") + if (!hasPerms) return cancel() - //return when busy - if (ticket.get("opendiscord:busy").value){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-ticket-busy").build("button",{guild,channel,user})) - return cancel() - } + const isInGuild = await openticketUtils.replyIsInGuild(instance,origin) + if (!isInGuild || !guild || channel.isDMBased()) return cancel() + + const ticket = await openticketUtils.replyIsTicket(instance,origin) + if (!ticket) return cancel() + + const isAvailable = await openticketUtils.replyTicketIsAvailable(instance,origin,ticket) + if (!isAvailable) return cancel() + //fetch data const data = instance.options.getUser("user",true) const reason = instance.options.getString("reason",false) @@ -52,15 +39,15 @@ export const registerCommandResponders = async () => { //start removing user from ticket await instance.defer(false) - await opendiscord.actions.get("opendiscord:remove-ticket-user").run(source,{guild,channel,user,ticket,reason,sendMessage:false,data}) - await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:remove-message").build(source,{guild,channel,user,ticket,reason,data})) + await opendiscord.actions.get("opendiscord:remove-ticket-user").run(origin,{guild,channel,user,ticket,reason,sendMessage:false,data}) + await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:remove-message").build(origin,{guild,channel,user,ticket,reason,data})) }), - new api.ODWorker("opendiscord:logs",-1,(instance,params,source,cancel) => { + new api.ODWorker("opendiscord:logs",-1,(instance,params,origin,cancel) => { opendiscord.log(instance.user.displayName+" used the 'remove' command!","info",[ {key:"user",value:instance.user.username}, {key:"userid",value:instance.user.id,hidden:true}, {key:"channelid",value:instance.channel.id,hidden:true}, - {key:"method",value:source} + {key:"method",value:origin} ]) }) ]) diff --git a/src/commands/rename.ts b/src/commands/rename.ts index 7bbee26..6b2dee6 100644 --- a/src/commands/rename.ts +++ b/src/commands/rename.ts @@ -1,59 +1,46 @@ /////////////////////////////////////// //RENAME COMMAND /////////////////////////////////////// -import {opendiscord, api, utilities} from "../index" +import {opendiscord, api, utilities, openticketUtils} from "../index.js" import * as discord from "discord.js" const generalConfig = opendiscord.configs.get("opendiscord:general") -export const registerCommandResponders = async () => { +export async function registerCommandResponders(){ //RENAME COMMAND RESPONDER opendiscord.responders.commands.add(new api.ODCommandResponder("opendiscord:rename",generalConfig.data.prefix,"rename")) opendiscord.responders.commands.get("opendiscord:rename").workers.add([ - new api.ODWorker("opendiscord:rename",0,async (instance,params,source,cancel) => { + new api.ODWorker("opendiscord:rename",0,async (instance,params,origin,cancel) => { const {guild,channel,user,member} = instance - - //check permissions - const permsResult = await opendiscord.permissions.checkCommandPerms(generalConfig.data.system.permissions.rename,"support",user,member,channel,guild) - if (!permsResult.hasPerms){ - if (permsResult.reason == "not-in-server") await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-not-in-guild").build("button",{channel,user})) - else await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-no-permissions").build(source,{guild,channel,user,permissions:["support"]})) - return cancel() - } - - //check is in guild/server - if (!guild){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-not-in-guild").build("button",{channel,user})) - return cancel() - } - - //check if ticket exists - const ticket = opendiscord.tickets.get(channel.id) - if (!ticket || channel.isDMBased()){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-ticket-unknown").build("button",{guild,channel,user})) - return cancel() - } - - //return when busy - if (ticket.get("opendiscord:busy").value){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-ticket-busy").build("button",{guild,channel,user})) - return cancel() - } + + //responder checks + const hasPerms = await openticketUtils.replyHasPermissions(instance,origin,"rename") + if (!hasPerms) return cancel() + + const isInGuild = await openticketUtils.replyIsInGuild(instance,origin) + if (!isInGuild || !guild || channel.isDMBased()) return cancel() + + const ticket = await openticketUtils.replyIsTicket(instance,origin) + if (!ticket) return cancel() + + const isAvailable = await openticketUtils.replyTicketIsAvailable(instance,origin,ticket) + if (!isAvailable) return cancel() + //fetch data const name = instance.options.getString("name",true) const reason = instance.options.getString("reason",false) //start renaming ticket await instance.defer(false) - await opendiscord.actions.get("opendiscord:rename-ticket").run(source,{guild,channel,user,ticket,reason,sendMessage:false,data:name}) - await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:rename-message").build(source,{guild,channel,user,ticket,reason,data:name})) + await opendiscord.actions.get("opendiscord:rename-ticket").run(origin,{guild,channel,user,ticket,reason,sendMessage:false,data:name}) + await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:rename-message").build(origin,{guild,channel,user,ticket,reason,data:name})) }), - new api.ODWorker("opendiscord:logs",-1,(instance,params,source,cancel) => { + new api.ODWorker("opendiscord:logs",-1,(instance,params,origin,cancel) => { opendiscord.log(instance.user.displayName+" used the 'rename' command!","info",[ {key:"user",value:instance.user.username}, {key:"userid",value:instance.user.id,hidden:true}, {key:"channelid",value:instance.channel.id,hidden:true}, - {key:"method",value:source} + {key:"method",value:origin} ]) }) ]) diff --git a/src/commands/reopen.ts b/src/commands/reopen.ts index fb86a30..d2fcc55 100644 --- a/src/commands/reopen.ts +++ b/src/commands/reopen.ts @@ -1,123 +1,261 @@ /////////////////////////////////////// //REOPEN COMMAND /////////////////////////////////////// -import {opendiscord, api, utilities} from "../index" +import {opendiscord, api, utilities, openticketUtils} from "../index.js" import * as discord from "discord.js" const generalConfig = opendiscord.configs.get("opendiscord:general") +const lang = opendiscord.languages +const interactiveMsgState = opendiscord.states.get("opendiscord:interactive-message") -export const registerCommandResponders = async () => { +export async function registerCommandResponders(){ //REOPEN COMMAND RESPONDER opendiscord.responders.commands.add(new api.ODCommandResponder("opendiscord:reopen",generalConfig.data.prefix,"reopen")) opendiscord.responders.commands.get("opendiscord:reopen").workers.add([ - new api.ODWorker("opendiscord:reopen",0,async (instance,params,source,cancel) => { + new api.ODWorker("opendiscord:reopen",0,async (instance,params,origin,cancel) => { const {guild,channel,user,member} = instance - //check permissions - const permsResult = await opendiscord.permissions.checkCommandPerms(generalConfig.data.system.permissions.reopen,"support",user,member,channel,guild) - if (!permsResult.hasPerms){ - if (permsResult.reason == "not-in-server") await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-not-in-guild").build("button",{channel,user})) - else await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-no-permissions").build(source,{guild,channel,user,permissions:["support"]})) - return cancel() - } - - //check is in guild/server - if (!guild){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-not-in-guild").build("button",{channel,user})) - return cancel() - } - - //check if ticket exists - const ticket = opendiscord.tickets.get(channel.id) - if (!ticket || channel.isDMBased()){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-ticket-unknown").build("button",{guild,channel,user})) - return cancel() - } - - //return when not closed - if (!ticket.get("opendiscord:closed").value){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error").build("button",{guild,channel,user,error:opendiscord.languages.getTranslation("errors.actionInvalid.reopen"),layout:"simple"})) - return cancel() - } - - //return when busy - if (ticket.get("opendiscord:busy").value){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-ticket-busy").build("button",{guild,channel,user})) - return cancel() - } - - const reason = instance.options.getString("reason",false) + //responder checks + const hasPerms = await openticketUtils.replyHasPermissions(instance,origin,"reopen") + if (!hasPerms) return cancel() + + const isInGuild = await openticketUtils.replyIsInGuild(instance,origin) + if (!isInGuild || !guild || channel.isDMBased()) return cancel() + + const ticket = await openticketUtils.replyIsTicket(instance,origin) + if (!ticket) return cancel() + + const isAvailable = await openticketUtils.replyTicketIsAvailable(instance,origin,ticket) + if (!isAvailable) return cancel() + + const isTicketClosed = await openticketUtils.replyTicketMustBeClosed(instance,origin,ticket) + if (!isTicketClosed) return cancel() //start reopening ticket await instance.defer(false) - await opendiscord.actions.get("opendiscord:reopen-ticket").run(source,{guild,channel,user,ticket,reason,sendMessage:false}) - await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:reopen-message").build(source,{guild,channel,user,ticket,reason})) + const reason = instance.options.getString("reason",false) + await opendiscord.actions.get("opendiscord:reopen-ticket").run(origin,{guild,channel,user,ticket,reason,sendMessage:false}) + + //send message & set state + const sentMsg = await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:reopen-message").build(origin,{guild,channel,user,ticket,reason})) + if (sentMsg.success) await interactiveMsgState.setMsgState({channel,message:sentMsg.message},{ + messageType:"reopen-message", + messageOrigin:origin, + messageAuthor:user.id, + messageReason:reason + },sentMsg.ephemeral) }), - new api.ODWorker("opendiscord:logs",-1,(instance,params,source,cancel) => { + new api.ODWorker("opendiscord:logs",-1,(instance,params,origin,cancel) => { opendiscord.log(instance.user.displayName+" used the 'reopen' command!","info",[ {key:"user",value:instance.user.username}, {key:"userid",value:instance.user.id,hidden:true}, {key:"channelid",value:instance.channel.id,hidden:true}, - {key:"method",value:source} + {key:"method",value:origin} ]) }) ]) } -export const registerButtonResponders = async () => { +export async function registerButtonResponders(){ //REOPEN TICKET BUTTON RESPONDER opendiscord.responders.buttons.add(new api.ODButtonResponder("opendiscord:reopen-ticket",/^od:reopen-ticket/)) opendiscord.responders.buttons.get("opendiscord:reopen-ticket").workers.add( - new api.ODWorker("opendiscord:reopen-ticket",0,async (instance,params,source,cancel) => { - const originalSource = instance.interaction.customId.split("_")[1] as Exclude + new api.ODWorker("opendiscord:reopen-ticket",0,async (instance,params,origin,cancel) => { + const {guild,channel,message,user,member} = instance - if (originalSource == "ticket-message") await opendiscord.verifybars.get("opendiscord:reopen-ticket-ticket-message").activate(instance) - else if (originalSource == "close-message") await opendiscord.verifybars.get("opendiscord:reopen-ticket-close-message").activate(instance) - else if (originalSource == "autoclose-message") await opendiscord.verifybars.get("opendiscord:reopen-ticket-autoclose-message").activate(instance) - else await instance.defer("update",false) + //responder checks + const hasPerms = await openticketUtils.replyHasPermissions(instance,origin,"reopen") + if (!hasPerms) return cancel() + + const isInGuild = await openticketUtils.replyIsInGuild(instance,origin) + if (!isInGuild || !guild || channel.isDMBased()) return cancel() + + const state = await openticketUtils.replyInteractiveMessageState(instance,origin,channel,message,"/reopen") + if (!state) return cancel() + + const ticket = await openticketUtils.replyIsTicket(instance,origin) + if (!ticket) return cancel() + + const isAvailable = await openticketUtils.replyTicketIsAvailable(instance,origin,ticket) + if (!isAvailable) return cancel() + + const isTicketClosed = await openticketUtils.replyTicketMustBeClosed(instance,origin,ticket) + if (!isTicketClosed) return cancel() + + //fetch state details + const verifybar = opendiscord.verifybars.get("opendiscord:reopen-ticket") + const originalUser = ((state.data.messageAuthor) ? await opendiscord.client.fetchUser(state.data.messageAuthor) : user) ?? user + const originalReason = state.data.messageReason ?? null + const originalMsgType = state.data.messageType + + //send verifybar + if (generalConfig.data.ticketSystem.disableVerifyBars){ + //verifybar disabled, directly run response + await verifybar.activate(instance,"accept") + + }else if (originalMsgType == "ticket-message"){ + //ticket message verifybar + const modifiedMsg = opendiscord.components.modifiers.get("opendiscord:reopen-ticket-verifybar").modify(opendiscord.builders.messages.getSafe("opendiscord:ticket-message"),originalMsgType,{guild,channel,user,verifybar}) + await instance.update(await modifiedMsg.build("verifybar",{guild,channel,user,ticket})) + }else if (originalMsgType == "close-message"){ + //close message verifybar + const modifiedMsg = opendiscord.components.modifiers.get("opendiscord:reopen-ticket-verifybar").modify(opendiscord.builders.messages.getSafe("opendiscord:close-message"),originalMsgType,{guild,channel,user,verifybar}) + await instance.update(await modifiedMsg.build("verifybar",{guild,channel,user:originalUser,ticket,reason:originalReason})) + }else if (originalMsgType == "autoclose-message"){ + //autoclose message verifybar + const modifiedMsg = opendiscord.components.modifiers.get("opendiscord:reopen-ticket-verifybar").modify(opendiscord.builders.messages.getSafe("opendiscord:autoclose-message"),originalMsgType,{guild,channel,user,verifybar}) + await instance.update(await modifiedMsg.build("verifybar",{guild,channel,user:originalUser,ticket})) + } }) ) } -export const registerModalResponders = async () => { - //REOPEN WITH REASON MODAL RESPONDER - opendiscord.responders.modals.add(new api.ODModalResponder("opendiscord:reopen-ticket-reason",/^od:reopen-ticket-reason_/)) - opendiscord.responders.modals.get("opendiscord:reopen-ticket-reason").workers.add([ - new api.ODWorker("opendiscord:reopen-ticket-reason",0,async (instance,params,source,cancel) => { - const {guild,channel,user} = instance - if (!channel) return - if (!guild){ - //error - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-not-in-guild").build(source,{channel,user:instance.user})) - return cancel() - } - const ticket = opendiscord.tickets.get(instance.interaction.customId.split("_")[1]) - if (!ticket || channel.isDMBased()){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-ticket-unknown").build("button",{guild,channel,user})) - return - } - - const originalSource = instance.interaction.customId.split("_")[2] as Exclude - const reason = instance.values.getTextField("reason",true) - - //reopen with reason - if (originalSource == "ticket-message"){ - await instance.defer("update",false) - await opendiscord.actions.get("opendiscord:reopen-ticket").run(originalSource,{guild,channel,user,ticket,reason,sendMessage:true}) - await instance.update(await opendiscord.builders.messages.getSafe("opendiscord:ticket-message").build("other",{guild,channel,user,ticket})) - }else if (originalSource == "close-message"){ - await instance.defer("update",false) - await opendiscord.actions.get("opendiscord:reopen-ticket").run(originalSource,{guild,channel,user,ticket,reason,sendMessage:false}) - await instance.update(await opendiscord.builders.messages.getSafe("opendiscord:reopen-message").build("other",{guild,channel,user,ticket,reason})) - }else if (originalSource == "autoclose-message"){ - await instance.defer("update",false) - await opendiscord.actions.get("opendiscord:reopen-ticket").run(originalSource,{guild,channel,user,ticket,reason,sendMessage:false}) - await instance.update(await opendiscord.builders.messages.getSafe("opendiscord:reopen-message").build("other",{guild,channel,user,ticket,reason})) - }else{ - await instance.defer("update",false) - await opendiscord.actions.get("opendiscord:reopen-ticket").run(originalSource,{guild,channel,user,ticket,reason,sendMessage:true}) - } +export async function registerVerifyBars(){ + //REOPEN TICKET + opendiscord.verifybars.add(new api.ODVerifyBar("opendiscord:reopen-ticket")) + opendiscord.verifybars.get("opendiscord:reopen-ticket").workers.add([ + new api.ODWorker("opendiscord:reopen-ticket",0,async (instance,params,origin,cancel) => { + const {user,member,channel,guild,message} = instance + //responder checks + const hasPerms = await openticketUtils.replyHasPermissions(instance,origin,"reopen") + if (!hasPerms) return cancel() + + const isInGuild = await openticketUtils.replyIsInGuild(instance,origin) + if (!isInGuild || !guild || channel.isDMBased()) return cancel() + + const state = await openticketUtils.replyInteractiveMessageState(instance,origin,channel,message,"/reopen") + if (!state) return cancel() + + const ticket = await openticketUtils.replyIsTicket(instance,origin) + if (!ticket) return cancel() + + const isAvailable = await openticketUtils.replyTicketIsAvailable(instance,origin,ticket) + if (!isAvailable) return cancel() + + const isTicketClosed = await openticketUtils.replyTicketMustBeClosed(instance,origin,ticket) + if (!isTicketClosed) return cancel() + + //fetch state details + const originalUser = ((state.data.messageAuthor) ? await opendiscord.client.fetchUser(state.data.messageAuthor) : user) ?? user + const originalReason = state.data.messageReason ?? null + const originalMsgType = state.data.messageType + + //start reopening ticket + if (params.selectedButtonId == "cancel"){ + //CANCEL + if (originalMsgType == "ticket-message"){ + //ticket message + await instance.update(await opendiscord.builders.messages.getSafe("opendiscord:ticket-message").build("verifybar",{guild,channel,user,ticket})) + }else if (originalMsgType == "close-message"){ + //close message + await instance.update(await opendiscord.builders.messages.getSafe("opendiscord:close-message").build("verifybar",{guild,channel,user:originalUser,ticket,reason:originalReason})) + }else if (originalMsgType == "autoclose-message"){ + //autoclose message + await instance.update(await opendiscord.builders.messages.getSafe("opendiscord:autoclose-message").build("verifybar",{guild,channel,user:originalUser,ticket})) + } + }else if (params.selectedButtonId == "accept"){ + //REOPEN TICKET + await instance.defer("update",false) + + if (originalMsgType == "ticket-message"){ + //ticket message + await opendiscord.actions.get("opendiscord:reopen-ticket").run(originalMsgType,{guild,channel,user,ticket,reason:null,sendMessage:true}) + await instance.update(await opendiscord.builders.messages.getSafe("opendiscord:ticket-message").build("verifybar",{guild,channel,user,ticket})) + }else if (originalMsgType == "close-message"){ + //converted to reopen message + await opendiscord.actions.get("opendiscord:reopen-ticket").run(originalMsgType,{guild,channel,user,ticket,reason:null,sendMessage:false}) + const sentMsg = await instance.update(await opendiscord.builders.messages.getSafe("opendiscord:reopen-message").build("verifybar",{guild,channel,user,ticket,reason:null})) + if (sentMsg.success) await interactiveMsgState.setMsgState({channel,message:sentMsg.message},{ + messageType:"reopen-message", + messageOrigin:origin, + messageAuthor:user.id, + messageReason:null + },sentMsg.ephemeral) + }else if (originalMsgType == "autoclose-message"){ + //converted to reopen message + await opendiscord.actions.get("opendiscord:reopen-ticket").run(originalMsgType,{guild,channel,user,ticket,reason:null,sendMessage:false}) + const sentMsg = await instance.update(await opendiscord.builders.messages.getSafe("opendiscord:reopen-message").build("verifybar",{guild,channel,user,ticket,reason:null})) + if (sentMsg.success) await interactiveMsgState.setMsgState({channel,message:sentMsg.message},{ + messageType:"reopen-message", + messageOrigin:origin, + messageAuthor:user.id, + messageReason:null + },sentMsg.ephemeral) + } + }else if (params.selectedButtonId == "accept-with-reason"){ + //REOPEN WITH REASON (MODAL) + instance.modal(await opendiscord.components.modals.get("opendiscord:reopen-ticket-reason").build("other",{guild,channel,user,ticket,message})) + } + }) + ]) +} + +export async function registerModalResponders(){ + //REOPEN WITH REASON MODAL RESPONDER + opendiscord.responders.modals.add(new api.ODModalResponder("opendiscord:reopen-ticket-reason",/^od:reopen-ticket-reason\|([^|]+)\|([^|]+)/)) + opendiscord.responders.modals.get("opendiscord:reopen-ticket-reason").workers.add([ + new api.ODWorker("opendiscord:reopen-ticket-reason",0,async (instance,params,origin,cancel) => { + const {guild,user} = instance + + const match = /^od:reopen-ticket-reason\|([^|]+)\|([^|]+)/.exec(instance.interaction.customId) + if (!match) return cancel() + const channel = await opendiscord.client.fetchTextChannel(match[1]) + const message = await opendiscord.client.fetchChannelMessage(match[1],match[2]) + + //responder checks + const hasPerms = await openticketUtils.replyHasPermissions(instance,origin,"reopen") + if (!hasPerms) return cancel() + + const isInGuild = await openticketUtils.replyIsInGuild(instance,origin) + if (!isInGuild || !guild || !channel || channel.isDMBased()) return cancel() + + const state = await openticketUtils.replyInteractiveMessageState(instance,origin,channel,message,"/reopen") + if (!state) return cancel() + + const ticket = await openticketUtils.replyIsTicket(instance,origin) + if (!ticket) return cancel() + + const isAvailable = await openticketUtils.replyTicketIsAvailable(instance,origin,ticket) + if (!isAvailable) return cancel() + + const isTicketClosed = await openticketUtils.replyTicketMustBeClosed(instance,origin,ticket) + if (!isTicketClosed) return cancel() + + //fetch state details + const reason = instance.values.getTextField("reason",true) + const originalMsgOrigin = state.data.messageOrigin + const originalMsgType = state.data.messageType + + //start reopening ticket + await instance.defer("update",false) + + if (originalMsgType == "ticket-message"){ + //ticket message + await opendiscord.actions.get("opendiscord:reopen-ticket").run(originalMsgType,{guild,channel,user,ticket,reason:null,sendMessage:true}) + await instance.update(await opendiscord.builders.messages.getSafe("opendiscord:ticket-message").build("other",{guild,channel,user,ticket})) + + }else if (originalMsgType == "close-message"){ + //converted to reopen message + await opendiscord.actions.get("opendiscord:reopen-ticket").run(originalMsgType,{guild,channel,user,ticket,reason:null,sendMessage:false}) + const sentMsg = await instance.update(await opendiscord.builders.messages.getSafe("opendiscord:reopen-message").build(originalMsgType,{guild,channel,user,ticket,reason})) + if (sentMsg.success) await interactiveMsgState.setMsgState({channel,message:sentMsg.message},{ + messageType:"reopen-message", + messageOrigin:originalMsgOrigin, + messageAuthor:user.id, + messageReason:reason + },sentMsg.ephemeral) + }else if (originalMsgType == "autoclose-message"){ + //converted to reopen message + await opendiscord.actions.get("opendiscord:reopen-ticket").run(originalMsgType,{guild,channel,user,ticket,reason:null,sendMessage:false}) + const sentMsg = await instance.update(await opendiscord.builders.messages.getSafe("opendiscord:reopen-message").build(originalMsgType,{guild,channel,user,ticket,reason})) + if (sentMsg.success) await interactiveMsgState.setMsgState({channel,message:sentMsg.message},{ + messageType:"reopen-message", + messageOrigin:originalMsgOrigin, + messageAuthor:user.id, + messageReason:reason + },sentMsg.ephemeral) + } }) ]) } \ No newline at end of file diff --git a/src/commands/role.ts b/src/commands/role.ts index e9de633..8e1249a 100644 --- a/src/commands/role.ts +++ b/src/commands/role.ts @@ -1,42 +1,52 @@ /////////////////////////////////////// //ROLE BUTTON (not command) /////////////////////////////////////// -import {opendiscord, api, utilities} from "../index" +import {opendiscord, api, utilities, openticketUtils} from "../index.js" import * as discord from "discord.js" const generalConfig = opendiscord.configs.get("opendiscord:general") +const panelMsgState = opendiscord.states.get("opendiscord:panel-message") +const lang = opendiscord.languages -export const registerButtonResponders = async () => { +export async function registerButtonResponders(){ //ROLE OPTION BUTTON RESPONDER - opendiscord.responders.buttons.add(new api.ODButtonResponder("opendiscord:role-option",/^od:role-option_/)) + opendiscord.responders.buttons.add(new api.ODButtonResponder("opendiscord:role-option",/^od:role-option\|([^|]+)/)) opendiscord.responders.buttons.get("opendiscord:role-option").workers.add( - new api.ODWorker("opendiscord:role-option",0,async (instance,params,source,cancel) => { - const {guild,channel,user} = instance - - //check is in guild/server - if (!guild){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-not-in-guild").build(source,{channel:instance.channel,user:instance.user})) + new api.ODWorker("opendiscord:role-option",0,async (instance,params,origin,cancel) => { + const {guild,channel,user,message} = instance + + const match = /^od:role-option\|([^|]+)/.exec(instance.interaction.customId) + if (!match) return cancel() + const optionId = match[1] + + //check message state + const state = await panelMsgState.getMsgState({channel,message}) + if (!state){ + await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error").build(origin,{guild,channel,user,error:lang.getTranslationWithParams("errors.descriptions.panelStateExpired",["/panel"]),layout:"simple",customTitle:"Message State Expired"})) return cancel() } + + //responder checks + const isInGuild = await openticketUtils.replyIsInGuild(instance,origin) + if (!isInGuild || !guild || channel.isDMBased()) return cancel() //get option data - const optionId = instance.interaction.customId.split("_")[2] const option = opendiscord.options.get(optionId) if (!option || !(option instanceof api.ODRoleOption)){ //error - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-option-unknown").build(source,{guild:instance.guild,channel:instance.channel,user:instance.user})) + instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-option-unknown").build(origin,{guild:instance.guild,channel:instance.channel,user:instance.user})) return cancel() } //reaction role - await instance.defer("reply",true) + await instance.defer(generalConfig.data.ticketSystem.replyOnReactionRole ? "reply" : "update",true) const res = await opendiscord.actions.get("opendiscord:reaction-role").run("panel-button",{guild,user,option,overwriteMode:null}) if (!res.result || !res.role){ //error - await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error").build(source,{guild,channel:instance.channel,user,error:"Unable to receive role update data from worker!",layout:"advanced"})) + await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error").build(origin,{guild,channel:instance.channel,user,error:"Unable to receive role update data from worker!",layout:"advanced"})) return cancel() } - if (generalConfig.data.system.replyOnReactionRole) await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:reaction-role").build("panel-button",{guild,user,role:res.role,result:res.result})) + if (generalConfig.data.ticketSystem.replyOnReactionRole) await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:reaction-role").build("panel-button",{guild,user,role:res.role,result:res.result})) }) ) } \ No newline at end of file diff --git a/src/commands/stats.ts b/src/commands/stats.ts index 46be1fb..d6d6da6 100644 --- a/src/commands/stats.ts +++ b/src/commands/stats.ts @@ -1,117 +1,58 @@ /////////////////////////////////////// //STATS COMMAND /////////////////////////////////////// -import {opendiscord, api, utilities} from "../index" +import {opendiscord, api, utilities, openticketUtils} from "../index.js" import * as discord from "discord.js" const generalConfig = opendiscord.configs.get("opendiscord:general") -export const registerCommandResponders = async () => { +export async function registerCommandResponders(){ //STATS COMMAND RESPONDER opendiscord.responders.commands.add(new api.ODCommandResponder("opendiscord:stats",generalConfig.data.prefix,/^stats/)) opendiscord.responders.commands.get("opendiscord:stats").workers.add([ - new api.ODWorker("opendiscord:permissions",1,async (instance,params,source,cancel) => { - const permissionMode = generalConfig.data.system.permissions.stats - - //command is disabled - if (permissionMode == "none"){ - //no permissions - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-no-permissions").build("button",{guild:instance.guild,channel:instance.channel,user:instance.user,permissions:[]})) - return cancel() - } - - //reset subcommand is owner/developer only - if (instance.options.getSubCommand() == "reset"){ - if (!opendiscord.permissions.hasPermissions("owner",await opendiscord.permissions.getPermissions(instance.user,instance.channel,instance.guild))){ - //no permissions - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-no-permissions").build(source,{guild:instance.guild,channel:instance.channel,user:instance.user,permissions:["owner","developer"]})) - return cancel() - }else return - } - - //permissions for normal scopes - if (permissionMode == "everyone") return - else if (permissionMode == "admin"){ - if (!opendiscord.permissions.hasPermissions("support",await opendiscord.permissions.getPermissions(instance.user,instance.channel,instance.guild))){ - //no permissions - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-no-permissions").build(source,{guild:instance.guild,channel:instance.channel,user:instance.user,permissions:["support"]})) - return cancel() - }else return - }else{ - if (!instance.guild || !instance.member){ - //error - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error").build(source,{guild:instance.guild,channel:instance.channel,user:instance.user,error:"Permission Error: Not in Server #1",layout:"advanced"})) - return cancel() - } - const role = await opendiscord.client.fetchGuildRole(instance.guild,permissionMode) - if (!role){ - //error - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error").build(source,{guild:instance.guild,channel:instance.channel,user:instance.user,error:"Permission Error: Not in Server #2",layout:"advanced"})) - return cancel() - } - if (!role.members.has(instance.member.id)){ - //no permissions - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-no-permissions").build(source,{guild:instance.guild,channel:instance.channel,user:instance.user,permissions:[]})) - return cancel() - }else return - } - }), - new api.ODWorker("opendiscord:stats",0,async (instance,params,source,cancel) => { + new api.ODWorker("opendiscord:stats",0,async (instance,params,origin,cancel) => { const {user,member,channel,guild} = instance //check permissions - if (generalConfig.data.system.permissions.stats === "none"){ - //command is disabled - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-no-permissions").build("button",{guild:instance.guild,channel:instance.channel,user:instance.user,permissions:[]})) - return cancel() - - }else if (instance.options.getSubCommand() === "reset" && !opendiscord.permissions.hasPermissions("owner",await opendiscord.permissions.getPermissions(instance.user,instance.channel,instance.guild))){ + if (instance.options.getSubCommand() === "reset" && !opendiscord.permissions.hasPermissions("owner",await opendiscord.permissions.getPermissions(instance.user,instance.channel,instance.guild))){ //reset --> owner/developer role is required - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-no-permissions").build(source,{guild:instance.guild,channel:instance.channel,user:instance.user,permissions:["owner","developer"]})) + await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-no-permissions").build(origin,{guild:instance.guild,channel:instance.channel,user:instance.user,permissions:["owner","developer"]})) return cancel() - }else{ //default permissions check - const permsResult = await opendiscord.permissions.checkCommandPerms(generalConfig.data.system.permissions.stats,"support",user,member,channel,guild) - if (!permsResult.hasPerms){ - if (permsResult.reason == "not-in-server") await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-not-in-guild").build("button",{channel,user})) - else await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-no-permissions").build(source,{guild,channel,user,permissions:["support"]})) - return cancel() - } + const hasPerms = await openticketUtils.replyHasPermissions(instance,origin,"stats") + if (!hasPerms) return cancel() } - //check is in guild/server - if (!guild){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-not-in-guild").build(source,{channel:instance.channel,user:instance.user})) - return cancel() - } + //responder checks + const isInGuild = await openticketUtils.replyIsInGuild(instance,origin) + if (!isInGuild || !guild || channel.isDMBased()) return cancel() //subcommands const scope = instance.options.getSubCommand() if (!scope || (scope != "global" && scope != "ticket" && scope != "user" && scope != "reset")) return if (scope == "global"){ - await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:stats-global").build(source,{guild,channel,user})) + await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:stats-global").build(origin,{guild,channel,user})) }else if (scope == "ticket"){ const id = instance.options.getChannel("ticket",false)?.id ?? channel.id const ticket = opendiscord.tickets.get(id) - if (ticket) await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:stats-ticket").build(source,{guild,channel,user,scopeData:ticket})) - else await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:stats-ticket-unknown").build(source,{guild,channel,user,id})) + if (ticket) await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:stats-ticket").build(origin,{guild,channel,user,scopeData:ticket})) + else await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:stats-ticket-unknown").build(origin,{guild,channel,user,id})) }else if (scope == "user"){ const statsUser = instance.options.getUser("user",false) ?? user - await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:stats-user").build(source,{guild,channel,user,scopeData:statsUser})) + await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:stats-user").build(origin,{guild,channel,user,scopeData:statsUser})) }else if (scope == "reset"){ const reason = instance.options.getString("reason",false) - opendiscord.stats.reset() - await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:stats-reset").build(source,{guild,channel,user,reason})) - + opendiscord.statistics.reset() + await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:stats-reset").build(origin,{guild,channel,user,reason})) } }), - new api.ODWorker("opendiscord:logs",-1,(instance,params,source,cancel) => { + new api.ODWorker("opendiscord:logs",-1,(instance,params,origin,cancel) => { const scope = instance.options.getSubCommand() let data: string if (scope == "ticket"){ @@ -123,7 +64,7 @@ export const registerCommandResponders = async () => { {key:"user",value:instance.user.username}, {key:"userid",value:instance.user.id,hidden:true}, {key:"channelid",value:instance.channel.id,hidden:true}, - {key:"method",value:source}, + {key:"method",value:origin}, {key:"data",value:data}, ]) }) diff --git a/src/commands/ticket.ts b/src/commands/ticket.ts index ce26854..66d0223 100644 --- a/src/commands/ticket.ts +++ b/src/commands/ticket.ts @@ -1,244 +1,216 @@ /////////////////////////////////////// //TICKET COMMAND /////////////////////////////////////// -import {opendiscord, api, utilities} from "../index" +import {opendiscord, api, utilities, openticketUtils} from "../index.js" import * as discord from "discord.js" const generalConfig = opendiscord.configs.get("opendiscord:general") const lang = opendiscord.languages +const panelMsgState = opendiscord.states.get("opendiscord:panel-message") -async function checkTicketCreationPerms(instance:api.ODButtonResponderInstance|api.ODDropdownResponderInstance|api.ODModalResponderInstance|api.ODCommandResponderInstance,source:api.ODActionManagerIds_Default["opendiscord:create-ticket-permissions"]["source"],guild:discord.Guild,user:discord.User,option:api.ODTicketOption){ - //check ticket permissions - const permsRes = await opendiscord.actions.get("opendiscord:create-ticket-permissions").run(source,{guild,user,option}) - if (!permsRes.valid && instance.channel){ - //error - const newSource = (source === "slash" || source === "text") ? source : "other" - if (permsRes.reason == "blacklist") instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-no-permissions-blacklisted").build(newSource,{guild:instance.guild,channel:instance.channel,user:instance.user})) - else if (permsRes.reason == "cooldown") instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-no-permissions-cooldown").build(newSource,{guild:instance.guild,channel:instance.channel,user:instance.user,until:permsRes.cooldownUntil})) - else if (permsRes.reason == "global-limit") instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-no-permissions-limits").build(newSource,{guild:instance.guild,channel:instance.channel,user:instance.user,limit:"global"})) - else if (permsRes.reason == "global-user-limit") instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-no-permissions-limits").build(newSource,{guild:instance.guild,channel:instance.channel,user:instance.user,limit:"global-user"})) - else if (permsRes.reason == "option-limit") instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-no-permissions-limits").build(newSource,{guild:instance.guild,channel:instance.channel,user:instance.user,limit:"option"})) - else if (permsRes.reason == "option-user-limit") instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-no-permissions-limits").build(newSource,{guild:instance.guild,channel:instance.channel,user:instance.user,limit:"option-user"})) - else if (permsRes.reason == "custom") instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error").build(newSource,{guild:instance.guild,channel:instance.channel,user:instance.user,layout:"simple",error:permsRes.customReason ?? lang.getTranslation("errors.descriptions.unableToCreateTicket")+" `Unknown invalid_permission_reason => no reason specified by plugin`",customTitle:lang.getTranslation("errors.titles.permissionError")})) - else instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error").build(newSource,{guild:instance.guild,channel:instance.channel,user:instance.user,error:"Unknown invalid_permission reason => calculation failed #1",layout:"advanced"})) - return false - }else return true -} - -export const registerCommandResponders = async () => { +export async function registerCommandResponders(){ //TICKET COMMAND RESPONDER opendiscord.responders.commands.add(new api.ODCommandResponder("opendiscord:ticket",generalConfig.data.prefix,/^ticket/)) opendiscord.responders.commands.get("opendiscord:ticket").workers.add([ - new api.ODWorker("opendiscord:ticket",0,async (instance,params,source,cancel) => { + new api.ODWorker("opendiscord:ticket",0,async (instance,params,origin,cancel) => { const {user,member,channel,guild} = instance - - //check permissions - const permsResult = await opendiscord.permissions.checkCommandPerms(generalConfig.data.system.permissions.ticket,"support",user,member,channel,guild) - if (!permsResult.hasPerms){ - if (permsResult.reason == "not-in-server") await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-not-in-guild").build("button",{channel,user})) - else await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-no-permissions").build(source,{guild,channel,user,permissions:["support"]})) - return cancel() - } - - //check is in guild/server - if (!guild){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-not-in-guild").build(source,{channel:instance.channel,user:instance.user})) - return cancel() - } + + //responder checks + const hasPerms = await openticketUtils.replyHasPermissions(instance,origin,"ticket") + if (!hasPerms) return cancel() + + const isInGuild = await openticketUtils.replyIsInGuild(instance,origin) + if (!isInGuild || !guild || channel.isDMBased()) return cancel() //get option data const optionId = instance.options.getString("id",true) const option = opendiscord.options.get(optionId) if (!option || !(option instanceof api.ODTicketOption)){ - //error - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-option-unknown").build(source,{guild:instance.guild,channel:instance.channel,user:instance.user})) + instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-option-unknown").build(origin,{guild:instance.guild,channel:instance.channel,user:instance.user})) return cancel() } - - //start ticket creation - if (option.exists("opendiscord:questions") && option.get("opendiscord:questions").value.length > 0){ - //send modal - instance.modal(await opendiscord.builders.modals.getSafe("opendiscord:ticket-questions").build(source,{guild,channel,user,option})) - }else{ - //check ticket permissions - if (!(await checkTicketCreationPerms(instance,source,guild,user,option))) return cancel() - //create ticket - await instance.defer(true) - const res = await opendiscord.actions.get("opendiscord:create-ticket").run(source,{guild,user,answers:[],option}) - if (!res.channel || !res.ticket){ - //error - await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error").build(source,{guild,channel:instance.channel,user,error:"Unable to receive ticket or channel from callback! #1",layout:"advanced"})) + //don't allow createTicketForOtherUser to non-global-admins when enabled + const otherUser = (generalConfig.data.ticketSystem.enableCreateTicketForOtherUser) ? instance.options.getUser("user",false) : null + if (otherUser && generalConfig.data.ticketSystem.enableCreateTicketForOtherUser){ + if (!opendiscord.permissions.hasPermissions("support",await opendiscord.permissions.getPermissions(instance.user,instance.channel,instance.guild,{allowChannelRoleScope:false,allowChannelUserScope:false,allowGlobalRoleScope:true,allowGlobalUserScope:true}))){ + instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-no-permissions").build("button",{guild:instance.guild,channel:instance.channel,user:instance.user,permissions:["support"]})) return cancel() } - await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:ticket-created").build(source,{guild,channel:res.channel,user,ticket:res.ticket})) + } + if (otherUser) opendiscord.log(instance.user.displayName+" created a ticket for "+otherUser.displayName+" using the 'ticket' command!","warning",[ + {key:"commanduser",value:user.username}, + {key:"commanduserid",value:user.id,hidden:true}, + {key:"ticketuser",value:otherUser.username}, + {key:"ticketuserid",value:otherUser.id,hidden:true}, + {key:"channelid",value:instance.channel.id,hidden:true}, + {key:"method",value:origin} + ]) + + //start ticket creation + const ticketUser = otherUser ?? user + if (option.exists("opendiscord:questions") && option.get("opendiscord:questions").value.length > 0){ + //SEND MODAL + instance.modal(await opendiscord.components.modals.get("opendiscord:ticket-questions").build(origin,{guild,channel,user:ticketUser,option})) + }else{ + //check ticket permissions (modals need check after submit) + if (!(await openticketUtils.checkTicketCreationPerms(instance,origin,guild,ticketUser,option))) return cancel() + + //CREATE TICKET + await instance.defer(true) + const res = await opendiscord.actions.get("opendiscord:create-ticket").run(origin,{guild,user:ticketUser,answers:[],option}) + if (!res.channel || !res.ticket){ + //error + await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error").build(origin,{guild,channel:instance.channel,user:ticketUser,error:"Unable to receive ticket or channel from callback! #1",layout:"advanced"})) + return cancel() + } + await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:ticket-created").build(origin,{guild,channel:res.channel,user:ticketUser,ticket:res.ticket})) } }), - new api.ODWorker("opendiscord:logs",-1,(instance,params,source,cancel) => { + new api.ODWorker("opendiscord:logs",-1,(instance,params,origin,cancel) => { opendiscord.log(instance.user.displayName+" used the 'ticket' command!","info",[ {key:"user",value:instance.user.username}, {key:"userid",value:instance.user.id,hidden:true}, {key:"channelid",value:instance.channel.id,hidden:true}, - {key:"method",value:source} + {key:"method",value:origin} ]) }) ]) } -export const registerButtonResponders = async () => { +export async function registerButtonResponders(){ //TICKET OPTION BUTTON RESPONDER - opendiscord.responders.buttons.add(new api.ODButtonResponder("opendiscord:ticket-option",/^od:ticket-option_/)) + opendiscord.responders.buttons.add(new api.ODButtonResponder("opendiscord:ticket-option",/^od:ticket-option\|([^|]+)/)) opendiscord.responders.buttons.get("opendiscord:ticket-option").workers.add( - new api.ODWorker("opendiscord:ticket-option",0,async (instance,params,source,cancel) => { - const {guild,channel,user} = instance - if (!guild){ - //error - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-not-in-guild").build(source,{channel:instance.channel,user:instance.user})) + new api.ODWorker("opendiscord:ticket-option",0,async (instance,params,origin,cancel) => { + const {guild,channel,user,message} = instance + + const match = /^od:ticket-option\|([^|]+)/.exec(instance.interaction.customId) + if (!match) return cancel() + const optionId = match[1] + + //check message state + const state = await panelMsgState.getMsgState({channel,message}) + if (!state){ + await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error").build(origin,{guild,channel,user,error:lang.getTranslationWithParams("errors.descriptions.panelStateExpired",["/panel"]),layout:"simple",customTitle:"Message State Expired"})) return cancel() } + + //responder checks + const isInGuild = await openticketUtils.replyIsInGuild(instance,origin) + if (!isInGuild || !guild || channel.isDMBased()) return cancel() - //get option - const optionId = instance.interaction.customId.split("_")[2] + //get option data const option = opendiscord.options.get(optionId) if (!option || !(option instanceof api.ODTicketOption)){ - //error - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-option-unknown").build(source,{guild:instance.guild,channel:instance.channel,user:instance.user})) + instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-option-unknown").build(origin,{guild:instance.guild,channel:instance.channel,user:instance.user})) return cancel() } //start ticket creation if (option.exists("opendiscord:questions") && option.get("opendiscord:questions").value.length > 0){ - //send modal - instance.modal(await opendiscord.builders.modals.getSafe("opendiscord:ticket-questions").build("panel-button",{guild,channel,user,option})) + //SEND MODAL + instance.modal(await opendiscord.components.modals.get("opendiscord:ticket-questions").build("panel-button",{guild,channel,user,option})) }else{ - //check ticket permissions - if (!(await checkTicketCreationPerms(instance,"panel-button",guild,user,option))) return cancel() - - //create ticket - await instance.defer((generalConfig.data.system.replyOnTicketCreation) ? "reply" : "update",true) + //check ticket permissions (modals need check after submit) + if (!(await openticketUtils.checkTicketCreationPerms(instance,"panel-button",guild,user,option))) return cancel() + + //CREATE TICKET + await instance.defer((generalConfig.data.ticketSystem.replyOnTicketCreation) ? "reply" : "update",true) const res = await opendiscord.actions.get("opendiscord:create-ticket").run("panel-button",{guild,user,answers:[],option}) if (!res.channel || !res.ticket){ //error - await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error").build(source,{guild,channel:instance.channel,user,error:"Unable to receive ticket or channel from callback! #1",layout:"advanced"})) + await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error").build(origin,{guild,channel:instance.channel,user,error:"Unable to receive ticket or channel from callback! #1",layout:"advanced"})) return cancel() } - if (generalConfig.data.system.replyOnTicketCreation) await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:ticket-created").build("panel-button",{guild,channel:res.channel,user,ticket:res.ticket})) + if (generalConfig.data.ticketSystem.replyOnTicketCreation) await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:ticket-created").build("panel-button",{guild,channel:res.channel,user,ticket:res.ticket})) } }) ) } -export const registerDropdownResponders = async () => { - //PANEL DROPDOWN TICKETS DROPDOWN RESPONDER - opendiscord.responders.dropdowns.add(new api.ODDropdownResponder("opendiscord:panel-dropdown-tickets",/^od:panel-dropdown_/)) - opendiscord.responders.dropdowns.get("opendiscord:panel-dropdown-tickets").workers.add( - new api.ODWorker("opendiscord:panel-dropdown-tickets",0,async (instance,params,source,cancel) => { - const {guild,channel,user} = instance - if (!guild){ - //error - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-not-in-guild").build(source,{channel:instance.channel,user:instance.user})) - return cancel() - } - - //get option - const optionId = instance.values.getStringValues()[0].split("_")[2] - const option = opendiscord.options.get(optionId) - if (!option || !(option instanceof api.ODTicketOption)){ - //error - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-option-unknown").build(source,{guild:instance.guild,channel:instance.channel,user:instance.user})) - return cancel() - } - - //start ticket creation - if (option.exists("opendiscord:questions") && option.get("opendiscord:questions").value.length > 0){ - //send modal - instance.modal(await opendiscord.builders.modals.getSafe("opendiscord:ticket-questions").build("panel-dropdown",{guild,channel,user,option})) - }else{ - //check ticket permissions - if (!(await checkTicketCreationPerms(instance,"panel-dropdown",guild,user,option))) return cancel() - - //create ticket - await instance.defer((generalConfig.data.system.replyOnTicketCreation) ? "reply" : "update",true) - - const res = await opendiscord.actions.get("opendiscord:create-ticket").run("panel-dropdown",{guild,user,answers:[],option}) - if (!res.channel || !res.ticket){ - //error - await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error").build(source,{guild,channel:instance.channel,user,error:"Unable to receive ticket or channel from callback! #1",layout:"advanced"})) - return cancel() - } - if (generalConfig.data.system.replyOnTicketCreation) await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:ticket-created").build("panel-dropdown",{guild,channel:res.channel,user,ticket:res.ticket})) - } - - //update panel after dropdown usage (reset panel choice) - const globalDatabase = opendiscord.databases.get("opendiscord:global") - const rawPanelId = await globalDatabase.get("opendiscord:panel-message",instance.message.channel.id+"_"+instance.message.id) - if (rawPanelId){ - const panel = opendiscord.panels.get(rawPanelId) - if (panel) await instance.message.edit((await opendiscord.builders.messages.getSafe("opendiscord:panel").build("auto-update",{guild,channel,user,panel})).message) - } - }) - ) -} - -export const registerModalResponders = async () => { +export async function registerModalResponders(){ //TICKET QUESTIONS RESPONDER - opendiscord.responders.modals.add(new api.ODModalResponder("opendiscord:ticket-questions",/^od:ticket-questions_/)) + opendiscord.responders.modals.add(new api.ODModalResponder("opendiscord:ticket-questions",/^od:ticket-questions\|([^|]+)\|([^|]+)\|([^|]+)/)) opendiscord.responders.modals.get("opendiscord:ticket-questions").workers.add([ - new api.ODWorker("opendiscord:ticket-questions",0,async (instance,params,source,cancel) => { - const {guild,channel,user} = instance - await instance.defer((generalConfig.data.system.replyOnTicketCreation) ? "reply" : "update",true) - if (!channel) throw new api.ODSystemError("The 'Ticket Questions' modal requires a channel for responding!") - if (!guild){ - //error - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-not-in-guild").build(source,{channel,user:instance.user})) - return cancel() - } + new api.ODWorker("opendiscord:ticket-questions",0,async (instance,params,origin,cancel) => { + const {guild,channel} = instance - const originalSource = instance.interaction.customId.split("_")[2] as ("panel-button"|"panel-dropdown"|"slash"|"text"|"other") + const match = /^od:ticket-questions\|([^|]+)\|([^|]+)\|([^|]+)/.exec(instance.interaction.customId) + if (!match) return cancel() + const optionId = match[1] + const originalOrigin = match[2] as ("panel-button"|"panel-dropdown"|"slash"|"text"|"other") + const user = await opendiscord.client.fetchUser(match[3]) + if (!user) return cancel() + + //responder checks + const isInGuild = await openticketUtils.replyIsInGuild(instance,origin) + if (!isInGuild || !guild || !channel || channel.isDMBased()) return cancel() - //get option - const optionId = instance.interaction.customId.split("_")[1] + //get option data const option = opendiscord.options.get(optionId) if (!option || !(option instanceof api.ODTicketOption)){ - //error - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-option-unknown").build(source,{guild:instance.guild,channel,user:instance.user})) + instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-option-unknown").build(origin,{guild:instance.guild,channel,user:instance.user})) return cancel() } + //check ticket permissions (modals need check after submit) + if (!(await openticketUtils.checkTicketCreationPerms(instance,originalOrigin,guild,user,option))) return cancel() + //get answers - const answers: {id:string,name:string,type:"short"|"paragraph",value:string|null}[] = [] - option.get("opendiscord:questions").value.forEach((id) => { - const question = opendiscord.questions.get(id) - if (!question) return - if (question instanceof api.ODShortQuestion){ - answers.push({ - id, - name:question.exists("opendiscord:name") ? question.get("opendiscord:name")?.value : id, - type:"short", - value:instance.values.getTextField(id,false) - }) - }else if (question instanceof api.ODParagraphQuestion){ - answers.push({ - id, - name:question.exists("opendiscord:name") ? question.get("opendiscord:name")?.value : id, - type:"paragraph", - value:instance.values.getTextField(id,false) - }) - } - }) + const answers: api.ODQuestionAnswer[] = [] + for (const questionId of option.get("opendiscord:questions").value){ + const question = opendiscord.questions.get(questionId) + if (!question) continue + if (question instanceof api.ODShortQuestion) answers.push({ + id:questionId, + name:question.exists("opendiscord:name") ? question.get("opendiscord:name")?.value : questionId, + type:"short", + value:instance.values.getTextField(questionId,false) + }) + else if (question instanceof api.ODParagraphQuestion) answers.push({ + id:questionId, + name:question.exists("opendiscord:name") ? question.get("opendiscord:name")?.value : questionId, + type:"paragraph", + value:instance.values.getTextField(questionId,false) + }) + else if (question instanceof api.ODDropdownQuestion) answers.push({ + id:questionId, + name:question.exists("opendiscord:name") ? question.get("opendiscord:name")?.value : questionId, + type:"dropdown", + value:instance.values.getStringDropdownValues(questionId)[0] ?? null + }) + else if (question instanceof api.ODRadioSelectQuestion) answers.push({ + id:questionId, + name:question.exists("opendiscord:name") ? question.get("opendiscord:name")?.value : questionId, + type:"radio-select", + value:instance.values.getRadioGroup(questionId,false) + }) + else if (question instanceof api.ODCheckboxSelectQuestion) answers.push({ + id:questionId, + name:question.exists("opendiscord:name") ? question.get("opendiscord:name")?.value : questionId, + type:"checkbox-select", + value:(instance.values.getCheckboxGroup(questionId).length > 0) ? instance.values.getCheckboxGroup(questionId).map((opt) => "> "+opt).join("\n") : null + }) + else if (question instanceof api.ODFileUploadQuestion) answers.push({ + id:questionId, + name:question.exists("opendiscord:name") ? question.get("opendiscord:name")?.value : questionId, + type:"file-upload", + files:(instance.values.getUploadedFiles(questionId).length > 0) ? instance.values.getUploadedFiles(questionId).map(({id,url,name,title,description,contentType}) => ({id,url,name,title,description,contentType})) : [] + }) + } + + await instance.defer((generalConfig.data.ticketSystem.replyOnTicketCreation) ? "reply" : "update",true) - //check ticket permissions - if (!(await checkTicketCreationPerms(instance,originalSource,guild,user,option))) return cancel() - - //create ticket - const res = await opendiscord.actions.get("opendiscord:create-ticket").run(originalSource,{guild,user,answers,option}) + //CREATE TICKET + const res = await opendiscord.actions.get("opendiscord:create-ticket").run(originalOrigin,{guild,user,answers,option}) if (!res.channel || !res.ticket){ //error - await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error").build(source,{guild,channel,user,error:"Unable to receive ticket or channel from callback! #2",layout:"advanced"})) + await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error").build(origin,{guild,channel,user,error:"Unable to receive ticket or channel from callback! #2",layout:"advanced"})) return cancel() } - if (generalConfig.data.system.replyOnTicketCreation) await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:ticket-created").build(originalSource,{guild,channel:res.channel,user,ticket:res.ticket})) + if (generalConfig.data.ticketSystem.replyOnTicketCreation) await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:ticket-created").build(originalOrigin,{guild,channel:res.channel,user,ticket:res.ticket})) }) ]) } \ No newline at end of file diff --git a/src/commands/topic.ts b/src/commands/topic.ts index 79992c2..b2b15ad 100644 --- a/src/commands/topic.ts +++ b/src/commands/topic.ts @@ -1,44 +1,29 @@ /////////////////////////////////////// //TOPIC COMMAND /////////////////////////////////////// -import {opendiscord, api, utilities} from "../index" +import {opendiscord, api, utilities, openticketUtils} from "../index.js" import * as discord from "discord.js" const generalConfig = opendiscord.configs.get("opendiscord:general") -export const registerCommandResponders = async () => { +export async function registerCommandResponders(){ //TOPIC COMMAND RESPONDER opendiscord.responders.commands.add(new api.ODCommandResponder("opendiscord:topic",generalConfig.data.prefix,"topic")) opendiscord.responders.commands.get("opendiscord:topic").workers.add([ - new api.ODWorker("opendiscord:topic",0,async (instance,params,source,cancel) => { + new api.ODWorker("opendiscord:topic",0,async (instance,params,origin,cancel) => { const {user,member,channel,guild} = instance - - //check permissions - const permsResult = await opendiscord.permissions.checkCommandPerms(generalConfig.data.system.permissions.topic,"support",user,member,channel,guild) - if (!permsResult.hasPerms){ - if (permsResult.reason == "not-in-server") await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-not-in-guild").build("button",{channel,user})) - else await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-no-permissions").build(source,{guild,channel,user,permissions:["support"]})) - return cancel() - } - - //check is in guild/Server - if (!guild){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-not-in-guild").build("button",{channel,user})) - return cancel() - } - - //check if ticket exists - const ticket = opendiscord.tickets.get(channel.id) - if (!ticket || channel.isDMBased()){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-ticket-unknown").build("button",{guild,channel,user})) - return cancel() - } - - //return when busy - if (ticket.get("opendiscord:busy").value){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-ticket-busy").build("button",{guild,channel,user})) - return cancel() - } + //responder checks + const hasPerms = await openticketUtils.replyHasPermissions(instance,origin,"topic") + if (!hasPerms) return cancel() + + const isInGuild = await openticketUtils.replyIsInGuild(instance,origin) + if (!isInGuild || !guild || channel.isDMBased()) return cancel() + + const ticket = await openticketUtils.replyIsTicket(instance,origin) + if (!ticket) return cancel() + + const isAvailable = await openticketUtils.replyTicketIsAvailable(instance,origin,ticket) + if (!isAvailable) return cancel() //subcommands const scope = instance.options.getSubCommand() @@ -48,16 +33,16 @@ export const registerCommandResponders = async () => { const topic = instance.options.getString("topic",true) //start changing ticket topic await instance.defer(false) - await opendiscord.actions.get("opendiscord:update-ticket-topic").run(source,{guild,channel,user,ticket,newTopic:topic,sendMessage:false}) - await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:topic-set").build(source,{guild,channel,user,ticket,topic})) + await opendiscord.actions.get("opendiscord:update-ticket-topic").run(origin,{guild,channel,user,ticket,newTopic:topic,sendMessage:false}) + await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:topic-set").build(origin,{guild,channel,user,ticket,topic})) } }), - new api.ODWorker("opendiscord:logs",-1,(instance,params,source,cancel) => { + new api.ODWorker("opendiscord:logs",-1,(instance,params,origin,cancel) => { opendiscord.log(instance.user.displayName+" used the 'topic set' command!","info",[ {key:"user",value:instance.user.username}, {key:"userid",value:instance.user.id,hidden:true}, {key:"channelid",value:instance.channel.id,hidden:true}, - {key:"method",value:source} + {key:"method",value:origin} ]) }) ]) diff --git a/src/commands/transcripts.ts b/src/commands/transcripts.ts new file mode 100644 index 0000000..23bc821 --- /dev/null +++ b/src/commands/transcripts.ts @@ -0,0 +1,44 @@ +/////////////////////////////////////// +//TRANSCRIPTS COMMAND +/////////////////////////////////////// +import {opendiscord, api, utilities, openticketUtils} from "../index.js" +import * as discord from "discord.js" + +const generalConfig = opendiscord.configs.get("opendiscord:general") +const transcriptsDatabase = opendiscord.databases.get("opendiscord:transcripts") + +export async function registerCommandResponders(){ + //TRANSCRIPTS COMMAND RESPONDER + opendiscord.responders.commands.add(new api.ODCommandResponder("opendiscord:transcripts",generalConfig.data.prefix,"transcripts")) + opendiscord.responders.commands.get("opendiscord:transcripts").workers.add([ + new api.ODWorker("opendiscord:transcripts",0,async (instance,params,origin,cancel) => { + const {guild,channel,user,member} = instance + + //responder checks + const hasPerms = await openticketUtils.replyHasPermissions(instance,origin,"transcripts") + if (!hasPerms) return cancel() + + const isInGuild = await openticketUtils.replyIsInGuild(instance,origin) + if (!isInGuild || !guild || channel.isDMBased()) return cancel() + + //fetch data + const transcriptUser = instance.options.getUser("user",true) + + const transcriptList = (await transcriptsDatabase.getCategory("opendiscord:transcript") ?? []) + .filter((t) => t.value.ticketCreatorId === transcriptUser.id) + .map((t) => t.value) + .sort((a,b) => (b.ticketDeletedDate ?? 0)-(a.ticketDeletedDate ?? 0)) + .slice(0,20) + + await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:transcript-history").build(origin,{guild,channel,user,transcriptUser,transcriptList})) + }), + new api.ODWorker("opendiscord:logs",-1,(instance,params,origin,cancel) => { + opendiscord.log(instance.user.displayName+" used the 'transcripts' command!","info",[ + {key:"user",value:instance.user.username}, + {key:"userid",value:instance.user.id,hidden:true}, + {key:"channelid",value:instance.channel.id,hidden:true}, + {key:"method",value:origin} + ]) + }) + ]) +} \ No newline at end of file diff --git a/src/commands/transfer.ts b/src/commands/transfer.ts index 5aa2298..7857ef8 100644 --- a/src/commands/transfer.ts +++ b/src/commands/transfer.ts @@ -1,60 +1,47 @@ /////////////////////////////////////// //TRANSFER COMMAND /////////////////////////////////////// -import {opendiscord, api, utilities} from "../index" +import {opendiscord, api, utilities, openticketUtils} from "../index.js" import * as discord from "discord.js" const generalConfig = opendiscord.configs.get("opendiscord:general") -export const registerCommandResponders = async () => { +export async function registerCommandResponders(){ //TRANSFER COMMAND RESPONDER opendiscord.responders.commands.add(new api.ODCommandResponder("opendiscord:transfer",generalConfig.data.prefix,"transfer")) opendiscord.responders.commands.get("opendiscord:transfer").workers.add([ - new api.ODWorker("opendiscord:transfer",0,async (instance,params,source,cancel) => { + new api.ODWorker("opendiscord:transfer",0,async (instance,params,origin,cancel) => { const {user,member,channel,guild} = instance - - //check permissions - const permsResult = await opendiscord.permissions.checkCommandPerms(generalConfig.data.system.permissions.transfer,"support",user,member,channel,guild) - if (!permsResult.hasPerms){ - if (permsResult.reason == "not-in-server") await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-not-in-guild").build("button",{channel,user})) - else await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-no-permissions").build(source,{guild,channel,user,permissions:["support"]})) - return cancel() - } - - //check is in guild/Server - if (!guild){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-not-in-guild").build("button",{channel,user})) - return cancel() - } - - //check if ticket exists - const ticket = opendiscord.tickets.get(channel.id) - if (!ticket || channel.isDMBased()){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-ticket-unknown").build("button",{guild,channel,user})) - return cancel() - } - - //return when busy - if (ticket.get("opendiscord:busy").value){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-ticket-busy").build("button",{guild,channel,user})) - return cancel() - } + + //responder checks + const hasPerms = await openticketUtils.replyHasPermissions(instance,origin,"transfer") + if (!hasPerms) return cancel() + + const isInGuild = await openticketUtils.replyIsInGuild(instance,origin) + if (!isInGuild || !guild || channel.isDMBased()) return cancel() + + const ticket = await openticketUtils.replyIsTicket(instance,origin) + if (!ticket) return cancel() + + const isAvailable = await openticketUtils.replyTicketIsAvailable(instance,origin,ticket) + if (!isAvailable) return cancel() + //fetch data const oldCreator = await opendiscord.tickets.getTicketUser(ticket,"creator") ?? opendiscord.client.client.user const newCreator = instance.options.getUser("user",true) const reason = instance.options.getString("reason",false) //start transferring ticket ownership await instance.defer(false) - await opendiscord.actions.get("opendiscord:transfer-ticket").run(source,{guild,channel,user,ticket,reason,sendMessage:false,newCreator}) - await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:transfer-message").build(source,{guild,channel,user,ticket,oldCreator,newCreator,reason})) + await opendiscord.actions.get("opendiscord:transfer-ticket").run(origin,{guild,channel,user,ticket,reason,sendMessage:false,newCreator}) + await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:transfer-message").build(origin,{guild,channel,user,ticket,oldCreator,newCreator,reason})) }), - new api.ODWorker("opendiscord:logs",-1,(instance,params,source,cancel) => { + new api.ODWorker("opendiscord:logs",-1,(instance,params,origin,cancel) => { opendiscord.log(instance.user.displayName+" used the 'transfer' command!","info",[ {key:"user",value:instance.user.username}, {key:"userid",value:instance.user.id,hidden:true}, {key:"channelid",value:instance.channel.id,hidden:true}, - {key:"method",value:source} + {key:"method",value:origin} ]) }) ]) diff --git a/src/commands/unclaim.ts b/src/commands/unclaim.ts index 73cb608..ba48bba 100644 --- a/src/commands/unclaim.ts +++ b/src/commands/unclaim.ts @@ -1,116 +1,233 @@ /////////////////////////////////////// //UNCLAIM COMMAND /////////////////////////////////////// -import {opendiscord, api, utilities} from "../index" +import {opendiscord, api, utilities, openticketUtils} from "../index.js" import * as discord from "discord.js" const generalConfig = opendiscord.configs.get("opendiscord:general") +const lang = opendiscord.languages +const interactiveMsgState = opendiscord.states.get("opendiscord:interactive-message") -export const registerCommandResponders = async () => { +export async function registerCommandResponders(){ //UNCLAIM COMMAND RESPONDER opendiscord.responders.commands.add(new api.ODCommandResponder("opendiscord:unclaim",generalConfig.data.prefix,"unclaim")) opendiscord.responders.commands.get("opendiscord:unclaim").workers.add([ - new api.ODWorker("opendiscord:unclaim",0,async (instance,params,source,cancel) => { + new api.ODWorker("opendiscord:unclaim",0,async (instance,params,origin,cancel) => { const {user,member,channel,guild} = instance - //check permissions - const permsResult = await opendiscord.permissions.checkCommandPerms(generalConfig.data.system.permissions.unclaim,"support",user,member,channel,guild) - if (!permsResult.hasPerms){ - if (permsResult.reason == "not-in-server") await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-not-in-guild").build("button",{channel,user})) - else await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-no-permissions").build(source,{guild,channel,user,permissions:["support"]})) - return cancel() - } - - //check is in guild/Server - if (!guild){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-not-in-guild").build("button",{channel,user})) - return cancel() - } - - //check if ticket exists - const ticket = opendiscord.tickets.get(channel.id) - if (!ticket || channel.isDMBased()){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-ticket-unknown").build("button",{guild,channel,user})) - return cancel() - } - - //return when not claimed - if (!ticket.get("opendiscord:claimed").value){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error").build("button",{guild,channel,user,error:opendiscord.languages.getTranslation("errors.actionInvalid.unclaim"),layout:"simple"})) - return cancel() - } - - //return when busy - if (ticket.get("opendiscord:busy").value){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-ticket-busy").build("button",{guild,channel,user})) - return cancel() - } - - const reason = instance.options.getString("reason",false) - + //responder checks + const hasPerms = await openticketUtils.replyHasPermissions(instance,origin,"unclaim") + if (!hasPerms) return cancel() + + const isInGuild = await openticketUtils.replyIsInGuild(instance,origin) + if (!isInGuild || !guild || channel.isDMBased()) return cancel() + + const ticket = await openticketUtils.replyIsTicket(instance,origin) + if (!ticket) return cancel() + + const isAvailable = await openticketUtils.replyTicketIsAvailable(instance,origin,ticket) + if (!isAvailable) return cancel() + + const isTicketClaimed = await openticketUtils.replyTicketMustBeClaimed(instance,origin,ticket) + if (!isTicketClaimed) return cancel() + //start unclaiming ticket await instance.defer(false) - await opendiscord.actions.get("opendiscord:unclaim-ticket").run(source,{guild,channel,user,ticket,reason,sendMessage:false}) - await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:unclaim-message").build(source,{guild,channel,user,ticket,reason})) + const reason = instance.options.getString("reason",false) + await opendiscord.actions.get("opendiscord:unclaim-ticket").run(origin,{guild,channel,user,ticket,reason,sendMessage:false}) + + //send message & set state + const sentMsg = await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:unclaim-message").build(origin,{guild,channel,user,ticket,reason})) + if (sentMsg.success) await interactiveMsgState.setMsgState({channel,message:sentMsg.message},{ + messageType:"unclaim-message", + messageOrigin:origin, + messageAuthor:user.id, + messageReason:reason + },sentMsg.ephemeral) }), - new api.ODWorker("opendiscord:logs",-1,(instance,params,source,cancel) => { + new api.ODWorker("opendiscord:logs",-1,(instance,params,origin,cancel) => { opendiscord.log(instance.user.displayName+" used the 'unclaim' command!","info",[ {key:"user",value:instance.user.username}, {key:"userid",value:instance.user.id,hidden:true}, {key:"channelid",value:instance.channel.id,hidden:true}, - {key:"method",value:source} + {key:"method",value:origin} ]) }) ]) } -export const registerButtonResponders = async () => { +export async function registerButtonResponders(){ //UNCLAIM TICKET BUTTON RESPONDER opendiscord.responders.buttons.add(new api.ODButtonResponder("opendiscord:unclaim-ticket",/^od:unclaim-ticket/)) opendiscord.responders.buttons.get("opendiscord:unclaim-ticket").workers.add( - new api.ODWorker("opendiscord:unclaim-ticket",0,async (instance,params,source,cancel) => { - const originalSource = instance.interaction.customId.split("_")[1] as Exclude + new api.ODWorker("opendiscord:unclaim-ticket",0,async (instance,params,origin,cancel) => { + const {guild,channel,user,message} = instance - if (originalSource == "ticket-message") await opendiscord.verifybars.get("opendiscord:unclaim-ticket-ticket-message").activate(instance) - else if (originalSource == "claim-message") await opendiscord.verifybars.get("opendiscord:unclaim-ticket-claim-message").activate(instance) - else await instance.defer("update",false) + //responder checks + const hasPerms = await openticketUtils.replyHasPermissions(instance,origin,"unclaim") + if (!hasPerms) return cancel() + + const isInGuild = await openticketUtils.replyIsInGuild(instance,origin) + if (!isInGuild || !guild || channel.isDMBased()) return cancel() + + const state = await openticketUtils.replyInteractiveMessageState(instance,origin,channel,message,"/unclaim") + if (!state) return cancel() + + const ticket = await openticketUtils.replyIsTicket(instance,origin) + if (!ticket) return cancel() + + const isAvailable = await openticketUtils.replyTicketIsAvailable(instance,origin,ticket) + if (!isAvailable) return cancel() + + const isTicketClaimed = await openticketUtils.replyTicketMustBeClaimed(instance,origin,ticket) + if (!isTicketClaimed) return cancel() + + //fetch state details + const verifybar = opendiscord.verifybars.get("opendiscord:unclaim-ticket") + const originalUser = ((state.data.messageAuthor) ? await opendiscord.client.fetchUser(state.data.messageAuthor) : user) ?? user + const originalReason = state.data.messageReason ?? null + const originalMsgType = state.data.messageType + + //send verifybar + if (generalConfig.data.ticketSystem.disableVerifyBars){ + //verifybar disabled, directly run response + await verifybar.activate(instance,"accept") + + }else if (originalMsgType == "ticket-message"){ + //ticket message verifybar + const modifiedMsg = opendiscord.components.modifiers.get("opendiscord:unclaim-ticket-verifybar").modify(opendiscord.builders.messages.getSafe("opendiscord:ticket-message"),originalMsgType,{guild,channel,user,verifybar}) + await instance.update(await modifiedMsg.build("verifybar",{guild,channel,user,ticket})) + }else if (originalMsgType == "claim-message"){ + //claim message verifybar + const modifiedMsg = opendiscord.components.modifiers.get("opendiscord:unclaim-ticket-verifybar").modify(opendiscord.builders.messages.getSafe("opendiscord:claim-message"),originalMsgType,{guild,channel,user,verifybar}) + await instance.update(await modifiedMsg.build("verifybar",{guild,channel,user:originalUser,ticket,reason:originalReason})) + } }) ) } -export const registerModalResponders = async () => { +export async function registerVerifyBars(){ + //UNCLAIM TICKET + opendiscord.verifybars.add(new api.ODVerifyBar("opendiscord:unclaim-ticket")) + opendiscord.verifybars.get("opendiscord:unclaim-ticket").workers.add([ + new api.ODWorker("opendiscord:unclaim-ticket",0,async (instance,params,origin,cancel) => { + const {user,member,channel,guild,message} = instance + + //responder checks + const hasPerms = await openticketUtils.replyHasPermissions(instance,origin,"unclaim") + if (!hasPerms) return cancel() + + const isInGuild = await openticketUtils.replyIsInGuild(instance,origin) + if (!isInGuild || !guild || channel.isDMBased()) return cancel() + + const state = await openticketUtils.replyInteractiveMessageState(instance,origin,channel,message,"/unclaim") + if (!state) return cancel() + + const ticket = await openticketUtils.replyIsTicket(instance,origin) + if (!ticket) return cancel() + + const isAvailable = await openticketUtils.replyTicketIsAvailable(instance,origin,ticket) + if (!isAvailable) return cancel() + + const isTicketClaimed = await openticketUtils.replyTicketMustBeClaimed(instance,origin,ticket) + if (!isTicketClaimed) return cancel() + + //fetch state details + const originalUser = ((state.data.messageAuthor) ? await opendiscord.client.fetchUser(state.data.messageAuthor) : user) ?? user + const originalReason = state.data.messageReason ?? null + const originalMsgType = state.data.messageType + + //start unclaiming ticket + if (params.selectedButtonId == "cancel"){ + //CANCEL + if (originalMsgType == "ticket-message"){ + //ticket message + await instance.update(await opendiscord.builders.messages.getSafe("opendiscord:ticket-message").build("verifybar",{guild,channel,user,ticket})) + }else if (originalMsgType == "claim-message"){ + //claim message + await instance.update(await opendiscord.builders.messages.getSafe("opendiscord:claim-message").build("verifybar",{guild,channel,user:originalUser,ticket,reason:originalReason})) + } + }else if (params.selectedButtonId == "accept"){ + //UNCLAIM TICKET + await instance.defer("update",false) + + if (originalMsgType == "ticket-message"){ + //ticket message + await opendiscord.actions.get("opendiscord:unclaim-ticket").run(originalMsgType,{guild,channel,user,ticket,reason:null,sendMessage:true}) + await instance.update(await opendiscord.builders.messages.getSafe("opendiscord:ticket-message").build("verifybar",{guild,channel,user,ticket})) + }else if (originalMsgType == "claim-message"){ + //converted to unclaim message + await opendiscord.actions.get("opendiscord:unclaim-ticket").run(originalMsgType,{guild,channel,user,ticket,reason:null,sendMessage:false}) + const sentMsg = await instance.update(await opendiscord.builders.messages.getSafe("opendiscord:unclaim-message").build("verifybar",{guild,channel,user,ticket,reason:null})) + if (sentMsg.success) await interactiveMsgState.setMsgState({channel,message:sentMsg.message},{ + messageType:"unclaim-message", + messageOrigin:origin, + messageAuthor:user.id, + messageReason:null + },sentMsg.ephemeral) + } + }else if (params.selectedButtonId == "accept-with-reason"){ + //UNCLAIM WITH REASON (MODAL) + instance.modal(await opendiscord.components.modals.get("opendiscord:unclaim-ticket-reason").build("other",{guild,channel,user,ticket,message})) + } + }) + ]) +} + +export async function registerModalResponders(){ //UNCLAIM WITH REASON MODAL RESPONDER - opendiscord.responders.modals.add(new api.ODModalResponder("opendiscord:unclaim-ticket-reason",/^od:unclaim-ticket-reason_/)) + opendiscord.responders.modals.add(new api.ODModalResponder("opendiscord:unclaim-ticket-reason",/^od:unclaim-ticket-reason\|([^|]+)\|([^|]+)/)) opendiscord.responders.modals.get("opendiscord:unclaim-ticket-reason").workers.add([ - new api.ODWorker("opendiscord:unclaim-ticket-reason",0,async (instance,params,source,cancel) => { - const {guild,channel,user} = instance - if (!channel) return - if (!guild){ - //error - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-not-in-guild").build(source,{channel,user:instance.user})) - return cancel() - } - const ticket = opendiscord.tickets.get(instance.interaction.customId.split("_")[1]) - if (!ticket || channel.isDMBased()){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-ticket-unknown").build("button",{guild,channel,user})) - return - } + new api.ODWorker("opendiscord:unclaim-ticket-reason",0,async (instance,params,origin,cancel) => { + const {guild,user} = instance + + const match = /^od:unclaim-ticket-reason\|([^|]+)\|([^|]+)/.exec(instance.interaction.customId) + if (!match) return cancel() + const channel = await opendiscord.client.fetchTextChannel(match[1]) + const message = await opendiscord.client.fetchChannelMessage(match[1],match[2]) + + //responder checks + const hasPerms = await openticketUtils.replyHasPermissions(instance,origin,"unclaim") + if (!hasPerms) return cancel() + + const isInGuild = await openticketUtils.replyIsInGuild(instance,origin) + if (!isInGuild || !guild || !channel || channel.isDMBased()) return cancel() - const originalSource = instance.interaction.customId.split("_")[2] as Exclude + const state = await openticketUtils.replyInteractiveMessageState(instance,origin,channel,message,"/unclaim") + if (!state) return cancel() + + const ticket = await openticketUtils.replyIsTicket(instance,origin) + if (!ticket) return cancel() + + const isAvailable = await openticketUtils.replyTicketIsAvailable(instance,origin,ticket) + if (!isAvailable) return cancel() + + const isTicketClaimed = await openticketUtils.replyTicketMustBeClaimed(instance,origin,ticket) + if (!isTicketClaimed) return cancel() + + //fetch state details const reason = instance.values.getTextField("reason",true) + const originalMsgOrigin = state.data.messageOrigin + const originalMsgType = state.data.messageType - //unclaim with reason - if (originalSource == "ticket-message"){ - await instance.defer("update",false) - await opendiscord.actions.get("opendiscord:unclaim-ticket").run(originalSource,{guild,channel,user,ticket,reason,sendMessage:true}) + //start claiming ticket + await instance.defer("update",false) + + if (originalMsgType == "ticket-message"){ + //ticket message + await opendiscord.actions.get("opendiscord:unclaim-ticket").run(originalMsgType,{guild,channel,user,ticket,reason:null,sendMessage:true}) await instance.update(await opendiscord.builders.messages.getSafe("opendiscord:ticket-message").build("other",{guild,channel,user,ticket})) - }else if (originalSource == "claim-message"){ - await instance.defer("update",false) - await opendiscord.actions.get("opendiscord:unclaim-ticket").run(originalSource,{guild,channel,user,ticket,reason,sendMessage:false}) - await instance.update(await opendiscord.builders.messages.getSafe("opendiscord:unclaim-message").build("other",{guild,channel,user,ticket,reason})) - }else{ - await instance.defer("update",false) - await opendiscord.actions.get("opendiscord:unclaim-ticket").run(originalSource,{guild,channel,user,ticket,reason,sendMessage:true}) + + }else if (originalMsgType == "claim-message"){ + //converted to unclaim message + await opendiscord.actions.get("opendiscord:unclaim-ticket").run(originalMsgType,{guild,channel,user,ticket,reason:null,sendMessage:false}) + const sentMsg = await instance.update(await opendiscord.builders.messages.getSafe("opendiscord:unclaim-message").build(originalMsgType,{guild,channel,user,ticket,reason})) + if (sentMsg.success) await interactiveMsgState.setMsgState({channel,message:sentMsg.message},{ + messageType:"unclaim-message", + messageOrigin:originalMsgOrigin, + messageAuthor:user.id, + messageReason:reason + },sentMsg.ephemeral) } }) ]) diff --git a/src/commands/unpin.ts b/src/commands/unpin.ts index 30a9579..627e156 100644 --- a/src/commands/unpin.ts +++ b/src/commands/unpin.ts @@ -1,116 +1,233 @@ /////////////////////////////////////// //UNPIN COMMAND /////////////////////////////////////// -import {opendiscord, api, utilities} from "../index" +import {opendiscord, api, utilities, openticketUtils} from "../index.js" import * as discord from "discord.js" const generalConfig = opendiscord.configs.get("opendiscord:general") +const lang = opendiscord.languages +const interactiveMsgState = opendiscord.states.get("opendiscord:interactive-message") -export const registerCommandResponders = async () => { +export async function registerCommandResponders(){ //UNPIN COMMAND RESPONDER opendiscord.responders.commands.add(new api.ODCommandResponder("opendiscord:unpin",generalConfig.data.prefix,"unpin")) opendiscord.responders.commands.get("opendiscord:unpin").workers.add([ - new api.ODWorker("opendiscord:unpin",0,async (instance,params,source,cancel) => { + new api.ODWorker("opendiscord:unpin",0,async (instance,params,origin,cancel) => { const {user,member,channel,guild} = instance - - //check permissions - const permsResult = await opendiscord.permissions.checkCommandPerms(generalConfig.data.system.permissions.unpin,"support",user,member,channel,guild) - if (!permsResult.hasPerms){ - if (permsResult.reason == "not-in-server") await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-not-in-guild").build("button",{channel,user})) - else await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-no-permissions").build(source,{guild,channel,user,permissions:["support"]})) - return cancel() - } - - //check is in guild/Server - if (!guild){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-not-in-guild").build("button",{channel,user})) - return cancel() - } - - //check if ticket exists - const ticket = opendiscord.tickets.get(channel.id) - if (!ticket || channel.isDMBased()){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-ticket-unknown").build("button",{guild,channel,user})) - return cancel() - } - - //return when not pinned yet - if (!ticket.get("opendiscord:pinned").value){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error").build("button",{guild,channel,user,error:opendiscord.languages.getTranslation("errors.actionInvalid.unpin"),layout:"simple"})) - return cancel() - } - //return when busy - if (ticket.get("opendiscord:busy").value){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-ticket-busy").build("button",{guild,channel,user})) - return cancel() - } - - const reason = instance.options.getString("reason",false) - - //start unpinning ticket + //responder checks + const hasPerms = await openticketUtils.replyHasPermissions(instance,origin,"unpin") + if (!hasPerms) return cancel() + + const isInGuild = await openticketUtils.replyIsInGuild(instance,origin) + if (!isInGuild || !guild || channel.isDMBased()) return cancel() + + const ticket = await openticketUtils.replyIsTicket(instance,origin) + if (!ticket) return cancel() + + const isAvailable = await openticketUtils.replyTicketIsAvailable(instance,origin,ticket) + if (!isAvailable) return cancel() + + const isTicketPinned = await openticketUtils.replyTicketMustBePinned(instance,origin,ticket) + if (!isTicketPinned) return cancel() + + //start unpining ticket await instance.defer(false) - await opendiscord.actions.get("opendiscord:unpin-ticket").run(source,{guild,channel,user,ticket,reason,sendMessage:false}) - await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:unpin-message").build(source,{guild,channel,user,ticket,reason})) + const reason = instance.options.getString("reason",false) + await opendiscord.actions.get("opendiscord:unpin-ticket").run(origin,{guild,channel,user,ticket,reason,sendMessage:false}) + + //send message & set state + const sentMsg = await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:unpin-message").build(origin,{guild,channel,user,ticket,reason})) + if (sentMsg.success) await interactiveMsgState.setMsgState({channel,message:sentMsg.message},{ + messageType:"unpin-message", + messageOrigin:origin, + messageAuthor:user.id, + messageReason:reason + },sentMsg.ephemeral) }), - new api.ODWorker("opendiscord:logs",-1,(instance,params,source,cancel) => { + new api.ODWorker("opendiscord:logs",-1,(instance,params,origin,cancel) => { opendiscord.log(instance.user.displayName+" used the 'unpin' command!","info",[ {key:"user",value:instance.user.username}, {key:"userid",value:instance.user.id,hidden:true}, {key:"channelid",value:instance.channel.id,hidden:true}, - {key:"method",value:source} + {key:"method",value:origin} ]) }) ]) } -export const registerButtonResponders = async () => { +export async function registerButtonResponders(){ //UNPIN TICKET BUTTON RESPONDER opendiscord.responders.buttons.add(new api.ODButtonResponder("opendiscord:unpin-ticket",/^od:unpin-ticket/)) opendiscord.responders.buttons.get("opendiscord:unpin-ticket").workers.add( - new api.ODWorker("opendiscord:unpin-ticket",0,async (instance,params,source,cancel) => { - const originalSource = instance.interaction.customId.split("_")[1] as Exclude + new api.ODWorker("opendiscord:unpin-ticket",0,async (instance,params,origin,cancel) => { + const {guild,channel,user,message} = instance + + //responder checks + const hasPerms = await openticketUtils.replyHasPermissions(instance,origin,"unpin") + if (!hasPerms) return cancel() - if (originalSource == "ticket-message") await opendiscord.verifybars.get("opendiscord:unpin-ticket-ticket-message").activate(instance) - else if (originalSource == "pin-message") await opendiscord.verifybars.get("opendiscord:unpin-ticket-pin-message").activate(instance) - else await instance.defer("update",false) + const isInGuild = await openticketUtils.replyIsInGuild(instance,origin) + if (!isInGuild || !guild || channel.isDMBased()) return cancel() + + const state = await openticketUtils.replyInteractiveMessageState(instance,origin,channel,message,"/unpin") + if (!state) return cancel() + + const ticket = await openticketUtils.replyIsTicket(instance,origin) + if (!ticket) return cancel() + + const isAvailable = await openticketUtils.replyTicketIsAvailable(instance,origin,ticket) + if (!isAvailable) return cancel() + + const isTicketPinned = await openticketUtils.replyTicketMustBePinned(instance,origin,ticket) + if (!isTicketPinned) return cancel() + + //fetch state details + const verifybar = opendiscord.verifybars.get("opendiscord:unpin-ticket") + const originalUser = ((state.data.messageAuthor) ? await opendiscord.client.fetchUser(state.data.messageAuthor) : user) ?? user + const originalReason = state.data.messageReason ?? null + const originalMsgType = state.data.messageType + + //send verifybar + if (generalConfig.data.ticketSystem.disableVerifyBars){ + //verifybar disabled, directly run response + await verifybar.activate(instance,"accept") + + }else if (originalMsgType == "ticket-message"){ + //ticket message verifybar + const modifiedMsg = opendiscord.components.modifiers.get("opendiscord:unpin-ticket-verifybar").modify(opendiscord.builders.messages.getSafe("opendiscord:ticket-message"),originalMsgType,{guild,channel,user,verifybar}) + await instance.update(await modifiedMsg.build("verifybar",{guild,channel,user,ticket})) + }else if (originalMsgType == "pin-message"){ + //pin message verifybar + const modifiedMsg = opendiscord.components.modifiers.get("opendiscord:unpin-ticket-verifybar").modify(opendiscord.builders.messages.getSafe("opendiscord:pin-message"),originalMsgType,{guild,channel,user,verifybar}) + await instance.update(await modifiedMsg.build("verifybar",{guild,channel,user:originalUser,ticket,reason:originalReason})) + } }) ) } -export const registerModalResponders = async () => { +export async function registerVerifyBars(){ + //UNPIN TICKET + opendiscord.verifybars.add(new api.ODVerifyBar("opendiscord:unpin-ticket")) + opendiscord.verifybars.get("opendiscord:unpin-ticket").workers.add([ + new api.ODWorker("opendiscord:unpin-ticket",0,async (instance,params,origin,cancel) => { + const {user,member,channel,guild,message} = instance + + //responder checks + const hasPerms = await openticketUtils.replyHasPermissions(instance,origin,"unpin") + if (!hasPerms) return cancel() + + const isInGuild = await openticketUtils.replyIsInGuild(instance,origin) + if (!isInGuild || !guild || channel.isDMBased()) return cancel() + + const state = await openticketUtils.replyInteractiveMessageState(instance,origin,channel,message,"/unpin") + if (!state) return cancel() + + const ticket = await openticketUtils.replyIsTicket(instance,origin) + if (!ticket) return cancel() + + const isAvailable = await openticketUtils.replyTicketIsAvailable(instance,origin,ticket) + if (!isAvailable) return cancel() + + const isTicketPinned = await openticketUtils.replyTicketMustBePinned(instance,origin,ticket) + if (!isTicketPinned) return cancel() + + //fetch state details + const originalUser = ((state.data.messageAuthor) ? await opendiscord.client.fetchUser(state.data.messageAuthor) : user) ?? user + const originalReason = state.data.messageReason ?? null + const originalMsgType = state.data.messageType + + //start unpinning ticket + if (params.selectedButtonId == "cancel"){ + //CANCEL + if (originalMsgType == "ticket-message"){ + //ticket message + await instance.update(await opendiscord.builders.messages.getSafe("opendiscord:ticket-message").build("verifybar",{guild,channel,user,ticket})) + }else if (originalMsgType == "pin-message"){ + //pin message + await instance.update(await opendiscord.builders.messages.getSafe("opendiscord:pin-message").build("verifybar",{guild,channel,user:originalUser,ticket,reason:originalReason})) + } + }else if (params.selectedButtonId == "accept"){ + //UNPIN TICKET + await instance.defer("update",false) + + if (originalMsgType == "ticket-message"){ + //ticket message + await opendiscord.actions.get("opendiscord:unpin-ticket").run(originalMsgType,{guild,channel,user,ticket,reason:null,sendMessage:true}) + await instance.update(await opendiscord.builders.messages.getSafe("opendiscord:ticket-message").build("verifybar",{guild,channel,user,ticket})) + }else if (originalMsgType == "pin-message"){ + //converted to unpin message + await opendiscord.actions.get("opendiscord:unpin-ticket").run(originalMsgType,{guild,channel,user,ticket,reason:null,sendMessage:false}) + const sentMsg = await instance.update(await opendiscord.builders.messages.getSafe("opendiscord:unpin-message").build("verifybar",{guild,channel,user,ticket,reason:null})) + if (sentMsg.success) await interactiveMsgState.setMsgState({channel,message:sentMsg.message},{ + messageType:"unpin-message", + messageOrigin:origin, + messageAuthor:user.id, + messageReason:null + },sentMsg.ephemeral) + } + }else if (params.selectedButtonId == "accept-with-reason"){ + //UNPIN WITH REASON (MODAL) + instance.modal(await opendiscord.components.modals.get("opendiscord:unpin-ticket-reason").build("other",{guild,channel,user,ticket,message})) + } + }) + ]) +} + +export async function registerModalResponders(){ //UNPIN WITH REASON MODAL RESPONDER - opendiscord.responders.modals.add(new api.ODModalResponder("opendiscord:unpin-ticket-reason",/^od:unpin-ticket-reason_/)) + opendiscord.responders.modals.add(new api.ODModalResponder("opendiscord:unpin-ticket-reason",/^od:unpin-ticket-reason\|([^|]+)\|([^|]+)/)) opendiscord.responders.modals.get("opendiscord:unpin-ticket-reason").workers.add([ - new api.ODWorker("opendiscord:unpin-ticket-reason",0,async (instance,params,source,cancel) => { - const {guild,channel,user} = instance - if (!channel) return - if (!guild){ - //error - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-not-in-guild").build(source,{channel,user:instance.user})) - return cancel() - } - const ticket = opendiscord.tickets.get(instance.interaction.customId.split("_")[1]) - if (!ticket || channel.isDMBased()){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-ticket-unknown").build("button",{guild,channel,user})) - return - } + new api.ODWorker("opendiscord:unpin-ticket-reason",0,async (instance,params,origin,cancel) => { + const {guild,user} = instance + + const match = /^od:unpin-ticket-reason\|([^|]+)\|([^|]+)/.exec(instance.interaction.customId) + if (!match) return cancel() + const channel = await opendiscord.client.fetchTextChannel(match[1]) + const message = await opendiscord.client.fetchChannelMessage(match[1],match[2]) + + //responder checks + const hasPerms = await openticketUtils.replyHasPermissions(instance,origin,"unpin") + if (!hasPerms) return cancel() + + const isInGuild = await openticketUtils.replyIsInGuild(instance,origin) + if (!isInGuild || !guild || !channel || channel.isDMBased()) return cancel() - const originalSource = instance.interaction.customId.split("_")[2] as Exclude + const state = await openticketUtils.replyInteractiveMessageState(instance,origin,channel,message,"/unpin") + if (!state) return cancel() + + const ticket = await openticketUtils.replyIsTicket(instance,origin) + if (!ticket) return cancel() + + const isAvailable = await openticketUtils.replyTicketIsAvailable(instance,origin,ticket) + if (!isAvailable) return cancel() + + const isTicketPinned = await openticketUtils.replyTicketMustBePinned(instance,origin,ticket) + if (!isTicketPinned) return cancel() + + //fetch state details const reason = instance.values.getTextField("reason",true) + const originalMsgOrigin = state.data.messageOrigin + const originalMsgType = state.data.messageType - //unpin with reason - if (originalSource == "ticket-message"){ - await instance.defer("update",false) - await opendiscord.actions.get("opendiscord:unpin-ticket").run(originalSource,{guild,channel,user,ticket,reason,sendMessage:true}) + //start pinning ticket + await instance.defer("update",false) + + if (originalMsgType == "ticket-message"){ + //ticket message + await opendiscord.actions.get("opendiscord:unpin-ticket").run(originalMsgType,{guild,channel,user,ticket,reason:null,sendMessage:true}) await instance.update(await opendiscord.builders.messages.getSafe("opendiscord:ticket-message").build("other",{guild,channel,user,ticket})) - }else if (originalSource == "pin-message"){ - await instance.defer("update",false) - await opendiscord.actions.get("opendiscord:unpin-ticket").run(originalSource,{guild,channel,user,ticket,reason,sendMessage:false}) - await instance.update(await opendiscord.builders.messages.getSafe("opendiscord:unpin-message").build("other",{guild,channel,user,ticket,reason})) - }else{ - await instance.defer("update",false) - await opendiscord.actions.get("opendiscord:unpin-ticket").run(originalSource,{guild,channel,user,ticket,reason,sendMessage:true}) + + }else if (originalMsgType == "pin-message"){ + //converted to unpin message + await opendiscord.actions.get("opendiscord:unpin-ticket").run(originalMsgType,{guild,channel,user,ticket,reason:null,sendMessage:false}) + const sentMsg = await instance.update(await opendiscord.builders.messages.getSafe("opendiscord:unpin-message").build(originalMsgType,{guild,channel,user,ticket,reason})) + if (sentMsg.success) await interactiveMsgState.setMsgState({channel,message:sentMsg.message},{ + messageType:"unpin-message", + messageOrigin:originalMsgOrigin, + messageAuthor:user.id, + messageReason:reason + },sentMsg.ephemeral) } }) ]) diff --git a/src/components/_INFO.md b/src/components/_INFO.md new file mode 100644 index 0000000..ccf360e --- /dev/null +++ b/src/components/_INFO.md @@ -0,0 +1,13 @@ +Open Discord Components: +------------------------ + +The "Components" system will be the new way of creating message and modal templates in Open Discord with support for Discord components v2. + +Messages, embeds, modals, buttons, etc are currently constructed using the "Builders" system located in `./src/builders/...`. + +This will be replaced with the new component factories in `./src/components/...`. + +### Timing +Messages, modals & components will slowly be migrated to the new system during upcoming updates. The full migration will happen after `Open Ticket v4.3` and the HTML Transcripts v3 are ready for it. + +For more information, contact [DJj123dj](https://github.com/DJj123dj) on Discord, Github or via email \ No newline at end of file diff --git a/src/components/modals.ts b/src/components/modals.ts new file mode 100644 index 0000000..304f572 --- /dev/null +++ b/src/components/modals.ts @@ -0,0 +1,303 @@ +/////////////////////////////////////// +//REGISTER MODAL COMPONENTS +/////////////////////////////////////// +import {opendiscord, api, utilities} from "../index.js" +import * as discord from "discord.js" + +const modals = opendiscord.components.modals +const lang = opendiscord.languages +const generalConfig = opendiscord.configs.get("opendiscord:general") + +export async function registerModalComponents(){ + //TICKET QUESTIONS + modals.add(new api.ODComponentFactory("opendiscord:ticket-questions")) + modals.get("opendiscord:ticket-questions").workers.add( + new api.ODWorker("opendiscord:ticket-questions",0,(instance,params,origin) => { + const {option,user} = params + + const modal = instance.setComponent(new api.ODModalComponent("opendiscord:questions-modal",{ + customId:"od:ticket-questions|"+option.id.value+"|"+origin+"|"+user.id, //add user ID for creating tickets for other users + title:lang.getTranslation("params.uppercase.ticket")+": "+(option.exists("opendiscord:name")) ? option.get("opendiscord:name").value : option.id.value + })) + + for (const questionId of option.get("opendiscord:questions").value){ + const question = opendiscord.questions.get(questionId) + if (!question) continue + + if (question instanceof api.ODShortQuestion){ + //SHORT QUESTION + const component = new api.ODLabelComponent(question.id.value,{ + title:question.get("opendiscord:name").value, + description:(question.get("opendiscord:description").value) ? question.get("opendiscord:description").value : undefined, + }) + component.setComponent(new api.ODShortInputComponent(question.id.value,{ + customId:question.id.value, + required:question.get("opendiscord:required").value, + placeholder:(question.get("opendiscord:placeholder").value) ? question.get("opendiscord:placeholder").value : undefined, + minLength:(question.get("opendiscord:length-enabled").value) ? question.get("opendiscord:length-min").value : undefined, + maxLength:Math.min(1024-6,(question.get("opendiscord:length-enabled").value) ? question.get("opendiscord:length-max").value : (1024-6)) //embed field limit - 6x` characters + })) + modal.addComponent(component,"end") + }else if (question instanceof api.ODParagraphQuestion){ + //PARAGRAPH QUESTION + const component = new api.ODLabelComponent(question.id.value,{ + title:question.get("opendiscord:name").value, + description:(question.get("opendiscord:description").value) ? question.get("opendiscord:description").value : undefined, + }) + component.setComponent(new api.ODParagraphInputComponent(question.id.value,{ + customId:question.id.value, + required:question.get("opendiscord:required").value, + placeholder:(question.get("opendiscord:placeholder").value) ? question.get("opendiscord:placeholder").value : undefined, + minLength:(question.get("opendiscord:length-enabled").value) ? question.get("opendiscord:length-min").value : undefined, + maxLength:Math.min(1024-6,(question.get("opendiscord:length-enabled").value) ? question.get("opendiscord:length-max").value : (1024-6)) //embed field limit - 6x` characters + })) + modal.addComponent(component,"end") + }else if (question instanceof api.ODDropdownQuestion){ + //DROPDOWN QUESTION + const component = new api.ODLabelComponent(question.id.value,{ + title:question.get("opendiscord:name").value, + description:(question.get("opendiscord:description").value) ? question.get("opendiscord:description").value : undefined, + }) + component.setComponent(new api.ODDropdownComponent(question.id.value,{ + type:"string", + customId:question.id.value, + required:question.get("opendiscord:required").value, + placeholder:(question.get("opendiscord:placeholder").value) ? question.get("opendiscord:placeholder").value : undefined, + maxValues:1, + options:question.get("opendiscord:choices").value.map((choice) => ({ + label:choice.title, + emoji:choice.emoji, + description:choice.description, + value:choice.title + })) + })) + modal.addComponent(component,"end") + }else if (question instanceof api.ODRadioSelectQuestion){ + //RADIO SELECT QUESTION + const component = new api.ODLabelComponent(question.id.value,{ + title:question.get("opendiscord:name").value, + description:(question.get("opendiscord:description").value) ? question.get("opendiscord:description").value : undefined, + }) + component.setComponent(new api.ODRadioGroupComponent(question.id.value,{ + customId:question.id.value, + required:question.get("opendiscord:required").value, + options:question.get("opendiscord:choices").value.map((choice) => ({ + label:choice.title, + default:choice.selectedByDefault, + description:choice.description, + value:choice.title + })) + })) + modal.addComponent(component,"end") + }else if (question instanceof api.ODCheckboxSelectQuestion){ + //CHECKBOX SELECT QUESTION + const component = new api.ODLabelComponent(question.id.value,{ + title:question.get("opendiscord:name").value, + description:(question.get("opendiscord:description").value) ? question.get("opendiscord:description").value : undefined, + }) + component.setComponent(new api.ODCheckboxGroupComponent(question.id.value,{ + customId:question.id.value, + required:question.get("opendiscord:required").value, + minValues:(question.get("opendiscord:limits-enabled").value) ? question.get("opendiscord:limits-min").value : undefined, + maxValues:(question.get("opendiscord:limits-enabled").value) ? question.get("opendiscord:limits-max").value : undefined, + options:question.get("opendiscord:choices").value.map((choice) => ({ + label:choice.title, + default:choice.selectedByDefault, + description:choice.description, + value:choice.title + })) + })) + modal.addComponent(component,"end") + }else if (question instanceof api.ODFileUploadQuestion){ + //FILE UPLOAD QUESTION + const component = new api.ODLabelComponent(question.id.value,{ + title:question.get("opendiscord:name").value, + description:(question.get("opendiscord:description").value) ? question.get("opendiscord:description").value : undefined, + }) + const minimumFilesIfRequired = (question.get("opendiscord:required").value) ? 1 : 0 + component.setComponent(new api.ODFileUploadComponent(question.id.value,{ + customId:question.id.value, + required:question.get("opendiscord:required").value, + minAmount:(question.get("opendiscord:limits-enabled").value) ? Math.max(minimumFilesIfRequired,question.get("opendiscord:limits-min").value) : undefined, + maxAmount:(question.get("opendiscord:limits-enabled").value) ? question.get("opendiscord:limits-max").value : undefined, + })) + modal.addComponent(component,"end") + }else if (question instanceof api.ODTextDisplayQuestion){ + //TEXT DISPLAY QUESTION + const component = new api.ODTextComponent(question.id.value,{ + content:question.get("opendiscord:text-contents").value + }) + modal.addComponent(component,"end") + } + } + }) + ) + + //CLOSE TICKET REASON + modals.add(new api.ODComponentFactory("opendiscord:close-ticket-reason")) + modals.get("opendiscord:close-ticket-reason").workers.add( + new api.ODWorker("opendiscord:close-ticket-reason",0,async (instance,params,origin) => { + const {channel,message} = params + + const modal = instance.setComponent(new api.ODModalComponent("opendiscord:close-ticket-reason",{ + customId:"od:close-ticket-reason|"+channel.id+"|"+message.id, + title:lang.getTranslation("actions.buttons.close") + })) + + const reasonComponent = new api.ODLabelComponent("reason",{ + title:lang.getTranslation("params.uppercase.reason"), + description:lang.getTranslation("actions.modal.closePlaceholder") + }) + reasonComponent.setComponent(new api.ODParagraphInputComponent("reason",{ + customId:"reason", + required:true, + placeholder:lang.getTranslation("params.uppercase.reason") + })) + modal.addComponent(reasonComponent,"end") + }) + ) + + //REOPEN TICKET REASON + modals.add(new api.ODComponentFactory("opendiscord:reopen-ticket-reason")) + modals.get("opendiscord:reopen-ticket-reason").workers.add( + new api.ODWorker("opendiscord:reopen-ticket-reason",0,async (instance,params,origin) => { + const {channel,message} = params + + const modal = instance.setComponent(new api.ODModalComponent("opendiscord:reopen-ticket-reason",{ + customId:"od:reopen-ticket-reason|"+channel.id+"|"+message.id, + title:lang.getTranslation("actions.buttons.reopen") + })) + + const reasonComponent = new api.ODLabelComponent("reason",{ + title:lang.getTranslation("params.uppercase.reason"), + description:lang.getTranslation("actions.modal.reopenPlaceholder") + }) + reasonComponent.setComponent(new api.ODParagraphInputComponent("reason",{ + customId:"reason", + required:true, + placeholder:lang.getTranslation("params.uppercase.reason") + })) + modal.addComponent(reasonComponent,"end") + }) + ) + + //DELETE TICKET REASON + modals.add(new api.ODComponentFactory("opendiscord:delete-ticket-reason")) + modals.get("opendiscord:delete-ticket-reason").workers.add( + new api.ODWorker("opendiscord:delete-ticket-reason",0,async (instance,params,origin) => { + const {channel,message} = params + + const modal = instance.setComponent(new api.ODModalComponent("opendiscord:delete-ticket-reason",{ + customId:"od:delete-ticket-reason|"+channel.id+"|"+message.id, + title:lang.getTranslation("actions.buttons.delete") + })) + + const reasonComponent = new api.ODLabelComponent("reason",{ + title:lang.getTranslation("params.uppercase.reason"), + description:lang.getTranslation("actions.modal.deletePlaceholder") + }) + reasonComponent.setComponent(new api.ODParagraphInputComponent("reason",{ + customId:"reason", + required:true, + placeholder:lang.getTranslation("params.uppercase.reason") + })) + modal.addComponent(reasonComponent,"end") + }) + ) + + //CLAIM TICKET REASON + modals.add(new api.ODComponentFactory("opendiscord:claim-ticket-reason")) + modals.get("opendiscord:claim-ticket-reason").workers.add( + new api.ODWorker("opendiscord:claim-ticket-reason",0,async (instance,params,origin) => { + const {channel,message} = params + + const modal = instance.setComponent(new api.ODModalComponent("opendiscord:claim-ticket-reason",{ + customId:"od:claim-ticket-reason|"+channel.id+"|"+message.id, + title:lang.getTranslation("actions.buttons.claim") + })) + + const reasonComponent = new api.ODLabelComponent("reason",{ + title:lang.getTranslation("params.uppercase.reason"), + description:lang.getTranslation("actions.modal.claimPlaceholder") + }) + reasonComponent.setComponent(new api.ODParagraphInputComponent("reason",{ + customId:"reason", + required:true, + placeholder:lang.getTranslation("params.uppercase.reason") + })) + modal.addComponent(reasonComponent,"end") + }) + ) + + //UNCLAIM TICKET REASON + modals.add(new api.ODComponentFactory("opendiscord:unclaim-ticket-reason")) + modals.get("opendiscord:unclaim-ticket-reason").workers.add( + new api.ODWorker("opendiscord:unclaim-ticket-reason",0,async (instance,params,origin) => { + const {channel,message} = params + + const modal = instance.setComponent(new api.ODModalComponent("opendiscord:unclaim-ticket-reason",{ + customId:"od:unclaim-ticket-reason|"+channel.id+"|"+message.id, + title:lang.getTranslation("actions.buttons.unclaim") + })) + + const reasonComponent = new api.ODLabelComponent("reason",{ + title:lang.getTranslation("params.uppercase.reason"), + description:lang.getTranslation("actions.modal.unclaimPlaceholder") + }) + reasonComponent.setComponent(new api.ODParagraphInputComponent("reason",{ + customId:"reason", + required:true, + placeholder:lang.getTranslation("params.uppercase.reason") + })) + modal.addComponent(reasonComponent,"end") + }) + ) + + //PIN TICKET REASON + modals.add(new api.ODComponentFactory("opendiscord:pin-ticket-reason")) + modals.get("opendiscord:pin-ticket-reason").workers.add( + new api.ODWorker("opendiscord:pin-ticket-reason",0,async (instance,params,origin) => { + const {channel,message} = params + + const modal = instance.setComponent(new api.ODModalComponent("opendiscord:pin-ticket-reason",{ + customId:"od:pin-ticket-reason|"+channel.id+"|"+message.id, + title:lang.getTranslation("actions.buttons.pin") + })) + + const reasonComponent = new api.ODLabelComponent("reason",{ + title:lang.getTranslation("params.uppercase.reason"), + description:lang.getTranslation("actions.modal.pinPlaceholder") + }) + reasonComponent.setComponent(new api.ODParagraphInputComponent("reason",{ + customId:"reason", + required:true, + placeholder:lang.getTranslation("params.uppercase.reason") + })) + modal.addComponent(reasonComponent,"end") + }) + ) + + //UNPIN TICKET REASON + modals.add(new api.ODComponentFactory("opendiscord:unpin-ticket-reason")) + modals.get("opendiscord:unpin-ticket-reason").workers.add( + new api.ODWorker("opendiscord:unpin-ticket-reason",0,async (instance,params,origin) => { + const {channel,message} = params + + const modal = instance.setComponent(new api.ODModalComponent("opendiscord:unpin-ticket-reason",{ + customId:"od:unpin-ticket-reason|"+channel.id+"|"+message.id, + title:lang.getTranslation("actions.buttons.unpin") + })) + + const reasonComponent = new api.ODLabelComponent("reason",{ + title:lang.getTranslation("params.uppercase.reason"), + description:lang.getTranslation("actions.modal.unpinPlaceholder") + }) + reasonComponent.setComponent(new api.ODParagraphInputComponent("reason",{ + customId:"reason", + required:true, + placeholder:lang.getTranslation("params.uppercase.reason") + })) + modal.addComponent(reasonComponent,"end") + }) + ) +} \ No newline at end of file diff --git a/src/components/verifybarModifiers.ts b/src/components/verifybarModifiers.ts new file mode 100644 index 0000000..60d35ed --- /dev/null +++ b/src/components/verifybarModifiers.ts @@ -0,0 +1,138 @@ +/////////////////////////////////////// +//VERIFYBAR MESSAGE MODIFIERS +/////////////////////////////////////// +import {opendiscord, api, utilities} from "../index.js" +import * as discord from "discord.js" + +const modifiers = opendiscord.components.modifiers +const lang = opendiscord.languages +const generalConfig = opendiscord.configs.get("opendiscord:general") + +export async function addVerifyButton(instance:api.ODMessageInstance|api.ODComponentFactoryInstance,params:{guild:discord.Guild|null,channel:discord.TextBasedChannel,user:discord.User,verifybar:api.ODVerifyBar},defaultButtonType:"✅"|"❌",useDefaultLabels:boolean,verifyButtonId:string,customLabel?:string,customColor?:api.ODValidButtonColor,customEmoji?:string){ + const {guild,channel,user,verifybar} = params + if (instance instanceof api.ODMessageInstance){ + //message builders + instance.addComponent(await opendiscord.builders.buttons.getSafe("opendiscord:verifybar-button").build("verifybar",{ + guild,channel,user,verifybar, + defaultButtonType,useDefaultLabels,customColor,customEmoji,customLabel, + verifyButtonId + })) + }else{ + //message components + throw new api.ODSystemError("registerAllVerifyBarModifiers addVerifyButton() => verifybar doesn't support ODComponents v2 yet!") + //const message = instance.getComponent() + //if (!message) return + //const actionRow: api.ODActionRowComponent|null = message.getComponentsOfType("action-row")[0] + //if (actionRow) actionRow.addComponent(new api.ODButtonComponent("opendiscord:verifybar-button",{ + // //TODO + //}),"end") + } +} + +export async function registerAllVerifyBarModifiers(){ + //CLOSE TICKET VERIFYBAR + modifiers.add(new api.ODMessageComponentModifier("opendiscord:close-ticket-verifybar")) + modifiers.get("opendiscord:close-ticket-verifybar").workers.add(new api.ODWorker("opendiscord:close-ticket-verifybar",100,async (instance,params,origin,cancel) => { + if (instance instanceof api.ODMessageInstance){ + instance.data.components = [] //clear actionrow + }else{ + throw new api.ODSystemError("registerAllVerifyBarModifiers()... => verifybars don't support ODComponents v2 yet!") + } + + //cancel, accept or close with reason + await addVerifyButton(instance,params,"❌",true,api.ODVerifyButtonId.Cancel) + await addVerifyButton(instance,params,"✅",true,api.ODVerifyButtonId.Accept,lang.getTranslation("actions.buttons.close")) + if (generalConfig.data.ticketSystem.enableTicketActionWithReason) await addVerifyButton(instance,params,"✅",false,api.ODVerifyButtonId.AcceptWithReason,lang.getTranslation("actions.buttons.withReason"),"blue","✏️") + })) + + //REOPEN TICKET VERIFYBAR + modifiers.add(new api.ODMessageComponentModifier("opendiscord:reopen-ticket-verifybar")) + modifiers.get("opendiscord:reopen-ticket-verifybar").workers.add(new api.ODWorker("opendiscord:reopen-ticket-verifybar",100,async (instance,params,origin,cancel) => { + if (instance instanceof api.ODMessageInstance){ + instance.data.components = [] //clear actionrow + }else{ + throw new api.ODSystemError("registerAllVerifyBarModifiers()... => verifybars don't support ODComponents v2 yet!") + } + + //cancel, accept or reopen with reason + await addVerifyButton(instance,params,"❌",true,api.ODVerifyButtonId.Cancel) + await addVerifyButton(instance,params,"✅",true,api.ODVerifyButtonId.Accept,lang.getTranslation("actions.buttons.reopen")) + if (generalConfig.data.ticketSystem.enableTicketActionWithReason) await addVerifyButton(instance,params,"✅",false,api.ODVerifyButtonId.AcceptWithReason,lang.getTranslation("actions.buttons.withReason"),"blue","✏️") + })) + + //DELETE TICKET VERIFYBAR + modifiers.add(new api.ODMessageComponentModifier("opendiscord:delete-ticket-verifybar")) + modifiers.get("opendiscord:delete-ticket-verifybar").workers.add(new api.ODWorker("opendiscord:delete-ticket-verifybar",100,async (instance,params,origin,cancel) => { + if (instance instanceof api.ODMessageInstance){ + instance.data.components = [] //clear actionrow + }else{ + throw new api.ODSystemError("registerAllVerifyBarModifiers()... => verifybars don't support ODComponents v2 yet!") + } + + //cancel, accept or delete with reason or without transcript + await addVerifyButton(instance,params,"❌",true,api.ODVerifyButtonId.Cancel) + await addVerifyButton(instance,params,"✅",true,api.ODVerifyButtonId.Accept,lang.getTranslation("actions.buttons.delete")) + if (generalConfig.data.ticketSystem.enableTicketActionWithReason) await addVerifyButton(instance,params,"✅",false,api.ODVerifyButtonId.AcceptWithReason,lang.getTranslation("actions.buttons.withReason"),"blue","✏️") + if (generalConfig.data.ticketSystem.enableDeleteWithoutTranscript) await addVerifyButton(instance,params,"✅",false,api.ODVerifyButtonId.AcceptWithoutTranscript,lang.getTranslation("actions.buttons.withoutTranscript"),"red","📄") + })) + + //CLAIM TICKET VERIFYBAR + modifiers.add(new api.ODMessageComponentModifier("opendiscord:claim-ticket-verifybar")) + modifiers.get("opendiscord:claim-ticket-verifybar").workers.add(new api.ODWorker("opendiscord:claim-ticket-verifybar",100,async (instance,params,origin,cancel) => { + if (instance instanceof api.ODMessageInstance){ + instance.data.components = [] //clear actionrow + }else{ + throw new api.ODSystemError("registerAllVerifyBarModifiers()... => verifybars don't support ODComponents v2 yet!") + } + + //cancel, accept or claim with reason + await addVerifyButton(instance,params,"❌",true,api.ODVerifyButtonId.Cancel) + await addVerifyButton(instance,params,"✅",true,api.ODVerifyButtonId.Accept,lang.getTranslation("actions.buttons.claim")) + if (generalConfig.data.ticketSystem.enableTicketActionWithReason) await addVerifyButton(instance,params,"✅",false,api.ODVerifyButtonId.AcceptWithReason,lang.getTranslation("actions.buttons.withReason"),"blue","✏️") + })) + + //UNCLAIM TICKET VERIFYBAR + modifiers.add(new api.ODMessageComponentModifier("opendiscord:unclaim-ticket-verifybar")) + modifiers.get("opendiscord:unclaim-ticket-verifybar").workers.add(new api.ODWorker("opendiscord:unclaim-ticket-verifybar",100,async (instance,params,origin,cancel) => { + if (instance instanceof api.ODMessageInstance){ + instance.data.components = [] //clear actionrow + }else{ + throw new api.ODSystemError("registerAllVerifyBarModifiers()... => verifybars don't support ODComponents v2 yet!") + } + + //cancel, accept or unclaim with reason + await addVerifyButton(instance,params,"❌",true,api.ODVerifyButtonId.Cancel) + await addVerifyButton(instance,params,"✅",true,api.ODVerifyButtonId.Accept,lang.getTranslation("actions.buttons.unclaim")) + if (generalConfig.data.ticketSystem.enableTicketActionWithReason) await addVerifyButton(instance,params,"✅",false,api.ODVerifyButtonId.AcceptWithReason,lang.getTranslation("actions.buttons.withReason"),"blue","✏️") + })) + + //PIN TICKET VERIFYBAR + modifiers.add(new api.ODMessageComponentModifier("opendiscord:pin-ticket-verifybar")) + modifiers.get("opendiscord:pin-ticket-verifybar").workers.add(new api.ODWorker("opendiscord:pin-ticket-verifybar",100,async (instance,params,origin,cancel) => { + if (instance instanceof api.ODMessageInstance){ + instance.data.components = [] //clear actionrow + }else{ + throw new api.ODSystemError("registerAllVerifyBarModifiers()... => verifybars don't support ODComponents v2 yet!") + } + + //cancel, accept or pin with reason + await addVerifyButton(instance,params,"❌",true,api.ODVerifyButtonId.Cancel) + await addVerifyButton(instance,params,"✅",true,api.ODVerifyButtonId.Accept,lang.getTranslation("actions.buttons.pin")) + if (generalConfig.data.ticketSystem.enableTicketActionWithReason) await addVerifyButton(instance,params,"✅",false,api.ODVerifyButtonId.AcceptWithReason,lang.getTranslation("actions.buttons.withReason"),"blue","✏️") + })) + + //UNPIN TICKET VERIFYBAR + modifiers.add(new api.ODMessageComponentModifier("opendiscord:unpin-ticket-verifybar")) + modifiers.get("opendiscord:unpin-ticket-verifybar").workers.add(new api.ODWorker("opendiscord:unpin-ticket-verifybar",100,async (instance,params,origin,cancel) => { + if (instance instanceof api.ODMessageInstance){ + instance.data.components = [] //clear actionrow + }else{ + throw new api.ODSystemError("registerAllVerifyBarModifiers()... => verifybars don't support ODComponents v2 yet!") + } + + //cancel, accept or unpin with reason + await addVerifyButton(instance,params,"❌",true,api.ODVerifyButtonId.Cancel) + await addVerifyButton(instance,params,"✅",true,api.ODVerifyButtonId.Accept,lang.getTranslation("actions.buttons.unpin")) + if (generalConfig.data.ticketSystem.enableTicketActionWithReason) await addVerifyButton(instance,params,"✅",false,api.ODVerifyButtonId.AcceptWithReason,lang.getTranslation("actions.buttons.withReason"),"blue","✏️") + })) +} \ No newline at end of file diff --git a/src/core/api.ts b/src/core/api.ts new file mode 100644 index 0000000..82cd8d5 --- /dev/null +++ b/src/core/api.ts @@ -0,0 +1,43 @@ +//EXPORT FRAMEWORK +export * from "@open-discord-bots/framework/api" + +//EXPORT OPEN TICKET MAPPINGS +export * from "./mappings/action.js" +export * from "./mappings/base.js" +export * from "./mappings/builder.js" +export * from "./mappings/checker.js" +export * from "./mappings/client.js" +export * from "./mappings/component.js" +export * from "./mappings/config.js" +export * from "./mappings/console.js" +export * from "./mappings/cooldown.js" +export * from "./mappings/database.js" +export * from "./mappings/event.js" +export * from "./mappings/flag.js" +export * from "./mappings/fuse.js" +export * from "./mappings/helpmenu.js" +export * from "./mappings/language.js" +export * from "./mappings/permission.js" +export * from "./mappings/plugin.js" +export * from "./mappings/post.js" +export * from "./mappings/progressbar.js" +export * from "./mappings/responder.js" +export * from "./mappings/session.js" +export * from "./mappings/startscreen.js" +export * from "./mappings/state.js" +export * from "./mappings/statistic.js" +export * from "./mappings/task.js" +export * from "./mappings/verifybar.js" + +//EXPORT OPENTICKET MODULES +export * from "./api/blacklist.js" +export * from "./api/option.js" +export * from "./api/panel.js" +export * from "./api/priority.js" +export * from "./api/question.js" +export * from "./api/role.js" +export * from "./api/ticket.js" +export * from "./api/transcript.js" + +//EXPORT MAIN MODULE +export { ODOpenTicketMain } from "./main.js" \ No newline at end of file diff --git a/src/core/api/api.ts b/src/core/api/api.ts deleted file mode 100644 index 7286d8f..0000000 --- a/src/core/api/api.ts +++ /dev/null @@ -1,64 +0,0 @@ -//MAIN MODULE -export * from "./main" - -//BASE MODULES -export * from "./modules/base" -export * from "./modules/event" -export * from "./modules/config" -export * from "./modules/database" -export * from "./modules/language" -export * from "./modules/flag" -export * from "./modules/console" -export * from "./modules/defaults" -export * from "./modules/plugin" -export * from "./modules/checker" -export * from "./modules/client" -export * from "./modules/worker" -export * from "./modules/builder" -export * from "./modules/responder" -export * from "./modules/action" -export * from "./modules/permission" -export * from "./modules/helpmenu" -export * from "./modules/session" -export * from "./modules/stat" -export * from "./modules/code" -export * from "./modules/cooldown" -export * from "./modules/post" -export * from "./modules/verifybar" -export * from "./modules/progressbar" -export * from "./modules/startscreen" - -//OPENTICKET DEFAULT MODULES -export * from "./defaults/base" -export * from "./defaults/event" -export * from "./defaults/config" -export * from "./defaults/database" -export * from "./defaults/plugin" -export * from "./defaults/checker" -export * from "./defaults/client" -export * from "./defaults/language" -export * from "./defaults/builder" -export * from "./defaults/responder" -export * from "./defaults/action" -export * from "./defaults/flag" -export * from "./defaults/permission" -export * from "./defaults/helpmenu" -export * from "./defaults/session" -export * from "./defaults/stat" -export * from "./defaults/worker" -export * from "./defaults/code" -export * from "./defaults/cooldown" -export * from "./defaults/post" -export * from "./defaults/progressbar" -export * from "./defaults/startscreen" -export * from "./defaults/console" - -//OPENTICKET MODULES -export * from "./openticket/question" -export * from "./openticket/option" -export * from "./openticket/panel" -export * from "./openticket/ticket" -export * from "./openticket/blacklist" -export * from "./openticket/transcript" -export * from "./openticket/role" -export * from "./openticket/priority" \ No newline at end of file diff --git a/src/core/api/openticket/blacklist.ts b/src/core/api/blacklist.ts similarity index 69% rename from src/core/api/openticket/blacklist.ts rename to src/core/api/blacklist.ts index d7dedcd..391f526 100644 --- a/src/core/api/openticket/blacklist.ts +++ b/src/core/api/blacklist.ts @@ -1,8 +1,7 @@ /////////////////////////////////////// //OPENTICKET BLACKLIST MODULE /////////////////////////////////////// -import { ODManager, ODManagerData, ODValidId } from "../modules/base" -import { ODDebugger } from "../modules/console" +import * as api from "@open-discord-bots/framework/api" /**## ODBlacklist `class` * This is an Open Ticket blacklisted user. @@ -11,22 +10,22 @@ import { ODDebugger } from "../modules/console" * * Create this class & add it to the `ODBlacklistManager` to blacklist someone! */ -export class ODBlacklist extends ODManagerData { +export class ODBlacklist extends api.ODManagerData { /**The reason why this user got blacklisted. (optional) */ - #reason: string|null + private rawReason: string|null - constructor(id:ODValidId,reason:string|null){ + constructor(id:api.ODValidId,reason:string|null){ super(id) - this.#reason = reason + this.rawReason = reason } /**The reason why this user got blacklisted. (optional) */ set reason(reason:string|null) { - this.#reason = reason + this.rawReason = reason this._change() } get reason(){ - return this.#reason + return this.rawReason } } @@ -37,8 +36,8 @@ export class ODBlacklist extends ODManagerData { * * All `ODBlacklist`'s added, removed & edited in this list will be synced automatically with the database. */ -export class ODBlacklistManager extends ODManager { - constructor(debug:ODDebugger){ +export class ODBlacklistManager extends api.ODManager { + constructor(debug:api.ODDebugger){ super(debug,"blacklist") } } \ No newline at end of file diff --git a/src/core/api/defaults/base.ts b/src/core/api/defaults/base.ts deleted file mode 100644 index 2cfa279..0000000 --- a/src/core/api/defaults/base.ts +++ /dev/null @@ -1,45 +0,0 @@ -/////////////////////////////////////// -//BASE MODULE -/////////////////////////////////////// -import { ODVersion, ODVersionManager, ODValidId } from "../modules/base" - -/**## ODVersionManagerIds_Default `interface` - * This interface is a list of ids available in the `ODVersionManager` class. - * It's used to generate typescript declarations for this class. - */ -export interface ODVersionManagerIds_Default { - "opendiscord:version":ODVersion, - "opendiscord:last-version":ODVersion, - "opendiscord:api":ODVersion, - "opendiscord:transcripts":ODVersion, - "opendiscord:livestatus":ODVersion -} - -/**## ODFlagManager_Default `default_class` - * This is a special class that adds type definitions & typescript to the ODFlagManager class. - * It doesn't add any extra features! - * - * This default class is made for the global variable `opendiscord.flags`! - */ -export class ODVersionManager_Default extends ODVersionManager { - get(id:VersionId): ODVersionManagerIds_Default[VersionId] - get(id:ODValidId): ODVersion|null - - get(id:ODValidId): ODVersion|null { - return super.get(id) - } - - remove(id:VersionId): ODVersionManagerIds_Default[VersionId] - remove(id:ODValidId): ODVersion|null - - remove(id:ODValidId): ODVersion|null { - return super.remove(id) - } - - exists(id:keyof ODVersionManagerIds_Default): boolean - exists(id:ODValidId): boolean - - exists(id:ODValidId): boolean { - return super.exists(id) - } -} \ No newline at end of file diff --git a/src/core/api/defaults/builder.ts b/src/core/api/defaults/builder.ts deleted file mode 100644 index 96c652b..0000000 --- a/src/core/api/defaults/builder.ts +++ /dev/null @@ -1,549 +0,0 @@ -/////////////////////////////////////// -//DEFAULT BUILDER MODULE -/////////////////////////////////////// -import { ODValidButtonColor, ODValidId } from "../modules/base" -import { ODBuilderManager, ODButton, ODButtonInstance, ODButtonManager, ODDropdown, ODDropdownInstance, ODDropdownManager, ODEmbed, ODEmbedInstance, ODEmbedManager, ODFile, ODFileInstance, ODFileManager, ODMessage, ODMessageInstance, ODMessageManager, ODModal, ODModalInstance, ODModalManager } from "../modules/builder" -import { ODWorkerManager_Default } from "./worker" -import { ODTicket, ODTicketClearFilter } from "../openticket/ticket" -import { ODPermissionEmbedType } from "../defaults/permission" -import { ODTextCommandErrorInvalidOption, ODTextCommandErrorMissingOption, ODTextCommandErrorUnknownCommand } from "../modules/client" -import { ODPanel } from "../openticket/panel" -import { ODRoleOption, ODTicketOption, ODWebsiteOption } from "../openticket/option" -import { ODVerifyBar } from "../modules/verifybar" -import * as discord from "discord.js" -import { ODTranscriptCompiler, ODTranscriptCompilerCompileResult } from "../openticket/transcript" -import { ODRole, ODRoleUpdateResult } from "../openticket/role" -import { ODPriorityLevel } from "../openticket/priority" - -/**## ODBuilderManager_Default `default_class` - * This is a special class that adds type definitions & typescript to the ODBuilderManager class. - * It doesn't add any extra features! - * - * This default class is made for the global variable `opendiscord.builders`! - */ -export class ODBuilderManager_Default extends ODBuilderManager { - declare buttons: ODButtonManager_Default - declare dropdowns: ODDropdownManager_Default - declare files: ODFileManager_Default - declare embeds: ODEmbedManager_Default - declare messages: ODMessageManager_Default - declare modals: ODModalManager_Default -} - -/**## ODButtonManagerIds_Default `interface` - * This interface is a list of ids available in the `ODButtonManager_Default` class. - * It's used to generate typescript declarations for this class. - */ -export interface ODButtonManagerIds_Default { - "opendiscord:verifybar-success":{source:"verifybar"|"other",params:{guild:discord.Guild|null,channel:discord.TextBasedChannel,user:discord.User,verifybar:ODVerifyBar,customData?:string,customColor?:ODValidButtonColor,customLabel?:string,customEmoji?:string},workers:"opendiscord:verifybar-success"}, - "opendiscord:verifybar-failure":{source:"verifybar"|"other",params:{guild:discord.Guild|null,channel:discord.TextBasedChannel,user:discord.User,verifybar:ODVerifyBar,customData?:string,customColor?:ODValidButtonColor,customLabel?:string,customEmoji?:string},workers:"opendiscord:verifybar-failure"}, - - "opendiscord:error-ticket-deprecated-transcript":{source:"slash"|"text"|"button"|"dropdown"|"modal"|"other",params:{},workers:"opendiscord:error-ticket-deprecated-transcript"}, - - "opendiscord:help-menu-previous":{source:"text"|"slash"|"button"|"other",params:{mode:"slash"|"text",page:number},workers:"opendiscord:help-menu-previous"}, - "opendiscord:help-menu-next":{source:"text"|"slash"|"button"|"other",params:{mode:"slash"|"text",page:number},workers:"opendiscord:help-menu-next"}, - "opendiscord:help-menu-page":{source:"text"|"slash"|"button"|"other",params:{mode:"slash"|"text",page:number},workers:"opendiscord:help-menu-page"} - "opendiscord:help-menu-switch":{source:"text"|"slash"|"button"|"other",params:{mode:"slash"|"text",page:number},workers:"opendiscord:help-menu-switch"}, - - "opendiscord:ticket-option":{source:"slash"|"text"|"auto-update"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,panel:ODPanel,option:ODTicketOption},workers:"opendiscord:ticket-option"}, - "opendiscord:website-option":{source:"slash"|"text"|"auto-update"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,panel:ODPanel,option:ODWebsiteOption},workers:"opendiscord:website-option"}, - "opendiscord:role-option":{source:"slash"|"text"|"auto-update"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,panel:ODPanel,option:ODRoleOption},workers:"opendiscord:role-option"} - - "opendiscord:visit-ticket":{source:"ticket-created"|"dm"|"logs"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,ticket:ODTicket},workers:"opendiscord:visit-ticket"}, - - "opendiscord:close-ticket":{source:"ticket-message"|"reopen-message"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,ticket:ODTicket},workers:"opendiscord:close-ticket"}, - "opendiscord:delete-ticket":{source:"ticket-message"|"close-message"|"autoclose-message"|"reopen-message"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,ticket:ODTicket},workers:"opendiscord:delete-ticket"}, - "opendiscord:reopen-ticket":{source:"ticket-message"|"close-message"|"autoclose-message"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,ticket:ODTicket},workers:"opendiscord:reopen-ticket"}, - "opendiscord:claim-ticket":{source:"ticket-message"|"unclaim-message"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,ticket:ODTicket},workers:"opendiscord:claim-ticket"}, - "opendiscord:unclaim-ticket":{source:"ticket-message"|"claim-message"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,ticket:ODTicket},workers:"opendiscord:unclaim-ticket"}, - "opendiscord:pin-ticket":{source:"ticket-message"|"unpin-message"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,ticket:ODTicket},workers:"opendiscord:pin-ticket"}, - "opendiscord:unpin-ticket":{source:"ticket-message"|"pin-message"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,ticket:ODTicket},workers:"opendiscord:unpin-ticket"}, - - "opendiscord:transcript-html-visit":{source:"channel"|"creator-dm"|"participant-dm"|"active-admin-dm"|"every-admin-dm"|"other",params:{guild:discord.Guild,channel:discord.GuildTextBasedChannel,user:discord.User,ticket:ODTicket,compiler:ODTranscriptCompiler<{url:string,availableUntil:Date},{auth:string}>,result:ODTranscriptCompilerCompileResult<{url:string,availableUntil:Date}>},workers:"opendiscord:transcript-html-visit"}, - "opendiscord:transcript-error-retry":{source:"slash"|"text"|"ticket-message"|"reopen-message"|"close-message"|"autoclose-message"|"autodelete"|"clear"|"other",params:{guild:discord.Guild,channel:discord.GuildTextBasedChannel,user:discord.User,ticket:ODTicket,compiler:ODTranscriptCompiler,reason:string|null},workers:"opendiscord:transcript-error-retry"}, - "opendiscord:transcript-error-continue":{source:"slash"|"text"|"ticket-message"|"reopen-message"|"close-message"|"autoclose-message"|"autodelete"|"clear"|"other",params:{guild:discord.Guild,channel:discord.GuildTextBasedChannel,user:discord.User,ticket:ODTicket,compiler:ODTranscriptCompiler,reason:string|null},workers:"opendiscord:transcript-error-continue"}, - - "opendiscord:clear-continue":{source:"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.GuildTextBasedChannel,user:discord.User,filter:ODTicketClearFilter,list:string[]},workers:"opendiscord:clear-continue"}, -} - -/**## ODButtonManager_Default `default_class` - * This is a special class that adds type definitions & typescript to the ODButtonManager class. - * It doesn't add any extra features! - * - * This default class is made for the global variable `opendiscord.builders.buttons`! - */ -export class ODButtonManager_Default extends ODButtonManager { - get(id:ButtonId): ODButton_Default - get(id:ODValidId): ODButton|null - - get(id:ODValidId): ODButton|null { - return super.get(id) - } - - remove(id:ButtonId): ODButton_Default - remove(id:ODValidId): ODButton|null - - remove(id:ODValidId): ODButton|null { - return super.remove(id) - } - - exists(id:keyof ODButtonManagerIds_Default): boolean - exists(id:ODValidId): boolean - - exists(id:ODValidId): boolean { - return super.exists(id) - } - - getSafe(id:ButtonId): ODButton_Default - getSafe(id:ODValidId): ODButton - - getSafe(id:ODValidId): ODButton { - return super.getSafe(id) - } -} - -/**## ODButton_Default `default_class` - * This is a special class that adds type definitions & typescript to the ODButton class. - * It doesn't add any extra features! - * - * This default class is made for the default `ODButton`'s! - */ -export class ODButton_Default extends ODButton { - declare workers: ODWorkerManager_Default -} - -/**## ODDropdownManagerIds_Default `interface` - * This interface is a list of ids available in the `ODDropdownManager_Default` class. - * It's used to generate typescript declarations for this class. - */ -export interface ODDropdownManagerIds_Default { - "opendiscord:panel-dropdown-tickets":{source:"slash"|"text"|"auto-update"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,panel:ODPanel,options:ODTicketOption[]},workers:"opendiscord:panel-dropdown-tickets"} -} - -/**## ODDropdownManager_Default `default_class` - * This is a special class that adds type definitions & typescript to the ODDropdownManager class. - * It doesn't add any extra features! - * - * This default class is made for the global variable `opendiscord.builders.dropdowns`! - */ -export class ODDropdownManager_Default extends ODDropdownManager { - get(id:DropdownId): ODDropdown_Default - get(id:ODValidId): ODDropdown|null - - get(id:ODValidId): ODDropdown|null { - return super.get(id) - } - - remove(id:DropdownId): ODDropdown_Default - remove(id:ODValidId): ODDropdown|null - - remove(id:ODValidId): ODDropdown|null { - return super.remove(id) - } - - exists(id:keyof ODDropdownManagerIds_Default): boolean - exists(id:ODValidId): boolean - - exists(id:ODValidId): boolean { - return super.exists(id) - } - - getSafe(id:DropdownId): ODDropdown_Default - getSafe(id:ODValidId): ODDropdown - - getSafe(id:ODValidId): ODDropdown { - return super.getSafe(id) - } -} - -/**## ODDropdown_Default `default_class` - * This is a special class that adds type definitions & typescript to the ODDropdown class. - * It doesn't add any extra features! - * - * This default class is made for the default `ODDropdown`'s! - */ -export class ODDropdown_Default extends ODDropdown { - declare workers: ODWorkerManager_Default -} - -/**## ODFileManagerIds_Default `interface` - * This interface is a list of ids available in the `ODFileManager_Default` class. - * It's used to generate typescript declarations for this class. - */ -export interface ODFileManagerIds_Default { - "opendiscord:text-transcript":{source:"channel"|"creator-dm"|"participant-dm"|"active-admin-dm"|"every-admin-dm"|"other",params:{guild:discord.Guild,channel:discord.GuildTextBasedChannel,user:discord.User,ticket:ODTicket,compiler:ODTranscriptCompiler,result:ODTranscriptCompilerCompileResult},workers:"opendiscord:text-transcript"} -} - -/**## ODFileManager_Default `default_class` - * This is a special class that adds type definitions & typescript to the ODFileManager class. - * It doesn't add any extra features! - * - * This default class is made for the global variable `opendiscord.builders.files`! - */ -export class ODFileManager_Default extends ODFileManager { - get(id:FileId): ODFile_Default - get(id:ODValidId): ODFile|null - - get(id:ODValidId): ODFile|null { - return super.get(id) - } - - remove(id:FileId): ODFile_Default - remove(id:ODValidId): ODFile|null - - remove(id:ODValidId): ODFile|null { - return super.remove(id) - } - - exists(id:keyof ODFileManagerIds_Default): boolean - exists(id:ODValidId): boolean - - exists(id:ODValidId): boolean { - return super.exists(id) - } - - getSafe(id:FileId): ODFile_Default - getSafe(id:ODValidId): ODFile - - getSafe(id:ODValidId): ODFile { - return super.getSafe(id) - } -} - -/**## ODFile_Default `default_class` - * This is a special class that adds type definitions & typescript to the ODFile class. - * It doesn't add any extra features! - * - * This default class is made for the default `ODFile`'s! - */ -export class ODFile_Default extends ODFile { - declare workers: ODWorkerManager_Default -} - -/**## ODEmbedManagerIds_Default `interface` - * This interface is a list of ids available in the `ODEmbedManager_Default` class. - * It's used to generate typescript declarations for this class. - */ -export interface ODEmbedManagerIds_Default { - "opendiscord:error":{source:"slash"|"text"|"button"|"dropdown"|"modal"|"other",params:{guild:discord.Guild|null,channel:discord.TextBasedChannel,user:discord.User,error:string,layout:"simple"|"advanced",customTitle?:string},workers:"opendiscord:error"}, - "opendiscord:error-option-missing":{source:"slash"|"text"|"other",params:{guild:discord.Guild|null,channel:discord.TextBasedChannel,user:discord.User,error:ODTextCommandErrorMissingOption},workers:"opendiscord:error-option-missing"}, - "opendiscord:error-option-invalid":{source:"slash"|"text"|"other",params:{guild:discord.Guild|null,channel:discord.TextBasedChannel,user:discord.User,error:ODTextCommandErrorInvalidOption},workers:"opendiscord:error-option-invalid"}, - "opendiscord:error-unknown-command":{source:"slash"|"text"|"other",params:{guild:discord.Guild|null,channel:discord.TextBasedChannel,user:discord.User,error:ODTextCommandErrorUnknownCommand},workers:"opendiscord:error-unknown-command"}, - "opendiscord:error-no-permissions":{source:"slash"|"text"|"button"|"dropdown"|"modal"|"other",params:{guild:discord.Guild|null,channel:discord.TextBasedChannel,user:discord.User,permissions:ODPermissionEmbedType[]},workers:"opendiscord:error-no-permissions"}, - "opendiscord:error-no-permissions-cooldown":{source:"slash"|"text"|"button"|"dropdown"|"modal"|"other",params:{guild:discord.Guild|null,channel:discord.TextBasedChannel,user:discord.User,until?:Date},workers:"opendiscord:error-no-permissions-cooldown"}, - "opendiscord:error-no-permissions-blacklisted":{source:"slash"|"text"|"button"|"dropdown"|"modal"|"other",params:{guild:discord.Guild|null,channel:discord.TextBasedChannel,user:discord.User},workers:"opendiscord:error-no-permissions-blacklisted"}, - "opendiscord:error-no-permissions-limits":{source:"slash"|"text"|"button"|"dropdown"|"modal"|"other",params:{guild:discord.Guild|null,channel:discord.TextBasedChannel,user:discord.User,limit:"global"|"global-user"|"option"|"option-user"},workers:"opendiscord:error-no-permissions-limits"}, - "opendiscord:error-responder-timeout":{source:"slash"|"text"|"button"|"dropdown"|"modal"|"other",params:{guild:discord.Guild|null,channel:discord.TextBasedChannel,user:discord.User},workers:"opendiscord:error-responder-timeout"}, - "opendiscord:error-ticket-unknown":{source:"slash"|"text"|"button"|"dropdown"|"modal"|"other",params:{guild:discord.Guild|null,channel:discord.TextBasedChannel,user:discord.User},workers:"opendiscord:error-ticket-unknown"}, - "opendiscord:error-ticket-deprecated":{source:"slash"|"text"|"button"|"dropdown"|"modal"|"other",params:{guild:discord.Guild|null,channel:discord.TextBasedChannel,user:discord.User},workers:"opendiscord:error-ticket-deprecated"}, - "opendiscord:error-option-unknown":{source:"slash"|"text"|"button"|"dropdown"|"modal"|"other",params:{guild:discord.Guild|null,channel:discord.TextBasedChannel,user:discord.User},workers:"opendiscord:error-option-unknown"}, - "opendiscord:error-panel-unknown":{source:"slash"|"text"|"button"|"dropdown"|"modal"|"other",params:{guild:discord.Guild|null,channel:discord.TextBasedChannel,user:discord.User},workers:"opendiscord:error-panel-unknown"}, - "opendiscord:error-not-in-guild":{source:"slash"|"text"|"button"|"dropdown"|"modal"|"other",params:{channel:discord.TextBasedChannel,user:discord.User},workers:"opendiscord:error-not-in-guild"}, - "opendiscord:error-channel-rename":{source:"slash"|"text"|"button"|"dropdown"|"modal"|"ticket-pin"|"ticket-unpin"|"ticket-rename"|"ticket-move"|"ticket-priority"|"ticket-transfer"|"other",params:{guild:discord.Guild|null,channel:discord.TextBasedChannel,user:discord.User,originalName:string,newName:string},workers:"opendiscord:error-channel-rename"}, - "opendiscord:error-ticket-busy":{source:"slash"|"text"|"button"|"dropdown"|"modal"|"other",params:{guild:discord.Guild|null,channel:discord.TextBasedChannel,user:discord.User},workers:"opendiscord:error-ticket-busy"}, - - "opendiscord:help-menu":{source:"text"|"slash"|"button"|"other",params:{mode:"slash"|"text",page:number},workers:"opendiscord:help-menu"}, - - "opendiscord:stats-global":{source:"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User},workers:"opendiscord:stats-global"}, - "opendiscord:stats-ticket":{source:"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,scopeData:ODTicket},workers:"opendiscord:stats-ticket"}, - "opendiscord:stats-user":{source:"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,scopeData:discord.User},workers:"opendiscord:stats-user"|"opendiscord:easter-egg"}, - "opendiscord:stats-reset":{source:"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,reason:string|null},workers:"opendiscord:stats-reset"}, - "opendiscord:stats-ticket-unknown":{source:"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,id:string},workers:"opendiscord:stats-ticket-unknown"}, - - "opendiscord:panel":{source:"slash"|"text"|"auto-update"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,panel:ODPanel},workers:"opendiscord:panel"}, - "opendiscord:ticket-created":{source:"panel-button"|"panel-dropdown"|"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,ticket:ODTicket},workers:"opendiscord:ticket-created"}, - "opendiscord:ticket-created-dm":{source:"panel-button"|"panel-dropdown"|"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,ticket:ODTicket},workers:"opendiscord:ticket-created-dm"}, - "opendiscord:ticket-created-logs":{source:"panel-button"|"panel-dropdown"|"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,ticket:ODTicket},workers:"opendiscord:ticket-created-logs"}, - "opendiscord:ticket-message":{source:"panel-button"|"panel-dropdown"|"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,ticket:ODTicket},workers:"opendiscord:ticket-message"}, - "opendiscord:close-message":{source:"slash"|"text"|"ticket-message"|"reopen-message"|"autoclose"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,ticket:ODTicket,reason:string|null},workers:"opendiscord:close-message"}, - "opendiscord:reopen-message":{source:"slash"|"text"|"ticket-message"|"close-message"|"autoclose-message"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,ticket:ODTicket,reason:string|null},workers:"opendiscord:reopen-message"}, - "opendiscord:delete-message":{source:"slash"|"text"|"ticket-message"|"reopen-message"|"close-message"|"autoclose-message"|"autodelete"|"clear"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,ticket:ODTicket,reason:string|null},workers:"opendiscord:delete-message"}, - "opendiscord:claim-message":{source:"slash"|"text"|"ticket-message"|"unclaim-message"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,ticket:ODTicket,reason:string|null},workers:"opendiscord:claim-message"}, - "opendiscord:unclaim-message":{source:"slash"|"text"|"ticket-message"|"claim-message"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,ticket:ODTicket,reason:string|null},workers:"opendiscord:unclaim-message"}, - "opendiscord:pin-message":{source:"slash"|"text"|"ticket-message"|"unpin-message"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,ticket:ODTicket,reason:string|null},workers:"opendiscord:pin-message"}, - "opendiscord:unpin-message":{source:"slash"|"text"|"ticket-message"|"pin-message"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,ticket:ODTicket,reason:string|null},workers:"opendiscord:unpin-message"}, - "opendiscord:rename-message":{source:"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,ticket:ODTicket,reason:string|null,data:string},workers:"opendiscord:rename-message"}, - "opendiscord:move-message":{source:"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,ticket:ODTicket,reason:string|null,data:ODTicketOption},workers:"opendiscord:move-message"}, - "opendiscord:add-message":{source:"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,ticket:ODTicket,reason:string|null,data:discord.User},workers:"opendiscord:add-message"}, - "opendiscord:remove-message":{source:"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,ticket:ODTicket,reason:string|null,data:discord.User},workers:"opendiscord:remove-message"}, - "opendiscord:ticket-action-dm":{source:"slash"|"text"|"ticket-message"|"close-message"|"reopen-message"|"delete-message"|"claim-message"|"unclaim-message"|"pin-message"|"unpin-message"|"autoclose-message"|"autoclose"|"autodelete"|"clear"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,mode:"close"|"reopen"|"delete"|"claim"|"unclaim"|"pin"|"unpin"|"rename"|"move"|"add"|"remove",ticket:ODTicket,reason:string|null,additionalData:null|string|discord.User|ODTicketOption},workers:"opendiscord:ticket-action-dm"}, - "opendiscord:ticket-action-logs":{source:"slash"|"text"|"ticket-message"|"close-message"|"reopen-message"|"delete-message"|"claim-message"|"unclaim-message"|"pin-message"|"unpin-message"|"autoclose-message"|"autoclose"|"autodelete"|"clear"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,mode:"close"|"reopen"|"delete"|"claim"|"unclaim"|"pin"|"unpin"|"rename"|"move"|"add"|"remove",ticket:ODTicket,reason:string|null,additionalData:null|string|discord.User|ODTicketOption},workers:"opendiscord:ticket-action-logs"}, - - "opendiscord:blacklist-view":{source:"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User},workers:"opendiscord:blacklist-view"}, - "opendiscord:blacklist-get":{source:"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,data:discord.User},workers:"opendiscord:blacklist-get"}, - "opendiscord:blacklist-add":{source:"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,data:discord.User,reason:string|null},workers:"opendiscord:blacklist-add"}, - "opendiscord:blacklist-remove":{source:"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,data:discord.User,reason:string|null},workers:"opendiscord:blacklist-remove"} - "opendiscord:blacklist-dm":{source:"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,mode:"add"|"remove",data:discord.User,reason:string|null},workers:"opendiscord:blacklist-dm"}, - "opendiscord:blacklist-logs":{source:"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,mode:"add"|"remove",data:discord.User,reason:string|null},workers:"opendiscord:blacklist-logs"}, - - "opendiscord:transcript-text-ready":{source:"channel"|"creator-dm"|"participant-dm"|"active-admin-dm"|"every-admin-dm"|"other",params:{guild:discord.Guild,channel:discord.GuildTextBasedChannel,user:discord.User,ticket:ODTicket,compiler:ODTranscriptCompiler<{contents:string},null>,result:ODTranscriptCompilerCompileResult<{contents:string}>},workers:"opendiscord:transcript-text-ready"}, - "opendiscord:transcript-html-ready":{source:"channel"|"creator-dm"|"participant-dm"|"active-admin-dm"|"every-admin-dm"|"other",params:{guild:discord.Guild,channel:discord.GuildTextBasedChannel,user:discord.User,ticket:ODTicket,compiler:ODTranscriptCompiler<{url:string,availableUntil:Date},{auth:string}>,result:ODTranscriptCompilerCompileResult<{url:string,availableUntil:Date}>},workers:"opendiscord:transcript-html-ready"}, - "opendiscord:transcript-html-progress":{source:"channel"|"other",params:{guild:discord.Guild,channel:discord.GuildTextBasedChannel,user:discord.User,ticket:ODTicket,compiler:ODTranscriptCompiler<{url:string,availableUntil:Date},{auth:string}>,remaining:number},workers:"opendiscord:transcript-html-progress"}, - "opendiscord:transcript-error":{source:"slash"|"text"|"ticket-message"|"reopen-message"|"close-message"|"autoclose-message"|"autodelete"|"clear"|"other",params:{guild:discord.Guild,channel:discord.GuildTextBasedChannel,user:discord.User,ticket:ODTicket,compiler:ODTranscriptCompiler,reason:string|null},workers:"opendiscord:transcript-error"}, - - "opendiscord:reaction-role":{source:"panel-button"|"other",params:{guild:discord.Guild,user:discord.User,role:ODRole,result:ODRoleUpdateResult[]},workers:"opendiscord:reaction-role"}, - "opendiscord:reaction-role-dm":{source:"panel-button"|"other",params:{guild:discord.Guild,user:discord.User,role:ODRole,result:ODRoleUpdateResult[]},workers:"opendiscord:reaction-role-dm"}, - "opendiscord:reaction-role-logs":{source:"panel-button"|"other",params:{guild:discord.Guild,user:discord.User,role:ODRole,result:ODRoleUpdateResult[]},workers:"opendiscord:reaction-role-logs"}, - - "opendiscord:clear-verify-message":{source:"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.GuildTextBasedChannel,user:discord.User,filter:ODTicketClearFilter,list:string[]},workers:"opendiscord:clear-verify-message"}, - "opendiscord:clear-message":{source:"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.GuildTextBasedChannel,user:discord.User,filter:ODTicketClearFilter,list:string[]},workers:"opendiscord:clear-message"}, - "opendiscord:clear-logs":{source:"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.GuildTextBasedChannel,user:discord.User,filter:ODTicketClearFilter,list:string[]},workers:"opendiscord:clear-logs"}, - - "opendiscord:autoclose-message":{source:"timeout"|"leave"|"other",params:{guild:discord.Guild,channel:discord.GuildTextBasedChannel,user:discord.User,ticket:ODTicket},workers:"opendiscord:autoclose-message"}, - "opendiscord:autodelete-message":{source:"timeout"|"leave"|"other",params:{guild:discord.Guild,channel:discord.GuildTextBasedChannel,user:discord.User,ticket:ODTicket},workers:"opendiscord:autodelete-message"}, - "opendiscord:autoclose-enable":{source:"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.GuildTextBasedChannel,user:discord.User,ticket:ODTicket,time:number,reason:string|null},workers:"opendiscord:autoclose-enable"}, - "opendiscord:autodelete-enable":{source:"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.GuildTextBasedChannel,user:discord.User,ticket:ODTicket,time:number,reason:string|null},workers:"opendiscord:autodelete-enable"}, - "opendiscord:autoclose-disable":{source:"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.GuildTextBasedChannel,user:discord.User,ticket:ODTicket,reason:string|null},workers:"opendiscord:autoclose-disable"}, - "opendiscord:autodelete-disable":{source:"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.GuildTextBasedChannel,user:discord.User,ticket:ODTicket,reason:string|null},workers:"opendiscord:autodelete-disable"}, - - "opendiscord:topic-set":{source:"slash"|"text"|"ticket-action"|"other",params:{guild:discord.Guild,channel:discord.GuildTextBasedChannel,user:discord.User,ticket:ODTicket,topic:string},workers:"opendiscord:topic-set"}, - "opendiscord:priority-set":{source:"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.GuildTextBasedChannel,user:discord.User,ticket:ODTicket,priority:ODPriorityLevel,reason:string|null},workers:"opendiscord:priority-set"}, - "opendiscord:priority-get":{source:"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.GuildTextBasedChannel,user:discord.User,ticket:ODTicket,priority:ODPriorityLevel},workers:"opendiscord:priority-get"}, - "opendiscord:transfer-message":{source:"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.GuildTextBasedChannel,user:discord.User,ticket:ODTicket,oldCreator:discord.User,newCreator:discord.User,reason:string|null},workers:"opendiscord:transfer-message"}, -} - -/**## ODEmbedManager_Default `default_class` - * This is a special class that adds type definitions & typescript to the ODEmbedManager class. - * It doesn't add any extra features! - * - * This default class is made for the global variable `opendiscord.builders.embeds`! - */ -export class ODEmbedManager_Default extends ODEmbedManager { - get(id:EmbedId): ODEmbed_Default - get(id:ODValidId): ODEmbed|null - - get(id:ODValidId): ODEmbed|null { - return super.get(id) - } - - remove(id:EmbedId): ODEmbed_Default - remove(id:ODValidId): ODEmbed|null - - remove(id:ODValidId): ODEmbed|null { - return super.remove(id) - } - - exists(id:keyof ODEmbedManagerIds_Default): boolean - exists(id:ODValidId): boolean - - exists(id:ODValidId): boolean { - return super.exists(id) - } - - getSafe(id:EmbedId): ODEmbed_Default - getSafe(id:ODValidId): ODEmbed - - getSafe(id:ODValidId): ODEmbed { - return super.getSafe(id) - } -} - -/**## ODEmbed_Default `default_class` - * This is a special class that adds type definitions & typescript to the ODEmbed class. - * It doesn't add any extra features! - * - * This default class is made for the default `ODEmbed`'s! - */ -export class ODEmbed_Default extends ODEmbed { - declare workers: ODWorkerManager_Default -} - -/**## ODMessageManagerIds_Default `interface` - * This interface is a list of ids available in the `ODMessageManager_Default` class. - * It's used to generate typescript declarations for this class. - */ -export interface ODMessageManagerIds_Default { - "opendiscord:verifybar-ticket-message":{source:"verifybar",params:{guild:discord.Guild|null,channel:discord.TextBasedChannel,user:discord.User,verifybar:ODVerifyBar,originalMessage:discord.Message},workers:"opendiscord:verifybar-ticket-message"}, - "opendiscord:verifybar-close-message":{source:"verifybar",params:{guild:discord.Guild|null,channel:discord.TextBasedChannel,user:discord.User,verifybar:ODVerifyBar,originalMessage:discord.Message},workers:"opendiscord:verifybar-close-message"}, - "opendiscord:verifybar-reopen-message":{source:"verifybar",params:{guild:discord.Guild|null,channel:discord.TextBasedChannel,user:discord.User,verifybar:ODVerifyBar,originalMessage:discord.Message},workers:"opendiscord:verifybar-reopen-message"}, - "opendiscord:verifybar-claim-message":{source:"verifybar",params:{guild:discord.Guild|null,channel:discord.TextBasedChannel,user:discord.User,verifybar:ODVerifyBar,originalMessage:discord.Message},workers:"opendiscord:verifybar-claim-message"}, - "opendiscord:verifybar-unclaim-message":{source:"verifybar",params:{guild:discord.Guild|null,channel:discord.TextBasedChannel,user:discord.User,verifybar:ODVerifyBar,originalMessage:discord.Message},workers:"opendiscord:verifybar-unclaim-message"}, - "opendiscord:verifybar-pin-message":{source:"verifybar",params:{guild:discord.Guild|null,channel:discord.TextBasedChannel,user:discord.User,verifybar:ODVerifyBar,originalMessage:discord.Message},workers:"opendiscord:verifybar-pin-message"}, - "opendiscord:verifybar-unpin-message":{source:"verifybar",params:{guild:discord.Guild|null,channel:discord.TextBasedChannel,user:discord.User,verifybar:ODVerifyBar,originalMessage:discord.Message},workers:"opendiscord:verifybar-unpin-message"} - "opendiscord:verifybar-autoclose-message":{source:"verifybar",params:{guild:discord.Guild|null,channel:discord.TextBasedChannel,user:discord.User,verifybar:ODVerifyBar,originalMessage:discord.Message},workers:"opendiscord:verifybar-autoclose-message"} - - "opendiscord:error":{source:"slash"|"text"|"button"|"dropdown"|"modal"|"other",params:{guild:discord.Guild|null,channel:discord.TextBasedChannel,user:discord.User,error:string,layout:"simple"|"advanced",customTitle?:string},workers:"opendiscord:error"}, - "opendiscord:error-option-missing":{source:"slash"|"text"|"other",params:{guild:discord.Guild|null,channel:discord.TextBasedChannel,user:discord.User,error:ODTextCommandErrorMissingOption},workers:"opendiscord:error-option-missing"}, - "opendiscord:error-option-invalid":{source:"slash"|"text"|"other",params:{guild:discord.Guild|null,channel:discord.TextBasedChannel,user:discord.User,error:ODTextCommandErrorInvalidOption},workers:"opendiscord:error-option-invalid"}, - "opendiscord:error-unknown-command":{source:"slash"|"text"|"other",params:{guild:discord.Guild|null,channel:discord.TextBasedChannel,user:discord.User,error:ODTextCommandErrorUnknownCommand},workers:"opendiscord:error-unknown-command"}, - "opendiscord:error-no-permissions":{source:"slash"|"text"|"button"|"dropdown"|"modal"|"other",params:{guild:discord.Guild|null,channel:discord.TextBasedChannel,user:discord.User,permissions:ODPermissionEmbedType[]},workers:"opendiscord:error-no-permissions"}, - "opendiscord:error-no-permissions-cooldown":{source:"slash"|"text"|"button"|"dropdown"|"modal"|"other",params:{guild:discord.Guild|null,channel:discord.TextBasedChannel,user:discord.User,until?:Date},workers:"opendiscord:error-no-permissions-cooldown"}, - "opendiscord:error-no-permissions-blacklisted":{source:"slash"|"text"|"button"|"dropdown"|"modal"|"other",params:{guild:discord.Guild|null,channel:discord.TextBasedChannel,user:discord.User},workers:"opendiscord:error-no-permissions-blacklisted"}, - "opendiscord:error-no-permissions-limits":{source:"slash"|"text"|"button"|"dropdown"|"modal"|"other",params:{guild:discord.Guild|null,channel:discord.TextBasedChannel,user:discord.User,limit:"global"|"global-user"|"option"|"option-user"},workers:"opendiscord:error-no-permissions-limits"}, - "opendiscord:error-responder-timeout":{source:"slash"|"text"|"button"|"dropdown"|"modal"|"other",params:{guild:discord.Guild|null,channel:discord.TextBasedChannel,user:discord.User},workers:"opendiscord:error-responder-timeout"}, - "opendiscord:error-ticket-unknown":{source:"slash"|"text"|"button"|"dropdown"|"modal"|"other",params:{guild:discord.Guild|null,channel:discord.TextBasedChannel,user:discord.User},workers:"opendiscord:error-ticket-unknown"}, - "opendiscord:error-ticket-deprecated":{source:"slash"|"text"|"button"|"dropdown"|"modal"|"other",params:{guild:discord.Guild|null,channel:discord.TextBasedChannel,user:discord.User},workers:"opendiscord:error-ticket-deprecated"}, - "opendiscord:error-option-unknown":{source:"slash"|"text"|"button"|"dropdown"|"modal"|"other",params:{guild:discord.Guild|null,channel:discord.TextBasedChannel,user:discord.User},workers:"opendiscord:error-option-unknown"}, - "opendiscord:error-panel-unknown":{source:"slash"|"text"|"button"|"dropdown"|"modal"|"other",params:{guild:discord.Guild|null,channel:discord.TextBasedChannel,user:discord.User},workers:"opendiscord:error-panel-unknown"}, - "opendiscord:error-not-in-guild":{source:"slash"|"text"|"button"|"dropdown"|"modal"|"other",params:{channel:discord.TextBasedChannel,user:discord.User},workers:"opendiscord:error-not-in-guild"}, - "opendiscord:error-channel-rename":{source:"slash"|"text"|"button"|"dropdown"|"modal"|"ticket-pin"|"ticket-unpin"|"ticket-rename"|"ticket-move"|"ticket-priority"|"ticket-transfer"|"other",params:{guild:discord.Guild|null,channel:discord.TextBasedChannel,user:discord.User,originalName:string,newName:string},workers:"opendiscord:error-channel-rename"}, - "opendiscord:error-ticket-busy":{source:"slash"|"text"|"button"|"dropdown"|"modal"|"other",params:{guild:discord.Guild|null,channel:discord.TextBasedChannel,user:discord.User},workers:"opendiscord:error-ticket-busy"}, - - "opendiscord:help-menu":{source:"slash"|"text"|"button"|"other",params:{mode:"slash"|"text",page:number},workers:"opendiscord:help-menu"}, - - "opendiscord:stats-global":{source:"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User},workers:"opendiscord:stats-global"}, - "opendiscord:stats-ticket":{source:"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,scopeData:ODTicket},workers:"opendiscord:stats-ticket"}, - "opendiscord:stats-user":{source:"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,scopeData:discord.User},workers:"opendiscord:stats-user"|"opendiscord:easter-egg"}, - "opendiscord:stats-reset":{source:"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,reason:string|null},workers:"opendiscord:stats-reset"}, - "opendiscord:stats-ticket-unknown":{source:"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,id:string},workers:"opendiscord:stats-ticket-unknown"}, - - "opendiscord:panel":{source:"slash"|"text"|"auto-update"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,panel:ODPanel},workers:"opendiscord:panel-layout"|"opendiscord:panel-components"}, - "opendiscord:panel-ready":{source:"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,panel:ODPanel},workers:"opendiscord:panel-ready"}, - - "opendiscord:ticket-created":{source:"panel-button"|"panel-dropdown"|"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,ticket:ODTicket},workers:"opendiscord:ticket-created"}, - "opendiscord:ticket-created-dm":{source:"panel-button"|"panel-dropdown"|"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,ticket:ODTicket},workers:"opendiscord:ticket-created-dm"}, - "opendiscord:ticket-created-logs":{source:"panel-button"|"panel-dropdown"|"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,ticket:ODTicket},workers:"opendiscord:ticket-created-logs"}, - "opendiscord:ticket-message":{source:"panel-button"|"panel-dropdown"|"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,ticket:ODTicket},workers:"opendiscord:ticket-message-layout"|"opendiscord:ticket-message-components"|"opendiscord:ticket-message-disable-components"}, - "opendiscord:close-message":{source:"slash"|"text"|"ticket-message"|"reopen-message"|"autoclose"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,ticket:ODTicket,reason:string|null},workers:"opendiscord:close-message"}, - "opendiscord:reopen-message":{source:"slash"|"text"|"ticket-message"|"close-message"|"autoclose-message"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,ticket:ODTicket,reason:string|null},workers:"opendiscord:reopen-message"}, - "opendiscord:delete-message":{source:"slash"|"text"|"ticket-message"|"reopen-message"|"close-message"|"autoclose-message"|"autodelete"|"clear"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,ticket:ODTicket,reason:string|null},workers:"opendiscord:delete-message"}, - "opendiscord:claim-message":{source:"slash"|"text"|"ticket-message"|"unclaim-message"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,ticket:ODTicket,reason:string|null},workers:"opendiscord:claim-message"}, - "opendiscord:unclaim-message":{source:"slash"|"text"|"ticket-message"|"claim-message"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,ticket:ODTicket,reason:string|null},workers:"opendiscord:unclaim-message"}, - "opendiscord:pin-message":{source:"slash"|"text"|"ticket-message"|"unpin-message"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,ticket:ODTicket,reason:string|null},workers:"opendiscord:pin-message"}, - "opendiscord:unpin-message":{source:"slash"|"text"|"ticket-message"|"pin-message"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,ticket:ODTicket,reason:string|null},workers:"opendiscord:unpin-message"}, - "opendiscord:rename-message":{source:"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,ticket:ODTicket,reason:string|null,data:string},workers:"opendiscord:rename-message"}, - "opendiscord:move-message":{source:"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,ticket:ODTicket,reason:string|null,data:ODTicketOption},workers:"opendiscord:move-message"}, - "opendiscord:add-message":{source:"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,ticket:ODTicket,reason:string|null,data:discord.User},workers:"opendiscord:add-message"}, - "opendiscord:remove-message":{source:"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,ticket:ODTicket,reason:string|null,data:discord.User},workers:"opendiscord:remove-message"}, - "opendiscord:ticket-action-dm":{source:"slash"|"text"|"ticket-message"|"close-message"|"reopen-message"|"delete-message"|"claim-message"|"unclaim-message"|"pin-message"|"unpin-message"|"autoclose-message"|"autoclose"|"autodelete"|"clear"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,mode:"close"|"reopen"|"delete"|"claim"|"unclaim"|"pin"|"unpin"|"rename"|"move"|"add"|"remove",ticket:ODTicket,reason:string|null,additionalData:null|string|discord.User|ODTicketOption},workers:"opendiscord:ticket-action-dm"}, - "opendiscord:ticket-action-logs":{source:"slash"|"text"|"ticket-message"|"close-message"|"reopen-message"|"delete-message"|"claim-message"|"unclaim-message"|"pin-message"|"unpin-message"|"autoclose-message"|"autoclose"|"autodelete"|"clear"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,mode:"close"|"reopen"|"delete"|"claim"|"unclaim"|"pin"|"unpin"|"rename"|"move"|"add"|"remove",ticket:ODTicket,reason:string|null,additionalData:null|string|discord.User|ODTicketOption},workers:"opendiscord:ticket-action-logs"}, - - "opendiscord:blacklist-view":{source:"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User},workers:"opendiscord:blacklist-view"}, - "opendiscord:blacklist-get":{source:"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,data:discord.User},workers:"opendiscord:blacklist-get"}, - "opendiscord:blacklist-add":{source:"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,data:discord.User,reason:string|null},workers:"opendiscord:blacklist-add"}, - "opendiscord:blacklist-remove":{source:"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,data:discord.User,reason:string|null},workers:"opendiscord:blacklist-remove"}, - "opendiscord:blacklist-dm":{source:"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,mode:"add"|"remove",data:discord.User,reason:string|null},workers:"opendiscord:blacklist-dm"}, - "opendiscord:blacklist-logs":{source:"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,mode:"add"|"remove",data:discord.User,reason:string|null},workers:"opendiscord:blacklist-logs"}, - - "opendiscord:transcript-text-ready":{source:"channel"|"creator-dm"|"participant-dm"|"active-admin-dm"|"every-admin-dm"|"other",params:{guild:discord.Guild,channel:discord.GuildTextBasedChannel,user:discord.User,ticket:ODTicket,compiler:ODTranscriptCompiler<{contents:string},null>,result:ODTranscriptCompilerCompileResult<{contents:string}>},workers:"opendiscord:transcript-text-ready"}, - "opendiscord:transcript-html-ready":{source:"channel"|"creator-dm"|"participant-dm"|"active-admin-dm"|"every-admin-dm"|"other",params:{guild:discord.Guild,channel:discord.GuildTextBasedChannel,user:discord.User,ticket:ODTicket,compiler:ODTranscriptCompiler<{url:string,availableUntil:Date},{auth:string}>,result:ODTranscriptCompilerCompileResult<{url:string,availableUntil:Date}>},workers:"opendiscord:transcript-html-ready"}, - "opendiscord:transcript-html-progress":{source:"channel"|"other",params:{guild:discord.Guild,channel:discord.GuildTextBasedChannel,user:discord.User,ticket:ODTicket,compiler:ODTranscriptCompiler<{url:string,availableUntil:Date},{auth:string}>,remaining:number},workers:"opendiscord:transcript-html-progress"}, - "opendiscord:transcript-error":{source:"slash"|"text"|"ticket-message"|"reopen-message"|"close-message"|"autoclose-message"|"autodelete"|"clear"|"other",params:{guild:discord.Guild,channel:discord.GuildTextBasedChannel,user:discord.User,ticket:ODTicket,compiler:ODTranscriptCompiler,reason:string|null},workers:"opendiscord:transcript-error"}, - - "opendiscord:reaction-role":{source:"panel-button"|"other",params:{guild:discord.Guild,user:discord.User,role:ODRole,result:ODRoleUpdateResult[]},workers:"opendiscord:reaction-role"}, - "opendiscord:reaction-role-dm":{source:"panel-button"|"other",params:{guild:discord.Guild,user:discord.User,role:ODRole,result:ODRoleUpdateResult[]},workers:"opendiscord:reaction-role-dm"}, - "opendiscord:reaction-role-logs":{source:"panel-button"|"other",params:{guild:discord.Guild,user:discord.User,role:ODRole,result:ODRoleUpdateResult[]},workers:"opendiscord:reaction-role-logs"}, - - "opendiscord:clear-verify-message":{source:"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.GuildTextBasedChannel,user:discord.User,filter:ODTicketClearFilter,list:string[]},workers:"opendiscord:clear-verify-message"}, - "opendiscord:clear-message":{source:"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.GuildTextBasedChannel,user:discord.User,filter:ODTicketClearFilter,list:string[]},workers:"opendiscord:clear-message"}, - "opendiscord:clear-logs":{source:"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.GuildTextBasedChannel,user:discord.User,filter:ODTicketClearFilter,list:string[]},workers:"opendiscord:clear-logs"}, - - "opendiscord:autoclose-message":{source:"timeout"|"leave"|"other",params:{guild:discord.Guild,channel:discord.GuildTextBasedChannel,user:discord.User,ticket:ODTicket},workers:"opendiscord:autoclose-message"}, - "opendiscord:autodelete-message":{source:"timeout"|"leave"|"other",params:{guild:discord.Guild,channel:discord.GuildTextBasedChannel,user:discord.User,ticket:ODTicket},workers:"opendiscord:autodelete-message"}, - "opendiscord:autoclose-enable":{source:"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.GuildTextBasedChannel,user:discord.User,ticket:ODTicket,time:number,reason:string|null},workers:"opendiscord:autoclose-enable"}, - "opendiscord:autodelete-enable":{source:"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.GuildTextBasedChannel,user:discord.User,ticket:ODTicket,time:number,reason:string|null},workers:"opendiscord:autodelete-enable"}, - "opendiscord:autoclose-disable":{source:"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.GuildTextBasedChannel,user:discord.User,ticket:ODTicket,reason:string|null},workers:"opendiscord:autoclose-disable"}, - "opendiscord:autodelete-disable":{source:"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.GuildTextBasedChannel,user:discord.User,ticket:ODTicket,reason:string|null},workers:"opendiscord:autodelete-disable"}, - - "opendiscord:topic-set":{source:"slash"|"text"|"ticket-action"|"other",params:{guild:discord.Guild,channel:discord.GuildTextBasedChannel,user:discord.User,ticket:ODTicket,topic:string},workers:"opendiscord:topic-set"}, - "opendiscord:priority-set":{source:"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.GuildTextBasedChannel,user:discord.User,ticket:ODTicket,priority:ODPriorityLevel,reason:string|null},workers:"opendiscord:priority-set"}, - "opendiscord:priority-get":{source:"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.GuildTextBasedChannel,user:discord.User,ticket:ODTicket,priority:ODPriorityLevel},workers:"opendiscord:priority-get"}, - "opendiscord:transfer-message":{source:"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.GuildTextBasedChannel,user:discord.User,ticket:ODTicket,oldCreator:discord.User,newCreator:discord.User,reason:string|null},workers:"opendiscord:transfer-message"}, -} - -/**## ODMessageManager_Default `default_class` - * This is a special class that adds type definitions & typescript to the ODMessageManager class. - * It doesn't add any extra features! - * - * This default class is made for the global variable `opendiscord.builders.messages`! - */ -export class ODMessageManager_Default extends ODMessageManager { - get(id:MessageId): ODMessage_Default - get(id:ODValidId): ODMessage|null - - get(id:ODValidId): ODMessage|null { - return super.get(id) - } - - remove(id:MessageId): ODMessage_Default - remove(id:ODValidId): ODMessage|null - - remove(id:ODValidId): ODMessage|null { - return super.remove(id) - } - - exists(id:keyof ODMessageManagerIds_Default): boolean - exists(id:ODValidId): boolean - - exists(id:ODValidId): boolean { - return super.exists(id) - } - - getSafe(id:MessageId): ODMessage_Default - getSafe(id:ODValidId): ODMessage - - getSafe(id:ODValidId): ODMessage { - return super.getSafe(id) - } -} - -/**## ODMessage_Default `default_class` - * This is a special class that adds type definitions & typescript to the ODMessage class. - * It doesn't add any extra features! - * - * This default class is made for the default `ODMessage`'s! - */ -export class ODMessage_Default extends ODMessage { - declare workers: ODWorkerManager_Default -} - -/**## ODModalManagerIds_Default `interface` - * This interface is a list of ids available in the `ODModalManager_Default` class. - * It's used to generate typescript declarations for this class. - */ -export interface ODModalManagerIds_Default { - "opendiscord:ticket-questions":{source:"panel-button"|"panel-dropdown"|"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,option:ODTicketOption},workers:"opendiscord:ticket-questions"} - "opendiscord:close-ticket-reason":{source:"ticket-message"|"reopen-message"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,ticket:ODTicket},workers:"opendiscord:close-ticket-reason"} - "opendiscord:reopen-ticket-reason":{source:"ticket-message"|"close-message"|"autoclose-message"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,ticket:ODTicket},workers:"opendiscord:reopen-ticket-reason"} - "opendiscord:delete-ticket-reason":{source:"ticket-message"|"reopen-message"|"close-message"|"autoclose-message"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,ticket:ODTicket},workers:"opendiscord:delete-ticket-reason"} - "opendiscord:claim-ticket-reason":{source:"ticket-message"|"unclaim-message"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,ticket:ODTicket},workers:"opendiscord:claim-ticket-reason"} - "opendiscord:unclaim-ticket-reason":{source:"ticket-message"|"claim-message"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,ticket:ODTicket},workers:"opendiscord:unclaim-ticket-reason"} - "opendiscord:pin-ticket-reason":{source:"ticket-message"|"unpin-message"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,ticket:ODTicket},workers:"opendiscord:pin-ticket-reason"} - "opendiscord:unpin-ticket-reason":{source:"ticket-message"|"pin-message"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,ticket:ODTicket},workers:"opendiscord:unpin-ticket-reason"} -} - -/**## ODModalManager_Default `default_class` - * This is a special class that adds type definitions & typescript to the ODModalManager class. - * It doesn't add any extra features! - * - * This default class is made for the global variable `opendiscord.builders.modals`! - */ -export class ODModalManager_Default extends ODModalManager { - get(id:ModalId): ODModal_Default - get(id:ODValidId): ODModal|null - - get(id:ODValidId): ODModal|null { - return super.get(id) - } - - remove(id:ModalId): ODModal_Default - remove(id:ODValidId): ODModal|null - - remove(id:ODValidId): ODModal|null { - return super.remove(id) - } - - exists(id:keyof ODModalManagerIds_Default): boolean - exists(id:ODValidId): boolean - - exists(id:ODValidId): boolean { - return super.exists(id) - } - - getSafe(id:ModalId): ODModal_Default - getSafe(id:ODValidId): ODModal - - getSafe(id:ODValidId): ODModal { - return super.getSafe(id) - } -} - -/**## ODModal_Default `default_class` - * This is a special class that adds type definitions & typescript to the ODModal class. - * It doesn't add any extra features! - * - * This default class is made for the default `ODModal`'s! - */ -export class ODModal_Default extends ODModal { - declare workers: ODWorkerManager_Default -} \ No newline at end of file diff --git a/src/core/api/defaults/checker.ts b/src/core/api/defaults/checker.ts deleted file mode 100644 index 9cc654c..0000000 --- a/src/core/api/defaults/checker.ts +++ /dev/null @@ -1,385 +0,0 @@ -/////////////////////////////////////// -//DEFAULT CONFIG CHECKER MODULE -/////////////////////////////////////// -import { ODLanguageManager_Default } from "../api" -import { ODValidId } from "../modules/base" -import { ODCheckerManager, ODChecker, ODCheckerTranslationRegister, ODCheckerRenderer, ODCheckerFunctionManager, ODCheckerResult, ODCheckerFunction } from "../modules/checker" -import ansis from "ansis" - -/**## ODCheckerManagerIds_Default `interface` - * This interface is a list of ids available in the `ODCheckerManager_Default` class. - * It's used to generate typescript declarations for this class. - */ -export interface ODCheckerManagerIds_Default { - "opendiscord:general":ODChecker, - "opendiscord:questions":ODChecker, - "opendiscord:options":ODChecker, - "opendiscord:panels":ODChecker, - "opendiscord:transcripts":ODChecker -} - -/**## ODCheckerManager_Default `default_class` - * This is a special class that adds type definitions & typescript to the ODCheckerManager class. - * It doesn't add any extra features! - * - * This default class is made for the global variable `opendiscord.checkers`! - */ -export class ODCheckerManager_Default extends ODCheckerManager { - declare translation: ODCheckerTranslationRegister_Default - declare renderer: ODCheckerRenderer_Default - declare functions: ODCheckerFunctionManager_Default - - get(id:CheckerId): ODCheckerManagerIds_Default[CheckerId] - get(id:ODValidId): ODChecker|null - - get(id:ODValidId): ODChecker|null { - return super.get(id) - } - - remove(id:CheckerId): ODCheckerManagerIds_Default[CheckerId] - remove(id:ODValidId): ODChecker|null - - remove(id:ODValidId): ODChecker|null { - return super.remove(id) - } - - exists(id:keyof ODCheckerManagerIds_Default): boolean - exists(id:ODValidId): boolean - - exists(id:ODValidId): boolean { - return super.exists(id) - } -} - -/**## ODCheckerRenderer_Default `default_class` - * This is a special class that adds type definitions & features to the ODCheckerRenderer class. - * It contains the code that renders the default config checker. - * - * This default class is made for the global variable `opendiscord.checkers.renderer`! - */ -export class ODCheckerRenderer_Default extends ODCheckerRenderer { - extraHeaderText: string[] = [] - extraFooterText: string[] = [] - extraTopText: string[] = [] - extraBottomText: string[] = [] - - horizontalFiller: string = "=" - verticalFiller: string = "|" - descriptionSeparator: string = " => " - headerSeparator: string = " => " - footerTipPrefix: string = "=> " - - disableHeader: boolean = false - disableFooter: boolean = false - - getComponents(compact:boolean, renderEmpty:boolean, translation:ODCheckerTranslationRegister_Default, data:ODCheckerResult): string[] { - const tm = translation - const t = { - headerOpenticket:tm.get("other","opendiscord:header-openticket") ?? "OPEN TICKET", - headerConfigchecker:tm.get("other","opendiscord:header-configchecker") ?? "CONFIG CHECKER", - headerDescription:tm.get("other","opendiscord:header-description") ?? "check for errors in your config files!", - footerError:tm.get("other","opendiscord:footer-error") ?? "the bot won't start until all {0}'s are fixed!", - footerWarning:tm.get("other","opendiscord:footer-warning") ?? "it's recommended to fix all {0}'s before starting!", - footerSupport:tm.get("other","opendiscord:footer-support") ?? "SUPPORT: {0} - DOCS: {1}", - error:tm.get("other","opendiscord:type-error") ?? "[ERROR]", - warning:tm.get("other","opendiscord:type-warning") ?? "[WARNING]", - info:tm.get("other","opendiscord:type-info") ?? "[INFO]", - compactInfo:tm.get("other","opendiscord:compact-information") ?? "use {0} for more information!", - dataPath:tm.get("other","opendiscord:data-path") ?? "path", - dataDocs:tm.get("other","opendiscord:data-docs") ?? "docs", - dataMessage:tm.get("other","opendiscord:data-message") ?? "message" - } - const hasErrors = data.messages.filter((m) => m.type == "error").length > 0 - const hasWarnings = data.messages.filter((m) => m.type == "warning").length > 0 - const hasInfo = data.messages.filter((m) => m.type == "info").length > 0 - - if (!renderEmpty && !hasErrors && !hasWarnings && (!hasInfo || compact)) return [] - - const headerText = ansis.bold.hex("#f8ba00")(t.headerOpenticket)+" "+t.headerConfigchecker+this.headerSeparator+ansis.hex("#f8ba00")(t.headerDescription) - const footerErrorText = (hasErrors) ? this.footerTipPrefix+ansis.gray(tm.insertTranslationParams(t.footerError,[ansis.bold.red(t.error)])) : "" - const footerWarningText = (hasWarnings) ? this.footerTipPrefix+ansis.gray(tm.insertTranslationParams(t.footerWarning,[ansis.bold.yellow(t.warning)])) : "" - const footerSupportText = tm.insertTranslationParams(t.footerSupport,[ansis.green("https://discord.dj-dj.be"),ansis.green("https://otdocs.dj-dj.be")]) - const bottomCompactInfo = (compact) ? ansis.gray(tm.insertTranslationParams(t.compactInfo,[ansis.bold.green("npm start -- --checker")])) : "" - - const finalHeader = [headerText,...this.extraHeaderText] - const finalFooter = [footerErrorText,footerWarningText,footerSupportText,...this.extraFooterText] - const finalTop = [...this.extraTopText] - const finalBottom = [bottomCompactInfo,...this.extraBottomText] - const borderLength = this.#getLongestLength([...finalHeader,...finalFooter]) - - const finalComponents: string[] = [] - - //header - if (!this.disableHeader){ - finalHeader.forEach((text) => { - if (text.length < 1) return - finalComponents.push(this.#createBlockFromText(text,borderLength)) - }) - } - finalComponents.push(this.#getHorizontalDivider(borderLength+4)) - - //top - finalTop.forEach((text) => { - if (text.length < 1) return - finalComponents.push(this.verticalFiller+" "+text) - }) - finalComponents.push(this.verticalFiller) - - //messages - if (compact){ - //use compact messages - data.messages.forEach((msg,index) => { - //compact mode doesn't render info - if (msg.type == "info") return - - //check if translation available & use it if possible - const rawTranslation = tm.get("message",msg.messageId.value) - const translatedMessage = (rawTranslation) ? tm.insertTranslationParams(rawTranslation,msg.translationParams) : msg.message - - if (msg.type == "error") finalComponents.push(this.verticalFiller+" "+ansis.bold.red(`${t.error} ${translatedMessage}`)) - else if (msg.type == "warning") finalComponents.push(this.verticalFiller+" "+ansis.bold.yellow(`${t.warning} ${translatedMessage}`)) - - const pathSplitter = msg.path ? ":" : "" - finalComponents.push(this.verticalFiller+ansis.bold(this.descriptionSeparator)+ansis.cyan(`${ansis.magenta(msg.filepath+pathSplitter)} ${msg.path}`)) - if (index != data.messages.length-1) finalComponents.push(this.verticalFiller) - }) - }else{ - //use full messages - data.messages.forEach((msg,index) => { - //check if translation available & use it if possible - const rawTranslation = tm.get("message",msg.messageId.value) - const translatedMessage = (rawTranslation) ? tm.insertTranslationParams(rawTranslation,msg.translationParams) : msg.message - - if (msg.type == "error") finalComponents.push(this.verticalFiller+" "+ansis.bold.red(`${t.error} ${translatedMessage}`)) - else if (msg.type == "warning") finalComponents.push(this.verticalFiller+" "+ansis.bold.yellow(`${t.warning} ${translatedMessage}`)) - else if (msg.type == "info") finalComponents.push(this.verticalFiller+" "+ansis.bold.blue(`${t.info} ${translatedMessage}`)) - - const pathSplitter = msg.path ? ":" : "" - finalComponents.push(this.verticalFiller+" "+ansis.bold((t.dataPath)+this.descriptionSeparator)+ansis.cyan(`${ansis.magenta(msg.filepath+pathSplitter)} ${msg.path}`)) - if (msg.locationDocs) finalComponents.push(this.verticalFiller+" "+ansis.bold(t.dataDocs+this.descriptionSeparator)+ansis.italic.gray(msg.locationDocs)) - if (msg.messageDocs) finalComponents.push(this.verticalFiller+" "+ansis.bold(t.dataMessage+this.descriptionSeparator)+ansis.italic.gray(msg.messageDocs)) - if (index != data.messages.length-1) finalComponents.push(this.verticalFiller) - }) - } - - //bottom - finalComponents.push(this.verticalFiller) - finalBottom.forEach((text) => { - if (text.length < 1) return - finalComponents.push(this.verticalFiller+" "+text) - }) - - //footer - finalComponents.push(this.#getHorizontalDivider(borderLength+4)) - if (!this.disableFooter){ - finalFooter.forEach((text) => { - if (text.length < 1) return - finalComponents.push(this.#createBlockFromText(text,borderLength)) - }) - finalComponents.push(this.#getHorizontalDivider(borderLength+4)) - } - - //return all components - return finalComponents - } - /**Get the length of the longest string in the array. */ - #getLongestLength(texts:string[]): number { - return Math.max(...texts.map((t) => ansis.strip(t).length)) - } - /**Get a horizontal divider used between different parts of the config checker result. */ - #getHorizontalDivider(width:number): string { - if (width > 2) width = width-2 - else return this.verticalFiller+this.verticalFiller - let divider = this.verticalFiller + this.horizontalFiller.repeat(width) + this.verticalFiller - return divider - } - /**Create a block of text with a vertical divider on the left & right side. */ - #createBlockFromText(text:string,width:number): string { - if (width < 3) return this.verticalFiller+this.verticalFiller - let newWidth = width-ansis.strip(text).length+1 - let final = this.verticalFiller+" "+text+" ".repeat(newWidth)+this.verticalFiller - return final - } -} - -/**## ODCheckerTranslationRegisterOtherIds_Default `interface` - * This interface is a list of ids available in the `ODCheckerTranslationRegister_Default` class. - * It's used to generate typescript declarations for this class. - */ -export type ODCheckerTranslationRegisterOtherIds_Default = ( - "opendiscord:header-openticket"| - "opendiscord:header-configchecker"| - "opendiscord:header-description"| - "opendiscord:type-error"| - "opendiscord:type-warning"| - "opendiscord:type-info"| - "opendiscord:data-path"| - "opendiscord:data-docs"| - "opendiscord:data-message"| - "opendiscord:compact-information"| - "opendiscord:footer-error"| - "opendiscord:footer-warning"| - "opendiscord:footer-support" -) - -/**## ODCheckerTranslationRegisterMessageIds_Default `interface` - * This interface is a list of ids available in the `ODCheckerTranslationRegister_Default` class. - * It's used to generate typescript declarations for this class. - */ -export type ODCheckerTranslationRegisterMessageIds_Default = ( - "opendiscord:invalid-type"| - "opendiscord:property-missing"| - "opendiscord:property-optional"| - "opendiscord:object-disabled"| - "opendiscord:null-invalid"| - "opendiscord:switch-invalid-type"| - "opendiscord:object-switch-invalid-type"| - - "opendiscord:string-too-short"| - "opendiscord:string-too-long"| - "opendiscord:string-length-invalid"| - "opendiscord:string-starts-with"| - "opendiscord:string-ends-with"| - "opendiscord:string-contains"| - "opendiscord:string-inverted-contains"| - "opendiscord:string-choices"| - "opendiscord:string-lowercase"| - "opendiscord:string-uppercase"| - "opendiscord:string-special-characters"| - "opendiscord:string-no-spaces"| - "opendiscord:string-regex"| - "opendiscord:string-capital-word"| - "opendiscord:string-capital-sentence"| - "opendiscord:string-punctuation"| - - "opendiscord:number-nan"| - "opendiscord:number-too-short"| - "opendiscord:number-too-long"| - "opendiscord:number-length-invalid"| - "opendiscord:number-too-small"| - "opendiscord:number-too-large"| - "opendiscord:number-not-equal"| - "opendiscord:number-step"| - "opendiscord:number-step-offset"| - "opendiscord:number-starts-with"| - "opendiscord:number-ends-with"| - "opendiscord:number-contains"| - "opendiscord:number-inverted-contains"| - "opendiscord:number-choices"| - "opendiscord:number-float"| - "opendiscord:number-negative"| - "opendiscord:number-positive"| - "opendiscord:number-zero"| - - "opendiscord:boolean-true"| - "opendiscord:boolean-false"| - - "opendiscord:array-empty-disabled"| - "opendiscord:array-empty-required"| - "opendiscord:array-too-short"| - "opendiscord:array-too-long"| - "opendiscord:array-length-invalid"| - "opendiscord:array-invalid-types"| - "opendiscord:array-double"| - - "opendiscord:discord-invalid-id"| - "opendiscord:discord-invalid-id-options"| - "opendiscord:discord-invalid-token"| - "opendiscord:color-invalid"| - "opendiscord:emoji-too-short"| - "opendiscord:emoji-too-long"| - "opendiscord:emoji-custom"| - "opendiscord:emoji-invalid"| - "opendiscord:url-invalid"| - "opendiscord:url-invalid-http"| - "opendiscord:url-invalid-protocol"| - "opendiscord:url-invalid-hostname"| - "opendiscord:url-invalid-extension"| - "opendiscord:url-invalid-path"| - "opendiscord:id-not-unique"| - "opendiscord:id-non-existent"| - - "opendiscord:invalid-language"| - "opendiscord:invalid-button"| - "opendiscord:unused-option"| - "opendiscord:unused-question"| - "opendiscord:dropdown-option" -) - -/**## ODCheckerTranslationRegister_Default `default_class` - * This is a special class that adds type definitions & typescript to the ODCheckerTranslationRegister class. - * It doesn't add any extra features! - * - * This default class is made for the global variable `opendiscord.checkers.translation`! - */ -export class ODCheckerTranslationRegister_Default extends ODCheckerTranslationRegister { - get(type:"other", id:ODCheckerTranslationRegisterOtherIds_Default): string - get(type:"message", id:ODCheckerTranslationRegisterMessageIds_Default): string - get(type:"message"|"other", id:string): string|null - - get(type:"message"|"other", id:string): string|null { - return super.get(type,id) - } - - set(type:"other", id:ODCheckerTranslationRegisterOtherIds_Default, translation:string): boolean - set(type:"message", id:ODCheckerTranslationRegisterMessageIds_Default, translation:string): boolean - set(type:"message"|"other", id:string, translation:string): boolean - - set(type:"message"|"other", id:string, translation:string): boolean { - return super.set(type,id,translation) - } - - delete(type:"other", id:ODCheckerTranslationRegisterOtherIds_Default): boolean - delete(type:"message", id:ODCheckerTranslationRegisterMessageIds_Default): boolean - delete(type:"message"|"other", id:string): boolean - - delete(type:"message"|"other", id:string): boolean { - return super.delete(type,id) - } - - quickTranslate(manager:ODLanguageManager_Default, translationId:string, type:"other"|"message", id:ODCheckerTranslationRegisterOtherIds_Default|ODCheckerTranslationRegisterMessageIds_Default) - quickTranslate(manager:ODLanguageManager_Default, translationId:string, type:"other"|"message", id:string) - - quickTranslate(manager:ODLanguageManager_Default, translationId:string, type:"other"|"message", id:ODCheckerTranslationRegisterOtherIds_Default|ODCheckerTranslationRegisterMessageIds_Default|string){ - super.quickTranslate(manager,translationId,type,id) - } -} - -/**## ODCheckerFunctionManagerIds_Default `interface` - * This interface is a list of ids available in the `ODCheckerFunctionManager_Default` class. - * It's used to generate typescript declarations for this class. - */ -export interface ODCheckerFunctionManagerIds_Default { - "opendiscord:unused-options":ODCheckerFunction, - "opendiscord:unused-questions":ODCheckerFunction, - "opendiscord:dropdown-options":ODCheckerFunction -} - -/**## ODCheckerFunctionManager_Default `default_class` - * This is a special class that adds type definitions & typescript to the ODCheckerFunctionManager class. - * It doesn't add any extra features! - * - * This default class is made for the global variable `opendiscord.checkers.functions`! - */ -export class ODCheckerFunctionManager_Default extends ODCheckerFunctionManager { - get(id:CheckerFunctionId): ODCheckerFunctionManagerIds_Default[CheckerFunctionId] - get(id:ODValidId): ODCheckerFunction|null - - get(id:ODValidId): ODCheckerFunction|null { - return super.get(id) - } - - remove(id:CheckerFunctionId): ODCheckerFunctionManagerIds_Default[CheckerFunctionId] - remove(id:ODValidId): ODCheckerFunction|null - - remove(id:ODValidId): ODCheckerFunction|null { - return super.remove(id) - } - - exists(id:keyof ODCheckerFunctionManagerIds_Default): boolean - exists(id:ODValidId): boolean - - exists(id:ODValidId): boolean { - return super.exists(id) - } -} \ No newline at end of file diff --git a/src/core/api/defaults/client.ts b/src/core/api/defaults/client.ts deleted file mode 100644 index 7b5a540..0000000 --- a/src/core/api/defaults/client.ts +++ /dev/null @@ -1,207 +0,0 @@ -/////////////////////////////////////// -//DEFAULT CLIENT MODULE -/////////////////////////////////////// -import { ODValidId } from "../modules/base" -import { ODClientManager, ODSlashCommand, ODTextCommand, ODSlashCommandManager, ODTextCommandManager, ODSlashCommandInteractionCallback, ODTextCommandInteractionCallback, ODContextMenu, ODContextMenuManager, ODContextMenuInteractionCallback } from "../modules/client" - -/** (CONTRIBUTOR GUIDE) HOW TO ADD NEW COMMANDS? - * - Register the command in loadAllSlashCommands() & loadAllTextCommands() in (./src/data/framework/commandLoader.ts) - * - Add autocomplete for the command in OD(Slash/Text)CommandManagerIds_Default in (./src/core/api/defaults/client.ts) - * - Add the command to the help menu in (./src/data/framework/helpMenuLoader.ts) - * - If required, new config variables should be added (incl. logs, dm-logs & permissions). - * - Update the Open Ticket Documentation. - * - If the command contains complex logic or can be executed from a button/dropdown, it should be placed inside an `ODAction`. - * - Check all files, test the bot carefully & try a lot of different scenario's with different settings. - */ - -/**## ODClientManager_Default `default_class` - * This is a special class that adds type definitions & typescript to the ODClientManager class. - * It doesn't add any extra features! - * - * This default class is made for the global variable `opendiscord.client`! - */ -export class ODClientManager_Default extends ODClientManager { - declare slashCommands: ODSlashCommandManager_Default - declare textCommands: ODTextCommandManager_Default - declare contextMenus: ODContextMenuManager_Default -} - -/**## ODSlashCommandManagerIds_Default `interface` - * This interface is a list of ids available in the `ODSlashCommandManager_Default` class. - * It's used to generate typescript declarations for this class. - */ -export interface ODSlashCommandManagerIds_Default { - "opendiscord:help":ODSlashCommand, - "opendiscord:panel":ODSlashCommand, - "opendiscord:ticket":ODSlashCommand, - "opendiscord:close":ODSlashCommand, - "opendiscord:delete":ODSlashCommand, - "opendiscord:reopen":ODSlashCommand, - "opendiscord:claim":ODSlashCommand, - "opendiscord:unclaim":ODSlashCommand, - "opendiscord:pin":ODSlashCommand, - "opendiscord:unpin":ODSlashCommand, - "opendiscord:move":ODSlashCommand, - "opendiscord:rename":ODSlashCommand, - "opendiscord:add":ODSlashCommand, - "opendiscord:remove":ODSlashCommand, - "opendiscord:blacklist":ODSlashCommand, - "opendiscord:stats":ODSlashCommand, - "opendiscord:clear":ODSlashCommand, - "opendiscord:autoclose":ODSlashCommand, - "opendiscord:autodelete":ODSlashCommand, - "opendiscord:topic":ODSlashCommand, - "opendiscord:priority":ODSlashCommand, - "opendiscord:transfer":ODSlashCommand, -} - -/**## ODSlashCommandManager_Default `default_class` - * This is a special class that adds type definitions & typescript to the ODSlashCommandManager class. - * It doesn't add any extra features! - * - * This default class is made for the global variable `opendiscord.client.slashCommands`! - */ -export class ODSlashCommandManager_Default extends ODSlashCommandManager { - get(id:SlashCommandId): ODSlashCommandManagerIds_Default[SlashCommandId] - get(id:ODValidId): ODSlashCommand|null - - get(id:ODValidId): ODSlashCommand|null { - return super.get(id) - } - - remove(id:SlashCommandId): ODSlashCommandManagerIds_Default[SlashCommandId] - remove(id:ODValidId): ODSlashCommand|null - - remove(id:ODValidId): ODSlashCommand|null { - return super.remove(id) - } - - exists(id:keyof ODSlashCommandManagerIds_Default): boolean - exists(id:ODValidId): boolean - - exists(id:ODValidId): boolean { - return super.exists(id) - } - - onInteraction(commandName:keyof ODSlashCommandManagerIds_Default, callback:ODSlashCommandInteractionCallback): void - onInteraction(commandName:string|RegExp, callback:ODSlashCommandInteractionCallback): void - - onInteraction(commandName:string|RegExp, callback:ODSlashCommandInteractionCallback): void { - return super.onInteraction(commandName,callback) - } -} - -/**## ODTextCommandManagerIds_Default `interface` - * This interface is a list of ids available in the `ODTextCommandManager_Default` class. - * It's used to generate typescript declarations for this class. - */ -export interface ODTextCommandManagerIds_Default { - "opendiscord:dump":ODTextCommand, - "opendiscord:help":ODTextCommand, - "opendiscord:panel":ODTextCommand, - "opendiscord:close":ODTextCommand, - "opendiscord:delete":ODTextCommand, - "opendiscord:reopen":ODTextCommand, - "opendiscord:claim":ODTextCommand, - "opendiscord:unclaim":ODTextCommand, - "opendiscord:pin":ODTextCommand, - "opendiscord:unpin":ODTextCommand, - "opendiscord:move":ODTextCommand, - "opendiscord:rename":ODTextCommand, - "opendiscord:add":ODTextCommand, - "opendiscord:remove":ODTextCommand, - "opendiscord:blacklist-view":ODTextCommand, - "opendiscord:blacklist-add":ODTextCommand, - "opendiscord:blacklist-remove":ODTextCommand, - "opendiscord:blacklist-get":ODTextCommand, - "opendiscord:stats-global":ODTextCommand, - "opendiscord:stats-reset":ODTextCommand, - "opendiscord:stats-ticket":ODTextCommand, - "opendiscord:stats-user":ODTextCommand, - "opendiscord:clear":ODTextCommand, - "opendiscord:autoclose-disable":ODTextCommand, - "opendiscord:autoclose-enable":ODTextCommand, - "opendiscord:autodelete-disable":ODTextCommand, - "opendiscord:autodelete-enable":ODTextCommand, - "opendiscord:topic-set":ODTextCommand, - "opendiscord:priority-set":ODTextCommand, - "opendiscord:priority-get":ODTextCommand, - "opendiscord:transfer":ODTextCommand, -} - -/**## ODTextCommandManager_Default `default_class` - * This is a special class that adds type definitions & typescript to the ODTextCommandManager class. - * It doesn't add any extra features! - * - * This default class is made for the global variable `opendiscord.client.textCommands`! - */ -export class ODTextCommandManager_Default extends ODTextCommandManager { - get(id:TextCommandId): ODTextCommandManagerIds_Default[TextCommandId] - get(id:ODValidId): ODTextCommand|null - - get(id:ODValidId): ODTextCommand|null { - return super.get(id) - } - - remove(id:TextCommandId): ODTextCommandManagerIds_Default[TextCommandId] - remove(id:ODValidId): ODTextCommand|null - - remove(id:ODValidId): ODTextCommand|null { - return super.remove(id) - } - - exists(id:keyof ODTextCommandManagerIds_Default): boolean - exists(id:ODValidId): boolean - - exists(id:ODValidId): boolean { - return super.exists(id) - } - - onInteraction(commandPrefix:string, commandName:string|RegExp, callback:ODTextCommandInteractionCallback): void { - return super.onInteraction(commandPrefix,commandName,callback) - } -} - -/**## ODContextMenuManagerIds_Default `interface` - * This interface is a list of ids available in the `ODContextMenuManager_Default` class. - * It's used to generate typescript declarations for this class. - */ -export interface ODContextMenuManagerIds_Default { - //"opendiscord:test-menu":ODContextMenu -} - -/**## ODContextMenuManager_Default `default_class` - * This is a special class that adds type definitions & typescript to the ODContextMenuManager class. - * It doesn't add any extra features! - * - * This default class is made for the global variable `opendiscord.client.contextMenus`! - */ -export class ODContextMenuManager_Default extends ODContextMenuManager { - get(id:ContextMenuId): ODContextMenuManagerIds_Default[ContextMenuId] - get(id:ODValidId): ODContextMenu|null - - get(id:ODValidId): ODContextMenu|null { - return super.get(id) - } - - remove(id:ContextMenuId): ODContextMenuManagerIds_Default[ContextMenuId] - remove(id:ODValidId): ODContextMenu|null - - remove(id:ODValidId): ODContextMenu|null { - return super.remove(id) - } - - exists(id:keyof ODContextMenuManagerIds_Default): boolean - exists(id:ODValidId): boolean - - exists(id:ODValidId): boolean { - return super.exists(id) - } - - onInteraction(menuName:keyof ODContextMenuManagerIds_Default, callback:ODContextMenuInteractionCallback): void - onInteraction(menuName:string|RegExp, callback:ODContextMenuInteractionCallback): void - - onInteraction(menuName:string|RegExp, callback:ODContextMenuInteractionCallback): void { - return super.onInteraction(menuName,callback) - } -} \ No newline at end of file diff --git a/src/core/api/defaults/code.ts b/src/core/api/defaults/code.ts deleted file mode 100644 index 7ef19a8..0000000 --- a/src/core/api/defaults/code.ts +++ /dev/null @@ -1,57 +0,0 @@ -/////////////////////////////////////// -//DEFAULT CODE MODULE -/////////////////////////////////////// -import { ODValidId } from "../modules/base" -import { ODCode, ODCodeManager } from "../modules/code" - -/**## ODCodeManagerIds_Default `interface` - * This interface is a list of ids available in the `ODCodeManager_Default` class. - * It's used to generate typescript declarations for this class. - */ -export interface ODCodeManagerIds_Default { - "opendiscord:command-error-handling":ODCode, - "opendiscord:start-listening-interactions":ODCode, - "opendiscord:panel-database-cleaner":ODCode, - "opendiscord:suffix-database-cleaner":ODCode, - "opendiscord:option-database-cleaner":ODCode, - "opendiscord:user-database-cleaner":ODCode, - "opendiscord:ticket-database-cleaner":ODCode, - "opendiscord:panel-auto-update":ODCode, - "opendiscord:ticket-saver":ODCode, - "opendiscord:blacklist-saver":ODCode, - "opendiscord:auto-role-on-join":ODCode, - "opendiscord:autoclose-timeout":ODCode, - "opendiscord:autoclose-leave":ODCode, - "opendiscord:autodelete-timeout":ODCode, - "opendiscord:autodelete-leave":ODCode, - "opendiscord:ticket-anti-busy":ODCode, -} - -/**## ODCodeManager_Default `default_class` - * This is a special class that adds type definitions & typescript to the ODCodeManager class. - * It doesn't add any extra features! - * - * This default class is made for the global variable `opendiscord.code`! - */ -export class ODCodeManager_Default extends ODCodeManager { - get(id:CodeId): ODCodeManagerIds_Default[CodeId] - get(id:ODValidId): ODCode|null - - get(id:ODValidId): ODCode|null { - return super.get(id) - } - - remove(id:CodeId): ODCodeManagerIds_Default[CodeId] - remove(id:ODValidId): ODCode|null - - remove(id:ODValidId): ODCode|null { - return super.remove(id) - } - - exists(id:keyof ODCodeManagerIds_Default): boolean - exists(id:ODValidId): boolean - - exists(id:ODValidId): boolean { - return super.exists(id) - } -} \ No newline at end of file diff --git a/src/core/api/defaults/console.ts b/src/core/api/defaults/console.ts deleted file mode 100644 index 6b4d0da..0000000 --- a/src/core/api/defaults/console.ts +++ /dev/null @@ -1,42 +0,0 @@ -/////////////////////////////////////// -//DEFAULT CONSOLE MODULE -/////////////////////////////////////// -import { ODValidId } from "../modules/base" -import { ODLiveStatusUrlSource, ODLiveStatusManager, ODLiveStatusSource } from "../modules/console" - -/**## ODLiveStatusManagerIds_Default `interface` - * This interface is a list of ids available in the `ODLiveStatusManager_Default` class. - * It's used to generate typescript declarations for this class. - */ -export interface ODLiveStatusManagerIds_Default { - "opendiscord:default-djdj-dev":ODLiveStatusUrlSource -} - -/**## ODLiveStatusManager_Default `default_class` - * This is a special class that adds type definitions & typescript to the ODLiveStatusManager class. - * It doesn't add any extra features! - * - * This default class is made for the global variable `opendiscord.livestatus`! - */ -export class ODLiveStatusManager_Default extends ODLiveStatusManager { - get(id:LiveStatusId): ODLiveStatusManagerIds_Default[LiveStatusId] - get(id:ODValidId): ODLiveStatusSource|null - - get(id:ODValidId): ODLiveStatusSource|null { - return super.get(id) - } - - remove(id:LiveStatusId): ODLiveStatusManagerIds_Default[LiveStatusId] - remove(id:ODValidId): ODLiveStatusSource|null - - remove(id:ODValidId): ODLiveStatusSource|null { - return super.remove(id) - } - - exists(id:keyof ODLiveStatusManagerIds_Default): boolean - exists(id:ODValidId): boolean - - exists(id:ODValidId): boolean { - return super.exists(id) - } -} \ No newline at end of file diff --git a/src/core/api/defaults/cooldown.ts b/src/core/api/defaults/cooldown.ts deleted file mode 100644 index 75a53e7..0000000 --- a/src/core/api/defaults/cooldown.ts +++ /dev/null @@ -1,42 +0,0 @@ -/////////////////////////////////////// -//DEFAULT COOLDOWN MODULE -/////////////////////////////////////// -import { ODValidId } from "../modules/base" -import { ODCooldown, ODCooldownManager } from "../modules/cooldown" - -/**## ODCooldownManagerIds_Default `interface` - * This interface is a list of ids available in the `ODCooldownManager_Default` class. - * It's used to generate typescript declarations for this class. - */ -export interface ODCooldownManagerIds_Default { - -} - -/**## ODCooldownManager_Default `default_class` - * This is a special class that adds type definitions & typescript to the ODCooldownManager class. - * It doesn't add any extra features! - * - * This default class is made for the global variable `opendiscord.cooldowns`! - */ -export class ODCooldownManager_Default extends ODCooldownManager { - get(id:CooldownId): ODCooldownManagerIds_Default[CooldownId] - get(id:ODValidId): ODCooldown|null - - get(id:ODValidId): ODCooldown|null { - return super.get(id) - } - - remove(id:CooldownId): ODCooldownManagerIds_Default[CooldownId] - remove(id:ODValidId): ODCooldown|null - - remove(id:ODValidId): ODCooldown|null { - return super.remove(id) - } - - exists(id:keyof ODCooldownManagerIds_Default): boolean - exists(id:ODValidId): boolean - - exists(id:ODValidId): boolean { - return super.exists(id) - } -} \ No newline at end of file diff --git a/src/core/api/defaults/database.ts b/src/core/api/defaults/database.ts deleted file mode 100644 index 8e73a10..0000000 --- a/src/core/api/defaults/database.ts +++ /dev/null @@ -1,257 +0,0 @@ -/////////////////////////////////////// -//DEFAULT DATABASE MODULE -/////////////////////////////////////// -import { ODOptionalPromise, ODValidId, ODValidJsonType } from "../modules/base" -import { ODDatabaseManager, ODDatabase, ODFormattedJsonDatabase } from "../modules/database" -import { ODTicketJson } from "../openticket/ticket" -import { ODOptionJson } from "../openticket/option" - -/**## ODDatabaseManagerIds_Default `interface` - * This interface is a list of ids available in the `ODDatabaseManager_Default` class. - * It's used to generate typescript declarations for this class. - */ -export interface ODDatabaseManagerIds_Default { - "opendiscord:global":ODFormattedJsonDatabase_DefaultGlobal, - "opendiscord:stats":ODFormattedJsonDatabase, - "opendiscord:tickets":ODFormattedJsonDatabase_DefaultTickets, - "opendiscord:users":ODFormattedJsonDatabase_DefaultUsers, - "opendiscord:options":ODFormattedJsonDatabase_DefaultOptions, -} - -/**## ODDatabaseManager_Default `default_class` - * This is a special class that adds type definitions & typescript to the ODDatabaseManager class. - * It doesn't add any extra features! - * - * This default class is made for the global variable `opendiscord.databases`! - */ -export class ODDatabaseManager_Default extends ODDatabaseManager { - get(id:DatabaseId): ODDatabaseManagerIds_Default[DatabaseId] - get(id:ODValidId): ODDatabase|null - - get(id:ODValidId): ODDatabase|null { - return super.get(id) - } - - remove(id:DatabaseId): ODDatabaseManagerIds_Default[DatabaseId] - remove(id:ODValidId): ODDatabase|null - - remove(id:ODValidId): ODDatabase|null { - return super.remove(id) - } - - exists(id:keyof ODDatabaseManagerIds_Default): boolean - exists(id:ODValidId): boolean - - exists(id:ODValidId): boolean { - return super.exists(id) - } -} - -/**## ODFormattedJsonDatabaseIds_DefaultGlobal `type` - * This interface is a list of ids available in the `ODFormattedJsonDatabase_DefaultGlobal` class. - * It's used to generate typescript declarations for this class. - */ -export interface ODFormattedJsonDatabaseIds_DefaultGlobal { - "opendiscord:panel-message":string, - "opendiscord:panel-update":string, - "opendiscord:option-suffix-counter":number, - "opendiscord:option-suffix-history":string[], - "opendiscord:last-version":string -} - -/**## ODFormattedJsonDatabase_DefaultGlobal `default_class` - * This is a special class that adds type definitions & typescript to the ODFormattedJsonDatabase class. - * It doesn't add any extra features! - * - * This default class is made for the `global.json` database! - */ -export class ODFormattedJsonDatabase_DefaultGlobal extends ODFormattedJsonDatabase { - set(category:CategoryId, key:string, value:ODFormattedJsonDatabaseIds_DefaultGlobal[CategoryId]): ODOptionalPromise - set(category:string, key:string, value:ODValidJsonType): ODOptionalPromise - - set(category:string, key:string, value:ODValidJsonType): ODOptionalPromise { - return super.set(category,key,value) - } - - get(category:CategoryId, key:string): ODOptionalPromise - get(category:string, key:string): ODOptionalPromise - - get(category:string, key:string): ODOptionalPromise { - return super.get(category,key) - } - - delete(category:CategoryId, key:string): ODOptionalPromise - delete(category:string, key:string): ODOptionalPromise - - delete(category:string, key:string): ODOptionalPromise { - return super.delete(category,key) - } - - exists(category:keyof ODFormattedJsonDatabaseIds_DefaultGlobal, key:string): ODOptionalPromise - exists(category:string, key:string): ODOptionalPromise - - exists(category:string, key:string): ODOptionalPromise { - return super.exists(category,key) - } - - getCategory(category:CategoryId): ODOptionalPromise<{key:string, value:ODFormattedJsonDatabaseIds_DefaultGlobal[CategoryId]}[]|undefined> - getCategory(category:string): ODOptionalPromise<{key:string, value:ODValidJsonType}[]|undefined> - - getCategory(category:string): ODOptionalPromise<{key:string, value:ODValidJsonType}[]|undefined> { - return super.getCategory(category) - } -} - -/**## ODFormattedJsonDatabaseIds_DefaultTickets `type` - * This interface is a list of ids available in the `ODDatabaseManager_Default` class. - * It's used to generate typescript declarations for this class. - */ -export interface ODFormattedJsonDatabaseIds_DefaultTickets { - "opendiscord:ticket":ODTicketJson -} - -/**## ODFormattedJsonDatabase_DefaultTickets `default_class` - * This is a special class that adds type definitions & typescript to the ODFormattedJsonDatabase class. - * It doesn't add any extra features! - * - * This default class is made for the `tickets.json` database! - */ -export class ODFormattedJsonDatabase_DefaultTickets extends ODFormattedJsonDatabase { - set(category:CategoryId, key:string, value:ODFormattedJsonDatabaseIds_DefaultTickets[CategoryId]): ODOptionalPromise - set(category:string, key:string, value:ODValidJsonType): ODOptionalPromise - - set(category:string, key:string, value:ODValidJsonType): ODOptionalPromise { - return super.set(category,key,value) - } - - get(category:CategoryId, key:string): ODOptionalPromise - get(category:string, key:string): ODOptionalPromise - - get(category:string, key:string): ODOptionalPromise { - return super.get(category,key) - } - - delete(category:CategoryId, key:string): ODOptionalPromise - delete(category:string, key:string): ODOptionalPromise - - delete(category:string, key:string): ODOptionalPromise { - return super.delete(category,key) - } - - exists(category:keyof ODFormattedJsonDatabaseIds_DefaultTickets, key:string): ODOptionalPromise - exists(category:string, key:string): ODOptionalPromise - - exists(category:string, key:string): ODOptionalPromise { - return super.exists(category,key) - } - - getCategory(category:CategoryId): ODOptionalPromise<{key:string, value:ODFormattedJsonDatabaseIds_DefaultTickets[CategoryId]}[]|undefined> - getCategory(category:string): ODOptionalPromise<{key:string, value:ODValidJsonType}[]|undefined> - - getCategory(category:string): ODOptionalPromise<{key:string, value:ODValidJsonType}[]|undefined> { - return super.getCategory(category) - } -} - -/**## ODFormattedJsonDatabaseIds_DefaultUsers `type` - * This interface is a list of ids available in the `ODDatabaseManager_Default` class. - * It's used to generate typescript declarations for this class. - */ -export interface ODFormattedJsonDatabaseIds_DefaultUsers { - "opendiscord:blacklist":ODTicketJson -} - -/**## ODFormattedJsonDatabase_DefaultUsers `default_class` - * This is a special class that adds type definitions & typescript to the ODFormattedJsonDatabase class. - * It doesn't add any extra features! - * - * This default class is made for the `users.json` database! - */ -export class ODFormattedJsonDatabase_DefaultUsers extends ODFormattedJsonDatabase { - set(category:CategoryId, key:string, value:ODFormattedJsonDatabaseIds_DefaultUsers[CategoryId]): ODOptionalPromise - set(category:string, key:string, value:ODValidJsonType): ODOptionalPromise - - set(category:string, key:string, value:ODValidJsonType): ODOptionalPromise { - return super.set(category,key,value) - } - - get(category:CategoryId, key:string): ODOptionalPromise - get(category:string, key:string): ODOptionalPromise - - get(category:string, key:string): ODOptionalPromise { - return super.get(category,key) - } - - delete(category:CategoryId, key:string): ODOptionalPromise - delete(category:string, key:string): ODOptionalPromise - - delete(category:string, key:string): ODOptionalPromise { - return super.delete(category,key) - } - - exists(category:keyof ODFormattedJsonDatabaseIds_DefaultUsers, key:string): ODOptionalPromise - exists(category:string, key:string): ODOptionalPromise - - exists(category:string, key:string): ODOptionalPromise { - return super.exists(category,key) - } - - getCategory(category:CategoryId): ODOptionalPromise<{key:string, value:ODFormattedJsonDatabaseIds_DefaultUsers[CategoryId]}[]|undefined> - getCategory(category:string): ODOptionalPromise<{key:string, value:ODValidJsonType}[]|undefined> - - getCategory(category:string): ODOptionalPromise<{key:string, value:ODValidJsonType}[]|undefined> { - return super.getCategory(category) - } -} - - -/**## ODFormattedJsonDatabaseIds_DefaultOptions `type` - * This interface is a list of ids available in the `ODDatabaseManager_Default` class. - * It's used to generate typescript declarations for this class. - */ -export interface ODFormattedJsonDatabaseIds_DefaultOptions { - "opendiscord:used-option":ODOptionJson -} - -/**## ODFormattedJsonDatabase_DefaultOptions `default_class` - * This is a special class that adds type definitions & typescript to the ODFormattedJsonDatabase class. - * It doesn't add any extra features! - * - * This default class is made for the `options.json` database! - */ -export class ODFormattedJsonDatabase_DefaultOptions extends ODFormattedJsonDatabase { - set(category:CategoryId, key:string, value:ODFormattedJsonDatabaseIds_DefaultOptions[CategoryId]): ODOptionalPromise - set(category:string, key:string, value:ODValidJsonType): ODOptionalPromise - - set(category:string, key:string, value:ODValidJsonType): ODOptionalPromise { - return super.set(category,key,value) - } - - get(category:CategoryId, key:string): ODOptionalPromise - get(category:string, key:string): ODOptionalPromise - - get(category:string, key:string): ODOptionalPromise { - return super.get(category,key) - } - - delete(category:CategoryId, key:string): ODOptionalPromise - delete(category:string, key:string): ODOptionalPromise - - delete(category:string, key:string): ODOptionalPromise { - return super.delete(category,key) - } - - exists(category:keyof ODFormattedJsonDatabaseIds_DefaultOptions, key:string): ODOptionalPromise - exists(category:string, key:string): ODOptionalPromise - - exists(category:string, key:string): ODOptionalPromise { - return super.exists(category,key) - } - - getCategory(category:CategoryId): ODOptionalPromise<{key:string, value:ODFormattedJsonDatabaseIds_DefaultOptions[CategoryId]}[]|undefined> - getCategory(category:string): ODOptionalPromise<{key:string, value:ODValidJsonType}[]|undefined> - - getCategory(category:string): ODOptionalPromise<{key:string, value:ODValidJsonType}[]|undefined> { - return super.getCategory(category) - } -} \ No newline at end of file diff --git a/src/core/api/defaults/event.ts b/src/core/api/defaults/event.ts deleted file mode 100644 index 86496fc..0000000 --- a/src/core/api/defaults/event.ts +++ /dev/null @@ -1,390 +0,0 @@ -/////////////////////////////////////// -//DEFAULT EVENT MODULE -/////////////////////////////////////// -//BASE MODULES -import { ODPromiseVoid, ODValidId } from "../modules/base" -import { ODConsoleManager, ODError } from "../modules/console" -import { ODCheckerResult, ODCheckerStorage } from "../modules/checker" -import { ODDefaultsManager } from "../modules/defaults" -import { ODLanguage } from "../modules/language" -import { ODClientActivityManager } from "../modules/client" -import { ODEvent, ODEventManager } from "../modules/event" -import * as discord from "discord.js" - -//DEFAULT MODULES -import { ODPluginClassManager_Default, ODPluginManager_Default } from "./plugin" -import { ODConfigManager_Default} from "./config" -import { ODDatabaseManager_Default } from "./database" -import { ODFlagManager_Default } from "./flag" -import { ODSessionManager_Default } from "./session" -import { ODLanguageManager_Default } from "./language" -import { ODCheckerFunctionManager_Default, ODCheckerManager_Default, ODCheckerRenderer_Default, ODCheckerTranslationRegister_Default } from "./checker" -import { ODClientManager_Default, ODContextMenuManager_Default, ODSlashCommandManager_Default, ODTextCommandManager_Default } from "./client" -import { ODBuilderManager_Default, ODButtonManager_Default, ODDropdownManager_Default, ODEmbedManager_Default, ODFileManager_Default, ODMessageManager_Default, ODModalManager_Default } from "./builder" -import { ODAutocompleteResponderManager_Default, ODButtonResponderManager_Default, ODCommandResponderManager_Default, ODContextMenuResponderManager_Default, ODDropdownResponderManager_Default, ODModalResponderManager_Default, ODResponderManager_Default } from "./responder" -import { ODActionManager_Default } from "./action" -import { ODPermissionManager_Default } from "./permission" -import { ODHelpMenuManager_Default } from "./helpmenu" -import { ODStatsManager_Default } from "./stat" -import { ODCodeManager_Default } from "./code" -import { ODCooldownManager_Default } from "./cooldown" -import { ODPostManager_Default } from "./post" -import { ODVerifyBarManager_Default } from "./verifybar" -import { ODStartScreenManager_Default } from "./startscreen" -import { ODLiveStatusManager_Default } from "./console" -import { ODProgressBarManager_Default, ODProgressBarRendererManager_Default } from "./progressbar" - -//OPEN TICKET MODULES -import { ODOptionManager, ODTicketOption } from "../openticket/option" -import { ODPanel, ODPanelManager } from "../openticket/panel" -import { ODTicket, ODTicketClearFilter, ODTicketManager } from "../openticket/ticket" -import { ODQuestionManager } from "../openticket/question" -import { ODBlacklistManager } from "../openticket/blacklist" -import { ODTranscriptManager_Default } from "../openticket/transcript" -import { ODRole, ODRoleManager } from "../openticket/role" -import { ODPriorityLevel, ODPriorityManager_Default } from "../openticket/priority" - -/**## ODEventIds_Default `interface` - * This interface is a list of ids available in the `ODEvent_Default` class. - * It's used to generate typescript declarations for this class. - */ -export interface ODEventIds_Default { - //error handling - "onErrorHandling": ODEvent_Default<(error:Error, origin:NodeJS.UncaughtExceptionOrigin) => ODPromiseVoid> - "afterErrorHandling": ODEvent_Default<(error:Error, origin:NodeJS.UncaughtExceptionOrigin, message:ODError) => ODPromiseVoid> - - //plugins - "afterPluginsLoaded": ODEvent_Default<(plugins:ODPluginManager_Default) => ODPromiseVoid> - "onPluginClassLoad": ODEvent_Default<(classes:ODPluginClassManager_Default, plugins:ODPluginManager_Default) => ODPromiseVoid> - "afterPluginClassesLoaded": ODEvent_Default<(classes:ODPluginClassManager_Default, plugins:ODPluginManager_Default) => ODPromiseVoid> - - //flags - "onFlagLoad": ODEvent_Default<(flags:ODFlagManager_Default) => ODPromiseVoid> - "afterFlagsLoaded": ODEvent_Default<(flags:ODFlagManager_Default) => ODPromiseVoid> - "onFlagInit": ODEvent_Default<(flags:ODFlagManager_Default) => ODPromiseVoid> - "afterFlagsInitiated": ODEvent_Default<(flags:ODFlagManager_Default) => ODPromiseVoid> - - //progress bars - "onProgressBarRendererLoad": ODEvent_Default<(renderers:ODProgressBarRendererManager_Default) => ODPromiseVoid> - "afterProgressBarRenderersLoaded": ODEvent_Default<(renderers:ODProgressBarRendererManager_Default) => ODPromiseVoid> - "onProgressBarLoad": ODEvent_Default<(progressbars:ODProgressBarManager_Default) => ODPromiseVoid> - "afterProgressBarsLoaded": ODEvent_Default<(progressbars:ODProgressBarManager_Default) => ODPromiseVoid> - - //configs - "onConfigLoad": ODEvent_Default<(configs:ODConfigManager_Default) => ODPromiseVoid> - "afterConfigsLoaded": ODEvent_Default<(configs:ODConfigManager_Default) => ODPromiseVoid> - "onConfigInit": ODEvent_Default<(configs:ODConfigManager_Default) => ODPromiseVoid> - "afterConfigsInitiated": ODEvent_Default<(configs:ODConfigManager_Default) => ODPromiseVoid> - - //databases - "onDatabaseLoad": ODEvent_Default<(databases:ODDatabaseManager_Default) => ODPromiseVoid> - "afterDatabasesLoaded": ODEvent_Default<(databases:ODDatabaseManager_Default) => ODPromiseVoid> - "onDatabaseInit": ODEvent_Default<(databases:ODDatabaseManager_Default) => ODPromiseVoid> - "afterDatabasesInitiated": ODEvent_Default<(databases:ODDatabaseManager_Default) => ODPromiseVoid> - - //languages - "onLanguageLoad": ODEvent_Default<(languages:ODLanguageManager_Default) => ODPromiseVoid> - "afterLanguagesLoaded": ODEvent_Default<(languages:ODLanguageManager_Default) => ODPromiseVoid> - "onLanguageInit": ODEvent_Default<(languages:ODLanguageManager_Default) => ODPromiseVoid> - "afterLanguagesInitiated": ODEvent_Default<(languages:ODLanguageManager_Default) => ODPromiseVoid> - "onLanguageSelect": ODEvent_Default<(languages:ODLanguageManager_Default) => ODPromiseVoid> - "afterLanguagesSelected": ODEvent_Default<(main:ODLanguage|null, backup:ODLanguage|null, languages:ODLanguageManager_Default) => ODPromiseVoid> - - //sessions - "onSessionLoad": ODEvent_Default<(languages:ODSessionManager_Default) => ODPromiseVoid> - "afterSessionsLoaded": ODEvent_Default<(languages:ODSessionManager_Default) => ODPromiseVoid> - - //config checkers - "onCheckerLoad": ODEvent_Default<(checkers:ODCheckerManager_Default) => ODPromiseVoid> - "afterCheckersLoaded": ODEvent_Default<(checkers:ODCheckerManager_Default) => ODPromiseVoid> - "onCheckerFunctionLoad": ODEvent_Default<(functions:ODCheckerFunctionManager_Default, checkers:ODCheckerManager_Default) => ODPromiseVoid> - "afterCheckerFunctionsLoaded": ODEvent_Default<(functions:ODCheckerFunctionManager_Default, checkers:ODCheckerManager_Default) => ODPromiseVoid> - "onCheckerExecute": ODEvent_Default<(checkers:ODCheckerManager_Default) => ODPromiseVoid> - "afterCheckersExecuted": ODEvent_Default<(result:ODCheckerResult, checkers:ODCheckerManager_Default) => ODPromiseVoid> - "onCheckerTranslationLoad": ODEvent_Default<(translations:ODCheckerTranslationRegister_Default, enabled:boolean, checkers:ODCheckerManager_Default) => ODPromiseVoid> - "afterCheckerTranslationsLoaded": ODEvent_Default<(translations:ODCheckerTranslationRegister_Default, checkers:ODCheckerManager_Default) => ODPromiseVoid> - "onCheckerRender": ODEvent_Default<(renderer:ODCheckerRenderer_Default, checkers:ODCheckerManager_Default) => ODPromiseVoid> - "afterCheckersRendered": ODEvent_Default<(renderer:ODCheckerRenderer_Default, checkers:ODCheckerManager_Default) => ODPromiseVoid> - "onCheckerQuit": ODEvent_Default<(checkers:ODCheckerManager_Default) => ODPromiseVoid> - - //plugin loading before client - "onPluginBeforeClientLoad": ODEvent_Default<() => ODPromiseVoid>, - "afterPluginBeforeClientLoaded": ODEvent_Default<() => ODPromiseVoid>, - - //client configuration - "onClientLoad": ODEvent_Default<(client:ODClientManager_Default) => ODPromiseVoid> - "afterClientLoaded": ODEvent_Default<(client:ODClientManager_Default) => ODPromiseVoid> - "onClientInit": ODEvent_Default<(client:ODClientManager_Default) => ODPromiseVoid> - "afterClientInitiated": ODEvent_Default<(client:ODClientManager_Default) => ODPromiseVoid> - "onClientReady": ODEvent_Default<(client:ODClientManager_Default) => ODPromiseVoid> - "afterClientReady": ODEvent_Default<(client:ODClientManager_Default) => ODPromiseVoid> - "onClientActivityLoad": ODEvent_Default<(activity:ODClientActivityManager, client:ODClientManager_Default) => ODPromiseVoid> - "afterClientActivityLoaded": ODEvent_Default<(activity:ODClientActivityManager, client:ODClientManager_Default) => ODPromiseVoid> - "onClientActivityInit": ODEvent_Default<(activity:ODClientActivityManager, client:ODClientManager_Default) => ODPromiseVoid> - "afterClientActivityInitiated": ODEvent_Default<(activity:ODClientActivityManager, client:ODClientManager_Default) => ODPromiseVoid> - - //priority levels - "onPriorityLoad": ODEvent_Default<(priorities:ODPriorityManager_Default) => ODPromiseVoid> - "afterPrioritiesLoaded": ODEvent_Default<(priorities:ODPriorityManager_Default) => ODPromiseVoid> - - //client slash commands - "onSlashCommandLoad": ODEvent_Default<(slash:ODSlashCommandManager_Default, client:ODClientManager_Default) => ODPromiseVoid> - "afterSlashCommandsLoaded": ODEvent_Default<(slash:ODSlashCommandManager_Default, client:ODClientManager_Default) => ODPromiseVoid> - "onSlashCommandRegister": ODEvent_Default<(slash:ODSlashCommandManager_Default, client:ODClientManager_Default) => ODPromiseVoid> - "afterSlashCommandsRegistered": ODEvent_Default<(slash:ODSlashCommandManager_Default, client:ODClientManager_Default) => ODPromiseVoid> - - //client context menus - "onContextMenuLoad": ODEvent_Default<(menu:ODContextMenuManager_Default, client:ODClientManager_Default) => ODPromiseVoid> - "afterContextMenusLoaded": ODEvent_Default<(menu:ODContextMenuManager_Default, client:ODClientManager_Default) => ODPromiseVoid> - "onContextMenuRegister": ODEvent_Default<(menu:ODContextMenuManager_Default, client:ODClientManager_Default) => ODPromiseVoid> - "afterContextMenusRegistered": ODEvent_Default<(menu:ODContextMenuManager_Default, client:ODClientManager_Default) => ODPromiseVoid> - - //client text commands - "onTextCommandLoad": ODEvent_Default<(text:ODTextCommandManager_Default, client:ODClientManager_Default,) => ODPromiseVoid> - "afterTextCommandsLoaded": ODEvent_Default<(text:ODTextCommandManager_Default, client:ODClientManager_Default) => ODPromiseVoid> - - //plugin loading before managers - "onPluginBeforeManagerLoad": ODEvent_Default<() => ODPromiseVoid>, - "afterPluginBeforeManagerLoaded": ODEvent_Default<() => ODPromiseVoid>, - - //questions - "onQuestionLoad": ODEvent_Default<(questions:ODQuestionManager) => ODPromiseVoid> - "afterQuestionsLoaded": ODEvent_Default<(questions:ODQuestionManager) => ODPromiseVoid> - - //options - "onOptionLoad": ODEvent_Default<(options:ODOptionManager) => ODPromiseVoid> - "afterOptionsLoaded": ODEvent_Default<(options:ODOptionManager) => ODPromiseVoid> - - //panels - "onPanelLoad": ODEvent_Default<(panels:ODPanelManager) => ODPromiseVoid> - "afterPanelsLoaded": ODEvent_Default<(panels:ODPanelManager) => ODPromiseVoid> - "onPanelSpawn": ODEvent_Default<(panel:ODPanel) => ODPromiseVoid> - "afterPanelSpawned": ODEvent_Default<(panel:ODPanel) => ODPromiseVoid> - - //tickets - "onTicketLoad": ODEvent_Default<(tickets:ODTicketManager) => ODPromiseVoid> - "afterTicketsLoaded": ODEvent_Default<(tickets:ODTicketManager) => ODPromiseVoid> - - //ticket creation - "onTicketChannelCreation": ODEvent_Default<(option:ODTicketOption, user:discord.User) => ODPromiseVoid> - "afterTicketChannelCreated": ODEvent_Default<(option:ODTicketOption, channel:discord.GuildTextBasedChannel, user:discord.User) => ODPromiseVoid> - "onTicketChannelDeletion": ODEvent_Default<(ticket:ODTicket, channel:discord.GuildTextBasedChannel, user:discord.User) => ODPromiseVoid> - "afterTicketChannelDeleted": ODEvent_Default<(ticket:ODTicket, user:discord.User) => ODPromiseVoid> - "onTicketPermissionsCreated": ODEvent_Default<(option:ODTicketOption, permissions:ODPermissionManager_Default, channel:discord.GuildTextBasedChannel, user:discord.User) => ODPromiseVoid> - "afterTicketPermissionsCreated": ODEvent_Default<(option:ODTicketOption, permissions:ODPermissionManager_Default, channel:discord.GuildTextBasedChannel, user:discord.User) => ODPromiseVoid> - "onTicketMainMessageCreated": ODEvent_Default<(ticket:ODTicket, channel:discord.GuildTextBasedChannel, user:discord.User) => ODPromiseVoid> - "afterTicketMainMessageCreated": ODEvent_Default<(ticket:ODTicket, message:discord.Message, channel:discord.GuildTextBasedChannel, user:discord.User) => ODPromiseVoid> - - //ticket actions - "onTicketCreate": ODEvent_Default<(creator:discord.User) => ODPromiseVoid> - "afterTicketCreated": ODEvent_Default<(ticket:ODTicket, creator:discord.User, channel:discord.GuildTextBasedChannel) => ODPromiseVoid> - "onTicketClose": ODEvent_Default<(ticket:ODTicket, closer:discord.User, channel:discord.GuildTextBasedChannel, reason:string|null) => ODPromiseVoid> - "afterTicketClosed": ODEvent_Default<(ticket:ODTicket, closer:discord.User, channel:discord.GuildTextBasedChannel, reason:string|null) => ODPromiseVoid> - "onTicketReopen": ODEvent_Default<(ticket:ODTicket, reopener:discord.User, channel:discord.GuildTextBasedChannel, reason:string|null) => ODPromiseVoid> - "afterTicketReopened": ODEvent_Default<(ticket:ODTicket, reopener:discord.User, channel:discord.GuildTextBasedChannel, reason:string|null) => ODPromiseVoid> - "onTicketDelete": ODEvent_Default<(ticket:ODTicket, deleter:discord.User, channel:discord.GuildTextBasedChannel, reason:string|null) => ODPromiseVoid> - "afterTicketDeleted": ODEvent_Default<(ticket:ODTicket, deleter:discord.User, reason:string|null) => ODPromiseVoid> - "onTicketMove": ODEvent_Default<(ticket:ODTicket, mover:discord.User, channel:discord.GuildTextBasedChannel, reason:string|null) => ODPromiseVoid> - "afterTicketMoved": ODEvent_Default<(ticket:ODTicket, mover:discord.User, channel:discord.GuildTextBasedChannel, reason:string|null) => ODPromiseVoid> - "onTicketClaim": ODEvent_Default<(ticket:ODTicket, claimer:discord.User, channel:discord.GuildTextBasedChannel, reason:string|null) => ODPromiseVoid> - "afterTicketClaimed": ODEvent_Default<(ticket:ODTicket, claimer:discord.User, channel:discord.GuildTextBasedChannel, reason:string|null) => ODPromiseVoid> - "onTicketUnclaim": ODEvent_Default<(ticket:ODTicket, unclaimer:discord.User, channel:discord.GuildTextBasedChannel, reason:string|null) => ODPromiseVoid> - "afterTicketUnclaimed": ODEvent_Default<(ticket:ODTicket, unclaimer:discord.User, channel:discord.GuildTextBasedChannel, reason:string|null) => ODPromiseVoid> - "onTicketPin": ODEvent_Default<(ticket:ODTicket, pinner:discord.User, channel:discord.GuildTextBasedChannel, reason:string|null) => ODPromiseVoid> - "afterTicketPinned": ODEvent_Default<(ticket:ODTicket, pinner:discord.User, channel:discord.GuildTextBasedChannel, reason:string|null) => ODPromiseVoid> - "onTicketUnpin": ODEvent_Default<(ticket:ODTicket, unpinner:discord.User, channel:discord.GuildTextBasedChannel, reason:string|null) => ODPromiseVoid> - "afterTicketUnpinned": ODEvent_Default<(ticket:ODTicket, unpinner:discord.User, channel:discord.GuildTextBasedChannel, reason:string|null) => ODPromiseVoid> - "onTicketUserAdd": ODEvent_Default<(ticket:ODTicket, adder:discord.User, user:discord.User, channel:discord.GuildTextBasedChannel, reason:string|null) => ODPromiseVoid> - "afterTicketUserAdded": ODEvent_Default<(ticket:ODTicket, adder:discord.User, user:discord.User, channel:discord.GuildTextBasedChannel, reason:string|null) => ODPromiseVoid> - "onTicketUserRemove": ODEvent_Default<(ticket:ODTicket, remover:discord.User, user:discord.User, channel:discord.GuildTextBasedChannel, reason:string|null) => ODPromiseVoid> - "afterTicketUserRemoved": ODEvent_Default<(ticket:ODTicket, remover:discord.User, user:discord.User, channel:discord.GuildTextBasedChannel, reason:string|null) => ODPromiseVoid> - "onTicketRename": ODEvent_Default<(ticket:ODTicket, renamer:discord.User, channel:discord.GuildTextBasedChannel, reason:string|null) => ODPromiseVoid> - "afterTicketRenamed": ODEvent_Default<(ticket:ODTicket, renamer:discord.User, channel:discord.GuildTextBasedChannel, reason:string|null) => ODPromiseVoid> - "onTicketsClear": ODEvent_Default<(tickets:ODTicket[], clearer:discord.User, channel:discord.GuildTextBasedChannel, filter:ODTicketClearFilter) => ODPromiseVoid> - "afterTicketsCleared": ODEvent_Default<(tickets:ODTicket[], clearer:discord.User, channel:discord.GuildTextBasedChannel, filter:ODTicketClearFilter) => ODPromiseVoid> - "onTicketTopicChange": ODEvent_Default<(ticket:ODTicket, changer:discord.User, channel:discord.GuildTextBasedChannel, oldTopic:string, newTopic:string) => ODPromiseVoid> - "afterTicketTopicChanged": ODEvent_Default<(ticket:ODTicket, changer:discord.User, channel:discord.GuildTextBasedChannel, oldTopic:string, newTopic:string) => ODPromiseVoid> - "onTicketPriorityChange": ODEvent_Default<(ticket:ODTicket, changer:discord.User, channel:discord.GuildTextBasedChannel, oldPriority:ODPriorityLevel, newPriority:ODPriorityLevel, reason:string|null) => ODPromiseVoid> - "afterTicketPriorityChanged": ODEvent_Default<(ticket:ODTicket, changer:discord.User, channel:discord.GuildTextBasedChannel, oldPriority:ODPriorityLevel, newPriority:ODPriorityLevel, reason:string|null) => ODPromiseVoid> - "onTicketTransfer": ODEvent_Default<(ticket:ODTicket, changer:discord.User, channel:discord.GuildTextBasedChannel, oldCreator:discord.User, newCreator:discord.User, reason:string|null) => ODPromiseVoid> - "afterTicketTransferred": ODEvent_Default<(ticket:ODTicket, changer:discord.User, channel:discord.GuildTextBasedChannel, oldCreator:discord.User, newCreator:discord.User, reason:string|null) => ODPromiseVoid> - - //roles - "onRoleLoad": ODEvent_Default<(roles:ODRoleManager) => ODPromiseVoid> - "afterRolesLoaded": ODEvent_Default<(roles:ODRoleManager) => ODPromiseVoid> - "onRoleUpdate": ODEvent_Default<(user:discord.User,role:ODRole) => ODPromiseVoid> - "afterRolesUpdated": ODEvent_Default<(user:discord.User,role:ODRole) => ODPromiseVoid> - - //blacklist - "onBlacklistLoad": ODEvent_Default<(blacklist:ODBlacklistManager) => ODPromiseVoid> - "afterBlacklistLoaded": ODEvent_Default<(blacklist:ODBlacklistManager) => ODPromiseVoid> - - //transcripts - "onTranscriptCompilerLoad": ODEvent_Default<(transcripts:ODTranscriptManager_Default) => ODPromiseVoid> - "afterTranscriptCompilersLoaded": ODEvent_Default<(transcripts:ODTranscriptManager_Default) => ODPromiseVoid> - "onTranscriptHistoryLoad": ODEvent_Default<(transcripts:ODTranscriptManager_Default) => ODPromiseVoid> - "afterTranscriptHistoryLoaded": ODEvent_Default<(transcripts:ODTranscriptManager_Default) => ODPromiseVoid> - - //transcript creation - "onTranscriptCreate": ODEvent_Default<(transcripts:ODTranscriptManager_Default,ticket:ODTicket,channel:discord.TextChannel,user:discord.User) => ODPromiseVoid> - "afterTranscriptCreated": ODEvent_Default<(transcripts:ODTranscriptManager_Default,ticket:ODTicket,channel:discord.TextChannel,user:discord.User) => ODPromiseVoid> - "onTranscriptInit": ODEvent_Default<(transcripts:ODTranscriptManager_Default,ticket:ODTicket,channel:discord.TextChannel,user:discord.User) => ODPromiseVoid> - "afterTranscriptInitiated": ODEvent_Default<(transcripts:ODTranscriptManager_Default,ticket:ODTicket,channel:discord.TextChannel,user:discord.User) => ODPromiseVoid> - "onTranscriptCompile": ODEvent_Default<(transcripts:ODTranscriptManager_Default,ticket:ODTicket,channel:discord.TextChannel,user:discord.User) => ODPromiseVoid> - "afterTranscriptCompiled": ODEvent_Default<(transcripts:ODTranscriptManager_Default,ticket:ODTicket,channel:discord.TextChannel,user:discord.User) => ODPromiseVoid> - "onTranscriptReady": ODEvent_Default<(transcripts:ODTranscriptManager_Default,ticket:ODTicket,channel:discord.TextChannel,user:discord.User) => ODPromiseVoid> - "afterTranscriptReady": ODEvent_Default<(transcripts:ODTranscriptManager_Default,ticket:ODTicket,channel:discord.TextChannel,user:discord.User) => ODPromiseVoid> - - //plugin loading before builders - "onPluginBeforeBuilderLoad": ODEvent_Default<() => ODPromiseVoid>, - "afterPluginBeforeBuilderLoaded": ODEvent_Default<() => ODPromiseVoid>, - - //builders - "onButtonBuilderLoad": ODEvent_Default<(buttons:ODButtonManager_Default, builders:ODBuilderManager_Default, actions:ODActionManager_Default) => ODPromiseVoid> - "afterButtonBuildersLoaded": ODEvent_Default<(buttons:ODButtonManager_Default, builders:ODBuilderManager_Default, actions:ODActionManager_Default) => ODPromiseVoid> - "onDropdownBuilderLoad": ODEvent_Default<(dropdowns:ODDropdownManager_Default, builders:ODBuilderManager_Default, actions:ODActionManager_Default) => ODPromiseVoid> - "afterDropdownBuildersLoaded": ODEvent_Default<(dropdowns:ODDropdownManager_Default, builders:ODBuilderManager_Default, actions:ODActionManager_Default) => ODPromiseVoid> - "onFileBuilderLoad": ODEvent_Default<(files:ODFileManager_Default, builders:ODBuilderManager_Default, actions:ODActionManager_Default) => ODPromiseVoid> - "afterFileBuildersLoaded": ODEvent_Default<(files:ODFileManager_Default, builders:ODBuilderManager_Default, actions:ODActionManager_Default) => ODPromiseVoid> - "onEmbedBuilderLoad": ODEvent_Default<(embeds:ODEmbedManager_Default, builders:ODBuilderManager_Default, actions:ODActionManager_Default) => ODPromiseVoid> - "afterEmbedBuildersLoaded": ODEvent_Default<(embeds:ODEmbedManager_Default, builders:ODBuilderManager_Default, actions:ODActionManager_Default) => ODPromiseVoid> - "onMessageBuilderLoad": ODEvent_Default<(messages:ODMessageManager_Default, builders:ODBuilderManager_Default, actions:ODActionManager_Default) => ODPromiseVoid> - "afterMessageBuildersLoaded": ODEvent_Default<(messages:ODMessageManager_Default, builders:ODBuilderManager_Default, actions:ODActionManager_Default) => ODPromiseVoid> - "onModalBuilderLoad": ODEvent_Default<(modals:ODModalManager_Default, builders:ODBuilderManager_Default, actions:ODActionManager_Default) => ODPromiseVoid> - "afterModalBuildersLoaded": ODEvent_Default<(modals:ODModalManager_Default, builders:ODBuilderManager_Default, actions:ODActionManager_Default) => ODPromiseVoid> - - //plugin loading before responders - "onPluginBeforeResponderLoad": ODEvent_Default<() => ODPromiseVoid>, - "afterPluginBeforeResponderLoaded": ODEvent_Default<() => ODPromiseVoid>, - - //responders - "onCommandResponderLoad": ODEvent_Default<(commands:ODCommandResponderManager_Default, responders:ODResponderManager_Default, actions:ODActionManager_Default) => ODPromiseVoid> - "afterCommandRespondersLoaded": ODEvent_Default<(commands:ODCommandResponderManager_Default, responders:ODResponderManager_Default, actions:ODActionManager_Default) => ODPromiseVoid> - "onButtonResponderLoad": ODEvent_Default<(buttons:ODButtonResponderManager_Default, responders:ODResponderManager_Default, actions:ODActionManager_Default) => ODPromiseVoid> - "afterButtonRespondersLoaded": ODEvent_Default<(buttons:ODButtonResponderManager_Default, responders:ODResponderManager_Default, actions:ODActionManager_Default) => ODPromiseVoid> - "onDropdownResponderLoad": ODEvent_Default<(dropdowns:ODDropdownResponderManager_Default, responders:ODResponderManager_Default, actions:ODActionManager_Default) => ODPromiseVoid> - "afterDropdownRespondersLoaded": ODEvent_Default<(dropdowns:ODDropdownResponderManager_Default, responders:ODResponderManager_Default, actions:ODActionManager_Default) => ODPromiseVoid> - "onModalResponderLoad": ODEvent_Default<(modals:ODModalResponderManager_Default, responders:ODResponderManager_Default, actions:ODActionManager_Default) => ODPromiseVoid> - "afterModalRespondersLoaded": ODEvent_Default<(modals:ODModalResponderManager_Default, responders:ODResponderManager_Default, actions:ODActionManager_Default) => ODPromiseVoid> - "onContextMenuResponderLoad": ODEvent_Default<(menus:ODContextMenuResponderManager_Default, responders:ODResponderManager_Default, actions:ODActionManager_Default) => ODPromiseVoid> - "afterContextMenuRespondersLoaded": ODEvent_Default<(menus:ODContextMenuResponderManager_Default, responders:ODResponderManager_Default, actions:ODActionManager_Default) => ODPromiseVoid> - "onAutocompleteResponderLoad": ODEvent_Default<(autocomplete:ODAutocompleteResponderManager_Default, responders:ODResponderManager_Default, actions:ODActionManager_Default) => ODPromiseVoid> - "afterAutocompleteRespondersLoaded": ODEvent_Default<(autocomplete:ODAutocompleteResponderManager_Default, responders:ODResponderManager_Default, actions:ODActionManager_Default) => ODPromiseVoid> - - //plugin loading before finalizations - "onPluginBeforeFinalizationLoad": ODEvent_Default<() => ODPromiseVoid>, - "afterPluginBeforeFinalizationLoaded": ODEvent_Default<() => ODPromiseVoid>, - - //actions - "onActionLoad": ODEvent_Default<(actions:ODActionManager_Default) => ODPromiseVoid> - "afterActionsLoaded": ODEvent_Default<(actions:ODActionManager_Default) => ODPromiseVoid> - - //verifybars - "onVerifyBarLoad": ODEvent_Default<(verifybars:ODVerifyBarManager_Default) => ODPromiseVoid> - "afterVerifyBarsLoaded": ODEvent_Default<(verifybars:ODVerifyBarManager_Default) => ODPromiseVoid> - - //permissions - "onPermissionLoad": ODEvent_Default<(permissions:ODPermissionManager_Default) => ODPromiseVoid> - "afterPermissionsLoaded": ODEvent_Default<(permissions:ODPermissionManager_Default) => ODPromiseVoid> - - //posts - "onPostLoad": ODEvent_Default<(posts:ODPostManager_Default) => ODPromiseVoid> - "afterPostsLoaded": ODEvent_Default<(posts:ODPostManager_Default) => ODPromiseVoid> - "onPostInit": ODEvent_Default<(posts:ODPostManager_Default) => ODPromiseVoid> - "afterPostsInitiated": ODEvent_Default<(posts:ODPostManager_Default) => ODPromiseVoid> - - //cooldowns - "onCooldownLoad": ODEvent_Default<(cooldowns:ODCooldownManager_Default) => ODPromiseVoid> - "afterCooldownsLoaded": ODEvent_Default<(cooldowns:ODCooldownManager_Default) => ODPromiseVoid> - "onCooldownInit": ODEvent_Default<(cooldowns:ODCooldownManager_Default) => ODPromiseVoid> - "afterCooldownsInitiated": ODEvent_Default<(cooldowns:ODCooldownManager_Default) => ODPromiseVoid> - - //help menu - "onHelpMenuCategoryLoad": ODEvent_Default<(menu:ODHelpMenuManager_Default) => ODPromiseVoid> - "afterHelpMenuCategoriesLoaded": ODEvent_Default<(menu:ODHelpMenuManager_Default) => ODPromiseVoid> - "onHelpMenuComponentLoad": ODEvent_Default<(menu:ODHelpMenuManager_Default) => ODPromiseVoid> - "afterHelpMenuComponentsLoaded": ODEvent_Default<(menu:ODHelpMenuManager_Default) => ODPromiseVoid> - - //stats - "onStatScopeLoad": ODEvent_Default<(stats:ODStatsManager_Default) => ODPromiseVoid> - "afterStatScopesLoaded": ODEvent_Default<(stats:ODStatsManager_Default) => ODPromiseVoid> - "onStatLoad": ODEvent_Default<(stats:ODStatsManager_Default) => ODPromiseVoid> - "afterStatsLoaded": ODEvent_Default<(stats:ODStatsManager_Default) => ODPromiseVoid> - "onStatInit": ODEvent_Default<(stats:ODStatsManager_Default) => ODPromiseVoid> - "afterStatsInitiated": ODEvent_Default<(stats:ODStatsManager_Default) => ODPromiseVoid> - - //plugin loading before code - "onPluginBeforeCodeLoad": ODEvent_Default<() => ODPromiseVoid>, - "afterPluginBeforeCodeLoaded": ODEvent_Default<() => ODPromiseVoid>, - - //code - "onCodeLoad": ODEvent_Default<(code:ODCodeManager_Default) => ODPromiseVoid> - "afterCodeLoaded": ODEvent_Default<(code:ODCodeManager_Default) => ODPromiseVoid> - "onCodeExecute": ODEvent_Default<(code:ODCodeManager_Default) => ODPromiseVoid> - "afterCodeExecuted": ODEvent_Default<(code:ODCodeManager_Default) => ODPromiseVoid> - - //livestatus - "onLiveStatusSourceLoad": ODEvent_Default<(livestatus:ODLiveStatusManager_Default) => ODPromiseVoid> - "afterLiveStatusSourcesLoaded": ODEvent_Default<(livestatus:ODLiveStatusManager_Default) => ODPromiseVoid> - - //startscreen - "onStartScreenLoad": ODEvent_Default<(startscreen:ODStartScreenManager_Default) => ODPromiseVoid> - "afterStartScreensLoaded": ODEvent_Default<(startscreen:ODStartScreenManager_Default) => ODPromiseVoid> - "onStartScreenRender": ODEvent_Default<(startscreen:ODStartScreenManager_Default) => ODPromiseVoid> - "afterStartScreensRendered": ODEvent_Default<(startscreen:ODStartScreenManager_Default) => ODPromiseVoid> - - //ready - "beforeReadyForUsage": ODEvent_Default<() => ODPromiseVoid> - "onReadyForUsage": ODEvent_Default<() => ODPromiseVoid> -} - -/**## ODEventManager_Default `default_class` - * This is a special class that adds type definitions & typescript to the ODEvent class. - * It doesn't add any extra features! - * - * This default class is made for the global variable `opendiscord.events`! - */ -export class ODEventManager_Default extends ODEventManager { - get(id:StartScreenId): ODEventIds_Default[StartScreenId] - get(id:ODValidId): ODEvent|null - - get(id:ODValidId): ODEvent|null { - return super.get(id) - } - - remove(id:StartScreenId): ODEventIds_Default[StartScreenId] - remove(id:ODValidId): ODEvent|null - - remove(id:ODValidId): ODEvent|null { - return super.remove(id) - } - - exists(id:keyof ODEventIds_Default): boolean - exists(id:ODValidId): boolean - - exists(id:ODValidId): boolean { - return super.exists(id) - } -} - -/**## ODEventManager_Default `default_class` - * This is a special class that adds type definitions & typescript to the ODEvent class. - * It doesn't add any extra features! - * - * This default class is made for the global variable `opendiscord.events`! - */ -export class ODEvent_Default ODPromiseVoid)> extends ODEvent { - listen(callback:Callback): void { - return super.listen(callback) - } - listenOnce(callback:Callback): void { - return super.listenOnce(callback) - } - wait(): Promise> - wait(): Promise { - return super.wait() - } - emit(params:Parameters): Promise { - return super.emit(params) - } -} \ No newline at end of file diff --git a/src/core/api/defaults/flag.ts b/src/core/api/defaults/flag.ts deleted file mode 100644 index 8830cc2..0000000 --- a/src/core/api/defaults/flag.ts +++ /dev/null @@ -1,57 +0,0 @@ -/////////////////////////////////////// -//DEFAULT PROCESS MODULE -/////////////////////////////////////// -import { ODValidId } from "../modules/base" -import { ODFlagManager, ODFlag } from "../modules/flag" - -/**## ODFlagManagerIds_Default `interface` - * This interface is a list of ids available in the `ODFlagManager_Default` class. - * It's used to generate typescript declarations for this class. - */ -export interface ODFlagManagerIds_Default { - "opendiscord:no-migration":ODFlag, - "opendiscord:dev-config":ODFlag, - "opendiscord:dev-database":ODFlag, - "opendiscord:debug":ODFlag, - "opendiscord:crash":ODFlag, - "opendiscord:no-transcripts":ODFlag, - "opendiscord:no-checker":ODFlag, - "opendiscord:checker":ODFlag, - "opendiscord:no-easter":ODFlag, - "opendiscord:no-plugins":ODFlag, - "opendiscord:soft-plugins":ODFlag, - "opendiscord:force-slash-update":ODFlag, - "opendiscord:no-compile":ODFlag, - "opendiscord:compile-only":ODFlag, - "opendiscord:silent":ODFlag, - "opendiscord:cli":ODFlag, -} - -/**## ODFlagManager_Default `default_class` - * This is a special class that adds type definitions & typescript to the ODFlagManager class. - * It doesn't add any extra features! - * - * This default class is made for the global variable `opendiscord.flags`! - */ -export class ODFlagManager_Default extends ODFlagManager { - get(id:FlagId): ODFlagManagerIds_Default[FlagId] - get(id:ODValidId): ODFlag|null - - get(id:ODValidId): ODFlag|null { - return super.get(id) - } - - remove(id:FlagId): ODFlagManagerIds_Default[FlagId] - remove(id:ODValidId): ODFlag|null - - remove(id:ODValidId): ODFlag|null { - return super.remove(id) - } - - exists(id:keyof ODFlagManagerIds_Default): boolean - exists(id:ODValidId): boolean - - exists(id:ODValidId): boolean { - return super.exists(id) - } -} \ No newline at end of file diff --git a/src/core/api/defaults/helpmenu.ts b/src/core/api/defaults/helpmenu.ts deleted file mode 100644 index 9864f18..0000000 --- a/src/core/api/defaults/helpmenu.ts +++ /dev/null @@ -1,338 +0,0 @@ -/////////////////////////////////////// -//DEFAULT HELP MODULE -/////////////////////////////////////// -import { ODValidId } from "../modules/base" -import { ODHelpMenuCategory, ODHelpMenuCommandComponent, ODHelpMenuComponent, ODHelpMenuManager } from "../modules/helpmenu" - -/** (CONTRIBUTOR GUIDE) HOW TO ADD NEW COMMANDS? - * - Register the command in loadAllSlashCommands() & loadAllTextCommands() in (./src/data/framework/commandLoader.ts) - * - Add autocomplete for the command in OD(Slash/Text)CommandManagerIds_Default in (./src/core/api/defaults/client.ts) - * - Add the command to the help menu in (./src/data/framework/helpMenuLoader.ts) - * - If required, new config variables should be added (incl. logs, dm-logs & permissions). - * - Update the Open Ticket Documentation. - * - If the command contains complex logic or can be executed from a button/dropdown, it should be placed inside an `ODAction`. - * - Check all files, test the bot carefully & try a lot of different scenario's with different settings. - */ - -/**## ODHelpMenuManagerIds_Default `interface` - * This interface is a list of ids available in the `ODHelpMenuManager_Default` class. - * It's used to generate typescript declarations for this class. - */ -export interface ODHelpMenuManagerIds_Default { - "opendiscord:general":ODHelpMenuCategory_DefaultGeneral, - "opendiscord:ticket-basic":ODHelpMenuCategory_DefaultTicketBasic, - "opendiscord:ticket-advanced":ODHelpMenuCategory_DefaultTicketAdvanced, - "opendiscord:ticket-user":ODHelpMenuCategory_DefaultTicketUser, - "opendiscord:admin":ODHelpMenuCategory_DefaultAdmin, - "opendiscord:advanced":ODHelpMenuCategory_DefaultAdvanced, - "opendiscord:extra":ODHelpMenuCategory_DefaultExtra -} - -/**## ODHelpMenuManager_Default `default_class` - * This is a special class that adds type definitions & typescript to the ODHelpMenuManager class. - * It doesn't add any extra features! - * - * This default class is made for the global variable `opendiscord.helpmenu`! - */ -export class ODHelpMenuManager_Default extends ODHelpMenuManager { - get(id:HelpMenuCategoryId): ODHelpMenuManagerIds_Default[HelpMenuCategoryId] - get(id:ODValidId): ODHelpMenuCategory|null - - get(id:ODValidId): ODHelpMenuCategory|null { - return super.get(id) - } - - remove(id:HelpMenuCategoryId): ODHelpMenuManagerIds_Default[HelpMenuCategoryId] - remove(id:ODValidId): ODHelpMenuCategory|null - - remove(id:ODValidId): ODHelpMenuCategory|null { - return super.remove(id) - } - - exists(id:keyof ODHelpMenuManagerIds_Default): boolean - exists(id:ODValidId): boolean - - exists(id:ODValidId): boolean { - return super.exists(id) - } -} - -/**## ODHelpMenuManagerCategoryIds_DefaultGeneral `type` - * This interface is a list of ids available in the `ODHelpMenuCategory_DefaultGeneral` class. - * It's used to generate typescript declarations for this class. - */ -export interface ODHelpMenuManagerCategoryIds_DefaultGeneral { - "opendiscord:help":ODHelpMenuCommandComponent, - "opendiscord:ticket":ODHelpMenuCommandComponent|null -} - -/**## ODHelpMenuCategory_DefaultGeneral `default_class` - * This is a special class that adds type definitions & typescript to the ODHelpMenuManager class. - * It doesn't add any extra features! - * - * This default class is made for the `opendiscord:general` category in `opendiscord.helpmenu`! - */ -export class ODHelpMenuCategory_DefaultGeneral extends ODHelpMenuCategory { - get(id:HelpMenuCategoryId): ODHelpMenuManagerCategoryIds_DefaultGeneral[HelpMenuCategoryId] - get(id:ODValidId): ODHelpMenuComponent|null - - get(id:ODValidId): ODHelpMenuComponent|null { - return super.get(id) - } - - remove(id:HelpMenuCategoryId): ODHelpMenuManagerCategoryIds_DefaultGeneral[HelpMenuCategoryId] - remove(id:ODValidId): ODHelpMenuComponent|null - - remove(id:ODValidId): ODHelpMenuComponent|null { - return super.remove(id) - } - - exists(id:keyof ODHelpMenuManagerCategoryIds_DefaultGeneral): boolean - exists(id:ODValidId): boolean - - exists(id:ODValidId): boolean { - return super.exists(id) - } -} - -/**## ODHelpMenuManagerCategoryIds_DefaultTicketBasic `type` - * This interface is a list of ids available in the `ODHelpMenuCategory_DefaultTicketBasic` class. - * It's used to generate typescript declarations for this class. - */ -export interface ODHelpMenuManagerCategoryIds_DefaultTicketBasic { - "opendiscord:close":ODHelpMenuCommandComponent, - "opendiscord:delete":ODHelpMenuCommandComponent, - "opendiscord:reopen":ODHelpMenuCommandComponent -} - -/**## ODHelpMenuCategory_DefaultTicketBasic `default_class` - * This is a special class that adds type definitions & typescript to the ODHelpMenuManager class. - * It doesn't add any extra features! - * - * This default class is made for the `opendiscord:ticket` category in `opendiscord.helpmenu`! - */ -export class ODHelpMenuCategory_DefaultTicketBasic extends ODHelpMenuCategory { - get(id:HelpMenuCategoryId): ODHelpMenuManagerCategoryIds_DefaultTicketBasic[HelpMenuCategoryId] - get(id:ODValidId): ODHelpMenuComponent|null - - get(id:ODValidId): ODHelpMenuComponent|null { - return super.get(id) - } - - remove(id:HelpMenuCategoryId): ODHelpMenuManagerCategoryIds_DefaultTicketBasic[HelpMenuCategoryId] - remove(id:ODValidId): ODHelpMenuComponent|null - - remove(id:ODValidId): ODHelpMenuComponent|null { - return super.remove(id) - } - - exists(id:keyof ODHelpMenuManagerCategoryIds_DefaultTicketBasic): boolean - exists(id:ODValidId): boolean - - exists(id:ODValidId): boolean { - return super.exists(id) - } -} - -/**## ODHelpMenuManagerCategoryIds_DefaultTicketAdvanced `type` - * This interface is a list of ids available in the `ODHelpMenuCategory_DefaultTicketAdvanced` class. - * It's used to generate typescript declarations for this class. - */ -export interface ODHelpMenuManagerCategoryIds_DefaultTicketAdvanced { - "opendiscord:pin":ODHelpMenuCommandComponent, - "opendiscord:unpin":ODHelpMenuCommandComponent, - "opendiscord:move":ODHelpMenuCommandComponent, - "opendiscord:rename":ODHelpMenuCommandComponent -} - -/**## ODHelpMenuCategory_DefaultTicketAdvanced `default_class` - * This is a special class that adds type definitions & typescript to the ODHelpMenuManager class. - * It doesn't add any extra features! - * - * This default class is made for the `opendiscord:ticket` category in `opendiscord.helpmenu`! - */ -export class ODHelpMenuCategory_DefaultTicketAdvanced extends ODHelpMenuCategory { - get(id:HelpMenuCategoryId): ODHelpMenuManagerCategoryIds_DefaultTicketAdvanced[HelpMenuCategoryId] - get(id:ODValidId): ODHelpMenuComponent|null - - get(id:ODValidId): ODHelpMenuComponent|null { - return super.get(id) - } - - remove(id:HelpMenuCategoryId): ODHelpMenuManagerCategoryIds_DefaultTicketAdvanced[HelpMenuCategoryId] - remove(id:ODValidId): ODHelpMenuComponent|null - - remove(id:ODValidId): ODHelpMenuComponent|null { - return super.remove(id) - } - - exists(id:keyof ODHelpMenuManagerCategoryIds_DefaultTicketAdvanced): boolean - exists(id:ODValidId): boolean - - exists(id:ODValidId): boolean { - return super.exists(id) - } -} - -/**## ODHelpMenuManagerCategoryIds_DefaultTicketUser `type` - * This interface is a list of ids available in the `ODHelpMenuCategory_DefaultTicketUser` class. - * It's used to generate typescript declarations for this class. - */ -export interface ODHelpMenuManagerCategoryIds_DefaultTicketUser { - "opendiscord:claim":ODHelpMenuCommandComponent, - "opendiscord:unclaim":ODHelpMenuCommandComponent, - "opendiscord:add":ODHelpMenuCommandComponent, - "opendiscord:remove":ODHelpMenuCommandComponent, - "opendiscord:transfer":ODHelpMenuCommandComponent, -} - -/**## ODHelpMenuCategory_DefaultTicketUser `default_class` - * This is a special class that adds type definitions & typescript to the ODHelpMenuManager class. - * It doesn't add any extra features! - * - * This default class is made for the `opendiscord:ticket` category in `opendiscord.helpmenu`! - */ -export class ODHelpMenuCategory_DefaultTicketUser extends ODHelpMenuCategory { - get(id:HelpMenuCategoryId): ODHelpMenuManagerCategoryIds_DefaultTicketUser[HelpMenuCategoryId] - get(id:ODValidId): ODHelpMenuComponent|null - - get(id:ODValidId): ODHelpMenuComponent|null { - return super.get(id) - } - - remove(id:HelpMenuCategoryId): ODHelpMenuManagerCategoryIds_DefaultTicketUser[HelpMenuCategoryId] - remove(id:ODValidId): ODHelpMenuComponent|null - - remove(id:ODValidId): ODHelpMenuComponent|null { - return super.remove(id) - } - - exists(id:keyof ODHelpMenuManagerCategoryIds_DefaultTicketUser): boolean - exists(id:ODValidId): boolean - - exists(id:ODValidId): boolean { - return super.exists(id) - } -} - -/**## ODHelpMenuManagerCategoryIds_DefaultAdmin `type` - * This interface is a list of ids available in the `ODHelpMenuCategory_DefaultAdmin` class. - * It's used to generate typescript declarations for this class. - */ -export interface ODHelpMenuManagerCategoryIds_DefaultAdmin { - "opendiscord:panel":ODHelpMenuCommandComponent, - "opendiscord:blacklist-view":ODHelpMenuCommandComponent, - "opendiscord:blacklist-add":ODHelpMenuCommandComponent, - "opendiscord:blacklist-remove":ODHelpMenuCommandComponent, - "opendiscord:blacklist-get":ODHelpMenuCommandComponent -} - -/**## ODHelpMenuCategory_DefaultAdmin `default_class` - * This is a special class that adds type definitions & typescript to the ODHelpMenuManager class. - * It doesn't add any extra features! - * - * This default class is made for the `opendiscord:admin` category in `opendiscord.helpmenu`! - */ -export class ODHelpMenuCategory_DefaultAdmin extends ODHelpMenuCategory { - get(id:HelpMenuCategoryId): ODHelpMenuManagerCategoryIds_DefaultAdmin[HelpMenuCategoryId] - get(id:ODValidId): ODHelpMenuComponent|null - - get(id:ODValidId): ODHelpMenuComponent|null { - return super.get(id) - } - - remove(id:HelpMenuCategoryId): ODHelpMenuManagerCategoryIds_DefaultAdmin[HelpMenuCategoryId] - remove(id:ODValidId): ODHelpMenuComponent|null - - remove(id:ODValidId): ODHelpMenuComponent|null { - return super.remove(id) - } - - exists(id:keyof ODHelpMenuManagerCategoryIds_DefaultAdmin): boolean - exists(id:ODValidId): boolean - - exists(id:ODValidId): boolean { - return super.exists(id) - } -} - -/**## ODHelpMenuManagerCategoryIds_DefaultAdvanced `type` - * This interface is a list of ids available in the `ODHelpMenuCategory_DefaultAdvanced` class. - * It's used to generate typescript declarations for this class. - */ -export interface ODHelpMenuManagerCategoryIds_DefaultAdvanced { - "opendiscord:stats-global":ODHelpMenuCommandComponent, - "opendiscord:stats-reset":ODHelpMenuCommandComponent, - "opendiscord:stats-ticket":ODHelpMenuCommandComponent, - "opendiscord:stats-user":ODHelpMenuCommandComponent, - "opendiscord:autoclose-disable":ODHelpMenuCommandComponent, - "opendiscord:autoclose-enable":ODHelpMenuCommandComponent, - "opendiscord:autodelete-disable":ODHelpMenuCommandComponent, - "opendiscord:autodelete-enable":ODHelpMenuCommandComponent, - "opendiscord:topic-set":ODHelpMenuCommandComponent, - "opendiscord:priority-set":ODHelpMenuCommandComponent, -} - -/**## ODHelpMenuCategory_DefaultAdvanced `default_class` - * This is a special class that adds type definitions & typescript to the ODHelpMenuManager class. - * It doesn't add any extra features! - * - * This default class is made for the `opendiscord:advanced` category in `opendiscord.helpmenu`! - */ -export class ODHelpMenuCategory_DefaultAdvanced extends ODHelpMenuCategory { - get(id:HelpMenuCategoryId): ODHelpMenuManagerCategoryIds_DefaultAdvanced[HelpMenuCategoryId] - get(id:ODValidId): ODHelpMenuComponent|null - - get(id:ODValidId): ODHelpMenuComponent|null { - return super.get(id) - } - - remove(id:HelpMenuCategoryId): ODHelpMenuManagerCategoryIds_DefaultAdvanced[HelpMenuCategoryId] - remove(id:ODValidId): ODHelpMenuComponent|null - - remove(id:ODValidId): ODHelpMenuComponent|null { - return super.remove(id) - } - - exists(id:keyof ODHelpMenuManagerCategoryIds_DefaultAdvanced): boolean - exists(id:ODValidId): boolean - - exists(id:ODValidId): boolean { - return super.exists(id) - } -} - -/**## ODHelpMenuManagerCategoryIds_DefaultExtra `type` - * This interface is a list of ids available in the `ODHelpMenuCategory_DefaultExtra` class. - * It's used to generate typescript declarations for this class. - */ -export interface ODHelpMenuManagerCategoryIds_DefaultExtra {} - -/**## ODHelpMenuCategory_DefaultExtra `default_class` - * This is a special class that adds type definitions & typescript to the ODHelpMenuManager class. - * It doesn't add any extra features! - * - * This default class is made for the `opendiscord:general` category in `opendiscord.helpmenu`! - */ -export class ODHelpMenuCategory_DefaultExtra extends ODHelpMenuCategory { - get(id:HelpMenuCategoryId): ODHelpMenuManagerCategoryIds_DefaultExtra[HelpMenuCategoryId] - get(id:ODValidId): ODHelpMenuComponent|null - - get(id:ODValidId): ODHelpMenuComponent|null { - return super.get(id) - } - - remove(id:HelpMenuCategoryId): ODHelpMenuManagerCategoryIds_DefaultExtra[HelpMenuCategoryId] - remove(id:ODValidId): ODHelpMenuComponent|null - - remove(id:ODValidId): ODHelpMenuComponent|null { - return super.remove(id) - } - - exists(id:keyof ODHelpMenuManagerCategoryIds_DefaultExtra): boolean - exists(id:ODValidId): boolean - - exists(id:ODValidId): boolean { - return super.exists(id) - } -} \ No newline at end of file diff --git a/src/core/api/defaults/permission.ts b/src/core/api/defaults/permission.ts deleted file mode 100644 index 2a67d3e..0000000 --- a/src/core/api/defaults/permission.ts +++ /dev/null @@ -1,31 +0,0 @@ -/////////////////////////////////////// -//DEFAULT PERMISSION MODULE -/////////////////////////////////////// -import { ODDebugger } from "../modules/console" -import { ODPermissionManager } from "../modules/permission" -import { ODClientManager_Default } from "./client" - -/**## ODPermissionManager_Default `default_class` - * This is a special class that adds type definitions & typescript to the ODPermissionManager class. - * It doesn't add any extra features! - * - * This default class is made for the global variable `opendiscord.permissions`! - */ -export class ODPermissionManager_Default extends ODPermissionManager { - constructor(debug:ODDebugger,client:ODClientManager_Default){ - super(debug,client,true) - } -} - -/**## ODPermissionEmbedType `type` - * This type contains all types available in the `opendiscord:no-permissions` embed. - */ -export type ODPermissionEmbedType = ( - "developer"| - "owner"| - "admin"| - "moderator"| - "support"| - "member"| - "discord-administrator" -) \ No newline at end of file diff --git a/src/core/api/defaults/plugin.ts b/src/core/api/defaults/plugin.ts deleted file mode 100644 index b9644b4..0000000 --- a/src/core/api/defaults/plugin.ts +++ /dev/null @@ -1,77 +0,0 @@ -/////////////////////////////////////// -//DEFAULT POST MODULE -/////////////////////////////////////// -import { ODValidId, ODManagerData } from "../modules/base" -import { ODPlugin, ODPluginClassManager, ODPluginManager } from "../modules/plugin" - -/**## ODPluginManagerIds_Default `interface` - * This interface is a list of ids available in the `ODPluginManager_Default` class. - * It's used to generate typescript declarations for this class. - */ -export interface ODPluginManagerIds_Default {} - -/**## ODPluginManager_Default `default_class` - * This is a special class that adds type definitions & typescript to the ODPluginManager class. - * It doesn't add any extra features! - * - * This default class is made for the global variable `opendiscord.plugins`! - */ -export class ODPluginManager_Default extends ODPluginManager { - declare classes: ODPluginClassManager_Default - - get(id:PluginId): ODPluginManagerIds_Default[PluginId] - get(id:ODValidId): ODPlugin|null - - get(id:ODValidId): ODPlugin|null { - return super.get(id) - } - - remove(id:PluginId): ODPluginManagerIds_Default[PluginId] - remove(id:ODValidId): ODPlugin|null - - remove(id:ODValidId): ODPlugin|null { - return super.remove(id) - } - - exists(id:keyof ODPluginManagerIds_Default): boolean - exists(id:ODValidId): boolean - - exists(id:ODValidId): boolean { - return super.exists(id) - } -} - -/**## ODPluginClassManagerIds_Default `interface` - * This interface is a list of ids available in the `ODPluginClassManager_Default` class. - * It's used to generate typescript declarations for this class. - */ -export interface ODPluginClassManagerIds_Default {} - -/**## ODPluginClassManager_Default `default_class` - * This is a special class that adds type definitions & typescript to the ODPluginClassManager class. - * It doesn't add any extra features! - * - * This default class is made for the global variable `opendiscord.plugins.classes`! - */ -export class ODPluginClassManager_Default extends ODPluginClassManager { - get(id:PluginClassId): ODPluginClassManagerIds_Default[PluginClassId] - get(id:ODValidId): ODManagerData|null - - get(id:ODValidId): ODManagerData|null { - return super.get(id) - } - - remove(id:PluginClassId): ODPluginClassManagerIds_Default[PluginClassId] - remove(id:ODValidId): ODManagerData|null - - remove(id:ODValidId): ODManagerData|null { - return super.remove(id) - } - - exists(id:keyof ODPluginClassManagerIds_Default): boolean - exists(id:ODValidId): boolean - - exists(id:ODValidId): boolean { - return super.exists(id) - } -} \ No newline at end of file diff --git a/src/core/api/defaults/post.ts b/src/core/api/defaults/post.ts deleted file mode 100644 index bdc64db..0000000 --- a/src/core/api/defaults/post.ts +++ /dev/null @@ -1,44 +0,0 @@ -/////////////////////////////////////// -//DEFAULT POST MODULE -/////////////////////////////////////// -import { ODValidId } from "../modules/base" -import { ODPost, ODPostManager } from "../modules/post" -import * as discord from "discord.js" - -/**## ODPostManagerIds_Default `interface` - * This interface is a list of ids available in the `ODPostManager_Default` class. - * It's used to generate typescript declarations for this class. - */ -export interface ODPostManagerIds_Default { - "opendiscord:logs":ODPost|null, - "opendiscord:transcripts":ODPost|null -} - -/**## ODPostManager_Default `default_class` - * This is a special class that adds type definitions & typescript to the ODPostManager class. - * It doesn't add any extra features! - * - * This default class is made for the global variable `opendiscord.code`! - */ -export class ODPostManager_Default extends ODPostManager { - get(id:PostId): ODPostManagerIds_Default[PostId] - get(id:ODValidId): ODPost|null - - get(id:ODValidId): ODPost|null { - return super.get(id) - } - - remove(id:PostId): ODPostManagerIds_Default[PostId] - remove(id:ODValidId): ODPost|null - - remove(id:ODValidId): ODPost|null { - return super.remove(id) - } - - exists(id:keyof ODPostManagerIds_Default): boolean - exists(id:ODValidId): boolean - - exists(id:ODValidId): boolean { - return super.exists(id) - } -} \ No newline at end of file diff --git a/src/core/api/defaults/progressbar.ts b/src/core/api/defaults/progressbar.ts deleted file mode 100644 index f353472..0000000 --- a/src/core/api/defaults/progressbar.ts +++ /dev/null @@ -1,171 +0,0 @@ -/////////////////////////////////////// -//DEFAULT PROGRESS BAR MODULE -/////////////////////////////////////// -import { ODValidId } from "../modules/base" -import { ODValidConsoleColor } from "../modules/console" -import { ODManualProgressBar, ODProgressBar, ODProgressBarManager, ODProgressBarRenderer, ODProgressBarRendererManager } from "../modules/progressbar" -import ansis from "ansis" - -/**## ODProgressBarRenderer_DefaultSettingsLabel `type` - * All available label types for the default progress bar renderer - */ -export type ODProgressBarRenderer_DefaultSettingsLabel = "value"|"percentage"|"fraction"|"time-ms"|"time-sec"|"time-min" - -/**## ODProgressBarRenderer_DefaultSettings `interface` - * This interface contains the settings for the default progress bar renderer. - */ -export interface ODProgressBarRenderer_DefaultSettings { - /**The color of the progress bar border. */ - borderColor:ODValidConsoleColor|"openticket", - /**The color of the progress bar (filled side). */ - filledBarColor:ODValidConsoleColor|"openticket", - /**The color of the progress bar (empty side). */ - emptyBarColor:ODValidConsoleColor|"openticket", - /**The color of the text before the progress bar. */ - prefixColor:ODValidConsoleColor|"openticket", - /**The color of the text after the progress bar. */ - suffixColor:ODValidConsoleColor|"openticket", - /**The color of the progress bar label. */ - labelColor:ODValidConsoleColor|"openticket", - - /**The character used in the left border. */ - leftBorderChar:string, - /**The character used in the right border. */ - rightBorderChar:string, - /**The character used in the filled side of the progress bar. */ - filledBarChar:string, - /**The character used in the empty side of the progress bar. */ - emptyBarChar:string, - /**The label type. (will show a number related to the progress) */ - labelType:ODProgressBarRenderer_DefaultSettingsLabel, - /**The position of the label. */ - labelPosition:"start"|"end", - /**The width of the bar. (50 characters by default) */ - barWidth:number, - - /**Show the bar. */ - showBar:boolean, - /**Show the label. */ - showLabel:boolean, - /**Show the border. */ - showBorder:boolean, -} - -export class ODProgressBarRenderer_Default extends ODProgressBarRenderer { - constructor(id:ODValidId,settings:ODProgressBarRenderer_DefaultSettings){ - super(id,(settings,min,max,value,rawPrefix,rawSuffix) => { - const percentage = (value-min)/(max-min) - const barLevel = Math.round(percentage*settings.barWidth) - - const borderAnsis = (settings.borderColor == "openticket") ? ansis.hex("#f8ba00") : ansis[settings.borderColor] - const filledBarAnsis = (settings.filledBarColor == "openticket") ? ansis.hex("#f8ba00") : ansis[settings.filledBarColor] - const emptyBarAnsis = (settings.emptyBarColor == "openticket") ? ansis.hex("#f8ba00") : ansis[settings.emptyBarColor] - const labelAnsis = (settings.labelColor == "openticket") ? ansis.hex("#f8ba00") : ansis[settings.labelColor] - const prefixAnsis = (settings.prefixColor == "openticket") ? ansis.hex("#f8ba00") : ansis[settings.prefixColor] - const suffixAnsis = (settings.suffixColor == "openticket") ? ansis.hex("#f8ba00") : ansis[settings.suffixColor] - - const leftBorder = (settings.showBorder) ? borderAnsis(settings.leftBorderChar) : "" - const rightBorder = (settings.showBorder) ? borderAnsis(settings.rightBorderChar) : "" - const bar = (settings.showBar) ? filledBarAnsis(settings.filledBarChar.repeat(barLevel))+emptyBarAnsis(settings.emptyBarChar.repeat(settings.barWidth-barLevel)) : "" - const prefix = (rawPrefix) ? prefixAnsis(rawPrefix)+" " : "" - const suffix = (rawSuffix) ? " "+suffixAnsis(rawSuffix) : "" - let label: string - if (!settings.showLabel) label = "" - if (settings.labelType == "fraction") label = labelAnsis(value+"/"+max) - else if (settings.labelType == "percentage") label = labelAnsis(Math.round(percentage*100)+"%") - else if (settings.labelType == "time-ms") label = labelAnsis(value+"ms") - else if (settings.labelType == "time-sec") label = labelAnsis(Math.round(value*10)/10+"sec") - else if (settings.labelType == "time-min") label = labelAnsis(Math.round(value*10)/10+"min") - else label = labelAnsis(value.toString()) - - const labelWithPrefixAndSuffix = prefix+label+suffix - return (settings.labelPosition == "start") ? labelWithPrefixAndSuffix+" "+leftBorder+bar+rightBorder : leftBorder+bar+rightBorder+" "+labelWithPrefixAndSuffix - },settings) - } -} - -/**## ODProgressBarRendererManagerIds_Default `interface` - * This interface is a list of ids available in the `ODProgressBarRendererManager_Default` class. - * It's used to generate typescript declarations for this class. - */ -export interface ODProgressBarRendererManagerIds_Default { - "opendiscord:value-renderer":ODProgressBarRenderer_Default, - "opendiscord:fraction-renderer":ODProgressBarRenderer_Default, - "opendiscord:percentage-renderer":ODProgressBarRenderer_Default, - "opendiscord:time-ms-renderer":ODProgressBarRenderer_Default, - "opendiscord:time-sec-renderer":ODProgressBarRenderer_Default, - "opendiscord:time-min-renderer":ODProgressBarRenderer_Default, -} - -/**## ODProgressBarRendererManager_Default `default_class` - * This is a special class that adds type definitions & typescript to the ODProgressBarRendererManager class. - * It doesn't add any extra features! - * - * This default class is made for the global variable `opendiscord.progressbars.renderers`! - */ -export class ODProgressBarRendererManager_Default extends ODProgressBarRendererManager { - get(id:ProgressBarId): ODProgressBarRendererManagerIds_Default[ProgressBarId] - get(id:ODValidId): ODProgressBarRenderer<{}>|null - - get(id:ODValidId): ODProgressBarRenderer<{}>|null { - return super.get(id) - } - - remove(id:ProgressBarId): ODProgressBarRendererManagerIds_Default[ProgressBarId] - remove(id:ODValidId): ODProgressBarRenderer<{}>|null - - remove(id:ODValidId): ODProgressBarRenderer<{}>|null { - return super.remove(id) - } - - exists(id:keyof ODProgressBarRendererManagerIds_Default): boolean - exists(id:ODValidId): boolean - - exists(id:ODValidId): boolean { - return super.exists(id) - } -} - -/**## ODProgressBarManagerIds_Default `interface` - * This interface is a list of ids available in the `ODProgressBarManager_Default` class. - * It's used to generate typescript declarations for this class. - */ -export interface ODProgressBarManagerIds_Default { - "opendiscord:slash-command-remove":ODManualProgressBar, - "opendiscord:slash-command-create":ODManualProgressBar, - "opendiscord:slash-command-update":ODManualProgressBar, - "opendiscord:context-menu-remove":ODManualProgressBar, - "opendiscord:context-menu-create":ODManualProgressBar, - "opendiscord:context-menu-update":ODManualProgressBar, -} - -/**## ODProgressBarManager_Default `default_class` - * This is a special class that adds type definitions & typescript to the ODProgressBarManager class. - * It doesn't add any extra features! - * - * This default class is made for the global variable `opendiscord.progressbars`! - */ -export class ODProgressBarManager_Default extends ODProgressBarManager { - declare renderers: ODProgressBarRendererManager_Default - - get(id:ProgressBarId): ODProgressBarManagerIds_Default[ProgressBarId] - get(id:ODValidId): ODProgressBar|null - - get(id:ODValidId): ODProgressBar|null { - return super.get(id) - } - - remove(id:ProgressBarId): ODProgressBarManagerIds_Default[ProgressBarId] - remove(id:ODValidId): ODProgressBar|null - - remove(id:ODValidId): ODProgressBar|null { - return super.remove(id) - } - - exists(id:keyof ODProgressBarManagerIds_Default): boolean - exists(id:ODValidId): boolean - - exists(id:ODValidId): boolean { - return super.exists(id) - } -} \ No newline at end of file diff --git a/src/core/api/defaults/responder.ts b/src/core/api/defaults/responder.ts deleted file mode 100644 index 8d7b317..0000000 --- a/src/core/api/defaults/responder.ts +++ /dev/null @@ -1,355 +0,0 @@ -/////////////////////////////////////// -//DEFAULT RESPONDER MODULE -/////////////////////////////////////// -import { ODValidId } from "../modules/base" -import { ODAutocompleteResponder, ODAutocompleteResponderInstance, ODAutocompleteResponderManager, ODButtonResponder, ODButtonResponderInstance, ODButtonResponderManager, ODCommandResponder, ODCommandResponderInstance, ODCommandResponderManager, ODContextMenuResponder, ODContextMenuResponderInstance, ODContextMenuResponderManager, ODDropdownResponder, ODDropdownResponderInstance, ODDropdownResponderManager, ODModalResponder, ODModalResponderInstance, ODModalResponderManager, ODResponderManager } from "../modules/responder" -import { ODWorkerManager_Default } from "./worker" - -/**## ODResponderManager_Default `default_class` - * This is a special class that adds type definitions & typescript to the ODResponderManager class. - * It doesn't add any extra features! - * - * This default class is made for the global variable `opendiscord.responders`! - */ -export class ODResponderManager_Default extends ODResponderManager { - declare commands: ODCommandResponderManager_Default - declare buttons: ODButtonResponderManager_Default - declare dropdowns: ODDropdownResponderManager_Default - declare modals: ODModalResponderManager_Default - declare contextMenus: ODContextMenuResponderManager_Default - declare autocomplete: ODAutocompleteResponderManager_Default -} - -/**## ODCommandResponderManagerIds_Default `interface` - * This interface is a list of ids available in the `ODCommandResponderManager_Default` class. - * It's used to generate typescript declarations for this class. - */ -export interface ODCommandResponderManagerIds_Default { - "opendiscord:help":{source:"slash"|"text",params:{},workers:"opendiscord:permissions"|"opendiscord:help"|"opendiscord:logs"}, - "opendiscord:stats":{source:"slash"|"text",params:{},workers:"opendiscord:permissions"|"opendiscord:stats"|"opendiscord:logs"}, - "opendiscord:panel":{source:"slash"|"text",params:{},workers:"opendiscord:permissions"|"opendiscord:panel"|"opendiscord:logs"}, - "opendiscord:ticket":{source:"slash"|"text",params:{},workers:"opendiscord:permissions"|"opendiscord:ticket"|"opendiscord:logs"}, - "opendiscord:blacklist":{source:"slash"|"text",params:{},workers:"opendiscord:permissions"|"opendiscord:blacklist"|"opendiscord:discord-logs"|"opendiscord:logs"}, - - "opendiscord:close":{source:"slash"|"text",params:{},workers:"opendiscord:permissions"|"opendiscord:close"|"opendiscord:logs"}, - "opendiscord:reopen":{source:"slash"|"text",params:{},workers:"opendiscord:permissions"|"opendiscord:reopen"|"opendiscord:logs"}, - "opendiscord:delete":{source:"slash"|"text",params:{},workers:"opendiscord:permissions"|"opendiscord:delete"|"opendiscord:logs"}, - "opendiscord:claim":{source:"slash"|"text",params:{},workers:"opendiscord:permissions"|"opendiscord:claim"|"opendiscord:logs"}, - "opendiscord:unclaim":{source:"slash"|"text",params:{},workers:"opendiscord:permissions"|"opendiscord:unclaim"|"opendiscord:logs"}, - "opendiscord:pin":{source:"slash"|"text",params:{},workers:"opendiscord:permissions"|"opendiscord:pin"|"opendiscord:logs"}, - "opendiscord:unpin":{source:"slash"|"text",params:{},workers:"opendiscord:permissions"|"opendiscord:unpin"|"opendiscord:logs"}, - - "opendiscord:rename":{source:"slash"|"text",params:{},workers:"opendiscord:permissions"|"opendiscord:rename"|"opendiscord:logs"}, - "opendiscord:move":{source:"slash"|"text",params:{},workers:"opendiscord:permissions"|"opendiscord:move"|"opendiscord:logs"}, - "opendiscord:add":{source:"slash"|"text",params:{},workers:"opendiscord:permissions"|"opendiscord:add"|"opendiscord:logs"}, - "opendiscord:remove":{source:"slash"|"text",params:{},workers:"opendiscord:permissions"|"opendiscord:remove"|"opendiscord:logs"}, - "opendiscord:clear":{source:"slash"|"text",params:{},workers:"opendiscord:permissions"|"opendiscord:clear"|"opendiscord:logs"}, - "opendiscord:topic":{source:"slash"|"text",params:{},workers:"opendiscord:permissions"|"opendiscord:topic"|"opendiscord:logs"}, - "opendiscord:priority":{source:"slash"|"text",params:{},workers:"opendiscord:permissions"|"opendiscord:priority"|"opendiscord:logs"}, - "opendiscord:transfer":{source:"slash"|"text",params:{},workers:"opendiscord:permissions"|"opendiscord:transfer"|"opendiscord:logs"}, - - "opendiscord:autoclose":{source:"slash"|"text",params:{},workers:"opendiscord:permissions"|"opendiscord:autoclose"|"opendiscord:logs"}, - "opendiscord:autodelete":{source:"slash"|"text",params:{},workers:"opendiscord:permissions"|"opendiscord:autodelete"|"opendiscord:logs"}, -} - -/**## ODCommandResponderManager_Default `default_class` - * This is a special class that adds type definitions & typescript to the ODCommandResponderManager class. - * It doesn't add any extra features! - * - * This default class is made for the global variable `opendiscord.responders.commands`! - */ -export class ODCommandResponderManager_Default extends ODCommandResponderManager { - get(id:CommandResponderId): ODCommandResponder_Default - get(id:ODValidId): ODCommandResponder<"slash"|"text",any>|null - - get(id:ODValidId): ODCommandResponder<"slash"|"text",any>|null { - return super.get(id) - } - - remove(id:CommandResponderId): ODCommandResponder_Default - remove(id:ODValidId): ODCommandResponder<"slash"|"text",any>|null - - remove(id:ODValidId): ODCommandResponder<"slash"|"text",any>|null { - return super.remove(id) - } - - exists(id:keyof ODCommandResponderManagerIds_Default): boolean - exists(id:ODValidId): boolean - - exists(id:ODValidId): boolean { - return super.exists(id) - } -} - -/**## ODCommandResponder_Default `default_class` - * This is a special class that adds type definitions & typescript to the ODCommandResponder class. - * It doesn't add any extra features! - * - * This default class is made for the default `ODCommandResponder`'s! - */ -export class ODCommandResponder_Default extends ODCommandResponder { - declare workers: ODWorkerManager_Default -} - -/**## ODButtonResponderManagerIds_Default `interface` - * This interface is a list of ids available in the `ODButtonResponderManager_Default` class. - * It's used to generate typescript declarations for this class. - */ -export interface ODButtonResponderManagerIds_Default { - "opendiscord:verifybar-success":{source:"button",params:{},workers:"opendiscord:handle-verifybar"}, - "opendiscord:verifybar-failure":{source:"button",params:{},workers:"opendiscord:handle-verifybar"}, - - "opendiscord:help-menu-switch":{source:"button",params:{},workers:"opendiscord:update-help-menu"}, - "opendiscord:help-menu-previous":{source:"button",params:{},workers:"opendiscord:update-help-menu"}, - "opendiscord:help-menu-next":{source:"button",params:{},workers:"opendiscord:update-help-menu"}, - - "opendiscord:ticket-option":{source:"button",params:{},workers:"opendiscord:ticket-option"}, - "opendiscord:role-option":{source:"button",params:{},workers:"opendiscord:role-option"}, - - "opendiscord:claim-ticket":{source:"button",params:{},workers:"opendiscord:claim-ticket"}, - "opendiscord:unclaim-ticket":{source:"button",params:{},workers:"opendiscord:unclaim-ticket"}, - "opendiscord:pin-ticket":{source:"button",params:{},workers:"opendiscord:pin-ticket"}, - "opendiscord:unpin-ticket":{source:"button",params:{},workers:"opendiscord:unpin-ticket"}, - "opendiscord:close-ticket":{source:"button",params:{},workers:"opendiscord:close-ticket"}, - "opendiscord:reopen-ticket":{source:"button",params:{},workers:"opendiscord:reopen-ticket"}, - "opendiscord:delete-ticket":{source:"button",params:{},workers:"opendiscord:delete-ticket"}, - - "opendiscord:transcript-error-retry":{source:"button",params:{},workers:"opendiscord:permissions"|"opendiscord:delete-ticket"|"opendiscord:logs"}, - "opendiscord:transcript-error-continue":{source:"button",params:{},workers:"opendiscord:permissions"|"opendiscord:delete-ticket"|"opendiscord:logs"}, - "opendiscord:clear-continue":{source:"button",params:{},workers:"opendiscord:clear-continue"}, -} - -/**## ODButtonResponderManager_Default `default_class` - * This is a special class that adds type definitions & typescript to the ODButtonResponderManager class. - * It doesn't add any extra features! - * - * This default class is made for the global variable `opendiscord.responders.buttons`! - */ -export class ODButtonResponderManager_Default extends ODButtonResponderManager { - get(id:ButtonResponderId): ODButtonResponder_Default - get(id:ODValidId): ODButtonResponder<"button",any>|null - - get(id:ODValidId): ODButtonResponder<"button",any>|null { - return super.get(id) - } - - remove(id:ButtonResponderId): ODButtonResponder_Default - remove(id:ODValidId): ODButtonResponder<"button",any>|null - - remove(id:ODValidId): ODButtonResponder<"button",any>|null { - return super.remove(id) - } - - exists(id:keyof ODButtonResponderManagerIds_Default): boolean - exists(id:ODValidId): boolean - - exists(id:ODValidId): boolean { - return super.exists(id) - } -} - -/**## ODButtonResponder_Default `default_class` - * This is a special class that adds type definitions & typescript to the ODButtonResponder class. - * It doesn't add any extra features! - * - * This default class is made for the default `ODButtonResponder`'s! - */ -export class ODButtonResponder_Default extends ODButtonResponder { - declare workers: ODWorkerManager_Default -} - -/**## ODDropdownResponderManagerIds_Default `interface` - * This interface is a list of ids available in the `ODDropdownResponderManager_Default` class. - * It's used to generate typescript declarations for this class. - */ -export interface ODDropdownResponderManagerIds_Default { - "opendiscord:panel-dropdown-tickets":{source:"dropdown",params:{},workers:"opendiscord:panel-dropdown-tickets"}, -} - -/**## ODDropdownResponderManager_Default `default_class` - * This is a special class that adds type definitions & typescript to the ODDropdownResponderManager class. - * It doesn't add any extra features! - * - * This default class is made for the global variable `opendiscord.responders.dropdowns`! - */ -export class ODDropdownResponderManager_Default extends ODDropdownResponderManager { - get(id:DropdownResponderId): ODDropdownResponder_Default - get(id:ODValidId): ODDropdownResponder<"dropdown",any>|null - - get(id:ODValidId): ODDropdownResponder<"dropdown",any>|null { - return super.get(id) - } - - remove(id:DropdownResponderId): ODDropdownResponder_Default - remove(id:ODValidId): ODDropdownResponder<"dropdown",any>|null - - remove(id:ODValidId): ODDropdownResponder<"dropdown",any>|null { - return super.remove(id) - } - - exists(id:keyof ODDropdownResponderManagerIds_Default): boolean - exists(id:ODValidId): boolean - - exists(id:ODValidId): boolean { - return super.exists(id) - } -} - -/**## ODDropdownResponder_Default `default_class` - * This is a special class that adds type definitions & typescript to the ODDropdownResponder class. - * It doesn't add any extra features! - * - * This default class is made for the default `ODDropdownResponder`'s! - */ -export class ODDropdownResponder_Default extends ODDropdownResponder { - declare workers: ODWorkerManager_Default -} - -/**## ODModalResponderManagerIds_Default `interface` - * This interface is a list of ids available in the `ODModalResponderManager_Default` class. - * It's used to generate typescript declarations for this class. - */ -export interface ODModalResponderManagerIds_Default { - "opendiscord:ticket-questions":{source:"modal",params:{},workers:"opendiscord:ticket-questions"}, - "opendiscord:close-ticket-reason":{source:"modal",params:{},workers:"opendiscord:close-ticket-reason"}, - "opendiscord:reopen-ticket-reason":{source:"modal",params:{},workers:"opendiscord:reopen-ticket-reason"}, - "opendiscord:delete-ticket-reason":{source:"modal",params:{},workers:"opendiscord:delete-ticket-reason"}, - "opendiscord:claim-ticket-reason":{source:"modal",params:{},workers:"opendiscord:claim-ticket-reason"}, - "opendiscord:unclaim-ticket-reason":{source:"modal",params:{},workers:"opendiscord:unclaim-ticket-reason"}, - "opendiscord:pin-ticket-reason":{source:"modal",params:{},workers:"opendiscord:pin-ticket-reason"}, - "opendiscord:unpin-ticket-reason":{source:"modal",params:{},workers:"opendiscord:unpin-ticket-reason"}, -} - -/**## ODModalResponderManager_Default `default_class` - * This is a special class that adds type definitions & typescript to the ODModalResponderManager class. - * It doesn't add any extra features! - * - * This default class is made for the global variable `opendiscord.responders.dropdowns`! - */ -export class ODModalResponderManager_Default extends ODModalResponderManager { - get(id:ModalResponderId): ODModalResponder_Default - get(id:ODValidId): ODModalResponder<"modal",any>|null - - get(id:ODValidId): ODModalResponder<"modal",any>|null { - return super.get(id) - } - - remove(id:ModalResponderId): ODModalResponder_Default - remove(id:ODValidId): ODModalResponder<"modal",any>|null - - remove(id:ODValidId): ODModalResponder<"modal",any>|null { - return super.remove(id) - } - - exists(id:keyof ODModalResponderManagerIds_Default): boolean - exists(id:ODValidId): boolean - - exists(id:ODValidId): boolean { - return super.exists(id) - } -} - -/**## ODModalResponder_Default `default_class` - * This is a special class that adds type definitions & typescript to the ODModalResponder class. - * It doesn't add any extra features! - * - * This default class is made for the default `ODModalResponder`'s! - */ -export class ODModalResponder_Default extends ODModalResponder { - declare workers: ODWorkerManager_Default -} - -/**## ODContextMenuResponderManagerIds_Default `interface` - * This interface is a list of ids available in the `ODContextMenuResponderManager_Default` class. - * It's used to generate typescript declarations for this class. - */ -export interface ODContextMenuResponderManagerIds_Default { - //"opendiscord:example":{source:"context-menu",params:{},workers:"opendiscord:example"}, -} - -/**## ODContextMenuResponderManager_Default `default_class` - * This is a special class that adds type definitions & typescript to the ODContextMenuResponderManager class. - * It doesn't add any extra features! - * - * This default class is made for the global variable `opendiscord.responders.contextMenus`! - */ -export class ODContextMenuResponderManager_Default extends ODContextMenuResponderManager { - get(id:ModalResponderId): ODContextMenuResponder_Default - get(id:ODValidId): ODContextMenuResponder<"context-menu",any>|null - - get(id:ODValidId): ODContextMenuResponder<"context-menu",any>|null { - return super.get(id) - } - - remove(id:ModalResponderId): ODContextMenuResponder_Default - remove(id:ODValidId): ODContextMenuResponder<"context-menu",any>|null - - remove(id:ODValidId): ODContextMenuResponder<"context-menu",any>|null { - return super.remove(id) - } - - exists(id:keyof ODContextMenuResponderManagerIds_Default): boolean - exists(id:ODValidId): boolean - - exists(id:ODValidId): boolean { - return super.exists(id) - } -} - -/**## ODContextMenuResponder_Default `default_class` - * This is a special class that adds type definitions & typescript to the ODContextMenuResponder class. - * It doesn't add any extra features! - * - * This default class is made for the default `ODContextMenuResponder`'s! - */ -export class ODContextMenuResponder_Default extends ODContextMenuResponder { - declare workers: ODWorkerManager_Default -} - -/**## ODAutocompleteResponderManagerIds_Default `interface` - * This interface is a list of ids available in the `ODAutocompleteResponderManager_Default` class. - * It's used to generate typescript declarations for this class. - */ -export interface ODAutocompleteResponderManagerIds_Default { - "opendiscord:panel-id":{source:"autocomplete",params:{},workers:"opendiscord:panel-id"}, - "opendiscord:option-id":{source:"autocomplete",params:{},workers:"opendiscord:option-id"} -} - -/**## ODAutocompleteResponderManager_Default `default_class` - * This is a special class that adds type definitions & typescript to the ODAutocompleteResponderManager class. - * It doesn't add any extra features! - * - * This default class is made for the global variable `opendiscord.responders.autocomplete`! - */ -export class ODAutocompleteResponderManager_Default extends ODAutocompleteResponderManager { - get(id:ModalResponderId): ODAutocompleteResponder_Default - get(id:ODValidId): ODAutocompleteResponder<"autocomplete",any>|null - - get(id:ODValidId): ODAutocompleteResponder<"autocomplete",any>|null { - return super.get(id) - } - - remove(id:ModalResponderId): ODAutocompleteResponder_Default - remove(id:ODValidId): ODAutocompleteResponder<"autocomplete",any>|null - - remove(id:ODValidId): ODAutocompleteResponder<"autocomplete",any>|null { - return super.remove(id) - } - - exists(id:keyof ODAutocompleteResponderManagerIds_Default): boolean - exists(id:ODValidId): boolean - - exists(id:ODValidId): boolean { - return super.exists(id) - } -} - -/**## ODAutocompleteResponder_Default `default_class` - * This is a special class that adds type definitions & typescript to the ODAutocompleteResponder class. - * It doesn't add any extra features! - * - * This default class is made for the default `ODAutocompleteResponder`'s! - */ -export class ODAutocompleteResponder_Default extends ODAutocompleteResponder { - declare workers: ODWorkerManager_Default -} \ No newline at end of file diff --git a/src/core/api/defaults/session.ts b/src/core/api/defaults/session.ts deleted file mode 100644 index 8eef65b..0000000 --- a/src/core/api/defaults/session.ts +++ /dev/null @@ -1,42 +0,0 @@ -/////////////////////////////////////// -//DEFAULT SESSION MODULE -/////////////////////////////////////// -import { ODValidId } from "../modules/base" -import { ODSession, ODSessionManager } from "../modules/session" - -/**## ODSessionManagerIds_Default `interface` - * This interface is a list of ids available in the `ODSessionManager_Default` class. - * It's used to generate typescript declarations for this class. - */ -export interface ODSessionManagerIds_Default { - //"test-session":ODSession -} - -/**## ODSessionManager_Default `default_class` - * This is a special class that adds type definitions & typescript to the ODSessionManager class. - * It doesn't add any extra features! - * - * This default class is made for the global variable `opendiscord.sessions`! - */ -export class ODSessionManager_Default extends ODSessionManager { - get(id:SessionId): ODSessionManagerIds_Default[SessionId] - get(id:ODValidId): ODSession|null - - get(id:ODValidId): ODSession|null { - return super.get(id) - } - - remove(id:SessionId): ODSessionManagerIds_Default[SessionId] - remove(id:ODValidId): ODSession|null - - remove(id:ODValidId): ODSession|null { - return super.remove(id) - } - - exists(id:keyof ODSessionManagerIds_Default): boolean - exists(id:ODValidId): boolean - - exists(id:ODValidId): boolean { - return super.exists(id) - } -} \ No newline at end of file diff --git a/src/core/api/defaults/startscreen.ts b/src/core/api/defaults/startscreen.ts deleted file mode 100644 index b3da322..0000000 --- a/src/core/api/defaults/startscreen.ts +++ /dev/null @@ -1,48 +0,0 @@ -/////////////////////////////////////// -//DEFAULT STARTSCREEN MODULE -/////////////////////////////////////// -import { ODValidId } from "../modules/base" -import { ODStartScreenCategoryComponent, ODStartScreenComponent, ODStartScreenFlagsCategoryComponent, ODStartScreenHeaderComponent, ODStartScreenLiveStatusCategoryComponent, ODStartScreenLogoComponent, ODStartScreenManager, ODStartScreenPluginsCategoryComponent, ODStartScreenPropertiesCategoryComponent } from "../modules/startscreen" - -/**## ODStartScreenManagerIds_Default `interface` - * This interface is a list of ids available in the `ODStartScreenManager_Default` class. - * It's used to generate typescript declarations for this class. - */ -export interface ODStartScreenManagerIds_Default { - "opendiscord:logo":ODStartScreenLogoComponent, - "opendiscord:header":ODStartScreenHeaderComponent, - "opendiscord:flags":ODStartScreenFlagsCategoryComponent, - "opendiscord:plugins":ODStartScreenPluginsCategoryComponent, - "opendiscord:stats":ODStartScreenPropertiesCategoryComponent, - "opendiscord:livestatus":ODStartScreenLiveStatusCategoryComponent, - "opendiscord:logs":ODStartScreenCategoryComponent -} - -/**## ODStartScreenManager_Default `default_class` - * This is a special class that adds type definitions & typescript to the ODStartScreenManager class. - * It doesn't add any extra features! - * - * This default class is made for the global variable `opendiscord.startscreen`! - */ -export class ODStartScreenManager_Default extends ODStartScreenManager { - get(id:StartScreenId): ODStartScreenManagerIds_Default[StartScreenId] - get(id:ODValidId): ODStartScreenComponent|null - - get(id:ODValidId): ODStartScreenComponent|null { - return super.get(id) - } - - remove(id:StartScreenId): ODStartScreenManagerIds_Default[StartScreenId] - remove(id:ODValidId): ODStartScreenComponent|null - - remove(id:ODValidId): ODStartScreenComponent|null { - return super.remove(id) - } - - exists(id:keyof ODStartScreenManagerIds_Default): boolean - exists(id:ODValidId): boolean - - exists(id:ODValidId): boolean { - return super.exists(id) - } -} \ No newline at end of file diff --git a/src/core/api/defaults/stat.ts b/src/core/api/defaults/stat.ts deleted file mode 100644 index dd8486e..0000000 --- a/src/core/api/defaults/stat.ts +++ /dev/null @@ -1,472 +0,0 @@ -/////////////////////////////////////// -//DEFAULT SESSION MODULE -/////////////////////////////////////// -import { ODValidId } from "../modules/base" -import { ODStatScope, ODStatGlobalScope, ODStatsManager, ODStat, ODBasicStat, ODDynamicStat, ODValidStatValue, ODStatScopeSetMode } from "../modules/stat" - -/**## ODStatsManagerIds_Default `interface` - * This interface is a list of ids available in the `ODStatsManager_Default` class. - * It's used to generate typescript declarations for this class. - */ -export interface ODStatsManagerIds_Default { - "opendiscord:global":ODStatGlobalScope_DefaultGlobal, - "opendiscord:system":ODStatGlobalScope_DefaultSystem, - "opendiscord:user":ODStatScope_DefaultUser, - "opendiscord:ticket":ODStatScope_DefaultTicket, - "opendiscord:participants":ODStatScope_DefaultParticipants, - "opendiscord:messages":ODStatScope_DefaultMessages, -} - -/**## ODStatsManager_Default `default_class` - * This is a special class that adds type definitions & typescript to the ODStatsManager class. - * It doesn't add any extra features! - * - * This default class is made for the global variable `opendiscord.stats`! - */ -export class ODStatsManager_Default extends ODStatsManager { - get(id:StatsId): ODStatsManagerIds_Default[StatsId] - get(id:ODValidId): ODStatScope|null - - get(id:ODValidId): ODStatScope|null { - return super.get(id) - } - - remove(id:StatsId): ODStatsManagerIds_Default[StatsId] - remove(id:ODValidId): ODStatScope|null - - remove(id:ODValidId): ODStatScope|null { - return super.remove(id) - } - - exists(id:keyof ODStatsManagerIds_Default): boolean - exists(id:ODValidId): boolean - - exists(id:ODValidId): boolean { - return super.exists(id) - } -} - -/**## ODStatGlobalScopeIds_DefaultGlobal `type` - * This interface is a list of ids available in the `ODStatGlobalScope_DefaultGlobal` class. - * It's used to generate typescript declarations for this class. - */ -export interface ODStatGlobalScopeIds_DefaultGlobal { - "opendiscord:tickets-created":ODBasicStat, - "opendiscord:tickets-closed":ODBasicStat, - "opendiscord:tickets-deleted":ODBasicStat, - "opendiscord:tickets-reopened":ODBasicStat, - "opendiscord:tickets-autoclosed":ODBasicStat, - "opendiscord:tickets-autodeleted":ODBasicStat, - "opendiscord:tickets-claimed":ODBasicStat, - "opendiscord:tickets-pinned":ODBasicStat, - "opendiscord:tickets-moved":ODBasicStat, - "opendiscord:tickets-transferred":ODBasicStat, - "opendiscord:users-blacklisted":ODBasicStat, - "opendiscord:transcripts-created":ODBasicStat, - "opendiscord:ticket-volume":ODDynamicStat, - "opendiscord:average-tickets":ODDynamicStat, -} - -/**## ODStatGlobalScope_DefaultGlobal `default_class` - * This is a special class that adds type definitions & typescript to the ODStatsManager class. - * It doesn't add any extra features! - * - * This default class is made for the `opendiscord:global` category in `opendiscord.stats`! - */ -export class ODStatGlobalScope_DefaultGlobal extends ODStatGlobalScope { - get(id:StatsId): ODStatGlobalScopeIds_DefaultGlobal[StatsId] - get(id:ODValidId): ODStat|null - - get(id:ODValidId): ODStat|null { - return super.get(id) - } - - remove(id:StatsId): ODStatGlobalScopeIds_DefaultGlobal[StatsId] - remove(id:ODValidId): ODStat|null - - remove(id:ODValidId): ODStat|null { - return super.remove(id) - } - - exists(id:keyof ODStatGlobalScopeIds_DefaultGlobal): boolean - exists(id:ODValidId): boolean - - exists(id:ODValidId): boolean { - return super.exists(id) - } - - getStat(id:StatsId): Promise - getStat(id:ODValidId): Promise - - getStat(id:ODValidId): Promise { - return super.getStat(id) - } - - getAllStats(id:StatsId): Promise<{id:string,value:ODValidStatValue}[]> - getAllStats(id:ODValidId): Promise<{id:string,value:ODValidStatValue}[]> - - getAllStats(id:ODValidId): Promise<{id:string,value:ODValidStatValue}[]> { - return super.getAllStats(id) - } - - setStat(id:StatsId, value:ODValidStatValue, mode:ODStatScopeSetMode): Promise - setStat(id:ODValidId, value:ODValidStatValue, mode:ODStatScopeSetMode): Promise - - setStat(id:ODValidId, value:ODValidStatValue, mode:ODStatScopeSetMode): Promise { - return super.setStat(id,value,mode) - } - - resetStat(id:ODValidId): Promise - resetStat(id:ODValidId): Promise - - resetStat(id:ODValidId): Promise { - return super.resetStat(id) - } -} - -/**## ODStatGlobalScopeIds_DefaultSystem `type` - * This interface is a list of ids available in the `ODStatScope_DefaultSystem` class. - * It's used to generate typescript declarations for this class. - */ -export interface ODStatGlobalScopeIds_DefaultSystem { - "opendiscord:startup-date":ODDynamicStat, - "opendiscord:system-uptime":ODDynamicStat, - "opendiscord:version":ODDynamicStat -} - -/**## ODStatGlobalScope_DefaultSystem `default_class` - * This is a special class that adds type definitions & typescript to the ODStatsManager class. - * It doesn't add any extra features! - * - * This default class is made for the `opendiscord:system` category in `opendiscord.stats`! - */ -export class ODStatGlobalScope_DefaultSystem extends ODStatGlobalScope { - get(id:StatsId): ODStatGlobalScopeIds_DefaultSystem[StatsId] - get(id:ODValidId): ODStat|null - - get(id:ODValidId): ODStat|null { - return super.get(id) - } - - remove(id:StatsId): ODStatGlobalScopeIds_DefaultSystem[StatsId] - remove(id:ODValidId): ODStat|null - - remove(id:ODValidId): ODStat|null { - return super.remove(id) - } - - exists(id:keyof ODStatGlobalScopeIds_DefaultSystem): boolean - exists(id:ODValidId): boolean - - exists(id:ODValidId): boolean { - return super.exists(id) - } - - getStat(id:StatsId): Promise - getStat(id:ODValidId): Promise - - getStat(id:ODValidId): Promise { - return super.getStat(id) - } - - getAllStats(id:StatsId): Promise<{id:string,value:ODValidStatValue}[]> - getAllStats(id:ODValidId): Promise<{id:string,value:ODValidStatValue}[]> - - getAllStats(id:ODValidId): Promise<{id:string,value:ODValidStatValue}[]> { - return super.getAllStats(id) - } - - setStat(id:StatsId, value:ODValidStatValue, mode:ODStatScopeSetMode): Promise - setStat(id:ODValidId, value:ODValidStatValue, mode:ODStatScopeSetMode): Promise - - setStat(id:ODValidId, value:ODValidStatValue, mode:ODStatScopeSetMode): Promise { - return super.setStat(id,value,mode) - } - - resetStat(id:ODValidId): Promise - resetStat(id:ODValidId): Promise - - resetStat(id:ODValidId): Promise { - return super.resetStat(id) - } -} - -/**## ODStatScopeIds_DefaultUser `type` - * This interface is a list of ids available in the `ODStatScope_DefaultUser` class. - * It's used to generate typescript declarations for this class. - */ -export interface ODStatScopeIds_DefaultUser { - "opendiscord:name":ODDynamicStat, - "opendiscord:role":ODDynamicStat, - "opendiscord:tickets-created":ODBasicStat, - "opendiscord:tickets-closed":ODBasicStat, - "opendiscord:tickets-deleted":ODBasicStat, - "opendiscord:tickets-reopened":ODBasicStat, - "opendiscord:tickets-claimed":ODBasicStat, - "opendiscord:tickets-pinned":ODBasicStat, - "opendiscord:tickets-moved":ODBasicStat, - "opendiscord:tickets-transferred":ODBasicStat, - "opendiscord:users-blacklisted":ODBasicStat, - "opendiscord:transcripts-created":ODBasicStat, - "opendiscord:current-tickets":ODDynamicStat, -} - -/**## ODStatScope_DefaultUser `default_class` - * This is a special class that adds type definitions & typescript to the ODStatsManager class. - * It doesn't add any extra features! - * - * This default class is made for the `opendiscord:user` category in `opendiscord.stats`! - */ -export class ODStatScope_DefaultUser extends ODStatScope { - get(id:StatsId): ODStatScopeIds_DefaultUser[StatsId] - get(id:ODValidId): ODStat|null - - get(id:ODValidId): ODStat|null { - return super.get(id) - } - - remove(id:StatsId): ODStatScopeIds_DefaultUser[StatsId] - remove(id:ODValidId): ODStat|null - - remove(id:ODValidId): ODStat|null { - return super.remove(id) - } - - exists(id:keyof ODStatScopeIds_DefaultUser): boolean - exists(id:ODValidId): boolean - - exists(id:ODValidId): boolean { - return super.exists(id) - } - - getStat(id:StatsId, scopeId:string): Promise - getStat(id:ODValidId, scopeId:string): Promise - - getStat(id:ODValidId, scopeId:string): Promise { - return super.getStat(id,scopeId) - } - - getAllStats(id:StatsId): Promise<{id:string,value:ODValidStatValue}[]> - getAllStats(id:ODValidId): Promise<{id:string,value:ODValidStatValue}[]> - - getAllStats(id:ODValidId): Promise<{id:string,value:ODValidStatValue}[]> { - return super.getAllStats(id) - } - - setStat(id:StatsId, scopeId:string, value:ODValidStatValue, mode:ODStatScopeSetMode): Promise - setStat(id:ODValidId, scopeId:string, value:ODValidStatValue, mode:ODStatScopeSetMode): Promise - - setStat(id:ODValidId, scopeId:string, value:ODValidStatValue, mode:ODStatScopeSetMode): Promise { - return super.setStat(id,scopeId,value,mode) - } - - resetStat(id:ODValidId, scopeId:string): Promise - resetStat(id:ODValidId, scopeId:string): Promise - - resetStat(id:ODValidId, scopeId:string): Promise { - return super.resetStat(id,scopeId) - } -} - -/**## ODStatScopeIds_DefaultTicket `type` - * This interface is a list of ids available in the `ODStatScope_DefaultTicket` class. - * It's used to generate typescript declarations for this class. - */ -export interface ODStatScopeIds_DefaultTicket { - "opendiscord:name":ODDynamicStat, - "opendiscord:status":ODDynamicStat, - "opendiscord:claimed":ODDynamicStat, - "opendiscord:pinned":ODDynamicStat, - "opendiscord:creation-date":ODDynamicStat, - "opendiscord:creator":ODDynamicStat, - "opendiscord:ticket-age":ODDynamicStat, - "opendiscord:response-time":ODDynamicStat, - "opendiscord:resolution-time":ODDynamicStat, -} - -/**## ODStatScope_DefaultTicket `default_class` - * This is a special class that adds type definitions & typescript to the ODStatsManager class. - * It doesn't add any extra features! - * - * This default class is made for the `opendiscord:ticket` category in `opendiscord.stats`! - */ -export class ODStatScope_DefaultTicket extends ODStatScope { - get(id:StatsId): ODStatScopeIds_DefaultTicket[StatsId] - get(id:ODValidId): ODStat|null - - get(id:ODValidId): ODStat|null { - return super.get(id) - } - - remove(id:StatsId): ODStatScopeIds_DefaultTicket[StatsId] - remove(id:ODValidId): ODStat|null - - remove(id:ODValidId): ODStat|null { - return super.remove(id) - } - - exists(id:keyof ODStatScopeIds_DefaultTicket): boolean - exists(id:ODValidId): boolean - - exists(id:ODValidId): boolean { - return super.exists(id) - } - - getStat(id:StatsId, scopeId:string): Promise - getStat(id:ODValidId, scopeId:string): Promise - - getStat(id:ODValidId, scopeId:string): Promise { - return super.getStat(id,scopeId) - } - - getAllStats(id:StatsId): Promise<{id:string,value:ODValidStatValue}[]> - getAllStats(id:ODValidId): Promise<{id:string,value:ODValidStatValue}[]> - - getAllStats(id:ODValidId): Promise<{id:string,value:ODValidStatValue}[]> { - return super.getAllStats(id) - } - - setStat(id:StatsId, scopeId:string, value:ODValidStatValue, mode:ODStatScopeSetMode): Promise - setStat(id:ODValidId, scopeId:string, value:ODValidStatValue, mode:ODStatScopeSetMode): Promise - - setStat(id:ODValidId, scopeId:string, value:ODValidStatValue, mode:ODStatScopeSetMode): Promise { - return super.setStat(id,scopeId,value,mode) - } - - resetStat(id:ODValidId, scopeId:string): Promise - resetStat(id:ODValidId, scopeId:string): Promise - - resetStat(id:ODValidId, scopeId:string): Promise { - return super.resetStat(id,scopeId) - } -} - -/**## ODStatScopeIds_DefaultParticipants `type` - * This interface is a list of ids available in the `ODStatScope_DefaultParticipants` class. - * It's used to generate typescript declarations for this class. - */ -export interface ODStatScopeIds_DefaultParticipants { - "opendiscord:participants":ODDynamicStat -} - -/**## ODStatScope_DefaultParticipants `default_class` - * This is a special class that adds type definitions & typescript to the ODStatsManager class. - * It doesn't add any extra features! - * - * This default class is made for the `opendiscord:participants` category in `opendiscord.stats`! - */ -export class ODStatScope_DefaultParticipants extends ODStatScope { - get(id:StatsId): ODStatScopeIds_DefaultParticipants[StatsId] - get(id:ODValidId): ODStat|null - - get(id:ODValidId): ODStat|null { - return super.get(id) - } - - remove(id:StatsId): ODStatScopeIds_DefaultParticipants[StatsId] - remove(id:ODValidId): ODStat|null - - remove(id:ODValidId): ODStat|null { - return super.remove(id) - } - - exists(id:keyof ODStatScopeIds_DefaultParticipants): boolean - exists(id:ODValidId): boolean - - exists(id:ODValidId): boolean { - return super.exists(id) - } - - getStat(id:StatsId, scopeId:string): Promise - getStat(id:ODValidId, scopeId:string): Promise - - getStat(id:ODValidId, scopeId:string): Promise { - return super.getStat(id,scopeId) - } - - getAllStats(id:StatsId): Promise<{id:string,value:ODValidStatValue}[]> - getAllStats(id:ODValidId): Promise<{id:string,value:ODValidStatValue}[]> - - getAllStats(id:ODValidId): Promise<{id:string,value:ODValidStatValue}[]> { - return super.getAllStats(id) - } - - setStat(id:StatsId, scopeId:string, value:ODValidStatValue, mode:ODStatScopeSetMode): Promise - setStat(id:ODValidId, scopeId:string, value:ODValidStatValue, mode:ODStatScopeSetMode): Promise - - setStat(id:ODValidId, scopeId:string, value:ODValidStatValue, mode:ODStatScopeSetMode): Promise { - return super.setStat(id,scopeId,value,mode) - } - - resetStat(id:ODValidId, scopeId:string): Promise - resetStat(id:ODValidId, scopeId:string): Promise - - resetStat(id:ODValidId, scopeId:string): Promise { - return super.resetStat(id,scopeId) - } -} - -/**## ODStatScopeIds_DefaultMessages `type` - * This interface is a list of ids available in the `ODStatScope_DefaultMessages` class. - * It's used to generate typescript declarations for this class. - */ -export interface ODStatScopeIds_DefaultMessages { - "opendiscord:count":ODDynamicStat -} - -/**## ODStatScope_DefaultMessages `default_class` - * This is a special class that adds type definitions & typescript to the ODStatsManager class. - * It doesn't add any extra features! - * - * This default class is made for the `opendiscord:participants` category in `opendiscord.stats`! - */ -export class ODStatScope_DefaultMessages extends ODStatScope { - get(id:StatsId): ODStatScopeIds_DefaultMessages[StatsId] - get(id:ODValidId): ODStat|null - - get(id:ODValidId): ODStat|null { - return super.get(id) - } - - remove(id:StatsId): ODStatScopeIds_DefaultMessages[StatsId] - remove(id:ODValidId): ODStat|null - - remove(id:ODValidId): ODStat|null { - return super.remove(id) - } - - exists(id:keyof ODStatScopeIds_DefaultMessages): boolean - exists(id:ODValidId): boolean - - exists(id:ODValidId): boolean { - return super.exists(id) - } - - getStat(id:StatsId, scopeId:string): Promise - getStat(id:ODValidId, scopeId:string): Promise - - getStat(id:ODValidId, scopeId:string): Promise { - return super.getStat(id,scopeId) - } - - getAllStats(id:StatsId): Promise<{id:string,value:ODValidStatValue}[]> - getAllStats(id:ODValidId): Promise<{id:string,value:ODValidStatValue}[]> - - getAllStats(id:ODValidId): Promise<{id:string,value:ODValidStatValue}[]> { - return super.getAllStats(id) - } - - setStat(id:StatsId, scopeId:string, value:ODValidStatValue, mode:ODStatScopeSetMode): Promise - setStat(id:ODValidId, scopeId:string, value:ODValidStatValue, mode:ODStatScopeSetMode): Promise - - setStat(id:ODValidId, scopeId:string, value:ODValidStatValue, mode:ODStatScopeSetMode): Promise { - return super.setStat(id,scopeId,value,mode) - } - - resetStat(id:ODValidId, scopeId:string): Promise - resetStat(id:ODValidId, scopeId:string): Promise - - resetStat(id:ODValidId, scopeId:string): Promise { - return super.resetStat(id,scopeId) - } -} \ No newline at end of file diff --git a/src/core/api/defaults/verifybar.ts b/src/core/api/defaults/verifybar.ts deleted file mode 100644 index 7ee623b..0000000 --- a/src/core/api/defaults/verifybar.ts +++ /dev/null @@ -1,72 +0,0 @@ -/////////////////////////////////////// -//DEFAULT VERIFYBAR MODULE -/////////////////////////////////////// -import { ODValidId } from "../modules/base" -import { ODButtonResponderInstance } from "../modules/responder" -import { ODWorkerManager_Default } from "../defaults/worker" -import { ODVerifyBarManager, ODVerifyBar } from "../modules/verifybar" -import * as discord from "discord.js" - -/**## ODVerifyBarManagerIds_Default `interface` - * This interface is a list of ids available in the `ODVerifyBarManager_Default` class. - * It's used to generate typescript declarations for this class. - */ -export interface ODVerifyBarManagerIds_Default { - "opendiscord:claim-ticket-ticket-message":{successWorkerIds:"opendiscord:permissions"|"opendiscord:claim-ticket",failureWorkerIds:"opendiscord:back-to-ticket-message"}, - "opendiscord:claim-ticket-unclaim-message":{successWorkerIds:"opendiscord:permissions"|"opendiscord:claim-ticket",failureWorkerIds:"opendiscord:back-to-unclaim-message"}, - "opendiscord:unclaim-ticket-ticket-message":{successWorkerIds:"opendiscord:permissions"|"opendiscord:unclaim-ticket",failureWorkerIds:"opendiscord:back-to-ticket-message"}, - "opendiscord:unclaim-ticket-claim-message":{successWorkerIds:"opendiscord:permissions"|"opendiscord:unclaim-ticket",failureWorkerIds:"opendiscord:back-to-claim-message"}, - "opendiscord:pin-ticket-ticket-message":{successWorkerIds:"opendiscord:permissions"|"opendiscord:pin-ticket",failureWorkerIds:"opendiscord:back-to-ticket-message"}, - "opendiscord:pin-ticket-unpin-message":{successWorkerIds:"opendiscord:permissions"|"opendiscord:pin-ticket",failureWorkerIds:"opendiscord:back-to-unpin-message"}, - "opendiscord:unpin-ticket-ticket-message":{successWorkerIds:"opendiscord:permissions"|"opendiscord:unpin-ticket",failureWorkerIds:"opendiscord:back-to-ticket-message"}, - "opendiscord:unpin-ticket-pin-message":{successWorkerIds:"opendiscord:permissions"|"opendiscord:unpin-ticket",failureWorkerIds:"opendiscord:back-to-pin-message"}, - "opendiscord:close-ticket-ticket-message":{successWorkerIds:"opendiscord:permissions"|"opendiscord:close-ticket",failureWorkerIds:"opendiscord:back-to-ticket-message"}, - "opendiscord:close-ticket-reopen-message":{successWorkerIds:"opendiscord:permissions"|"opendiscord:close-ticket",failureWorkerIds:"opendiscord:back-to-reopen-message"}, - "opendiscord:reopen-ticket-ticket-message":{successWorkerIds:"opendiscord:permissions"|"opendiscord:reopen-ticket",failureWorkerIds:"opendiscord:back-to-ticket-message"}, - "opendiscord:reopen-ticket-close-message":{successWorkerIds:"opendiscord:permissions"|"opendiscord:reopen-ticket",failureWorkerIds:"opendiscord:back-to-close-message"}, - "opendiscord:reopen-ticket-autoclose-message":{successWorkerIds:"opendiscord:permissions"|"opendiscord:reopen-ticket",failureWorkerIds:"opendiscord:back-to-autoclose-message"}, - "opendiscord:delete-ticket-ticket-message":{successWorkerIds:"opendiscord:permissions"|"opendiscord:delete-ticket",failureWorkerIds:"opendiscord:back-to-ticket-message"} - "opendiscord:delete-ticket-close-message":{successWorkerIds:"opendiscord:permissions"|"opendiscord:delete-ticket",failureWorkerIds:"opendiscord:back-to-close-message"} - "opendiscord:delete-ticket-reopen-message":{successWorkerIds:"opendiscord:permissions"|"opendiscord:delete-ticket",failureWorkerIds:"opendiscord:back-to-reopen-message"} - "opendiscord:delete-ticket-autoclose-message":{successWorkerIds:"opendiscord:permissions"|"opendiscord:delete-ticket",failureWorkerIds:"opendiscord:back-to-autoclose-message"} -} - -/**## ODVerifyBarManager_Default `default_class` - * This is a special class that adds type definitions & typescript to the ODVerifyBarManager class. - * It doesn't add any extra features! - * - * This default class is made for the global variable `opendiscord.verifybars`! - */ -export class ODVerifyBarManager_Default extends ODVerifyBarManager { - get(id:VerifyBarId): ODVerifyBar_Default - get(id:ODValidId): ODVerifyBar|null - - get(id:ODValidId): ODVerifyBar|null { - return super.get(id) - } - - remove(id:VerifyBarId): ODVerifyBar_Default - remove(id:ODValidId): ODVerifyBar|null - - remove(id:ODValidId): ODVerifyBar|null { - return super.remove(id) - } - - exists(id:keyof ODVerifyBarManagerIds_Default): boolean - exists(id:ODValidId): boolean - - exists(id:ODValidId): boolean { - return super.exists(id) - } -} - -/**## ODVerifyBar_Default `default_class` - * This is a special class that adds type definitions & typescript to the ODVerifyBar class. - * It doesn't add any extra features! - * - * This default class is made for the default `ODVerifyBar`'s! - */ -export class ODVerifyBar_Default extends ODVerifyBar { - declare success: ODWorkerManager_Default|null},SuccessWorkerIds> - declare failure: ODWorkerManager_Default|null},FailureWorkerIds> -} \ No newline at end of file diff --git a/src/core/api/defaults/worker.ts b/src/core/api/defaults/worker.ts deleted file mode 100644 index 3642aee..0000000 --- a/src/core/api/defaults/worker.ts +++ /dev/null @@ -1,35 +0,0 @@ -/////////////////////////////////////// -//DEFAULT WORKER MODULE -/////////////////////////////////////// -import { ODValidId } from "../modules/base" -import { ODWorker, ODWorkerManager } from "../modules/worker" - - -/**## ODWorkerManager_Default `default_class` - * This is a special class that adds type definitions & typescript to the ODWorkerManager class. - * It doesn't add any extra features! - * - * This default class is made for the worker manager in actions, builders & responders! - */ -export class ODWorkerManager_Default extends ODWorkerManager { - get(id:WorkerIds): ODWorker - get(id:ODValidId): ODWorker|null - - get(id:ODValidId): ODWorker|null { - return super.get(id) - } - - remove(id:WorkerIds): ODWorker - remove(id:ODValidId): ODWorker|null - - remove(id:ODValidId): ODWorker|null { - return super.remove(id) - } - - exists(id:WorkerIds): boolean - exists(id:ODValidId): boolean - - exists(id:ODValidId): boolean { - return super.exists(id) - } -} \ No newline at end of file diff --git a/src/core/api/main.ts b/src/core/api/main.ts deleted file mode 100644 index f5e282a..0000000 --- a/src/core/api/main.ts +++ /dev/null @@ -1,193 +0,0 @@ -//BASE MODULES -import { ODEnvHelper, ODVersion } from "./modules/base" -import { ODConsoleManager, ODConsoleMessage, ODConsoleMessageParam, ODConsoleMessageTypes, ODDebugFileManager, ODDebugger, ODError } from "./modules/console" -import { ODCheckerStorage } from "./modules/checker" -import { ODDefaultsManager } from "./modules/defaults" - -//DEFAULT MODULES -import { ODVersionManager_Default } from "./defaults/base" -import { ODPluginManager_Default } from "./defaults/plugin" -import { ODEventManager_Default } from "./defaults/event" -import { ODConfigManager_Default} from "./defaults/config" -import { ODDatabaseManager_Default } from "./defaults/database" -import { ODFlagManager_Default } from "./defaults/flag" -import { ODSessionManager_Default } from "./defaults/session" -import { ODLanguageManager_Default } from "./defaults/language" -import { ODCheckerFunctionManager_Default, ODCheckerManager_Default, ODCheckerRenderer_Default, ODCheckerTranslationRegister_Default } from "./defaults/checker" -import { ODClientManager_Default } from "./defaults/client" -import { ODBuilderManager_Default } from "./defaults/builder" -import { ODResponderManager_Default } from "./defaults/responder" -import { ODActionManager_Default } from "./defaults/action" -import { ODPermissionManager_Default } from "./defaults/permission" -import { ODHelpMenuManager_Default } from "./defaults/helpmenu" -import { ODStatsManager_Default } from "./defaults/stat" -import { ODCodeManager_Default } from "./defaults/code" -import { ODCooldownManager_Default } from "./defaults/cooldown" -import { ODPostManager_Default } from "./defaults/post" -import { ODVerifyBarManager_Default } from "./defaults/verifybar" -import { ODProgressBarManager_Default } from "./defaults/progressbar" -import { ODStartScreenManager_Default } from "./defaults/startscreen" -import { ODLiveStatusManager_Default } from "./defaults/console" - -//OPEN TICKET MODULES -import { ODOptionManager } from "./openticket/option" -import { ODPanelManager } from "./openticket/panel" -import { ODTicketManager } from "./openticket/ticket" -import { ODQuestionManager } from "./openticket/question" -import { ODBlacklistManager } from "./openticket/blacklist" -import { ODTranscriptManager_Default } from "./openticket/transcript" -import { ODRoleManager } from "./openticket/role" -import { ODPriorityManager_Default } from "./openticket/priority" - -/**## ODMain `class` - * This is the main Open Ticket class. - * It contains all managers from the entire bot & has shortcuts to the event & logging system. - * - * This class can't be overwritten or extended & is available as the global variable `openticket`! - */ -export class ODMain { - /**The manager that handles all versions in the bot. */ - versions: ODVersionManager_Default - - /**The timestamp that the (node.js) process of the bot started. */ - processStartupDate: Date = new Date() - /**The timestamp that the bot finished loading and is ready for usage. */ - readyStartupDate: Date|null = null - - /**The manager responsible for the debug file. (`otdebug.txt`) */ - debugfile: ODDebugFileManager - /**The manager responsible for the console system. (logs, errors, etc) */ - console: ODConsoleManager - /**The manager responsible for sending debug logs to the debug file. (`otdebug.txt`) */ - debug: ODDebugger - /**The manager containing all Open Ticket events. */ - events: ODEventManager_Default - - /**The manager that handles & executes all plugins in the bot. */ - plugins: ODPluginManager_Default - /**The manager that manages & checks all the console flags of the bot. (like `--debug`) */ - flags: ODFlagManager_Default - /**The manager responsible for progress bars in the console. */ - progressbars: ODProgressBarManager_Default - /**The manager that manages & contains all the config files of the bot. (like `config/general.json`) */ - configs: ODConfigManager_Default - /**The manager that manages & contains all the databases of the bot. (like `database/global.json`) */ - databases: ODDatabaseManager_Default - /**The manager that manages all the data sessions of the bot. (it's a temporary database) */ - sessions: ODSessionManager_Default - /**The manager that manages all languages & translations of the bot. (but not for plugins) */ - languages: ODLanguageManager_Default - - /**The manager that handles & executes all config checkers in the bot. (the code that checks if you have something wrong in your config) */ - checkers: ODCheckerManager_Default - /**The manager that manages all builders in the bot. (e.g. buttons, dropdowns, messages, modals, etc) */ - builders: ODBuilderManager_Default - /**The manager that manages all responders in the bot. (e.g. commands, buttons, dropdowns, modals) */ - responders: ODResponderManager_Default - /**The manager that manages all actions or procedures in the bot. (e.g. ticket-creation, ticket-deletion, ticket-claiming, etc) */ - actions: ODActionManager_Default - /**The manager that manages all verify bars in the bot. (the ✅ ❌ buttons) */ - verifybars: ODVerifyBarManager_Default - /**The manager that contains all permissions for commands & actions in the bot. (use it to check if someone has admin perms or not) */ - permissions: ODPermissionManager_Default - /**The manager that contains all cooldowns of the bot. (e.g. ticket-cooldowns) */ - cooldowns: ODCooldownManager_Default - /**The manager that manages & renders the Open Ticket help menu. (not the embed, but the text) */ - helpmenu: ODHelpMenuManager_Default - /**The manager that manages, saves & renders the Open Ticket statistics. (not the embed, but the text & database) */ - stats: ODStatsManager_Default - /**This manager is a place where you can put code that executes when the bot almost finishes the setup. (can be used for less important stuff that doesn't require an exact time-order) */ - code: ODCodeManager_Default - /**The manager that manages all posts (static discord channels) in the bot. (e.g. (transcript) logs, etc) */ - posts: ODPostManager_Default - - /**The manager responsible for everything related to the client. (e.g. status, login, slash & text commands, etc) */ - client: ODClientManager_Default - /**This manager contains A LOD of booleans. With these switches, you can turn off "default behaviours" from the bot. This is used if you want to replace the default Open Ticket code. */ - defaults: ODDefaultsManager - /**This manager manages all the variables in the ENV. It reads from both the `.env` file & the `process.env`. (these 2 will be combined) */ - env: ODEnvHelper - - /**The manager responsible for the livestatus system. (remote console logs) */ - livestatus: ODLiveStatusManager_Default - /**The manager responsible for the livestatus system. (remote console logs) */ - startscreen: ODStartScreenManager_Default - - //OPEN TICKET - /**The manager that manages all the data of questions in the bot. (these are used in options & tickets) */ - questions: ODQuestionManager - /**The manager that manages all the data of options in the bot. (these are used for panels, ticket creation, reaction roles) */ - options: ODOptionManager - /**The manager that manages all the data of panels in the bot. (panels contain the options) */ - panels: ODPanelManager - /**The manager that manages all tickets in the bot. (here, you can get & edit a lot of data from tickets) */ - tickets: ODTicketManager - /**The manager that manages the ticket blacklist. (people who are blacklisted can't create a ticket) */ - blacklist: ODBlacklistManager - /**The manager that manages the ticket transcripts. (both the history & compilers) */ - transcripts: ODTranscriptManager_Default - /**The manager that manages all reaction roles in the bot. (here, you can add additional data to roles) */ - roles: ODRoleManager - /**The manager that manages all priority levels in the bot. (register/edit ticket priority levels) */ - priorities: ODPriorityManager_Default - - constructor(){ - this.versions = new ODVersionManager_Default() - this.versions.add(ODVersion.fromString("opendiscord:version","v4.1.3")) - this.versions.add(ODVersion.fromString("opendiscord:api","v1.0.0")) - this.versions.add(ODVersion.fromString("opendiscord:transcripts","v2.1.0")) - this.versions.add(ODVersion.fromString("opendiscord:livestatus","v2.0.0")) - - this.debugfile = new ODDebugFileManager("./","otdebug.txt",5000,this.versions.get("opendiscord:version")) - this.console = new ODConsoleManager(100,this.debugfile) - this.debug = new ODDebugger(this.console) - this.events = new ODEventManager_Default(this.debug) - - this.plugins = new ODPluginManager_Default(this.debug) - this.flags = new ODFlagManager_Default(this.debug) - this.progressbars = new ODProgressBarManager_Default(this.debug) - this.configs = new ODConfigManager_Default(this.debug) - this.databases = new ODDatabaseManager_Default(this.debug) - this.sessions = new ODSessionManager_Default(this.debug) - this.languages = new ODLanguageManager_Default(this.debug,false) - - this.checkers = new ODCheckerManager_Default(this.debug,new ODCheckerStorage(),new ODCheckerRenderer_Default(),new ODCheckerTranslationRegister_Default(),new ODCheckerFunctionManager_Default(this.debug)) - this.builders = new ODBuilderManager_Default(this.debug) - this.client = new ODClientManager_Default(this.debug) - this.responders = new ODResponderManager_Default(this.debug,this.client) - this.actions = new ODActionManager_Default(this.debug) - this.verifybars = new ODVerifyBarManager_Default(this.debug) - this.permissions = new ODPermissionManager_Default(this.debug,this.client) - this.cooldowns = new ODCooldownManager_Default(this.debug) - this.helpmenu = new ODHelpMenuManager_Default(this.debug) - this.stats = new ODStatsManager_Default(this.debug) - this.code = new ODCodeManager_Default(this.debug) - this.posts = new ODPostManager_Default(this.debug) - - this.defaults = new ODDefaultsManager() - this.env = new ODEnvHelper() - - this.livestatus = new ODLiveStatusManager_Default(this.debug,this) - this.startscreen = new ODStartScreenManager_Default(this.debug,this.livestatus) - - //OPEN TICKET - this.questions = new ODQuestionManager(this.debug) - this.options = new ODOptionManager(this.debug) - this.panels = new ODPanelManager(this.debug) - this.tickets = new ODTicketManager(this.debug,this.client) - this.blacklist = new ODBlacklistManager(this.debug) - this.transcripts = new ODTranscriptManager_Default(this.debug,this.tickets,this.client,this.permissions) - this.roles = new ODRoleManager(this.debug) - this.priorities = new ODPriorityManager_Default(this.debug) - } - - /**Log a message to the console. But in the Open Ticket style :) */ - log(message:ODConsoleMessage): void - log(message:ODError): void - log(message:string, type?:ODConsoleMessageTypes, params?:ODConsoleMessageParam[]): void - log(message:ODConsoleMessage|ODError|string, type?:ODConsoleMessageTypes, params?:ODConsoleMessageParam[]){ - if (message instanceof ODConsoleMessage) this.console.log(message) - else if (message instanceof ODError) this.console.log(message) - else if (["string","number","boolean","object"].includes(typeof message)) this.console.log(message,type,params) - } -} \ No newline at end of file diff --git a/src/core/api/modules/action.ts b/src/core/api/modules/action.ts deleted file mode 100644 index 92a5993..0000000 --- a/src/core/api/modules/action.ts +++ /dev/null @@ -1,58 +0,0 @@ -/////////////////////////////////////// -//ACTION MODULE -/////////////////////////////////////// -import { ODId, ODManager, ODValidId, ODSystemError, ODManagerData } from "./base" -import { ODWorkerManager, ODWorkerCallback, ODWorker } from "./worker" -import { ODDebugger } from "./console" - -/**## ODActionImplementation `class` - * This is an Open Ticket action implementation. - * - * It is a basic implementation of the `ODWorkerManager` used by all `ODAction` classes. - * - * This class can't be used stand-alone & needs to be extended from! - */ -export class ODActionImplementation extends ODManagerData { - /**The manager that has all workers of this implementation */ - workers: ODWorkerManager - - constructor(id:ODValidId, callback?:ODWorkerCallback, priority?:number, callbackId?:ODValidId){ - super(id) - this.workers = new ODWorkerManager("descending") - if (callback) this.workers.add(new ODWorker(callbackId ? callbackId : id,priority ?? 0,callback)) - } - /**Execute all workers & return the result. */ - async run(source:Source, params:Params): Promise> { - throw new ODSystemError("Tried to build an unimplemented ODResponderImplementation") - } -} - -/**## ODActionManager `class` - * This is an Open Ticket action manager. - * - * It contains all Open Ticket actions. You can compare actions with some sort of "procedure". - * It's a complicated task that is divided into multiple functions. - * - * Some examples are `ticket-creation`, `ticket-closing`, `ticket-claiming`, ... - * - * It's recommended to use this system in combination with Open Ticket responders! - */ -export class ODActionManager extends ODManager> { - constructor(debug:ODDebugger){ - super(debug,"action") - } -} - -export class ODAction extends ODActionImplementation { - /**Run this action */ - async run(source:Source, params:Params): Promise> { - //create instance - const instance = {} - - //wait for workers to finish - await this.workers.executeWorkers(instance,source,params) - - //return data generated by workers - return instance - } -} \ No newline at end of file diff --git a/src/core/api/modules/base.ts b/src/core/api/modules/base.ts deleted file mode 100644 index ef70065..0000000 --- a/src/core/api/modules/base.ts +++ /dev/null @@ -1,763 +0,0 @@ -/////////////////////////////////////// -//BASE MODULE -/////////////////////////////////////// -import * as fs from "fs" -import { ODConsoleWarningMessage, ODDebugger } from "./console" - -/**## ODPromiseVoid `type` - * This is a simple type to represent a callback return value that could be a promise or not. - */ -export type ODPromiseVoid = void|Promise - -/**## ODOptionalPromise `type` - * This is a simple type to represent a type as normal value or a promise value. - */ -export type ODOptionalPromise = T|Promise - - -/**## ODValidButtonColor `type` - * This is a collection of all the possible button colors. - */ -export type ODValidButtonColor = "gray"|"red"|"green"|"blue" - -/**## ODValidId `type` - * This is a valid Open Ticket identifier. It can be an `ODId` or `string`! - * - * You will see this type in many functions from Open Ticket. - */ -export type ODValidId = string|ODId - -/**## ODValidJsonType `type` - * This is a collection of all types that can be stored in a JSON file! - * - * list: `string`, `number`, `boolean`, `array`, `object`, `null` - */ -export type ODValidJsonType = string|number|boolean|object|ODValidJsonType[]|null - - -/**## ODInterfaceWithPartialProperty `type` - * This is a utility type to create an interface where some properties are optional! - */ -export type ODInterfaceWithPartialProperty = Omit & Partial> - -/**## ODDiscordIdType `type` - * A list of all available discord ID types. Used in the config checker. - */ -export type ODDiscordIdType = "role"|"server"|"channel"|"category"|"user"|"member"|"interaction"|"message" - -/**## ODId `class` - * This is an Open Ticket identifier. - * - * It can only contain the following characters: `a-z`, `A-Z`, `0-9`, `:`, `-` & `_` - * - * You can use this class to assign a unique id when creating configs, databases, languages & more! - */ -export class ODId { - /**The full value of this `ODId` as a `string`. */ - #value: string - /**The full value of this `ODId` as a `string`. */ - set value(id:string){ - this._change(this.#value,id) - this.#value = id - } - get value(){ - return this.#value - } - /**The change listener for the parent `ODManager` of this `ODId`. */ - #change: ((oldId:string,newId:string) => void)|null = null - - constructor(id:ODValidId){ - if (typeof id != "string" && !(id instanceof ODId)) throw new ODSystemError("Invalid constructor parameter => id:ODValidId") - - if (typeof id == "string"){ - //id is string - const result: string[] = [] - const charregex = /[a-zA-Z0-9éèçàêâôûî\:\-\_]/ - - id.split("").forEach((char) => { - if (charregex.test(char)){ - result.push(char) - } - }) - - if (result.length > 0) this.#value = result.join("") - else throw new ODSystemError("invalid ID at 'new ODID(id: "+id+")'") - }else{ - //id is ODId - this.#value = id.#value - } - } - - /**Returns a string representation of this id. (same as `this.value`) */ - toString(){ - return this.#value - } - /**The namespace of the id before `:`. (e.g. `openticket` for `openticket:autoclose-enabled`) */ - getNamespace(){ - const splitted = this.#value.split(":") - if (splitted.length > 1) return splitted[0] - else return "" - } - /**The identifier of the id after `:`. (e.g. `autoclose-enabled` for `openticket:autoclose-enabled`) */ - getIdentifier(){ - const splitted = this.#value.split(":") - if (splitted.length > 1){ - splitted.shift() - return splitted.join(":") - }else return this.#value - } - /**Trigger an `onChange()` event in the parent `ODManager` of this class. */ - protected _change(oldId:string,newId:string){ - if (this.#change){ - try{ - this.#change(oldId,newId) - }catch(err){ - process.emit("uncaughtException",err) - throw new ODSystemError("Failed to execute _change() callback!") - } - } - } - /****(❌ SYSTEM ONLY!!)** Set the callback executed when a value inside this class changes. */ - changed(callback:((oldId:string,newId:string) => void)|null){ - this.#change = callback - } -} - -/**## ODManagerChangeHelper `class` - * This is an Open Ticket manager change helper. - * - * It is used to let the "onChange" event in the `ODManager` class work. - * You can use this class when extending your own `ODManager` - */ -export class ODManagerChangeHelper { - #change: (() => void)|null = null - - /**Trigger an `onChange()` event in the parent `ODManager` of this class. */ - protected _change(){ - if (this.#change){ - try{ - this.#change() - }catch(err){ - process.emit("uncaughtException",err) - throw new ODSystemError("Failed to execute _change() callback!") - } - } - } - /****(❌ SYSTEM ONLY!!)** Set the callback executed when a value inside this class changes. */ - changed(callback:(() => void)|null){ - this.#change = callback - } -} - -/**## ODManagerData `class` - * This is Open Ticket manager data. - * - * It provides a template for all classes that are used in the `ODManager`. - * - * There is an `id:ODId` property & also some events used in the manager. - */ -export class ODManagerData extends ODManagerChangeHelper { - /**The id of this data. */ - id: ODId - - constructor(id:ODValidId){ - if (typeof id != "string" && !(id instanceof ODId)) throw new ODSystemError("Invalid constructor parameter => id:ODValidId") - super() - this.id = new ODId(id) - } -} - -/**## ODManagerCallback `type` - * This is a callback for the `onChange` and `onRemove` events in the `ODManager` - */ -export type ODManagerCallback = (data:DataType) => void -/**## ODManagerAddCallback `type` - * This is a callback for the `onAdd` event in the `ODManager` - */ -export type ODManagerAddCallback = (data:DataType, overwritten:boolean) => void - -/**## ODManager `class` - * This is an Open Ticket manager. - * - * It can be used to store & manage classes based on their `ODId`. - * It is somewhat the same as the default JS `Map()`. - * You can extend this class when creating your own classes & managers. - * - * This class has many useful functions based on `ODId` (add, get, remove, getAll, getFiltered, exists, loopAll, ...) - */ -export class ODManager extends ODManagerChangeHelper { - /**Alias to Open Ticket debugger. */ - #debug?: ODDebugger - /**The message to send when debugging this manager. */ - #debugname?: string - /**The map storing all data classes in this manager. */ - #data: Map = new Map() - /**An array storing all listeners when data is added. */ - #addListeners: ODManagerAddCallback[] = [] - /**An array storing all listeners when data has changed. */ - #changeListeners: ODManagerCallback[] = [] - /**An array storing all listeners when data is removed. */ - #removeListeners: ODManagerCallback[] = [] - - constructor(debug?:ODDebugger, debugname?:string){ - super() - this.#debug = debug - this.#debugname = debugname - } - - /**Add data to the manager. The `ODId` in the data class will be used as identifier! You can optionally select to overwrite existing data!*/ - add(data:DataType|DataType[], overwrite?:boolean): boolean { - //repeat same command when data is an array - if (Array.isArray(data)){ - data.forEach((arrayData) => { - this.add(arrayData,overwrite) - }) - return false - } - - //add listener for data id change => transfer data within manager - data.id.changed((oldId,newId) => { - this.#data.delete(oldId) - this.#data.set(newId,data) - }) - - //add data - let didOverwrite: boolean - if (this.#data.has(data.id.value)){ - if (!overwrite) throw new ODSystemError("Id '"+data.id.value+"' already exists in "+this.#debugname+" manager. Use 'overwrite:true' to allow overwriting!") - this.#data.set(data.id.value,data) - didOverwrite = true - if (this.#debug) this.#debug.debug("Added new "+this.#debugname+" to manager",[{key:"id",value:data.id.value},{key:"overwrite",value:"true"}]) - - }else{ - this.#data.set(data.id.value,data) - didOverwrite = false - if (this.#debug) this.#debug.debug("Added new "+this.#debugname+" to manager",[{key:"id",value:data.id.value},{key:"overwrite",value:"false"}]) - - } - - //emit change listeners - data.changed(() => { - //notify change in upper-manager (because data in this manager changed) - this._change() - this.#changeListeners.forEach((cb) => { - try{ - cb(data) - }catch(err){ - throw new ODSystemError("Failed to run manager onChange() listener.\n"+err) - } - }) - }) - - //emit add listeners - this.#addListeners.forEach((cb) => { - try{ - cb(data,didOverwrite) - }catch(err){ - throw new ODSystemError("Failed to run manager onAdd() listener.\n"+err) - } - }) - - //notify change in upper-manager (because data added) - this._change() - - return didOverwrite - } - /**Get data that matches the `ODId`. Returns the found data.*/ - get(id:ODValidId): DataType|null { - const newId = new ODId(id) - const data = this.#data.get(newId.value) - if (data) return data - else return null - } - /**Remove data that matches the `ODId`. Returns the removed data. */ - remove(id:ODValidId): DataType|null { - const newId = new ODId(id) - const data = this.#data.get(newId.value) - - if (!data){ - if (this.#debug) this.#debug.debug("Removed "+this.#debugname+" from manager",[{key:"id",value:newId.value},{key:"found",value:"false"}]) - return null - }else{ - this.#data.delete(newId.value) - if (this.#debug) this.#debug.debug("Removed "+this.#debugname+" from manager",[{key:"id",value:newId.value},{key:"found",value:"true"}]) - } - - //remove all listeners - data.id.changed(null) - data.changed(null) - - //emit remove listeners - this.#removeListeners.forEach((cb) => { - try{ - cb(data) - }catch(err){ - throw new ODSystemError("Failed to run manager onRemove() listener.\n"+err) - } - }) - - //notify change in upper-manager (because data removed) - this._change() - - return data - } - /**Check if data that matches the `ODId` exists. Returns a boolean. */ - exists(id:ODValidId): boolean { - const newId = new ODId(id) - if (this.#data.has(newId.value)) return true - else return false - } - /**Get all data inside this manager*/ - getAll(): DataType[] { - return Array.from(this.#data.values()) - } - /**Get all data that matches inside the filter function*/ - getFiltered(predicate:(value:DataType, index:number, array:DataType[]) => unknown): DataType[] { - return Array.from(this.#data.values()).filter(predicate) - } - /**Get all data where the `ODId` matches the provided RegExp. */ - getRegex(regex:RegExp): DataType[] { - return Array.from(this.#data.values()).filter((data) => regex.test(data.id.value)) - } - /**Get the length/size/amount of the data inside this manager. */ - getLength(){ - return this.#data.size - } - /**Get a list of all the ids inside this manager*/ - getIds(): ODId[] { - const ids = Array.from(this.#data.keys()) - return ids.map((id) => new ODId(id)) - } - /**Run an iterator over all data in this manager. This method also supports async-await behaviour!*/ - async loopAll(cb:(data:DataType,id:ODId) => ODPromiseVoid): Promise { - for (const data of this.getAll()){ - await cb(data,data.id) - } - } - /**Use the Open Ticket debugger in this manager for logs*/ - useDebug(debug?:ODDebugger, debugname?:string){ - this.#debug = debug - this.#debugname = debugname - } - /**Listen for when data is added to this manager. */ - onAdd(callback:ODManagerAddCallback){ - this.#addListeners.push(callback) - } - /**Listen for when data is changed in this manager. */ - onChange(callback:ODManagerCallback){ - this.#changeListeners.push(callback) - } - /**Listen for when data is removed from this manager. */ - onRemove(callback:ODManagerCallback){ - this.#removeListeners.push(callback) - } -} - -/**## ODManagerWithSafety `class` - * This is an Open Ticket safe manager. - * - * It functions exactly the same as a normal `ODManager`, but it has 1 function extra! - * The `getSafe()` function will always return data, because when it doesn't find an id, it returns pre-configured backup data. - */ -export class ODManagerWithSafety extends ODManager { - /**The function that creates backup data returned in `getSafe()` when an id is missing in this manager. */ - #backupCreator: () => DataType - /** Temporary storage for manager debug name. */ - #debugname: string - - constructor(backupCreator:() => DataType, debug?:ODDebugger, debugname?:string){ - super(debug,debugname) - this.#backupCreator = backupCreator - this.#debugname = debugname ?? "unknown" - } - - /**Get data that matches the `ODId`. Returns the backup data when not found. - * - * ### ⚠️ This should only be used when the data doesn't need to be written/edited - */ - getSafe(id:ODValidId): DataType { - const data = super.get(id) - if (!data){ - process.emit("uncaughtException",new ODSystemError("ODManagerWithSafety:getSafe(\""+id+"\") => Unknown Id => Used backup data ("+this.#debugname+" manager)")) - return this.#backupCreator() - } - else return data - } -} - -/**## ODVersionManager `class` - * A Open Ticket version manager. - * - * It is used to manage different `ODVersion`'s from the bot. You will use it to check which version of the bot is used. - */ -export class ODVersionManager extends ODManager { - constructor(){ - super() - } -} - -/**## ODVersion `class` - * This is an Open Ticket version. - * - * It has many features like comparing versions & checking if they are compatible. - * - * You can use it in your own plugin, but most of the time you will use it to check the Open Ticket version! - */ -export class ODVersion extends ODManagerData { - /**The first number of the version (example: `v1.2.3` => `1`) */ - primary: number - /**The second number of the version (example: `v1.2.3` => `2`) */ - secondary: number - /**The third number of the version (example: `v1.2.3` => `3`) */ - tertiary: number - - constructor(id:ODValidId, primary:number, secondary:number, tertiary:number){ - super(id) - if (typeof primary != "number") throw new ODSystemError("Invalid constructor parameter => primary:number") - if (typeof secondary != "number") throw new ODSystemError("Invalid constructor parameter => secondary:number") - if (typeof tertiary != "number") throw new ODSystemError("Invalid constructor parameter => tertiary:number") - - this.primary = primary - this.secondary = secondary - this.tertiary = tertiary - } - - /**Get the version from a string (also possible with `v` prefix) - * @example const version = api.ODVersion.fromString("id","v1.2.3") //creates version 1.2.3 - */ - static fromString(id:ODValidId, version:string){ - if (typeof id != "string" && !(id instanceof ODId)) throw new ODSystemError("Invalid function parameter => id:ODValidId") - if (typeof version != "string") throw new ODSystemError("Invalid function parameter => version:string") - - const versionCheck = (version.startsWith("v")) ? version.substring(1) : version - const splittedVersion = versionCheck.split(".") - - return new this(id,Number(splittedVersion[0]),Number(splittedVersion[1]),Number(splittedVersion[2])) - } - /**Get the version as a string (`noprefix:true` => with `v` prefix) - * @example - * new api.ODVersion(1,0,0).toString(false) //returns "v1.0.0" - * new api.ODVersion(1,0,0).toString(true) //returns "1.0.0" - */ - toString(noprefix?:boolean){ - const prefix = noprefix ? "" : "v" - return prefix+[this.primary,this.secondary,this.tertiary].join(".") - } - /**Compare this version with another version and returns the result: `higher`, `lower` or `equal` - * @example - * new api.ODVersion(1,0,0).compare(new api.ODVersion(1,2,0)) //returns "lower" - * new api.ODVersion(1,3,0).compare(new api.ODVersion(1,2,0)) //returns "higher" - * new api.ODVersion(1,2,0).compare(new api.ODVersion(1,2,0)) //returns "equal" - */ - compare(comparator:ODVersion): "higher"|"lower"|"equal" { - if (!(comparator instanceof ODVersion)) throw new ODSystemError("Invalid function parameter => comparator:ODVersion") - - if (this.primary < comparator.primary) return "lower" - else if (this.primary > comparator.primary) return "higher" - else { - if (this.secondary < comparator.secondary) return "lower" - else if (this.secondary > comparator.secondary) return "higher" - else { - if (this.tertiary < comparator.tertiary) return "lower" - else if (this.tertiary > comparator.tertiary) return "higher" - else return "equal" - } - } - } - /**Check if this version is included in the list - * @example - * const list = [ - * new api.ODVersion(1,0,0), - * new api.ODVersion(1,0,1), - * new api.ODVersion(1,0,2) - * ] - * new api.ODVersion(1,0,0).compatible(list) //returns true - * new api.ODVersion(1,0,1).compatible(list) //returns true - * new api.ODVersion(1,0,3).compatible(list) //returns false - */ - compatible(list:ODVersion[]): boolean { - if (!Array.isArray(list)) throw new ODSystemError("Invalid function parameter => list:ODVersion[]") - if (!list.every((v) => (v instanceof ODVersion))) throw new ODSystemError("Invalid function parameter => list:ODVersion[]") - - return list.some((v) => { - return (v.toString() === this.toString()) - }) - } - /**Check if this version is higher or equal to the provided `requirement`. */ - min(requirement:string|ODVersion){ - if (typeof requirement == "string") requirement = ODVersion.fromString("temp",requirement) - - //skip when primary version is higher or lower than current one. - if (this.primary < requirement.primary) return false - else if (this.primary > requirement.primary) return true - - //skip when secondary version is higher or lower than current one. - if (this.secondary < requirement.secondary) return false - else if (this.secondary > requirement.secondary) return true - - //skip when tertiary version is higher or lower than current one. - if (this.tertiary < requirement.tertiary) return false - else if (this.tertiary > requirement.tertiary) return true - - return true - } - /**Check if this version is lower or equal to the provided `requirement`. */ - max(requirement:string|ODVersion){ - if (typeof requirement == "string") requirement = ODVersion.fromString("temp",requirement) - - //skip when primary version is higher or lower than current one. - if (this.primary < requirement.primary) return true - else if (this.primary > requirement.primary) return false - - //skip when secondary version is higher or lower than current one. - if (this.secondary < requirement.secondary) return true - else if (this.secondary > requirement.secondary) return false - - //skip when tertiary version is higher or lower than current one. - if (this.tertiary < requirement.tertiary) return true - else if (this.tertiary > requirement.tertiary) return false - - return true - } - /**Check if this version is matches the major version (`vX.X`) of the provided `requirement`. */ - major(requirement:string|ODVersion){ - if (typeof requirement == "string") requirement = ODVersion.fromString("temp",requirement) - return (this.primary == requirement.primary && this.secondary == requirement.secondary) - } - /**Check if this version is matches the minor version (`vX.X.X`) of the provided `requirement`. */ - minor(requirement:string|ODVersion){ - if (typeof requirement == "string") requirement = ODVersion.fromString("temp",requirement) - return (this.primary == requirement.primary && this.secondary == requirement.secondary && this.tertiary == requirement.tertiary) - } -} - -/**## ODHTTPGetRequest `class` - * This is a class that can help you with creating simple HTTP GET requests. - * - * It works using the native node.js fetch() method. You can configure all options in the constructor! - * @example - * const request = new api.ODHTTPGetRequest("https://www.example.com/abc.txt",false,{}) - * - * const result = await request.run() - * result.body //the response body (string) - * result.status //the response code (number) - * result.response //the full response (object) - */ -export class ODHTTPGetRequest { - /**The url used in the request */ - url: string - /**The request config for additional options */ - config: RequestInit - /**Throw on error OR return http code 500 */ - throwOnError: boolean - - constructor(url:string,throwOnError:boolean,config?:RequestInit){ - if (typeof url != "string") throw new ODSystemError("Invalid constructor parameter => url:string") - if (typeof throwOnError != "boolean") throw new ODSystemError("Invalid constructor parameter => throwOnError:boolean") - if (typeof config != "undefined" && typeof config != "object") throw new ODSystemError("Invalid constructor parameter => config?:RequestInit") - - this.url = url - this.throwOnError = throwOnError - const newConfig = config ?? {} - newConfig.method = "GET" - if (newConfig.headers) Object.assign(newConfig.headers,{"User-Agent":"OpenDiscordBots-OpenTicket/4.1.3"}) - else newConfig.headers = {"User-Agent":"OpenDiscordBots-OpenTicket/4.1.3"} - this.config = newConfig - } - - /**Execute the GET request.*/ - run(): Promise<{status:number, body:string, response?:Response}> { - return new Promise(async (resolve,reject) => { - try{ - const response = await fetch(this.url,this.config) - resolve({ - status:response.status, - body:(await response.text()), - response:response - }) - }catch(err){ - if (this.throwOnError) return reject("[OPENTICKET ERROR]: ODHTTPGetRequest => Unknown fetch() error: "+err) - else return resolve({ - status:500, - body:"Open Ticket Error: Unknown fetch() error: "+err, - }) - } - }) - } -} - -/**## ODHTTPPostRequest `class` - * This is a class that can help you with creating simple HTTP POST requests. - * - * It works using the native node.js fetch() method. You can configure all options in the constructor! - * @example - * const request = new api.ODHTTPPostRequest("https://www.example.com/abc.txt",false,{}) - * - * const result = await request.run() - * result.body //the response body (string) - * result.status //the response code (number) - * result.response //the full response (object) - */ -export class ODHTTPPostRequest { - /**The url used in the request */ - url: string - /**The request config for additional options */ - config: RequestInit - /**Throw on error OR return http code 500 */ - throwOnError: boolean - - constructor(url:string,throwOnError:boolean,config?:RequestInit){ - if (typeof url != "string") throw new ODSystemError("Invalid constructor parameter => url:string") - if (typeof throwOnError != "boolean") throw new ODSystemError("Invalid constructor parameter => throwOnError:boolean") - if (typeof config != "undefined" && typeof config != "object") throw new ODSystemError("Invalid constructor parameter => config?:RequestInit") - - this.url = url - this.throwOnError = throwOnError - const newConfig = config ?? {} - newConfig.method = "POST" - if (newConfig.headers) Object.assign(newConfig.headers,{"User-Agent":"OpenDiscordBots-OpenTicket/4.1.3"}) - else newConfig.headers = {"User-Agent":"OpenDiscordBots-OpenTicket/4.1.3"} - this.config = newConfig - } - - /**Execute the POST request.*/ - run(): Promise<{status:number, body:string, response?:Response}> { - return new Promise(async (resolve,reject) => { - try{ - const response = await fetch(this.url,this.config) - resolve({ - status:response.status, - body:(await response.text()), - response:response - }) - }catch(err){ - if (this.throwOnError) return reject("[OPENTICKET ERROR]: ODHTTPPostRequest => Unknown fetch() error: "+err) - else return resolve({ - status:500, - body:"Open Ticket Error: Unknown fetch() error!", - }) - } - }) - } -} - -/**## ODEnvHelper `class` - * This is a utility class that helps you with reading the ENV. - * - * It has support for the built-in `process.env` & `.env` file - * @example - * const envHelper = new api.ODEnvHelper() - * - * const variableA = envHelper.getVariable("value-a") - * const variableB = envHelper.getVariable("value-b","dotenv") //only get from .env - * const variableA = envHelper.getVariable("value-c","env") //only get from process.env - */ -export class ODEnvHelper { - /**All variables found in the `.env` file */ - dotenv: object - /**All variables found in `process.env` */ - env: object - - constructor(customEnvPath?:string){ - if (typeof customEnvPath != "undefined" && typeof customEnvPath != "string") throw new ODSystemError("Invalid constructor parameter => customEnvPath?:string") - - const path = customEnvPath ? customEnvPath : ".env" - this.dotenv = fs.existsSync(path) ? this.#readDotEnv(fs.readFileSync(path)) : {} - this.env = process.env - } - - /**Get a variable from the env */ - getVariable(name:string,source?:"dotenv"|"env"): any|undefined { - if (typeof name != "string") throw new ODSystemError("Invalid function parameter => name:string") - if ((typeof source != "undefined" && typeof source != "string") || (source && !["env","dotenv"].includes(source))) throw new ODSystemError("Invalid function parameter => source:'dotenv'|'env'") - - if (source == "dotenv"){ - return this.dotenv[name] - }else if (source == "env"){ - return this.env[name] - }else{ - //when no source specified => .env has priority over process.env - if (this.dotenv[name]) return this.dotenv[name] - else return this.env[name] - } - } - - //THIS CODE IS COPIED FROM THE DODENV-LIB - //Repo: https://github.com/motdotla/dotenv - //Source: https://github.com/motdotla/dotenv/blob/master/lib/main.js#L12 - #readDotEnv(src:Buffer){ - const LINE = /(?:^|^)\s*(?:export\s+)?([\w.-]+)(?:\s*=\s*?|:\s+?)(\s*'(?:\\'|[^'])*'|\s*"(?:\\"|[^"])*"|\s*`(?:\\`|[^`])*`|[^#\r\n]+)?\s*(?:#.*)?(?:$|$)/mg - const obj = {} - - // Convert buffer to string - let lines = src.toString() - - // Convert line breaks to same format - lines = lines.replace(/\r\n?/mg, '\n') - - let match - while ((match = LINE.exec(lines)) != null) { - const key = match[1] - - // Default undefined or null to empty string - let value = (match[2] || '') - - // Remove whitespace - value = value.trim() - - // Check if double quoted - const maybeQuote = value[0] - - // Remove surrounding quotes - value = value.replace(/^(['"`])([\s\S]*)\1$/mg, '$2') - - // Expand newlines if double quoted - if (maybeQuote === '"') { - value = value.replace(/\\n/g, '\n') - value = value.replace(/\\r/g, '\r') - } - - // Add to object - obj[key] = value - } - return obj - } -} - -/**## ODSystemError `class` - * A wrapper for the node.js `Error` class that makes the error look better in the console! - * - * This wrapper is made for Open Ticket system errors! **It can only be used by Open Ticket itself!** - */ -export class ODSystemError extends Error { - /**This variable gets detected by the error handling system to know how to render it */ - _ODErrorType = "system" - - /**Create an `ODSystemError` directly from an `Error` class */ - static fromError(err:Error){ - err["_ODErrorType"] = "system" - return err as ODSystemError - } -} - -/**## ODPluginError `class` - * A wrapper for the node.js `Error` class that makes the error look better in the console! - * - * This wrapper is made for Open Ticket plugin errors! **It can only be used by plugins!** - */ -export class ODPluginError extends Error { - /**This variable gets detected by the error handling system to know how to render it */ - _ODErrorType = "plugin" - - /**Create an `ODPluginError` directly from an `Error` class */ - static fromError(err:Error){ - err["_ODErrorType"] = "plugin" - return err as ODPluginError - } -} - -/**Oh, what could this be `¯\_(ツ)_/¯` */ -export interface ODEasterEggs { - creator:string, - translators:string[] -} \ No newline at end of file diff --git a/src/core/api/modules/builder.ts b/src/core/api/modules/builder.ts deleted file mode 100644 index f864f2a..0000000 --- a/src/core/api/modules/builder.ts +++ /dev/null @@ -1,1554 +0,0 @@ -/////////////////////////////////////// -//BUILDER MODULE -/////////////////////////////////////// -import { ODId, ODValidButtonColor, ODValidId, ODSystemError, ODInterfaceWithPartialProperty, ODManagerWithSafety, ODManagerData } from "./base" -import * as discord from "discord.js" -import { ODWorkerManager, ODWorkerCallback, ODWorker } from "./worker" -import { ODDebugger } from "./console" - -/**## ODBuilderImplementation `class` - * This is an Open Ticket builder implementation. - * - * It is a basic implementation of the `ODWorkerManager` used by all `ODBuilder` classes. - * - * This class can't be used stand-alone & needs to be extended from! - */ -export class ODBuilderImplementation extends ODManagerData { - /**The manager that has all workers of this implementation */ - workers: ODWorkerManager - /**Cache a build or create it every time from scratch when this.build() gets executed. */ - allowCache: boolean = false - /**Did the build already got created/cached? */ - didCache: boolean = false - /**The cache of this build. */ - cache:BuildType|null = null - - constructor(id:ODValidId, callback?:ODWorkerCallback, priority?:number, callbackId?:ODValidId){ - super(id) - this.workers = new ODWorkerManager("ascending") - if (callback) this.workers.add(new ODWorker(callbackId ? callbackId : id,priority ?? 0,callback)) - } - - /**Set if caching is allowed */ - setCacheMode(allowed:boolean){ - this.allowCache = allowed - this.resetCache() - return this - } - /**Reset the current cache */ - resetCache(){ - this.cache = null - this.didCache = false - return this - } - /**Execute all workers & return the result. */ - async build(source:Source, params:Params): Promise { - throw new ODSystemError("Tried to build an unimplemented ODBuilderImplementation") - } -} - -/**## ODBuilderManager `class` - * This is an Open Ticket builder manager. - * - * It contains all Open Ticket builders. You can find messages, embeds, files & dropdowns, buttons & modals all here! - * - * Using the Open Ticket builder system has a few advantages compared to vanilla discord.js: - * - plugins can extend/edit messages - * - automatically reply on error - * - independent workers (with priority) - * - fail-safe design using try-catch - * - cache frequently used objects - * - get to know the source of the build request for a specific message, button, etc - * - And so much more! - */ -export class ODBuilderManager { - /**The manager for all button builders */ - buttons: ODButtonManager - /**The manager for all dropdown builders */ - dropdowns: ODDropdownManager - /**The manager for all file/attachment builders */ - files: ODFileManager - /**The manager for all embed builders */ - embeds: ODEmbedManager - /**The manager for all message builders */ - messages: ODMessageManager - /**The manager for all modal builders */ - modals: ODModalManager - - constructor(debug:ODDebugger){ - this.buttons = new ODButtonManager(debug) - this.dropdowns = new ODDropdownManager(debug) - this.files = new ODFileManager(debug) - this.embeds = new ODEmbedManager(debug) - this.messages = new ODMessageManager(debug) - this.modals = new ODModalManager(debug) - } -} - -/**## ODComponentBuildResult `interface` - * This interface contains the result from a built component (button/dropdown). This can be used in the `ODMessage` builder! - */ -export interface ODComponentBuildResult { - /**The id of this component (button or dropdown) */ - id:ODId, - /**The discord component or `\n` when it is a spacer between action rows */ - component:discord.MessageActionRowComponentBuilder|"\n"|null -} - -/**## ODButtonManager `class` - * This is an Open Ticket button manager. - * - * It contains all Open Ticket button builders. Here, you can add your own buttons or edit existing ones! - * - * It's recommended to use this system in combination with all the other Open Ticket builders! - */ -export class ODButtonManager extends ODManagerWithSafety> { - constructor(debug:ODDebugger){ - super(() => { - return new ODButton("opendiscord:unknown-button",(instance,params,source,cancel) => { - instance.setCustomId("od:unknown-button") - instance.setMode("button") - instance.setColor("red") - instance.setLabel("") - instance.setEmoji("✖") - instance.setDisabled(true) - cancel() - }) - },debug,"button") - } - - /**Get a newline component for buttons & dropdowns! */ - getNewLine(id:ODValidId): ODComponentBuildResult { - return { - id:new ODId(id), - component:"\n" - } - } -} - -/**## ODButtonData `interface` - * This interface contains the data to build a button. - */ -export interface ODButtonData { - /**The custom id of this button */ - customId:string, - /**The mode of this button */ - mode:"button"|"url", - /**The url for when the mode is set to "url" */ - url:string|null, - /**The button color */ - color:ODValidButtonColor|null, - /**The button label */ - label:string|null, - /**The button emoji */ - emoji:string|null, - /**Is the button disabled? */ - disabled:boolean -} - -/**## ODButtonInstance `class` - * This is an Open Ticket button instance. - * - * It contains all properties & functions to build a button! - */ -export class ODButtonInstance { - /**The current data of this button */ - data: ODButtonData = { - customId:"", - mode:"button", - url:null, - color:null, - label:null, - emoji:null, - disabled:false - } - - /**Set the custom id of this button */ - setCustomId(id:ODButtonData["customId"]){ - this.data.customId = id - return this - } - /**Set the mode of this button */ - setMode(mode:ODButtonData["mode"]){ - this.data.mode = mode - return this - } - /**Set the url of this button */ - setUrl(url:ODButtonData["url"]){ - this.data.url = url - return this - } - /**Set the color of this button */ - setColor(color:ODButtonData["color"]){ - this.data.color = color - return this - } - /**Set the label of this button */ - setLabel(label:ODButtonData["label"]){ - this.data.label = label - return this - } - /**Set the emoji of this button */ - setEmoji(emoji:ODButtonData["emoji"]){ - this.data.emoji = emoji - return this - } - /**Disable this button */ - setDisabled(disabled:ODButtonData["disabled"]){ - this.data.disabled = disabled - return this - } -} - -/**## ODButton `class` - * This is an Open Ticket button builder. - * - * With this class, you can create a button to use in a message. - * The only difference with normal buttons is that this one can be edited by Open Ticket plugins! - * - * This is possible by using "workers" or multiple functions that will be executed in priority order! - */ -export class ODButton extends ODBuilderImplementation { - /**Build this button & compile it for discord.js */ - async build(source:Source, params:Params): Promise { - if (this.didCache && this.cache && this.allowCache) return this.cache - - try { - //create instance - const instance = new ODButtonInstance() - - //wait for workers to finish - await this.workers.executeWorkers(instance,source,params) - - //create the discord.js button - const button = new discord.ButtonBuilder() - if (instance.data.mode == "button") button.setCustomId(instance.data.customId) - if (instance.data.mode == "url") button.setStyle(discord.ButtonStyle.Link) - else if (instance.data.color == "gray") button.setStyle(discord.ButtonStyle.Secondary) - else if (instance.data.color == "blue") button.setStyle(discord.ButtonStyle.Primary) - else if (instance.data.color == "green") button.setStyle(discord.ButtonStyle.Success) - else if (instance.data.color == "red") button.setStyle(discord.ButtonStyle.Danger) - if (instance.data.url) button.setURL(instance.data.url) - if (instance.data.label) button.setLabel(instance.data.label) - if (instance.data.emoji) button.setEmoji(instance.data.emoji) - if (instance.data.disabled) button.setDisabled(instance.data.disabled) - if (!instance.data.emoji && !instance.data.label) button.setLabel(instance.data.customId) - - this.cache = {id:this.id,component:button} - this.didCache = true - return {id:this.id,component:button} - }catch(err){ - process.emit("uncaughtException",new ODSystemError("ODButton:build(\""+this.id.value+"\") => Major Error (see next error)")) - process.emit("uncaughtException",err) - return {id:this.id,component:null} - } - } -} - -/**## ODQuickButton `class` - * This is an Open Ticket quick button builder. - * - * With this class, you can quickly create a button to use in a message. - * This quick button can be used by Open Ticket plugins instead of the normal builders to speed up the process! - * - * Because of the quick functionality, these buttons are less customisable by other plugins. - */ -export class ODQuickButton { - /**The id of this button. */ - id: ODId - /**The current data of this button */ - data: Partial - - constructor(id:ODValidId,data:Partial){ - this.id = new ODId(id) - this.data = data - } - - /**Build this button & compile it for discord.js */ - async build(): Promise { - try { - //create the discord.js button - const button = new discord.ButtonBuilder() - if (this.data.mode == "button" || (!this.data.mode && this.data.customId)) button.setCustomId(this.data.customId ?? "od:unknown-button") - if (this.data.mode == "url") button.setStyle(discord.ButtonStyle.Link) - else if (this.data.color == "gray") button.setStyle(discord.ButtonStyle.Secondary) - else if (this.data.color == "blue") button.setStyle(discord.ButtonStyle.Primary) - else if (this.data.color == "green") button.setStyle(discord.ButtonStyle.Success) - else if (this.data.color == "red") button.setStyle(discord.ButtonStyle.Danger) - else button.setStyle(discord.ButtonStyle.Secondary) - if (this.data.url) button.setURL(this.data.url) - if (this.data.label) button.setLabel(this.data.label) - if (this.data.emoji) button.setEmoji(this.data.emoji) - if (this.data.disabled) button.setDisabled(this.data.disabled) - if (!this.data.emoji && !this.data.label) button.setLabel(this.data.customId ?? "od:unknown-button") - - return {id:this.id,component:button} - }catch(err){ - process.emit("uncaughtException",new ODSystemError("ODQuickButton:build(\""+this.id.value+"\") => Major Error (see next error)")) - process.emit("uncaughtException",err) - return {id:this.id,component:null} - } - } -} - -/**## ODDropdownManager `class` - * This is an Open Ticket dropdown manager. - * - * It contains all Open Ticket dropdown builders. Here, you can add your own dropdowns or edit existing ones! - * - * It's recommended to use this system in combination with all the other Open Ticket builders! - */ -export class ODDropdownManager extends ODManagerWithSafety> { - constructor(debug:ODDebugger){ - super(() => { - return new ODDropdown("opendiscord:unknown-dropdown",(instance,params,source,cancel) => { - instance.setCustomId("od:unknown-dropdown") - instance.setType("string") - instance.setPlaceholder("❌ ") - instance.setDisabled(true) - instance.setOptions([ - {emoji:"❌",label:"",value:"error"} - ]) - cancel() - }) - },debug,"dropdown") - } - - /**Get a newline component for buttons & dropdowns! */ - getNewLine(id:ODValidId): ODComponentBuildResult { - return { - id:new ODId(id), - component:"\n" - } - } -} - -/**## ODDropdownData `interface` - * This interface contains the data to build a dropdown. - */ -export interface ODDropdownData { - /**The custom id of this dropdown */ - customId:string, - /**The type of this dropdown */ - type:"string"|"role"|"channel"|"user"|"mentionable", - /**The placeholder of this dropdown */ - placeholder:string|null, - /**The minimum amount of items to be selected in this dropdown */ - minValues:number|null, - /**The maximum amount of items to be selected in this dropdown */ - maxValues:number|null, - /**Is this dropdown disabled? */ - disabled:boolean, - /**Allowed channel types when the type is "channel" */ - channelTypes:discord.ChannelType[] - - /**The options when the type is "string" */ - options:discord.SelectMenuComponentOptionData[], - /**The options when the type is "user" */ - users:discord.User[], - /**The options when the type is "role" */ - roles:discord.Role[], - /**The options when the type is "channel" */ - channels:discord.Channel[], - /**The options when the type is "mentionable" */ - mentionables:(discord.User|discord.Role)[], -} - -/**## ODDropdownInstance `class` - * This is an Open Ticket dropdown instance. - * - * It contains all properties & functions to build a dropdown! - */ -export class ODDropdownInstance { - /**The current data of this dropdown */ - data: ODDropdownData = { - customId:"", - type:"string", - placeholder:null, - minValues:null, - maxValues:null, - disabled:false, - channelTypes:[], - - options:[], - users:[], - roles:[], - channels:[], - mentionables:[] - } - - /**Set the custom id of this dropdown */ - setCustomId(id:ODDropdownData["customId"]){ - this.data.customId = id - return this - } - /**Set the type of this dropdown */ - setType(type:ODDropdownData["type"]){ - this.data.type = type - return this - } - /**Set the placeholder of this dropdown */ - setPlaceholder(placeholder:ODDropdownData["placeholder"]){ - this.data.placeholder = placeholder - return this - } - /**Set the minimum amount of values in this dropdown */ - setMinValues(minValues:ODDropdownData["minValues"]){ - this.data.minValues = minValues - return this - } - /**Set the maximum amount of values ax this dropdown */ - setMaxValues(maxValues:ODDropdownData["maxValues"]){ - this.data.maxValues = maxValues - return this - } - /**Set the disabled of this dropdown */ - setDisabled(disabled:ODDropdownData["disabled"]){ - this.data.disabled = disabled - return this - } - /**Set the channel types of this dropdown */ - setChannelTypes(channelTypes:ODDropdownData["channelTypes"]){ - this.data.channelTypes = channelTypes - return this - } - /**Set the options of this dropdown (when `type == "string"`) */ - setOptions(options:ODDropdownData["options"]){ - this.data.options = options - return this - } - /**Set the users of this dropdown (when `type == "user"`) */ - setUsers(users:ODDropdownData["users"]){ - this.data.users = users - return this - } - /**Set the roles of this dropdown (when `type == "role"`) */ - setRoles(roles:ODDropdownData["roles"]){ - this.data.roles = roles - return this - } - /**Set the channels of this dropdown (when `type == "channel"`) */ - setChannels(channels:ODDropdownData["channels"]){ - this.data.channels = channels - return this - } - /**Set the mentionables of this dropdown (when `type == "mentionable"`) */ - setMentionables(mentionables:ODDropdownData["mentionables"]){ - this.data.mentionables = mentionables - return this - } -} - -/**## ODDropdown `class` - * This is an Open Ticket dropdown builder. - * - * With this class, you can create a dropdown to use in a message. - * The only difference with normal dropdowns is that this one can be edited by Open Ticket plugins! - * - * This is possible by using "workers" or multiple functions that will be executed in priority order! - */ -export class ODDropdown extends ODBuilderImplementation { - /**Build this dropdown & compile it for discord.js */ - async build(source:Source, params:Params): Promise { - if (this.didCache && this.cache && this.allowCache) return this.cache - - try{ - //create instance - const instance = new ODDropdownInstance() - - //wait for workers to finish - await this.workers.executeWorkers(instance,source,params) - - //create the discord.js dropdown - if (instance.data.type == "string"){ - const dropdown = new discord.StringSelectMenuBuilder() - dropdown.setCustomId(instance.data.customId) - dropdown.setOptions(...instance.data.options) - if (instance.data.placeholder) dropdown.setPlaceholder(instance.data.placeholder) - if (instance.data.minValues) dropdown.setMinValues(instance.data.minValues) - if (instance.data.maxValues) dropdown.setMaxValues(instance.data.maxValues) - if (instance.data.disabled) dropdown.setDisabled(instance.data.disabled) - - this.cache = {id:this.id,component:dropdown} - this.didCache = true - return {id:this.id,component:dropdown} - - }else if (instance.data.type == "user"){ - const dropdown = new discord.UserSelectMenuBuilder() - dropdown.setCustomId(instance.data.customId) - if (instance.data.users.length > 0) dropdown.setDefaultUsers(...instance.data.users.map((u) => u.id)) - if (instance.data.placeholder) dropdown.setPlaceholder(instance.data.placeholder) - if (instance.data.minValues) dropdown.setMinValues(instance.data.minValues) - if (instance.data.maxValues) dropdown.setMaxValues(instance.data.maxValues) - if (instance.data.disabled) dropdown.setDisabled(instance.data.disabled) - - this.cache = {id:this.id,component:dropdown} - this.didCache = true - return {id:this.id,component:dropdown} - - }else if (instance.data.type == "role"){ - const dropdown = new discord.RoleSelectMenuBuilder() - dropdown.setCustomId(instance.data.customId) - if (instance.data.roles.length > 0) dropdown.setDefaultRoles(...instance.data.roles.map((r) => r.id)) - if (instance.data.placeholder) dropdown.setPlaceholder(instance.data.placeholder) - if (instance.data.minValues) dropdown.setMinValues(instance.data.minValues) - if (instance.data.maxValues) dropdown.setMaxValues(instance.data.maxValues) - if (instance.data.disabled) dropdown.setDisabled(instance.data.disabled) - - this.cache = {id:this.id,component:dropdown} - this.didCache = true - return {id:this.id,component:dropdown} - - }else if (instance.data.type == "channel"){ - const dropdown = new discord.ChannelSelectMenuBuilder() - dropdown.setCustomId(instance.data.customId) - if (instance.data.channels.length > 0) dropdown.setDefaultChannels(...instance.data.channels.map((c) => c.id)) - if (instance.data.placeholder) dropdown.setPlaceholder(instance.data.placeholder) - if (instance.data.minValues) dropdown.setMinValues(instance.data.minValues) - if (instance.data.maxValues) dropdown.setMaxValues(instance.data.maxValues) - if (instance.data.disabled) dropdown.setDisabled(instance.data.disabled) - - this.cache = {id:this.id,component:dropdown} - this.didCache = true - return {id:this.id,component:dropdown} - - }else if (instance.data.type == "mentionable"){ - const dropdown = new discord.MentionableSelectMenuBuilder() - - const values: ({type:discord.SelectMenuDefaultValueType.User,id:string}|{type:discord.SelectMenuDefaultValueType.Role,id:string})[] = [] - instance.data.mentionables.forEach((m) => { - if (m instanceof discord.User){ - values.push({type:discord.SelectMenuDefaultValueType.User,id:m.id}) - }else{ - values.push({type:discord.SelectMenuDefaultValueType.Role,id:m.id}) - } - }) - - dropdown.setCustomId(instance.data.customId) - if (instance.data.mentionables.length > 0) dropdown.setDefaultValues(...values) - if (instance.data.placeholder) dropdown.setPlaceholder(instance.data.placeholder) - if (instance.data.minValues) dropdown.setMinValues(instance.data.minValues) - if (instance.data.maxValues) dropdown.setMaxValues(instance.data.maxValues) - if (instance.data.disabled) dropdown.setDisabled(instance.data.disabled) - - this.cache = {id:this.id,component:dropdown} - this.didCache = true - return {id:this.id,component:dropdown} - }else{ - throw new Error("Tried to build an ODDropdown with unknown type!") - } - }catch(err){ - process.emit("uncaughtException",new ODSystemError("ODDropdown:build(\""+this.id.value+"\") => Major Error (see next error)")) - process.emit("uncaughtException",err) - return {id:this.id,component:null} - } - } -} - -/**## ODQuickDropdown `class` - * This is an Open Ticket quick dropdown builder. - * - * With this class, you can quickly create a dropdown to use in a message. - * This quick dropdown can be used by Open Ticket plugins instead of the normal builders to speed up the process! - * - * Because of the quick functionality, these dropdowns are less customisable by other plugins. - */ -export class ODQuickDropdown { - /**The id of this dropdown. */ - id: ODId - /**The current data of this dropdown */ - data: Partial - - constructor(id:ODValidId,data:Partial){ - this.id = new ODId(id) - this.data = data - } - - /**Build this dropdown & compile it for discord.js */ - async build(): Promise { - try{ - //create the discord.js dropdown - if (this.data.type == "string"){ - if (!this.data.options) throw new ODSystemError("ODQuickDropdown:build(): "+this.id.value+" => Dropdown requires at least 1 option to be present.") - const dropdown = new discord.StringSelectMenuBuilder() - dropdown.setCustomId(this.data.customId ?? "od:unknown-dropdown") - dropdown.setOptions(...this.data.options) - if (this.data.placeholder) dropdown.setPlaceholder(this.data.placeholder) - if (this.data.minValues) dropdown.setMinValues(this.data.minValues) - if (this.data.maxValues) dropdown.setMaxValues(this.data.maxValues) - if (this.data.disabled) dropdown.setDisabled(this.data.disabled) - - return {id:this.id,component:dropdown} - - }else if (this.data.type == "user"){ - if (!this.data.users) throw new ODSystemError("ODQuickDropdown:build(): "+this.id.value+" => Dropdown requires at least 1 user option to be present.") - const dropdown = new discord.UserSelectMenuBuilder() - dropdown.setCustomId(this.data.customId ?? "od:unknown-dropdown") - if (this.data.users.length > 0) dropdown.setDefaultUsers(...this.data.users.map((u) => u.id)) - if (this.data.placeholder) dropdown.setPlaceholder(this.data.placeholder) - if (this.data.minValues) dropdown.setMinValues(this.data.minValues) - if (this.data.maxValues) dropdown.setMaxValues(this.data.maxValues) - if (this.data.disabled) dropdown.setDisabled(this.data.disabled) - - return {id:this.id,component:dropdown} - - }else if (this.data.type == "role"){ - if (!this.data.roles) throw new ODSystemError("ODQuickDropdown:build(): "+this.id.value+" => Dropdown requires at least 1 role option to be present.") - const dropdown = new discord.RoleSelectMenuBuilder() - dropdown.setCustomId(this.data.customId ?? "od:unknown-dropdown") - if (this.data.roles.length > 0) dropdown.setDefaultRoles(...this.data.roles.map((r) => r.id)) - if (this.data.placeholder) dropdown.setPlaceholder(this.data.placeholder) - if (this.data.minValues) dropdown.setMinValues(this.data.minValues) - if (this.data.maxValues) dropdown.setMaxValues(this.data.maxValues) - if (this.data.disabled) dropdown.setDisabled(this.data.disabled) - - return {id:this.id,component:dropdown} - - }else if (this.data.type == "channel"){ - if (!this.data.channels) throw new ODSystemError("ODQuickDropdown:build(): "+this.id.value+" => Dropdown requires at least 1 channel option to be present.") - const dropdown = new discord.ChannelSelectMenuBuilder() - dropdown.setCustomId(this.data.customId ?? "od:unknown-dropdown") - if (this.data.channels.length > 0) dropdown.setDefaultChannels(...this.data.channels.map((c) => c.id)) - if (this.data.placeholder) dropdown.setPlaceholder(this.data.placeholder) - if (this.data.minValues) dropdown.setMinValues(this.data.minValues) - if (this.data.maxValues) dropdown.setMaxValues(this.data.maxValues) - if (this.data.disabled) dropdown.setDisabled(this.data.disabled) - - return {id:this.id,component:dropdown} - - }else if (this.data.type == "mentionable"){ - if (!this.data.mentionables) throw new ODSystemError("ODQuickDropdown:build(): "+this.id.value+" => Dropdown requires at least 1 mentionable option to be present.") - const dropdown = new discord.MentionableSelectMenuBuilder() - - const values: ({type:discord.SelectMenuDefaultValueType.User,id:string}|{type:discord.SelectMenuDefaultValueType.Role,id:string})[] = [] - this.data.mentionables.forEach((m) => { - if (m instanceof discord.User){ - values.push({type:discord.SelectMenuDefaultValueType.User,id:m.id}) - }else{ - values.push({type:discord.SelectMenuDefaultValueType.Role,id:m.id}) - } - }) - - dropdown.setCustomId(this.data.customId ?? "od:unknown-dropdown") - if (this.data.mentionables.length > 0) dropdown.setDefaultValues(...values) - if (this.data.placeholder) dropdown.setPlaceholder(this.data.placeholder) - if (this.data.minValues) dropdown.setMinValues(this.data.minValues) - if (this.data.maxValues) dropdown.setMaxValues(this.data.maxValues) - if (this.data.disabled) dropdown.setDisabled(this.data.disabled) - - return {id:this.id,component:dropdown} - }else{ - throw new Error("Tried to build an ODQuickDropdown with unknown type!") - } - }catch(err){ - process.emit("uncaughtException",new ODSystemError("ODQuickDropdown:build(\""+this.id.value+"\") => Major Error (see next error)")) - process.emit("uncaughtException",err) - return {id:this.id,component:null} - } - } -} - -/**## ODFileManager `class` - * This is an Open Ticket file manager. - * - * It contains all Open Ticket file builders. Here, you can add your own files or edit existing ones! - * - * It's recommended to use this system in combination with all the other Open Ticket builders! - */ -export class ODFileManager extends ODManagerWithSafety> { - constructor(debug:ODDebugger){ - super(() => { - return new ODFile("opendiscord:unknown-file",(instance,params,source,cancel) => { - instance.setName("openticket_unknown-file.txt") - instance.setDescription("❌ ") - instance.setContents("Couldn't find file in registery `opendiscord.builders.files`") - cancel() - }) - },debug,"file") - } -} - -/**## ODFileData `interface` - * This interface contains the data to build a file. - */ -export interface ODFileData { - /**The file buffer, string or raw data */ - file:discord.BufferResolvable - /**The name of the file */ - name:string, - /**The description of the file */ - description:string|null, - /**Set the file to be a spoiler */ - spoiler:boolean -} - -/**## ODFileBuildResult `interface` - * This interface contains the result from a built file (attachment). This can be used in the `ODMessage` builder! - */ -export interface ODFileBuildResult { - /**The id of this file */ - id:ODId, - /**The discord file */ - file:discord.AttachmentBuilder|null -} - -/**## ODFileInstance `class` - * This is an Open Ticket file instance. - * - * It contains all properties & functions to build a file! - */ -export class ODFileInstance { - /**The current data of this file */ - data: ODFileData = { - file:"", - name:"file.txt", - description:null, - spoiler:false - } - - /**Set the file path of this attachment */ - setFile(file:string){ - this.data.file = file - return this - } - /**Set the file contents of this attachment */ - setContents(contents:string|Buffer){ - this.data.file = (typeof contents == "string") ? Buffer.from(contents) : contents - return this - } - /**Set the name of this attachment */ - setName(name:ODFileData["name"]){ - this.data.name = name - return this - } - /**Set the description of this attachment */ - setDescription(description:ODFileData["description"]){ - this.data.description = description - return this - } - /**Set this attachment to show as a spoiler */ - setSpoiler(spoiler:ODFileData["spoiler"]){ - this.data.spoiler = spoiler - return this - } -} - -/**## ODFile `class` - * This is an Open Ticket file builder. - * - * With this class, you can create a file to use in a message. - * The only difference with normal files is that this one can be edited by Open Ticket plugins! - * - * This is possible by using "workers" or multiple functions that will be executed in priority order! - */ -export class ODFile extends ODBuilderImplementation { - /**Build this attachment & compile it for discord.js */ - async build(source:Source, params:Params): Promise { - if (this.didCache && this.cache && this.allowCache) return this.cache - - try{ - //create instance - const instance = new ODFileInstance() - - //wait for workers to finish - await this.workers.executeWorkers(instance,source,params) - - //create the discord.js attachment - const file = new discord.AttachmentBuilder(instance.data.file) - file.setName(instance.data.name ? instance.data.name : "file.txt") - if (instance.data.description) file.setDescription(instance.data.description) - if (instance.data.spoiler) file.setSpoiler(instance.data.spoiler) - - - this.cache = {id:this.id,file} - this.didCache = true - return {id:this.id,file} - }catch(err){ - process.emit("uncaughtException",new ODSystemError("ODFile:build(\""+this.id.value+"\") => Major Error (see next error)")) - process.emit("uncaughtException",err) - return {id:this.id,file:null} - } - } -} - -/**## ODQuickFile `class` - * This is an Open Ticket quick file builder. - * - * With this class, you can quickly create a file to use in a message. - * This quick file can be used by Open Ticket plugins instead of the normal builders to speed up the process! - * - * Because of the quick functionality, these files are less customisable by other plugins. - */ -export class ODQuickFile { - /**The id of this file. */ - id: ODId - /**The current data of this file */ - data: Partial - - constructor(id:ODValidId,data:Partial){ - this.id = new ODId(id) - this.data = data - } - - /**Build this attachment & compile it for discord.js */ - async build(): Promise { - try{ - //create the discord.js attachment - const file = new discord.AttachmentBuilder(this.data.file ?? "") - file.setName(this.data.name ? this.data.name : "file.txt") - if (this.data.description) file.setDescription(this.data.description) - if (this.data.spoiler) file.setSpoiler(this.data.spoiler) - - return {id:this.id,file} - }catch(err){ - process.emit("uncaughtException",new ODSystemError("ODQuickFile:build(\""+this.id.value+"\") => Major Error (see next error)")) - process.emit("uncaughtException",err) - return {id:this.id,file:null} - } - } -} - -/**## ODEmbedManager `class` - * This is an Open Ticket embed manager. - * - * It contains all Open Ticket embed builders. Here, you can add your own embeds or edit existing ones! - * - * It's recommended to use this system in combination with all the other Open Ticket builders! - */ -export class ODEmbedManager extends ODManagerWithSafety> { - constructor(debug:ODDebugger){ - super(() => { - return new ODEmbed("opendiscord:unknown-embed",(instance,params,source,cancel) => { - instance.setFooter("opendiscord:unknown-embed") - instance.setColor("#ff0000") - instance.setTitle("❌ ") - instance.setDescription("Couldn't find embed in registery `opendiscord.builders.embeds`") - cancel() - }) - },debug,"embed") - } -} - -/**## ODEmbedData `interface` - * This interface contains the data to build an embed. - */ -export interface ODEmbedData { - /**The title of the embed */ - title:string|null, - /**The color of the embed */ - color:discord.ColorResolvable|string|null, - /**The url of the embed */ - url:string|null, - /**The description of the embed */ - description:string|null, - /**The author text of the embed */ - authorText:string|null, - /**The author image of the embed */ - authorImage:string|null, - /**The author url of the embed */ - authorUrl:string|null, - /**The footer text of the embed */ - footerText:string|null, - /**The footer image of the embed */ - footerImage:string|null, - /**The image of the embed */ - image:string|null, - /**The thumbnail of the embed */ - thumbnail:string|null, - /**The fields of the embed */ - fields:ODInterfaceWithPartialProperty[], - /**The timestamp of the embed */ - timestamp:number|Date|null -} - -/**## ODEmbedBuildResult `interface` - * This interface contains the result from a built embed. This can be used in the `ODMessage` builder! - */ -export interface ODEmbedBuildResult { - /**The id of this embed */ - id:ODId, - /**The discord embed */ - embed:discord.EmbedBuilder|null -} - -/**## ODEmbedInstance `class` - * This is an Open Ticket embed instance. - * - * It contains all properties & functions to build an embed! - */ -export class ODEmbedInstance { - /**The current data of this embed */ - data: ODEmbedData = { - title:null, - color:null, - url:null, - description:null, - authorText:null, - authorImage:null, - authorUrl:null, - footerText:null, - footerImage:null, - image:null, - thumbnail:null, - fields:[], - timestamp:null - } - - /**Set the title of this embed */ - setTitle(title:ODEmbedData["title"]){ - this.data.title = title - return this - } - /**Set the color of this embed */ - setColor(color:ODEmbedData["color"]){ - this.data.color = color - return this - } - /**Set the url of this embed */ - setUrl(url:ODEmbedData["url"]){ - this.data.url = url - return this - } - /**Set the description of this embed */ - setDescription(description:ODEmbedData["description"]){ - this.data.description = description - return this - } - /**Set the author of this embed */ - setAuthor(text:ODEmbedData["authorText"], image?:ODEmbedData["authorImage"], url?:ODEmbedData["authorUrl"]){ - this.data.authorText = text - this.data.authorImage = image ?? null - this.data.authorUrl = url ?? null - return this - } - /**Set the footer of this embed */ - setFooter(text:ODEmbedData["footerText"], image?:ODEmbedData["footerImage"]){ - this.data.footerText = text - this.data.footerImage = image ?? null - return this - } - /**Set the image of this embed */ - setImage(image:ODEmbedData["image"]){ - this.data.image = image - return this - } - /**Set the thumbnail of this embed */ - setThumbnail(thumbnail:ODEmbedData["thumbnail"]){ - this.data.thumbnail = thumbnail - return this - } - /**Set the fields of this embed */ - setFields(fields:ODEmbedData["fields"]){ - //TEMP CHECKS - fields.forEach((field,index) => { - if (field.value.length >= 1024) throw new ODSystemError("ODEmbed:setFields() => field "+index+" reached 1024 character limit!") - if (field.name.length >= 256) throw new ODSystemError("ODEmbed:setFields() => field "+index+" reached 256 name character limit!") - }) - - this.data.fields = fields - return this - } - /**Add fields to this embed */ - addFields(...fields:ODEmbedData["fields"]){ - //TEMP CHECKS - fields.forEach((field,index) => { - if (field.value.length >= 1024) throw new ODSystemError("ODEmbed:addFields() => field "+index+" reached 1024 character limit!") - if (field.name.length >= 256) throw new ODSystemError("ODEmbed:addFields() => field "+index+" reached 256 name character limit!") - }) - - this.data.fields.push(...fields) - return this - } - /**Clear all fields from this embed */ - clearFields(){ - this.data.fields = [] - return this - } - /**Set the timestamp of this embed */ - setTimestamp(timestamp:ODEmbedData["timestamp"]){ - this.data.timestamp = timestamp - return this - } -} - -/**## ODEmbed `class` - * This is an Open Ticket embed builder. - * - * With this class, you can create a embed to use in a message. - * The only difference with normal embeds is that this one can be edited by Open Ticket plugins! - * - * This is possible by using "workers" or multiple functions that will be executed in priority order! - */ -export class ODEmbed extends ODBuilderImplementation { - /**Build this embed & compile it for discord.js */ - async build(source:Source, params:Params): Promise { - if (this.didCache && this.cache && this.allowCache) return this.cache - - try{ - //create instance - const instance = new ODEmbedInstance() - - //wait for workers to finish - await this.workers.executeWorkers(instance,source,params) - - //create the discord.js embed - const embed = new discord.EmbedBuilder() - if (instance.data.title) embed.setTitle(instance.data.title) - if (instance.data.color) embed.setColor(instance.data.color as discord.ColorResolvable) - if (instance.data.url) embed.setURL(instance.data.url) - if (instance.data.description) embed.setDescription(instance.data.description) - if (instance.data.authorText) embed.setAuthor({ - name:instance.data.authorText, - iconURL:instance.data.authorImage ?? undefined, - url:instance.data.authorUrl ?? undefined - }) - if (instance.data.footerText) embed.setFooter({ - text:instance.data.footerText, - iconURL:instance.data.footerImage ?? undefined, - }) - if (instance.data.image) embed.setImage(instance.data.image) - if (instance.data.thumbnail) embed.setThumbnail(instance.data.thumbnail) - if (instance.data.timestamp) embed.setTimestamp(instance.data.timestamp) - if (instance.data.fields.length > 0) embed.setFields(instance.data.fields) - - this.cache = {id:this.id,embed} - this.didCache = true - return {id:this.id,embed} - }catch(err){ - process.emit("uncaughtException",new ODSystemError("ODEmbed:build(\""+this.id.value+"\") => Major Error (see next error)")) - process.emit("uncaughtException",err) - return {id:this.id,embed:null} - } - } -} - -/**## ODQuickEmbed `class` - * This is an Open Ticket quick embed builder. - * - * With this class, you can quickly create a embed to use in a message. - * This quick embed can be used by Open Ticket plugins instead of the normal builders to speed up the process! - * - * Because of the quick functionality, these embeds are less customisable by other plugins. - */ -export class ODQuickEmbed { - /**The id of this embed. */ - id: ODId - /**The current data of this embed */ - data: Partial - - constructor(id:ODValidId,data:Partial){ - this.id = new ODId(id) - this.data = data - } - - /**Build this embed & compile it for discord.js */ - async build(): Promise { - try{ - //create the discord.js embed - const embed = new discord.EmbedBuilder() - if (this.data.title) embed.setTitle(this.data.title) - if (this.data.color) embed.setColor(this.data.color as discord.ColorResolvable) - if (this.data.url) embed.setURL(this.data.url) - if (this.data.description) embed.setDescription(this.data.description) - if (this.data.authorText) embed.setAuthor({ - name:this.data.authorText, - iconURL:this.data.authorImage ?? undefined, - url:this.data.authorUrl ?? undefined - }) - if (this.data.footerText) embed.setFooter({ - text:this.data.footerText, - iconURL:this.data.footerImage ?? undefined, - }) - if (this.data.image) embed.setImage(this.data.image) - if (this.data.thumbnail) embed.setThumbnail(this.data.thumbnail) - if (this.data.timestamp) embed.setTimestamp(this.data.timestamp) - if (this.data.fields && this.data.fields.length > 0) embed.setFields(this.data.fields) - - return {id:this.id,embed} - }catch(err){ - process.emit("uncaughtException",new ODSystemError("ODQuickEmbed:build(\""+this.id.value+"\") => Major Error (see next error)")) - process.emit("uncaughtException",err) - return {id:this.id,embed:null} - } - } -} - -/**## ODMessageManager `class` - * This is an Open Ticket message manager. - * - * It contains all Open Ticket message builders. Here, you can add your own messages or edit existing ones! - * - * It's recommended to use this system in combination with all the other Open Ticket builders! - */ -export class ODMessageManager extends ODManagerWithSafety> { - constructor(debug:ODDebugger){ - super(() => { - return new ODMessage("opendiscord:unknown-message",(instance,params,source,cancel) => { - instance.setContent("**❌ **\nCouldn't find message in registery `opendiscord.builders.messages`") - cancel() - }) - },debug,"message") - } -} - -/**## ODMessageData `interface` - * This interface contains the data to build a message. - */ -export interface ODMessageData { - /**The content of this message. `null` when no content */ - content:string|null, - /**Poll data for this message */ - poll:discord.PollData|null, - /**Try to make this message ephemeral when available */ - ephemeral:boolean, - - /**Embeds from this message */ - embeds:ODEmbedBuildResult[], - /**Components from this message */ - components:ODComponentBuildResult[], - /**Files from this message */ - files:ODFileBuildResult[], - - /**Additional options that aren't covered by the Open Ticket api!*/ - additionalOptions:Omit -} - -/**## ODMessageBuildResult `interface` - * This interface contains the result from a built message. This can be sent in a discord channel! - */ -export interface ODMessageBuildResult { - /**The id of this message */ - id:ODId, - /**The discord message */ - message:Omit, - /**When enabled, the bot will try to send this as an ephemeral message */ - ephemeral:boolean -} - -/**## ODMessageBuildSentResult `interface` - * This interface contains the result from a sent built message. This can be used to edit, view & save the message that got created. - */ -export interface ODMessageBuildSentResult { - /**Did the message get sent successfully? */ - success:boolean, - /**The message that got sent. */ - message:discord.Message|null -} - -/**## ODMessageInstance `class` - * This is an Open Ticket message instance. - * - * It contains all properties & functions to build a message! - */ -export class ODMessageInstance { - /**The current data of this message */ - data: ODMessageData = { - content:null, - poll:null, - ephemeral:false, - embeds:[], - components:[], - files:[], - additionalOptions:{} - } - - /**Set the content of this message */ - setContent(content:ODMessageData["content"]){ - this.data.content = content - return this - } - /**Set the poll of this message */ - setPoll(poll:ODMessageData["poll"]){ - this.data.poll = poll - return this - } - /**Make this message ephemeral when possible */ - setEphemeral(ephemeral:ODMessageData["ephemeral"]){ - this.data.ephemeral = ephemeral - return this - } - /**Set the embeds of this message */ - setEmbeds(...embeds:ODEmbedBuildResult[]){ - this.data.embeds = embeds - return this - } - /**Add an embed to this message! */ - addEmbed(embed:ODEmbedBuildResult){ - this.data.embeds.push(embed) - return this - } - /**Remove an embed from this message */ - removeEmbed(id:ODValidId){ - const index = this.data.embeds.findIndex((embed) => embed.id.value === new ODId(id).value) - if (index > -1) this.data.embeds.splice(index,1) - return this - } - /**Get an embed from this message */ - getEmbed(id:ODValidId){ - const embed = this.data.embeds.find((embed) => embed.id.value === new ODId(id).value) - if (embed) return embed.embed - else return null - } - /**Set the components of this message */ - setComponents(...components:ODComponentBuildResult[]){ - this.data.components = components - return this - } - /**Add a component to this message! */ - addComponent(component:ODComponentBuildResult){ - this.data.components.push(component) - return this - } - /**Remove a component from this message */ - removeComponent(id:ODValidId){ - const index = this.data.components.findIndex((component) => component.id.value === new ODId(id).value) - if (index > -1) this.data.components.splice(index,1) - return this - } - /**Get a component from this message */ - getComponent(id:ODValidId){ - const component = this.data.components.find((component) => component.id.value === new ODId(id).value) - if (component) return component.component - else return null - } - /**Set the files of this message */ - setFiles(...files:ODFileBuildResult[]){ - this.data.files = files - return this - } - /**Add a file to this message! */ - addFile(file:ODFileBuildResult){ - this.data.files.push(file) - return this - } - /**Remove a file from this message */ - removeFile(id:ODValidId){ - const index = this.data.files.findIndex((file) => file.id.value === new ODId(id).value) - if (index > -1) this.data.files.splice(index,1) - return this - } - /**Get a file from this message */ - getFile(id:ODValidId){ - const file = this.data.files.find((file) => file.id.value === new ODId(id).value) - if (file) return file.file - else return null - } -} - -/**## ODMessage `class` - * This is an Open Ticket message builder. - * - * With this class, you can create a message to send in a discord channel. - * The only difference with normal messages is that this one can be edited by Open Ticket plugins! - * - * This is possible by using "workers" or multiple functions that will be executed in priority order! - */ -export class ODMessage extends ODBuilderImplementation { - /**Build this message & compile it for discord.js */ - async build(source:Source, params:Params){ - if (this.didCache && this.cache && this.allowCache) return this.cache - - //create instance - const instance = new ODMessageInstance() - - //wait for workers to finish - await this.workers.executeWorkers(instance,source,params) - - //create the discord.js message - const componentArray: discord.ActionRowBuilder[] = [] - let currentRow: discord.ActionRowBuilder = new discord.ActionRowBuilder() - instance.data.components.forEach((c) => { - //return when component crashed - if (c.component == null) return - else if (c.component == "\n"){ - //create new current row when required - if (currentRow.components.length > 0){ - componentArray.push(currentRow) - currentRow = new discord.ActionRowBuilder() - } - }else if (c.component instanceof discord.BaseSelectMenuBuilder){ - //push current row when not empty - if (currentRow.components.length > 0){ - componentArray.push(currentRow) - currentRow = new discord.ActionRowBuilder() - } - currentRow.addComponents(c.component) - //create new current row after dropdown - componentArray.push(currentRow) - currentRow = new discord.ActionRowBuilder() - }else{ - //push button to current row - currentRow.addComponents(c.component) - } - - //create new row when 5 rows in length - if (currentRow.components.length == 5){ - componentArray.push(currentRow) - currentRow = new discord.ActionRowBuilder() - } - }) - //push final row to array - if (currentRow.components.length > 0) componentArray.push(currentRow) - - const filteredEmbeds = instance.data.embeds.map((e) => e.embed).filter((e) => e instanceof discord.EmbedBuilder) as discord.EmbedBuilder[] - const filteredFiles = instance.data.files.map((f) => f.file).filter((f) => f instanceof discord.AttachmentBuilder) as discord.AttachmentBuilder[] - - const message : discord.MessageCreateOptions = { - content:instance.data.content ?? "", - poll:instance.data.poll ?? undefined, - embeds:filteredEmbeds, - components:componentArray, - files:filteredFiles - } - - let result = {id:this.id,message,ephemeral:instance.data.ephemeral} - - Object.assign(result.message,instance.data.additionalOptions) - - this.cache = result - this.didCache = true - return result - } -} - -/**## ODQuickMessage `class` - * This is an Open Ticket quick message builder. - * - * With this class, you can quickly create a message to send in a discord channel. - * This quick message can be used by Open Ticket plugins instead of the normal builders to speed up the process! - * - * Because of the quick functionality, these messages are less customisable by other plugins. - */ -export class ODQuickMessage { - /**The id of this message. */ - id: ODId - /**The current data of this message. */ - data: Partial - - constructor(id:ODValidId,data:Partial){ - this.id = new ODId(id) - this.data = data - } - - /**Build this message & compile it for discord.js */ - async build(): Promise { - //create the discord.js message - const componentArray: discord.ActionRowBuilder[] = [] - let currentRow: discord.ActionRowBuilder = new discord.ActionRowBuilder() - this.data.components?.forEach((c) => { - //return when component crashed - if (c.component == null) return - else if (c.component == "\n"){ - //create new current row when required - if (currentRow.components.length > 0){ - componentArray.push(currentRow) - currentRow = new discord.ActionRowBuilder() - } - }else if (c.component instanceof discord.BaseSelectMenuBuilder){ - //push current row when not empty - if (currentRow.components.length > 0){ - componentArray.push(currentRow) - currentRow = new discord.ActionRowBuilder() - } - currentRow.addComponents(c.component) - //create new current row after dropdown - componentArray.push(currentRow) - currentRow = new discord.ActionRowBuilder() - }else{ - //push button to current row - currentRow.addComponents(c.component) - } - - //create new row when 5 rows in length - if (currentRow.components.length == 5){ - componentArray.push(currentRow) - currentRow = new discord.ActionRowBuilder() - } - }) - //push final row to array - if (currentRow.components.length > 0) componentArray.push(currentRow) - - const filteredEmbeds = (this.data.embeds?.map((e) => e.embed).filter((e) => e instanceof discord.EmbedBuilder) as discord.EmbedBuilder[]) ?? [] - const filteredFiles = (this.data.files?.map((f) => f.file).filter((f) => f instanceof discord.AttachmentBuilder) as discord.AttachmentBuilder[]) ?? [] - - const message : discord.MessageCreateOptions = { - content:this.data.content ?? "", - poll:this.data.poll ?? undefined, - embeds:filteredEmbeds, - components:componentArray, - files:filteredFiles - } - - let result = {id:this.id,message,ephemeral:this.data.ephemeral ?? false} - - Object.assign(result.message,this.data.additionalOptions) - return result - } -} - -/**## ODModalManager `class` - * This is an Open Ticket modal manager. - * - * It contains all Open Ticket modal builders. Here, you can add your own modals or edit existing ones! - * - * It's recommended to use this system in combination with all the other Open Ticket builders! - */ -export class ODModalManager extends ODManagerWithSafety> { - constructor(debug:ODDebugger){ - super(() => { - return new ODModal("opendiscord:unknown-modal",(instance,params,source,cancel) => { - instance.setCustomId("od:unknown-modal") - instance.setTitle("❌ ") - instance.setQuestions( - { - style:"short", - customId:"error", - label:"error", - placeholder:"Contact the bot creator for more info!" - } - ) - cancel() - }) - },debug,"modal") - } -} - -/**## ODModalDataQuestion `interface` - * This interface contains the data to build a modal question. - */ -export interface ODModalDataQuestion { - /**The style of this modal question */ - style:"short"|"paragraph", - /**The custom id of this modal question */ - customId:string - /**The label of this modal question */ - label?:string, - /**The min length of this modal question */ - minLength?:number, - /**The max length of this modal question */ - maxLength?:number, - /**Is this modal question required? */ - required?:boolean, - /**The placeholder of this modal question */ - placeholder?:string, - /**The initial value of this modal question */ - value?:string -} - -/**## ODModalData `interface` - * This interface contains the data to build a modal. - */ -export interface ODModalData { - /**The custom id of this modal */ - customId:string, - /**The title of this modal */ - title:string|null, - /**The collection of questions in this modal */ - questions:ODModalDataQuestion[], -} - -/**## ODModalBuildResult `interface` - * This interface contains the result from a built modal (form). This can be used in the `ODMessage` builder! - */ -export interface ODModalBuildResult { - /**The id of this modal */ - id:ODId, - /**The discord modal */ - modal:discord.ModalBuilder -} - -/**## ODModalInstance `class` - * This is an Open Ticket modal instance. - * - * It contains all properties & functions to build a modal! - */ -export class ODModalInstance { - /**The current data of this modal */ - data: ODModalData = { - customId:"", - title:null, - questions:[] - } - - /**Set the custom id of this modal */ - setCustomId(customId:ODModalData["customId"]){ - this.data.customId = customId - return this - } - /**Set the title of this modal */ - setTitle(title:ODModalData["title"]){ - this.data.title = title - return this - } - /**Set the questions of this modal */ - setQuestions(...questions:ODModalData["questions"]){ - this.data.questions = questions - return this - } - /**Add a question to this modal! */ - addQuestion(question:ODModalDataQuestion){ - this.data.questions.push(question) - return this - } - /**Remove a question from this modal */ - removeQuestion(customId:string){ - const index = this.data.questions.findIndex((question) => question.customId === customId) - if (index > -1) this.data.questions.splice(index,1) - return this - } - /**Get a question from this modal */ - getQuestion(customId:string){ - const question = this.data.questions.find((question) => question.customId === customId) - if (question) return question - else return null - } -} - -/**## ODModal `class` - * This is an Open Ticket modal builder. - * - * With this class, you can create a modal to use as response in interactions. - * The only difference with normal modals is that this one can be edited by Open Ticket plugins! - * - * This is possible by using "workers" or multiple functions that will be executed in priority order! - */ -export class ODModal extends ODBuilderImplementation { - /**Build this modal & compile it for discord.js */ - async build(source:Source, params:Params){ - if (this.didCache && this.cache && this.allowCache) return this.cache - - //create instance - const instance = new ODModalInstance() - - //wait for workers to finish - await this.workers.executeWorkers(instance,source,params) - - //create the discord.js modal - const modal = new discord.ModalBuilder() - modal.setCustomId(instance.data.customId) - if (instance.data.title) modal.setTitle(instance.data.title) - else modal.setTitle(instance.data.customId) - - instance.data.questions.forEach((question) => { - const input = new discord.TextInputBuilder() - .setStyle(question.style == "paragraph" ? discord.TextInputStyle.Paragraph : discord.TextInputStyle.Short) - .setCustomId(question.customId) - .setLabel(question.label ? question.label : question.customId) - .setRequired(question.required ? true : false) - - if (question.minLength) input.setMinLength(question.minLength) - if (question.maxLength) input.setMaxLength(question.maxLength) - if (question.value) input.setValue(question.value) - if (question.placeholder) input.setPlaceholder(question.placeholder) - - modal.addComponents( - new discord.ActionRowBuilder() - .addComponents(input) - ) - }) - - this.cache = {id:this.id,modal} - this.didCache = true - return {id:this.id,modal} - } -} \ No newline at end of file diff --git a/src/core/api/modules/checker.ts b/src/core/api/modules/checker.ts deleted file mode 100644 index ceb2e84..0000000 --- a/src/core/api/modules/checker.ts +++ /dev/null @@ -1,1549 +0,0 @@ -/////////////////////////////////////// -//CONFIG CHECKER MODULE -/////////////////////////////////////// -import { ODDiscordIdType, ODId, ODManager, ODManagerData, ODValidId, ODValidJsonType } from "./base" -import { ODConfig } from "./config" -import { ODLanguageManager } from "./language" -import { ODDebugger } from "./console" - -/**## ODCheckerResult `interface` - * This interface is the result from a config checker check() function. - */ -export interface ODCheckerResult { - valid:boolean - messages:ODCheckerMessage[] -} - -/**## ODCheckerManager `class` - * This is an Open Ticket checker manager. - * - * It manages all config checkers in the bot and allows plugins to access config checkers from Open Ticket & other plugins! - * - * You can use this class to get/add a config checker (`ODChecker`) in your plugin! - */ -export class ODCheckerManager extends ODManager { - /**The global temporary storage shared between all config checkers. */ - storage: ODCheckerStorage - /**The class responsible for rendering the config checker report. */ - renderer: ODCheckerRenderer - /**The class responsible for translating the config checker report. */ - translation: ODCheckerTranslationRegister - /**Final functions are global functions executed just before the report is created. */ - functions: ODCheckerFunctionManager - /**A variable containing the last result returned from `checkAll()` */ - lastResult: ODCheckerResult|null = null - - constructor(debug:ODDebugger, storage:ODCheckerStorage, renderer:ODCheckerRenderer, translation:ODCheckerTranslationRegister, functions:ODCheckerFunctionManager){ - super(debug,"config checker") - this.storage = storage - this.renderer = renderer - this.translation = translation - this.functions = functions - } - /**Check all config checkers registered in this manager.*/ - checkAll(sort:boolean): ODCheckerResult { - this.storage.reset() - - let isValid = true - const final: ODCheckerMessage[] = [] - - const checkers = this.getAll() - checkers.sort((a,b) => b.priority-a.priority) - - checkers.forEach((checker) => { - const res = checker.check() - final.push(...res.messages) - - if (!res.valid) isValid = false - }) - - this.functions.getAll().forEach((func) => { - const res = func.func(this,this.functions) - final.push(...res.messages) - - if (!res.valid) isValid = false - }) - - //sort messages => (info, warning, error) - if (sort) final.sort((a,b) => { - const typeA = (a.type == "error") ? 2 : (a.type == "warning") ? 1 : 0 - const typeB = (b.type == "error") ? 2 : (b.type == "warning") ? 1 : 0 - - return typeA-typeB - }) - - this.lastResult = { - valid:isValid, - messages:final - } - - return { - valid:isValid, - messages:final - } - } - /**Create temporary and unlisted `ODConfig`, `ODChecker` & `ODCheckerStorage` classes. This will help you use a `ODCheckerStructure` validator without officially registering it in `opendiscord.checkers`. */ - createTemporaryCheckerEnvironment(){ - return new ODChecker("opendiscord:temporary-environment",new ODCheckerStorage(),0,new ODConfig("opendiscord:temporary-environment",{}),new ODCheckerStructure("opendiscord:temporary-environment",{})) - } -} - -/**## ODCheckerStorage `class` - * This is an Open Ticket checker storage. - * - * It stores temporary data to share between config checkers! - * (e.g. The `messages.json` needs to access the `"id"` from `options.json`) - * - * - * You can use this class when you create your own config checker implementation! (not required for using the built-in config checker) - */ -export class ODCheckerStorage { - /**This is the array that stores all the data. ❌ **(don't edit unless really needed!)***/ - storage: {source:ODId, key:string, value:any}[] = [] - - /**Get data from the database (`source` => id of `ODChecker`) */ - get(source:ODValidId, key:string): any|null { - const result = this.storage.find(d => (d.source.value == new ODId(source).value) && (d.key == key)) - return (result) ? result.value : null - } - /**Add data to the database (`source` => id of `ODChecker`). This function also overwrites existing data!*/ - set(source:ODValidId, key:string, value:any){ - const index = this.storage.findIndex(d => (d.source.value == new ODId(source).value) && (d.key == key)) - if (index > -1){ - //overwrite - this.storage[index] = { - source:new ODId(source), - key,value - } - return true - }else{ - this.storage.push({ - source:new ODId(source), - key,value - }) - return false - } - } - /**Delete data from the database (`source` => id of `ODChecker`) */ - delete(source:ODValidId, key:string){ - const index = this.storage.findIndex(d => (d.source.value == new ODId(source).value) && (d.key == key)) - if (index > -1){ - //delete - this.storage.splice(index,1) - return true - }else return false - } - - /**Reset the entire database */ - reset(){ - this.storage = [] - } -} - -/**## ODCheckerRenderer `class` - * This is an Open Ticket checker renderer. - * - * It's responsible for rendering the config checker result in the console. - * This class doesn't provide any components! You need to create them by extending this class - * - * You can use this class if you want to change how the config checker looks! - */ -export class ODCheckerRenderer { - /**Get all components */ - getComponents(compact:boolean, renderEmpty:boolean, translation:ODCheckerTranslationRegister, data:ODCheckerResult): string[] { - return [] - } - /**Render all components */ - render(components:string[]){ - if (components.length < 1) return - console.log("\n") - components.forEach((c) => { - console.log(c) - }) - console.log("\n") - } -} - -/**## ODCheckerTranslationRegister `class` - * This is an Open Ticket checker translation register. - * - * It's used to store & manage the translation for each message from the config checker! - * Most translations are stored by message id, but there are some exceptions like the additional text on the checker report. - * - * You can use this class if you want to translate your config checker messages! **This is optional & isn't required for the checker to work!** - */ -export class ODCheckerTranslationRegister { - /**This is the array that stores all the data. ❌ **(don't edit unless really needed!)***/ - #translations: {type:"message"|"other", id:string, translation:string}[] = [] - - /**Get the translation from a config checker message/sentence */ - get(type:"message"|"other", id:string): string|null { - const result = this.#translations.find(d => (d.id == id) && (d.type == type)) - return (result) ? result.translation : null - } - /**Set the translation for a config checker message/sentence. This function also overwrites existing translations!*/ - set(type:"message"|"other", id:string, translation:string){ - const index = this.#translations.findIndex(d => (d.id == id) && (d.type == type)) - if (index > -1){ - //overwrite - this.#translations[index] = {type,id,translation} - return true - }else{ - this.#translations.push({type,id,translation}) - return false - } - } - /**Delete the translation for a config checker message/sentence. */ - delete(type:"message"|"other", id:string){ - const index = this.#translations.findIndex(d => (d.id == id) && (d.type == type)) - if (index > -1){ - //delete - this.#translations.splice(index,1) - return true - }else return false - } - - /**Get all translations */ - getAll(){ - return this.#translations - } - - /**Insert the translation params into the text. */ - insertTranslationParams(text:string, translationParams:string[]){ - translationParams.forEach((value,index) => { - text = text.replace(`{${index}}`,value) - }) - return text - } - /**A shortcut to copy translations from the `ODLanguageManager` to `ODCheckerTranslationRegister` */ - quickTranslate(manager:ODLanguageManager, translationId:string, type:"other"|"message", id:string){ - const translation = manager.getTranslation(translationId) - if (translation) this.set(type,id,translation) - } -} - -/**## ODCheckerFunctionCallback `type` - * This is the function used in the `ODCheckerFunction` class. - */ -export type ODCheckerFunctionCallback = (manager:ODCheckerManager, functions:ODCheckerFunctionManager) => ODCheckerResult - -/**## ODCheckerFunction `class` - * This is an Open Ticket config checker function. - * - * It is a global function that will be executed after all config checkers. It can do additional checks for invalid/missing configurations. - * It's mostly used for things that need to be checked globally! - */ -export class ODCheckerFunction extends ODManagerData { - /**The function which will be executed globally after all config checkers. */ - func: ODCheckerFunctionCallback - - constructor(id:ODValidId, func:ODCheckerFunctionCallback){ - super(id) - this.func = func - } -} - -/**## ODCheckerFunctionManager `class` - * This is an Open Ticket config checker function manager. - * - * It manages all `ODCheckerFunction`'s and it has some extra shortcuts for frequently used methods. - */ -export class ODCheckerFunctionManager extends ODManager { - constructor(debug:ODDebugger){ - super(debug,"config checker function") - } - - /**A shortcut to create a warning, info or error message */ - createMessage(checkerId:ODValidId, id:ODValidId, filepath:string, type:"info"|"warning"|"error", message:string, locationTrace:ODCheckerLocationTrace, docs:string|null, translationParams:string[], locationId:ODId, locationDocs:string|null): ODCheckerMessage { - return { - checkerId:new ODId(checkerId), - messageId:new ODId(id), - locationId, - - type,message, - path:this.locationTraceToString(locationTrace), - filepath, - translationParams, - - messageDocs:docs, - locationDocs - } - } - /**Create a string from the location trace (path)*/ - locationTraceToString(trace:ODCheckerLocationTrace){ - const final: ODCheckerLocationTrace = [] - trace.forEach((t) => { - if (typeof t == "number"){ - final.push(`:${t}`) - }else{ - final.push(`."${t}"`) - } - }) - return final.join("").substring(1) - } - /**De-reference the locationTrace array. Use this before adding a value to the array*/ - locationTraceDeref(trace:ODCheckerLocationTrace): ODCheckerLocationTrace { - return JSON.parse(JSON.stringify(trace)) - } -} - -/**## ODCheckerLocationTrace `type` - * This type is an array of strings & numbers which represents the location trace from the config checker. - * It's used to generate a path to the error (e.g. `"abc"."efg".1."something"`) - */ -export type ODCheckerLocationTrace = (string|number)[] - -/**## ODCheckerOptions `interface` - * This interface contains all optional properties to customise in the `ODChecker` class. - */ -export interface ODCheckerOptions { - /**The name of this config in the Interactive Setup CLI. */ - cliDisplayName?:string - /**The description of this config in the Interactive Setup CLI. */ - cliDisplayDescription?:string -} - -/**## ODChecker `class` - * This is an Open Ticket config checker. - * - * It checks a specific config file for invalid/missing configurations. This data can then be used to show to the user what's wrong! - * You can check for example if a string is longer/shorter than a certain amount of characters & more! - * - * You can use this class when you create your own custom config file & you want to check it for syntax errors. - */ -export class ODChecker extends ODManagerData { - /**The storage of this checker (reference for `ODCheckerManager.storage`) */ - storage: ODCheckerStorage - /**The higher the priority, the faster it gets checked! */ - priority: number - /**The config file that needs to be checked */ - config: ODConfig - /**The structure of the config file */ - structure: ODCheckerStructure - /**Temporary storage for all error messages from the check() method (not recommended to use) */ - messages: ODCheckerMessage[] = [] - /**Temporary storage for the quit status from the check() method (not recommended to use) */ - quit: boolean = false - /**All additional properties of this config checker. */ - options: ODCheckerOptions - - constructor(id:ODValidId, storage: ODCheckerStorage, priority:number, config:ODConfig, structure:ODCheckerStructure, options?:ODCheckerOptions){ - super(id) - this.storage = storage - this.priority = priority - this.config = config - this.structure = structure - this.options = options ?? {} - } - - /**Get a human-readable number string. */ - #ordinalNumber(num:number){ - const i = Math.abs(Math.round(num)) - const cent = i % 100 - if (cent >= 10 && cent <= 20) return i+'th' - const dec = i % 10 - if (dec === 1) return i+'st' - if (dec === 2) return i+'nd' - if (dec === 3) return i+'rd' - return i+'th' - } - /**Run this checker. Returns all errors*/ - check(): ODCheckerResult { - this.messages = [] - this.quit = false - - this.structure.check(this,this.config.data,[]) - return { - valid:!this.quit, - messages:this.messages - } - } - /**Create a string from the location trace/path in a human readable format. */ - locationTraceToString(trace:ODCheckerLocationTrace){ - const final: ODCheckerLocationTrace = [] - trace.forEach((t) => { - if (typeof t == "number"){ - final.push(`:(${this.#ordinalNumber(t+1)})`) - }else{ - final.push(`."${t}"`) - } - }) - return final.join("").substring(1) - } - /**De-reference the locationTrace array. Use this before adding a value to the array*/ - locationTraceDeref(trace:ODCheckerLocationTrace): ODCheckerLocationTrace { - return JSON.parse(JSON.stringify(trace)) - } - - /**A shortcut to create a warning, info or error message */ - createMessage(id:ODValidId, type:"info"|"warning"|"error", message:string, locationTrace:ODCheckerLocationTrace, docs:string|null, translationParams:string[], locationId:ODId, locationDocs:string|null){ - if (type == "error") this.quit = true - this.messages.push({ - checkerId:this.id, - messageId:new ODId(id), - locationId, - - type,message, - path:this.locationTraceToString(locationTrace), - filepath:this.config.path, - translationParams, - - messageDocs:docs, - locationDocs - }) - } -} - -/**## ODCheckerMessage `interface` - * This interface is an object which has all variables required for a config checker message! - */ -export interface ODCheckerMessage { - checkerId:ODId, - messageId:ODId, - locationId:ODId, - - type:"info"|"warning"|"error", - message:string, - path:string, - filepath:string, - translationParams:string[], - - messageDocs:string|null, - locationDocs:string|null -} - -/**## ODCheckerStructureOptions `interface` - * This interface has the basic options for the `ODCheckerStructure`! - */ -export interface ODCheckerStructureOptions { - /**Add a custom checker function. Returns `true` when valid. */ - custom?:(checker:ODChecker, value:ODValidJsonType, locationTrace:ODCheckerLocationTrace, locationId:ODId, locationDocs:string|null) => boolean, - /**Set the url to the documentation of this variable. */ - docs?:string, - /**The name of this config in the Interactive Setup CLI. */ - cliDisplayName?:string - /**The description of this config in the Interactive Setup CLI. */ - cliDisplayDescription?:string - /**Hide the description of this config in the Interactive Setup CLI parent view/list. */ - cliHideDescriptionInParent?:boolean - /**The default value of this variable when creating it in the Interactive Setup CLI. When not specified, the user will be asked to insert a value. */ - cliInitDefaultValue?:ODValidJsonType -} - -/**## ODCheckerStructure `class` - * This is an Open Ticket config checker structure. - * - * This class will check for a single variable in a config file, customise it in the settings! - * If you want prebuilt checkers (for strings, booleans, numbers, ...), check the other `ODCheckerStructure`'s! - * - * **Not recommended to use!** It's recommended to extend from another `ODConfigCheckerStructure` class! - */ -export class ODCheckerStructure { - /**The id of this checker structure */ - id: ODId - /**The options for this checker structure */ - options: ODCheckerStructureOptions - - constructor(id:ODValidId, options:ODCheckerStructureOptions){ - this.id = new ODId(id) - this.options = options - } - - /**Check a variable if it matches all settings in this checker. This function is automatically executed by Open Ticket! */ - check(checker:ODChecker, value:ODValidJsonType, locationTrace:ODCheckerLocationTrace): boolean { - if (typeof this.options.custom != "undefined"){ - return this.options.custom(checker,value,locationTrace,this.id,(this.options.docs ?? null)) - }else return true - } -} - -/**## ODCheckerObjectStructureOptions `interface` - * This interface has the options for `ODCheckerObjectStructure`! - */ -export interface ODCheckerObjectStructureOptions extends ODCheckerStructureOptions { - /**Add a checker for a property in an object (can also be optional) */ - children:{key:string, priority?:number, optional?:boolean, cliHideInEditMode?:boolean, checker:ODCheckerStructure}[], - /**A list of keys to skip when creating this object with the Interactive Setup CLI. The default value of these properties will be used instead. */ - cliInitSkipKeys?:string[], - /**The key of a (primitive) property in this object to show the value of in the Interactive Setup CLI when listed in an array. */ - cliDisplayKeyInParentArray?:string, - /**A list of additional (primitive) property keys in this object to show the value of in the Interactive Setup CLI when listed in an array. */ - cliDisplayAdditionalKeysInParentArray?:string[] -} - -/**## ODCheckerObjectStructure `class` - * This is an Open Ticket config checker structure. - * - * This class will check for an object variable in a config file, customise it in the settings! - * A checker for the children can be set in the settings. - */ -export class ODCheckerObjectStructure extends ODCheckerStructure { - declare options: ODCheckerObjectStructureOptions - - constructor(id:ODValidId, options:ODCheckerObjectStructureOptions){ - super(id,options) - } - - check(checker:ODChecker, value:object, locationTrace:ODCheckerLocationTrace): boolean { - const lt = checker.locationTraceDeref(locationTrace) - - //check type & options - if (typeof value != "object"){ - checker.createMessage("opendiscord:invalid-type","error","This property needs to be the type: object!",lt,null,["object"],this.id,(this.options.docs ?? null)) - return false - } - - //sort children - if (typeof this.options.children == "undefined") return super.check(checker,value,locationTrace) - const sortedChildren = this.options.children.sort((a,b) => { - if ((a.priority ?? 0) < (b.priority ?? 0)) return -1 - else if ((a.priority ?? 0) > (b.priority ?? 0)) return 1 - else return 0 - }) - - //check children - let localQuit = false - sortedChildren.forEach((child) => { - const localLt = checker.locationTraceDeref(lt) - localLt.push(child.key) - - if (typeof value[child.key] == "undefined"){ - if (!child.optional){ - localQuit = true - checker.createMessage("opendiscord:property-missing","error",`The property "${child.key}" is mising from this object!`,lt,null,[`"${child.key}"`],this.id,(this.options.docs ?? null)) - }else{ - checker.createMessage("opendiscord:property-optional","info",`The property "${child.key}" is optional in this object!`,lt,null,[`"${child.key}"`],this.id,(this.options.docs ?? null)) - } - }else if (!child.checker.check(checker,value[child.key],localLt)) localQuit = true - }) - - //do local quit or check custom function - if (localQuit) return false - else return super.check(checker,value,locationTrace) - } -} - -/**## ODCheckerStringStructureOptions `interface` - * This interface has the options for `ODCheckerStringStructure`! - */ -export interface ODCheckerStringStructureOptions extends ODCheckerStructureOptions { - /**The minimum length of this string */ - minLength?:number, - /**The maximum length of this string */ - maxLength?:number, - /**Set the required length of this string */ - length?:number, - /**This string needs to start with ... */ - startsWith?:string, - /**This string needs to end with ... */ - endsWith?:string, - /**This string needs to contain ... */ - contains?:string, - /**This string is not allowed to contain ... */ - invertedContains?:string, - /**You need to choose between ... */ - choices?:string[], - /**This string needs to be in lowercase. */ - lowercaseOnly?:boolean, - /**This string needs to be in uppercase. */ - uppercaseOnly?:boolean, - /**This string shouldn't contain any special characters (allowed: A-Z, a-z, 0-9, space, a few punctuation marks, ...). */ - noSpecialCharacters?:boolean, - /**Do not allow any spaces in this string. */ - withoutSpaces?:boolean, - /**Give a warning when a sentence doesn't start with a capital letter. Or require every word to start with a capital letter. (Ignores numbers, unicode characters, ...) */ - capitalLetterWarning?:false|"sentence"|"word" - /**Give a warning when a sentence doesn't end with a punctuation letter (.,?!) */ - punctuationWarning?:boolean - /**The string needs to match this regex */ - regex?:RegExp, - /**Provide an optional list for autocomplete when using the Interactive Setup CLI. Defaults to the `choices` option. */ - cliAutocompleteList?:string[], - /**Dynamically provide a list for autocomplete items when using the Interactive Setup CLI. */ - cliAutocompleteFunc?:() => Promise -} - -/**## ODCheckerStringStructure `class` - * This is an Open Ticket config checker structure. - * - * This class will check for a string variable in a config file, customise it in the settings! - */ -export class ODCheckerStringStructure extends ODCheckerStructure { - declare options: ODCheckerStringStructureOptions - - constructor(id:ODValidId, options:ODCheckerStringStructureOptions){ - super(id,options) - } - - check(checker:ODChecker, value:string, locationTrace:ODCheckerLocationTrace): boolean { - const lt = checker.locationTraceDeref(locationTrace) - - //check type & options - if (typeof value != "string"){ - checker.createMessage("opendiscord:invalid-type","error","This property needs to be the type: string!",lt,null,["string"],this.id,(this.options.docs ?? null)) - return false - }else if (typeof this.options.minLength != "undefined" && value.length < this.options.minLength){ - checker.createMessage("opendiscord:string-too-short","error",`This string can't be shorter than ${this.options.minLength} characters!`,lt,null,[this.options.minLength.toString()],this.id,(this.options.docs ?? null)) - return false - }else if (typeof this.options.maxLength != "undefined" && value.length > this.options.maxLength){ - checker.createMessage("opendiscord:string-too-long","error",`This string can't be longer than ${this.options.maxLength} characters!`,lt,null,[this.options.maxLength.toString()],this.id,(this.options.docs ?? null)) - return false - }else if (typeof this.options.length != "undefined" && value.length !== this.options.length){ - checker.createMessage("opendiscord:string-length-invalid","error",`This string needs to be ${this.options.length} characters long!`,lt,null,[this.options.length.toString()],this.id,(this.options.docs ?? null)) - return false - }else if (typeof this.options.startsWith != "undefined" && !value.startsWith(this.options.startsWith)){ - checker.createMessage("opendiscord:string-starts-with","error",`This string needs to start with "${this.options.startsWith}"!`,lt,null,[`"${this.options.startsWith}"`],this.id,(this.options.docs ?? null)) - return false - }else if (typeof this.options.endsWith != "undefined" && !value.endsWith(this.options.endsWith)){ - checker.createMessage("opendiscord:string-ends-with","error",`This string needs to end with "${this.options.endsWith}"!`,lt,null,[`"${this.options.endsWith}"`],this.id,(this.options.docs ?? null)) - return false - }else if (typeof this.options.contains != "undefined" && !value.includes(this.options.contains)){ - checker.createMessage("opendiscord:string-contains","error",`This string needs to contain "${this.options.contains}"!`,lt,null,[`"${this.options.contains}"`],this.id,(this.options.docs ?? null)) - return false - }else if (typeof this.options.invertedContains != "undefined" && value.includes(this.options.invertedContains)){ - checker.createMessage("opendiscord:string-inverted-contains","error",`This string is not allowed to contain "${this.options.invertedContains}"!`,lt,null,[`"${this.options.invertedContains}"`],this.id,(this.options.docs ?? null)) - return false - }else if (typeof this.options.choices != "undefined" && !this.options.choices.includes(value)){ - checker.createMessage("opendiscord:string-choices","error",`This string can only be one of the following values: "${this.options.choices.join(`", "`)}"!`,lt,null,[`"${this.options.choices.join(`", "`)}"`],this.id,(this.options.docs ?? null)) - return false - }else if (this.options.lowercaseOnly && value !== value.toLowerCase()){ - checker.createMessage("opendiscord:string-lowercase","error",`This string must be written in lowercase only!`,lt,null,[],this.id,(this.options.docs ?? null)) - return false - }else if (this.options.uppercaseOnly && value !== value.toUpperCase()){ - checker.createMessage("opendiscord:string-uppercase","error",`This string must be written in uppercase only!`,lt,null,[],this.id,(this.options.docs ?? null)) - return false - }else if (this.options.noSpecialCharacters && !/^[A-Za-z0-9 ]*$/.test(value)){ - checker.createMessage("opendiscord:string-special-characters","error",`This string is not allowed to contain any special characters! (a-z, 0-9 & space only)`,lt,null,[],this.id,(this.options.docs ?? null)) - return false - }else if (this.options.withoutSpaces && value.includes(" ")){ - checker.createMessage("opendiscord:string-no-spaces","error",`This string is not allowed to contain spaces!`,lt,null,[],this.id,(this.options.docs ?? null)) - return false - }else if (typeof this.options.regex != "undefined" && !this.options.regex.test(value)){ - checker.createMessage("opendiscord:string-regex","error","This string is invalid!",lt,null,[],this.id,(this.options.docs ?? null)) - return false - }else{ - //warnings - if ((this.options.capitalLetterWarning == "word" && !value.split(" ").every((word) => word.length == 0 || /^[^a-z].*/.test(word)))) checker.createMessage("opendiscord:string-capital-word","warning",`It's recommended that each word in this string starts with a capital letter!`,lt,null,[],this.id,(this.options.docs ?? null)) - if ((this.options.capitalLetterWarning == "sentence" && !value.split(/ *[.?!] */).every((sentence) => sentence.length == 0 || /^[^a-z].*/.test(sentence)))) checker.createMessage("opendiscord:string-capital-sentence","warning",`It looks like some sentences in this string don't start with a capital letter!`,lt,null,[],this.id,(this.options.docs ?? null)) - if (this.options.punctuationWarning && value.length > 0 && (!value.endsWith(".") && !value.endsWith("?") && !value.endsWith("!") && !value.endsWith("'") && !value.endsWith('"') && !value.endsWith(",") && !value.endsWith(";") && !value.endsWith(":") && !value.endsWith("="))) checker.createMessage("opendiscord:string-punctuation","warning",`It looks like the sentence in this string doesn't end with a punctuation mark!`,lt,null,[],this.id,(this.options.docs ?? null)) - - return super.check(checker,value,locationTrace) - } - } -} - -/**## ODCheckerNumberStructureOptions `interface` - * This interface has the options for `ODCheckerNumberStructure`! - */ -export interface ODCheckerNumberStructureOptions extends ODCheckerStructureOptions { - /**Is `NaN` (not a number) allowed? (`false` by default) */ - nanAllowed?:boolean - /**The minimum length of this number */ - minLength?:number, - /**The maximum length of this number */ - maxLength?:number, - /**Set the required length of this number */ - length?:number, - /**The minimum value of this number */ - min?:number, - /**The maximum value of this number */ - max?:number, - /**This number is required to match the value */ - is?:number, - /**Only allow a multiple of ... starting at `this.offset` or 0 */ - step?:number, - /**The offset for the step function. */ - offset?:number, - /**This number needs to start with ... */ - startsWith?:string, - /**This number needs to end with ... */ - endsWith?:string, - /**This number needs to contain ... */ - contains?:string, - /**This number is not allowed to contain ... */ - invertedContains?:string, - /**You need to choose between ... */ - choices?:number[], - /**Are numbers with a decimal value allowed? */ - floatAllowed?:boolean, - /**Are negative numbers allowed (without zero) */ - negativeAllowed?:boolean, - /**Are positive numers allowed (without zero) */ - positiveAllowed?:boolean, - /**Is zero allowed? */ - zeroAllowed?:boolean -} - -/**## ODCheckerNumberStructure `class` - * This is an Open Ticket config checker structure. - * - * This class will check for a number variable in a config file, customise it in the settings! - */ -export class ODCheckerNumberStructure extends ODCheckerStructure { - declare options: ODCheckerNumberStructureOptions - - constructor(id:ODValidId, options:ODCheckerNumberStructureOptions){ - super(id,options) - } - - check(checker:ODChecker, value:number, locationTrace:ODCheckerLocationTrace): boolean { - const lt = checker.locationTraceDeref(locationTrace) - - //offset for step - const stepOffset = (typeof this.options.offset != "undefined") ? this.options.offset : 0 - - //check type & options - if (typeof value != "number"){ - checker.createMessage("opendiscord:invalid-type","error","This property needs to be the type: number!",lt,null,["number"],this.id,(this.options.docs ?? null)) - return false - }else if (!this.options.nanAllowed && isNaN(value)){ - checker.createMessage("opendiscord:number-nan","error",`This number can't be NaN (Not A Number)!`,lt,null,[],this.id,(this.options.docs ?? null)) - return false - }else if (typeof this.options.minLength != "undefined" && value.toString().length < this.options.minLength){ - checker.createMessage("opendiscord:number-too-short","error",`This number can't be shorter than ${this.options.minLength} characters!`,lt,null,[this.options.minLength.toString()],this.id,(this.options.docs ?? null)) - return false - }else if (typeof this.options.maxLength != "undefined" && value.toString().length > this.options.maxLength){ - checker.createMessage("opendiscord:number-too-long","error",`This number can't be longer than ${this.options.maxLength} characters!`,lt,null,[this.options.maxLength.toString()],this.id,(this.options.docs ?? null)) - return false - }else if (typeof this.options.length != "undefined" && value.toString().length !== this.options.length){ - checker.createMessage("opendiscord:number-length-invalid","error",`This number needs to be ${this.options.length} characters long!`,lt,null,[this.options.length.toString()],this.id,(this.options.docs ?? null)) - return false - }else if (typeof this.options.min != "undefined" && value < this.options.min){ - checker.createMessage("opendiscord:number-too-small","error",`This number needs to be at least ${this.options.min}!`,lt,null,[this.options.min.toString()],this.id,(this.options.docs ?? null)) - return false - }else if (typeof this.options.max != "undefined" && value > this.options.max){ - checker.createMessage("opendiscord:number-too-large","error",`This number needs to be at most ${this.options.max}!`,lt,null,[this.options.max.toString()],this.id,(this.options.docs ?? null)) - return false - }else if (typeof this.options.is != "undefined" && value == this.options.is){ - checker.createMessage("opendiscord:number-not-equal","error",`This number needs to be ${this.options.is}!`,lt,null,[this.options.is.toString()],this.id,(this.options.docs ?? null)) - return false - }else if (typeof this.options.step != "undefined" && ((value - stepOffset) % this.options.step) !== 0){ - if (stepOffset > 0) checker.createMessage("opendiscord:number-step-offset","error",`This number needs to be a multiple of ${this.options.step} starting with ${stepOffset}!`,lt,null,[this.options.step.toString(),stepOffset.toString()],this.id,(this.options.docs ?? null)) - else checker.createMessage("opendiscord:number-step","error",`This number needs to be a multiple of ${this.options.step}!`,lt,null,[this.options.step.toString()],this.id,(this.options.docs ?? null)) - return false - }else if (typeof this.options.startsWith != "undefined" && !value.toString().startsWith(this.options.startsWith)){ - checker.createMessage("opendiscord:number-starts-with","error",`This number needs to start with "${this.options.startsWith}"!`,lt,null,[`"${this.options.startsWith}"`],this.id,(this.options.docs ?? null)) - return false - }else if (typeof this.options.endsWith != "undefined" && !value.toString().endsWith(this.options.endsWith)){ - checker.createMessage("opendiscord:number-ends-with","error",`This number needs to end with "${this.options.endsWith}"!`,lt,null,[`"${this.options.endsWith}"`],this.id,(this.options.docs ?? null)) - return false - }else if (typeof this.options.contains != "undefined" && !value.toString().includes(this.options.contains)){ - checker.createMessage("opendiscord:number-contains","error",`This number needs to contain "${this.options.contains}"!`,lt,null,[`"${this.options.contains}"`],this.id,(this.options.docs ?? null)) - return false - }else if (typeof this.options.invertedContains != "undefined" && value.toString().includes(this.options.invertedContains)){ - checker.createMessage("opendiscord:number-inverted-contains","error",`This number is not allowed to contain "${this.options.invertedContains}"!`,lt,null,[`"${this.options.invertedContains}"`],this.id,(this.options.docs ?? null)) - return false - }else if (typeof this.options.choices != "undefined" && !this.options.choices.includes(value)){ - checker.createMessage("opendiscord:number-choices","error",`This number can only be one of the following values: "${this.options.choices.join(`", "`)}"!`,lt,null,[`"${this.options.choices.join(`", "`)}"`],this.id,(this.options.docs ?? null)) - return false - }else if (typeof this.options.floatAllowed != "undefined" && !this.options.floatAllowed && (value % 1) !== 0){ - checker.createMessage("opendiscord:number-float","error","This number can't be a decimal!",lt,null,[],this.id,(this.options.docs ?? null)) - return false - }else if (typeof this.options.negativeAllowed != "undefined" && !this.options.negativeAllowed && value < 0){ - checker.createMessage("opendiscord:number-negative","error","This number can't be negative!",lt,null,[],this.id,(this.options.docs ?? null)) - return false - }else if (typeof this.options.positiveAllowed != "undefined" && !this.options.positiveAllowed && value > 0){ - checker.createMessage("opendiscord:number-positive","error","This number can't be positive!",lt,null,[],this.id,(this.options.docs ?? null)) - return false - }else if (typeof this.options.zeroAllowed != "undefined" && !this.options.zeroAllowed && value === 0){ - checker.createMessage("opendiscord:number-zero","error","This number can't be zero!",lt,null,[],this.id,(this.options.docs ?? null)) - return false - }else return super.check(checker,value,locationTrace) - } -} - -/**## ODCheckerBooleanStructureOptions `interface` - * This interface has the options for `ODCheckerBooleanStructure`! - */ -export interface ODCheckerBooleanStructureOptions extends ODCheckerStructureOptions { - /**Is `true` allowed? */ - trueAllowed?:boolean, - /**Is `false` allowed? */ - falseAllowed?:boolean -} - -/**## ODCheckerBooleanStructure `class` - * This is an Open Ticket config checker structure. - * - * This class will check for a boolean variable in a config file, customise it in the settings! - */ -export class ODCheckerBooleanStructure extends ODCheckerStructure { - declare options: ODCheckerBooleanStructureOptions - - constructor(id:ODValidId, options:ODCheckerBooleanStructureOptions){ - super(id,options) - } - - check(checker:ODChecker, value:boolean, locationTrace:ODCheckerLocationTrace): boolean { - const lt = checker.locationTraceDeref(locationTrace) - - //check type & options - if (typeof value != "boolean"){ - checker.createMessage("opendiscord:invalid-type","error","This property needs to be the type: boolean!",lt,null,["boolean"],this.id,(this.options.docs ?? null)) - return false - }else if (typeof this.options.trueAllowed != "undefined" && !this.options.trueAllowed && value == true){ - checker.createMessage("opendiscord:boolean-true","error","This boolean can't be true!",lt,null,[],this.id,(this.options.docs ?? null)) - return false - }else if (typeof this.options.falseAllowed != "undefined" && !this.options.falseAllowed && value == false){ - checker.createMessage("opendiscord:boolean-false","error","This boolean can't be false!",lt,null,[],this.id,(this.options.docs ?? null)) - return false - }else return super.check(checker,value,locationTrace) - } -} - -/**## ODCheckerArrayStructureOptions `interface` - * This interface has the options for `ODCheckerArrayStructure`! - */ -export interface ODCheckerArrayStructureOptions extends ODCheckerStructureOptions { - /**The checker for all the properties in this array */ - propertyChecker?:ODCheckerStructure, - /**Don't allow this array to be empty */ - disableEmpty?:boolean, - /**This array is required to be empty */ - emptyRequired?:boolean, - /**The minimum length of this array */ - minLength?:number, - /**The maximum length of this array */ - maxLength?:number, - /**The length of the array needs to be the same as this value */ - length?:number, - /**Allow double values (only for `string`, `number` & `boolean`) */ - allowDoubles?:boolean - /**Only allow these types in the array (for multi-type propertyCheckers) */ - allowedTypes?:("string"|"number"|"boolean"|"null"|"array"|"object"|"other")[], - /**The name of the properties inside this array. Used in the GUI of the Interactive Setup CLI. */ - cliDisplayPropertyName?:string -} - -/**## ODCheckerArrayStructure `class` - * This is an Open Ticket config checker structure. - * - * This class will check for an array variable in a config file, customise it in the settings! - */ -export class ODCheckerArrayStructure extends ODCheckerStructure { - declare options: ODCheckerArrayStructureOptions - - constructor(id:ODValidId, options:ODCheckerArrayStructureOptions){ - super(id,options) - } - - check(checker:ODChecker, value:Array, locationTrace:ODCheckerLocationTrace): boolean { - const lt = checker.locationTraceDeref(locationTrace) - - if (!Array.isArray(value)){ - checker.createMessage("opendiscord:invalid-type","error","This property needs to be the type: array!",lt,null,["array"],this.id,(this.options.docs ?? null)) - return false - }else if (typeof this.options.disableEmpty != "undefined" && this.options.disableEmpty && value.length == 0){ - checker.createMessage("opendiscord:array-empty-disabled","error","This array isn't allowed to be empty!",lt,null,[],this.id,(this.options.docs ?? null)) - return false - }else if (typeof this.options.emptyRequired != "undefined" && this.options.emptyRequired && value.length != 0){ - checker.createMessage("opendiscord:array-empty-required","error","This array is required to be empty!",lt,null,[],this.id,(this.options.docs ?? null)) - return false - }else if (typeof this.options.minLength != "undefined" && value.length < this.options.minLength){ - checker.createMessage("opendiscord:array-too-short","error",`This array needs to have a length of at least ${this.options.minLength}!`,lt,null,[this.options.minLength.toString()],this.id,(this.options.docs ?? null)) - return false - }else if (typeof this.options.maxLength != "undefined" && value.length > this.options.maxLength){ - checker.createMessage("opendiscord:array-too-long","error",`This array needs to have a length of at most ${this.options.maxLength}!`,lt,null,[this.options.maxLength.toString()],this.id,(this.options.docs ?? null)) - return false - }else if (typeof this.options.length != "undefined" && value.length == this.options.length){ - checker.createMessage("opendiscord:array-length-invalid","error",`This array needs to have a length of ${this.options.length}!`,lt,null,[this.options.length.toString()],this.id,(this.options.docs ?? null)) - return false - }else if (typeof this.options.allowedTypes != "undefined" && !this.#arrayAllowedTypesCheck(value,this.options.allowedTypes)){ - checker.createMessage("opendiscord:array-invalid-types","error",`This array can only contain the following types: ${this.options.allowedTypes.join(", ")}!`,lt,null,[this.options.allowedTypes.join(", ").toString()],this.id,(this.options.docs ?? null)) - return false - }else if (typeof this.options.allowDoubles != "undefined" && !this.options.allowDoubles && this.#arrayHasDoubles(value)){ - checker.createMessage("opendiscord:array-double","error","This array doesn't allow the same value twice!",lt,null,[],this.id,(this.options.docs ?? null)) - return false - }else{ - //check all properties - let localQuit = false - if (this.options.propertyChecker) value.forEach((property,index) => { - if (!this.options.propertyChecker) return - - const localLt = checker.locationTraceDeref(lt) - localLt.push(index) - - if (!this.options.propertyChecker.check(checker,property,localLt)) localQuit = true - }) - - //return false if invalid properties - if (localQuit){ - checker.quit = true - return false - }else return super.check(checker,value,locationTrace) - } - } - - /**Check this array for the allowed types */ - #arrayAllowedTypesCheck(array:any[],allowedTypes:("string"|"number"|"boolean"|"null"|"array"|"object"|"other")[]): boolean { - //return TRUE if ALL values are valid - return !array.some((value) => { - if (allowedTypes.includes("string") && typeof value == "string"){ - return false //this value is valid - }else if (allowedTypes.includes("number") && typeof value == "number"){ - return false //this value is valid - }else if (allowedTypes.includes("boolean") && typeof value == "boolean"){ - return false //this value is valid - }else if (allowedTypes.includes("object") && typeof value == "object"){ - return false //this value is valid - }else if (allowedTypes.includes("array") && Array.isArray(value)){ - return false //this value is valid - }else if (allowedTypes.includes("null") && value === null){ - return false //this value is valid - }else if (allowedTypes.includes("other")){ - return false //this value is valid - }else{ - return true //this value is invalid - } - }) - } - /**Check this array for doubles */ - #arrayHasDoubles(array:any[]): boolean { - const alreadyFound: string[] = [] - let hasDoubles = false - array.forEach((value) => { - if (alreadyFound.includes(value)) hasDoubles = true - else alreadyFound.push(value) - }) - - return hasDoubles - } -} - -/**## ODCheckerNullStructureOptions `interface` - * This interface has the options for `ODCheckerNullStructure`! - */ -export interface ODCheckerNullStructureOptions extends ODCheckerStructureOptions { - /**Is the value allowed to be null */ - nullAllowed?:boolean -} - -/**## ODCheckerNullStructure `class` - * This is an Open Ticket config checker structure. - * - * This class will check for a null variable in a config file, customise it in the settings! - */ -export class ODCheckerNullStructure extends ODCheckerStructure { - declare options: ODCheckerNullStructureOptions - - constructor(id:ODValidId, options:ODCheckerNullStructureOptions){ - super(id,options) - } - - check(checker:ODChecker, value:null, locationTrace:ODCheckerLocationTrace): boolean { - const lt = checker.locationTraceDeref(locationTrace) - - //check type & options - if (typeof this.options.nullAllowed != "undefined" && !this.options.nullAllowed && value == null){ - checker.createMessage("opendiscord:null-invalid","error","This property can't be null!",lt,null,[],this.id,(this.options.docs ?? null)) - return false - }else if (value !== null){ - checker.createMessage("opendiscord:invalid-type","error","This property needs to be the type: null!",lt,null,["null"],this.id,(this.options.docs ?? null)) - return false - }else return super.check(checker,value,locationTrace) - } -} - -/**## ODCheckerTypeSwitchStructureOptions `interface` - * This interface has the options for `ODCheckerTypeSwitchStructure`! - */ -export interface ODCheckerTypeSwitchStructureOptions extends ODCheckerStructureOptions { - /**A checker that will always run (replaces all other checkers) */ - all?:ODCheckerStructure, - /**A checker when the property is a string */ - string?:ODCheckerStringStructure, - /**A checker when the property is a number */ - number?:ODCheckerNumberStructure, - /**A checker when the property is a boolean */ - boolean?:ODCheckerBooleanStructure, - /**A checker when the property is null */ - null?:ODCheckerNullStructure, - /**A checker when the property is an array */ - array?:ODCheckerArrayStructure, - /**A checker when the property is an object */ - object?:ODCheckerObjectStructure, - /**A checker when the property is something else */ - other?:ODCheckerStructure, - /**A list of allowed types */ - allowedTypes:("string"|"number"|"boolean"|"null"|"array"|"object"|"other")[] -} - -/**## ODCheckerTypeSwitchStructure `class` - * This is an Open Ticket config checker structure. - * - * This class will switch checkers based on the type of the variable in a config file, customise it in the settings! - */ -export class ODCheckerTypeSwitchStructure extends ODCheckerStructure { - declare options: ODCheckerTypeSwitchStructureOptions - - constructor(id:ODValidId, options:ODCheckerTypeSwitchStructureOptions){ - super(id,options) - } - - check(checker:ODChecker, value:any, locationTrace:ODCheckerLocationTrace): boolean { - const lt = checker.locationTraceDeref(locationTrace) - - if (this.options.all){ - return this.options.all.check(checker,value,lt) - - }else if (this.options.string && typeof value == "string"){ - return this.options.string.check(checker,value,lt) - - }else if (this.options.number && typeof value == "number"){ - return this.options.number.check(checker,value,lt) - - }else if (this.options.boolean && typeof value == "boolean"){ - return this.options.boolean.check(checker,value,lt) - - }else if (this.options.array && Array.isArray(value)){ - return this.options.array.check(checker,value,lt) - - }else if (this.options.null && value === null){ - return this.options.null.check(checker,value,lt) - - }else if (this.options.object && typeof value == "object"){ - return this.options.object.check(checker,value,lt) - - }else if (this.options.other){ - return this.options.other.check(checker,value,lt) - - }else if (this.options.allowedTypes && this.options.allowedTypes.length > 0){ - checker.createMessage("opendiscord:switch-invalid-type","error",`This needs to be one of the following types: ${this.options.allowedTypes.join(", ")}!`,lt,null,[this.options.allowedTypes.join(", ")],this.id,(this.options.docs ?? null)) - return false - }else return super.check(checker,value,locationTrace) - } -} - -/**## ODCheckerObjectSwitchStructureOptions `interface` - * This interface has the options for `ODCheckerObjectSwitchStructure`! - */ -export interface ODCheckerObjectSwitchStructureOptions extends ODCheckerStructureOptions { - /**An array of object checkers with their name, properties & priority. */ - objects:{ - /**The properties to match for this checker to be used. */ - properties:{key:string, value:boolean|string|number}[], - /**The name for this object type (used in rendering) */ - name:string, - /**The higher the priority, the earlier this checker will be tested. */ - priority:number, - /**The object checker used once the properties have been matched. */ - checker:ODCheckerObjectStructure - }[] -} - -/**## ODCheckerObjectSwitchStructure `class` - * This is an Open Ticket config checker structure. - * - * This class will switch object checkers based on a variable match in one of the objects, customise it in the settings! - */ -export class ODCheckerObjectSwitchStructure extends ODCheckerStructure { - declare options: ODCheckerObjectSwitchStructureOptions - - constructor(id:ODValidId, options:ODCheckerObjectSwitchStructureOptions){ - super(id,options) - } - - check(checker:ODChecker, value:object, locationTrace:ODCheckerLocationTrace): boolean { - const lt = checker.locationTraceDeref(locationTrace) - - if (this.options.objects){ - //check type & options - if (typeof value != "object"){ - checker.createMessage("opendiscord:invalid-type","error","This property needs to be the type: object!",lt,null,["object"],this.id,(this.options.docs ?? null)) - return false - } - - //sort objects - const sortedObjects = this.options.objects.sort((a,b) => { - if (a.priority < b.priority) return -1 - else if (a.priority > b.priority) return 1 - else return 0 - }) - - - //check objects - let localQuit = false - let didSelectObject = false - sortedObjects.forEach((obj) => { - if (!obj.properties.some((p) => value[p.key] !== p.value)){ - didSelectObject = true - if (!obj.checker.check(checker,value,lt)) localQuit = true - } - }) - - //do local quit or check custom function - if (!didSelectObject){ - checker.createMessage("opendiscord:object-switch-invalid-type","error",`This object needs to be one of the following types: ${this.options.objects.map((obj) => obj.name).join(", ")}!`,lt,null,[this.options.objects.map((obj) => obj.name).join(", ")],this.id,(this.options.docs ?? null)) - return false - }else if (localQuit){ - return false - }else return super.check(checker,value,locationTrace) - }else return super.check(checker,value,locationTrace) - } -} - -/**## ODCheckerEnabledObjectStructureOptions `interface` - * This interface has the options for `ODCheckerEnabledObjectStructure`! - */ -export interface ODCheckerEnabledObjectStructureOptions extends ODCheckerStructureOptions { - /**The name of the property to match the `enabledValue`. */ - property:string, - /**The value of the property to be enabled. (e.g. `true`) */ - enabledValue:boolean|string|number, - /**The object checker to use once the property has been matched. */ - checker:ODCheckerObjectStructure -} - -/**## ODCheckerEnabledObjectStructure `class` - * This is an Open Ticket config checker structure. - * - * This class will enable an object checker based on a variable match in the object, customise it in the settings! - */ -export class ODCheckerEnabledObjectStructure extends ODCheckerStructure { - declare options: ODCheckerEnabledObjectStructureOptions - - constructor(id:ODValidId, options:ODCheckerEnabledObjectStructureOptions){ - super(id,options) - } - - check(checker:ODChecker, value:object, locationTrace:ODCheckerLocationTrace): boolean { - const lt = checker.locationTraceDeref(locationTrace) - - if (typeof value != "object"){ - //value isn't an object - checker.createMessage("opendiscord:invalid-type","error","This property needs to be the type: object!",lt,null,["object"],this.id,(this.options.docs ?? null)) - return false - - }else if (this.options.property && typeof value[this.options.property] == "undefined"){ - //property doesn't exist - checker.createMessage("opendiscord:property-missing","error",`The property "${this.options.property}" is mising from this object!`,lt,null,[`"${this.options.property}"`],this.id,(this.options.docs ?? null)) - return false - - }else if (this.options.property && value[this.options.property] === (typeof this.options.enabledValue == "undefined" ? true : this.options.enabledValue)){ - //this object is enabled - if (this.options.checker) return this.options.checker.check(checker,value,lt) - else return super.check(checker,value,locationTrace) - - }else{ - //this object is disabled - if (this.options.property) checker.createMessage("opendiscord:object-disabled","info",`This object is disabled, enable it using "${this.options.property}"!`,lt,null,[`"${this.options.property}"`],this.id,(this.options.docs ?? null)) - return super.check(checker,value,locationTrace) - } - } -} - -/**## ODCheckerCustomStructure_DiscordId `class` - * This is an Open Ticket custom checker structure. - * - * This class extends a primitive config checker & adds another layer of checking in the `custom` function. - * You can compare it to a blueprint for a specific checker. - * - * **This custom checker is made for discord ids (channel, user, role, ...)** - */ -export class ODCheckerCustomStructure_DiscordId extends ODCheckerStringStructure { - /**The type of id (used in rendering) */ - readonly type: ODDiscordIdType - /**Is this id allowed to be empty */ - readonly emptyAllowed: boolean - /**Extra matches (value will also be valid when one of these options match) */ - readonly extraOptions: string[] - - constructor(id:ODValidId, type:ODDiscordIdType, emptyAllowed:boolean, extraOptions:string[], options?:ODCheckerStringStructureOptions){ - //add premade custom structure checker - const newOptions = options ?? {} - newOptions.custom = (checker,value,locationTrace,locationId,locationDocs) => { - const lt = checker.locationTraceDeref(locationTrace) - - if (typeof value != "string") return false - else if ((!emptyAllowed && value.length < 15) || value.length > 50 || !/^[0-9]*$/.test(value)){ - if (!(extraOptions.length > 0 && extraOptions.some((opt) => opt == value))){ - //value is not an id & not one of the extra options - if (extraOptions.length > 0) checker.createMessage("opendiscord:discord-invalid-id-options","error",`This is an invalid discord ${type} id! You can also use one of these: ${extraOptions.join(", ")}!`,lt,null,[type,extraOptions.join(", ")],this.id,(this.options.docs ?? null)) - else checker.createMessage("opendiscord:discord-invalid-id","error",`This is an invalid discord ${type} id!`,lt,null,[type],this.id,(this.options.docs ?? null)) - return false - }else return true - } - return true - } - super(id,newOptions) - this.type = type - this.emptyAllowed = emptyAllowed - this.extraOptions = extraOptions - } -} - -/**## ODCheckerCustomStructure_DiscordIdArray `class` - * This is an Open Ticket custom checker structure. - * - * This class extends a primitive config checker & adds another layer of checking in the `custom` function. - * You can compare it to a blueprint for a specific checker. - * - * **This custom checker is made for discord id arrays (channel, user, role, ...)** - */ -export class ODCheckerCustomStructure_DiscordIdArray extends ODCheckerArrayStructure { - /**The type of id (used in rendering) */ - readonly type: ODDiscordIdType - /**Extra matches (value will also be valid when one of these options match) */ - readonly extraOptions: string[] - - constructor(id:ODValidId, type:ODDiscordIdType, extraOptions:string[], options?:ODCheckerArrayStructureOptions, idOptions?:ODCheckerStringStructureOptions){ - //add premade custom structure checker - const newOptions = options ?? {} - newOptions.propertyChecker = new ODCheckerCustomStructure_DiscordId(id,type,false,extraOptions,idOptions) - super(id,newOptions) - this.type = type - this.extraOptions = extraOptions - } -} - -/**## ODCheckerCustomStructure_DiscordToken `class` - * This is an Open Ticket custom checker structure. - * - * This class extends a primitive config checker & adds another layer of checking in the `custom` function. - * You can compare it to a blueprint for a specific checker. - * - * **This custom checker is made for a discord (auth) token** - */ -export class ODCheckerCustomStructure_DiscordToken extends ODCheckerStringStructure { - constructor(id:ODValidId, options?:ODCheckerStringStructureOptions){ - //add premade custom structure checker - const newOptions = options ?? {} - newOptions.custom = (checker,value,locationTrace,locationId,locationDocs) => { - const lt = checker.locationTraceDeref(locationTrace) - - if (typeof value != "string" || !/^[A-Za-z0-9-_\.]+$/.test(value)){ - checker.createMessage("opendiscord:discord-invalid-token","error","This is an invalid discord token (syntactically)!",lt,null,[],this.id,(this.options.docs ?? null)) - return false - } - return true - } - super(id,newOptions) - } -} - -/**## ODCheckerCustomStructure_DiscordToken `class` - * This is an Open Ticket custom checker structure. - * - * This class extends a primitive config checker & adds another layer of checking in the `custom` function. - * You can compare it to a blueprint for a specific checker. - * - * **This custom checker is made for a hex color** - */ -export class ODCheckerCustomStructure_HexColor extends ODCheckerStringStructure { - /**When enabled, you are also allowed to use `#fff` instead of `#ffffff` */ - readonly allowShortForm: boolean - /**Allow this hex color to be empty. */ - readonly emptyAllowed: boolean - - constructor(id:ODValidId, allowShortForm:boolean, emptyAllowed:boolean, options?:ODCheckerStringStructureOptions){ - //add premade custom structure checker - const newOptions = options ?? {} - newOptions.custom = (checker,value,locationTrace,locationId,locationDocs) => { - const lt = checker.locationTraceDeref(locationTrace) - - if (typeof value != "string") return false - else if (emptyAllowed && value.length == 0){ - return true - }else if ((!allowShortForm && !/^#[a-fA-F0-9]{6}$/.test(value)) || (allowShortForm && !/^#[a-fA-F0-9]{6}$/.test(value) && !/^#[a-fA-F0-9]{3}$/.test(value))){ - checker.createMessage("opendiscord:color-invalid","error","This is an invalid hex color!",lt,null,[],this.id,(this.options.docs ?? null)) - return false - }else return true - } - super(id,newOptions) - this.allowShortForm = allowShortForm - this.emptyAllowed = emptyAllowed - } -} - -/**## ODCheckerCustomStructure_EmojiString `class` - * This is an Open Ticket custom checker structure. - * - * This class extends a primitive config checker & adds another layer of checking in the `custom` function. - * You can compare it to a blueprint for a specific checker. - * - * **This custom checker is made for an emoji (string)** - */ -export class ODCheckerCustomStructure_EmojiString extends ODCheckerStringStructure { - /**The minimum amount of emojis required (0 to allow empty) */ - readonly minLength: number - /**The maximum amount of emojis allowed */ - readonly maxLength: number - /**Allow custom discord emoji ids (`<:12345678910:emoji_name>`) */ - readonly allowCustomDiscordEmoji: boolean - - constructor(id:ODValidId, minLength:number, maxLength:number, allowCustomDiscordEmoji:boolean, options?:ODCheckerStringStructureOptions){ - //add premade custom structure checker - const newOptions = options ?? {} - newOptions.custom = (checker,value,locationTrace,locationId,locationDocs) => { - const lt = checker.locationTraceDeref(locationTrace) - if (typeof value != "string") return false - - const discordEmojiSplitter = /(?:)/g - const splitted = value.split(discordEmojiSplitter) - const discordEmojiAmount = splitted.length-1 - const unicodeEmojiAmount = [...new Intl.Segmenter().segment(splitted.join(""))].length - const emojiAmount = discordEmojiAmount+unicodeEmojiAmount - - if (emojiAmount < minLength){ - checker.createMessage("opendiscord:emoji-too-short","error",`This string needs to have at least ${minLength} emoji's!`,lt,null,[maxLength.toString()],this.id,(this.options.docs ?? null)) - return false - }else if (emojiAmount > maxLength){ - checker.createMessage("opendiscord:emoji-too-long","error",`This string needs to have at most ${maxLength} emoji's!`,lt,null,[maxLength.toString()],this.id,(this.options.docs ?? null)) - return false - }else if (!allowCustomDiscordEmoji && //.test(value)){ - checker.createMessage("opendiscord:emoji-custom","error",`This emoji can't be a custom discord emoji!`,lt,null,[],this.id,(this.options.docs ?? null)) - return false - }else if (!/^(?:\p{Emoji}|\p{Emoji_Component}|(?:))*$/u.test(value)){ - checker.createMessage("opendiscord:emoji-invalid","error","This is an invalid emoji!",lt,null,[],this.id,(this.options.docs ?? null)) - return false - } - return true - } - super(id,newOptions) - this.minLength = minLength - this.maxLength = maxLength - this.allowCustomDiscordEmoji = allowCustomDiscordEmoji - } -} - -/**## ODCheckerCustomStructureOptions_UrlString `interface` - * This interface has the options for `ODCheckerCustomStructure_UrlString`! - */ -export interface ODCheckerCustomStructureOptions_UrlString { - /**Allow urls with `http://` instead of `https://` */ - allowHttp?:boolean - /**Allowed hostnames (string or regex) => will match domain + subdomain */ - allowedHostnames?: (string|RegExp)[] - /**Allowed extentions (string) => will match the end of the url (`.png`,`.svg`,...) */ - allowedExtensions?: string[] - /**Allowed paths (string or regex) => will match path + extension (not domain + subdomain) */ - allowedPaths?: (string|RegExp)[], - /**A regex that will be executed on the entire url (including search params, protcol, domain, ...) */ - regex?:RegExp -} - -/**## ODCheckerCustomStructure_UrlString `class` - * This is an Open Ticket custom checker structure. - * - * This class extends a primitive config checker & adds another layer of checking in the `custom` function. - * You can compare it to a blueprint for a specific checker. - * - * **This custom checker is made for a URL (string)** - */ -export class ODCheckerCustomStructure_UrlString extends ODCheckerStringStructure { - /**The settings for this url */ - readonly urlSettings: ODCheckerCustomStructureOptions_UrlString - /**Is this url allowed to be empty? */ - readonly emptyAllowed: boolean - - constructor(id:ODValidId, emptyAllowed:boolean, urlSettings:ODCheckerCustomStructureOptions_UrlString, options?:ODCheckerStringStructureOptions){ - //add premade custom structure checker - const newOptions = options ?? {} - newOptions.custom = (checker,value,locationTrace,locationId,locationDocs) => { - const lt = checker.locationTraceDeref(locationTrace) - - if (typeof value != "string") return false - else if (emptyAllowed && value.length == 0){ - return true - }else if (!this.#urlIsValid(value)){ - checker.createMessage("opendiscord:url-invalid","error","This url is invalid!",lt,null,[],this.id,(this.options.docs ?? null)) - return false - }else if (typeof this.urlSettings.allowHttp != "undefined" && !this.urlSettings.allowHttp && !/^(https:\/\/)/.test(value)){ - checker.createMessage("opendiscord:url-invalid-http","error","This url can only use the https:// protocol!",lt,null,[],this.id,(this.options.docs ?? null)) - return false - }else if (!/^(http(s)?:\/\/)/.test(value)){ - checker.createMessage("opendiscord:url-invalid-protocol","error","This url can only use the http:// & https:// protocols!",lt,null,[],this.id,(this.options.docs ?? null)) - return false - }else if (typeof this.urlSettings.allowedHostnames != "undefined" && !this.#urlHasValidHostname(value,this.urlSettings.allowedHostnames)){ - checker.createMessage("opendiscord:url-invalid-hostname","error","This url has a disallowed hostname!",lt,null,[],this.id,(this.options.docs ?? null)) - return false - }else if (typeof this.urlSettings.allowedExtensions != "undefined" && !this.#urlHasValidExtension(value,this.urlSettings.allowedExtensions)){ - checker.createMessage("opendiscord:url-invalid-extension","error",`This url has an invalid extension! Choose between: ${this.urlSettings.allowedExtensions.join(", ")}!"`,lt,null,[this.urlSettings.allowedExtensions.join(", ")],this.id,(this.options.docs ?? null)) - return false - }else if (typeof this.urlSettings.allowedPaths != "undefined" && !this.#urlHasValidPath(value,this.urlSettings.allowedPaths)){ - checker.createMessage("opendiscord:url-invalid-path","error","This url has an invalid path!",lt,null,[],this.id,(this.options.docs ?? null)) - return false - }else if (typeof this.urlSettings.regex != "undefined" && !this.urlSettings.regex.test(value)){ - checker.createMessage("opendiscord:url-invalid","error","This url is invalid!",lt,null,[],this.id,(this.options.docs ?? null)) - return false - }else return true - } - super(id,newOptions) - this.urlSettings = urlSettings - this.emptyAllowed = emptyAllowed - } - - /**Check for the hostname */ - #urlHasValidHostname(url:string,hostnames:(string|RegExp)[]): boolean { - try { - const hostname = new URL(url).hostname - return hostnames.some((rule) => { - if (typeof rule == "string"){ - return rule == hostname - }else{ - return rule.test(hostname) - } - }) - - }catch{ - return false - } - } - /**Check for the extension */ - #urlHasValidExtension(url:string,extensions:string[]): boolean { - try { - const path = new URL(url).pathname - return extensions.some((rule) => { - return path.endsWith(rule) - }) - }catch{ - return false - } - } - /**Check for the path */ - #urlHasValidPath(url:string,paths:(string|RegExp)[]): boolean { - try { - const path = new URL(url).pathname - return paths.some((rule) => { - if (typeof rule == "string"){ - return rule == path - }else{ - return rule.test(path) - } - }) - }catch{ - return false - } - } - /**Do general syntax check on url */ - #urlIsValid(url:string){ - try { - new URL(url) - return true - }catch{ - return false - } - } -} - -/**## ODCheckerCustomStructure_UniqueId `class` - * This is an Open Ticket custom checker structure. - * - * This class extends a primitive config checker & adds another layer of checking in the `custom` function. - * You can compare it to a blueprint for a specific checker. - * - * **This custom checker is made for a unique id (per source & scope)** - */ -export class ODCheckerCustomStructure_UniqueId extends ODCheckerStringStructure { - /**The source of this unique id (generally the plugin name or `openticket`) */ - readonly source: string - /**The scope of this unique id (id needs to be unique in this scope) */ - readonly scope: string - - constructor(id:ODValidId, source:string, scope:string, options?:ODCheckerStringStructureOptions){ - //add premade custom structure checker - const newOptions = options ?? {} - newOptions.custom = (checker,value,locationTrace,locationId,locationDocs) => { - const lt = checker.locationTraceDeref(locationTrace) - - if (typeof value != "string") return false - const uniqueArray: string[] = (checker.storage.get(source,scope) === null) ? [] : checker.storage.get(source,scope) - if (uniqueArray.includes(value)){ - //unique id already exists => throw error - checker.createMessage("opendiscord:id-not-unique","error","This id isn't unique, use another id instead!",lt,null,[],this.id,(this.options.docs ?? null)) - return false - }else{ - //unique id doesn't exists => add to list - uniqueArray.push(value) - checker.storage.set(source,scope,uniqueArray) - return true - } - } - super(id,newOptions) - this.source = source - this.scope = scope - } -} - -/**## ODCheckerCustomStructure_UniqueIdArray `class` - * This is an Open Ticket custom checker structure. - * - * This class extends a primitive config checker & adds another layer of checking in the `custom` function. - * You can compare it to a blueprint for a specific checker. - * - * **This custom checker is made for a unique id array (per source & scope)** - */ -export class ODCheckerCustomStructure_UniqueIdArray extends ODCheckerArrayStructure { - /**The source to read unique ids (generally the plugin name or `openticket`) */ - readonly source: string - /**The scope to read unique ids (id needs to be unique in this scope) */ - readonly scope: string - /**The scope to push unique ids when used in this array! */ - readonly usedScope: string|null - - constructor(id:ODValidId, source:string, scope:string, usedScope?:string, options?:ODCheckerArrayStructureOptions, idOptions?:Omit){ - //add premade custom structure checker - const newOptions = options ?? {} - newOptions.propertyChecker = new ODCheckerStringStructure("opendiscord:unique-id",{...(idOptions ?? {}),minLength:1,custom:(checker,value,locationTrace,locationId,locationDocs) => { - if (typeof value != "string") return false - const localLt = checker.locationTraceDeref(locationTrace) - localLt.pop() - - const uniqueArray: string[] = checker.storage.get(source,scope) ?? [] - if (uniqueArray.includes(value)){ - //exists - if (usedScope){ - const current: string[] = checker.storage.get(source,usedScope) ?? [] - current.push(value) - checker.storage.set(source,usedScope,current) - } - return true - }else{ - //doesn't exist - checker.createMessage("opendiscord:id-non-existent","error",`The id "${value}" doesn't exist!`,localLt,null,[`"${value}"`],locationId,locationDocs) - return false - } - }}) - super(id,newOptions) - this.source = source - this.scope = scope - this.usedScope = usedScope ?? null - } -} - -/*TEMPLATE!!!! -export interface ODCheckerTemplateStructureOptions extends ODCheckerStructureOptions { - -} -export class ODCheckerTemplateStructure extends ODCheckerStructure { - declare options: ODCheckerTemplateStructureOptions - - constructor(id:ODValidId, options:ODCheckerTemplateStructureOptions){ - super(id,options) - } - - check(checker:ODChecker, value:any, locationTrace:ODCheckerLocationTrace): boolean { - const lt = checker.locationTraceDeref(locationTrace) - - return super.check(checker,value,locationTrace) - } -} -*/ -/*CUSTOM TEMPLATE!!!! -export class ODCheckerCustomStructure_Template extends ODCheckerTemplateStructure { - idk: string - - constructor(id:ODValidId, idk:string, options?:ODCheckerStringStructureOptions){ - //add premade custom structure checker - const newOptions = options ?? {} - newOptions.custom = (checker,value,locationTrace,locationId,locationDocs) => { - const lt = checker.locationTraceDeref(locationTrace) - - //do custom check & push error message. Return true if correct - return boolean - } - super(id,newOptions) - this.idk = idk - } -} -*/ diff --git a/src/core/api/modules/client.ts b/src/core/api/modules/client.ts deleted file mode 100644 index 5638a99..0000000 --- a/src/core/api/modules/client.ts +++ /dev/null @@ -1,2247 +0,0 @@ -/////////////////////////////////////// -//DISCORD CLIENT MODULE -/////////////////////////////////////// -import { ODId, ODManager, ODManagerData, ODSystemError, ODValidId } from "./base" -import * as discord from "discord.js" -import {REST} from "@discordjs/rest" -import { ODConsoleWarningMessage, ODDebugger } from "./console" -import { ODMessageBuildResult, ODMessageBuildSentResult } from "./builder" -import { ODManualProgressBar } from "./progressbar" - -/**## ODClientIntents `type` - * A list of intents required when inviting the bot. - */ -export type ODClientIntents = ("Guilds"|"GuildMembers"|"GuildModeration"|"GuildEmojisAndStickers"|"GuildIntegrations"|"GuildWebhooks"|"GuildInvites"|"GuildVoiceStates"|"GuildPresences"|"GuildMessages"|"GuildMessageReactions"|"GuildMessageTyping"|"DirectMessages"|"DirectMessageReactions"|"DirectMessageTyping"|"MessageContent"|"GuildScheduledEvents"|"AutoModerationConfiguration"|"AutoModerationExecution") -/**## ODClientPriviligedIntents `type` - * A list of priviliged intents required to be enabled in the developer portal. - */ -export type ODClientPriviligedIntents = ("GuildMembers"|"MessageContent"|"Presence") -/**## ODClientPartials `type` - * A list of partials required for the bot to work. (`Message` & `Channel` are for receiving DM messages from uncached channels) - */ -export type ODClientPartials = ("User"|"Channel"|"GuildMember"|"Message"|"Reaction"|"GuildScheduledEvent"|"ThreadMember") -/**## ODClientPermissions `type` - * A list of permissions required in the server that the bot is active in. - */ -export type ODClientPermissions = ("CreateInstantInvite"|"KickMembers"|"BanMembers"|"Administrator"|"ManageChannels"|"ManageGuild"|"AddReactions"|"ViewAuditLog"|"PrioritySpeaker"|"Stream"|"ViewChannel"|"SendMessages"|"SendTTSMessages"|"ManageMessages"|"EmbedLinks"|"AttachFiles"|"ReadMessageHistory"|"MentionEveryone"|"UseExternalEmojis"|"ViewGuildInsights"|"Connect"|"Speak"|"MuteMembers"|"DeafenMembers"|"MoveMembers"|"UseVAD"|"ChangeNickname"|"ManageNicknames"|"ManageRoles"|"ManageWebhooks"|"ManageGuildExpressions"|"UseApplicationCommands"|"RequestToSpeak"|"ManageEvents"|"ManageThreads"|"CreatePublicThreads"|"CreatePrivateThreads"|"UseExternalStickers"|"SendMessagesInThreads"|"UseEmbeddedActivities"|"ModerateMembers"|"ViewCreatorMonetizationAnalytics"|"UseSoundboard"|"UseExternalSounds"|"SendVoiceMessages") - -/**## ODClientManager `class` - * This is an Open Ticket client manager. - * - * It is responsible for managing the discord.js client. Here, you can set the status, register slash commands and much more! - * - * If you want, you can also listen for custom events on the `ODClientManager.client` variable (`discord.Client`) - */ -export class ODClientManager { - /**Alias to Open Ticket debugger. */ - #debug: ODDebugger - - /**List of required bot intents. Add intents to this list using the `onClientLoad` event. */ - intents: ODClientIntents[] = [] - /**List of required bot privileged intents. Add intents to this list using the `onClientLoad` event. */ - privileges: ODClientPriviligedIntents[] = [] - /**List of required bot partials. Add intents to this list using the `onClientLoad` event. **❌ Only use when neccessery!** */ - partials: ODClientPartials[] = [] - /**List of required bot permissions. Add permissions to this list using the `onClientLoad` event. */ - permissions: ODClientPermissions[] = [] - /**The discord bot token, empty by default. */ - set token(value:string){ - this.#token = value - this.rest.setToken(value) - } - get token(){ - return this.#token - } - /**The discord bot token. **DON'T USE THIS!!!** (use `ODClientManager.token` instead) */ - #token: string = "" - - /**The discord.js `discord.Client`. Only use it when initiated! */ - client: discord.Client = new discord.Client({intents:[]}) //temporary client - /**The discord.js REST client. Used for stuff that discord.js can't handle :) */ - rest: discord.REST = new REST({version:"10"}) - /**Is the bot initiated? */ - initiated: boolean = false - /**Is the bot logged in? */ - loggedIn: boolean = false - /**Is the bot ready? */ - ready: boolean = false - - /**The main server of the bot. Provided by serverId in the config */ - mainServer: discord.Guild|null = null - /**(❌ DO NOT OVERWRITE ❌) Internal Open Ticket function to continue the startup when the client is ready! */ - readyListener: (() => Promise)|null = null - /**The status manager is responsible for setting the bot status. */ - activity: ODClientActivityManager - /**The slash command manager is responsible for all slash commands & their events inside the bot. */ - slashCommands: ODSlashCommandManager - /**The text command manager is responsible for all text commands & their events inside the bot. */ - textCommands: ODTextCommandManager - /**The context menu manager is responsible for all context menus & their events inside the bot. */ - contextMenus: ODContextMenuManager - /**The autocomplete manager is responsible for all autocomplete events inside the bot. */ - autocompletes: ODAutocompleteManager - - constructor(debug:ODDebugger){ - this.#debug = debug - this.activity = new ODClientActivityManager(this.#debug,this) - this.slashCommands = new ODSlashCommandManager(this.#debug,this) - this.textCommands = new ODTextCommandManager(this.#debug,this) - this.contextMenus = new ODContextMenuManager(this.#debug,this) - this.autocompletes = new ODAutocompleteManager(this.#debug,this) - } - - /**Initiate the `client` variable & add the intents & partials to the bot. */ - initClient(){ - if (!this.intents.every((value) => typeof discord.GatewayIntentBits[value] != "undefined")) throw new ODSystemError("Client has non-existing intents!") - if (!this.privileges.every((value) => typeof {GuildMembers:true,MessageContent:true,Presence:true}[value] != "undefined")) throw new ODSystemError("Client has non-existing privileged intents!") - if (!this.partials.every((value) => typeof discord.Partials[value] != "undefined")) throw new ODSystemError("Client has non-existing partials!") - if (!this.permissions.every((value) => typeof discord.PermissionFlagsBits[value] != "undefined")) throw new ODSystemError("Client has non-existing partials!") - - const intents = this.intents.map((value) => discord.GatewayIntentBits[value]) - const partials = this.partials.map((value) => discord.Partials[value]) - - const oldClient = this.client - this.client = new discord.Client({intents,partials}) - - //@ts-ignore - oldClient.eventNames().forEach((event:keyof discord.ClientEvents) => { - //@ts-ignore - const callbacks = oldClient.rawListeners(event) - callbacks.forEach((cb:() => void) => { - this.client.on(event,cb) - }) - }) - - this.initiated = true - - this.#debug.debug("Created client with intents: "+this.intents.join(", ")) - this.#debug.debug("Created client with privileged intents: "+this.privileges.join(", ")) - this.#debug.debug("Created client with partials: "+this.partials.join(", ")) - this.#debug.debug("Created client with permissions: "+this.permissions.join(", ")) - } - /**Get all servers the bot is part of. */ - async getGuilds(): Promise { - if (!this.initiated) throw new ODSystemError("Client isn't initiated yet!") - if (!this.ready) throw new ODSystemError("Client isn't ready yet!") - - return this.client.guilds.cache.map((guild) => guild) - } - /**Check if the bot is in a specific guild */ - checkBotInGuild(guild:discord.Guild){ - return (guild.members.me) ? true : false - } - /**Check if a specific guild has all required permissions (or `Administrator`) */ - checkGuildPerms(guild:discord.Guild){ - if (!guild.members.me) throw new ODSystemError("Client isn't a member in this server!") - const perms = guild.members.me.permissions - if (perms.has("Administrator")) return true - else{ - return this.permissions.every((perm) => { - return perms.has(perm) - }) - } - } - /**Log-in with a discord auth token. Rejects returns `false` using 'softErrors' on failure. */ - login(softErrors?:boolean): Promise { - return new Promise(async (resolve,reject) => { - if (!this.initiated) reject("Client isn't initiated yet!") - if (!this.token) reject("Client doesn't have a token!") - - try { - this.client.once("clientReady",async () => { - this.ready = true - - //set slashCommandManager & contextMenuManager to client applicationCommandManager - if (!this.client.application) throw new ODSystemError("Couldn't get client application for slashCommand & contextMenu managers!") - this.slashCommands.commandManager = this.client.application.commands - this.contextMenus.commandManager = this.client.application.commands - this.autocompletes.commandManager = this.client.application.commands - - if (this.readyListener) await this.readyListener() - resolve(true) - }) - - this.#debug.debug("Actual discord.js client.login()") - await this.client.login(this.token) - this.#debug.debug("Finished discord.js client.login()") - this.loggedIn = true - }catch(err){ - if (softErrors) return resolve(false) - else if (err.message.toLowerCase().includes("used disallowed intents")){ - process.emit("uncaughtException",new ODSystemError("Used disallowed intents")) - }else if (err.message.toLowerCase().includes("tokeninvalid") || err.message.toLowerCase().includes("an invalid token was provided")){ - process.emit("uncaughtException",new ODSystemError("Invalid discord bot token provided")) - }else reject("OT Login Error: "+err) - } - }) - } - /**A simplified shortcut to get a `discord.User` :) */ - async fetchUser(id:string): Promise { - if (!this.initiated) throw new ODSystemError("Client isn't initiated yet!") - if (!this.ready) throw new ODSystemError("Client isn't ready yet!") - - try{ - return await this.client.users.fetch(id) - }catch{ - return null - } - } - /**A simplified shortcut to get a `discord.Guild` :) */ - async fetchGuild(id:string): Promise { - if (!this.initiated) throw new ODSystemError("Client isn't initiated yet!") - if (!this.ready) throw new ODSystemError("Client isn't ready yet!") - - try{ - return await this.client.guilds.fetch(id) - }catch{ - return null - } - } - /**A simplified shortcut to get a `discord.Channel` :) */ - async fetchChannel(id:string): Promise { - if (!this.initiated) throw new ODSystemError("Client isn't initiated yet!") - if (!this.ready) throw new ODSystemError("Client isn't ready yet!") - - try{ - return await this.client.channels.fetch(id) - }catch{ - return null - } - } - /**A simplified shortcut to get a `discord.GuildBasedChannel` :) */ - async fetchGuildChannel(guildId:string|discord.Guild, id:string): Promise { - if (!this.initiated) throw new ODSystemError("Client isn't initiated yet!") - if (!this.ready) throw new ODSystemError("Client isn't ready yet!") - - try{ - const guild = (guildId instanceof discord.Guild) ? guildId : await this.fetchGuild(guildId) - if (!guild) return null - const channel = await guild.channels.fetch(id) - return channel - }catch{ - return null - } - } - /**A simplified shortcut to get a `discord.TextChannel` :) */ - async fetchGuildTextChannel(guildId:string|discord.Guild, id:string): Promise { - if (!this.initiated) throw new ODSystemError("Client isn't initiated yet!") - if (!this.ready) throw new ODSystemError("Client isn't ready yet!") - - try{ - const guild = (guildId instanceof discord.Guild) ? guildId : await this.fetchGuild(guildId) - if (!guild) return null - const channel = await guild.channels.fetch(id) - if (!channel || channel.type != discord.ChannelType.GuildText) return null - return channel - }catch{ - return null - } - } - /**A simplified shortcut to get a `discord.CategoryChannel` :) */ - async fetchGuildCategoryChannel(guildId:string|discord.Guild, id:string): Promise { - if (!this.initiated) throw new ODSystemError("Client isn't initiated yet!") - if (!this.ready) throw new ODSystemError("Client isn't ready yet!") - - try{ - const guild = (guildId instanceof discord.Guild) ? guildId : await this.fetchGuild(guildId) - if (!guild) return null - const channel = await guild.channels.fetch(id) - if (!channel || channel.type != discord.ChannelType.GuildCategory) return null - return channel - }catch{ - return null - } - } - /**A simplified shortcut to get a `discord.GuildMember` :) */ - async fetchGuildMember(guildId:string|discord.Guild, id:string): Promise { - if (!this.initiated) throw new ODSystemError("Client isn't initiated yet!") - if (!this.ready) throw new ODSystemError("Client isn't ready yet!") - if (typeof id != "string") throw new ODSystemError("TEMP ERROR => ODClientManager.fetchGuildMember() => id param isn't string") - - try{ - const guild = (guildId instanceof discord.Guild) ? guildId : await this.fetchGuild(guildId) - if (!guild) return null - return await guild.members.fetch(id) - }catch{ - return null - } - } - /**A simplified shortcut to get a `discord.Role` :) */ - async fetchGuildRole(guildId:string|discord.Guild, id:string): Promise { - if (!this.initiated) throw new ODSystemError("Client isn't initiated yet!") - if (!this.ready) throw new ODSystemError("Client isn't ready yet!") - if (typeof id != "string") throw new ODSystemError("TEMP ERROR => ODClientManager.fetchGuildRole() => id param isn't string") - - try{ - const guild = (guildId instanceof discord.Guild) ? guildId : await this.fetchGuild(guildId) - if (!guild) return null - return await guild.roles.fetch(id) - }catch{ - return null - } - } - /**A simplified shortcut to get a `discord.Message` :) */ - async fetchGuildChannelMessage(guildId:string|discord.Guild, channelId:string|discord.TextChannel, id:string): Promise|null> - async fetchGuildChannelMessage(channelId:discord.TextChannel, id:string): Promise|null> - async fetchGuildChannelMessage(guildId:string|discord.Guild|discord.TextChannel, channelId:string|discord.TextChannel|string, id?:string): Promise|null> { - if (!this.initiated) throw new ODSystemError("Client isn't initiated yet!") - if (!this.ready) throw new ODSystemError("Client isn't ready yet!") - - try{ - if (guildId instanceof discord.TextChannel && typeof channelId == "string"){ - const channel = guildId - return await channel.messages.fetch(channelId) - }else if (!(guildId instanceof discord.TextChannel) && id){ - const channel = (channelId instanceof discord.TextChannel) ? channelId : await this.fetchGuildTextChannel(guildId,channelId) - if (!channel) return null - return await channel.messages.fetch(id) - }else return null - }catch{ - return null - } - } - /**A simplified shortcut to send a DM to a user :) */ - async sendUserDm(user:string|discord.User, message:ODMessageBuildResult): Promise> { - if (!this.initiated) throw new ODSystemError("Client isn't initiated yet!") - if (!this.ready) throw new ODSystemError("Client isn't ready yet!") - - try{ - if (user instanceof discord.User){ - if (user.bot) return {success:false,message:null} - const channel = await user.createDM() - const msg = await channel.send(message.message) - return {success:true,message:msg} - }else{ - const newUser = await this.fetchUser(user) - if (!newUser) throw new Error() - if (newUser.bot) return {success:false,message:null} - const channel = await newUser.createDM() - const msg = await channel.send(message.message) - return {success:true,message:msg} - } - }catch{ - try{ - this.#debug.console.log("Failed to send DM to user! ","warning",[ - {key:"id",value:(user instanceof discord.User ? user.id : user)}, - {key:"message",value:message.id.value} - ]) - }catch{} - return {success:false,message:null} - } - } -} - -/**## ODClientActivityType `type` - * Possible activity types for the bot. - */ -export type ODClientActivityType = ("playing"|"listening"|"watching"|"custom"|false) -/**## ODClientActivityMode `type` - * Possible activity statuses for the bot. - */ -export type ODClientActivityMode = ("online"|"invisible"|"idle"|"dnd") - - -/**## ODClientActivityManager `class` - * This is an Open Ticket client activity manager. - * - * It's responsible for managing the client status. Here, you can set the activity & status of the bot. - * - * It also has a built-in refresh function, so the status will refresh every 10 minutes to keep it visible. - */ -export class ODClientActivityManager { - /**Alias to Open Ticket debugger. */ - #debug: ODDebugger - - /**Copy of discord.js client */ - manager: ODClientManager - /**The current status type */ - type: ODClientActivityType = false - /**The current status text */ - text: string = "" - /**The current status mode */ - mode: ODClientActivityMode = "online" - /**Additional state text */ - state: string = "" - - /**The timer responsible for refreshing the status. Stop it using `clearInterval(interval)` */ - interval?: NodeJS.Timeout - /**status refresh interval in seconds (5 minutes by default)*/ - refreshInterval: number = 600 - /**Is the status already initiated? */ - initiated: boolean = false - - constructor(debug:ODDebugger, manager:ODClientManager){ - this.#debug = debug - this.manager = manager - } - - /**Update the status. When already initiated, it can take up to 10min to see the updated status in discord. */ - setStatus(type:ODClientActivityType, text:string, mode:ODClientActivityMode, state:string, forceUpdate?:boolean){ - this.type = type - this.text = text - this.mode = mode - this.state = state - if (forceUpdate) this.#updateClientActivity(this.type,this.text) - } - - /**When initiating the status, the bot starts updating the status using `discord.js`. Returns `true` when successfull. */ - initStatus(): boolean { - if (this.initiated || !this.manager.ready) return false - this.#updateClientActivity(this.type,this.text) - this.interval = setInterval(() => { - this.#updateClientActivity(this.type,this.text) - this.#debug.debug("Client status update cycle") - },this.refreshInterval*1000) - this.initiated = true - this.#debug.debug("Client status initiated") - return true - } - - /**Update the client status */ - #updateClientActivity(type:ODClientActivityType,text:string){ - if (!this.manager.client.user) throw new ODSystemError("Couldn't set client status: client.user == undefined") - if (type == false){ - this.manager.client.user.setActivity() - return - } - this.manager.client.user.setPresence({ - activities:[{ - type:this.#getStatusTypeEnum(type), - state:this.state ? this.state : undefined, - name:text, - }], - status:this.mode - }) - } - /**Get the enum that links to the correct type */ - #getStatusTypeEnum(type:Exclude){ - if (type == "playing") return discord.ActivityType.Playing - else if (type == "listening") return discord.ActivityType.Listening - else if (type == "watching") return discord.ActivityType.Watching - else if (type == "custom") return discord.ActivityType.Custom - else return discord.ActivityType.Listening - } - /**Get the status type (for displaying the status) */ - getStatusType(): "listening "|"playing "|"watching "|"" { - if (this.type == "listening" || this.type == "playing" || this.type == "watching") return this.type+" " as "listening "|"playing "|"watching "|"" - else return "" - } -} - -/**## ODSlashCommandUniversalTranslation `interface` - * A universal template for a slash command translation. (used in names & descriptions) - * - * Why universal? Both **existing slash commands** & **unregistered templates** can be converted to this type. - */ -export interface ODSlashCommandUniversalTranslation { - /**The language code or locale of this language. */ - language:`${discord.Locale}`, - /**The translation of the name in this language. */ - value:string -} - -/**## ODSlashCommandUniversalOptionChoice `interface` - * A universal template for a slash command option choice. (used in `string` options) - * - * Why universal? Both **existing slash commands** & **unregistered templates** can be converted to this type. - */ -export interface ODSlashCommandUniversalOptionChoice { - /**The name of this choice. */ - name:string, - /**All localized names of this choice. */ - nameLocalizations:readonly ODSlashCommandUniversalTranslation[], - /**The value of this choice. */ - value:string -} - -/**## ODSlashCommandUniversalOption `interface` - * A universal template for a slash command option. - * - * Why universal? Both **existing slash commands** & **unregistered templates** can be converted to this type. - */ -export interface ODSlashCommandUniversalOption { - /**The type of this option. */ - type:discord.ApplicationCommandOptionType, - /**The name of this option. */ - name:string, - /**All localized names of this option. */ - nameLocalizations:readonly ODSlashCommandUniversalTranslation[], - /**The description of this option. */ - description:string, - /**All localized descriptions of this option. */ - descriptionLocalizations:readonly ODSlashCommandUniversalTranslation[], - /**Is this option required? */ - required:boolean, - - /**Is autocomplete enabled in this option? */ - autocomplete:boolean|null, - /**Choices for this option (only when type is `string`) */ - choices:ODSlashCommandUniversalOptionChoice[], - /**A list of sub-options for this option (only when type is `subCommand` or `subCommandGroup`) */ - options:readonly ODSlashCommandUniversalOption[], - /**A list of allowed channel types for this option (only when type is `channel`) */ - channelTypes:readonly discord.ChannelType[], - /**The minimum amount required for this option (only when type is `number` or `integer`) */ - minValue:number|null, - /**The maximum amount required for this option (only when type is `number` or `integer`) */ - maxValue:number|null, - /**The minimum length required for this option (only when type is `string`) */ - minLength:number|null, - /**The maximum length required for this option (only when type is `string`) */ - maxLength:number|null -} - -/**## ODSlashCommandUniversalCommand `interface` - * A universal template for a slash command. - * - * Why universal? Both **existing slash commands** & **unregistered templates** can be converted to this type. - */ -export interface ODSlashCommandUniversalCommand { - /**The type of this command. (required => `ChatInput`) */ - type:discord.ApplicationCommandType.ChatInput, - /**The name of this command. */ - name:string, - /**All localized names of this command. */ - nameLocalizations:readonly ODSlashCommandUniversalTranslation[], - /**The description of this command. */ - description:string, - /**All localized descriptions of this command. */ - descriptionLocalizations:readonly ODSlashCommandUniversalTranslation[], - /**The id of the guild this command is registered in. */ - guildId:string|null, - /**Is this command for 18+ users only? */ - nsfw:boolean, - /**A list of options for this command. */ - options:readonly ODSlashCommandUniversalOption[], - /**A bitfield of the user permissions required to use this command. */ - defaultMemberPermissions:bigint, - /**Is this command available in DM? */ - dmPermission:boolean, - /**A list of contexts where you can install this command. */ - integrationTypes:readonly discord.ApplicationIntegrationType[], - /**A list of contexts where you can use this command. */ - contexts:readonly discord.InteractionContextType[] -} - -/**## ODSlashCommandBuilder `interface` - * The builder for slash commands. Here you can add options to the command. - */ -export interface ODSlashCommandBuilder extends discord.ChatInputApplicationCommandData { - /**This field is required in Open Ticket for future compatibility. */ - integrationTypes:discord.ApplicationIntegrationType[], - /**This field is required in Open Ticket for future compatibility. */ - contexts:discord.InteractionContextType[] -} - -/**## ODSlashCommandComparator `class` - * A utility class to compare existing slash commands with newly registered ones. - */ -export class ODSlashCommandComparator { - /**Convert a `discord.ApplicationCommandOptionChoiceData` to a universal Open Ticket slash command option choice object for comparison. */ - #convertOptionChoice(choice:discord.ApplicationCommandOptionChoiceData): ODSlashCommandUniversalOptionChoice { - const nameLoc = choice.nameLocalizations ?? {} - return { - name:choice.name, - nameLocalizations:Object.keys(nameLoc).map((key) => {return {language:key as `${discord.Locale}`,value:nameLoc[key]}}), - value:choice.value - } - } - /**Convert a `discord.ApplicationCommandOptionData` to a universal Open Ticket slash command option object for comparison. */ - #convertBuilderOption(option:discord.ApplicationCommandOptionData): ODSlashCommandUniversalOption { - const nameLoc = option.nameLocalizations ?? {} - const descLoc = option.descriptionLocalizations ?? {} - return { - type:option.type, - name:option.name, - nameLocalizations:Object.keys(nameLoc).map((key) => {return {language:key as `${discord.Locale}`,value:nameLoc[key]}}), - description:option.description, - descriptionLocalizations:Object.keys(descLoc).map((key) => {return {language:key as `${discord.Locale}`,value:descLoc[key]}}), - required:(option.type != discord.ApplicationCommandOptionType.SubcommandGroup && option.type != discord.ApplicationCommandOptionType.Subcommand && option.required) ? true : false, - - autocomplete:option.autocomplete ?? false, - choices:(option.type == discord.ApplicationCommandOptionType.String && !option.autocomplete && option.choices) ? option.choices.map((choice) => this.#convertOptionChoice(choice)) : [], - options:((option.type == discord.ApplicationCommandOptionType.SubcommandGroup || option.type == discord.ApplicationCommandOptionType.Subcommand) && option.options) ? option.options.map((opt) => this.#convertBuilderOption(opt)) : [], - channelTypes:(option.type == discord.ApplicationCommandOptionType.Channel && option.channelTypes) ? option.channelTypes : [], - minValue:(option.type == discord.ApplicationCommandOptionType.Number && option.minValue) ? option.minValue : null, - maxValue:(option.type == discord.ApplicationCommandOptionType.Number && option.maxValue) ? option.maxValue : null, - minLength:(option.type == discord.ApplicationCommandOptionType.String && option.minLength) ? option.minLength : null, - maxLength:(option.type == discord.ApplicationCommandOptionType.String && option.maxLength) ? option.maxLength : null - } - } - /**Convert a `discord.ApplicationCommandOption` to a universal Open Ticket slash command option object for comparison. */ - #convertCommandOption(option:discord.ApplicationCommandOption): ODSlashCommandUniversalOption { - const nameLoc = option.nameLocalizations ?? {} - const descLoc = option.descriptionLocalizations ?? {} - - return { - type:option.type, - name:option.name, - nameLocalizations:Object.keys(nameLoc).map((key) => {return {language:key as `${discord.Locale}`,value:nameLoc[key]}}), - description:option.description, - descriptionLocalizations:Object.keys(descLoc).map((key) => {return {language:key as `${discord.Locale}`,value:descLoc[key]}}), - required:(option.type != discord.ApplicationCommandOptionType.SubcommandGroup && option.type != discord.ApplicationCommandOptionType.Subcommand && option.required) ? true : false, - - autocomplete:option.autocomplete ?? false, - choices:(option.type == discord.ApplicationCommandOptionType.String && !option.autocomplete && option.choices) ? option.choices.map((choice) => this.#convertOptionChoice(choice)) : [], - options:((option.type == discord.ApplicationCommandOptionType.SubcommandGroup || option.type == discord.ApplicationCommandOptionType.Subcommand) && option.options) ? option.options.map((opt) => this.#convertBuilderOption(opt)) : [], - channelTypes:(option.type == discord.ApplicationCommandOptionType.Channel && option.channelTypes) ? option.channelTypes : [], - minValue:(option.type == discord.ApplicationCommandOptionType.Number && option.minValue) ? option.minValue : null, - maxValue:(option.type == discord.ApplicationCommandOptionType.Number && option.maxValue) ? option.maxValue : null, - minLength:(option.type == discord.ApplicationCommandOptionType.String && option.minLength) ? option.minLength : null, - maxLength:(option.type == discord.ApplicationCommandOptionType.String && option.maxLength) ? option.maxLength : null - } - } - /**Convert a `ODSlashCommandBuilder` to a universal Open Ticket slash command object for comparison. */ - convertBuilder(builder:ODSlashCommandBuilder,guildId:string|null): ODSlashCommandUniversalCommand|null { - if (builder.type != discord.ApplicationCommandType.ChatInput) return null //throw new ODSystemError("ODSlashCommandComparator:convertBuilder() is not supported for other types than 'ChatInput'!") - const nameLoc = builder.nameLocalizations ?? {} - const descLoc = builder.descriptionLocalizations ?? {} - return { - type:1, - name:builder.name, - nameLocalizations:Object.keys(nameLoc).map((key) => {return {language:key as `${discord.Locale}`,value:nameLoc[key]}}), - description:builder.description, - descriptionLocalizations:Object.keys(descLoc).map((key) => {return {language:key as `${discord.Locale}`,value:descLoc[key]}}), - guildId:guildId, - nsfw:builder.nsfw ?? false, - options:builder.options ? builder.options.map((opt) => this.#convertBuilderOption(opt)) : [], - defaultMemberPermissions:discord.PermissionsBitField.resolve(builder.defaultMemberPermissions ?? ["ViewChannel"]), - dmPermission:(builder.contexts && builder.contexts.includes(discord.InteractionContextType.BotDM)) ?? false, - integrationTypes:builder.integrationTypes ?? [discord.ApplicationIntegrationType.GuildInstall], - contexts:builder.contexts ?? [] - } - } - /**Convert a `discord.ApplicationCommand` to a universal Open Ticket slash command object for comparison. */ - convertCommand(cmd:discord.ApplicationCommand): ODSlashCommandUniversalCommand|null { - if (cmd.type != discord.ApplicationCommandType.ChatInput) return null //throw new ODSystemError("ODSlashCommandComparator:convertCommand() is not supported for other types than 'ChatInput'!") - const nameLoc = cmd.nameLocalizations ?? {} - const descLoc = cmd.descriptionLocalizations ?? {} - return { - type:1, - name:cmd.name, - nameLocalizations:Object.keys(nameLoc).map((key) => {return {language:key as `${discord.Locale}`,value:nameLoc[key]}}), - description:cmd.description, - descriptionLocalizations:Object.keys(descLoc).map((key) => {return {language:key as `${discord.Locale}`,value:descLoc[key]}}), - guildId:cmd.guildId, - nsfw:cmd.nsfw, - options:cmd.options ? cmd.options.map((opt) => this.#convertCommandOption(opt)) : [], - defaultMemberPermissions:discord.PermissionsBitField.resolve(cmd.defaultMemberPermissions ?? ["ViewChannel"]), - dmPermission:(cmd.contexts && cmd.contexts.includes(discord.InteractionContextType.BotDM)) ? true : false, - integrationTypes:cmd.integrationTypes ?? [discord.ApplicationIntegrationType.GuildInstall], - contexts:cmd.contexts ?? [] - } - } - /**Returns `true` when the 2 slash command options are the same. */ - compareOption(optA:ODSlashCommandUniversalOption,optB:ODSlashCommandUniversalOption): boolean { - if (optA.name != optB.name) return false - if (optA.description != optB.description) return false - if (optA.type != optB.type) return false - if (optA.required != optB.required) return false - if (optA.autocomplete != optB.autocomplete) return false - if (optA.minValue != optB.minValue) return false - if (optA.maxValue != optB.maxValue) return false - if (optA.minLength != optB.minLength) return false - if (optA.maxLength != optB.maxLength) return false - - //nameLocalizations - if (optA.nameLocalizations.length != optB.nameLocalizations.length) return false - if (!optA.nameLocalizations.every((nameA) => { - const nameB = optB.nameLocalizations.find((nameB) => nameB.language == nameA.language) - if (!nameB || nameA.value != nameB.value) return false - else return true - })) return false - - //descriptionLocalizations - if (optA.descriptionLocalizations.length != optB.descriptionLocalizations.length) return false - if (!optA.descriptionLocalizations.every((descA) => { - const descB = optB.descriptionLocalizations.find((descB) => descB.language == descA.language) - if (!descB || descA.value != descB.value) return false - else return true - })) return false - - //choices - if (optA.choices.length != optB.choices.length) return false - if (!optA.choices.every((choiceA,index) => { - const choiceB = optB.choices[index] - if (choiceA.name != choiceB.name) return false - if (choiceA.value != choiceB.value) return false - - //nameLocalizations - if (choiceA.nameLocalizations.length != choiceB.nameLocalizations.length) return false - if (!choiceA.nameLocalizations.every((nameA) => { - const nameB = choiceB.nameLocalizations.find((nameB) => nameB.language == nameA.language) - if (!nameB || nameA.value != nameB.value) return false - else return true - })) return false - - return true - })) return false - - //channelTypes - if (optA.channelTypes.length != optB.channelTypes.length) return false - if (!optA.channelTypes.every((typeA) => { - return optB.channelTypes.includes(typeA) - })) return false - - //options - if (optA.options.length != optB.options.length) return false - if (!optA.options.every((subOptA,index) => { - return this.compareOption(subOptA,optB.options[index]) - })) return false - - return true - } - /**Returns `true` when the 2 slash commands are the same. */ - compare(cmdA:ODSlashCommandUniversalCommand,cmdB:ODSlashCommandUniversalCommand): boolean { - if (cmdA.name != cmdB.name) return false - if (cmdA.description != cmdB.description) return false - if (cmdA.type != cmdB.type) return false - if (cmdA.nsfw != cmdB.nsfw) return false - if (cmdA.guildId != cmdB.guildId) return false - if (cmdA.dmPermission != cmdB.dmPermission) return false - if (cmdA.defaultMemberPermissions != cmdB.defaultMemberPermissions) return false - - //nameLocalizations - if (cmdA.nameLocalizations.length != cmdB.nameLocalizations.length) return false - if (!cmdA.nameLocalizations.every((nameA) => { - const nameB = cmdB.nameLocalizations.find((nameB) => nameB.language == nameA.language) - if (!nameB || nameA.value != nameB.value) return false - else return true - })) return false - - //descriptionLocalizations - if (cmdA.descriptionLocalizations.length != cmdB.descriptionLocalizations.length) return false - if (!cmdA.descriptionLocalizations.every((descA) => { - const descB = cmdB.descriptionLocalizations.find((descB) => descB.language == descA.language) - if (!descB || descA.value != descB.value) return false - else return true - })) return false - - //contexts - if (cmdA.contexts.length != cmdB.contexts.length) return false - if (!cmdA.contexts.every((contextA) => { - return cmdB.contexts.includes(contextA) - })) return false - - //integrationTypes - if (cmdA.integrationTypes.length != cmdB.integrationTypes.length) return false - if (!cmdA.integrationTypes.every((integrationA) => { - return cmdB.integrationTypes.includes(integrationA) - })) return false - - //options - if (cmdA.options.length != cmdB.options.length) return false - if (!cmdA.options.every((optA,index) => { - return this.compareOption(optA,cmdB.options[index]) - })) return false - - return true - } -} - -/**## ODSlashCommandInteractionCallback `type` - * Callback for the slash command interaction listener. - */ -export type ODSlashCommandInteractionCallback = (interaction:discord.ChatInputCommandInteraction,cmd:ODSlashCommand) => void - -/**## ODSlashCommandRegisteredResult `type` - * The result which will be returned when getting all (un)registered slash commands from the manager. - */ -export type ODSlashCommandRegisteredResult = { - /**A list of all registered commands. */ - registered:{ - /**The instance (`ODSlashCommand`) from this command. */ - instance:ODSlashCommand, - /**The (universal) slash command object/template of this command. */ - cmd:ODSlashCommandUniversalCommand, - /**Does this command require an update? */ - requiresUpdate:boolean - }[], - /**A list of all unregistered commands. */ - unregistered:{ - /**The instance (`ODSlashCommand`) from this command. */ - instance:ODSlashCommand, - /**The (universal) slash command object/template of this command. */ - cmd:null, - /**Does this command require an update? */ - requiresUpdate:true - }[], - /**A list of all unused commands (not found in `ODSlashCommandManager`). */ - unused:{ - /**The instance (`ODSlashCommand`) from this command. */ - instance:null, - /**The (universal) slash command object/template of this command. */ - cmd:ODSlashCommandUniversalCommand, - /**Does this command require an update? */ - requiresUpdate:false - }[] -} - -/**## ODSlashCommandManager `class` - * This is an Open Ticket client slash manager. - * - * It's responsible for managing all the slash commands from the client. - * - * Here, you can add & remove slash commands & the bot will do the (de)registering. - */ -export class ODSlashCommandManager extends ODManager { - /**Alias to Open Ticket debugger. */ - #debug: ODDebugger - - /**Refrerence to discord.js client. */ - manager: ODClientManager - /**Discord.js application commands manager. */ - commandManager: discord.ApplicationCommandManager|null - /**Collection of all interaction listeners. */ - #interactionListeners: {name:string|RegExp, callback:ODSlashCommandInteractionCallback}[] = [] - /**Set the soft limit for maximum amount of listeners. A warning will be shown when there are more listeners than this limit. */ - listenerLimit: number = 100 - /**A utility class used to compare 2 slash commands with each other. */ - comparator: ODSlashCommandComparator = new ODSlashCommandComparator() - - constructor(debug:ODDebugger, manager:ODClientManager){ - super(debug,"slash command") - this.#debug = debug - this.manager = manager - this.commandManager = (manager.client.application) ? manager.client.application.commands : null - } - - /**Get all registered & unregistered slash commands. */ - async getAllRegisteredCommands(guildId?:string): Promise { - if (!this.commandManager) throw new ODSystemError("Couldn't get client application to register slash commands!") - - const cmds = (await this.commandManager.fetch({guildId})).toJSON() - const registered: {instance:ODSlashCommand, cmd:ODSlashCommandUniversalCommand, requiresUpdate:boolean}[] = [] - const unregistered: {instance:ODSlashCommand, cmd:null, requiresUpdate:true}[] = [] - const unused: {instance:null, cmd:ODSlashCommandUniversalCommand, requiresUpdate:false}[] = [] - - await this.loopAll((instance) => { - if (guildId && instance.guildId != guildId) return - - const index = cmds.findIndex((cmd) => cmd.name == instance.name) - const cmd = cmds[index] - cmds.splice(index,1) - if (cmd){ - //command is registered (and may need to be updated) - const universalBuilder = this.comparator.convertBuilder(instance.builder,instance.guildId) - const universalCmd = this.comparator.convertCommand(cmd) - - //command is not of the type 'chatinput' - if (!universalBuilder || !universalCmd) return - - const didChange = !this.comparator.compare(universalBuilder,universalCmd) - const requiresUpdate = didChange || (instance.requiresUpdate ? instance.requiresUpdate(universalCmd) : false) - registered.push({instance,cmd:universalCmd,requiresUpdate}) - - //command is not registered - }else unregistered.push({instance,cmd:null,requiresUpdate:true}) - }) - - cmds.forEach((cmd) => { - //command does not exist in the manager (only append to unused when type == 'chatinput') - const universalCmd = this.comparator.convertCommand(cmd) - if (!universalCmd) return - unused.push({instance:null,cmd:universalCmd,requiresUpdate:false}) - }) - - return {registered,unregistered,unused} - } - /**Create all commands that are not registered yet.*/ - async createNewCommands(instances:ODSlashCommand[],progress?:ODManualProgressBar){ - if (!this.manager.ready) throw new ODSystemError("Client isn't ready yet! Unable to register slash commands!") - if (instances.length > 0 && progress){ - progress.max = instances.length - progress.start() - } - - for (const instance of instances){ - await this.createCmd(instance) - this.#debug.debug("Created new slash command",[ - {key:"id",value:instance.id.value}, - {key:"name",value:instance.name} - ]) - if (progress) progress.increase(1) - } - } - /**Update all commands that are already registered. */ - async updateExistingCommands(instances:ODSlashCommand[],progress?:ODManualProgressBar){ - if (!this.manager.ready) throw new ODSystemError("Client isn't ready yet! Unable to register slash commands!") - if (instances.length > 0 && progress){ - progress.max = instances.length - progress.start() - } - - for (const instance of instances){ - await this.createCmd(instance) - this.#debug.debug("Updated existing slash command",[{key:"id",value:instance.id.value},{key:"name",value:instance.name}]) - if (progress) progress.increase(1) - } - } - /**Remove all commands that are registered but unused by Open Ticket. */ - async removeUnusedCommands(instances:ODSlashCommandUniversalCommand[],guildId?:string,progress?:ODManualProgressBar){ - if (!this.manager.ready) throw new ODSystemError("Client isn't ready yet! Unable to register slash commands!") - if (!this.commandManager) throw new ODSystemError("Couldn't get client application to register slash commands!") - if (instances.length > 0 && progress){ - progress.max = instances.length - progress.start() - } - - const cmds = await this.commandManager.fetch({guildId}) - - for (const instance of instances){ - const cmd = cmds.find((cmd) => cmd.name == instance.name) - if (cmd){ - try { - await cmd.delete() - this.#debug.debug("Removed existing slash command",[{key:"name",value:cmd.name},{key:"guildId",value:guildId ?? "/"}]) - }catch(err){ - process.emit("uncaughtException",err) - throw new ODSystemError("Failed to delete slash command '/"+cmd.name+"'!") - } - } - if (progress) progress.increase(1) - } - } - /**Create a slash command. **(SYSTEM ONLY)** => Use `ODSlashCommandManager` for registering commands the default way! */ - async createCmd(cmd:ODSlashCommand){ - if (!this.commandManager) throw new ODSystemError("Couldn't get client application to register slash commands!") - try { - await this.commandManager.create(cmd.builder,(cmd.guildId ?? undefined)) - }catch(err){ - process.emit("uncaughtException",err) - throw new ODSystemError("Failed to register slash command '/"+cmd.name+"'!") - } - } - /**Start listening to the discord.js client `interactionCreate` event. */ - startListeningToInteractions(){ - this.manager.client.on("interactionCreate",(interaction) => { - //return when not in main server or DM - if (!this.manager.mainServer || (interaction.guild && interaction.guild.id != this.manager.mainServer.id)) return - - if (!interaction.isChatInputCommand()) return - const cmd = this.getFiltered((cmd) => cmd.name == interaction.commandName)[0] - if (!cmd) return - - this.#interactionListeners.forEach((listener) => { - if (typeof listener.name == "string" && (interaction.commandName != listener.name)) return - else if (listener.name instanceof RegExp && !listener.name.test(interaction.commandName)) return - - //this is a valid listener - listener.callback(interaction,cmd) - }) - }) - } - /**Callback on interaction from one or multiple slash commands. */ - onInteraction(commandName:string|RegExp, callback:ODSlashCommandInteractionCallback){ - this.#interactionListeners.push({ - name:commandName, - callback - }) - - if (this.#interactionListeners.length > this.listenerLimit){ - this.#debug.console.log(new ODConsoleWarningMessage("Possible slash command interaction memory leak detected!",[ - {key:"listeners",value:this.#interactionListeners.length.toString()} - ])) - } - } -} - -/**## ODSlashCommandUpdateFunction `type` - * The function responsible for updating slash commands when they already exist. - */ -export type ODSlashCommandUpdateFunction = (command:ODSlashCommandUniversalCommand) => boolean - -/**## ODSlashCommand `class` - * This is an Open Ticket slash command. - * - * When registered, you can listen for this command using the `ODCommandResponder`. The advantages of using this class for creating a slash command are: - * - automatic option parsing (even for channels, users, roles & mentions)! - * - automatic registration in discord.js - * - error reporting to the user when the bot fails to respond - * - plugins can extend this command - * - the bot won't re-register the command when it already exists (except when requested)! - * - * And more! - */ -export class ODSlashCommand extends ODManagerData { - /**The discord.js builder for this slash command. */ - builder: ODSlashCommandBuilder - /**The id of the guild this command is for. Null when not set. */ - guildId: string|null - /**Function to check if the slash command requires to be updated (when it already exists). */ - requiresUpdate: ODSlashCommandUpdateFunction|null = null - - constructor(id:ODValidId, builder:ODSlashCommandBuilder, requiresUpdate?:ODSlashCommandUpdateFunction, guildId?:string){ - super(id) - if (builder.type != discord.ApplicationCommandType.ChatInput) throw new ODSystemError("ApplicationCommandData is required to be the 'ChatInput' type!") - - this.builder = builder - this.guildId = guildId ?? null - this.requiresUpdate = requiresUpdate ?? null - } - - /**The name of this slash command. */ - get name(): string { - return this.builder.name - } - set name(name:string){ - this.builder.name = name - } -} - -/**## ODTextCommandBuilderBaseOptionType `type` - * The types available in the text command option builder. - */ -export type ODTextCommandBuilderBaseOptionType = "string"|"number"|"boolean"|"user"|"guildmember"|"role"|"mentionable"|"channel" - -/**## ODTextCommandBuilderBaseOption `interface` - * The default option builder for text commands. - */ -export interface ODTextCommandBuilderBaseOption { - /**The name of this option */ - name:string, - /**The type of this option */ - type:ODTextCommandBuilderBaseOptionType, - /**Is this option required? (optional options can only exist at the end of the command!) */ - required?:boolean -} - -/**## ODTextCommandBuilderStringOption `interface` - * The string option builder for text commands. - */ -export interface ODTextCommandBuilderStringOption extends ODTextCommandBuilderBaseOption { - type:"string", - /**Set the maximum length of this string */ - maxLength?:number, - /**Set the minimum length of this string */ - minLength?:number, - /**The string needs to match this regex or it will be invalid */ - regex?:RegExp, - /**The string needs to match one of these choices or it will be invalid */ - choices?:string[], - /**When this is the last option, allow this string to contain spaces */ - allowSpaces?:boolean -} - -/**## ODTextCommandBuilderNumberOption `interface` - * The number option builder for text commands. - */ -export interface ODTextCommandBuilderNumberOption extends ODTextCommandBuilderBaseOption { - type:"number", - /**The number can't be higher than this value */ - max?:number, - /**The number can't be lower than this value */ - min?:number, - /**Allow the number to be negative */ - allowNegative?:boolean, - /**Allow the number to be positive */ - allowPositive?:boolean, - /**Allow the number to be zero */ - allowZero?:boolean, - /**Allow a number with decimal */ - allowDecimal?:boolean -} - -/**## ODTextCommandBuilderBooleanOption `interface` - * The boolean option builder for text commands. - */ -export interface ODTextCommandBuilderBooleanOption extends ODTextCommandBuilderBaseOption { - type:"boolean", - /**The value when `true` */ - trueValue?:string, - /**The value when `false` */ - falseValue?:string -} - -/**## ODTextCommandBuilderChannelOption `interface` - * The channel option builder for text commands. - */ -export interface ODTextCommandBuilderChannelOption extends ODTextCommandBuilderBaseOption { - type:"channel", - /**When specified, only allow the following channel types */ - channelTypes?:discord.GuildChannelType[] -} - -/**## ODTextCommandBuilderRoleOption `interface` - * The role option builder for text commands. - */ -export interface ODTextCommandBuilderRoleOption extends ODTextCommandBuilderBaseOption { - type:"role" -} - -/**## ODTextCommandBuilderUserOption `interface` - * The user option builder for text commands. - */ -export interface ODTextCommandBuilderUserOption extends ODTextCommandBuilderBaseOption { - type:"user" -} - -/**## ODTextCommandBuilderGuildMemberOption `interface` - * The guild member option builder for text commands. - */ -export interface ODTextCommandBuilderGuildMemberOption extends ODTextCommandBuilderBaseOption { - type:"guildmember" -} - -/**## ODTextCommandBuilderMentionableOption `interface` - * The mentionable option builder for text commands. - */ -export interface ODTextCommandBuilderMentionableOption extends ODTextCommandBuilderBaseOption { - type:"mentionable" -} - -/**## ODTextCommandBuilderOption `type` - * The option builder for text commands. - */ -export type ODTextCommandBuilderOption = ( - ODTextCommandBuilderStringOption| - ODTextCommandBuilderBooleanOption| - ODTextCommandBuilderNumberOption| - ODTextCommandBuilderChannelOption| - ODTextCommandBuilderRoleOption| - ODTextCommandBuilderUserOption| - ODTextCommandBuilderGuildMemberOption| - ODTextCommandBuilderMentionableOption -) - -/**## ODTextCommandBuilder `interface` - * The builder for text commands. Here you can add options to the command. - */ -export interface ODTextCommandBuilder { - /**The prefix of this command */ - prefix:string, - /**The name of this command (can include spaces for subcommands) */ - name:string, - /**Is this command allowed in dm? */ - dmPermission?:boolean, - /**Is this command allowed in guilds? */ - guildPermission?:boolean, - /**When specified, only allow this command to be executed in the following guilds */ - allowedGuildIds?:string[], - /**Are bots allowed to execute this command? */ - allowBots?:boolean - /**The options for this text command (like slash commands) */ - options?:ODTextCommandBuilderOption[] -} - -/**## ODTextCommand `class` - * This is an Open Ticket text command. - * - * When registered, you can listen for this command using the `ODCommandResponder`. The advantages of using this class for creating a text command are: - * - automatic option parsing (even for channels, users, roles & mentions)! - * - automatic errors on invalid parameters - * - error reporting to the user when the bot fails to respond - * - plugins can extend this command - * - * And more! - */ -export class ODTextCommand extends ODManagerData { - /**The builder for this slash command. */ - builder: ODTextCommandBuilder - /**The name of this slash command. */ - name: string - - constructor(id:ODValidId, builder:ODTextCommandBuilder){ - super(id) - this.builder = builder - this.name = builder.name - } -} - -/**## ODTextCommandInteractionOptionBase `interface` - * The object returned for options from a text command interaction. - */ -export interface ODTextCommandInteractionOptionBase { - /**The name of this option */ - name:string, - /**The type of this option */ - type:Name, - /**The value of this option */ - value:Type -} - -/**## ODTextCommandInteractionOption `type` - * A list of types returned for options from a text command interaction. - */ -export type ODTextCommandInteractionOption = ( - ODTextCommandInteractionOptionBase<"string",string>| - ODTextCommandInteractionOptionBase<"number",number>| - ODTextCommandInteractionOptionBase<"boolean",boolean>| - ODTextCommandInteractionOptionBase<"channel",discord.GuildBasedChannel>| - ODTextCommandInteractionOptionBase<"role",discord.Role>| - ODTextCommandInteractionOptionBase<"user",discord.User>| - ODTextCommandInteractionOptionBase<"guildmember",discord.GuildMember>| - ODTextCommandInteractionOptionBase<"mentionable",discord.Role|discord.User> -) - -/**## ODTextCommandInteractionCallback `type` - * Callback for the text command interaction listener. - */ -export type ODTextCommandInteractionCallback = (msg:discord.Message, cmd:ODTextCommand, options:ODTextCommandInteractionOption[]) => void - -/**## ODTextCommandErrorBase `interface` - * The object returned from a text command error callback. - */ -export interface ODTextCommandErrorBase { - /**The type of text command error */ - type:"unknown_prefix"|"unknown_command"|"invalid_option"|"missing_option", - /**The message this error originates from */ - msg:discord.Message -} - -/**## ODTextCommandErrorUnknownPrefix `interface` - * The object returned from a text command unknown prefix error callback. - */ -export interface ODTextCommandErrorUnknownPrefix extends ODTextCommandErrorBase { - type:"unknown_prefix" -} - -/**## ODTextCommandErrorUnknownCommand `interface` - * The object returned from a text command unknown command error callback. - */ -export interface ODTextCommandErrorUnknownCommand extends ODTextCommandErrorBase { - type:"unknown_command" -} - -/**## ODTextCommandErrorInvalidOptionReason `type` - * A list of reasons for the invalid_option error to be thrown. - */ -export type ODTextCommandErrorInvalidOptionReason = ( - "boolean"| - "number_max"| - "number_min"| - "number_decimal"| - "number_negative"| - "number_positive"| - "number_zero"| - "number_invalid"| - "string_max_length"| - "string_min_length"| - "string_regex"| - "string_choice"| - "not_in_guild"| - "channel_not_found"| - "channel_type"| - "user_not_found"| - "member_not_found"| - "role_not_found"| - "mentionable_not_found" -) - -/**## ODTextCommandErrorInvalidOption `interface` - * The object returned from a text command invalid option error callback. - */ -export interface ODTextCommandErrorInvalidOption extends ODTextCommandErrorBase { - type:"invalid_option", - /**The command this error originates from */ - command:ODTextCommand, - /**The command prefix this error originates from */ - prefix:string, - /**The command name this error originates from (can include spaces for subcommands) */ - name:string, - /**The option that this error originates from */ - option:ODTextCommandBuilderOption - /**The location that this option was found */ - location:number, - /**The current value of this invalid option */ - value:string, - /**The reason for this invalid option */ - reason:ODTextCommandErrorInvalidOptionReason -} - -/**## ODTextCommandErrorMissingOption `interface` - * The object returned from a text command missing option error callback. - */ -export interface ODTextCommandErrorMissingOption extends ODTextCommandErrorBase { - type:"missing_option", - /**The command this error originates from */ - command:ODTextCommand, - /**The command prefix this error originates from */ - prefix:string, - /**The command name this error originates from (can include spaces for subcommands) */ - name:string, - /**The option that this error originates from */ - option:ODTextCommandBuilderOption - /**The location that this option was found */ - location:number -} - -/**## ODTextCommandError `type` - * A list of types returned for errors from a text command interaction. - */ -export type ODTextCommandError = ( - ODTextCommandErrorUnknownPrefix| - ODTextCommandErrorUnknownCommand| - ODTextCommandErrorInvalidOption| - ODTextCommandErrorMissingOption -) - -/**## ODTextCommandErrorCallback `type` - * Callback for the text command error listener. - */ -export type ODTextCommandErrorCallback = (error:ODTextCommandError) => void - -/**## ODTextCommandManager `class` - * This is an Open Ticket client text manager. - * - * It's responsible for managing all the text commands from the client. - * - * Here, you can add & remove text commands & the bot will do the (de)registering. - */ -export class ODTextCommandManager extends ODManager { - /**Alias to Open Ticket debugger. */ - #debug: ODDebugger - /**Copy of discord.js client. */ - manager: ODClientManager - /**Collection of all interaction listeners. */ - #interactionListeners: {prefix:string, name:string|RegExp, callback:ODTextCommandInteractionCallback}[] = [] - /**Collection of all error listeners. */ - #errorListeners: ODTextCommandErrorCallback[] = [] - /**Set the soft limit for maximum amount of listeners. A warning will be shown when there are more listeners than this limit. */ - listenerLimit: number = 100 - - constructor(debug:ODDebugger, manager:ODClientManager){ - super(debug,"text command") - this.#debug = debug - this.manager = manager - } - - /*Check if a message is a registered command. */ - async #checkMessage(msg:discord.Message){ - if (this.manager.client.user && msg.author.id == this.manager.client.user.id) return false - - //filter commands for correct prefix - const validPrefixCommands: {cmd:ODTextCommand,newContent:string}[] = [] - await this.loopAll((cmd) => { - if (msg.content.startsWith(cmd.builder.prefix)) validPrefixCommands.push({ - cmd:cmd, - newContent:msg.content.substring(cmd.builder.prefix.length) - }) - }) - - //return when no command with prefix - if (validPrefixCommands.length == 0){ - this.#errorListeners.forEach((cb) => cb({ - type:"unknown_prefix", - msg:msg - })) - return false - } - - //filter commands for correct name - const validNameCommands: {cmd:ODTextCommand,newContent:string}[] = [] - validPrefixCommands.forEach((cmd) => { - if (cmd.newContent.startsWith(cmd.cmd.builder.name+" ") || cmd.newContent == cmd.cmd.builder.name) validNameCommands.push({ - cmd:cmd.cmd, - newContent:cmd.newContent.substring(cmd.cmd.builder.name.length+1) //+1 because of space after command name - }) - }) - - //return when no command with name - if (validNameCommands.length == 0){ - this.#errorListeners.forEach((cb) => cb({ - type:"unknown_command", - msg:msg - })) - return false - } - - //the final command - const command = validNameCommands[0] - const builder = command.cmd.builder - - //check additional options - if (typeof builder.allowBots != "undefined" && !builder.allowBots && msg.author.bot) return false - else if (typeof builder.dmPermission != "undefined" && !builder.dmPermission && msg.channel.type == discord.ChannelType.DM) return false - else if (typeof builder.guildPermission != "undefined" && !builder.guildPermission && msg.guild) return false - else if (typeof builder.allowedGuildIds != "undefined" && msg.guild && !builder.allowedGuildIds.includes(msg.guild.id)) return false - - //check all command options & return when incorrect - const options = await this.#checkOptions(command.cmd,command.newContent,msg) - if (!options.valid) return false - - //a command matched this message => emit event - this.#interactionListeners.forEach((listener) => { - if (typeof listener.prefix == "string" && (command.cmd.builder.prefix != listener.prefix)) return - if (typeof listener.name == "string" && (command.cmd.name.split(" ")[0] != listener.name)) return - else if (listener.name instanceof RegExp && !listener.name.test(command.cmd.name.split(" ")[0])) return - - //this is a valid listener - listener.callback(msg,command.cmd,options.data) - }) - return true - } - /**Check if all options of a command are correct. */ - async #checkOptions(cmd:ODTextCommand, newContent:string, msg:discord.Message){ - const options = cmd.builder.options - if (!options) return {valid:true,data:[]} - - let tempContent = newContent - let optionInvalid = false - const optionData: ODTextCommandInteractionOption[] = [] - - const optionError = (type:"invalid_option"|"missing_option", option:ODTextCommandBuilderOption, location:number, value?:string, reason?:ODTextCommandErrorInvalidOptionReason) => { - //ERROR INVALID - if (type == "invalid_option" && value && reason){ - this.#errorListeners.forEach((cb) => cb({ - type:"invalid_option", - msg:msg, - prefix:cmd.builder.prefix, - command:cmd, - name:cmd.builder.name, - option, - location, - value, - reason - })) - }else if (type == "missing_option"){ - this.#errorListeners.forEach((cb) => cb({ - type:"missing_option", - msg:msg, - prefix:cmd.builder.prefix, - command:cmd, - name:cmd.builder.name, - option, - location - })) - } - optionInvalid = true - } - - for (let location = 0;location < options.length;location++){ - const option = options[location] - if (optionInvalid) break - - //CHECK BOOLEAN - if (option.type == "boolean"){ - const falseValue = option.falseValue ?? "false" - const trueValue = option.trueValue ?? "true" - - if (tempContent.startsWith(falseValue+" ")){ - //FALSE VALUE - optionData.push({ - name:option.name, - type:"boolean", - value:false - }) - tempContent = tempContent.substring(falseValue.length+1) - - }else if (tempContent.startsWith(trueValue+" ")){ - //TRUE VALUE - optionData.push({ - name:option.name, - type:"boolean", - value:true - }) - tempContent = tempContent.substring(trueValue.length+1) - - }else if (option.required){ - //REQUIRED => ERROR IF NOT EXISTING - const invalidregex = /^[^ ]+/ - const invalidRes = invalidregex.exec(tempContent) - if (invalidRes) optionError("invalid_option",option,location,invalidRes[0],"boolean") - else optionError("missing_option",option,location) - } - - //CHECK NUMBER - }else if (option.type == "number"){ - const numRegex = /^[0-9\.\,]+/ - const res = numRegex.exec(tempContent) - if (res){ - const value = res[0].replace(/\,/g,".") - tempContent = tempContent.substring(value.length+1) - const numValue = Number(value) - - if (isNaN(numValue)){ - optionError("invalid_option",option,location,value,"number_invalid") - - }else if (typeof option.allowDecimal == "boolean" && !option.allowDecimal && (numValue % 1) !== 0){ - optionError("invalid_option",option,location,value,"number_decimal") - - }else if (typeof option.allowNegative == "boolean" && !option.allowNegative && numValue < 0){ - optionError("invalid_option",option,location,value,"number_negative") - - }else if (typeof option.allowPositive == "boolean" && !option.allowPositive && numValue > 0){ - optionError("invalid_option",option,location,value,"number_positive") - - }else if (typeof option.allowZero == "boolean" && !option.allowZero && numValue == 0){ - optionError("invalid_option",option,location,value,"number_zero") - - }else if (typeof option.max == "number" && numValue > option.max){ - optionError("invalid_option",option,location,value,"number_max") - - }else if (typeof option.min == "number" && numValue < option.min){ - optionError("invalid_option",option,location,value,"number_min") - - }else{ - //VALID NUMBER - optionData.push({ - name:option.name, - type:"number", - value:numValue - }) - } - }else if (option.required){ - //REQUIRED => ERROR IF NOT EXISTING - const invalidRegex = /^[^ ]+/ - const invalidRes = invalidRegex.exec(tempContent) - if (invalidRes) optionError("invalid_option",option,location,invalidRes[0],"number_invalid") - else optionError("missing_option",option,location) - } - //CHECK STRING - }else if (option.type == "string"){ - if (option.allowSpaces){ - //STRING WITH SPACES - const value = tempContent - tempContent = "" - - if (typeof option.minLength == "number" && value.length < option.minLength){ - optionError("invalid_option",option,location,value,"string_min_length") - - }else if (typeof option.maxLength == "number" && value.length > option.maxLength){ - optionError("invalid_option",option,location,value,"string_max_length") - - }else if (option.regex && !option.regex.test(value)){ - optionError("invalid_option",option,location,value,"string_regex") - - }else if (option.choices && !option.choices.includes(value)){ - optionError("invalid_option",option,location,value,"string_choice") - - }else if (option.required && value === ""){ - //REQUIRED => ERROR IF NOT EXISTING - optionError("missing_option",option,location) - - }else{ - //VALID STRING - optionData.push({ - name:option.name, - type:"string", - value - }) - } - }else{ - //STRING WITHOUT SPACES - const stringRegex = /^[^ ]+/ - const res = stringRegex.exec(tempContent) - if (res){ - const value = res[0] - tempContent = tempContent.substring(value.length+1) - - if (typeof option.minLength == "number" && value.length < option.minLength){ - optionError("invalid_option",option,location,value,"string_min_length") - - }else if (typeof option.maxLength == "number" && value.length > option.maxLength){ - optionError("invalid_option",option,location,value,"string_max_length") - - }else if (option.regex && !option.regex.test(value)){ - optionError("invalid_option",option,location,value,"string_regex") - - }else if (option.choices && !option.choices.includes(value)){ - optionError("invalid_option",option,location,value,"string_choice") - - }else{ - //VALID STRING - optionData.push({ - name:option.name, - type:"string", - value - }) - } - }else if (option.required){ - //REQUIRED => ERROR IF NOT EXISTING - optionError("missing_option",option,location) - } - } - //CHECK CHANNEL - }else if (option.type == "channel"){ - const channelRegex = /^(?:<#)?([0-9]+)>?/ - const res = channelRegex.exec(tempContent) - if (res){ - const value = res[0] - tempContent = tempContent.substring(value.length+1) - const channelId = res[1] - - if (!msg.guild){ - optionError("invalid_option",option,location,value,"not_in_guild") - }else{ - try{ - const channel = await msg.guild.channels.fetch(channelId) - if (!channel){ - optionError("invalid_option",option,location,value,"channel_not_found") - - }else if (option.channelTypes && !option.channelTypes.includes(channel.type)){ - optionError("invalid_option",option,location,value,"channel_type") - - }else{ - //VALID CHANNEL - optionData.push({ - name:option.name, - type:"channel", - value:channel - }) - } - }catch{ - optionError("invalid_option",option,location,value,"channel_not_found") - } - } - }else if (option.required){ - //REQUIRED => ERROR IF NOT EXISTING - const invalidRegex = /^[^ ]+/ - const invalidRes = invalidRegex.exec(tempContent) - if (invalidRes) optionError("invalid_option",option,location,invalidRes[0],"channel_not_found") - else optionError("missing_option",option,location) - } - //CHECK ROLE - }else if (option.type == "role"){ - const roleRegex = /^(?:<@&)?([0-9]+)>?/ - const res = roleRegex.exec(tempContent) - if (res){ - const value = res[0] - tempContent = tempContent.substring(value.length+1) - const roleId = res[1] - - if (!msg.guild){ - optionError("invalid_option",option,location,value,"not_in_guild") - }else{ - try{ - const role = await msg.guild.roles.fetch(roleId) - if (!role){ - optionError("invalid_option",option,location,value,"role_not_found") - }else{ - //VALID ROLE - optionData.push({ - name:option.name, - type:"role", - value:role - }) - } - }catch{ - optionError("invalid_option",option,location,value,"role_not_found") - } - } - }else if (option.required){ - //REQUIRED => ERROR IF NOT EXISTING - const invalidRegex = /^[^ ]+/ - const invalidRes = invalidRegex.exec(tempContent) - if (invalidRes) optionError("invalid_option",option,location,invalidRes[0],"role_not_found") - else optionError("missing_option",option,location) - } - //CHECK GUILD MEMBER - }else if (option.type == "guildmember"){ - const memberRegex = /^(?:<@)?([0-9]+)>?/ - const res = memberRegex.exec(tempContent) - if (res){ - const value = res[0] - tempContent = tempContent.substring(value.length+1) - const memberId = res[1] - - if (!msg.guild){ - optionError("invalid_option",option,location,value,"not_in_guild") - }else{ - try{ - const member = await msg.guild.members.fetch(memberId) - if (!member){ - optionError("invalid_option",option,location,value,"member_not_found") - }else{ - //VALID GUILD MEMBER - optionData.push({ - name:option.name, - type:"guildmember", - value:member - }) - } - }catch{ - optionError("invalid_option",option,location,value,"member_not_found") - } - } - }else if (option.required){ - //REQUIRED => ERROR IF NOT EXISTING - const invalidRegex = /^[^ ]+/ - const invalidRes = invalidRegex.exec(tempContent) - if (invalidRes) optionError("invalid_option",option,location,invalidRes[0],"member_not_found") - else optionError("missing_option",option,location) - } - //CHECK USER - }else if (option.type == "user"){ - const userRegex = /^(?:<@)?([0-9]+)>?/ - const res = userRegex.exec(tempContent) - if (res){ - const value = res[0] - tempContent = tempContent.substring(value.length+1) - const userId = res[1] - - try{ - const user = await this.manager.client.users.fetch(userId) - if (!user){ - optionError("invalid_option",option,location,value,"user_not_found") - }else{ - //VALID USER - optionData.push({ - name:option.name, - type:"user", - value:user - }) - } - }catch{ - optionError("invalid_option",option,location,value,"user_not_found") - } - }else if (option.required){ - //REQUIRED => ERROR IF NOT EXISTING - const invalidRegex = /^[^ ]+/ - const invalidRes = invalidRegex.exec(tempContent) - if (invalidRes) optionError("invalid_option",option,location,invalidRes[0],"user_not_found") - else optionError("missing_option",option,location) - } - //CHECK MENTIONABLE - }else if (option.type == "mentionable"){ - const mentionableRegex = /^<(@&?)([0-9]+)>/ - const res = mentionableRegex.exec(tempContent) - if (res){ - const value = res[0] - const type = (res[1] == "@&") ? "role" : "user" - tempContent = tempContent.substring(value.length+1) - const mentionableId = res[2] - - if (!msg.guild){ - optionError("invalid_option",option,location,value,"not_in_guild") - }else if (type == "role"){ - try { - const role = await msg.guild.roles.fetch(mentionableId) - if (!role){ - optionError("invalid_option",option,location,value,"mentionable_not_found") - }else{ - //VALID ROLE - optionData.push({ - name:option.name, - type:"mentionable", - value:role - }) - } - }catch{ - optionError("invalid_option",option,location,value,"mentionable_not_found") - } - }else if (type == "user"){ - try{ - const user = await this.manager.client.users.fetch(mentionableId) - if (!user){ - optionError("invalid_option",option,location,value,"mentionable_not_found") - }else{ - //VALID USER - optionData.push({ - name:option.name, - type:"mentionable", - value:user - }) - } - }catch{ - optionError("invalid_option",option,location,value,"mentionable_not_found") - } - } - }else if (option.required){ - //REQUIRED => ERROR IF NOT EXISTING - const invalidRegex = /^[^ ]+/ - const invalidRes = invalidRegex.exec(tempContent) - if (invalidRes) optionError("invalid_option",option,location,invalidRes[0],"mentionable_not_found") - else optionError("missing_option",option,location) - } - } - } - return {valid:!optionInvalid,data:optionData} - } - /**Start listening to the discord.js client `messageCreate` event. */ - startListeningToInteractions(){ - this.manager.client.on("messageCreate",(msg) => { - //return when not in main server or DM - if (!this.manager.mainServer || (msg.guild && msg.guild.id != this.manager.mainServer.id)) return - this.#checkMessage(msg) - }) - } - /**Check if optional values are only present at the end of the command. */ - #checkBuilderOptions(builder:ODTextCommandBuilder): {valid:boolean,reason:"required_after_optional"|"allowspaces_not_last"|null} { - let optionalVisited = false - let valid = true - let reason: "required_after_optional"|"allowspaces_not_last"|null = null - if (!builder.options) return {valid:true,reason:null} - builder.options.forEach((opt,index,list) => { - if (!opt.required) optionalVisited = true - if (optionalVisited && opt.required){ - valid = false - reason = "required_after_optional" - } - - if (opt.type == "string" && opt.allowSpaces && ((index+1) != list.length)){ - valid = false - reason = "allowspaces_not_last" - } - }) - - return {valid,reason} - } - /**Callback on interaction from one of the registered text commands */ - onInteraction(commandPrefix:string,commandName:string|RegExp, callback:ODTextCommandInteractionCallback){ - this.#interactionListeners.push({ - prefix:commandPrefix, - name:commandName, - callback - }) - - if (this.#interactionListeners.length > this.listenerLimit){ - this.#debug.console.log(new ODConsoleWarningMessage("Possible text command interaction memory leak detected!",[ - {key:"listeners",value:this.#interactionListeners.length.toString()} - ])) - } - } - /**Callback on error from all the registered text commands */ - onError(callback:ODTextCommandErrorCallback){ - this.#errorListeners.push(callback) - } - - add(data:ODTextCommand, overwrite?:boolean): boolean { - const checkResult = this.#checkBuilderOptions(data.builder) - if (!checkResult.valid && checkResult.reason == "required_after_optional") throw new ODSystemError("Invalid text command '"+data.id.value+"' => optional options are only allowed at the end of a command!") - else if (!checkResult.valid && checkResult.reason == "allowspaces_not_last") throw new ODSystemError("Invalid text command '"+data.id.value+"' => string option with 'allowSpaces' is only allowed at the end of a command!") - else return super.add(data,overwrite) - } -} - -/**## ODContextMenuUniversalMenu `interface` - * A universal template for a context menu. - * - * Why universal? Both **existing context menus** & **unregistered templates** can be converted to this type. - */ -export interface ODContextMenuUniversalMenu { - /**The type of this context menu. (required => `Message`|`User`) */ - type:discord.ApplicationCommandType.Message|discord.ApplicationCommandType.User, - /**The name of this context menu. */ - name:string, - /**All localized names of this context menu. */ - nameLocalizations:readonly ODSlashCommandUniversalTranslation[], - /**The id of the guild this context menu is registered in. */ - guildId:string|null, - /**Is this context menu for 18+ users only? */ - nsfw:boolean, - /**A bitfield of the user permissions required to use this context menu. */ - defaultMemberPermissions:bigint, - /**Is this context menu available in DM? */ - dmPermission:boolean, - /**A list of contexts where you can install this context menu. */ - integrationTypes:readonly discord.ApplicationIntegrationType[], - /**A list of contexts where you can use this context menu. */ - contexts:readonly discord.InteractionContextType[] -} - -/**## ODContextMenuBuilderMessage `interface` - * The builder for message context menus. - */ -export interface ODContextMenuBuilderMessage extends discord.MessageApplicationCommandData { - /**This field is required in Open Ticket for future compatibility. */ - integrationTypes:discord.ApplicationIntegrationType[], - /**This field is required in Open Ticket for future compatibility. */ - contexts:discord.InteractionContextType[] -} - -/**## ODContextMenuBuilderUser `interface` - * The builder for user context menus. - */ -export interface ODContextMenuBuilderUser extends discord.UserApplicationCommandData { - /**This field is required in Open Ticket for future compatibility. */ - integrationTypes:discord.ApplicationIntegrationType[], - /**This field is required in Open Ticket for future compatibility. */ - contexts:discord.InteractionContextType[] -} - -/**## ODContextMenuBuilderUser `interface` - * The builder for context menus. - */ -export type ODContextMenuBuilder = (ODContextMenuBuilderMessage|ODContextMenuBuilderUser) - -/**## ODContextMenuComparator `class` - * A utility class to compare existing context menu's with newly registered ones. - */ -export class ODContextMenuComparator { - /**Convert a `ODContextMenuBuilder` to a universal Open Ticket context menu object for comparison. */ - convertBuilder(builder:ODContextMenuBuilder,guildId:string|null): ODContextMenuUniversalMenu|null { - if (builder.type != discord.ApplicationCommandType.Message && builder.type != discord.ApplicationCommandType.User) return null - const nameLoc = builder.nameLocalizations ?? {} - - return { - type:builder.type, - name:builder.name, - nameLocalizations:Object.keys(nameLoc).map((key) => {return {language:key as `${discord.Locale}`,value:nameLoc[key]}}), - guildId:guildId, - nsfw:builder.nsfw ?? false, - defaultMemberPermissions:discord.PermissionsBitField.resolve(builder.defaultMemberPermissions ?? ["ViewChannel"]), - dmPermission:(builder.contexts && builder.contexts.includes(discord.InteractionContextType.BotDM)) ?? false, - integrationTypes:builder.integrationTypes ?? [discord.ApplicationIntegrationType.GuildInstall], - contexts:builder.contexts ?? [] - } - } - /**Convert a `discord.ApplicationCommand` to a universal Open Ticket context menu object for comparison. */ - convertMenu(cmd:discord.ApplicationCommand): ODContextMenuUniversalMenu|null { - if (cmd.type != discord.ApplicationCommandType.Message && cmd.type != discord.ApplicationCommandType.User) return null - const nameLoc = cmd.nameLocalizations ?? {} - - return { - type:cmd.type, - name:cmd.name, - nameLocalizations:Object.keys(nameLoc).map((key) => {return {language:key as `${discord.Locale}`,value:nameLoc[key]}}), - guildId:cmd.guildId, - nsfw:cmd.nsfw, - defaultMemberPermissions:discord.PermissionsBitField.resolve(cmd.defaultMemberPermissions ?? ["ViewChannel"]), - dmPermission:(cmd.contexts && cmd.contexts.includes(discord.InteractionContextType.BotDM)) ? true : false, - integrationTypes:cmd.integrationTypes ?? [discord.ApplicationIntegrationType.GuildInstall], - contexts:cmd.contexts ?? [] - } - } - /**Returns `true` when the 2 context menus are the same. */ - compare(ctxA:ODContextMenuUniversalMenu,ctxB:ODContextMenuUniversalMenu): boolean { - if (ctxA.name != ctxB.name) return false - if (ctxA.type != ctxB.type) return false - if (ctxA.nsfw != ctxB.nsfw) return false - if (ctxA.guildId != ctxB.guildId) return false - if (ctxA.dmPermission != ctxB.dmPermission) return false - if (ctxA.defaultMemberPermissions != ctxB.defaultMemberPermissions) return false - - //nameLocalizations - if (ctxA.nameLocalizations.length != ctxB.nameLocalizations.length) return false - if (!ctxA.nameLocalizations.every((nameA) => { - const nameB = ctxB.nameLocalizations.find((nameB) => nameB.language == nameA.language) - if (!nameB || nameA.value != nameB.value) return false - else return true - })) return false - - //contexts - if (ctxA.contexts.length != ctxB.contexts.length) return false - if (!ctxA.contexts.every((contextA) => { - return ctxB.contexts.includes(contextA) - })) return false - - //integrationTypes - if (ctxA.integrationTypes.length != ctxB.integrationTypes.length) return false - if (!ctxA.integrationTypes.every((integrationA) => { - return ctxB.integrationTypes.includes(integrationA) - })) return false - - return true - } -} - -/**## ODContextMenuInteractionCallback `type` - * Callback for the context menu interaction listener. - */ -export type ODContextMenuInteractionCallback = (interaction:discord.ContextMenuCommandInteraction,cmd:ODContextMenu) => void - -/**## ODContextMenuRegisteredResult `type` - * The result which will be returned when getting all (un)registered user context menu's from the manager. - */ -export type ODContextMenuRegisteredResult = { - /**A list of all registered context menus. */ - registered:{ - /**The instance (`ODContextMenu`) from this context menu. */ - instance:ODContextMenu, - /**The universal object/template/builder of this context menu. */ - menu:ODContextMenuUniversalMenu, - /**Does this context menu require an update? */ - requiresUpdate:boolean - }[], - /**A list of all unregistered context menus. */ - unregistered:{ - /**The instance (`ODContextMenu`) from this context menu. */ - instance:ODContextMenu, - /**The universal object/template/builder of this context menu. */ - menu:null, - /**Does this context menu require an update? */ - requiresUpdate:true - }[], - /**A list of all unused context menus (not found in `ODContextMenuManager`). */ - unused:{ - /**The instance (`ODContextMenu`) from this context menu. */ - instance:null, - /**The universal object/template/builder of this context menu. */ - menu:ODContextMenuUniversalMenu, - /**Does this context menu require an update? */ - requiresUpdate:false - }[] -} - -/**## ODContextMenuManager `class` - * This is an Open Ticket client context menu manager. - * - * It's responsible for managing all the context interactions from the client. - * - * Here, you can add & remove context interactions & the bot will do the (de)registering. - */ -export class ODContextMenuManager extends ODManager { - /**Alias to Open Ticket debugger. */ - #debug: ODDebugger - - /**Refrerence to discord.js client. */ - manager: ODClientManager - /**Discord.js application commands manager. */ - commandManager: discord.ApplicationCommandManager|null - /**Collection of all interaction listeners. */ - #interactionListeners: {name:string|RegExp, callback:ODContextMenuInteractionCallback}[] = [] - /**Set the soft limit for maximum amount of listeners. A warning will be shown when there are more listeners than this limit. */ - listenerLimit: number = 100 - /**A utility class used to compare 2 context menus with each other. */ - comparator: ODContextMenuComparator = new ODContextMenuComparator() - - constructor(debug:ODDebugger, manager:ODClientManager){ - super(debug,"context menu") - this.#debug = debug - this.manager = manager - this.commandManager = (manager.client.application) ? manager.client.application.commands : null - } - - /**Get all registered & unregistered message context menu commands. */ - async getAllRegisteredMenus(guildId?:string): Promise { - if (!this.commandManager) throw new ODSystemError("Couldn't get client application to register message context menus!") - - const menus = (await this.commandManager.fetch({guildId})).toJSON() - const registered: {instance:ODContextMenu, menu:ODContextMenuUniversalMenu, requiresUpdate:boolean}[] = [] - const unregistered: {instance:ODContextMenu, menu:null, requiresUpdate:true}[] = [] - const unused: {instance:null, menu:ODContextMenuUniversalMenu, requiresUpdate:false}[] = [] - - await this.loopAll((instance) => { - if (guildId && instance.guildId != guildId) return - - const index = menus.findIndex((menu) => menu.name == instance.name) - const menu = menus[index] - menus.splice(index,1) - if (menu){ - //menu is registered (and may need to be updated) - const universalBuilder = this.comparator.convertBuilder(instance.builder,instance.guildId) - const universalMenu = this.comparator.convertMenu(menu) - - //menu is not of the type 'message'|'user' - if (!universalBuilder || !universalMenu) return - - const didChange = !this.comparator.compare(universalBuilder,universalMenu) - const requiresUpdate = didChange || (instance.requiresUpdate ? instance.requiresUpdate(universalMenu) : false) - registered.push({instance,menu:universalMenu,requiresUpdate}) - - //menu is not registered - }else unregistered.push({instance,menu:null,requiresUpdate:true}) - }) - - menus.forEach((menu) => { - //menu does not exist in the manager (only append to unused when type == 'message'|'user') - const universalCmd = this.comparator.convertMenu(menu) - if (!universalCmd) return - unused.push({instance:null,menu:universalCmd,requiresUpdate:false}) - }) - - return {registered,unregistered,unused} - } - /**Create all context menus that are not registered yet.*/ - async createNewMenus(instances:ODContextMenu[],progress?:ODManualProgressBar){ - if (!this.manager.ready) throw new ODSystemError("Client isn't ready yet! Unable to register context menus!") - if (instances.length > 0 && progress){ - progress.max = instances.length - progress.start() - } - - for (const instance of instances){ - await this.createMenu(instance) - this.#debug.debug("Created new context menu",[ - {key:"id",value:instance.id.value}, - {key:"name",value:instance.name}, - {key:"type",value:(instance.builder.type == discord.ApplicationCommandType.Message) ? "message-context" : "user-context"} - ]) - if (progress) progress.increase(1) - } - } - /**Update all context menus that are already registered. */ - async updateExistingMenus(instances:ODContextMenu[],progress?:ODManualProgressBar){ - if (!this.manager.ready) throw new ODSystemError("Client isn't ready yet! Unable to register context menus!") - if (instances.length > 0 && progress){ - progress.max = instances.length - progress.start() - } - - for (const instance of instances){ - await this.createMenu(instance) - this.#debug.debug("Updated existing context menu",[ - {key:"id",value:instance.id.value}, - {key:"name",value:instance.name}, - {key:"type",value:(instance.builder.type == discord.ApplicationCommandType.Message) ? "message-context" : "user-context"} - ]) - if (progress) progress.increase(1) - } - } - /**Remove all context menus that are registered but unused by Open Ticket. */ - async removeUnusedMenus(instances:ODContextMenuUniversalMenu[],guildId?:string,progress?:ODManualProgressBar){ - if (!this.manager.ready) throw new ODSystemError("Client isn't ready yet! Unable to register context menus!") - if (!this.commandManager) throw new ODSystemError("Couldn't get client application to register context menus!") - if (instances.length > 0 && progress){ - progress.max = instances.length - progress.start() - } - - const menus = await this.commandManager.fetch({guildId}) - - for (const instance of instances){ - const menu = menus.find((menu) => menu.name == instance.name) - if (menu){ - try { - await menu.delete() - this.#debug.debug("Removed existing context menu",[ - {key:"name",value:menu.name}, - {key:"guildId",value:guildId ?? "/"}, - {key:"type",value:(instance.type == discord.ApplicationCommandType.Message) ? "message-context" : "user-context"} - ]) - }catch(err){ - process.emit("uncaughtException",err) - throw new ODSystemError("Failed to delete context menu '"+menu.name+"'!") - } - } - if (progress) progress.increase(1) - } - } - /**Create a context menu. **(SYSTEM ONLY)** => Use `ODContextMenuManager` for registering context menu's the default way! */ - async createMenu(menu:ODContextMenu){ - if (!this.commandManager) throw new ODSystemError("Couldn't get client application to register context menu's!") - try { - await this.commandManager.create(menu.builder,(menu.guildId ?? undefined)) - }catch(err){ - process.emit("uncaughtException",err) - throw new ODSystemError("Failed to register context menu '"+menu.name+"'!") - } - } - /**Start listening to the discord.js client `interactionCreate` event. */ - startListeningToInteractions(){ - this.manager.client.on("interactionCreate",(interaction) => { - //return when not in main server or DM - if (!this.manager.mainServer || (interaction.guild && interaction.guild.id != this.manager.mainServer.id)) return - - if (!interaction.isContextMenuCommand()) return - const menu = this.getFiltered((menu) => menu.name == interaction.commandName)[0] - if (!menu) return - - this.#interactionListeners.forEach((listener) => { - if (typeof listener.name == "string" && (interaction.commandName != listener.name)) return - else if (listener.name instanceof RegExp && !listener.name.test(interaction.commandName)) return - - //this is a valid listener - listener.callback(interaction,menu) - }) - }) - } - /**Callback on interaction from one or multiple context menu's. */ - onInteraction(menuName:string|RegExp, callback:ODContextMenuInteractionCallback){ - this.#interactionListeners.push({ - name:menuName, - callback - }) - - if (this.#interactionListeners.length > this.listenerLimit){ - this.#debug.console.log("Possible context menu interaction memory leak detected!","warning",[ - {key:"listeners",value:this.#interactionListeners.length.toString()} - ]) - } - } -} - -/**## ODContextMenuUpdateFunction `type` - * The function responsible for updating context menu's when they already exist. - */ -export type ODContextMenuUpdateFunction = (menu:ODContextMenuUniversalMenu) => boolean - -/**## ODContextMenu `class` - * This is an Open Ticket context menu. - * - * When registered, you can listen for this context menu using the `ODContextResponder`. The advantages of using this class for creating a context menu are: - * - automatic registration in discord.js - * - error reporting to the user when the bot fails to respond - * - plugins can extend this context menu - * - the bot won't re-register the context menu when it already exists (except when requested)! - * - * And more! - */ -export class ODContextMenu extends ODManagerData { - /**The discord.js builder for this context menu. */ - builder: ODContextMenuBuilder - /**The id of the guild this context menu is for. `null` when not set. */ - guildId: string|null - /**Function to check if the context menu requires to be updated (when it already exists). */ - requiresUpdate: ODContextMenuUpdateFunction|null = null - - constructor(id:ODValidId, builder:ODContextMenuBuilder, requiresUpdate?:ODContextMenuUpdateFunction, guildId?:string){ - super(id) - if (builder.type != discord.ApplicationCommandType.Message && builder.type != discord.ApplicationCommandType.User) throw new ODSystemError("ApplicationCommandData is required to be the 'Message'|'User' type!") - - this.builder = builder - this.guildId = guildId ?? null - this.requiresUpdate = requiresUpdate ?? null - } - - /**The name of this context menu. */ - get name(): string { - return this.builder.name - } - set name(name:string){ - this.builder.name = name - } -} - -/**## ODAutocompleteInteractionCallback `type` - * Callback for the autocomplete interaction listener. - */ -export type ODAutocompleteInteractionCallback = (interaction:discord.AutocompleteInteraction) => void - -/**## ODAutocompleteManager `class` - * This is an Open Ticket client autocomplete interaction manager. - * - * It's responsible for managing all the autocomplete interactions from the client. - */ -export class ODAutocompleteManager { - /**Alias to Open Ticket debugger. */ - #debug: ODDebugger - - /**Refrerence to discord.js client. */ - manager: ODClientManager - /**Discord.js application commands manager. */ - commandManager: discord.ApplicationCommandManager|null - /**Collection of all interaction listeners. */ - #interactionListeners: {cmdName:string|RegExp, optName:string|RegExp, callback:ODAutocompleteInteractionCallback}[] = [] - /**Set the soft limit for maximum amount of listeners. A warning will be shown when there are more listeners than this limit. */ - listenerLimit: number = 100 - - constructor(debug:ODDebugger, manager:ODClientManager){ - this.#debug = debug - this.manager = manager - this.commandManager = (manager.client.application) ? manager.client.application.commands : null - } - - /**Start listening to the discord.js client `interactionCreate` event. */ - startListeningToInteractions(){ - this.manager.client.on("interactionCreate",(interaction) => { - //return when not in main server or DM - if (!this.manager.mainServer || (interaction.guild && interaction.guild.id != this.manager.mainServer.id)) return - - if (!interaction.isAutocomplete()) return - this.#interactionListeners.forEach((listener) => { - - if (typeof listener.cmdName == "string" && (interaction.commandName != listener.cmdName)) return - else if (listener.cmdName instanceof RegExp && !listener.cmdName.test(interaction.commandName)) return - if (typeof listener.optName == "string" && (interaction.options.getFocused(true).name != listener.optName)) return - else if (listener.optName instanceof RegExp && !listener.optName.test(interaction.options.getFocused(true).name)) return - - //this is a valid listener - listener.callback(interaction) - }) - }) - } - /**Callback on interaction from one or multiple autocompletes. */ - onInteraction(cmdName:string|RegExp,optName:string|RegExp,callback:ODAutocompleteInteractionCallback){ - this.#interactionListeners.push({ - cmdName,optName,callback - }) - - if (this.#interactionListeners.length > this.listenerLimit){ - this.#debug.console.log("Possible autocomplete interaction memory leak detected!","warning",[ - {key:"listeners",value:this.#interactionListeners.length.toString()} - ]) - } - } -} \ No newline at end of file diff --git a/src/core/api/modules/code.ts b/src/core/api/modules/code.ts deleted file mode 100644 index a655b46..0000000 --- a/src/core/api/modules/code.ts +++ /dev/null @@ -1,58 +0,0 @@ -/////////////////////////////////////// -//CODE MODULE -/////////////////////////////////////// -import { ODId, ODManager, ODManagerData, ODValidId } from "./base" -import { ODDebugger } from "./console" - - -/**## ODCode `class` - * This is an Open Ticket code runner. - * - * Using this, you're able to execute a function just before the startup screen. (90% of the code is already loaded) - * You can also specify a priority to change the execution order. - * In Open Ticket, this is used for the following processes: - * - Autoclose/delete - * - Database syncronisation (with tickets, stats & used options) - * - Panel auto-update - * - Database Garbage Collection (removing tickets that don't exist anymore) - * - And more! - */ -export class ODCode extends ODManagerData { - /**The priority of this code */ - priority: number - /**The main function of this code */ - func: () => void|Promise - - constructor(id:ODValidId, priority:number, func:() => void|Promise){ - super(id) - this.priority = priority - this.func = func - } -} - -/**## ODCodeManager `class` - * This is an Open Ticket code manager. - * - * It manages & executes `ODCode`'s in the correct order. - * - * Use this to register a function/code which executes just before the startup screen. (90% is already loaded) - */ -export class ODCodeManager extends ODManager { - constructor(debug:ODDebugger){ - super(debug,"code") - } - - /**Execute all `ODCode` functions in order of their priority (high to low). */ - async execute(){ - const derefArray = [...this.getAll()] - const workers = derefArray.sort((a,b) => b.priority-a.priority) - - for (const worker of workers){ - try { - await worker.func() - }catch(err){ - process.emit("uncaughtException",err) - } - } - } -} \ No newline at end of file diff --git a/src/core/api/modules/config.ts b/src/core/api/modules/config.ts deleted file mode 100644 index c59b11a..0000000 --- a/src/core/api/modules/config.ts +++ /dev/null @@ -1,159 +0,0 @@ -/////////////////////////////////////// -//CONFIG MODULE -/////////////////////////////////////// -import { ODId, ODManager, ODManagerData, ODPromiseVoid, ODSystemError, ODValidId } from "./base" -import nodepath from "path" -import { ODDebugger } from "./console" -import fs from "fs" -import * as fjs from "formatted-json-stringify" - -/**## ODConfigManager `class` - * This is an Open Ticket config manager. - * - * It manages all config files in the bot and allows plugins to access config files from Open Ticket & other plugins! - * - * You can use this class to get/change/add a config file (`ODConfig`) in your plugin! - */ -export class ODConfigManager extends ODManager { - /**Alias to Open Ticket debugger. */ - #debug: ODDebugger - - constructor(debug:ODDebugger){ - super(debug,"config") - this.#debug = debug - } - add(data:ODConfig|ODConfig[],overwrite?:boolean): boolean { - if (Array.isArray(data)) data.forEach((d) => d.useDebug(this.#debug)) - else data.useDebug(this.#debug) - return super.add(data,overwrite) - } - /**Init all config files. */ - async init(){ - for (const config of this.getAll()){ - try{ - await config.init() - }catch(err){ - process.emit("uncaughtException",new ODSystemError(err)) - } - } - } -} - -/**## ODConfig `class` - * This is an Open Ticket config helper. - * This class doesn't do anything at all, it just gives a template & basic methods for a config. Use `ODJsonConfig` instead! - * - * You can use this class if you want to create your own config implementation (e.g. `yml`, `xml`,...)! - */ -export class ODConfig extends ODManagerData { - /**The name of the file with extension. */ - file: string = "" - /**The path to the file relative to the main directory. */ - path: string = "" - /**An object/array of the entire config file! Variables inside it can be edited while the bot is running! */ - data: any - /**Is this config already initiated? */ - initiated: boolean = false - /**An array of listeners to run when the config gets reloaded. These are not executed on the initial loading. */ - protected reloadListeners: Function[] = [] - /**Alias to Open Ticket debugger. */ - protected debug: ODDebugger|null = null - - constructor(id:ODValidId, data:any){ - super(id) - this.data = data - } - - /**Use the Open Ticket debugger for logs. */ - useDebug(debug:ODDebugger|null){ - this.debug = debug - } - /**Init the config. */ - init(): ODPromiseVoid { - this.initiated = true - if (this.debug) this.debug.debug("Initiated config '"+this.file+"' in ODConfigManager.",[{key:"id",value:this.id.value}]) - //please implement this feature in your own config extension & extend this function. - } - /**Reload the config. Be aware that this doesn't update the config data everywhere in the bot! */ - reload(): ODPromiseVoid { - if (this.debug) this.debug.debug("Reloaded config '"+this.file+"' in ODConfigManager.",[{key:"id",value:this.id.value}]) - //please implement this feature in your own config extension & extend this function. - } - /**Save the edited config to the filesystem. This is used by the Interactive Setup CLI. It's not recommended to use this while the bot is running. */ - save(): ODPromiseVoid { - if (this.debug) this.debug.debug("Saved config '"+this.file+"' in ODConfigManager.",[{key:"id",value:this.id.value}]) - //please implement this feature in your own config extension & extend this function. - } - /**Listen for a reload of this JSON file! */ - onReload(cb:Function){ - this.reloadListeners.push(cb) - } - /**Remove all reload listeners. Not recommended! */ - removeAllReloadListeners(){ - this.reloadListeners = [] - } -} - -/**## ODJsonConfig `class` - * This is an Open Ticket JSON config. - * You can use this class to get & edit variables from the config files or to create your own JSON config! - * @example - * //create a config from: ./config/test.json with the id "some-config" - * const config = new api.ODJsonConfig("some-config","test.json") - * - * //create a config with custom dir: ./plugins/testplugin/test.json - * const config = new api.ODJsonConfig("plugin-config","test.json","./plugins/testplugin/") - */ -export class ODJsonConfig extends ODConfig { - formatter: fjs.custom.BaseFormatter - - constructor(id:ODValidId, file:string, customPath?:string, formatter?:fjs.custom.BaseFormatter){ - super(id,{}) - this.file = (file.endsWith(".json")) ? file : file+".json" - this.path = customPath ? nodepath.join("./",customPath,this.file) : nodepath.join("./config/",this.file) - this.formatter = formatter ?? new fjs.DefaultFormatter(null,true," ") - } - - /**Init the config. */ - init(): ODPromiseVoid { - if (!fs.existsSync(this.path)) throw new ODSystemError("Unable to parse config \""+nodepath.join("./",this.path)+"\", the file doesn't exist!") - try{ - this.data = JSON.parse(fs.readFileSync(this.path).toString()) - super.init() - }catch(err){ - process.emit("uncaughtException",err) - throw new ODSystemError("Unable to parse config \""+nodepath.join("./",this.path)+"\"!") - } - } - /**Reload the config. Be aware that this doesn't update the config data everywhere in the bot! */ - reload(){ - if (!this.initiated) throw new ODSystemError("Unable to reload config \""+nodepath.join("./",this.path)+"\", the file hasn't been initiated yet!") - if (!fs.existsSync(this.path)) throw new ODSystemError("Unable to reload config \""+nodepath.join("./",this.path)+"\", the file doesn't exist!") - try{ - this.data = JSON.parse(fs.readFileSync(this.path).toString()) - super.reload() - this.reloadListeners.forEach((cb) => { - try{ - cb() - }catch(err){ - process.emit("uncaughtException",err) - } - }) - }catch(err){ - process.emit("uncaughtException",err) - throw new ODSystemError("Unable to reload config \""+nodepath.join("./",this.path)+"\"!") - } - } - /**Save the edited config to the filesystem. This is used by the Interactive Setup CLI. It's not recommended to use this while the bot is running. */ - save(): ODPromiseVoid { - if (!this.initiated) throw new ODSystemError("Unable to save config \""+nodepath.join("./",this.path)+"\", the file hasn't been initiated yet!") - try{ - const contents = this.formatter.stringify(this.data) - fs.writeFileSync(this.path,contents) - super.save() - }catch(err){ - process.emit("uncaughtException",err) - throw new ODSystemError("Unable to save config \""+nodepath.join("./",this.path)+"\"!") - } - } -} \ No newline at end of file diff --git a/src/core/api/modules/console.ts b/src/core/api/modules/console.ts deleted file mode 100644 index f34399c..0000000 --- a/src/core/api/modules/console.ts +++ /dev/null @@ -1,665 +0,0 @@ -/////////////////////////////////////// -//CONSOLE MODULE -/////////////////////////////////////// -import { ODHTTPGetRequest, ODVersion, ODSystemError, ODPluginError, ODManager, ODManagerData, ODValidId } from "./base" -import { ODMain } from "../main" -import nodepath from "path" -import fs from "fs" -import ansis from "ansis" - -/**## ODValidConsoleColor `type` - * This is a collection of all the supported console colors within Open Ticket. - */ -export type ODValidConsoleColor = "white"|"red"|"yellow"|"green"|"blue"|"gray"|"cyan"|"magenta" - -/**## ODConsoleMessageParam `type` - * This interface contains all data required for a console log parameter within Open Ticket. - */ -export interface ODConsoleMessageParam { - /**The key of this parameter. */ - key:string, - /**The value of this parameter. */ - value:string, - /**When enabled, this parameter will only be shown in the debug file. */ - hidden?:boolean -} - -/**## ODConsoleMessage `class` - * This is an Open Ticket console message. - * - * It is used to create beautiful & styled logs in the console with a prefix, message & parameters. - * It also has full color support using `ansis` and parameters are parsed for you! - */ -export class ODConsoleMessage { - /**The main message sent in the console */ - message: string - /**An array of all the parameters in this message */ - params: ODConsoleMessageParam[] - /**The prefix of this message (!uppercase recommended!) */ - prefix: string - /**The color of the prefix of this message */ - color: ODValidConsoleColor - - constructor(message:string, prefix:string, color:ODValidConsoleColor, params?:ODConsoleMessageParam[]){ - this.message = message - this.params = params ? params : [] - this.prefix = prefix - - if (["white","red","yellow","green","blue","gray","cyan","magenta"].includes(color)){ - this.color = color - }else{ - this.color = "white" - } - } - /**Render this message to the console using `console.log`! Returns `false` when something went wrong. */ - render(){ - try { - const prefixcolor = ansis[this.color] - - const paramsstring = " "+this.createParamsString("gray") - const message = prefixcolor("["+this.prefix+"] ")+this.message - - console.log(message+paramsstring) - return true - }catch{ - return false - } - } - /**Create a more-detailed, non-colored version of this message to store it in the `otdebug.txt` file! */ - toDebugString(){ - const pstrings: string[] = [] - this.params.forEach((p) => { - pstrings.push(p.key+": "+p.value) - }) - const pstring = (pstrings.length > 0) ? " ("+pstrings.join(", ")+")" : "" - const date = new Date() - const dstring = `${date.getDate()}/${date.getMonth()+1}/${date.getFullYear()} ${date.getHours()}:${date.getMinutes()}:${date.getSeconds()}` - return `[${dstring} ${this.prefix}] ${this.message}${pstring}` - } - /**Render the parameters of this message in a specific color. */ - createParamsString(color:ODValidConsoleColor){ - let validcolor: ODValidConsoleColor = "white" - if (["white","red","yellow","green","blue","gray","cyan","magenta"].includes(color)){ - validcolor = color - } - - const pstrings: string[] = [] - this.params.forEach((p) => { - if (!p.hidden) pstrings.push(p.key+": "+p.value) - }) - - return (pstrings.length > 0) ? ansis[validcolor](" ("+pstrings.join(", ")+")") : "" - } - /**Set the message */ - setMessage(message:string){ - this.message = message - return this - } - /**Set the params */ - setParams(params:ODConsoleMessageParam[]){ - this.params = params - return this - } - /**Set the prefix */ - setPrefix(prefix:string){ - this.prefix = prefix - return this - } - /**Set the prefix color */ - setColor(color:ODValidConsoleColor){ - if (["white","red","yellow","green","blue","gray","cyan","magenta"].includes(color)){ - this.color = color - }else{ - this.color = "white" - } - return this - } -} - -/**## ODConsoleInfoMessage `class` - * This is an Open Ticket console info message. - * - * It is the same as a normal `ODConsoleMessage`, but it has a predefined prefix & color scheme for the "INFO" messages! - */ -export class ODConsoleInfoMessage extends ODConsoleMessage { - constructor(message:string,params?:ODConsoleMessageParam[]){ - super(message,"INFO","blue",params) - } -} - -/**## ODConsoleSystemMessage `class` - * This is an Open Ticket console system message. - * - * It is the same as a normal `ODConsoleMessage`, but it has a predefined prefix & color scheme for the "SYSTEM" messages! - */ -export class ODConsoleSystemMessage extends ODConsoleMessage { - constructor(message:string,params?:ODConsoleMessageParam[]){ - super(message,"SYSTEM","green",params) - } -} - -/**## ODConsolePluginMessage `class` - * This is an Open Ticket console plugin message. - * - * It is the same as a normal `ODConsoleMessage`, but it has a predefined prefix & color scheme for the "PLUGIN" messages! - */ -export class ODConsolePluginMessage extends ODConsoleMessage { - constructor(message:string,params?:ODConsoleMessageParam[]){ - super(message,"PLUGIN","magenta",params) - } -} - -/**## ODConsoleDebugMessage `class` - * This is an Open Ticket console debug message. - * - * It is the same as a normal `ODConsoleMessage`, but it has a predefined prefix & color scheme for the "DEBUG" messages! - */ -export class ODConsoleDebugMessage extends ODConsoleMessage { - constructor(message:string,params?:ODConsoleMessageParam[]){ - super(message,"DEBUG","cyan",params) - } -} - -/**## ODConsoleWarningMessage `class` - * This is an Open Ticket console warning message. - * - * It is the same as a normal `ODConsoleMessage`, but it has a predefined prefix & color scheme for the "WARNING" messages! - */ -export class ODConsoleWarningMessage extends ODConsoleMessage { - constructor(message:string,params?:ODConsoleMessageParam[]){ - super(message,"WARNING","yellow",params) - } -} - -/**## ODConsoleErrorMessage `class` - * This is an Open Ticket console error message. - * - * It is the same as a normal `ODConsoleMessage`, but it has a predefined prefix & color scheme for the "ERROR" messages! - */ -export class ODConsoleErrorMessage extends ODConsoleMessage { - constructor(message:string,params?:ODConsoleMessageParam[]){ - super(message,"ERROR","red",params) - } -} - -/**## ODError `class` - * This is an Open Ticket error. - * - * It is used to render and log Node.js errors & crashes in a styled way to the console & `otdebug.txt` file! - */ -export class ODError { - /**The original error that this class wraps around */ - error: Error|ODSystemError|ODPluginError - /**The origin of the original error */ - origin: NodeJS.UncaughtExceptionOrigin - - constructor(error:Error|ODSystemError|ODPluginError, origin:NodeJS.UncaughtExceptionOrigin){ - this.error = error - this.origin = origin - } - - /**Render this error to the console using `console.log`! Returns `false` when something went wrong. */ - render(){ - try { - let prefix = (this.error["_ODErrorType"] == "plugin") ? "PLUGIN ERROR" : ((this.error["_ODErrorType"] == "system") ? "OPENTICKET ERROR" : "UNKNOWN ERROR") - //title - console.log(ansis.red("["+prefix+"]: ")+this.error.message+" | origin: "+this.origin) - //stack trace - if (this.error.stack) console.log(ansis.gray(this.error.stack)) - //additional message - if (this.error["_ODErrorType"] == "plugin") console.log(ansis.red.bold("\nPlease report this error to the plugin developer and help us create a more stable plugin!")) - else console.log(ansis.red.bold("\nPlease report this error to our discord server and help us create a more stable ticket bot!")) - console.log(ansis.red("Also send the "+ansis.cyan.bold("otdebug.txt")+" file! It would help a lot!\n")) - return true - }catch{ - return false - } - } - /**Create a more-detailed, non-colored version of this error to store it in the `otdebug.txt` file! */ - toDebugString(){ - return "[UNKNOWN OD ERROR]: "+this.error.message+" | origin: "+this.origin+"\n"+this.error.stack - } -} - -/**## ODConsoleMessageTypes `type` - * This is a collection of all the default console message types within Open Ticket. - */ -export type ODConsoleMessageTypes = "info"|"system"|"plugin"|"debug"|"warning"|"error" - -/**## ODConsoleManager `class` - * This is the Open Ticket console manager. - * - * It handles the entire console system of Open Ticket. It's also the place where you need to log `ODConsoleMessage`'s. - * This manager keeps a short history of messages sent to the console which is configurable by plugins. - * - * The debug file (`otdebug.txt`) is handled in a sub-manager! - */ -export class ODConsoleManager { - /**The history of `ODConsoleMessage`'s and `ODError`'s since startup */ - history: (ODConsoleMessage|ODError)[] = [] - /**The max length of the history. The oldest messages will be removed when over the limit */ - historylength = 100 - /**An alias to the debugfile manager. (`otdebug.txt`) */ - debugfile: ODDebugFileManager - /**Is silent mode enabled? */ - silent: boolean = false - - constructor(historylength:number, debugfile:ODDebugFileManager){ - this.historylength = historylength - this.debugfile = debugfile - } - - /**Log a message to the console ... But in the Open Ticket way :) */ - log(message:ODConsoleMessage): void - log(message:ODError): void - log(message:string, type?:ODConsoleMessageTypes, params?:ODConsoleMessageParam[]): void - log(message:ODConsoleMessage|ODError|string, type?:ODConsoleMessageTypes, params?:ODConsoleMessageParam[]){ - if (message instanceof ODConsoleMessage){ - if (!this.silent) message.render() - if (this.debugfile) this.debugfile.writeConsoleMessage(message) - this.history.push(message) - - }else if (message instanceof ODError){ - if (!this.silent) message.render() - if (this.debugfile) this.debugfile.writeErrorMessage(message) - this.history.push(message) - - }else if (["string","number","boolean","object"].includes(typeof message)){ - let newMessage: ODConsoleMessage - if (type == "info") newMessage = new ODConsoleInfoMessage(message,params) - else if (type == "system") newMessage = new ODConsoleSystemMessage(message,params) - else if (type == "plugin") newMessage = new ODConsolePluginMessage(message,params) - else if (type == "debug") newMessage = new ODConsoleDebugMessage(message,params) - else if (type == "warning") newMessage = new ODConsoleWarningMessage(message,params) - else if (type == "error") newMessage = new ODConsoleErrorMessage(message,params) - else newMessage = new ODConsoleSystemMessage(message,params) - - if (!this.silent) newMessage.render() - if (this.debugfile) this.debugfile.writeConsoleMessage(newMessage) - this.history.push(newMessage) - } - this.#purgeHistory() - } - /**Shorten the history when it exceeds the max history length! */ - #purgeHistory(){ - if (this.history.length > this.historylength) this.history.shift() - } -} - -/**## ODDebugFileManager `class` - * This is the Open Ticket debug file manager. - * - * It manages the Open Ticket debug file (`otdebug.txt`) which keeps a history of all system logs. - * There are even internal logs that aren't logged to the console which are available in this file! - * - * Using this class, you can change the max length of this file and some other cool things! - */ -export class ODDebugFileManager { - /**The path to the debugfile (`./otdebug.txt` by default) */ - path: string - /**The filename of the debugfile (`otdebug.txt` by default) */ - filename: string - /**The current version of the bot used in the debug file. */ - version: ODVersion - /**The max length of the debug file. */ - maxlines: number - - constructor(path:string, filename:string, maxlines:number, version:ODVersion){ - this.path = nodepath.join(path,filename) - this.filename = filename - this.version = version - this.maxlines = maxlines - - this.#writeStartupStats() - } - - /**Check if the debug file exists */ - #existsDebugFile(){ - return fs.existsSync(this.path) - } - /**Read from the debug file */ - #readDebugFile(){ - if (this.#existsDebugFile()){ - try { - return fs.readFileSync(this.path).toString() - }catch{ - return false - } - }else{ - return false - } - } - /**Write to the debug file and shorten it when needed. */ - #writeDebugFile(text:string){ - const currenttext = this.#readDebugFile() - if (currenttext){ - const splitted = currenttext.split("\n") - - if (splitted.length+text.split("\n").length > this.maxlines){ - splitted.splice(7,(text.split("\n").length)) - } - - splitted.push(text) - fs.writeFileSync(this.path,splitted.join("\n")) - }else{ - //write new file: - const newtext = this.#createStatsText()+text - fs.writeFileSync(this.path,newtext) - } - } - /**Generate the stats/header of the debug file (containing the version) */ - #createStatsText(){ - const date = new Date() - const dstring = `${date.getDate()}/${date.getMonth()+1}/${date.getFullYear()} ${date.getHours()}:${date.getMinutes()}:${date.getSeconds()}` - return [ - "=========================", - "OPEN TICKET DEBUG FILE:", - "version: "+this.version.toString(), - "last startup: "+dstring, - "=========================\n\n" - ].join("\n") - } - /**Write the stats/header to the debug file on startup */ - #writeStartupStats(){ - const currenttext = this.#readDebugFile() - if (currenttext){ - //edit previous file: - const splitted = currenttext.split("\n") - splitted.splice(0,7) - - if (splitted.length+11 > this.maxlines){ - splitted.splice(0,((splitted.length+11) - this.maxlines)) - } - - splitted.unshift(this.#createStatsText()) - splitted.push("\n---------------------------------------------------------------------\n---------------------------------------------------------------------\n") - - fs.writeFileSync(this.path,splitted.join("\n")) - }else{ - //write new file: - const newtext = this.#createStatsText() - fs.writeFileSync(this.path,newtext) - } - } - /**Write an `ODConsoleMessage` to the debug file */ - writeConsoleMessage(message:ODConsoleMessage){ - this.#writeDebugFile(message.toDebugString()) - } - /**Write an `ODError` to the debug file */ - writeErrorMessage(error:ODError){ - this.#writeDebugFile(error.toDebugString()) - } - /**Write custom text to the debug file */ - writeText(text:string){ - this.#writeDebugFile(text) - } - /**Write a custom note to the debug file (starting with `[NOTE]:`) */ - writeNote(text:string){ - this.#writeDebugFile("[NOTE]: "+text) - } -} - -/**## ODDebugger `class` - * This is the Open Ticket debugger. - * - * It is a simple wrapper around the `ODConsoleManager` to handle debugging (primarily for `ODManagers`). - * Messages created using this debugger are only logged to the debug file unless specified otherwise. - * - * You will probably notice this class being used in the `ODManager` constructor. - * - * Using this system, all additions & removals inside a manager are logged to the debug file. This makes searching for errors a lot easier! - */ -export class ODDebugger { - /**An alias to the Open Ticket console manager. */ - console: ODConsoleManager - /**When enabled, debug logs are also shown in the console. */ - visible: boolean = false - - constructor(console:ODConsoleManager){ - this.console = console - } - - /**Create a debug message. This will always be logged to `otdebug.txt` & sometimes to the console (when enabled). Returns `true` when visible */ - debug(message:string, params?:{key:string,value:string}[]): boolean { - if (this.visible){ - this.console.log(new ODConsoleDebugMessage(message,params)) - return true - }else{ - this.console.debugfile.writeConsoleMessage(new ODConsoleDebugMessage(message,params)) - return false - } - } -} - -/**## ODLivestatusColor `type` - * This is a collection of all the colors available within the LiveStatus system. - */ -export type ODLiveStatusColor = "normal"|"red"|"green"|"blue"|"yellow"|"white"|"gray"|"magenta"|"cyan" - -/**## ODLiveStatusSourceData `interface` - * This is an interface containing all raw data received from the LiveStatus system. - */ -export interface ODLiveStatusSourceData { - /**The message to display */ - message:{ - /**The title of the message to display */ - title:string, - /**The title color of the message to display */ - titleColor:ODLiveStatusColor, - /**The description of the message to display */ - description:string, - /**The description color of the message to display */ - descriptionColor:ODLiveStatusColor - }, - /**The message will only be shown when the bot matches all statements */ - active:{ - /**A list of versions to match */ - versions:string[], - /**A list of languages to match */ - languages:string[], - /**All languages should match */ - allLanguages:boolean, - /**Match when the bot is using plugins */ - usingPlugins:boolean, - /**Match when the bot is not using plugins */ - notUsingPlugins:boolean, - /**Match when the bot is using slash commands */ - usingSlashCommands:boolean, - /**Match when the bot is not using slash commands */ - notUsingSlashCommands:boolean, - /**Match when the bot is not using transcripts */ - notUsingTranscripts:boolean, - /**Match when the bot is using text transcripts */ - usingTextTranscripts:boolean, - /**Match when the bot is using html transcripts */ - usingHtmlTranscripts:boolean - } -} - -/**## ODLiveStatusSource `class` - * This is the Open Ticket livestatus source. - * - * It is an empty template for a livestatus source. - * By default, you should use `ODLiveStatusUrlSource` or `ODLiveStatusFileSource`, - * unless you want to create one on your own! - * - * This class doesn't do anything on it's own! It's just a template! - */ -export class ODLiveStatusSource extends ODManagerData { - /**The raw data of this source */ - data: ODLiveStatusSourceData[] - - constructor(id:ODValidId, data:ODLiveStatusSourceData[]){ - super(id) - this.data = data - } - - /**Change the current data using this method! */ - setData(data:ODLiveStatusSourceData[]){ - this.data = data - } - /**Get all messages relevant to the bot based on some parameters. */ - async getMessages(main:ODMain): Promise { - const validMessages: ODLiveStatusSourceData[] = [] - - //parse data from ODMain - const currentVersion: string = main.versions.get("opendiscord:version").toString(true) - const usingSlashCommands: boolean = main.configs.get("opendiscord:general").data.slashCommands - const usingTranscripts: false|"text"|"html" = false as false|"text"|"html" //TODO - const currentLanguage: string = main.languages.getCurrentLanguageId() - const usingPlugins: boolean = (main.plugins.getLength() > 0) - - //check data for each message - this.data.forEach((msg) => { - const {active} = msg - - const correctVersion = active.versions.includes(currentVersion) - const correctSlashMode = (usingSlashCommands && active.usingSlashCommands) || (!usingSlashCommands && active.notUsingSlashCommands) - const correctTranscriptMode = (usingTranscripts == "text" && active.usingTextTranscripts) || (usingTranscripts == "html" && active.usingHtmlTranscripts) || (!usingTranscripts && active.notUsingTranscripts) - const correctLanguage = active.languages.includes(currentLanguage) || active.allLanguages - const correctPlugins = (usingPlugins && active.usingPlugins) || (!usingPlugins && active.notUsingPlugins) - - if (correctVersion && correctLanguage && correctPlugins && correctSlashMode && correctTranscriptMode) validMessages.push(msg) - }) - - //return the valid messages - return validMessages - } -} - -/**## ODLiveStatusFileSource `class` - * This is the Open Ticket livestatus file source. - * - * It is a LiveStatus source that will read the data from a local file. - * - * This can be used for testing/extending the LiveStatus system! - */ -export class ODLiveStatusFileSource extends ODLiveStatusSource { - /**The path to the source file */ - path: string - - constructor(id:ODValidId, path:string){ - if (fs.existsSync(path)){ - super(id,JSON.parse(fs.readFileSync(path).toString())) - }else throw new ODSystemError("LiveStatus source file doesn't exist!") - this.path = path - } -} - -/**## ODLiveStatusUrlSource `class` - * This is the Open Ticket livestatus url source. - * - * It is a LiveStatus source that will read the data from a http URL (json file). - * - * This is the default way of receiving LiveStatus messages! - */ -export class ODLiveStatusUrlSource extends ODLiveStatusSource { - /**The url used in the request */ - url: string - /**The `ODHTTPGetRequest` helper to fetch the url! */ - request: ODHTTPGetRequest - - constructor(id:ODValidId, url:string){ - super(id,[]) - this.url = url - this.request = new ODHTTPGetRequest(url,false) - } - async getMessages(main:ODMain): Promise { - //additional setup - this.request.url = this.url - const rawRes = await this.request.run() - if (rawRes.status != 200) throw new ODSystemError("ODLiveStatusUrlSource => Request Failed!") - try{ - this.setData(JSON.parse(rawRes.body)) - }catch{ - throw new ODSystemError("ODLiveStatusUrlSource => Request Failed!") - } - - //default - return super.getMessages(main) - } -} - -/**## ODLiveStatusManager `class` - * This is the Open Ticket livestatus manager. - * - * It manages all LiveStatus sources and has the renderer for all LiveStatus messages. - * - * You can use this to customise or add stuff to the LiveStatus system. - * Access it in the global `opendiscord.startscreen.livestatus` variable! - */ -export class ODLiveStatusManager extends ODManager { - /**The class responsible for rendering the livestatus messages. */ - renderer: ODLiveStatusRenderer - /**A reference to the ODMain or "openticket" global variable */ - #main: ODMain - - constructor(debug:ODDebugger, main:ODMain){ - super(debug,"livestatus source") - this.renderer = new ODLiveStatusRenderer(main.console) - this.#main = main - } - - /**Get the messages from all sources combined! */ - async getAllMessages(): Promise { - const messages: ODLiveStatusSourceData[] = [] - for (const source of this.getAll()){ - try { - messages.push(...(await source.getMessages(this.#main))) - }catch{} - } - return messages - } -} - -/**## ODLiveStatusRenderer `class` - * This is the Open Ticket livestatus renderer. - * - * It's responsible for rendering all LiveStatus messages to the console. - */ -export class ODLiveStatusRenderer { - /**A reference to the ODConsoleManager or "opendiscord.console" global variable */ - #console: ODConsoleManager - - constructor(console:ODConsoleManager){ - this.#console = console - } - - /**Render all messages */ - render(messages:ODLiveStatusSourceData[]): string { - try { - //process data - const final: string[] = [] - messages.forEach((msg) => { - const titleColor = msg.message.titleColor - const title = "["+msg.message.title+"] " - - const descriptionColor = msg.message.descriptionColor - const description = msg.message.description.split("\n").map((text,row) => { - //first row row doesn't need prefix - if (row < 1) return text - //other rows do need a prefix - let text2 = text - for (const i of title){ - text2 = " "+text2 - } - return text2 - }).join("\n") - - - if (!["red","yellow","green","blue","gray","magenta","cyan"].includes(titleColor)) var finalTitle = ansis.white(title) - else var finalTitle = ansis[titleColor](title) - if (!["red","yellow","green","blue","gray","magenta","cyan"].includes(descriptionColor)) var finalDescription = ansis.white(description) - else var finalDescription = ansis[descriptionColor](description) - - final.push(finalTitle+finalDescription) - }) - - //return all messages - return final.join("\n") - }catch{ - this.#console.log("Failed to render LiveStatus messages!","error") - return "" - } - } -} \ No newline at end of file diff --git a/src/core/api/modules/cooldown.ts b/src/core/api/modules/cooldown.ts deleted file mode 100644 index ad5abfe..0000000 --- a/src/core/api/modules/cooldown.ts +++ /dev/null @@ -1,348 +0,0 @@ -/////////////////////////////////////// -//COOLDOWN MODULE -/////////////////////////////////////// -import { ODId, ODValidId, ODManager, ODSystemError, ODManagerData } from "./base" -import { ODDebugger } from "./console" - -/**## ODCooldownManager `class` - * This is an Open Ticket cooldown manager. - * - * It is responsible for managing all cooldowns in Open Ticket. An example of this is the ticket creation cooldown. - * - * There are many types of cooldowns available, but you can also create your own! - */ -export class ODCooldownManager extends ODManager> { - constructor(debug:ODDebugger){ - super(debug,"cooldown") - } - /**Initiate all cooldowns in this manager. */ - async init(){ - for (const cooldown of this.getAll()){ - await cooldown.init() - } - } -} - -/**## ODCooldownData `class` - * This is Open Ticket cooldown data. - * - * It contains the instance of an active cooldown (e.g. for a user). It is handled by the cooldown itself. - */ -export class ODCooldownData extends ODManagerData { - /**Is this cooldown active? */ - active: boolean - /**Additional data of this cooldown instance. (different for each cooldown type) */ - data: Data - - constructor(id:ODValidId,active:boolean,data:Data){ - super(id) - this.active = active - this.data = data - } -} - -/**## ODCooldown `class` - * This is an Open Ticket cooldown. - * - * It doesn't do anything on it's own, but it provides the methods that are used to interact with a cooldown. - * This class can be extended from to create a working cooldown. - * - * There are also premade cooldowns available in the bot! - */ -export class ODCooldown extends ODManagerData { - data: ODManager> = new ODManager() - /**Is this cooldown already initialized? */ - ready: boolean = false - - constructor(id:ODValidId){ - super(id) - } - - /**Check this id and start cooldown when it exeeds the limit! Returns `true` when on cooldown! */ - use(id:string): boolean { - throw new ODSystemError("Tried to use an unimplemented ODCooldown!") - } - /**Check this id without starting or updating the cooldown. Returns `true` when on cooldown! */ - check(id:string): boolean { - throw new ODSystemError("Tried to use an unimplemented ODCooldown!") - } - /**Remove the cooldown for an id when available.*/ - delete(id:string){ - throw new ODSystemError("Tried to use an unimplemented ODCooldown!") - } - /**Initialize the internal systems of this cooldown. */ - async init(){ - throw new ODSystemError("Tried to use an unimplemented ODCooldown!") - } -} - -/**## ODCounterCooldown `class` - * This is an Open Ticket counter cooldown. - * - * It is is a cooldown based on a counter. When the number exceeds the limit, the cooldown is activated. - * The number will automatically be decreased with a set amount & interval. - */ -export class ODCounterCooldown extends ODCooldown<{value:number}> { - /**The cooldown will activate when exceeding this limit. */ - activeLimit: number - /**The cooldown will deactivate when below this limit. */ - cancelLimit: number - /**The amount to increase the counter with everytime the cooldown is triggered/updated. */ - increment: number - /**The amount to decrease the counter over time. */ - decrement: number - /**The interval between decrements in milliseconds. */ - invervalMs: number - - constructor(id:ODValidId, activeLimit:number, cancelLimit:number, increment:number, decrement:number, intervalMs:number){ - super(id) - this.activeLimit = activeLimit - this.cancelLimit = cancelLimit - this.increment = increment - this.decrement = decrement - this.invervalMs = intervalMs - } - - use(id:string): boolean { - const cooldown = this.data.get(id) - if (cooldown){ - //cooldown for this id already exists - if (cooldown.active){ - return true - - }else if (cooldown.data.value < this.activeLimit){ - cooldown.data.value = cooldown.data.value + this.increment - return false - - }else{ - cooldown.active = true - return false - } - }else{ - //cooldown for this id doesn't exist - this.data.add(new ODCooldownData(id,(this.increment >= this.activeLimit),{ - value:this.increment - })) - return false - } - } - check(id:string): boolean { - const cooldown = this.data.get(id) - if (cooldown){ - //cooldown for this id already exists - return cooldown.active - }else return false - } - delete(id:string): void { - this.data.remove(id) - } - async init(){ - if (this.ready) return - setInterval(async () => { - await this.data.loopAll((cooldown) => { - cooldown.data.value = cooldown.data.value - this.decrement - if (cooldown.data.value <= this.cancelLimit){ - cooldown.active = false - } - if (cooldown.data.value <= 0){ - this.data.remove(cooldown.id) - } - }) - },this.invervalMs) - this.ready = true - } -} - -/**## ODIncrementalCounterCooldown `class` - * This is an Open Ticket incremental counter cooldown. - * - * It is is a cooldown based on an incremental counter. It is exactly the same as the normal counter, - * with the only difference being that it still increments when the limit is already exeeded. - */ -export class ODIncrementalCounterCooldown extends ODCooldown<{value:number}> { - /**The cooldown will activate when exceeding this limit. */ - activeLimit: number - /**The cooldown will deactivate when below this limit. */ - cancelLimit: number - /**The amount to increase the counter with everytime the cooldown is triggered/updated. */ - increment: number - /**The amount to decrease the counter over time. */ - decrement: number - /**The interval between decrements in milliseconds. */ - invervalMs: number - - constructor(id:ODValidId, activeLimit:number, cancelLimit:number, increment:number, decrement:number, intervalMs:number){ - super(id) - this.activeLimit = activeLimit - this.cancelLimit = cancelLimit - this.increment = increment - this.decrement = decrement - this.invervalMs = intervalMs - } - - use(id:string): boolean { - const cooldown = this.data.get(id) - if (cooldown){ - //cooldown for this id already exists - if (cooldown.active){ - cooldown.data.value = cooldown.data.value + this.increment - return true - - }else if (cooldown.data.value < this.activeLimit){ - cooldown.data.value = cooldown.data.value + this.increment - return false - - }else{ - cooldown.active = true - return false - } - }else{ - //cooldown for this id doesn't exist - this.data.add(new ODCooldownData(id,(this.increment >= this.activeLimit),{ - value:this.increment - })) - return false - } - } - check(id:string): boolean { - const cooldown = this.data.get(id) - if (cooldown){ - //cooldown for this id already exists - return cooldown.active - }else return false - } - delete(id:string): void { - this.data.remove(id) - } - async init(){ - if (this.ready) return - setInterval(async () => { - await this.data.loopAll((cooldown) => { - cooldown.data.value = cooldown.data.value - this.decrement - if (cooldown.data.value <= this.cancelLimit){ - cooldown.active = false - } - if (cooldown.data.value <= 0){ - this.data.remove(cooldown.id) - } - }) - },this.invervalMs) - this.ready = true - } -} - -/**## ODTimeoutCooldown `class` - * This is an Open Ticket timeout cooldown. - * - * It is a cooldown based on a timer. When triggered/updated, the cooldown is activated for the set amount of time. - * After the timer has timed out, the cooldown will be deleted. - */ -export class ODTimeoutCooldown extends ODCooldown<{date:number}> { - /**The amount of milliseconds before the cooldown times-out */ - timeoutMs: number - - constructor(id:ODValidId, timeoutMs:number){ - super(id) - this.timeoutMs = timeoutMs - } - - use(id:string): boolean { - const cooldown = this.data.get(id) - if (cooldown){ - //cooldown for this id already exists - if ((new Date().getTime() - cooldown.data.date) > this.timeoutMs){ - this.data.remove(id) - return false - }else{ - return true - } - }else{ - //cooldown for this id doesn't exist - this.data.add(new ODCooldownData(id,true,{ - date:new Date().getTime() - })) - return false - } - } - check(id:string): boolean { - const cooldown = this.data.get(id) - if (cooldown){ - //cooldown for this id already exists - return true - }else return false - } - delete(id:string): void { - this.data.remove(id) - } - /**Get the remaining amount of milliseconds before the timeout stops. */ - remaining(id:string): number|null { - const cooldown = this.data.get(id) - if (!cooldown) return null - const rawResult = this.timeoutMs - (new Date().getTime() - cooldown.data.date) - return (rawResult > 0) ? rawResult : 0 - } - async init(){ - if (this.ready) return - this.ready = true - } -} - -/**## ODIncrementalTimeoutCooldown `class` - * This is an Open Ticket incremental timeout cooldown. - * - * It is is a cooldown based on an incremental timer. It is exactly the same as the normal timer, - * with the only difference being that it adds additional time when triggered/updated while the cooldown is already active. - */ -export class ODIncrementalTimeoutCooldown extends ODCooldown<{date:number}> { - /**The amount of milliseconds before the cooldown times-out */ - timeoutMs: number - /**The amount of milliseconds to add when triggered/updated while the cooldown is already active. */ - incrementMs: number - - constructor(id:ODValidId, timeoutMs:number, incrementMs:number){ - super(id) - this.timeoutMs = timeoutMs - this.incrementMs = incrementMs - } - - use(id:string): boolean { - const cooldown = this.data.get(id) - if (cooldown){ - //cooldown for this id already exists - if ((new Date().getTime() - cooldown.data.date) > this.timeoutMs){ - this.data.remove(id) - return false - }else{ - cooldown.data.date = cooldown.data.date + this.incrementMs - return true - } - }else{ - //cooldown for this id doesn't exist - this.data.add(new ODCooldownData(id,true,{ - date:new Date().getTime() - })) - return false - } - } - check(id:string): boolean { - const cooldown = this.data.get(id) - if (cooldown){ - //cooldown for this id already exists - return true - }else return false - } - delete(id:string): void { - this.data.remove(id) - } - /**Get the remaining amount of milliseconds before the timeout stops. */ - remaining(id:string): number|null { - const cooldown = this.data.get(id) - if (!cooldown) return null - const rawResult = this.timeoutMs - (new Date().getTime() - cooldown.data.date) - return (rawResult > 0) ? rawResult : 0 - } - async init(){ - if (this.ready) return - this.ready = true - } -} \ No newline at end of file diff --git a/src/core/api/modules/database.ts b/src/core/api/modules/database.ts deleted file mode 100644 index ab6d601..0000000 --- a/src/core/api/modules/database.ts +++ /dev/null @@ -1,278 +0,0 @@ -/////////////////////////////////////// -//DATABASE MODULE -/////////////////////////////////////// -import { ODId, ODManager, ODManagerData, ODOptionalPromise, ODPromiseVoid, ODSystemError, ODValidId, ODValidJsonType } from "./base" -import fs from "fs" -import nodepath from "path" -import { ODDebugger } from "./console" -import * as fjs from "formatted-json-stringify" - -/**## ODDatabaseManager `class` - * This is an Open Ticket database manager. - * - * It manages all databases in the bot and allows to permanently store data from the bot! - * - * You can use this class to get/add a database (`ODDatabase`) in your plugin! - */ -export class ODDatabaseManager extends ODManager { - constructor(debug:ODDebugger){ - super(debug,"database") - } - - /**Init all database files. */ - async init(){ - for (const database of this.getAll()){ - try{ - await database.init() - }catch(err){ - process.emit("uncaughtException",new ODSystemError(err)) - } - } - } -} - -/**## ODDatabase `class` - * This is an Open Ticket database template. - * This class doesn't do anything at all, it just gives a template & basic methods for a database. Use `ODJsonDatabase` instead! - * - * You can use this class if you want to create your own database implementation (e.g. `mongodb`, `mysql`,...)! - */ -export class ODDatabase extends ODManagerData { - /**The name of the file with extension. */ - file: string = "" - /**The path to the file relative to the main directory. */ - path: string = "" - - /**Init the database. */ - init(): ODPromiseVoid { - //nothing - } - /**Add/Overwrite a specific category & key in the database. Returns `true` when overwritten. */ - set(category:string, key:string, value:ODValidJsonType): ODOptionalPromise { - return false - } - /**Get a specific category & key in the database */ - get(category:string, key:string): ODOptionalPromise { - return undefined - } - /**Delete a specific category & key in the database */ - delete(category:string, key:string): ODOptionalPromise { - return false - } - /**Check if a specific category & key exists in the database */ - exists(category:string, key:string): ODOptionalPromise { - return false - } - /**Get a specific category in the database */ - getCategory(category:string): ODOptionalPromise<{key:string, value:ODValidJsonType}[]|undefined> { - return undefined - } - /**Get all values in the database */ - getAll(): ODOptionalPromise { - return [] - } -} - -/**## ODJsonDatabaseStructure `type` - * This is the structure of how a JSON database file! - */ -export type ODJsonDatabaseStructure = {category:string, key:string, value:ODValidJsonType}[] - -/**## ODJsonDatabase `class` - * This is an Open Ticket JSON database. - * It stores data in a `json` file as a large `Array` using the `category`, `key`, `value` strategy. - * You can store the following types: `string`, `number`, `boolean`, `array`, `object` & `null`! - * - * You can use this class if you want to add your own database or to use an existing one! - */ -export class ODJsonDatabase extends ODDatabase { - constructor(id:ODValidId, file:string, customPath?:string){ - super(id) - this.file = (file.endsWith(".json")) ? file : file+".json" - this.path = customPath ? nodepath.join("./",customPath,this.file) : nodepath.join("./database/",this.file) - } - - /**Init the database. */ - init(): ODPromiseVoid { - this.#system.getData() - } - /**Set/overwrite the value of `category` & `key`. Returns `true` when overwritten! - * @example - * const didOverwrite = database.setData("category","key","value") //value can be any of the valid types - * //You need an ODJsonDatabase class named "database" for this example to work! - */ - set(category:string, key:string, value:ODValidJsonType): ODOptionalPromise { - const currentList = this.#system.getData() - const currentData = currentList.find((d) => (d.category === category) && (d.key === key)) - - //overwrite when already present - if (currentData){ - currentList[currentList.indexOf(currentData)].value = value - }else{ - currentList.push({category,key,value}) - } - - this.#system.setData(currentList) - return currentData ? true : false - } - /**Get the value of `category` & `key`. Returns `undefined` when non-existent! - * @example - * const data = database.getData("category","key") //data will be the value - * //You need an ODJsonDatabase class named "database" for this example to work! - */ - get(category:string, key:string): ODOptionalPromise { - const currentList = this.#system.getData() - const tempresult = currentList.find((d) => (d.category === category) && (d.key === key)) - return tempresult ? tempresult.value : undefined - } - /**Remove the value of `category` & `key`. Returns `undefined` when non-existent! - * @example - * const didExist = database.deleteData("category","key") //delete this value - * //You need an ODJsonDatabase class named "database" for this example to work! - */ - delete(category:string, key:string): ODOptionalPromise { - const currentList = this.#system.getData() - const currentData = currentList.find((d) => (d.category === category) && (d.key === key)) - if (currentData) currentList.splice(currentList.indexOf(currentData),1) - - this.#system.setData(currentList) - return currentData ? true : false - } - /**Check if a value of `category` & `key` exists. Returns `false` when non-existent! */ - exists(category:string, key:string): ODOptionalPromise { - const currentList = this.#system.getData() - const tempresult = currentList.find((d) => (d.category === category) && (d.key === key)) - return tempresult ? true : false - } - /**Get all values in `category`. Returns `undefined` when non-existent! */ - getCategory(category:string): ODOptionalPromise<{key:string, value:ODValidJsonType}[]|undefined> { - const currentList = this.#system.getData() - const tempresult = currentList.filter((d) => (d.category === category)) - return tempresult ? tempresult.map((data) => {return {key:data.key,value:data.value}}) : undefined - } - /**Get all values in `category`. */ - getAll(): ODOptionalPromise { - return this.#system.getData() - } - - #system = { - /**Read parsed data from the json file */ - getData: (): ODJsonDatabaseStructure => { - if (fs.existsSync(this.path)){ - try{ - return JSON.parse(fs.readFileSync(this.path).toString()) - }catch(err){ - process.emit("uncaughtException",err) - throw new ODSystemError("Unable to read database "+this.path+"! getData() read error. (see error above)") - } - }else{ - fs.writeFileSync(this.path,"[]") - return [] - } - }, - /**Write parsed data to the json file */ - setData: (data:ODJsonDatabaseStructure) => { - fs.writeFileSync(this.path,JSON.stringify(data,null,"\t")) - } - } -} - - -/**## ODFormattedJsonDatabase `class` - * This is an Open Ticket Formatted JSON database. - * It stores data in a `json` file as a large `Array` using the `category`, `key`, `value` strategy. - * You can store the following types: `string`, `number`, `boolean`, `array`, `object` & `null`! - * - * This one is exactly the same as `ODJsonDatabase`, but it has a formatter from the `formatted-json-stringify` package. - * This can help you organise it a little bit better! - */ -export class ODFormattedJsonDatabase extends ODDatabase { - /**The formatter to use on the database array */ - formatter: fjs.ArrayFormatter - - constructor(id:ODValidId, file:string, formatter:fjs.ArrayFormatter, customPath?:string){ - super(id) - this.file = (file.endsWith(".json")) ? file : file+".json" - this.path = customPath ? nodepath.join("./",customPath,this.file) : nodepath.join("./database/",this.file) - this.formatter = formatter - } - - /**Init the database. */ - init(): ODPromiseVoid { - this.#system.getData() - } - /**Set/overwrite the value of `category` & `key`. Returns `true` when overwritten! - * @example - * const didOverwrite = database.setData("category","key","value") //value can be any of the valid types - * //You need an ODFormattedJsonDatabase class named "database" for this example to work! - */ - set(category:string, key:string, value:ODValidJsonType): ODOptionalPromise { - const currentList = this.#system.getData() - const currentData = currentList.find((d) => (d.category === category) && (d.key === key)) - - //overwrite when already present - if (currentData){ - currentList[currentList.indexOf(currentData)].value = value - }else{ - currentList.push({category,key,value}) - } - - this.#system.setData(currentList) - return currentData ? true : false - } - /**Get the value of `category` & `key`. Returns `undefined` when non-existent! - * @example - * const data = database.getData("category","key") //data will be the value - * //You need an ODFormattedJsonDatabase class named "database" for this example to work! - */ - get(category:string, key:string): ODOptionalPromise { - const currentList = this.#system.getData() - const tempresult = currentList.find((d) => (d.category === category) && (d.key === key)) - return tempresult ? tempresult.value : undefined - } - /**Remove the value of `category` & `key`. Returns `undefined` when non-existent! - * @example - * const didExist = database.deleteData("category","key") //delete this value - * //You need an ODFormattedJsonDatabase class named "database" for this example to work! - */ - delete(category:string, key:string): ODOptionalPromise { - const currentList = this.#system.getData() - const currentData = currentList.find((d) => (d.category === category) && (d.key === key)) - if (currentData) currentList.splice(currentList.indexOf(currentData),1) - - this.#system.setData(currentList) - return currentData ? true : false - } - /**Check if a value of `category` & `key` exists. Returns `false` when non-existent! */ - exists(category:string, key:string): ODOptionalPromise { - const currentList = this.#system.getData() - const tempresult = currentList.find((d) => (d.category === category) && (d.key === key)) - return tempresult ? true : false - } - /**Get all values in `category`. Returns `undefined` when non-existent! */ - getCategory(category:string): ODOptionalPromise<{key:string, value:ODValidJsonType}[]|undefined> { - const currentList = this.#system.getData() - const tempresult = currentList.filter((d) => (d.category === category)) - return tempresult ? tempresult.map((data) => {return {key:data.key,value:data.value}}) : undefined - } - /**Get all values in `category`. */ - getAll(): ODOptionalPromise { - return this.#system.getData() - } - - #system = { - /**Read parsed data from the json file */ - getData: (): ODJsonDatabaseStructure => { - if (fs.existsSync(this.path)){ - return JSON.parse(fs.readFileSync(this.path).toString()) - }else{ - fs.writeFileSync(this.path,"[]") - return [] - } - }, - /**Write parsed data to the json file */ - setData: (data:ODJsonDatabaseStructure) => { - fs.writeFileSync(this.path,this.formatter.stringify(data)) - } - } -} \ No newline at end of file diff --git a/src/core/api/modules/defaults.ts b/src/core/api/modules/defaults.ts deleted file mode 100644 index c0b4491..0000000 --- a/src/core/api/modules/defaults.ts +++ /dev/null @@ -1,366 +0,0 @@ -/////////////////////////////////////// -//DEFAULTS MODULE -/////////////////////////////////////// - -/**## ODDefaults `interface` - * This type is a list of all defaults available in the `ODDefaultsManager` class. - * It's used to generate typescript declarations for this class. - */ -export interface ODDefaults { - /**Enable the default error handling system. */ - errorHandling:boolean, - /**Crash when there is an unknown bot error. */ - crashOnError:boolean, - /**Enable the system responsible for the `--debug` flag. */ - debugLoading:boolean, - /**Enable the system responsible for the `--silent` flag. */ - silentLoading:boolean, - /**When enabled, you're able to use the "!OPENTICKET:dump" command to send the OT debug file. This is only possible when you're the owner of the bot. */ - allowDumpCommand:boolean, - /**Enable loading all Open Ticket plugins, sadly enough is only useful for the system :) */ - pluginLoading:boolean, - /**Don't crash the bot when a plugin crashes! */ - softPluginLoading:boolean, - - /**Load the default Open Ticket plugin classes. */ - pluginClassLoading:boolean, - - /**Load the default Open Ticket flags. */ - flagLoading:boolean, - /**Enable the default initializer for Open Ticket flags. */ - flagInitiating:boolean, - /**Load the default Open Ticket progress bar renderers. */ - progressBarRendererLoading:boolean, - /**Load the default Open Ticket progress bars. */ - progressBarLoading:boolean, - /**Load the default Open Ticket configs. */ - configLoading:boolean, - /**Enable the default initializer for Open Ticket config. */ - configInitiating:boolean, - /**Load the default Open Ticket databases. */ - databaseLoading:boolean, - /**Enable the default initializer for Open Ticket database. */ - databaseInitiating:boolean, - /**Load the default Open Ticket sessions. */ - sessionLoading:boolean, - - /**Load the default Open Ticket languages. */ - languageLoading:boolean, - /**Enable the default initializer for Open Ticket languages. */ - languageInitiating:boolean, - /**Enable selecting the current language from `config/general.json`. */ - languageSelection:boolean, - /**Set the backup language when the primary language is missing a property. */ - backupLanguage:string, - /****[NOT FOR PLUGIN TRANSLATIONS]** The full list of available languages (used in the default config checker). */ - languageList:string[], - - /**Load the default Open Ticket config checker. */ - checkerLoading:boolean, - /**Load the default Open Ticket config checker functions. */ - checkerFunctionLoading:boolean, - /**Enable the default execution of the config checkers. */ - checkerExecution:boolean, - /**Load the default Open Ticket config checker translations. */ - checkerTranslationLoading:boolean, - /**Enable the default rendering of the config checkers. */ - checkerRendering:boolean, - /**Enable the default quit action when there is an error in the config checker. */ - checkerQuit:boolean, - /**Render the checker even when there are no errors & warnings. */ - checkerRenderEmpty:boolean, - - /**Load the default Open Ticket client configuration. */ - clientLoading:boolean, - /**Load the default Open Ticket client initialization. */ - clientInitiating:boolean, - /**Load the default Open Ticket client ready actions (status, commands, permissions, ...). */ - clientReady:boolean, - /**Create a warning when the bot is present in multiple guilds. */ - clientMultiGuildWarning:boolean, - /**Load the default Open Ticket client activity (from `config/general.json`). */ - clientActivityLoading:boolean, - /**Load the default Open Ticket client activity initialization (& status refresh). */ - clientActivityInitiating:boolean, - - /**Load the default Open Ticket priority levels. */ - priorityLoading:boolean, - - /**Load the default Open Ticket slash commands. */ - slashCommandLoading:boolean, - /**Load the default Open Ticket slash command registerer (register slash cmds in discord). */ - slashCommandRegistering:boolean, - /**When enabled, the bot is forced to re-register all slash commands in the server. This can be used in case of a auto-update malfunction. */ - forceSlashCommandRegistration:boolean, - /**When enabled, the bot is allowed to unregister all slash commands which aren't used in Open Ticket. Disable this if you don't want to use the Open Ticket `ODSlashCommand` classes. */ - allowSlashCommandRemoval:boolean, - /**Load the default Open Ticket context menus. */ - contextMenuLoading:boolean, - /**Load the default Open Ticket context menu registerer (register menus in discord). */ - contextMenuRegistering:boolean, - /**When enabled, the bot is forced to re-register all context menus in the server. This can be used in case of a auto-update malfunction. */ - forceContextMenuRegistration:boolean, - /**When enabled, the bot is allowed to unregister all context menus which aren't used in Open Ticket. Disable this if you don't want to use the Open Ticket `ODContextMenu` classes. */ - allowContextMenuRemoval:boolean, - /**Load the default Open Ticket text commands. */ - textCommandLoading:boolean, - - /**Load the default Open Ticket questions (from `config/questions.json`) */ - questionLoading:boolean, - /**Load the default Open Ticket options (from `config/options.json`) */ - optionLoading:boolean, - /**Load the default Open Ticket panels (from `config/panels.json`) */ - panelLoading:boolean, - /**Load the default Open Ticket tickets (from `database/tickets.json`) */ - ticketLoading:boolean, - /**Load the default Open Ticket reaction roles (from `config/options.json`) */ - roleLoading:boolean, - /**Load the default Open Ticket blacklist (from `database/users.json`) */ - blacklistLoading:boolean, - /**Load the default Open Ticket transcript compilers. */ - transcriptCompilerLoading:boolean, - /**Load the default Open Ticket transcript history (from `database/transcripts.json`) */ - transcriptHistoryLoading:boolean, - - /**Load the default Open Ticket button builders. */ - buttonBuildersLoading:boolean, - /**Load the default Open Ticket dropdown builders. */ - dropdownBuildersLoading:boolean, - /**Load the default Open Ticket file builders. */ - fileBuildersLoading:boolean, - /**Load the default Open Ticket embed builders. */ - embedBuildersLoading:boolean, - /**Load the default Open Ticket message builders. */ - messageBuildersLoading:boolean, - /**Load the default Open Ticket modal builders. */ - modalBuildersLoading:boolean, - - /**Load the default Open Ticket command responders. */ - commandRespondersLoading:boolean, - /**Load the default Open Ticket button responders. */ - buttonRespondersLoading:boolean, - /**Load the default Open Ticket dropdown responders. */ - dropdownRespondersLoading:boolean, - /**Load the default Open Ticket modal responders. */ - modalRespondersLoading:boolean, - /**Load the default Open Ticket context menu responders. */ - contextMenuRespondersLoading:boolean, - /**Load the default Open Ticket autocomplete responders. */ - autocompleteRespondersLoading:boolean, - /**Set the time (in ms) before Open Ticket sends an error message when no reply is sent in a responder. */ - responderTimeoutMs:number, - - /**Load the default Open Ticket actions. */ - actionsLoading:boolean, - - /**Load the default Open Ticket verify bars. */ - verifyBarsLoading:boolean, - /**Load the default Open Ticket permissions. */ - permissionsLoading:boolean, - /**Load the default Open Ticket posts. */ - postsLoading:boolean, - /**Initiate the default Open Ticket posts. */ - postsInitiating:boolean, - /**Load the default Open Ticket cooldowns. */ - cooldownsLoading:boolean, - /**Initiate the default Open Ticket cooldowns. */ - cooldownsInitiating:boolean, - /**Load the default Open Ticket help menu categories. */ - helpMenuCategoryLoading:boolean, - /**Load the default Open Ticket help menu components. */ - helpMenuComponentLoading:boolean, - - /**Load the default Open Ticket stat scopes. */ - statScopesLoading:boolean, - /**Load the default Open Ticket stats. */ - statLoading:boolean, - /**Initiate the default Open Ticket stats. */ - statInitiating:boolean, - - /**Load the default Open Ticket code/functions. */ - codeLoading:boolean, - /**Execute the default Open Ticket code/functions. */ - codeExecution:boolean, - - /**Load the default Open Ticket livestatus. */ - liveStatusLoading:boolean, - /**Load the default Open Ticket startscreen. */ - startScreenLoading:boolean, - /**Render the default Open Ticket startscreen. */ - startScreenRendering:boolean, - - /**Load the emoji style from the Open Ticket general config. */ - emojiTitleStyleLoading:boolean, - /**The emoji style to use in embed & message titles using `utilities.emoijTitle()` */ - emojiTitleStyle:"disabled"|"before"|"after"|"double", - /**The emoji divider to use in embed & message titles using `utilities.emoijTitle()` */ - emojiTitleDivider:string - /**The interval in milliseconds that are between autoclose timeout checkers. */ - autocloseCheckInterval:number - /**The interval in milliseconds that are between autodelete timeout checkers. */ - autodeleteCheckInterval:number -} - -/**## ODDefaultsBooleans `type` - * This type is a list of boolean defaults available in the `ODDefaultsManager` class. - * It's used to generate typescript declarations for this class. - */ -export type ODDefaultsBooleans = { - [Key in keyof ODDefaults]: ODDefaults[Key] extends boolean ? Key : never -}[keyof ODDefaults] - -/**## ODDefaultsStrings `type` - * This type is a list of string defaults available in the `ODDefaultsManager` class. - * It's used to generate typescript declarations for this class. - */ -export type ODDefaultsStrings = { - [Key in keyof ODDefaults]: ODDefaults[Key] extends string ? Key : never -}[keyof ODDefaults] - -/**## ODDefaultsNumbers `type` - * This type is a list of number defaults available in the `ODDefaultsManager` class. - * It's used to generate typescript declarations for this class. - */ -export type ODDefaultsNumbers = { - [Key in keyof ODDefaults]: ODDefaults[Key] extends number ? Key : never -}[keyof ODDefaults] - -/**## ODDefaultsStringArray `type` - * This type is a list of string[] defaults available in the `ODDefaultsManager` class. - * It's used to generate typescript declarations for this class. - */ -export type ODDefaultsStringArray = { - [Key in keyof ODDefaults]: ODDefaults[Key] extends string[] ? Key : never -}[keyof ODDefaults] - -/**## ODDefaultsManager `class` - * This is an Open Ticket defaults manager. - * - * It manages all settings in Open Ticket that are not meant to be in the config. - * Here you can disable certain default features to replace them or to specifically enable them! - * - * You are unable to add your own defaults, you can only edit Open Ticket defaults! - */ -export class ODDefaultsManager { - /**A list of all the defaults */ - #defaults: ODDefaults - - constructor(){ - this.#defaults = { - errorHandling:true, - crashOnError:false, - debugLoading:true, - silentLoading:true, - allowDumpCommand:true, - pluginLoading:true, - softPluginLoading:false, - - pluginClassLoading:true, - - flagLoading:true, - flagInitiating:true, - progressBarRendererLoading:true, - progressBarLoading:true, - configLoading:true, - configInitiating:true, - databaseLoading:true, - databaseInitiating:true, - sessionLoading:true, - - languageLoading:true, - languageInitiating:true, - languageSelection:true, - backupLanguage:"opendiscord:english", - languageList:[], - - checkerLoading:true, - checkerFunctionLoading:true, - checkerExecution:true, - checkerTranslationLoading:true, - checkerRendering:true, - checkerQuit:true, - checkerRenderEmpty:false, - - clientLoading:true, - clientInitiating:true, - clientReady:true, - clientMultiGuildWarning:true, - clientActivityLoading:true, - clientActivityInitiating:true, - - priorityLoading:true, - - slashCommandLoading:true, - slashCommandRegistering:true, - forceSlashCommandRegistration:false, - allowSlashCommandRemoval:true, - contextMenuLoading:true, - contextMenuRegistering:true, - forceContextMenuRegistration:false, - allowContextMenuRemoval:true, - textCommandLoading:true, - - questionLoading:true, - optionLoading:true, - panelLoading:true, - ticketLoading:true, - roleLoading:true, - blacklistLoading:true, - transcriptCompilerLoading:true, - transcriptHistoryLoading:true, - - buttonBuildersLoading:true, - dropdownBuildersLoading:true, - fileBuildersLoading:true, - embedBuildersLoading:true, - messageBuildersLoading:true, - modalBuildersLoading:true, - - commandRespondersLoading:true, - buttonRespondersLoading:true, - dropdownRespondersLoading:true, - modalRespondersLoading:true, - contextMenuRespondersLoading:true, - autocompleteRespondersLoading:true, - responderTimeoutMs:2500, - - actionsLoading:true, - - verifyBarsLoading:true, - permissionsLoading:true, - postsLoading:true, - postsInitiating:true, - cooldownsLoading:true, - cooldownsInitiating:true, - helpMenuCategoryLoading:true, - helpMenuComponentLoading:true, - - statScopesLoading:true, - statLoading:true, - statInitiating:true, - - codeLoading:true, - codeExecution:true, - - liveStatusLoading:true, - startScreenLoading:true, - startScreenRendering:true, - - emojiTitleStyleLoading:true, - emojiTitleStyle:"before", - emojiTitleDivider:" ", - autocloseCheckInterval:300000, //5 minutes - autodeleteCheckInterval:300000 //5 minutes - } - } - - /**Set a default to a specific value. Remember! All plugins can edit these values, so your value could be overwritten! */ - setDefault(key:DefaultName, value:ODDefaults[DefaultName]): void { - this.#defaults[key] = value - } - - /**Get a default. Remember! All plugins can edit these values, so this value could be overwritten! */ - getDefault(key:DefaultName): ODDefaults[DefaultName] { - return this.#defaults[key] - } -} \ No newline at end of file diff --git a/src/core/api/modules/event.ts b/src/core/api/modules/event.ts deleted file mode 100644 index c7a20a2..0000000 --- a/src/core/api/modules/event.ts +++ /dev/null @@ -1,99 +0,0 @@ -/////////////////////////////////////// -//EVENT MODULE -/////////////////////////////////////// -import { ODManagerData, ODManager, ODValidId } from "./base" -import { ODConsoleWarningMessage, ODDebugger } from "./console" - -/**## ODEvent `class` - * This is an Open Ticket event. - * - * This class is made to work with the `ODEventManager` to handle events. - * The function of this specific class is to manage all listeners for a specifc event! - */ -export class ODEvent extends ODManagerData { - /**Alias to Open Ticket debugger. */ - #debug?: ODDebugger - /**The list of permanent listeners. */ - listeners: Function[] = [] - /**The list of one-time listeners. List is cleared every time the event is emitted. */ - oncelisteners: Function[] = [] - /**The max listener limit before a possible memory leak will be announced */ - listenerLimit: number = 25 - - /**Use the Open Ticket debugger in this manager for logs*/ - useDebug(debug:ODDebugger|null){ - this.#debug = debug ?? undefined - } - /**Get a collection of listeners combined from both types. Also clears the one-time listeners array! */ - #getCurrentListeners(){ - const final: Function[] = [] - this.oncelisteners.forEach((l) => final.push(l)) - this.listeners.forEach((l) => final.push(l)) - - this.oncelisteners = [] - return final - } - /**Edit the listener limit */ - setListenerLimit(limit:number){ - this.listenerLimit = limit - } - /**Add a permanent callback to this event. This will stay as long as the bot is running! */ - listen(callback:Function){ - this.listeners.push(callback) - - if (this.listeners.length > this.listenerLimit){ - if (this.#debug) this.#debug.console.log(new ODConsoleWarningMessage("Possible event memory leak detected!",[ - {key:"event",value:this.id.value}, - {key:"listeners",value:this.listeners.length.toString()} - ])) - } - } - /**Add a one-time-only callback to this event. This will only trigger the callback once! */ - listenOnce(callback:Function){ - this.oncelisteners.push(callback) - } - /**Wait until this event is fired! Be carefull with it, because it could block the entire bot when wrongly used! */ - async wait(): Promise { - return new Promise((resolve,reject) => { - this.oncelisteners.push((...args:any) => {resolve(args)}) - }) - } - /**Emit this event to all listeners. You are required to provide all parameters of the event! */ - async emit(params:any[]): Promise { - for (const listener of this.#getCurrentListeners()){ - try{ - await listener(...params) - }catch(err){ - process.emit("uncaughtException",err) - } - } - } -} - -/**## ODEventManager `class` - * This is an Open Ticket event manager. - * - * This class is made to manage all events in the bot. You can compare it with the built-in node.js `EventEmitter` - * - * It's not recommended to create this class yourself. Plugin events should be registered in their `plugin.json` file instead. - * All events are available in the `opendiscord.events` global! - */ -export class ODEventManager extends ODManager { - /**Reference to the Open Ticket debugger */ - #debug: ODDebugger - - constructor(debug:ODDebugger){ - super(debug,"event") - this.#debug = debug - } - - add(data:ODEvent, overwrite?:boolean): boolean { - data.useDebug(this.#debug) - return super.add(data,overwrite) - } - remove(id:ODValidId): ODEvent|null { - const data = super.remove(id) - if (data) data.useDebug(null) - return data - } -} \ No newline at end of file diff --git a/src/core/api/modules/flag.ts b/src/core/api/modules/flag.ts deleted file mode 100644 index 3794f3a..0000000 --- a/src/core/api/modules/flag.ts +++ /dev/null @@ -1,73 +0,0 @@ -/////////////////////////////////////// -//FLAG MODULE -/////////////////////////////////////// -import { ODId, ODValidId, ODManager, ODManagerData } from "./base" -import { ODDebugger } from "./console" - -/**## ODFlag `class` - * This is an Open Ticket flag. - * - * A flag is a boolean that can be specified by a parameter in the console. - * It's useful for small settings that are only required once in a while. - * - * Flags can also be enabled manually by plugins! - */ -export class ODFlag extends ODManagerData { - /**The method that has been used to set the value of this flag. (`null` when not set) */ - method: "param"|"manual"|null = null - /**The name of this flag. Visible to the user. */ - name: string - /**The description of this flag. Visible to the user. */ - description: string - /**The name of the parameter in the console. (e.g. `--test`) */ - param: string - /**A list of aliases for the parameter in the console. */ - aliases: string[] - /**The value of this flag. */ - value: boolean = false - - constructor(id:ODValidId, name:string, description:string, param:string, aliases?:string[], initialValue?:boolean){ - super(id) - this.name = name - this.description = description - this.param = param - this.aliases = aliases ?? [] - this.value = initialValue ?? false - } - - /**Set the value of this flag. */ - setValue(value:boolean,method?:"param"|"manual"){ - this.value = value - this.method = method ?? "manual" - } - /**Detect if the process contains the param or aliases & set the value. Use `force` to overwrite a manually set value. */ - detectProcessParams(force?:boolean){ - if (force){ - const params = [this.param,...this.aliases] - this.setValue(params.some((p) => process.argv.includes(p)),"param") - - }else if (this.method != "manual"){ - const params = [this.param,...this.aliases] - this.setValue(params.some((p) => process.argv.includes(p)),"param") - } - } -} - -/**## ODFlagManager `class` - * This is an Open Ticket flag manager. - * - * This class is responsible for managing & initiating all flags of the bot. - * It also contains a shortcut for initiating all flags. - */ -export class ODFlagManager extends ODManager { - constructor(debug:ODDebugger){ - super(debug,"flag") - } - - /**Set all flags to their `process.argv` value. */ - async init(){ - await this.loopAll((flag) => { - flag.detectProcessParams(false) - }) - } -} \ No newline at end of file diff --git a/src/core/api/modules/helpmenu.ts b/src/core/api/modules/helpmenu.ts deleted file mode 100644 index 44deec2..0000000 --- a/src/core/api/modules/helpmenu.ts +++ /dev/null @@ -1,216 +0,0 @@ -/////////////////////////////////////// -//HELP MODULE -/////////////////////////////////////// -import { ODId, ODManager, ODManagerData, ODValidId } from "./base" -import { ODDebugger } from "./console" - -/**## ODHelpMenuComponentRenderer `type` - * This is the callback of the help menu component renderer. It also contains information about how & where it is rendered. - */ -export type ODHelpMenuComponentRenderer = (page:number, category:number, location:number, mode:"slash"|"text") => string|Promise - -/**## ODHelpMenuComponent `class` - * This is an Open Ticket help menu component. - * - * It can render something on the Open Ticket help menu. - */ -export class ODHelpMenuComponent extends ODManagerData { - /**The priority of this component. The higher, the earlier it will appear in the help menu. */ - priority: number - /**The render function for this component. */ - render: ODHelpMenuComponentRenderer - - constructor(id:ODValidId, priority:number, render:ODHelpMenuComponentRenderer){ - super(id) - this.priority = priority - this.render = render - } -} - -/**## ODHelpMenuTextComponent `class` - * This is an Open Ticket help menu text component. - * - * It can render a static piece of text on the Open Ticket help menu. - */ -export class ODHelpMenuTextComponent extends ODHelpMenuComponent { - constructor(id:ODValidId, priority:number, text:string){ - super(id,priority,() => { - return text - }) - } -} - -/**## ODHelpMenuCommandComponentOption `interface` - * This interface contains a command option for the `ODHelpMenuCommandComponent`. - */ -export interface ODHelpMenuCommandComponentOption { - /**The name of this option. */ - name:string, - /**Is this option optional? */ - optional:boolean -} - -/**## ODHelpMenuCommandComponentSettings `interface` - * This interface contains the settings for the `ODHelpMenuCommandComponent`. - */ -export interface ODHelpMenuCommandComponentSettings { - /**The name of this text command. */ - textName?:string, - /**The name of this slash command. */ - slashName?:string, - /**Options available in the text command. */ - textOptions?:ODHelpMenuCommandComponentOption[], - /**Options available in the slash command. */ - slashOptions?:ODHelpMenuCommandComponentOption[], - /**The description for the text command. */ - textDescription?:string, - /**The description for the slash command. */ - slashDescription?:string -} - -/**## ODHelpMenuCommandComponent `class` - * This is an Open Ticket help menu command component. - * - * It contains a useful helper to render a command in the Open Ticket help menu. - */ -export class ODHelpMenuCommandComponent extends ODHelpMenuComponent { - constructor(id:ODValidId, priority:number, settings:ODHelpMenuCommandComponentSettings){ - super(id,priority,(page,category,location,mode) => { - if (mode == "slash" && settings.slashName){ - return `\`${settings.slashName}${(settings.slashOptions) ? this.#renderOptions(settings.slashOptions) : ""}\` ➜ ${settings.slashDescription ?? ""}` - - }else if (mode == "text" && settings.textName){ - return `\`${settings.textName}${(settings.textOptions) ? this.#renderOptions(settings.textOptions) : ""}\` ➜ ${settings.textDescription ?? ""}` - - }else return "" - }) - } - - /**Utility function to render all command options. */ - #renderOptions(options:ODHelpMenuCommandComponentOption[]){ - return " "+options.map((opt) => (opt.optional) ? `[${opt.name}]` : `<${opt.name}>`).join(" ") - } -} - -/**## ODHelpMenuCategory `class` - * This is an Open Ticket help menu category. - * - * Every category in the help menu is an embed field by default. - * Try to limit the amount of components per category. - */ -export class ODHelpMenuCategory extends ODManager { - /**The id of this category. */ - id: ODId - /**The priority of this category. The higher, the earlier it will appear in the menu. */ - priority: number - /**The name of this category. (can include emoji's) */ - name: string - /**When enabled, it automatically starts this category on a new page. */ - newPage: boolean - - constructor(id:ODValidId, priority:number, name:string, newPage?:boolean){ - super() - this.id = new ODId(id) - this.priority = priority - this.name = name - this.newPage = newPage ?? false - } - - /**Render this category and it's components. */ - async render(page:number, category:number, mode:"slash"|"text"){ - //sort from high priority to low - const derefArray = [...this.getAll()] - derefArray.sort((a,b) => { - return b.priority-a.priority - }) - const result: string[] = [] - - let i = 0 - for (const component of derefArray){ - try { - result.push(await component.render(page,category,i,mode)) - }catch(err){ - process.emit("uncaughtException",err) - } - i++ - } - - //only return the non-empty components - return result.filter((component) => component !== "").join("\n\n") - } -} - -/**## ODHelpMenuRenderResult `type` - * This is the array returned when the help menu has been rendered successfully. - * - * It contains a list of pages, which contain categories by name & value (content). - */ -export type ODHelpMenuRenderResult = {name:string, value:string}[][] - -/**## ODHelpMenuManager `class` - * This is an Open Ticket help menu manager. - * - * It is responsible for rendering the entire help menu content. - * You are also able to configure the amount of categories per page here. - * - * Fewer Categories == More Clean Menu - */ -export class ODHelpMenuManager extends ODManager { - /**Alias to Open Ticket debugger. */ - #debug: ODDebugger - /**The amount of categories per-page. */ - categoriesPerPage: number = 3 - - constructor(debug:ODDebugger){ - super(debug,"help menu category") - this.#debug = debug - } - - add(data:ODHelpMenuCategory, overwrite?:boolean): boolean { - data.useDebug(this.#debug,"help menu component") - return super.add(data,overwrite) - } - - /**Render this entire help menu & return a `ODHelpMenuRenderResult`. */ - async render(mode:"slash"|"text"): Promise { - //sort from high priority to low - const derefArray = [...this.getAll()] - derefArray.sort((a,b) => { - return b.priority-a.priority - }) - const result: {name:string, value:string}[][] = [] - let currentPage: {name:string, value:string}[] = [] - - for (const category of derefArray){ - try { - const renderedCategory = await category.render(result.length,currentPage.length,mode) - - if (renderedCategory !== ""){ - //create new page when category wants to - if (currentPage.length > 0 && category.newPage){ - result.push(currentPage) - currentPage = [] - } - - currentPage.push({ - name:category.name, - value:renderedCategory - }) - - //create new page when page is full - if (currentPage.length >= this.categoriesPerPage){ - result.push(currentPage) - currentPage = [] - } - } - }catch(err){ - process.emit("uncaughtException",err) - } - } - - //push current page when not-empty - if (currentPage.length > 0) result.push(currentPage) - - return result - } -} \ No newline at end of file diff --git a/src/core/api/modules/language.ts b/src/core/api/modules/language.ts deleted file mode 100644 index 5333945..0000000 --- a/src/core/api/modules/language.ts +++ /dev/null @@ -1,201 +0,0 @@ -/////////////////////////////////////// -//LANGUAGE MODULE -/////////////////////////////////////// -import { ODId, ODManager, ODManagerData, ODPromiseVoid, ODSystemError, ODValidId } from "./base" -import nodepath from "path" -import { ODDebugger } from "./console" -import fs from "fs" - -/**## ODLanguageMetadata `interface` - * This interface contains all metadata available in the language files. - */ -export interface ODLanguageMetadata { - /**The version of Open Ticket this translation is made for. */ - otversion:string, - /**The name of the language in english (with capital letter). */ - language:string, - /**A list of translators (discord/github username) who've contributed to this language. */ - translators:string[], - /**The last date that this translation has been modified (format: DD/MM/YYYY) */ - lastedited:string, - /**When `true`, the translator made use of some sort of automation while creating the translation. (e.g. ChatGPT, Google Translate, DeepL, ...) */ - automated:boolean -} - -/**## ODLanguageManager `class` - * This is an Open Ticket language manager. - * - * It manages all languages in the bot and manages translation for you! - * Get a translation via the `getTranslation()` or `getTranslationWithParams()` methods. - * - * Add new languages using the `ODlanguage` class in your plugin! - */ -export class ODLanguageManager extends ODManager { - /**The currently selected language. */ - current: ODLanguage|null = null - /**The currently selected backup language. (used when translation missing in current language) */ - backup: ODLanguage|null = null - /**An alias to Open Ticket debugger. */ - #debug: ODDebugger - - constructor(debug:ODDebugger, presets:boolean){ - super(debug,"language") - if (presets) this.add(new ODLanguage("english","english.json")) - this.current = presets ? new ODLanguage("english","english.json") : null - this.backup = presets ? new ODLanguage("english","english.json") : null - this.#debug = debug - } - - /**Set the current language by providing the ID of a language which is registered in this manager. */ - setCurrentLanguage(id:ODValidId){ - this.current = this.get(id) - const languageId = this.current?.id.value ?? "" - const languageAutomated = this.current?.metadata?.automated.toString() ?? "" - this.#debug.debug("Selected current language",[ - {key:"id",value:languageId}, - {key:"automated",value:languageAutomated}, - ]) - } - /**Get the current language (same as `this.current`) */ - getCurrentLanguage(){ - return (this.current) ? this.current : null - } - /**Set the backup language by providing the ID of a language which is registered in this manager. */ - setBackupLanguage(id:ODValidId){ - this.backup = this.get(id) - const languageId = this.backup?.id.value ?? "" - const languageAutomated = this.backup?.metadata?.automated.toString() ?? "" - this.#debug.debug("Selected backup language",[ - {key:"id",value:languageId}, - {key:"automated",value:languageAutomated}, - ]) - } - /**Get the backup language (same as `this.backup`) */ - getBackupLanguage(){ - return (this.backup) ? this.backup : null - } - /**Get the metadata of the current/backup language. */ - getLanguageMetadata(frombackup?:boolean): ODLanguageMetadata|null { - if (frombackup) return (this.backup) ? this.backup.metadata : null - return (this.current) ? this.current.metadata : null - } - /**Get the ID (string) of the current language. (Not backup language) */ - getCurrentLanguageId(){ - return (this.current) ? this.current.id.value : "" - } - /**Get a translation string by JSON location. (e.g. `"checker.system.typeError"`) */ - getTranslation(id:string): string|null { - if (!this.current) return this.#getBackupTranslation(id) - - const splitted = id.split(".") - let currentObject = this.current.data - let result: string|false = false - splitted.forEach((id) => { - if (typeof currentObject[id] == "object"){ - currentObject = currentObject[id] - }else if (typeof currentObject[id] == "string"){ - result = currentObject[id] - } - }) - - if (typeof result == "string") return result - else return this.#getBackupTranslation(id) - } - /**Get a backup translation string by JSON location. (system only) */ - #getBackupTranslation(id:string): string|null { - if (!this.backup) return null - - const splitted = id.split(".") - let currentObject = this.backup.data - let result: string|false = false - splitted.forEach((id) => { - if (typeof currentObject[id] == "object"){ - currentObject = currentObject[id] - }else if (typeof currentObject[id] == "string"){ - result = currentObject[id] - } - }) - - if (typeof result == "string") return result - else return null - } - /**Get a backup translation string by JSON location and replace `{0}`,`{1}`,`{2}`,... with the provided parameters. */ - getTranslationWithParams(id:string, params:string[]): string|null { - let translation = this.getTranslation(id) - if (!translation) return translation - - params.forEach((value,index) => { - if (!translation) return - translation = translation.replace(`{${index}}`,value) - }) - return translation - } - - /**Init all language files. */ - async init(){ - for (const language of this.getAll()){ - try{ - await language.init() - }catch(err){ - process.emit("uncaughtException",new ODSystemError(err)) - } - } - } -} - -/**## ODLanguage `class` - * This is an Open Ticket language file. - * - * It contains metadata and all translation strings available in this language. - * Register this class to an `ODLanguageManager` to use it! - * - * JSON languages should be created using the `ODJsonLanguage` class instead! - */ -export class ODLanguage extends ODManagerData { - /**The name of the file with extension. */ - file: string = "" - /**The path to the file relative to the main directory. */ - path: string = "" - /**The raw object data of the translation. */ - data: any - /**The metadata of the language if available. */ - metadata: ODLanguageMetadata|null = null - - constructor(id:ODValidId, data:any){ - super(id) - this.data = data - } - - /**Init the language. */ - init(): ODPromiseVoid { - //nothing - } -} - -/**## ODJsonLanguage `class` - * This is an Open Ticket JSON language file. - * - * It contains metadata and all translation strings from a certain JSON file (in `./languages/`). - * Register this class to an `ODLanguageManager` to use it! - * - * Use the `ODLanguage` class to use translations from non-JSON files! - */ -export class ODJsonLanguage extends ODLanguage { - constructor(id:ODValidId, file:string, customPath?:string){ - super(id,{}) - this.file = (file.endsWith(".json")) ? file : file+".json" - this.path = customPath ? nodepath.join("./",customPath,this.file) : nodepath.join("./languages/",this.file) - } - - /**Init the langauge. */ - init(): ODPromiseVoid { - if (!fs.existsSync(this.path)) throw new ODSystemError("Unable to parse language \""+nodepath.join("./",this.path)+"\", the file doesn't exist!") - try{ - this.data = JSON.parse(fs.readFileSync(this.path).toString()) - }catch(err){ - process.emit("uncaughtException",err) - throw new ODSystemError("Unable to parse language \""+nodepath.join("./",this.path)+"\"!") - } - if (this.data["_TRANSLATION"]) this.metadata = this.data["_TRANSLATION"] - } -} \ No newline at end of file diff --git a/src/core/api/modules/permission.ts b/src/core/api/modules/permission.ts deleted file mode 100644 index 4b4f883..0000000 --- a/src/core/api/modules/permission.ts +++ /dev/null @@ -1,340 +0,0 @@ -/////////////////////////////////////// -//PERMISSION MODULE -/////////////////////////////////////// -import { ODId, ODValidId, ODManager, ODSystemError, ODManagerData } from "./base" -import * as discord from "discord.js" -import { ODDebugger } from "./console" -import { ODClientManager } from "./client" - -/**## ODPermissionType `type` - * All available permission types/levels. Can be used in the `ODPermission` class. - */ -export type ODPermissionType = "member"|"support"|"moderator"|"admin"|"owner"|"developer" - -/**## ODPermissionScope `type` - * The scope in which a certain permission is active. - */ -export type ODPermissionScope = "global-user"|"channel-user"|"global-role"|"channel-role" - -/**## ODPermissionResult `interface` - * The result returned by `ODPermissionManager.getPermissions()`. - */ -export interface ODPermissionResult { - /**The permission type. */ - type:ODPermissionType - /**The permission scope. */ - scope:ODPermissionScope|"default" - /**The highest level available for this scope. */ - level:ODPermissionLevel, - /**The permission which returned this level. */ - source:ODPermission|null -} - -/**## ODPermissionLevel `enum` - * All available permission types/levels. But as `enum` instead of `type`. Used to calculate the level. - */ -export enum ODPermissionLevel { - /**A normal member. (Default for everyone) */ - member, - /**Support team. Higher than a normal member. (Used for ticket-admins) */ - support, - /**Moderator. Higher than the support team. (Unused) */ - moderator, - /**Admin. Higher than a moderator. (Used for global-admins) */ - admin, - /**Server owner. (Able to use all commands including `/stats reset`) */ - owner, - /**Bot owner or all users from dev team. (Able to use all commands including `/stats reset`) */ - developer -} - -/**## ODPermission `class` - * This is an Open Ticket permission. - * - * It defines a single permission level for a specific scope (global/channel & user/role) - * These permissions only apply to commands & interactions. - * They are not related to channel permissions in the ticket system. - * - * Register this class to an `ODPermissionManager` to use it! - */ -export class ODPermission extends ODManagerData { - /**The scope of this permission. */ - readonly scope: ODPermissionScope - /**The type/level of this permission. */ - readonly permission: ODPermissionType - /**The user/role of this permission. */ - readonly value: discord.Role|discord.User - /**The channel that this permission applies to. (`null` when global) */ - readonly channel: discord.Channel|null - - constructor(id:ODValidId, scope:"global-user", permission:ODPermissionType, value:discord.User) - constructor(id:ODValidId, scope:"global-role", permission:ODPermissionType, value:discord.Role) - constructor(id:ODValidId, scope:"channel-user", permission:ODPermissionType, value:discord.User, channel:discord.Channel) - constructor(id:ODValidId, scope:"channel-role", permission:ODPermissionType, value:discord.Role, channel:discord.Channel) - constructor(id:ODValidId, scope:ODPermissionScope, permission:ODPermissionType, value:discord.Role|discord.User, channel?:discord.Channel){ - super(id) - this.scope = scope - this.permission = permission - this.value = value - this.channel = channel ?? null - } -} - -/**## ODPermissionSettings `interface` - * Optional settings for the `getPermissions()` method in the `ODPermissionManager`. - */ -export interface ODPermissionSettings { - /**Include permissions from the global user scope. */ - allowGlobalUserScope?:boolean, - /**Include permissions from the global role scope. */ - allowGlobalRoleScope?:boolean, - /**Include permissions from the channel user scope. */ - allowChannelUserScope?:boolean, - /**Include permissions from the channel role scope. */ - allowChannelRoleScope?:boolean, - /**Only include permissions of which the id matches this regex. */ - idRegex?:RegExp -} - -/**## ODPermissionCalculationCallback `type` - * The callback of the permission calculation. (Used in `ODPermissionManager`) - */ -export type ODPermissionCalculationCallback = (user:discord.User, channel?:discord.Channel|null, guild?:discord.Guild|null, settings?:ODPermissionSettings|null) => Promise - -/**## ODPermissionCommandResult `type` - * The result of calculating permissions for a command. - */ -export type ODPermissionCommandResult = { - /**Returns `true` when the user has valid permissions. */ - hasPerms:false, - reason:"no-perms"|"disabled"|"not-in-server" -}|{ - /**Returns `true` when the user has valid permissions. */ - hasPerms:true, - /**Is the user a server admin or a normal member? This does not decide if the user has permissions or not. */ - isAdmin:boolean -} - -/**## ODPermissionManager `class` - * This is an Open Ticket permission manager. - * - * It manages all permissions in the bot! - * Use the `getPermissions()` and `hasPermissions()` methods to get user perms. - * - * Add new permissions using the `ODPermission` class in your plugin! - */ -export class ODPermissionManager extends ODManager { - /**Alias for Open Ticket debugger. */ - #debug: ODDebugger - /**The function for calculating permissions in this manager. */ - #calculation: ODPermissionCalculationCallback|null - /**An alias to the Open Discord client manager. */ - #client: ODClientManager - /**The result which is returned when no other permissions match. (`member` by default) */ - defaultResult: ODPermissionResult = { - level:ODPermissionLevel["member"], - scope:"default", - type:"member", - source:null - } - - constructor(debug:ODDebugger, client:ODClientManager, useDefaultCalculation?:boolean){ - super(debug,"permission") - this.#debug = debug - this.#calculation = useDefaultCalculation ? this.#defaultCalculation : null - this.#client = client - } - - /**Edit the permission calculation function in this manager. */ - setCalculation(calculation:ODPermissionCalculationCallback){ - this.#calculation = calculation - } - /**Edit the result which is returned when no other permissions match. (`member` by default) */ - setDefaultResult(result:ODPermissionResult){ - this.defaultResult = result - } - /**Get an `ODPermissionResult` based on a few context factors. Use `hasPermissions()` to simplify the result. */ - getPermissions(user:discord.User, channel?:discord.Channel|null, guild?:discord.Guild|null, settings?:ODPermissionSettings|null): Promise { - try{ - if (!this.#calculation) throw new ODSystemError("ODPermissionManager:getPermissions() => missing perms calculation") - return this.#calculation(user,channel,guild,settings) - }catch(err){ - process.emit("uncaughtException",err) - throw new ODSystemError("ODPermissionManager:getPermissions() => failed perms calculation") - } - } - /**Simplifies the `ODPermissionResult` returned from `getPermissions()` and returns a boolean to check if the user matches the required permissions. */ - hasPermissions(minimum:ODPermissionType, data:ODPermissionResult){ - if (minimum == "member") return true - else if (minimum == "support") return (data.level >= ODPermissionLevel["support"]) - else if (minimum == "moderator") return (data.level >= ODPermissionLevel["moderator"]) - else if (minimum == "admin") return (data.level >= ODPermissionLevel["admin"]) - else if (minimum == "owner") return (data.level >= ODPermissionLevel["owner"]) - else if (minimum == "developer") return (data.level >= ODPermissionLevel["developer"]) - else throw new ODSystemError("Invalid minimum permission type at ODPermissionManager.hasPermissions()") - } - /**Check for permissions. (default calculation) */ - async #defaultCalculation(user:discord.User,channel?:discord.Channel|null,guild?:discord.Guild|null, settings?:ODPermissionSettings|null): Promise { - const globalCalc = await this.#defaultGlobalCalculation(user,channel,guild,settings) - const channelCalc = await this.#defaultChannelCalculation(user,channel,guild,settings) - - if (globalCalc.level > channelCalc.level) return globalCalc - else return channelCalc - } - /**Check for global permissions. Result will be compared with the channel perms in `#defaultCalculation()`. */ - async #defaultGlobalCalculation(user:discord.User,channel?:discord.Channel|null,guild?:discord.Guild|null, settings?:ODPermissionSettings|null): Promise { - const idRegex = (settings && typeof settings.idRegex != "undefined") ? settings.idRegex : null - const allowGlobalUserScope = (settings && typeof settings.allowGlobalUserScope != "undefined") ? settings.allowGlobalUserScope : true - const allowGlobalRoleScope = (settings && typeof settings.allowGlobalRoleScope != "undefined") ? settings.allowGlobalRoleScope : true - - //check for global user permissions - if (allowGlobalUserScope){ - const users = this.getFiltered((permission) => (!idRegex || (idRegex && idRegex.test(permission.id.value))) && permission.scope == "global-user" && (permission.value instanceof discord.User) && permission.value.id == user.id) - - if (users.length > 0){ - //sort all permisions from highest to lowest - users.sort((a,b) => { - const levelA = ODPermissionLevel[a.permission] - const levelB = ODPermissionLevel[b.permission] - - if (levelB > levelA) return 1 - else if (levelA > levelB) return -1 - else return 0 - }) - - return { - type:users[0].permission, - scope:"global-user", - level:ODPermissionLevel[users[0].permission], - source:users[0] ?? null - } - } - } - - //check for global role permissions - if (allowGlobalRoleScope){ - if (guild){ - const member = await this.#client.fetchGuildMember(guild,user.id) - if (member){ - const memberRoles = member.roles.cache.map((role) => role.id) - const roles = this.getFiltered((permission) => (!idRegex || (idRegex && idRegex.test(permission.id.value))) && permission.scope == "global-role" && (permission.value instanceof discord.Role) && memberRoles.includes(permission.value.id) && permission.value.guild.id == guild.id) - - if (roles.length > 0){ - //sort all permisions from highest to lowest - roles.sort((a,b) => { - const levelA = ODPermissionLevel[a.permission] - const levelB = ODPermissionLevel[b.permission] - - if (levelB > levelA) return 1 - else if (levelA > levelB) return -1 - else return 0 - }) - - return { - type:roles[0].permission, - scope:"global-role", - level:ODPermissionLevel[roles[0].permission], - source:roles[0] ?? null - } - } - } - } - } - - //spread result to prevent accidental referencing - return {...this.defaultResult} - } - /**Check for channel permissions. Result will be compared with the global perms in `#defaultCalculation()`. */ - async #defaultChannelCalculation(user:discord.User,channel?:discord.Channel|null,guild?:discord.Guild|null, settings?:ODPermissionSettings|null): Promise { - const idRegex = (settings && typeof settings.idRegex != "undefined") ? settings.idRegex : null - const allowChannelUserScope = (settings && typeof settings.allowChannelUserScope != "undefined") ? settings.allowChannelUserScope : true - const allowChannelRoleScope = (settings && typeof settings.allowChannelRoleScope != "undefined") ? settings.allowChannelRoleScope : true - - if (guild && channel && !channel.isDMBased()){ - //check for channel user permissions - if (allowChannelUserScope){ - const users = this.getFiltered((permission) => (!idRegex || (idRegex && idRegex.test(permission.id.value))) && permission.scope == "channel-user" && permission.channel && (permission.channel.id == channel.id) && (permission.value instanceof discord.User) && permission.value.id == user.id) - - if (users.length > 0){ - //sort all permisions from highest to lowest - users.sort((a,b) => { - const levelA = ODPermissionLevel[a.permission] - const levelB = ODPermissionLevel[b.permission] - - if (levelB > levelA) return 1 - else if (levelA > levelB) return -1 - else return 0 - }) - - return { - type:users[0].permission, - scope:"channel-user", - level:ODPermissionLevel[users[0].permission], - source:users[0] ?? null - } - } - } - - //check for channel role permissions - if (allowChannelRoleScope){ - const member = await this.#client.fetchGuildMember(guild,user.id) - if (member){ - const memberRoles = member.roles.cache.map((role) => role.id) - const roles = this.getFiltered((permission) => (!idRegex || (idRegex && idRegex.test(permission.id.value))) && permission.scope == "channel-role" && permission.channel && (permission.channel.id == channel.id) && (permission.value instanceof discord.Role) && memberRoles.includes(permission.value.id) && permission.value.guild.id == guild.id) - - if (roles.length > 0){ - //sort all permisions from highest to lowest - roles.sort((a,b) => { - const levelA = ODPermissionLevel[a.permission] - const levelB = ODPermissionLevel[b.permission] - - if (levelB > levelA) return 1 - else if (levelA > levelB) return -1 - else return 0 - }) - - return { - type:roles[0].permission, - scope:"channel-role", - level:ODPermissionLevel[roles[0].permission], - source:roles[0] ?? null - } - } - } - } - } - - //spread result to prevent accidental modification because of referencing - return {...this.defaultResult} - } - - /**Check the permissions for a certain command of the bot. */ - async checkCommandPerms(permissionMode:string,requiredLevel:ODPermissionType,user:discord.User,member?:discord.GuildMember|null,channel?:discord.Channel|null,guild?:discord.Guild|null,settings?:ODPermissionSettings): Promise { - if (permissionMode === "none"){ - return {hasPerms:false,reason:"disabled"} - - }else if (permissionMode === "everyone"){ - const isAdmin = this.hasPermissions(requiredLevel,await this.getPermissions(user,channel,guild,settings)) - return {hasPerms:true,isAdmin} - - }else if (permissionMode === "admin"){ - const isAdmin = this.hasPermissions(requiredLevel,await this.getPermissions(user,channel,guild,settings)) - if (!isAdmin) return {hasPerms:false,reason:"no-perms"} - else return {hasPerms:true,isAdmin} - }else{ - if (!guild || !member){ - this.#debug.debug("ODPermissionManager.checkCommandPerms(): Permission Error, Not in server! (#1)") - return {hasPerms:false,reason:"not-in-server"} - } - const role = await this.#client.fetchGuildRole(guild,permissionMode) - if (!role){ - this.#debug.debug("ODPermissionManager.checkCommandPerms(): Permission Error, Not in server! (#2)") - return {hasPerms:false,reason:"not-in-server"} - } - if (!role.members.has(member.id)) return {hasPerms:false,reason:"no-perms"} - - const isAdmin = this.hasPermissions(requiredLevel,await this.getPermissions(user,channel,guild,settings)) - return {hasPerms:true,isAdmin} - } - } -} \ No newline at end of file diff --git a/src/core/api/modules/plugin.ts b/src/core/api/modules/plugin.ts deleted file mode 100644 index 81ae500..0000000 --- a/src/core/api/modules/plugin.ts +++ /dev/null @@ -1,242 +0,0 @@ -/////////////////////////////////////// -//PLUGIN MODULE -/////////////////////////////////////// -import { ODId, ODManager, ODManagerData, ODSystemError, ODValidId, ODVersion } from "./base" -import nodepath from "path" -import { ODConsolePluginMessage, ODConsoleWarningMessage, ODDebugger } from "./console" - -/**## ODUnknownCrashedPlugin `interface` - * Basic details for a plugin that crashed while loading the `plugin.json` file. - */ -export interface ODUnknownCrashedPlugin { - /**The name of the plugin. (path when plugin crashed before `name` was loaded) */ - name:string, - /**The description of the plugin. (when found before crashing) */ - description:string -} - -/**## ODPluginManager `class` - * This is an Open Ticket plugin manager. - * - * It manages all active plugins in the bot! - * It also contains all "plugin classes" which are managers registered by plugins. - * These are accessible via the `opendiscord.plugins.classes` global. - * - * Use `isPluginLoaded()` to check if a plugin has been loaded. - */ -export class ODPluginManager extends ODManager { - /**A manager for all custom managers registered by plugins. */ - classes: ODPluginClassManager - /**A list of basic details from all plugins that crashed while loading the `plugin.json` file. */ - unknownCrashedPlugins: ODUnknownCrashedPlugin[] = [] - - constructor(debug:ODDebugger){ - super(debug,"plugin") - this.classes = new ODPluginClassManager(debug) - } - - /**Check if a plugin has been loaded successfully and is available for usage.*/ - isPluginLoaded(id:ODValidId): boolean { - const newId = new ODId(id) - const plugin = this.get(newId) - return (plugin !== null && plugin.executed) - } -} - -/**## ODPluginData `interface` - * Parsed data from the `plugin.json` file in a plugin. - */ -export interface ODPluginData { - /**The name of this plugin (shown on startup) */ - name:string, - /**The id of this plugin. (Must be identical to directory name) */ - id:string, - /**The version of this plugin. */ - version:string, - /**The location of the start file of the plugin relative to the rootDir of the plugin */ - startFile:string, - /**A list of compatible versions. (e.g. `["OTv4.0.x", "OMv1.x.x"]`) (optional, will be required in future version) - * - `OT` --> Open Ticket support - * - `OM` --> Open Moderation support - */ - supportedVersions?:string[], - - /**Is this plugin enabled? */ - enabled:boolean, - /**The priority of this plugin. Higher priority will load before lower priority. */ - priority:number, - /**A list of events to register to the `opendiscord.events` global before loading any plugins. This way, plugins with a higher priority are able to use events from this plugin as well! */ - events:string[] - - /**Npm dependencies which are required for this plugin to work. */ - npmDependencies:string[], - /**Plugins which are required for this plugin to work. */ - requiredPlugins:string[], - /**Plugins which are incompatible with this plugin. */ - incompatiblePlugins:string[], - - /**Additional details about this plugin. */ - details:ODPluginDetails -} - -/**## ODPluginDetails `interface` - * Additional details in the `plugin.json` file from a plugin. - */ -export interface ODPluginDetails { - /**The main author of the plugin. Additional contributors can be specified in `contributors`. */ - author:string, - /**A list of plugin contributors. (optional, will be required in future version) */ - contributors?:string[], - /**A short description of this plugin. */ - shortDescription:string, - /**A large description of this plugin. */ - longDescription:string, - /**A URL to a cover image of this plugin. (currently unused) */ - imageUrl:string, - /**A URL to the website/project page of this plugin. (currently unused) */ - projectUrl:string, - /**A list of tags/categories that this plugin affects. */ - tags:string[] -} - -/**## ODPlugin `class` - * This is an Open Ticket plugin. - * - * It represents a single plugin in the `./plugins/` directory. - * All plugins are accessible via the `opendiscord.plugins` global. - * - * Don't re-execute plugins which are already enabled! It might break the bot or plugin. - */ -export class ODPlugin extends ODManagerData { - /**The name of the directory of this plugin. (same as id) */ - dir: string - /**All plugin data found in the `plugin.json` file. */ - data: ODPluginData - /**The name of this plugin. */ - name: string - /**The priority of this plugin. */ - priority: number - /**The version of this plugin. */ - version: ODVersion - /**The additional details of this plugin. */ - details: ODPluginDetails - - /**Is this plugin enabled? */ - enabled: boolean - /**Did this plugin execute successfully?. */ - executed: boolean - /**Did this plugin crash? (A reason is available in the `crashReason`) */ - crashed: boolean - /**The reason which caused this plugin to crash. */ - crashReason: null|"incompatible.plugin"|"missing.plugin"|"missing.dependency"|"incompatible.version"|"executed" = null - - constructor(dir:string, jsondata:ODPluginData){ - super(jsondata.id) - this.dir = dir - this.data = jsondata - this.name = jsondata.name - this.priority = jsondata.priority - this.version = ODVersion.fromString("plugin",jsondata.version) - this.details = jsondata.details - - this.enabled = jsondata.enabled - this.executed = false - this.crashed = false - } - - /**Get the startfile location relative to the `./plugins/` directory. (`./dist/plugins/`) when compiled) */ - getStartFile(){ - const newFile = this.data.startFile.replace(/\.ts$/,".js") - return nodepath.join(this.dir,newFile) - } - /**Execute this plugin. Returns `false` on crash. */ - async execute(debug:ODDebugger,force?:boolean): Promise { - if ((this.enabled && !this.crashed) || force){ - try{ - //import relative plugin directory path (works on windows & unix based systems) - const pluginPath = nodepath.join("../../../../plugins/",this.getStartFile()).replaceAll("\\","/") - await import(pluginPath) - debug.console.log("Plugin \""+this.id.value+"\" loaded successfully!","plugin") - this.executed = true - return true - }catch(error){ - this.crashed = true - this.crashReason = "executed" - - debug.console.log(error.message+", canceling plugin execution...","plugin",[ - {key:"path",value:"./plugins/"+this.dir} - ]) - debug.console.log("You can see more about this error in the ./otdebug.txt file!","info") - debug.console.debugfile.writeText(error.stack) - - return false - } - }else return true - } - - /**Check if a npm dependency exists. */ - #checkDependency(id:string){ - try{ - require.resolve(id) - return true - }catch{ - return false - } - } - - /**Get a list of all missing npm dependencies that are required for this plugin. */ - dependenciesInstalled(){ - const missing: string[] = [] - this.data.npmDependencies.forEach((d) => { - if (!this.#checkDependency(d)){ - missing.push(d) - } - }) - - return missing - } - /**Get a list of all missing plugins that are required for this plugin. */ - pluginsInstalled(manager:ODPluginManager){ - const missing: string[] = [] - this.data.requiredPlugins.forEach((p) => { - const plugin = manager.get(p) - if (!plugin || !plugin.enabled){ - missing.push(p) - } - }) - - return missing - } - /**Get a list of all enabled incompatible plugins that interfere with this plugin. */ - pluginsIncompatible(manager:ODPluginManager){ - const incompatible: string[] = [] - this.data.incompatiblePlugins.forEach((p) => { - const plugin = manager.get(p) - if (plugin && plugin.enabled){ - incompatible.push(p) - } - }) - - return incompatible - } - /**Get a list of all authors & contributors of this plugin. */ - getAuthors(): string[] { - return [this.details.author,...(this.details.contributors ?? [])] - } -} - -/**## ODPluginClassManager `class` - * This is an Open Ticket plugin class manager. - * - * It manages all managers registered by plugins! - * Plugins are able to register their own managers, handlers, functions, classes, ... here. - * By doing this, other plugins are also able to make use of it. - * This can be useful for plugins that want to extend other plugins. - * - * Use `isPluginLoaded()` to check if a plugin has been loaded before trying to access the manager. - */ -export class ODPluginClassManager extends ODManager { - constructor(debug:ODDebugger){ - super(debug,"plugin class") - } -} \ No newline at end of file diff --git a/src/core/api/modules/post.ts b/src/core/api/modules/post.ts deleted file mode 100644 index ef6dfca..0000000 --- a/src/core/api/modules/post.ts +++ /dev/null @@ -1,90 +0,0 @@ -/////////////////////////////////////// -//POST MODULE -/////////////////////////////////////// -import { ODId, ODManager, ODManagerData, ODValidId } from "./base" -import { ODMessageBuildResult, ODMessageBuildSentResult } from "./builder" -import { ODDebugger } from "./console" -import * as discord from "discord.js" - -/**## ODPostManager `class` - * This is an Open Ticket post manager. - * - * It manages `ODPosts`'s for you. - * - * You can use this to get the logs channel of the bot (or some other static channel/category). - */ -export class ODPostManager extends ODManager> { - /**A reference to the main server of the bot */ - #guild: discord.Guild|null = null - - constructor(debug:ODDebugger){ - super(debug,"post") - } - - add(data:ODPost, overwrite?:boolean): boolean { - if (this.#guild) data.useGuild(this.#guild) - return super.add(data,overwrite) - } - /**Initialize the post manager & all posts. */ - async init(guild:discord.Guild){ - this.#guild = guild - for (const post of this.getAll()){ - post.useGuild(guild) - await post.init() - } - } -} - -/**## ODPost `class` - * This is an Open Ticket post class. - * - * A post is just a shortcut to a static discord channel or category. - * This can be used to get a specific channel over and over again! - * - * This class also contains utilities for sending messages via the Open Ticket builders. - */ -export class ODPost extends ODManagerData { - /**A reference to the main server of the bot */ - #guild: discord.Guild|null = null - /**Is this post already initialized? */ - ready: boolean = false - /**The discord.js channel */ - channel: ChannelType|null = null - /**The discord channel id */ - channelId: string - - constructor(id:ODValidId, channelId:string){ - super(id) - this.channelId = channelId - } - - /**Use a specific guild in this class for fetching the channel*/ - useGuild(guild:discord.Guild|null){ - this.#guild = guild - } - /**Change the channel id to another channel! */ - setChannelId(id:string){ - this.channelId = id - } - /**Initialize the discord.js channel of this post. */ - async init(){ - if (this.ready) return - if (!this.#guild) return this.channel = null - try{ - this.channel = await this.#guild.channels.fetch(this.channelId) as ChannelType - }catch{ - this.channel = null - } - this.ready = true - } - /**Send a message to this channel using the Open Ticket builder system */ - async send(msg:ODMessageBuildResult): Promise> { - if (!this.channel || !this.channel.isTextBased()) return {success:false,message:null} - try{ - const sent = await this.channel.send(msg.message) - return {success:true,message:sent} - }catch{ - return {success:false,message:null} - } - } -} \ No newline at end of file diff --git a/src/core/api/modules/progressbar.ts b/src/core/api/modules/progressbar.ts deleted file mode 100644 index b9b8289..0000000 --- a/src/core/api/modules/progressbar.ts +++ /dev/null @@ -1,232 +0,0 @@ -/////////////////////////////////////// -//PROGRESS BAR MODULE -/////////////////////////////////////// -import { ODSystemError, ODManager, ODManagerData, ODValidId } from "./base" -import { ODDebugger } from "./console" -import readline from "readline" - -/**## ODProgressBarRendererManager `class` - * This is an Open Ticket progress bar renderer manager. - * - * It is responsible for managing all console progress bar renderers in Open Ticket. - * - * A renderer is a function which will try to visualize the progress bar in the console. - */ -export class ODProgressBarRendererManager extends ODManager> { - constructor(debug:ODDebugger){ - super(debug,"progress bar renderer") - } -} - -/**## ODProgressBarManager `class` - * This is an Open Ticket progress bar manager. - * - * It is responsible for managing all console progress bars in Open Ticket. An example of this is the slash command registration progress bar. - * - * There are many types of progress bars available, but you can also create your own! - */ -export class ODProgressBarManager extends ODManager { - renderers: ODProgressBarRendererManager - - constructor(debug:ODDebugger){ - super(debug,"progress bar") - this.renderers = new ODProgressBarRendererManager(debug) - } -} - -/**## ODProgressBarRenderFunc `type` - * This is the render function for an Open Ticket console progress bar. - */ -export type ODProgressBarRenderFunc = (settings:Settings,min:number,max:number,value:number,prefix:string|null,suffix:string|null) => string - -/**## ODProgressBarRenderer `class` - * This is an Open Ticket console progress bar renderer. - * - * It is used to render a progress bar in the console of the bot. - * - * There are already a lot of default options available if you just want an easy progress bar! - */ -export class ODProgressBarRenderer extends ODManagerData { - settings: Settings - #render: ODProgressBarRenderFunc - - constructor(id:ODValidId,render:ODProgressBarRenderFunc,settings:Settings){ - super(id) - this.#render = render - this.settings = settings - } - - /**Render a progress bar using this renderer. */ - render(min:number,max:number,value:number,prefix:string|null,suffix:string|null){ - try { - return this.#render(this.settings,min,max,value,prefix,suffix) - }catch(err){ - process.emit("uncaughtException",err) - return "" - } - } - - withAdditionalSettings(settings:Partial): ODProgressBarRenderer { - const newSettings: Settings = {...this.settings} - for (const key of Object.keys(settings)){ - if (typeof settings[key] != "undefined") newSettings[key] = settings[key] - } - return new ODProgressBarRenderer(this.id,this.#render,newSettings) - } -} - -/**## ODProgressBar `class` - * This is an Open Ticket console progress bar. - * - * It is used to create a simple or advanced progress bar in the console of the bot. - * These progress bars are not visible in the `otdebug.txt` file and should only be used as extra visuals. - * - * Use other classes as existing templates or create your own progress bar from scratch using this class. - */ -export class ODProgressBar extends ODManagerData { - /**The renderer of this progress bar. */ - renderer: ODProgressBarRenderer<{}> - /**Is this progress bar currently active? */ - #active: boolean = false - /**A list of listeners when the progress bar stops. */ - #stopListeners: Function[] = [] - /**The current value of the progress bar. */ - protected value: number - /**The minimum value of the progress bar. */ - min: number - /**The maximum value of the progress bar. */ - max: number - /**The initial value of the progress bar. */ - initialValue: number - /**The prefix displayed in the progress bar. */ - prefix:string|null - /**The prefix displayed in the progress bar. */ - suffix:string|null - - /**Enable automatic stopping when reaching `min` or `max`. */ - autoStop: null|"min"|"max" - - constructor(id:ODValidId,renderer:ODProgressBarRenderer<{}>,min:number,max:number,value:number,autoStop:null|"min"|"max",prefix:string|null,suffix:string|null){ - super(id) - this.renderer = renderer - this.min = min - this.max = max - this.initialValue = this.#parseValue(value) - this.value = this.#parseValue(value) - this.autoStop = autoStop - this.prefix = prefix - this.suffix = suffix - } - /**Parse a value in such a way that it doesn't go below/above the min/max limits. */ - #parseValue(value:number){ - if (value > this.max) return this.max - else if (value < this.min) return this.min - else return value - } - /**Render progress bar to the console. */ - #renderStdout(){ - if (!this.#active) return - readline.clearLine(process.stdout,0) - readline.cursorTo(process.stdout,0) - process.stdout.write(this.renderer.render(this.min,this.max,this.value,this.prefix,this.suffix)) - } - /**Start showing this progress bar in the console. */ - start(): boolean { - if (this.#active) return false - this.value = this.#parseValue(this.initialValue) - this.#active = true - this.#renderStdout() - return true - } - /**Update this progress bar while active. (will automatically update the progress bar in the console) */ - protected update(value:number,stop?:boolean): boolean { - if (!this.#active) return false - this.value = this.#parseValue(value) - this.#renderStdout() - if (stop || (this.autoStop == "max" && this.value == this.max) || (this.autoStop == "min" && this.value == this.min)){ - process.stdout.write("\n") - this.#active = false - this.#stopListeners.forEach((cb) => cb()) - this.#stopListeners = [] - } - return true - } - /**Wait for the progress bar to finish. */ - finished(): Promise { - return new Promise((resolve) => { - this.#stopListeners.push(resolve) - }) - } -} - -/**## ODTimedProgressBar `class` - * This is an Open Ticket timed console progress bar. - * - * It is used to create a simple timed progress bar in the console. - * You can set a fixed duration (milliseconds) in the constructor. - */ -export class ODTimedProgressBar extends ODProgressBar { - /**The time in milliseconds. */ - time: number - /**The mode of the timer. */ - mode: "increasing"|"decreasing" - - constructor(id:ODValidId,renderer:ODProgressBarRenderer<{}>,time:number,mode:"increasing"|"decreasing",prefix:string|null,suffix:string|null){ - super(id,renderer,0,time,0,(mode == "increasing") ? "max" : "min",prefix,suffix) - this.time = time - this.mode = mode - } - - /**The timer which is used. */ - async #timer(ms:number): Promise { - return new Promise((resolve) => { - setTimeout(() => { - resolve() - },ms) - }) - } - /**Run the timed progress bar. */ - async #execute(){ - let i = 0 - const fragment = this.time/100 - while (i < 100){ - await this.#timer(fragment) - i++ - super.update((this.mode == "increasing") ? (i*fragment) : this.time-(i*fragment)) - } - } - start(){ - const res = super.start() - if (!res) return false - this.#execute() - return true - } -} - -/**## ODManualProgressBar `class` - * This is an Open Ticket manual console progress bar. - * - * It is used to create a simple manual progress bar in the console. - * You can update the progress manually using `update()`. - */ -export class ODManualProgressBar extends ODProgressBar { - constructor(id:ODValidId,renderer:ODProgressBarRenderer<{}>,amount:number,autoStop:null|"min"|"max",prefix:string|null,suffix:string|null){ - super(id,renderer,0,amount,0,autoStop,prefix,suffix) - } - /**Set the value of the progress bar. */ - set(value:number,stop?:boolean){ - super.update(value,stop) - } - /**Get the current value of the progress bar. */ - get(){ - return this.value - } - /**Increase the value of the progress bar. */ - increase(amount:number,stop?:boolean){ - super.update(this.value+amount,stop) - } - /**Decrease the value of the progress bar. */ - decrease(amount:number,stop?:boolean){ - super.update(this.value-amount,stop) - } -} \ No newline at end of file diff --git a/src/core/api/modules/responder.ts b/src/core/api/modules/responder.ts deleted file mode 100644 index 9f1da86..0000000 --- a/src/core/api/modules/responder.ts +++ /dev/null @@ -1,1418 +0,0 @@ -/////////////////////////////////////// -//RESPONDER MODULE -/////////////////////////////////////// -import { ODId, ODManager, ODValidId, ODSystemError, ODManagerData } from "./base" -import * as discord from "discord.js" -import { ODWorkerManager, ODWorkerCallback, ODWorker } from "./worker" -import { ODDebugger } from "./console" -import { ODClientManager, ODContextMenu, ODSlashCommand, ODTextCommand, ODTextCommandInteractionOption } from "./client" -import { ODDropdownData, ODMessageBuildResult, ODMessageBuildSentResult, ODModalBuildResult } from "./builder" - -/**## ODResponderImplementation `class` - * This is an Open Ticket responder implementation. - * - * It is a basic implementation of the `ODWorkerManager` used by all `ODResponder` classes. - * - * This class can't be used stand-alone & needs to be extended from! - */ -export class ODResponderImplementation extends ODManagerData { - /**The manager that has all workers of this implementation */ - workers: ODWorkerManager - /**The `commandName` or `customId` needs to match this string or regex for this responder to be executed. */ - match: string|RegExp - - constructor(id:ODValidId, match:string|RegExp, callback?:ODWorkerCallback, priority?:number, callbackId?:ODValidId){ - super(id) - this.match = match - this.workers = new ODWorkerManager("descending") - if (callback) this.workers.add(new ODWorker(callbackId ? callbackId : id,priority ?? 0,callback)) - } - /**Execute all workers & return the result. */ - async respond(instance:Instance, source:Source, params:Params): Promise { - throw new ODSystemError("Tried to build an unimplemented ODResponderImplementation") - } -} - -/**## ODResponderTimeoutErrorCallback `type` - * This is the callback for the responder timeout function. It will be executed when something went wrong or the action takes too much time. - */ -export type ODResponderTimeoutErrorCallback = (instance:Instance, source:Source) => void|Promise - -/**## ODResponderManager `class` - * This is an Open Ticket responder manager. - * - * It contains all Open Ticket responders. Responders can respond to an interaction, button, dropdown, modal or command. - * - * Using the Open Ticket responder system has a few advantages compared to vanilla discord.js: - * - plugins can extend/edit replies - * - automatically reply on error - * - independent workers (with priority) - * - fail-safe design using try-catch - * - write code once => reply to both slash & text commands at the same time! - * - know where the request came from & parse options/subcommands & without errors! - * - And so much more! - */ -export class ODResponderManager { - /**A manager for all (text & slash) command responders. */ - commands: ODCommandResponderManager - /**A manager for all button responders. */ - buttons: ODButtonResponderManager - /**A manager for all dropdown/select menu responders. */ - dropdowns: ODDropdownResponderManager - /**A manager for all modal responders. */ - modals: ODModalResponderManager - /**A manager for all context menu responders. */ - contextMenus: ODContextMenuResponderManager - /**A manager for all autocomplete responders. */ - autocomplete: ODAutocompleteResponderManager - - constructor(debug:ODDebugger, client:ODClientManager){ - this.commands = new ODCommandResponderManager(debug,"command responder",client) - this.buttons = new ODButtonResponderManager(debug,"button responder",client) - this.dropdowns = new ODDropdownResponderManager(debug,"dropdown responder",client) - this.modals = new ODModalResponderManager(debug,"modal responder",client) - this.contextMenus = new ODContextMenuResponderManager(debug,"context menu responder",client) - this.autocomplete = new ODAutocompleteResponderManager(debug,"autocomplete responder",client) - } -} - -/**## ODCommandResponderManager `class` - * This is an Open Ticket command responder manager. - * - * It contains all Open Ticket command responders. These can respond to text & slash commands. - * - * Using the Open Ticket responder system has a few advantages compared to vanilla discord.js: - * - plugins can extend/edit replies - * - automatically reply on error - * - independent workers (with priority) - * - fail-safe design using try-catch - * - write code once => reply to both slash & text commands at the same time! - * - know where the request came from & parse options/subcommands & without errors! - * - And so much more! - */ -export class ODCommandResponderManager extends ODManager> { - /**An alias to the Open Ticket client manager. */ - #client: ODClientManager - /**The callback executed when the default workers take too much time to reply. */ - #timeoutErrorCallback: ODResponderTimeoutErrorCallback|null = null - /**The amount of milliseconds before the timeout error callback is executed. */ - #timeoutMs: number|null = null - - constructor(debug:ODDebugger, debugname:string, client:ODClientManager){ - super(debug,debugname) - this.#client = client - } - - /**Set the message to send when the response times out! */ - setTimeoutErrorCallback(callback:ODResponderTimeoutErrorCallback|null, ms:number|null){ - this.#timeoutErrorCallback = callback - this.#timeoutMs = ms - } - - add(data:ODCommandResponder<"slash"|"text",any>, overwrite?:boolean){ - const res = super.add(data,overwrite) - - //add the callback to the slash command manager - this.#client.slashCommands.onInteraction(data.match,(interaction,cmd) => { - const newData = this.get(data.id) - if (!newData) return - newData.respond(new ODCommandResponderInstance(interaction,cmd,this.#timeoutErrorCallback,this.#timeoutMs),"slash",{}) - }) - - //add the callback to the text command manager - this.#client.textCommands.onInteraction(data.prefix,data.match,(interaction,cmd,options) => { - const newData = this.get(data.id) - if (!newData) return - newData.respond(new ODCommandResponderInstance(interaction,cmd,this.#timeoutErrorCallback,this.#timeoutMs,options),"text",{}) - }) - - return res - } -} - -/**## ODCommandResponderInstanceOptions `class` - * This is an Open Ticket command responder instance options manager. - * - * This class will manage all options & subcommands from slash & text commands. - */ -export class ODCommandResponderInstanceOptions { - /**The interaction to get data from. */ - #interaction: discord.ChatInputCommandInteraction|discord.Message - /**The command which is related to the interaction. */ - #cmd:ODSlashCommand|ODTextCommand - /**A list of options which have been parsed by the text command parser. */ - #options: ODTextCommandInteractionOption[] - - constructor(interaction:discord.ChatInputCommandInteraction|discord.Message, cmd:ODSlashCommand|ODTextCommand, options?:ODTextCommandInteractionOption[]){ - this.#interaction = interaction - this.#cmd = cmd - this.#options = options ?? [] - } - - /**Get a string option. */ - getString(name:string,required:true): string - getString(name:string,required:false): string|null - getString(name:string,required:boolean){ - if (this.#interaction instanceof discord.ChatInputCommandInteraction){ - try { - return this.#interaction.options.getString(name,required) - }catch{ - throw new ODSystemError("ODCommandResponderInstanceOptions:getString() slash command option not found!") - } - - }else if (this.#interaction instanceof discord.Message){ - const opt = this.#options.find((opt) => opt.type == "string" && opt.name == name) - if (opt && typeof opt.value == "string") return opt.value - else return null - - }else return null - } - /**Get a boolean option. */ - getBoolean(name:string,required:true): boolean - getBoolean(name:string,required:false): boolean|null - getBoolean(name:string,required:boolean){ - if (this.#interaction instanceof discord.ChatInputCommandInteraction){ - try { - return this.#interaction.options.getBoolean(name,required) - }catch{ - throw new ODSystemError("ODCommandResponderInstanceOptions:getBoolean() slash command option not found!") - } - - }else if (this.#interaction instanceof discord.Message){ - const opt = this.#options.find((opt) => opt.type == "boolean" && opt.name == name) - if (opt && typeof opt.value == "boolean") return opt.value - else return null - - }else return null - } - /**Get a number option. */ - getNumber(name:string,required:true): number - getNumber(name:string,required:false): number|null - getNumber(name:string,required:boolean){ - if (this.#interaction instanceof discord.ChatInputCommandInteraction){ - try { - return this.#interaction.options.getNumber(name,required) - }catch{ - throw new ODSystemError("ODCommandResponderInstanceOptions:getNumber() slash command option not found!") - } - - }else if (this.#interaction instanceof discord.Message){ - const opt = this.#options.find((opt) => opt.type == "number" && opt.name == name) - if (opt && typeof opt.value == "number") return opt.value - else return null - - }else return null - } - /**Get a channel option. */ - getChannel(name:string,required:true): discord.TextChannel|discord.VoiceChannel|discord.StageChannel|discord.NewsChannel|discord.MediaChannel|discord.ForumChannel|discord.CategoryChannel - getChannel(name:string,required:false): discord.TextChannel|discord.VoiceChannel|discord.StageChannel|discord.NewsChannel|discord.MediaChannel|discord.ForumChannel|discord.CategoryChannel|null - getChannel(name:string,required:boolean){ - if (this.#interaction instanceof discord.ChatInputCommandInteraction){ - try { - return this.#interaction.options.getChannel(name,required) - }catch{ - throw new ODSystemError("ODCommandResponderInstanceOptions:getChannel() slash command option not found!") - } - - }else if (this.#interaction instanceof discord.Message){ - const opt = this.#options.find((opt) => opt.type == "channel" && opt.name == name) - if (opt && (opt.value instanceof discord.TextChannel || opt.value instanceof discord.VoiceChannel || opt.value instanceof discord.StageChannel || opt.value instanceof discord.NewsChannel || opt.value instanceof discord.MediaChannel || opt.value instanceof discord.ForumChannel || opt.value instanceof discord.CategoryChannel)) return opt.value - else return null - - }else return null - } - /**Get a role option. */ - getRole(name:string,required:true): discord.Role - getRole(name:string,required:false): discord.Role|null - getRole(name:string,required:boolean){ - if (this.#interaction instanceof discord.ChatInputCommandInteraction){ - try { - return this.#interaction.options.getRole(name,required) - }catch{ - throw new ODSystemError("ODCommandResponderInstanceOptions:getRole() slash command option not found!") - } - - }else if (this.#interaction instanceof discord.Message){ - const opt = this.#options.find((opt) => opt.type == "role" && opt.name == name) - if (opt && opt.value instanceof discord.Role) return opt.value - else return null - - }else return null - } - /**Get a user option. */ - getUser(name:string,required:true): discord.User - getUser(name:string,required:false): discord.User|null - getUser(name:string,required:boolean){ - if (this.#interaction instanceof discord.ChatInputCommandInteraction){ - try { - return this.#interaction.options.getUser(name,required) - }catch{ - throw new ODSystemError("ODCommandResponderInstanceOptions:getUser() slash command option not found!") - } - - }else if (this.#interaction instanceof discord.Message){ - const opt = this.#options.find((opt) => opt.type == "user" && opt.name == name) - if (opt && opt.value instanceof discord.User) return opt.value - else return null - - }else return null - } - /**Get a guild member option. */ - getGuildMember(name:string,required:true): discord.GuildMember - getGuildMember(name:string,required:false): discord.GuildMember|null - getGuildMember(name:string,required:boolean){ - if (this.#interaction instanceof discord.ChatInputCommandInteraction){ - try { - const member = this.#interaction.options.getMember(name) - if (!member && required) throw new ODSystemError("ODCommandResponderInstanceOptions:getGuildMember() slash command option not found!") - return member - }catch{ - throw new ODSystemError("ODCommandResponderInstanceOptions:getGuildMember() slash command option not found!") - } - - }else if (this.#interaction instanceof discord.Message){ - const opt = this.#options.find((opt) => opt.type == "guildmember" && opt.name == name) - if (opt && opt.value instanceof discord.GuildMember) return opt.value - else return null - - }else return null - } - /**Get a mentionable option. */ - getMentionable(name:string,required:true): discord.User|discord.GuildMember|discord.Role - getMentionable(name:string,required:false): discord.User|discord.GuildMember|discord.Role|null - getMentionable(name:string,required:boolean){ - if (this.#interaction instanceof discord.ChatInputCommandInteraction){ - try { - return this.#interaction.options.getMentionable(name,required) - }catch{ - throw new ODSystemError("ODCommandResponderInstanceOptions:getGuildMember() slash command option not found!") - } - - }else if (this.#interaction instanceof discord.Message){ - const opt = this.#options.find((opt) => opt.type == "mentionable" && opt.name == name) - if (opt && (opt.value instanceof discord.User || opt.value instanceof discord.GuildMember || opt.value instanceof discord.Role)) return opt.value - else return null - - }else return null - } - /**Get a subgroup. */ - getSubGroup(): string|null - getSubGroup(){ - if (this.#interaction instanceof discord.ChatInputCommandInteraction){ - try { - return this.#interaction.options.getSubcommandGroup(true) - }catch{ - throw new ODSystemError("ODCommandResponderInstanceOptions:getSubGroup() slash command option not found!") - } - - }else if (this.#interaction instanceof discord.Message && this.#cmd instanceof ODTextCommand){ - //0: name, 1:sub/group, 2:sub - const splittedName: string[] = this.#cmd.builder.name.split(" ") - return splittedName[1] ?? null - - }else return null - } - /**Get a subcommand. */ - getSubCommand(): string|null - getSubCommand(){ - if (this.#interaction instanceof discord.ChatInputCommandInteraction){ - try { - return this.#interaction.options.getSubcommand(true) - }catch{ - throw new ODSystemError("ODCommandResponderInstanceOptions:getSubCommand() slash command option not found!") - } - - }else if (this.#interaction instanceof discord.Message && this.#cmd instanceof ODTextCommand){ - //0: name, 1:sub/group, 2:sub - const splittedName: string[] = this.#cmd.builder.name.split(" ") - - //return the second subcommand when there is a subgroup - if (splittedName.length > 2){ - return splittedName[2] ?? null - }else return splittedName[1] ?? null - - }else return null - } -} - - -/**## ODCommandResponderInstance `class` - * This is an Open Ticket command responder instance. - * - * An instance is an active slash interaction or used text command. You can reply to the command using `reply()` for both slash & text commands. - */ -export class ODCommandResponderInstance { - /**The interaction which is the source of this instance. */ - interaction: discord.ChatInputCommandInteraction|discord.Message - /**The command wich is the source of this instance. */ - cmd:ODSlashCommand|ODTextCommand - /**The type/source of instance. (from text or slash command) */ - type: "message"|"interaction" - /**Did a worker already reply to this instance/interaction? */ - didReply: boolean = false - /**The manager for all options of this command. */ - options: ODCommandResponderInstanceOptions - /**The user who triggered this command. */ - user: discord.User - /**The guild member who triggered this command. */ - member: discord.GuildMember|null - /**The guild where this command was triggered. */ - guild: discord.Guild|null - /**The channel where this command was triggered. */ - channel: discord.TextBasedChannel - - constructor(interaction:discord.ChatInputCommandInteraction|discord.Message, cmd:ODSlashCommand|ODTextCommand, errorCallback:ODResponderTimeoutErrorCallback|null, timeoutMs:number|null, options?:ODTextCommandInteractionOption[]){ - if (!interaction.channel) throw new ODSystemError("ODCommandResponderInstance: Unable to find interaction channel!") - this.interaction = interaction - this.cmd = cmd - this.type = (interaction instanceof discord.Message) ? "message" : "interaction" - this.options = new ODCommandResponderInstanceOptions(interaction,cmd,options) - this.user = (interaction instanceof discord.Message) ? interaction.author : interaction.user - this.member = (interaction.member instanceof discord.GuildMember) ? interaction.member : null - this.guild = interaction.guild - this.channel = interaction.channel - - - setTimeout(async () => { - if (!this.didReply){ - try { - if (!errorCallback){ - this.reply({id:new ODId("looks-like-we-got-an-error-here"), ephemeral:true, message:{ - content:":x: **Something went wrong while replying to this command!**" - }}) - }else{ - await errorCallback(this,(this.type == "interaction") ? "slash" : "text") - } - - }catch(err){ - process.emit("uncaughtException",err) - } - } - },timeoutMs ?? 2500) - } - - /**Reply to this command. */ - async reply(msg:ODMessageBuildResult): Promise> { - try { - const msgFlags: number[] = msg.ephemeral ? [discord.MessageFlags.Ephemeral] : [] - if (this.type == "interaction" && this.interaction instanceof discord.ChatInputCommandInteraction){ - if (this.interaction.replied || this.interaction.deferred){ - const sent = await this.interaction.editReply(Object.assign(msg.message,{flags:msgFlags})) - this.didReply = true - return {success:true,message:sent} - }else{ - const sent = await this.interaction.reply(Object.assign(msg.message,{flags:msgFlags})) - this.didReply = true - return {success:true,message:await sent.fetch()} - } - }else if (this.type == "message" && this.interaction instanceof discord.Message && this.interaction.channel.type != discord.ChannelType.GroupDM){ - const sent = await this.interaction.channel.send(msg.message) - this.didReply = true - return {success:true,message:sent} - }else return {success:false,message:null} - }catch{ - return {success:false,message:null} - } - } - /**Defer this command. */ - async defer(ephemeral:boolean){ - if (this.type != "interaction" || !(this.interaction instanceof discord.ChatInputCommandInteraction)) return false - if (this.interaction.deferred || this.interaction.replied) return false - const msgFlags: number[] = ephemeral ? [discord.MessageFlags.Ephemeral] : [] - await this.interaction.deferReply({flags:msgFlags}) - this.didReply = true - return true - } - /**Show a modal as reply to this command. */ - async modal(modal:ODModalBuildResult){ - if (this.type != "interaction" || !(this.interaction instanceof discord.ChatInputCommandInteraction)) return false - if (this.interaction.deferred || this.interaction.replied) return false - await this.interaction.showModal(modal.modal) - this.didReply = true - return true - } -} - -/**## ODCommandResponder `class` - * This is an Open Ticket command responder. - * - * This class manages all workers which are executed when the related command is triggered. - */ -export class ODCommandResponder extends ODResponderImplementation { - /**The prefix of the text command needs to match this */ - prefix: string - - constructor(id:ODValidId, prefix:string, match:string|RegExp, callback?:ODWorkerCallback, priority?:number, callbackId?:ODValidId){ - super(id,match,callback,priority,callbackId) - this.prefix = prefix - } - - /**Respond to this command */ - async respond(instance:ODCommandResponderInstance, source:Source, params:Params){ - //wait for workers to finish - await this.workers.executeWorkers(instance,source,params) - } -} - -/**## ODButtonResponderManager `class` - * This is an Open Ticket button responder manager. - * - * It contains all Open Ticket button responders. These can respond to button interactions. - * - * Using the Open Ticket responder system has a few advantages compared to vanilla discord.js: - * - plugins can extend/edit replies - * - automatically reply on error - * - independent workers (with priority) - * - fail-safe design using try-catch - * - know where the request came from! - * - And so much more! - */ -export class ODButtonResponderManager extends ODManager> { - /**An alias to the Open Ticket client manager. */ - #client: ODClientManager - /**The callback executed when the default workers take too much time to reply. */ - #timeoutErrorCallback: ODResponderTimeoutErrorCallback|null = null - /**The amount of milliseconds before the timeout error callback is executed. */ - #timeoutMs: number|null = null - /**A list of listeners which will listen to the raw interactionCreate event from discord.js */ - #listeners: ((interaction:discord.ButtonInteraction) => void)[] = [] - - constructor(debug:ODDebugger, debugname:string, client:ODClientManager){ - super(debug,debugname) - this.#client = client - - this.#client.client.on("interactionCreate",(interaction) => { - if (!interaction.isButton()) return - this.#listeners.forEach((cb) => cb(interaction)) - }) - } - - /**Set the message to send when the response times out! */ - setTimeoutErrorCallback(callback:ODResponderTimeoutErrorCallback|null, ms:number|null){ - this.#timeoutErrorCallback = callback - this.#timeoutMs = ms - } - - add(data:ODButtonResponder<"button",any>, overwrite?:boolean){ - const res = super.add(data,overwrite) - - this.#listeners.push((interaction) => { - const newData = this.get(data.id) - if (!newData) return - if ((typeof newData.match == "string") ? interaction.customId == newData.match : newData.match.test(interaction.customId)) newData.respond(new ODButtonResponderInstance(interaction,this.#timeoutErrorCallback,this.#timeoutMs),"button",{}) - }) - - return res - } -} - -/**## ODButtonResponderInstance `class` - * This is an Open Ticket button responder instance. - * - * An instance is an active button interaction. You can reply to the button using `reply()`. - */ -export class ODButtonResponderInstance { - /**The interaction which is the source of this instance. */ - interaction: discord.ButtonInteraction - /**Did a worker already reply to this instance/interaction? */ - didReply: boolean = false - /**The user who triggered this button. */ - user: discord.User - /**The guild member who triggered this button. */ - member: discord.GuildMember|null - /**The guild where this button was triggered. */ - guild: discord.Guild|null - /**The channel where this button was triggered. */ - channel: discord.TextBasedChannel - /**The message this button originates from. */ - message: discord.Message - - constructor(interaction:discord.ButtonInteraction, errorCallback:ODResponderTimeoutErrorCallback|null, timeoutMs:number|null){ - if (!interaction.channel) throw new ODSystemError("ODButtonResponderInstance: Unable to find interaction channel!") - this.interaction = interaction - this.user = interaction.user - this.member = (interaction.member instanceof discord.GuildMember) ? interaction.member : null - this.guild = interaction.guild - this.channel = interaction.channel - this.message = interaction.message - - setTimeout(async () => { - if (!this.didReply){ - try { - if (!errorCallback){ - this.reply({id:new ODId("looks-like-we-got-an-error-here"), ephemeral:true, message:{ - content:":x: **Something went wrong while replying to this button!**" - }}) - }else{ - await errorCallback(this,"button") - } - - }catch(err){ - process.emit("uncaughtException",err) - } - } - },timeoutMs ?? 2500) - } - - /**Reply to this button. */ - async reply(msg:ODMessageBuildResult): Promise> { - try{ - const msgFlags: number[] = msg.ephemeral ? [discord.MessageFlags.Ephemeral] : [] - if (this.interaction.replied || this.interaction.deferred){ - const sent = await this.interaction.editReply(Object.assign(msg.message,{flags:msgFlags})) - this.didReply = true - return {success:true,message:sent} - }else{ - const sent = await this.interaction.reply(Object.assign(msg.message,{flags:msgFlags})) - this.didReply = true - return {success:true,message:await sent.fetch()} - } - }catch{ - return {success:false,message:null} - } - } - /**Update the message of this button. */ - async update(msg:ODMessageBuildResult): Promise> { - try{ - const msgFlags: number[] = msg.ephemeral ? [discord.MessageFlags.Ephemeral] : [] - if (this.interaction.replied || this.interaction.deferred){ - const sent = await this.interaction.editReply(Object.assign(msg.message,{flags:msgFlags})) - this.didReply = true - return {success:true,message:await sent.fetch()} - }else{ - const sent = await this.interaction.update(Object.assign(msg.message,{flags:msgFlags})) - this.didReply = true - return {success:true,message:await sent.fetch()} - } - }catch{ - return {success:false,message:null} - } - } - /**Defer this button. */ - async defer(type:"reply"|"update", ephemeral:boolean){ - if (this.interaction.deferred || this.interaction.replied) return false - if (type == "reply"){ - const msgFlags: number[] = ephemeral ? [discord.MessageFlags.Ephemeral] : [] - await this.interaction.deferReply({flags:msgFlags}) - }else{ - await this.interaction.deferUpdate() - } - this.didReply = true - return true - } - /**Show a modal as reply to this button. */ - async modal(modal:ODModalBuildResult){ - if (this.interaction.deferred || this.interaction.replied) return false - await this.interaction.showModal(modal.modal) - this.didReply = true - return true - } - - /**Get a component from the original message of this button. */ - getMessageComponent(type:"button",id:string|RegExp): discord.ButtonComponent|null - getMessageComponent(type:"string-dropdown",id:string|RegExp): discord.StringSelectMenuComponent|null - getMessageComponent(type:"user-dropdown",id:string|RegExp): discord.UserSelectMenuComponent|null - getMessageComponent(type:"channel-dropdown",id:string|RegExp): discord.ChannelSelectMenuComponent|null - getMessageComponent(type:"role-dropdown",id:string|RegExp): discord.RoleSelectMenuComponent|null - getMessageComponent(type:"mentionable-dropdown",id:string|RegExp): discord.MentionableSelectMenuComponent|null - - getMessageComponent(type:"button"|"string-dropdown"|"user-dropdown"|"channel-dropdown"|"role-dropdown"|"mentionable-dropdown", id:string|RegExp): discord.ButtonComponent|discord.StringSelectMenuComponent|discord.RoleSelectMenuComponent|discord.ChannelSelectMenuComponent|discord.MentionableSelectMenuComponent|discord.UserSelectMenuComponent|null { - let result: discord.ButtonComponent|discord.StringSelectMenuComponent|discord.RoleSelectMenuComponent|discord.ChannelSelectMenuComponent|discord.MentionableSelectMenuComponent|discord.UserSelectMenuComponent|null = null - this.message.components.forEach((row) => { - if (row.type != discord.ComponentType.ActionRow) return - row.components.forEach((component) => { - if (type == "button" && component.type == discord.ComponentType.Button && component.customId && ((typeof id == "string") ? component.customId == id : id.test(component.customId))) result = component - else if (type == "string-dropdown" && component.type == discord.ComponentType.StringSelect && component.customId && ((typeof id == "string") ? component.customId == id : id.test(component.customId))) result = component - else if (type == "user-dropdown" && component.type == discord.ComponentType.UserSelect && component.customId && ((typeof id == "string") ? component.customId == id : id.test(component.customId))) result = component - else if (type == "channel-dropdown" && component.type == discord.ComponentType.ChannelSelect && component.customId && ((typeof id == "string") ? component.customId == id : id.test(component.customId))) result = component - else if (type == "role-dropdown" && component.type == discord.ComponentType.RoleSelect && component.customId && ((typeof id == "string") ? component.customId == id : id.test(component.customId))) result = component - else if (type == "mentionable-dropdown" && component.type == discord.ComponentType.MentionableSelect && component.customId && ((typeof id == "string") ? component.customId == id : id.test(component.customId))) result = component - }) - }) - - return result - } - - /**Get the first embed of the original message if it exists. */ - getMessageEmbed(): discord.Embed|null { - return this.message.embeds[0] ?? null - } -} - -/**## ODButtonResponder `class` - * This is an Open Ticket button responder. - * - * This class manages all workers which are executed when the related button is triggered. - */ -export class ODButtonResponder extends ODResponderImplementation { - /**Respond to this button */ - async respond(instance:ODButtonResponderInstance, source:Source, params:Params){ - //wait for workers to finish - await this.workers.executeWorkers(instance,source,params) - } -} - -/**## ODDropdownResponderManager `class` - * This is an Open Ticket dropdown responder manager. - * - * It contains all Open Ticket dropdown responders. These can respond to dropdown interactions. - * - * Using the Open Ticket responder system has a few advantages compared to vanilla discord.js: - * - plugins can extend/edit replies - * - automatically reply on error - * - independent workers (with priority) - * - fail-safe design using try-catch - * - know where the request came from! - * - And so much more! - */ -export class ODDropdownResponderManager extends ODManager> { - /**An alias to the Open Ticket client manager. */ - #client: ODClientManager - /**The callback executed when the default workers take too much time to reply. */ - #timeoutErrorCallback: ODResponderTimeoutErrorCallback|null = null - /**The amount of milliseconds before the timeout error callback is executed. */ - #timeoutMs: number|null = null - /**A list of listeners which will listen to the raw interactionCreate event from discord.js */ - #listeners: ((interaction:discord.AnySelectMenuInteraction) => void)[] = [] - - constructor(debug:ODDebugger, debugname:string, client:ODClientManager){ - super(debug,debugname) - this.#client = client - - this.#client.client.on("interactionCreate",(interaction) => { - if (!interaction.isAnySelectMenu()) return - this.#listeners.forEach((cb) => cb(interaction)) - }) - } - - /**Set the message to send when the response times out! */ - setTimeoutErrorCallback(callback:ODResponderTimeoutErrorCallback|null, ms:number|null){ - this.#timeoutErrorCallback = callback - this.#timeoutMs = ms - } - - add(data:ODDropdownResponder<"dropdown",any>, overwrite?:boolean){ - const res = super.add(data,overwrite) - - this.#listeners.push((interaction) => { - const newData = this.get(data.id) - if (!newData) return - if ((typeof newData.match == "string") ? interaction.customId == newData.match : newData.match.test(interaction.customId)) newData.respond(new ODDropdownResponderInstance(interaction,this.#timeoutErrorCallback,this.#timeoutMs),"dropdown",{}) - }) - - return res - } -} - -/**## ODDropdownResponderInstanceValues `class` - * This is an Open Ticket dropdown responder instance values manager. - * - * This class will manage all values from the dropdowns & select menus. - */ -export class ODDropdownResponderInstanceValues { - /**The interaction to get data from. */ - #interaction: discord.AnySelectMenuInteraction - /**The type of this dropdown. */ - #type: ODDropdownData["type"] - - constructor(interaction:discord.AnySelectMenuInteraction, type:ODDropdownData["type"]){ - this.#interaction = interaction - this.#type = type - - if (interaction.isChannelSelectMenu()){ - interaction.values - } - } - - /**Get the selected values. */ - getStringValues(): string[] { - try { - return this.#interaction.values - }catch{ - throw new ODSystemError("ODDropdownResponderInstanceValues:getStringValues() invalid values!") - } - } - /**Get the selected roles. */ - async getRoleValues(): Promise { - if (this.#type != "role") throw new ODSystemError("ODDropdownResponderInstanceValues:getRoleValues() dropdown type isn't role!") - try { - const result: discord.Role[] = [] - for (const id of this.#interaction.values){ - if (!this.#interaction.guild) break - const role = await this.#interaction.guild.roles.fetch(id) - if (role) result.push(role) - } - return result - }catch{ - throw new ODSystemError("ODDropdownResponderInstanceValues:getRoleValues() invalid values!") - } - } - /**Get the selected users. */ - async getUserValues(): Promise { - if (this.#type != "role") throw new ODSystemError("ODDropdownResponderInstanceValues:getUserValues() dropdown type isn't user!") - try { - const result: discord.User[] = [] - for (const id of this.#interaction.values){ - const user = await this.#interaction.client.users.fetch(id) - if (user) result.push(user) - } - return result - }catch{ - throw new ODSystemError("ODDropdownResponderInstanceValues:getUserValues() invalid values!") - } - } - /**Get the selected channels. */ - async getChannelValues(): Promise { - if (this.#type != "role") throw new ODSystemError("ODDropdownResponderInstanceValues:getChannelValues() dropdown type isn't channel!") - try { - const result: discord.GuildBasedChannel[] = [] - for (const id of this.#interaction.values){ - if (!this.#interaction.guild) break - const guild = await this.#interaction.guild.channels.fetch(id) - if (guild) result.push(guild) - } - return result - }catch{ - throw new ODSystemError("ODDropdownResponderInstanceValues:getChannelValues() invalid values!") - } - } -} - -/**## ODDropdownResponderInstance `class` - * This is an Open Ticket dropdown responder instance. - * - * An instance is an active dropdown interaction. You can reply to the dropdown using `reply()`. - */ -export class ODDropdownResponderInstance { - /**The interaction which is the source of this instance. */ - interaction: discord.AnySelectMenuInteraction - /**Did a worker already reply to this instance/interaction? */ - didReply: boolean = false - /**The dropdown type. */ - type: ODDropdownData["type"] - /**The manager for all values of this dropdown. */ - values: ODDropdownResponderInstanceValues - /**The user who triggered this dropdown. */ - user: discord.User - /**The guild member who triggered this dropdown. */ - member: discord.GuildMember|null - /**The guild where this dropdown was triggered. */ - guild: discord.Guild|null - /**The channel where this dropdown was triggered. */ - channel: discord.TextBasedChannel - /**The message this dropdown originates from. */ - message: discord.Message - - constructor(interaction:discord.AnySelectMenuInteraction, errorCallback:ODResponderTimeoutErrorCallback|null, timeoutMs:number|null){ - if (!interaction.channel) throw new ODSystemError("ODDropdownResponderInstance: Unable to find interaction channel!") - this.interaction = interaction - if (interaction.isStringSelectMenu()){ - this.type = "string" - }else if (interaction.isRoleSelectMenu()){ - this.type = "role" - }else if (interaction.isUserSelectMenu()){ - this.type = "user" - }else if (interaction.isChannelSelectMenu()){ - this.type = "channel" - }else if (interaction.isMentionableSelectMenu()){ - this.type = "mentionable" - }else throw new ODSystemError("ODDropdownResponderInstance: invalid dropdown type!") - - this.values = new ODDropdownResponderInstanceValues(interaction,this.type) - this.user = interaction.user - this.member = (interaction.member instanceof discord.GuildMember) ? interaction.member : null - this.guild = interaction.guild - this.channel = interaction.channel - this.message = interaction.message - - setTimeout(async () => { - if (!this.didReply){ - try { - if (!errorCallback){ - this.reply({id:new ODId("looks-like-we-got-an-error-here"), ephemeral:true, message:{ - content:":x: **Something went wrong while replying to this dropdown!**" - }}) - }else{ - await errorCallback(this,"dropdown") - } - - }catch(err){ - process.emit("uncaughtException",err) - } - } - },timeoutMs ?? 2500) - } - - /**Reply to this dropdown. */ - async reply(msg:ODMessageBuildResult): Promise> { - try { - const msgFlags: number[] = msg.ephemeral ? [discord.MessageFlags.Ephemeral] : [] - if (this.interaction.replied || this.interaction.deferred){ - const sent = await this.interaction.editReply(Object.assign(msg.message,{flags:msgFlags})) - this.didReply = true - return {success:true,message:sent} - }else{ - const sent = await this.interaction.reply(Object.assign(msg.message,{flags:msgFlags})) - this.didReply = true - return {success:true,message:await sent.fetch()} - } - }catch{ - return {success:false,message:null} - } - } - /**Update the message of this dropdown. */ - async update(msg:ODMessageBuildResult): Promise> { - try{ - const msgFlags: number[] = msg.ephemeral ? [discord.MessageFlags.Ephemeral] : [] - if (this.interaction.replied || this.interaction.deferred){ - const sent = await this.interaction.editReply(Object.assign(msg.message,{flags:msgFlags})) - this.didReply = true - return {success:true,message:await sent.fetch()} - }else{ - const sent = await this.interaction.update(Object.assign(msg.message,{flags:msgFlags})) - this.didReply = true - return {success:true,message:await sent.fetch()} - } - }catch{ - return {success:false,message:null} - } - } - /**Defer this dropdown. */ - async defer(type:"reply"|"update", ephemeral:boolean){ - if (this.interaction.deferred || this.interaction.replied) return false - if (type == "reply"){ - const msgFlags: number[] = ephemeral ? [discord.MessageFlags.Ephemeral] : [] - await this.interaction.deferReply({flags:msgFlags}) - }else{ - await this.interaction.deferUpdate() - } - this.didReply = true - return true - } - /**Show a modal as reply to this dropdown. */ - async modal(modal:ODModalBuildResult){ - if (this.interaction.deferred || this.interaction.replied) return false - await this.interaction.showModal(modal.modal) - this.didReply = true - return true - } - - /**Get a component from the original message of this dropdown. */ - getMessageComponent(type:"button",id:string|RegExp): discord.ButtonComponent|null - getMessageComponent(type:"string-dropdown",id:string|RegExp): discord.StringSelectMenuComponent|null - getMessageComponent(type:"user-dropdown",id:string|RegExp): discord.UserSelectMenuComponent|null - getMessageComponent(type:"channel-dropdown",id:string|RegExp): discord.ChannelSelectMenuComponent|null - getMessageComponent(type:"role-dropdown",id:string|RegExp): discord.RoleSelectMenuComponent|null - getMessageComponent(type:"mentionable-dropdown",id:string|RegExp): discord.MentionableSelectMenuComponent|null - - getMessageComponent(type:"button"|"string-dropdown"|"user-dropdown"|"channel-dropdown"|"role-dropdown"|"mentionable-dropdown", id:string|RegExp): discord.ButtonComponent|discord.StringSelectMenuComponent|discord.RoleSelectMenuComponent|discord.ChannelSelectMenuComponent|discord.MentionableSelectMenuComponent|discord.UserSelectMenuComponent|null { - let result: discord.ButtonComponent|discord.StringSelectMenuComponent|discord.RoleSelectMenuComponent|discord.ChannelSelectMenuComponent|discord.MentionableSelectMenuComponent|discord.UserSelectMenuComponent|null = null - this.message.components.forEach((row) => { - if (row.type != discord.ComponentType.ActionRow) return - row.components.forEach((component) => { - if (type == "button" && component.type == discord.ComponentType.Button && component.customId && ((typeof id == "string") ? component.customId == id : id.test(component.customId))) result = component - else if (type == "string-dropdown" && component.type == discord.ComponentType.StringSelect && component.customId && ((typeof id == "string") ? component.customId == id : id.test(component.customId))) result = component - else if (type == "user-dropdown" && component.type == discord.ComponentType.UserSelect && component.customId && ((typeof id == "string") ? component.customId == id : id.test(component.customId))) result = component - else if (type == "channel-dropdown" && component.type == discord.ComponentType.ChannelSelect && component.customId && ((typeof id == "string") ? component.customId == id : id.test(component.customId))) result = component - else if (type == "role-dropdown" && component.type == discord.ComponentType.RoleSelect && component.customId && ((typeof id == "string") ? component.customId == id : id.test(component.customId))) result = component - else if (type == "mentionable-dropdown" && component.type == discord.ComponentType.MentionableSelect && component.customId && ((typeof id == "string") ? component.customId == id : id.test(component.customId))) result = component - }) - }) - - return result - } - - /**Get the first embed of the original message if it exists. */ - getMessageEmbed(): discord.Embed|null { - return this.message.embeds[0] ?? null - } -} - -/**## ODDropdownResponder `class` - * This is an Open Ticket dropdown responder. - * - * This class manages all workers which are executed when the related dropdown is triggered. - */ -export class ODDropdownResponder extends ODResponderImplementation { - /**Respond to this dropdown */ - async respond(instance:ODDropdownResponderInstance, source:Source, params:Params){ - //wait for workers to finish - await this.workers.executeWorkers(instance,source,params) - } -} - -/**## ODModalResponderManager `class` - * This is an Open Ticket modal responder manager. - * - * It contains all Open Ticket modal responders. These can respond to modal interactions. - * - * Using the Open Ticket responder system has a few advantages compared to vanilla discord.js: - * - plugins can extend/edit replies - * - automatically reply on error - * - independent workers (with priority) - * - fail-safe design using try-catch - * - know where the request came from! - * - And so much more! - */ -export class ODModalResponderManager extends ODManager> { - /**An alias to the Open Ticket client manager. */ - #client: ODClientManager - /**The callback executed when the default workers take too much time to reply. */ - #timeoutErrorCallback: ODResponderTimeoutErrorCallback|null = null - /**The amount of milliseconds before the timeout error callback is executed. */ - #timeoutMs: number|null = null - /**A list of listeners which will listen to the raw interactionCreate event from discord.js */ - #listeners: ((interaction:discord.ModalSubmitInteraction) => void)[] = [] - - constructor(debug:ODDebugger, debugname:string, client:ODClientManager){ - super(debug,debugname) - this.#client = client - - this.#client.client.on("interactionCreate",(interaction) => { - if (!interaction.isModalSubmit()) return - this.#listeners.forEach((cb) => cb(interaction)) - }) - } - - /**Set the message to send when the response times out! */ - setTimeoutErrorCallback(callback:ODResponderTimeoutErrorCallback|null, ms:number|null){ - this.#timeoutErrorCallback = callback - this.#timeoutMs = ms - } - - add(data:ODModalResponder<"modal",any>, overwrite?:boolean){ - const res = super.add(data,overwrite) - - this.#listeners.push((interaction) => { - const newData = this.get(data.id) - if (!newData) return - if ((typeof newData.match == "string") ? interaction.customId == newData.match : newData.match.test(interaction.customId)) newData.respond(new ODModalResponderInstance(interaction,this.#timeoutErrorCallback,this.#timeoutMs),"modal",{}) - }) - - return res - } -} - -/**## ODModalResponderInstanceValues `class` - * This is an Open Ticket modal responder instance values manager. - * - * This class will manage all fields from the modals. - */ -export class ODModalResponderInstanceValues { - /**The interaction to get data from. */ - #interaction: discord.ModalSubmitInteraction - - constructor(interaction:discord.ModalSubmitInteraction){ - this.#interaction = interaction - } - - /**Get the value of a text field. */ - getTextField(name:string,required:true): string - getTextField(name:string,required:false): string|null - getTextField(name:string,required:boolean){ - try { - const data = this.#interaction.fields.getField(name,discord.ComponentType.TextInput) - if (!data && required) throw new ODSystemError("ODModalResponderInstanceValues:getTextField() field not found!") - return (data) ? data.value : null - }catch{ - throw new ODSystemError("ODModalResponderInstanceValues:getTextField() field not found!") - } - } -} - -/**## ODModalResponderInstance `class` - * This is an Open Ticket modal responder instance. - * - * An instance is an active modal interaction. You can reply to the modal using `reply()`. - */ -export class ODModalResponderInstance { - /**The interaction which is the source of this instance. */ - interaction: discord.ModalSubmitInteraction - /**Did a worker already reply to this instance/interaction? */ - didReply: boolean = false - /**The manager for all fields of this modal. */ - values: ODModalResponderInstanceValues - /**The user who triggered this modal. */ - user: discord.User - /**The guild member who triggered this modal. */ - member: discord.GuildMember|null - /**The guild where this modal was triggered. */ - guild: discord.Guild|null - /**The channel where this modal was triggered. */ - channel: discord.TextBasedChannel|null - - constructor(interaction:discord.ModalSubmitInteraction, errorCallback:ODResponderTimeoutErrorCallback|null, timeoutMs:number|null){ - this.interaction = interaction - this.values = new ODModalResponderInstanceValues(interaction) - this.user = interaction.user - this.member = (interaction.member instanceof discord.GuildMember) ? interaction.member : null - this.guild = interaction.guild - this.channel = interaction.channel - - setTimeout(async () => { - if (!this.didReply){ - try { - if (!errorCallback){ - this.reply({id:new ODId("looks-like-we-got-an-error-here"), ephemeral:true, message:{ - content:":x: **Something went wrong while replying to this modal!**" - }}) - }else{ - await errorCallback(this,"modal") - } - - }catch(err){ - process.emit("uncaughtException",err) - } - } - },timeoutMs ?? 2500) - } - - /**Reply to this modal. */ - async reply(msg:ODMessageBuildResult): Promise> { - try{ - const msgFlags: number[] = msg.ephemeral ? [discord.MessageFlags.Ephemeral] : [] - const sent = await this.interaction.followUp(Object.assign(msg.message,{flags:msgFlags})) - this.didReply = true - return {success:true,message:sent} - }catch{ - return {success:false,message:null} - } - } - /**Update the message of this modal. */ - async update(msg:ODMessageBuildResult): Promise> { - try{ - const msgFlags: number[] = msg.ephemeral ? [discord.MessageFlags.Ephemeral] : [] - if (this.interaction.replied || this.interaction.deferred){ - const sent = await this.interaction.editReply(Object.assign(msg.message,{flags:msgFlags})) - this.didReply = true - return {success:true,message:await sent.fetch()} - }else{ - const sent = await this.interaction.reply(Object.assign(msg.message,{flags:msgFlags})) - this.didReply = true - return {success:true,message:await sent.fetch()} - } - }catch{ - return {success:false,message:null} - } - } - /**Defer this modal. */ - async defer(type:"reply"|"update", ephemeral:boolean){ - if (this.interaction.deferred || this.interaction.replied) return false - if (type == "reply"){ - const msgFlags: number[] = ephemeral ? [discord.MessageFlags.Ephemeral] : [] - await this.interaction.deferReply({flags:msgFlags}) - }else{ - await this.interaction.deferUpdate() - } - this.didReply = true - return true - } -} - -/**## ODModalResponder `class` - * This is an Open Ticket modal responder. - * - * This class manages all workers which are executed when the related modal is triggered. - */ -export class ODModalResponder extends ODResponderImplementation { - /**Respond to this modal */ - async respond(instance:ODModalResponderInstance, source:Source, params:Params){ - //wait for workers to finish - await this.workers.executeWorkers(instance,source,params) - } -} - -/**## ODContextMenuResponderManager `class` - * This is an Open Ticket context menu responder manager. - * - * It contains all Open Ticket context menu responders. These can respond to user/message context menu interactions. - * - * Using the Open Ticket responder system has a few advantages compared to vanilla discord.js: - * - plugins can extend/edit replies - * - automatically reply on error - * - independent workers (with priority) - * - fail-safe design using try-catch - * - know where the request came from! - * - And so much more! - */ -export class ODContextMenuResponderManager extends ODManager> { - /**An alias to the Open Ticket client manager. */ - #client: ODClientManager - /**The callback executed when the default workers take too much time to reply. */ - #timeoutErrorCallback: ODResponderTimeoutErrorCallback|null = null - /**The amount of milliseconds before the timeout error callback is executed. */ - #timeoutMs: number|null = null - - constructor(debug:ODDebugger, debugname:string, client:ODClientManager){ - super(debug,debugname) - this.#client = client - } - - /**Set the message to send when the response times out! */ - setTimeoutErrorCallback(callback:ODResponderTimeoutErrorCallback|null, ms:number|null){ - this.#timeoutErrorCallback = callback - this.#timeoutMs = ms - } - - add(data:ODContextMenuResponder<"context-menu",any>, overwrite?:boolean){ - const res = super.add(data,overwrite) - - this.#client.contextMenus.onInteraction(data.match,(interaction,cmd) => { - const newData = this.get(data.id) - if (!newData) return - newData.respond(new ODContextMenuResponderInstance(interaction,cmd,this.#timeoutErrorCallback,this.#timeoutMs),"context-menu",{}) - }) - - return res - } -} - -/**## ODContextMenuResponderInstance `class` - * This is an Open Ticket context menu responder instance. - * - * An instance is an active context menu interaction. You can reply to the context menu using `reply()`. - */ -export class ODContextMenuResponderInstance { - /**The interaction which is the source of this instance. */ - interaction: discord.ContextMenuCommandInteraction - /**Did a worker already reply to this instance/interaction? */ - didReply: boolean = false - /**The context menu wich is the source of this instance. */ - menu:ODContextMenu - /**The user who triggered this context menu. */ - user: discord.User - /**The guild member who triggered this context menu. */ - member: discord.GuildMember|null - /**The guild where this context menu was triggered. */ - guild: discord.Guild|null - /**The channel where this context menu was triggered. */ - channel: discord.TextBasedChannel - /**The target of this context menu (user or message). */ - target: discord.Message|discord.User - - constructor(interaction:discord.ContextMenuCommandInteraction, menu:ODContextMenu, errorCallback:ODResponderTimeoutErrorCallback|null, timeoutMs:number|null){ - if (!interaction.channel) throw new ODSystemError("ODContextMenuResponderInstance: Unable to find interaction channel!") - this.interaction = interaction - this.menu = menu - this.user = interaction.user - this.member = (interaction.member instanceof discord.GuildMember) ? interaction.member : null - this.guild = interaction.guild - this.channel = interaction.channel - if (interaction.isMessageContextMenuCommand()) this.target = interaction.targetMessage - else if (interaction.isUserContextMenuCommand()) this.target = interaction.targetUser - else throw new ODSystemError("ODContextMenuResponderInstance: Invalid context menu type. Should be of the type User/Message!") - - setTimeout(async () => { - if (!this.didReply){ - try { - if (!errorCallback){ - this.reply({id:new ODId("looks-like-we-got-an-error-here"), ephemeral:true, message:{ - content:":x: **Something went wrong while replying to this context menu!**" - }}) - }else{ - await errorCallback(this,"context-menu") - } - - }catch(err){ - process.emit("uncaughtException",err) - } - } - },timeoutMs ?? 2500) - } - - /**Reply to this context menu. */ - async reply(msg:ODMessageBuildResult): Promise> { - try{ - const msgFlags: number[] = msg.ephemeral ? [discord.MessageFlags.Ephemeral] : [] - if (this.interaction.replied || this.interaction.deferred){ - const sent = await this.interaction.editReply(Object.assign(msg.message,{flags:msgFlags})) - this.didReply = true - return {success:true,message:sent} - }else{ - const sent = await this.interaction.reply(Object.assign(msg.message,{flags:msgFlags})) - this.didReply = true - return {success:true,message:await sent.fetch()} - } - }catch{ - return {success:false,message:null} - } - } - /**Update the message of this context menu. */ - async update(msg:ODMessageBuildResult): Promise> { - try{ - const msgFlags: number[] = msg.ephemeral ? [discord.MessageFlags.Ephemeral] : [] - if (this.interaction.replied || this.interaction.deferred){ - const sent = await this.interaction.editReply(Object.assign(msg.message,{flags:msgFlags})) - this.didReply = true - return {success:true,message:await sent.fetch()} - }else throw new ODSystemError("Unable to update context menu interaction!") - }catch{ - return {success:false,message:null} - } - } - /**Defer this context menu. */ - async defer(type:"reply", ephemeral:boolean){ - if (this.interaction.deferred || this.interaction.replied) return false - if (type == "reply"){ - const msgFlags: number[] = ephemeral ? [discord.MessageFlags.Ephemeral] : [] - await this.interaction.deferReply({flags:msgFlags}) - } - this.didReply = true - return true - } - /**Show a modal as reply to this context menu. */ - async modal(modal:ODModalBuildResult){ - if (this.interaction.deferred || this.interaction.replied) return false - await this.interaction.showModal(modal.modal) - this.didReply = true - return true - } -} - -/**## ODContextMenuResponder `class` - * This is an Open Ticket context menu responder. - * - * This class manages all workers which are executed when the related context menu is triggered. - */ -export class ODContextMenuResponder extends ODResponderImplementation { - /**Respond to this button */ - async respond(instance:ODContextMenuResponderInstance, source:Source, params:Params){ - //wait for workers to finish - await this.workers.executeWorkers(instance,source,params) - } -} - -/**## ODAutocompleteResponderManager `class` - * This is an Open Ticket autocomplete responder manager. - * - * It contains all Open Ticket autocomplete responders. These can respond to autocomplete interactions. - * - * Using the Open Ticket responder system has a few advantages compared to vanilla discord.js: - * - plugins can extend/edit replies - * - automatically reply on error - * - independent workers (with priority) - * - fail-safe design using try-catch - * - know where the request came from! - * - And so much more! - */ -export class ODAutocompleteResponderManager extends ODManager> { - /**An alias to the Open Ticket client manager. */ - #client: ODClientManager - /**The callback executed when the default workers take too much time to reply. */ - #timeoutErrorCallback: ODResponderTimeoutErrorCallback|null = null - /**The amount of milliseconds before the timeout error callback is executed. */ - #timeoutMs: number|null = null - - constructor(debug:ODDebugger, debugname:string, client:ODClientManager){ - super(debug,debugname) - this.#client = client - } - - /**Set the message to send when the response times out! */ - setTimeoutErrorCallback(callback:ODResponderTimeoutErrorCallback|null, ms:number|null){ - this.#timeoutErrorCallback = callback - this.#timeoutMs = ms - } - - add(data:ODAutocompleteResponder<"autocomplete",any>, overwrite?:boolean){ - const res = super.add(data,overwrite) - - this.#client.autocompletes.onInteraction(data.cmdMatch,data.match,(interaction) => { - const newData = this.get(data.id) - if (!newData) return - newData.respond(new ODAutocompleteResponderInstance(interaction,this.#timeoutErrorCallback,this.#timeoutMs),"autocomplete",{}) - }) - - return res - } -} - -/**## ODAutocompleteResponderInstance `class` - * This is an Open Ticket autocomplete responder instance. - * - * An instance is an active autocomplete interaction. You can reply to the autocomplete using `reply()`. - */ -export class ODAutocompleteResponderInstance { - /**The interaction which is the source of this instance. */ - interaction: discord.AutocompleteInteraction - /**Did a worker already respond to this instance/interaction? */ - didRespond: boolean = false - /**The user who triggered this autocomplete. */ - user: discord.User - /**The guild member who triggered this autocomplete. */ - member: discord.GuildMember|null - /**The guild where this autocomplete was triggered. */ - guild: discord.Guild|null - /**The channel where this autocomplete was triggered. */ - channel: discord.TextBasedChannel - /**The target slash command option of this autocomplete. */ - target: discord.AutocompleteFocusedOption - - constructor(interaction:discord.AutocompleteInteraction, errorCallback:ODResponderTimeoutErrorCallback|null, timeoutMs:number|null){ - if (!interaction.channel) throw new ODSystemError("ODAutocompleteResponderInstance: Unable to find interaction channel!") - this.interaction = interaction - this.user = interaction.user - this.member = (interaction.member instanceof discord.GuildMember) ? interaction.member : null - this.guild = interaction.guild - this.channel = interaction.channel - this.target = interaction.options.getFocused(true) - - setTimeout(async () => { - if (!this.didRespond){ - process.emit("uncaughtException",new ODSystemError("Autocomplete responder instance failed to respond widthin 2.5sec!")) - } - },timeoutMs ?? 2500) - } - - /**Reply to this autocomplete. */ - async autocomplete(choices:(string|discord.ApplicationCommandOptionChoiceData)[]): Promise<{success:boolean}> { - const newChoices: (discord.ApplicationCommandOptionChoiceData)[] = choices.map((raw) => { - if (typeof raw == "string") return {name:raw,value:raw} - else return raw - }) - - try{ - if (this.interaction.responded){ - return {success:false} - }else{ - await this.interaction.respond(newChoices) - this.didRespond = true - return {success:true} - } - }catch(err){ - process.emit("uncaughtException",err) - return {success:false} - } - } - /**Reply to this autocomplete, but filter choices based on the input of the user. */ - async filteredAutocomplete(choices:(string|discord.ApplicationCommandOptionChoiceData)[]): Promise<{success:boolean}> { - const newChoices: (discord.ApplicationCommandOptionChoiceData)[] = choices.map((raw) => { - if (typeof raw == "string") return {name:raw,value:raw} - else return raw - }) - - const filteredChoices = newChoices.filter((choice) => choice.name.startsWith(this.target.value) || choice.value.toString().startsWith(this.target.value)).slice(0,25) - return await this.autocomplete(filteredChoices) - } -} - -/**## ODAutocompleteResponder `class` - * This is an Open Ticket autocomplete responder. - * - * This class manages all workers which are executed when the related autocomplete is triggered. - */ -export class ODAutocompleteResponder extends ODResponderImplementation { - /**The slash command of the autocomplete should match the following regex. */ - cmdMatch: string|RegExp - - constructor(id:ODValidId,cmdMatch:string|RegExp,match:string|RegExp,callback?:ODWorkerCallback,priority?:number,callbackId?:ODValidId){ - super(id,match,callback,priority,callbackId) - this.cmdMatch = cmdMatch - } - - /**Respond to this autocomplete interaction. */ - async respond(instance:ODAutocompleteResponderInstance, source:Source, params:Params){ - //wait for workers to finish - await this.workers.executeWorkers(instance,source,params) - } -} \ No newline at end of file diff --git a/src/core/api/modules/session.ts b/src/core/api/modules/session.ts deleted file mode 100644 index 5be116b..0000000 --- a/src/core/api/modules/session.ts +++ /dev/null @@ -1,155 +0,0 @@ -/////////////////////////////////////// -//SESSION MODULE -/////////////////////////////////////// -import { ODId, ODManager, ODManagerData, ODValidId } from "./base" -import { ODDebugger } from "./console" -import * as crypto from "crypto" - -/**## ODSessionManager `class` - * This is an Open Ticket session manager. - * - * It contains all sessions in Open Ticket. Sessions are a sort of temporary storage which will be cleared when the bot stops. - * Data in sessions have a randomly generated key which will always be unique. - * - * Visit the `ODSession` class for more info - */ -export class ODSessionManager extends ODManager { - constructor(debug:ODDebugger){ - super(debug,"session") - } -} - -/**## ODSessionInstance `interface` - * This interface represents a single session instance. It contains an id, data & some dates. - */ -export interface ODSessionInstance { - /**The id of this session instance. */ - id:string, - /**The creation date of this session instance. */ - creation:number, - /**The custom amount of minutes before this session expires. */ - timeout:number|null, - /**This is the data from this session instance */ - data:any -} - -/**## ODSessionTimeoutCallback `type` - * This is the callback used for session timeout listeners. - */ -export type ODSessionTimeoutCallback = (id:string, timeout:"default"|"custom", data:any, creation:Date) => void - -/**## ODSession `class` - * This is an Open Ticket session. - * - * It can be used to create 100% unique id's for usage in the bot. An id can also store additional data which isn't saved to the filesystem. - * You can almost compare it to the PHP session system. - */ -export class ODSession extends ODManagerData { - /**The history of previously generated instance ids. Used to reduce the risk of generating the same id twice. */ - #idHistory: string[] = [] - /**The max length of the instance id history. */ - #maxIdHistoryLength: number = 500 - /**An array of all the currently active session instances. */ - sessions: ODSessionInstance[] = [] - /**The default amount of minutes before a session automatically stops. */ - timeoutMinutes: number = 30 - /**The id of the auto-timeout session checker interval */ - #intervalId: NodeJS.Timeout - /**Listeners for when a session times-out. */ - #timeoutListeners: ODSessionTimeoutCallback[] = [] - - constructor(id:ODValidId, intervalSeconds?:number){ - super(id) - - //create the auto-timeout session checker - this.#intervalId = setInterval(() => { - const deletableSessions: {instance:ODSessionInstance,reason:"default"|"custom"}[] = [] - - //collect all deletable sessions - this.sessions.forEach((session) => { - if (session.timeout && (new Date().getTime() - session.creation) > session.timeout*60000){ - //stop session => custom timeout - deletableSessions.push({instance:session,reason:"custom"}) - }else if (!session.timeout && (new Date().getTime() - session.creation) > this.timeoutMinutes*60000){ - //stop session => default timeout - deletableSessions.push({instance:session,reason:"default"}) - } - }) - - //permanently delete sessions - deletableSessions.forEach((session) => { - const index = this.sessions.findIndex((s) => s.id === session.instance.id) - this.sessions.splice(index,1) - - //emit timeout listeners - this.#timeoutListeners.forEach((cb) => cb(session.instance.id,session.reason,session.instance.data,new Date(session.instance.creation))) - }) - - },((intervalSeconds) ? (intervalSeconds * 1000) : 60000)) - } - - /**Create a unique hex id of 8 characters and add it to the instance id history */ - #createUniqueId(): string { - const hex = crypto.randomBytes(4).toString("hex") - if (this.#idHistory.includes(hex)){ - return this.#createUniqueId() - }else{ - this.#idHistory.push(hex) - if (this.#idHistory.length > this.#maxIdHistoryLength) this.#idHistory.shift() - return hex - } - } - /**Stop the global interval that automatically deletes timed-out sessions. (This action can't be reverted!) */ - stopAutoTimeout(){ - clearInterval(this.#intervalId) - } - - /**Start a session instance with data. Returns the unique id required to access the session. */ - start(data?:any): string { - const id = this.#createUniqueId() - this.sessions.push({ - id,data, - creation:new Date().getTime(), - timeout:null - }) - return id - } - /**Get the data of a session instance. Returns `null` when not found. */ - data(id:string): any|null { - const session = this.sessions.find((session) => session.id === id) - if (!session) return null - return session.data - } - /**Stop & delete a session instance. Returns `true` when sucessful. */ - stop(id:string): boolean { - const index = this.sessions.findIndex((session) => session.id === id) - if (index < 0) return false - this.sessions.splice(index,1) - return true - } - /**Update the data of a session instance. Returns `true` when sucessful. */ - update(id:string, data:any): boolean { - const session = this.sessions.find((session) => session.id === id) - if (!session) return false - session.data = data - return true - } - /**Change the global or session timeout minutes. Returns `true` when sucessful. */ - setTimeout(min:number, id?:string): boolean { - if (!id){ - //change global timeout minutes - this.timeoutMinutes = min - return true - }else{ - //change session instance timeout minutes - const session = this.sessions.find((session) => session.id === id) - if (!session) return false - session.timeout = min - return true - } - } - /**Listen for a session timeout (default or custom) */ - onTimeout(callback:ODSessionTimeoutCallback){ - this.#timeoutListeners.push(callback) - } -} \ No newline at end of file diff --git a/src/core/api/modules/startscreen.ts b/src/core/api/modules/startscreen.ts deleted file mode 100644 index 9aab04b..0000000 --- a/src/core/api/modules/startscreen.ts +++ /dev/null @@ -1,320 +0,0 @@ -/////////////////////////////////////// -//STARTSCREEN MODULE -/////////////////////////////////////// -import { ODId, ODManager, ODManagerData, ODValidId } from "./base" -import { ODDebugger, ODError, ODLiveStatusManager } from "./console" -import { ODFlag } from "./flag" -import { ODPlugin, ODUnknownCrashedPlugin } from "./plugin" -import ansis from "ansis" - -/**## ODStartScreenComponentRenderCallback `type` - * This is the render function of a startscreen component. It also sends the location of where the component is rendered. - */ -export type ODStartScreenComponentRenderCallback = (location:number) => string|Promise - -/**## ODStartScreenManager `class` - * This is an Open Ticket startscreen manager. - * - * This class is responsible for managing & rendering the startscreen of the bot. - * The startscreen is the part you see when the bot has started up successfully. (e.g. the Open Ticket logo, logs, livestatus, flags, ...) - */ -export class ODStartScreenManager extends ODManager { - /**Alias to the Open Ticket debugger. */ - #debug: ODDebugger - /**Alias to the livestatus manager. */ - livestatus: ODLiveStatusManager - - constructor(debug:ODDebugger,livestatus:ODLiveStatusManager){ - super(debug,"startscreen component") - this.#debug = debug - this.livestatus = livestatus - } - - /**Get all components in sorted order. */ - getSortedComponents(priority:"ascending"|"descending"){ - return this.getAll().sort((a,b) => { - if (priority == "ascending") return a.priority-b.priority - else return b.priority-a.priority - }) - } - /**Render all startscreen components in priority order. */ - async renderAllComponents(){ - const components = this.getSortedComponents("descending") - - let location = 0 - for (const component of components){ - try { - const renderedText = await component.renderAll(location) - console.log(renderedText) - this.#debug.console.debugfile.writeText("[STARTSCREEN] Component: \""+component.id+"\"\n"+ansis.strip(renderedText)) - }catch(e){ - this.#debug.console.log("Unable to render \""+component.id+"\" startscreen component!","error") - this.#debug.console.debugfile.writeErrorMessage(new ODError(e,"uncaughtException")) - } - location++ - } - } -} - -/**## ODStartScreenComponent `class` - * This is an Open Ticket startscreen component. - * - * This component can be rendered to the start screen of the bot. - * An optional priority can be specified to choose the location of the component. - * - * It's recommended to use pre-built components except if you really need a custom one. - */ -export class ODStartScreenComponent extends ODManagerData { - /**The priority of this component. */ - priority: number - /**An optional render function which will be inserted before the default renderer. */ - renderBefore: ODStartScreenComponentRenderCallback|null = null - /**The render function which will render the contents of this component. */ - render: ODStartScreenComponentRenderCallback - /**An optional render function which will be inserted behind the default renderer. */ - renderAfter: ODStartScreenComponentRenderCallback|null = null - - constructor(id:ODValidId, priority:number, render:ODStartScreenComponentRenderCallback){ - super(id) - this.priority = priority - this.render = render - } - - /**Render this component and combine it with the `renderBefore` & `renderAfter` contents. */ - async renderAll(location:number){ - const textBefore = (this.renderBefore) ? await this.renderBefore(location) : "" - const text = await this.render(location) - const textAfter = (this.renderAfter) ? await this.renderAfter(location) : "" - return (textBefore ? textBefore+"\n" : "")+text+(textAfter ? "\n"+textAfter : "") - } -} - -/**## ODStartScreenProperty `type` - * This interface contains properties used in a few default templates of the startscreen component. - */ -export interface ODStartScreenProperty { - /**The key or name of this property. */ - key:string, - /**The value or contents of this property. */ - value:string -} - -/**## ODStartScreenLogoComponent `class` - * This is an Open Ticket startscreen logo component. - * - * This component will render an ASCII art logo (from an array) to the startscreen. Every property in the array is another row. - * An optional priority can be specified to choose the location of the component. - */ -export class ODStartScreenLogoComponent extends ODStartScreenComponent { - /**The ASCII logo contents. */ - logo: string[] - /**When enabled, the component will add a new line above the logo. */ - topPadding: boolean - /**When enabled, the component will add a new line below the logo. */ - bottomPadding: boolean - /**The color of the logo in hex format. */ - logoHexColor: string - - constructor(id:ODValidId, priority:number, logo:string[], topPadding?:boolean, bottomPadding?:boolean, logoHexColor?:string){ - super(id,priority,() => { - const renderedTop = (this.topPadding ? "\n" : "") - const renderedLogo = this.logo.join("\n") - const renderedBottom = (this.bottomPadding ? "\n" : "") - return ansis.hex(this.logoHexColor)(renderedTop+renderedLogo+renderedBottom) - }) - this.logo = logo - this.topPadding = topPadding ?? false - this.bottomPadding = bottomPadding ?? false - this.logoHexColor = logoHexColor ?? "#f8ba00" - } -} - -/**## ODStartScreenHeaderAlignmentSettings `type` - * This interface contains all settings used in the startscreen header component. - */ -export interface ODStartScreenHeaderAlignmentSettings { - /**The alignment settings for this header. */ - align:"center"|"left"|"right", - /**The width or component to use when calculating center & right alignment. */ - width:number|ODStartScreenComponent -} - -/**## ODStartScreenHeaderComponent `class` - * This is an Open Ticket startscreen header component. - * - * This component will render a header to the startscreen. Properties can be aligned left, right or centered. - * An optional priority can be specified to choose the location of the component. - */ -export class ODStartScreenHeaderComponent extends ODStartScreenComponent { - /**All properties of this header component. */ - properties: ODStartScreenProperty[] - /**The spacer used between properties. */ - spacer: string - /**The alignment settings of this header component. */ - align: ODStartScreenHeaderAlignmentSettings|null - - constructor(id:ODValidId, priority:number, properties:ODStartScreenProperty[], spacer?:string, align?:ODStartScreenHeaderAlignmentSettings){ - super(id,priority,async () => { - const renderedProperties = ansis.bold(this.properties.map((prop) => prop.key+": "+prop.value).join(this.spacer)) - if (!this.align || this.align.align == "left"){ - return renderedProperties - }else if (this.align.align == "right"){ - const width = (typeof this.align.width == "number") ? this.align.width : ( - ansis.strip(await this.align.width.renderAll(0)).split("\n").map((row) => row.length).reduce((prev,curr) => { - if (prev < curr) return curr - else return prev - },0) - ) - const offset = width - ansis.strip(renderedProperties).length - if (offset < 0) return renderedProperties - else{ - return (" ".repeat(offset) + renderedProperties) - } - }else if (this.align.align == "center"){ - const width = (typeof this.align.width == "number") ? this.align.width : ( - ansis.strip(await this.align.width.renderAll(0)).split("\n").map((row) => row.length).reduce((prev,curr) => { - if (prev < curr) return curr - else return prev - }) - ) - const offset = Math.round((width - ansis.strip(renderedProperties).length)/2) - if (offset < 0) return renderedProperties - else{ - return (" ".repeat(offset) + renderedProperties) - } - } - return renderedProperties - }) - this.properties = properties - this.spacer = spacer ?? " - " - this.align = align ?? null - } -} - -/**## ODStartScreenCategoryComponent `class` - * This is an Open Ticket startscreen category component. - * - * This component will render a category to the startscreen. This will only render the category name. You'll need to provide your own renderer for the contents. - * An optional priority can be specified to choose the location of the component. - */ -export class ODStartScreenCategoryComponent extends ODStartScreenComponent { - /**The name of this category. */ - name: string - /**When enabled, this category will still be rendered when the contents are empty. (enabled by default) */ - renderIfEmpty: boolean - - constructor(id:ODValidId, priority:number, name:string, render:ODStartScreenComponentRenderCallback, renderIfEmpty?:boolean){ - super(id,priority,async (location) => { - const contents = await render(location) - if (contents != "" || this.renderIfEmpty){ - return ansis.bold.underline("\n"+name.toUpperCase()+(contents != "" ? ":\n" : ":")) + contents - }else return "" - }) - this.name = name - this.renderIfEmpty = renderIfEmpty ?? true - } -} - -/**## ODStartScreenPropertiesCategoryComponent `class` - * This is an Open Ticket startscreen properties category component. - * - * This component will render a properties category to the startscreen. This will list the properties in the category. - * An optional priority can be specified to choose the location of the component. - */ -export class ODStartScreenPropertiesCategoryComponent extends ODStartScreenCategoryComponent { - /**The properties of this category component. */ - properties: ODStartScreenProperty[] - /**The hex color for the key/name of all the properties. */ - propertyHexColor: string - - constructor(id:ODValidId, priority:number, name:string, properties:ODStartScreenProperty[], propertyHexColor?:string, renderIfEmpty?:boolean){ - super(id,priority,name,() => { - return this.properties.map((prop) => ansis.hex(this.propertyHexColor)(prop.key+": ")+prop.value).join("\n") - },renderIfEmpty) - - this.properties = properties - this.propertyHexColor = propertyHexColor ?? "#f8ba00" - } -} - -/**## ODStartScreenFlagsCategoryComponent `class` - * This is an Open Ticket startscreen flags category component. - * - * This component will render a flags category to the startscreen. This will list the enabled flags in the category. - * An optional priority can be specified to choose the location of the component. - */ -export class ODStartScreenFlagsCategoryComponent extends ODStartScreenCategoryComponent { - /**A list of all flags to render. */ - flags: ODFlag[] - - constructor(id:ODValidId, priority:number, flags:ODFlag[]){ - super(id,priority,"flags",() => { - return this.flags.filter((flag) => (flag.value == true)).map((flag) => ansis.blue("["+flag.name+"] "+flag.description)).join("\n") - },false) - this.flags = flags - } -} - -/**## ODStartScreenPluginsCategoryComponent `class` - * This is an Open Ticket startscreen plugins category component. - * - * This component will render a plugins category to the startscreen. This will list the enabled, disabled & crashed plugins in the category. - * An optional priority can be specified to choose the location of the component. - */ -export class ODStartScreenPluginsCategoryComponent extends ODStartScreenCategoryComponent { - /**A list of all plugins to render. */ - plugins: ODPlugin[] - /**A list of all crashed plugins to render. */ - unknownCrashedPlugins: ODUnknownCrashedPlugin[] - - constructor(id:ODValidId, priority:number, plugins:ODPlugin[], unknownCrashedPlugins:ODUnknownCrashedPlugin[]){ - super(id,priority,"plugins",() => { - const disabledPlugins = this.plugins.filter((plugin) => !plugin.enabled) - - const renderedActivePlugins = this.plugins.filter((plugin) => plugin.enabled && plugin.executed).sort((a,b) => b.priority-a.priority).map((plugin) => ansis.green("✅ ["+plugin.name+"] "+plugin.details.shortDescription)) - const renderedCrashedPlugins = this.plugins.filter((plugin) => plugin.enabled && plugin.crashed).sort((a,b) => b.priority-a.priority).map((plugin) => ansis.red("❌ ["+plugin.name+"] "+plugin.details.shortDescription)) - const renderedDisabledPlugins = (disabledPlugins.length > 4) ? [ansis.gray("💤 (+"+disabledPlugins.length+" disabled plugins)")] : disabledPlugins.sort((a,b) => b.priority-a.priority).map((plugin) => ansis.gray("💤 ["+plugin.name+"] "+plugin.details.shortDescription)) - const renderedUnknownPlugins = unknownCrashedPlugins.map((plugin) => ansis.red("❌ ["+plugin.name+"] "+plugin.description)) - - return [ - ...renderedActivePlugins, - ...renderedDisabledPlugins, - ...renderedCrashedPlugins, - ...renderedUnknownPlugins - ].join("\n") - },false) - this.plugins = plugins - this.unknownCrashedPlugins = unknownCrashedPlugins - } -} - -/**## ODStartScreenLiveStatusCategoryComponent `class` - * This is an Open Ticket startscreen livestatus category component. - * - * This component will render a livestatus category to the startscreen. This will list the livestatus messages in the category. - * An optional priority can be specified to choose the location of the component. - */ -export class ODStartScreenLiveStatusCategoryComponent extends ODStartScreenCategoryComponent { - /**A reference to the Open Ticket livestatus manager. */ - livestatus: ODLiveStatusManager - - constructor(id:ODValidId, priority:number, livestatus:ODLiveStatusManager){ - super(id,priority,"livestatus",async () => { - const messages = await this.livestatus.getAllMessages() - return this.livestatus.renderer.render(messages) - },false) - this.livestatus = livestatus - } -} - -/**## ODStartScreenLogsCategoryComponent `class` - * This is an Open Ticket startscreen logs category component. - * - * This component will render a logs category to the startscreen. This will only render the logs category name. - * An optional priority can be specified to choose the location of the component. - */ -export class ODStartScreenLogCategoryComponent extends ODStartScreenCategoryComponent { - constructor(id:ODValidId, priority:number){ - super(id,priority,"logs",() => "",true) - } -} \ No newline at end of file diff --git a/src/core/api/modules/stat.ts b/src/core/api/modules/stat.ts deleted file mode 100644 index dccc1e6..0000000 --- a/src/core/api/modules/stat.ts +++ /dev/null @@ -1,313 +0,0 @@ -/////////////////////////////////////// -//STAT MODULE -/////////////////////////////////////// -import { ODId, ODManager, ODManagerData, ODSystemError, ODValidId } from "./base" -import { ODDebugger } from "./console" -import { ODDatabase, ODJsonDatabaseStructure } from "./database" -import * as discord from "discord.js" - -/**## ODValidStatValue `type` - * These are the only allowed types for a stat value to improve compatibility with different database systems. - */ -export type ODValidStatValue = string|number|boolean - -/**## ODStatsManagerInitCallback `type` - * This callback can be used to execute something when the stats have been initiated. - * - * By default this is used to clear stats from users that left the server or tickets which don't exist anymore. - */ -export type ODStatsManagerInitCallback = (database:ODJsonDatabaseStructure, deletables:ODJsonDatabaseStructure) => void|Promise - -/**## ODStatScopeSetMode `type` - * This type contains all valid methods for changing the value of a stat. - */ -export type ODStatScopeSetMode = "set"|"increase"|"decrease" - -/**## ODStatsManager `class` - * This is an Open Ticket stats manager. - * - * This class is responsible for managing all stats of the bot. - * Stats are categorized in "scopes" which can be accessed in this manager. - * - * Stats can be accessed in the individual scopes. - */ -export class ODStatsManager extends ODManager { - /**Alias to Open Ticket debugger. */ - #debug: ODDebugger - /**Alias to Open Ticket stats database. */ - database: ODDatabase|null = null - /**All the listeners for the init event. */ - #initListeners: ODStatsManagerInitCallback[] = [] - - constructor(debug:ODDebugger){ - super(debug,"stat scope") - this.#debug = debug - } - - /**Select the database to use to read/write all stats from/to. */ - useDatabase(database:ODDatabase){ - this.database = database - } - add(data:ODStatScope, overwrite?:boolean): boolean { - data.useDebug(this.#debug,"stat") - if (this.database) data.useDatabase(this.database) - return super.add(data,overwrite) - } - /**Init all stats and run `onInit()` listeners. */ - async init(){ - if (!this.database) throw new ODSystemError("Unable to initialize stats scopes due to missing database!") - - //get all valid categories - const validCategories: string[] = [] - for (const scope of this.getAll()){ - validCategories.push(...scope.init()) - } - - //filter out the deletable stats - const deletableStats: ODJsonDatabaseStructure = [] - const data = await this.database.getAll() - data.forEach((data) => { - if (!validCategories.includes(data.category)) deletableStats.push(data) - }) - - //do additional deletion - for (const cb of this.#initListeners){ - await cb(data,deletableStats) - } - - //delete all deletable stats - for (const data of deletableStats){ - if (!this.database) return - await this.database.delete(data.category,data.key) - } - } - /**Reset all stats. (clears the entire database) */ - async reset(){ - if (!this.database) return - const data = await this.database.getAll() - for (const d of data){ - if (!this.database) return - await this.database.delete(d.category,d.key) - } - } - /**Run a function when the stats are initialized. This can be used to clear stats from users that left the server or tickets which don't exist anymore. */ - onInit(callback:ODStatsManagerInitCallback){ - this.#initListeners.push(callback) - } -} - -/**## ODStatScope `class` - * This is an Open Ticket stat scope. - * - * A scope can contain multiple stats. Every scope is seperated from other scopes. - * Here, you can read & write the values of all stats. - * - * The built-in Open Ticket scopes are: `global`, `user`, `ticket` - */ -export class ODStatScope extends ODManager { - /**The id of this statistics scope. */ - id: ODId - /**Is this stat scope already initialized? */ - ready: boolean = false - /**Alias to Open Ticket stats database. */ - database: ODDatabase|null = null - /**The name of this scope (used in embed title) */ - name:string - - constructor(id:ODValidId, name:string){ - super() - this.id = new ODId(id) - this.name = name - } - - /**Select the database to use to read/write all stats from/to. (Automatically assigned when used in `ODStatsManager`) */ - useDatabase(database:ODDatabase){ - this.database = database - } - /**Get the value of a statistic. The `scopeId` is the unique id of the user, channel, role, etc that the stats are related to. */ - async getStat(id:ODValidId, scopeId:string): Promise { - if (!this.database) return null - const newId = new ODId(id) - const data = await this.database.get(this.id.value+"_"+newId.value,scopeId) - - if (typeof data == "undefined"){ - //set stats to default value & return - return this.resetStat(id,scopeId) - }else if (typeof data == "string" || typeof data == "boolean" || typeof data == "number"){ - //return value received from database - return data - } - //return null on error - return null - } - /**Get the value of a statistic for all `scopeId`'s. The `scopeId` is the unique id of the user, channel, role, etc that the stats are related to. */ - async getAllStats(id:ODValidId): Promise<{id:string,value:ODValidStatValue}[]> { - if (!this.database) return [] - const newId = new ODId(id) - const data = await this.database.getCategory(this.id.value+"_"+newId.value) ?? [] - const output: {id:string,value:ODValidStatValue}[] = [] - - for (const stat of data){ - if (typeof stat.value == "string" || typeof stat.value == "boolean" || typeof stat.value == "number"){ - //return value received from database - output.push({id:stat.key,value:stat.value}) - } - } - - //return null on error - return output - } - /**Set, increase or decrease the value of a statistic. The `scopeId` is the unique id of the user, channel, role, etc that the stats are related to. */ - async setStat(id:ODValidId, scopeId:string, value:ODValidStatValue, mode:ODStatScopeSetMode): Promise { - if (!this.database) return false - const stat = this.get(id) - if (!stat) return false - if (mode == "set" || typeof value != "number"){ - await this.database.set(this.id.value+"_"+stat.id.value,scopeId,value) - }else if (mode == "increase"){ - const currentValue = await this.getStat(id,scopeId) - if (typeof currentValue != "number") await this.database.set(this.id.value+"_"+stat.id.value,scopeId,0+value) - else await this.database.set(this.id.value+"_"+stat.id.value,scopeId,currentValue+value) - }else if (mode == "decrease"){ - const currentValue = await this.getStat(id,scopeId) - if (typeof currentValue != "number") await this.database.set(this.id.value+"_"+stat.id.value,scopeId,0-value) - else await this.database.set(this.id.value+"_"+stat.id.value,scopeId,currentValue-value) - } - return true - } - /**Reset the value of a statistic to the initial value. The `scopeId` is the unique id of the user, channel, role, etc that the stats are related to. */ - async resetStat(id:ODValidId, scopeId:string): Promise { - if (!this.database) return null - const stat = this.get(id) - if (!stat) return null - if (stat.value != null) await this.database.set(this.id.value+"_"+stat.id.value,scopeId,stat.value) - return stat.value - } - /**Initialize this stat scope & return a list of all statistic ids in the following format: `_` */ - init(): string[] { - //get all valid stats categories - this.ready = true - return this.getAll().map((stat) => this.id.value+"_"+stat.id.value) - } - /**Render all stats in this scope for usage in a discord message/embed. */ - async render(scopeId:string, guild:discord.Guild, channel:discord.TextBasedChannel, user:discord.User): Promise { - //sort from high priority to low - const derefArray = [...this.getAll()] - derefArray.sort((a,b) => { - return b.priority-a.priority - }) - const result: string[] = [] - - for (const stat of derefArray){ - try { - if (stat instanceof ODDynamicStat){ - //dynamic render (without value) - result.push(await stat.render("",scopeId,guild,channel,user)) - }else{ - //normal render (with value) - const value = await this.getStat(stat.id,scopeId) - if (value != null) result.push(await stat.render(value,scopeId,guild,channel,user)) - } - - }catch(err){ - process.emit("uncaughtException",err) - } - } - - return result.filter((stat) => stat !== "").join("\n") - } -} - -/**## ODStatGlobalScope `class` - * This is an Open Ticket stat global scope. - * - * A scope can contain multiple stats. Every scope is seperated from other scopes. - * Here, you can read & write the values of all stats. - * - * This scope is made specifically for the global stats of Open Ticket. - */ -export class ODStatGlobalScope extends ODStatScope { - getStat(id:ODValidId): Promise { - return super.getStat(id,"GLOBAL") - } - getAllStats(id:ODValidId): Promise<{id:string,value:ODValidStatValue}[]> { - return super.getAllStats(id) - } - setStat(id:ODValidId, value:ODValidStatValue, mode:ODStatScopeSetMode): Promise { - return super.setStat(id,"GLOBAL",value,mode) - } - resetStat(id:ODValidId): Promise { - return super.resetStat(id,"GLOBAL") - } - render(scopeId:"GLOBAL", guild:discord.Guild, channel:discord.TextBasedChannel, user: discord.User): Promise { - return super.render("GLOBAL",guild,channel,user) - } -} - -/**## ODStatRenderer `type` - * This callback will render a single statistic for a discord embed/message. - */ -export type ODStatRenderer = (value:ODValidStatValue, scopeId:string, guild:discord.Guild, channel:discord.TextBasedChannel, user:discord.User) => string|Promise - -/**## ODStat `class` - * This is an Open Ticket statistic. - * - * This single statistic doesn't do anything except defining the rules of this statistic. - * Use it in a stats scope to register a new statistic. A statistic can also include a priority to choose the render priority. - * - * It's recommended to use the `ODBasicStat` & `ODDynamicStat` classes instead of this one! - */ -export class ODStat extends ODManagerData { - /**The priority of this statistic. */ - priority: number - /**The render function of this statistic. */ - render: ODStatRenderer - /**The value of this statistic. */ - value: ODValidStatValue|null - - constructor(id:ODValidId, priority:number, render:ODStatRenderer, value?:ODValidStatValue){ - super(id) - this.priority = priority - this.render = render - this.value = value ?? null - } -} - -/**## ODBasicStat `class` - * This is an Open Ticket basic statistic. - * - * This single statistic will store a number, boolean or string in the database. - * Use it to create a simple statistic for any stats scope. - */ -export class ODBasicStat extends ODStat { - /**The name of this stat. Rendered in discord embeds/messages. */ - name: string - - constructor(id:ODValidId, priority:number, name:string, value:ODValidStatValue){ - super(id,priority,(value) => { - return ""+name+": `"+value.toString()+"`" - },value) - this.name = name - } -} - -/**## ODDynamicStatRenderer `type` - * This callback will render a single dynamic statistic for a discord embed/message. - */ -export type ODDynamicStatRenderer = (scopeId:string, guild:discord.Guild, channel:discord.TextBasedChannel, user:discord.User) => string|Promise - -/**## ODDynamicStat `class` - * This is an Open Ticket dynamic statistic. - * - * A dynamic statistic does not store anything in the database! Instead, it will execute a function to return a custom result. - * This can be used to show statistics which are not stored in the database. - * - * This is used in Open Ticket for the live ticket status, participants & system status. - */ -export class ODDynamicStat extends ODStat { - constructor(id:ODValidId, priority:number, render:ODDynamicStatRenderer){ - super(id,priority,(value,scopeId,guild,channel,user) => { - return render(scopeId,guild,channel,user) - }) - } -} \ No newline at end of file diff --git a/src/core/api/modules/verifybar.ts b/src/core/api/modules/verifybar.ts deleted file mode 100644 index 2b74277..0000000 --- a/src/core/api/modules/verifybar.ts +++ /dev/null @@ -1,61 +0,0 @@ -/////////////////////////////////////// -//VERIFYBAR MODULE -/////////////////////////////////////// -import { ODId, ODManager, ODManagerData, ODValidId } from "./base" -import { ODMessage } from "./builder" -import { ODDebugger } from "./console" -import { ODButtonResponderInstance } from "./responder" -import * as discord from "discord.js" -import { ODWorkerManager } from "./worker" - -/**## ODVerifyBar `class` - * This is an Open Ticket verifybar. - * - * It is contains 2 sets of workers and a lot of utilities for the (✅ ❌) verifybars in the bot. - * - * It doesn't contain the code which activates or spawns the verifybars! - */ -export class ODVerifyBar extends ODManagerData { - /**All workers that will run when the verifybar is accepted. */ - success: ODWorkerManager|null}> - /**All workers that will run when the verifybar is stopped. */ - failure: ODWorkerManager|null}> - /**The message that will be built wen activating this verifybar. */ - message: ODMessage<"verifybar",{guild:discord.Guild|null,channel:discord.TextBasedChannel,user:discord.User,verifybar:ODVerifyBar,originalMessage:discord.Message}> - /**When disabled, it will skip the verifybar and instantly fire the `success` workers. */ - enabled: boolean - - constructor(id:ODValidId, message:ODMessage<"verifybar",{guild:discord.Guild|null,channel:discord.TextBasedChannel,user:discord.User,originalMessage:discord.Message}>, enabled?:boolean){ - super(id) - this.success = new ODWorkerManager("descending") - this.failure = new ODWorkerManager("descending") - this.message = message - this.enabled = enabled ?? true - } - - /**Build the message and reply to a button with this verifybar. */ - async activate(responder:ODButtonResponderInstance){ - if (this.enabled){ - //show verifybar - const {guild,channel,user,message} = responder - await responder.update(await this.message.build("verifybar",{guild,channel,user,verifybar:this,originalMessage:message})) - }else{ - //instant success - if (this.success) await this.success.executeWorkers(responder,"verifybar",{data:null,verifybarMessage:null}) - } - } -} - -/**## ODVerifyBarManager `class` - * This is an Open Ticket verifybar manager. - * - * It contains all (✅ ❌) verifybars in the bot. - * The `ODVerifyBar` classes contain `ODWorkerManager`'s that will be fired when the continue/stop buttons are pressed. - * - * It doesn't contain the code which activates the verifybars! This should be implemented by your own. - */ -export class ODVerifyBarManager extends ODManager { - constructor(debug:ODDebugger){ - super(debug,"verifybar") - } -} \ No newline at end of file diff --git a/src/core/api/modules/worker.ts b/src/core/api/modules/worker.ts deleted file mode 100644 index 566603d..0000000 --- a/src/core/api/modules/worker.ts +++ /dev/null @@ -1,93 +0,0 @@ -/////////////////////////////////////// -//WORKER MODULE -/////////////////////////////////////// -import { ODId, ODManager, ODManagerData, ODValidId } from "./base" - -/**## ODWorkerCallback `type` - * This is the callback used in `ODWorker`! - */ -export type ODWorkerCallback = (instance:Instance, params:Params, source:Source, cancel:() => void) => void|Promise - -/**## ODWorker `class` - * This is an Open Ticket worker. - * - * You can compare it with a normal javascript callback, but slightly more advanced! - * - * - It has an `id` for identification of the function - * - A `priority` to know when to execute this callback (related to others) - * - It knows who called this callback (`source`) - * - And much more! - */ -export class ODWorker extends ODManagerData { - /**The priority of this worker */ - priority: number - /**The main callback of this worker */ - callback: ODWorkerCallback - - constructor(id:ODValidId, priority:number, callback:ODWorkerCallback){ - super(id) - this.priority = priority - this.callback = callback - } -} - -/**## ODWorker `class` - * This is an Open Ticket worker manager. - * - * It manages & executes `ODWorker`'s in the correct order. - * - * You can register a custom worker in this class to create a message or button. - */ -export class ODWorkerManager extends ODManager> { - /**The order of execution for workers inside this manager. */ - #priorityOrder: "ascending"|"descending" - /**The backup worker will be executed when one of the workers fails or cancels execution. */ - backupWorker: ODWorker<{reason:"error"|"cancel"},Source,Params>|null = null - - constructor(priorityOrder:"ascending"|"descending"){ - super() - this.#priorityOrder = priorityOrder - } - - /**Get all workers in sorted order. */ - getSortedWorkers(priority:"ascending"|"descending"){ - const derefArray = [...this.getAll()] - - return derefArray.sort((a,b) => { - if (priority == "ascending") return a.priority-b.priority - else return b.priority-a.priority - }) - } - /**Execute all workers on an instance using the given source & parameters. */ - async executeWorkers(instance:Instance, source:Source, params:Params){ - const derefParams = {...params} - const workers = this.getSortedWorkers(this.#priorityOrder) - let didCancel = false - let didCrash = false - - for (const worker of workers){ - if (didCancel) break - try { - await worker.callback(instance,derefParams,source,() => { - didCancel = true - }) - }catch(err){ - process.emit("uncaughtException",err) - didCrash = true - } - } - if (didCancel && this.backupWorker){ - try{ - await this.backupWorker.callback({reason:"cancel"},derefParams,source,() => {}) - }catch(err){ - process.emit("uncaughtException",err) - } - }else if (didCrash && this.backupWorker){ - try{ - await this.backupWorker.callback({reason:"error"},derefParams,source,() => {}) - }catch(err){ - process.emit("uncaughtException",err) - } - } - } -} \ No newline at end of file diff --git a/src/core/api/openticket/question.ts b/src/core/api/openticket/question.ts deleted file mode 100644 index 5f19a4b..0000000 --- a/src/core/api/openticket/question.ts +++ /dev/null @@ -1,234 +0,0 @@ -/////////////////////////////////////// -//OPENTICKET OPTION MODULE -/////////////////////////////////////// -import { ODId, ODManager, ODValidJsonType, ODValidId, ODVersion, ODManagerData } from "../modules/base" -import { ODDebugger } from "../modules/console" - -/**## ODQuestionManager `class` - * This is an Open Ticket question manager. - * - * This class manages all registered questions in the bot. Only questions which are available in this manager can be used in options. - * - * Questions are not stored in the database and will be parsed from the config every startup. - */ -export class ODQuestionManager extends ODManager { - /**A reference to the Open Ticket debugger. */ - #debug: ODDebugger - - constructor(debug:ODDebugger){ - super(debug,"question") - this.#debug = debug - } - - add(data:ODQuestion, overwrite?:boolean): boolean { - data.useDebug(this.#debug,"question data") - return super.add(data,overwrite) - } -} - -/**## ODQuestionDataJson `interface` - * The JSON representatation from a single question property. - */ -export interface ODQuestionDataJson { - /**The id of this property. */ - id:string, - /**The value of this property. */ - value:ODValidJsonType -} - -/**## ODQuestionDataJson `interface` - * The JSON representatation from a single question. - */ -export interface ODQuestionJson { - /**The id of this question. */ - id:string, - /**The type of this question. */ - type:string, - /**The version of Open Ticket used to create this question. */ - version:string, - /**The full list of properties/variables related to this question. */ - data:ODQuestionDataJson[] -} - -/**## ODQuestion `class` - * This is an Open Ticket question. - * - * This class contains all data related to this question (parsed from the config). - * - * Use `ODShortQuestion` or `ODParagraphQuestion` instead! - */ -export class ODQuestion extends ODManager> { - /**The id of this question. (from the config) */ - id:ODId - /**The type of this question (e.g. `opendiscord:short` or `opendiscord:paragraph`) */ - type: string - - constructor(id:ODValidId, type:string, data:ODQuestionData[]){ - super() - this.id = new ODId(id) - this.type = type - data.forEach((data) => { - this.add(data) - }) - } - - /**Convert this question to a JSON object for storing this question in the database. */ - toJson(version:ODVersion): ODQuestionJson { - const data = this.getAll().map((data) => { - return { - id:data.id.toString(), - value:data.value - } - }) - - return { - id:this.id.toString(), - type:this.type, - version:version.toString(), - data - } - } - - /**Create a question from a JSON object in the database. */ - static fromJson(json:ODQuestionJson): ODQuestion { - return new ODQuestion(json.id,json.type,json.data.map((data) => new ODQuestionData(data.id,data.value))) - } -} - -/**## ODQuestionData `class` - * This is Open Ticket question data. - * - * This class contains a single property for a question. (string, number, boolean, object, array, null) - * - * When this property is edited, the database will be updated automatically. - */ -export class ODQuestionData extends ODManagerData { - /**The value of this property. */ - #value: DataType - - constructor(id:ODValidId, value:DataType){ - super(id) - this.#value = value - } - - /**The value of this property. */ - set value(value:DataType){ - this.#value = value - this._change() - } - get value(): DataType { - return this.#value - } - /**Refresh the database. Is only required to be used when updating `ODQuestionData` with an object/array as value. */ - refreshDatabase(){ - this._change() - } -} - -/**## ODShortQuestionIds `type` - * This interface is a list of ids available in the `ODShortQuestion` class. - * It's used to generate typescript declarations for this class. - */ -export interface ODShortQuestionIds { - "opendiscord:name":ODQuestionData, - "opendiscord:required":ODQuestionData, - "opendiscord:placeholder":ODQuestionData, - - "opendiscord:length-enabled":ODQuestionData, - "opendiscord:length-min":ODQuestionData, - "opendiscord:length-max":ODQuestionData -} - -/**## ODShortQuestion `class` - * This is an Open Ticket short question. - * - * This class contains all data related to an Open Ticket short question (parsed from the config). - * - * Use this question in an option to add a short text field to the modal! - */ -export class ODShortQuestion extends ODQuestion { - type: "opendiscord:short" = "opendiscord:short" - - constructor(id:ODValidId, data:ODQuestionData[]){ - super(id,"opendiscord:short",data) - } - - get(id:QuestionId): ODShortQuestionIds[QuestionId] - get(id:ODValidId): ODQuestionData|null - - get(id:ODValidId): ODQuestionData|null { - return super.get(id) - } - - remove(id:QuestionId): ODShortQuestionIds[QuestionId] - remove(id:ODValidId): ODQuestionData|null - - remove(id:ODValidId): ODQuestionData|null { - return super.remove(id) - } - - exists(id:keyof ODShortQuestionIds): boolean - exists(id:ODValidId): boolean - - exists(id:ODValidId): boolean { - return super.exists(id) - } - - static fromJson(json: ODQuestionJson): ODShortQuestion { - return new ODShortQuestion(json.id,json.data.map((data) => new ODQuestionData(data.id,data.value))) - } -} - -/**## ODParagraphQuestionIds `type` - * This interface is a list of ids available in the `ODParagraphQuestion` class. - * It's used to generate typescript declarations for this class. - */ -export interface ODParagraphQuestionIds { - "opendiscord:name":ODQuestionData, - "opendiscord:required":ODQuestionData, - "opendiscord:placeholder":ODQuestionData, - - "opendiscord:length-enabled":ODQuestionData, - "opendiscord:length-min":ODQuestionData, - "opendiscord:length-max":ODQuestionData -} - -/**## ODParagraphQuestion `class` - * This is an Open Ticket paragraph question. - * - * This class contains all data related to an Open Ticket paragraph question (parsed from the config). - * - * Use this question in an option to add a paragraph text field to the modal! - */ -export class ODParagraphQuestion extends ODQuestion { - type: "opendiscord:paragraph" = "opendiscord:paragraph" - - constructor(id:ODValidId, data:ODQuestionData[]){ - super(id,"opendiscord:paragraph",data) - } - - get(id:QuestionId): ODParagraphQuestionIds[QuestionId] - get(id:ODValidId): ODQuestionData|null - - get(id:ODValidId): ODQuestionData|null { - return super.get(id) - } - - remove(id:QuestionId): ODParagraphQuestionIds[QuestionId] - remove(id:ODValidId): ODQuestionData|null - - remove(id:ODValidId): ODQuestionData|null { - return super.remove(id) - } - - exists(id:keyof ODParagraphQuestionIds): boolean - exists(id:ODValidId): boolean - - exists(id:ODValidId): boolean { - return super.exists(id) - } - - static fromJson(json: ODQuestionJson): ODParagraphQuestion { - return new ODParagraphQuestion(json.id,json.data.map((data) => new ODQuestionData(data.id,data.value))) - } -} \ No newline at end of file diff --git a/src/core/api/openticket/option.ts b/src/core/api/option.ts similarity index 70% rename from src/core/api/openticket/option.ts rename to src/core/api/option.ts index 69d2be9..a8fedad 100644 --- a/src/core/api/openticket/option.ts +++ b/src/core/api/option.ts @@ -1,13 +1,115 @@ /////////////////////////////////////// //OPENTICKET OPTION MODULE /////////////////////////////////////// -import { ODDatabase } from "../modules/database" -import { ODJsonConfig_DefaultOptionEmbedSettingsType, ODJsonConfig_DefaultOptionPingSettingsType } from "../defaults/config" -import { ODId, ODManager, ODValidJsonType, ODValidId, ODVersion, ODValidButtonColor, ODManagerData, ODSystemError } from "../modules/base" -import { ODDebugger } from "../modules/console" +import * as api from "@open-discord-bots/framework/api" import * as discord from "discord.js" import * as crypto from "crypto" -import { ODRoleUpdateMode } from "./role" +import { ODOptionsJsonConfig_TicketOptionEmbedSettings, ODOptionsJsonConfig_TicketOptionPingSettings } from "../mappings/config.js" +import { ODRoleUpdateMode } from "./role.js" + +/**## ODOptionIdConstraint `type` + * The constraint/layout for id mappings/interfaces of the `ODOption` class. + */ +export type ODOptionIdConstraint = Record> + +/**## ODTicketOptionIdMappings `interface` + * A list of all available IDs in the default `ODTicketOption` class in `opendiscord`. + * It's used to generate typescript declarations for this class. + */ +export interface ODTicketOptionIdMappings extends ODOptionIdConstraint { + "opendiscord:name":ODOptionData, + "opendiscord:description":ODOptionData, + + "opendiscord:button-emoji":ODOptionData, + "opendiscord:button-label":ODOptionData, + "opendiscord:button-color":ODOptionData, + + "opendiscord:admins":ODOptionData, + "opendiscord:admins-readonly":ODOptionData, + "opendiscord:allow-blacklisted-users":ODOptionData, + "opendiscord:questions":ODOptionData, + + "opendiscord:channel-prefix":ODOptionData, + "opendiscord:channel-suffix":ODOptionData<"user-name"|"user-nickname"|"user-id"|"random-number"|"random-hex"|"counter-dynamic"|"counter-fixed">, + "opendiscord:channel-category":ODOptionData, + "opendiscord:channel-topic":ODOptionData, + + "opendiscord:dm-message-enabled":ODOptionData, + "opendiscord:dm-message-text":ODOptionData, + "opendiscord:dm-message-embed":ODOptionData, + + "opendiscord:ticket-message-enabled":ODOptionData, + "opendiscord:ticket-message-text":ODOptionData, + "opendiscord:ticket-message-embed":ODOptionData, + "opendiscord:ticket-message-ping":ODOptionData, + + "opendiscord:autoclose-enable-hours":ODOptionData, + "opendiscord:autoclose-enable-leave":ODOptionData, + "opendiscord:autoclose-disable-claim":ODOptionData, + "opendiscord:autoclose-hours":ODOptionData, + + "opendiscord:autodelete-enable-days":ODOptionData, + "opendiscord:autodelete-enable-leave":ODOptionData, + "opendiscord:autodelete-disable-claim":ODOptionData, + "opendiscord:autodelete-days":ODOptionData, + + "opendiscord:cooldown-enabled":ODOptionData, + "opendiscord:cooldown-minutes":ODOptionData, + + "opendiscord:limits-enabled":ODOptionData, + "opendiscord:limits-maximum-global":ODOptionData, + "opendiscord:limits-maximum-user":ODOptionData + + "opendiscord:slowmode-enabled":ODOptionData, + "opendiscord:slowmode-seconds":ODOptionData, +} + +/**## ODWebsiteOptionIdMappings `interface` + * A list of all available IDs in the default `ODWebsiteOption` class in `opendiscord`. + * It's used to generate typescript declarations for this class. + */ +export interface ODWebsiteOptionIdMappings extends ODOptionIdConstraint { + "opendiscord:name":ODOptionData, + "opendiscord:description":ODOptionData, + + "opendiscord:button-emoji":ODOptionData, + "opendiscord:button-label":ODOptionData, + + "opendiscord:url":ODOptionData, +} + +/**## ODRoleOptionIdMappings `interface` + * A list of all available IDs in the default `ODRoleOption` class in `opendiscord`. + * It's used to generate typescript declarations for this class. + */ +export interface ODRoleOptionIdMappings extends ODOptionIdConstraint { + "opendiscord:name":ODOptionData, + "opendiscord:description":ODOptionData, + + "opendiscord:button-emoji":ODOptionData, + "opendiscord:button-label":ODOptionData, + "opendiscord:button-color":ODOptionData, + + "opendiscord:roles":ODOptionData, + "opendiscord:mode":ODOptionData, + "opendiscord:remove-roles-on-add":ODOptionData, + "opendiscord:add-on-join":ODOptionData +} + +/**## ODSubPanelOptionIdMappings `interface` + * A list of all available IDs in the default `ODSubPanelOption` class in `opendiscord`. + * It's used to generate typescript declarations for this class. + */ +export interface ODSubPanelOptionIdMappings extends ODOptionIdConstraint { + "opendiscord:name":ODOptionData, + "opendiscord:description":ODOptionData, + + "opendiscord:button-emoji":ODOptionData, + "opendiscord:button-label":ODOptionData, + "opendiscord:button-color":ODOptionData, + + "opendiscord:panel-id":ODOptionData +} /**## ODOptionManager `class` * This is an Open Ticket option manager. @@ -16,20 +118,17 @@ import { ODRoleUpdateMode } from "./role" * * All option types including: tickets, websites & reaction roles are stored here. */ -export class ODOptionManager extends ODManager { - /**A reference to the Open Ticket debugger. */ - #debug: ODDebugger +export class ODOptionManager extends api.ODManager { /**The option suffix manager used to generate channel suffixes for ticket names. */ suffix: ODOptionSuffixManager - constructor(debug:ODDebugger){ + constructor(debug:api.ODDebugger){ super(debug,"option") - this.#debug = debug this.suffix = new ODOptionSuffixManager(debug) } add(data:ODOption, overwrite?:boolean): boolean { - data.useDebug(this.#debug,"option data") + data.useDebug(this.debug,"option data") return super.add(data,overwrite) } } @@ -41,7 +140,7 @@ export interface ODOptionDataJson { /**The id of this property. */ id:string, /**The value of this property. */ - value:ODValidJsonType + value:api.ODValidJsonType } /**## ODOptionDataJson `interface` @@ -65,23 +164,43 @@ export interface ODOptionJson { * * It's recommended to use `ODTicketOption`, `ODWebsiteOption` or `ODRoleOption` instead! */ -export class ODOption extends ODManager> { +export abstract class ODOption extends api.ODManager> { /**The id of this option. (from the config) */ - id:ODId + id:api.ODId /**The type of this option. (e.g. `opendiscord:ticket`, `opendiscord:website`, `opendiscord:role`) */ - type: string + abstract readonly type: string - constructor(id:ODValidId, type:string, data:ODOptionData[]){ + constructor(id:api.ODValidId, data:ODOptionData[]){ super() - this.id = new ODId(id) - this.type = type + this.id = new api.ODId(id) data.forEach((data) => { this.add(data) }) } + get>(id:OptionId): IdList[OptionId] + get(id:api.ODValidId): ODOptionData|null + + get(id:api.ODValidId): ODOptionData|null { + return super.get(id) + } + + remove>(id:OptionId): IdList[OptionId] + remove(id:api.ODValidId): ODOptionData|null + + remove(id:api.ODValidId): ODOptionData|null { + return super.remove(id) + } + + exists(id:keyof api.ODNoGeneric): boolean + exists(id:api.ODValidId): boolean + + exists(id:api.ODValidId): boolean { + return super.exists(id) + } + /**Convert this option to a JSON object for storing this option in the database. */ - toJson(version:ODVersion): ODOptionJson { + toJson(version:api.ODVersion): ODOptionJson { const data = this.getAll().map((data) => { return { id:data.id.toString(), @@ -96,11 +215,6 @@ export class ODOption extends ODManager> { data } } - - /**Create an option from a JSON object in the database. */ - static fromJson(json:ODOptionJson): ODOption { - return new ODOption(json.id,json.type,json.data.map((data) => new ODOptionData(data.id,data.value))) - } } /**## ODOptionData `class` @@ -110,22 +224,22 @@ export class ODOption extends ODManager> { * * When this property is edited, the database will be updated automatically. */ -export class ODOptionData extends ODManagerData { +export class ODOptionData extends api.ODManagerData { /**The value of this property. */ - #value: DataType + private rawValue: DataType - constructor(id:ODValidId, value:DataType){ + constructor(id:api.ODValidId, value:DataType){ super(id) - this.#value = value + this.rawValue = value } /**The value of this property. */ set value(value:DataType){ - this.#value = value + this.rawValue = value this._change() } get value(): DataType { - return this.#value + return this.rawValue } /**Refresh the database. Is only required to be used when updating `ODOptionData` with an object/array as value. */ refreshDatabase(){ @@ -133,61 +247,6 @@ export class ODOptionData extends ODManagerDat } } -/**## ODTicketOptionIds `type` - * This interface is a list of ids available in the `ODTicketOption` class. - * It's used to generate typescript declarations for this class. - */ -export interface ODTicketOptionIds { - "opendiscord:name":ODOptionData, - "opendiscord:description":ODOptionData, - - "opendiscord:button-emoji":ODOptionData, - "opendiscord:button-label":ODOptionData, - "opendiscord:button-color":ODOptionData, - - "opendiscord:admins":ODOptionData, - "opendiscord:admins-readonly":ODOptionData, - "opendiscord:allow-blacklisted-users":ODOptionData, - "opendiscord:questions":ODOptionData, - - "opendiscord:channel-prefix":ODOptionData, - "opendiscord:channel-suffix":ODOptionData<"user-name"|"user-nickname"|"user-id"|"random-number"|"random-hex"|"counter-dynamic"|"counter-fixed">, - "opendiscord:channel-category":ODOptionData, - "opendiscord:channel-category-closed":ODOptionData, - "opendiscord:channel-category-backup":ODOptionData, - "opendiscord:channel-categories-claimed":ODOptionData<{user:string,category:string}[]>, - "opendiscord:channel-topic":ODOptionData, - - "opendiscord:dm-message-enabled":ODOptionData, - "opendiscord:dm-message-text":ODOptionData, - "opendiscord:dm-message-embed":ODOptionData, - - "opendiscord:ticket-message-enabled":ODOptionData, - "opendiscord:ticket-message-text":ODOptionData, - "opendiscord:ticket-message-embed":ODOptionData, - "opendiscord:ticket-message-ping":ODOptionData, - - "opendiscord:autoclose-enable-hours":ODOptionData, - "opendiscord:autoclose-enable-leave":ODOptionData, - "opendiscord:autoclose-disable-claim":ODOptionData, - "opendiscord:autoclose-hours":ODOptionData, - - "opendiscord:autodelete-enable-days":ODOptionData, - "opendiscord:autodelete-enable-leave":ODOptionData, - "opendiscord:autodelete-disable-claim":ODOptionData, - "opendiscord:autodelete-days":ODOptionData, - - "opendiscord:cooldown-enabled":ODOptionData, - "opendiscord:cooldown-minutes":ODOptionData, - - "opendiscord:limits-enabled":ODOptionData, - "opendiscord:limits-maximum-global":ODOptionData, - "opendiscord:limits-maximum-user":ODOptionData - - "opendiscord:slowmode-enabled":ODOptionData, - "opendiscord:slowmode-seconds":ODOptionData, -} - /**## ODTicketOption `class` * This is an Open Ticket ticket option. * @@ -195,32 +254,11 @@ export interface ODTicketOptionIds { * * Use this option to create a new ticket! */ -export class ODTicketOption extends ODOption { - type: "opendiscord:ticket" = "opendiscord:ticket" +export class ODTicketOption extends ODOption { + readonly type: "opendiscord:ticket" = "opendiscord:ticket" - constructor(id:ODValidId, data:ODOptionData[]){ - super(id,"opendiscord:ticket",data) - } - - get(id:OptionId): ODTicketOptionIds[OptionId] - get(id:ODValidId): ODOptionData|null - - get(id:ODValidId): ODOptionData|null { - return super.get(id) - } - - remove(id:OptionId): ODTicketOptionIds[OptionId] - remove(id:ODValidId): ODOptionData|null - - remove(id:ODValidId): ODOptionData|null { - return super.remove(id) - } - - exists(id:keyof ODTicketOptionIds): boolean - exists(id:ODValidId): boolean - - exists(id:ODValidId): boolean { - return super.exists(id) + constructor(id:api.ODValidId, data:ODOptionData[]){ + super(id,data) } static fromJson(json: ODOptionJson): ODTicketOption { @@ -228,20 +266,6 @@ export class ODTicketOption extends ODOption { } } -/**## ODWebsiteOptionIds `type` - * This interface is a list of ids available in the `ODWebsiteOption` class. - * It's used to generate typescript declarations for this class. - */ -export interface ODWebsiteOptionIds { - "opendiscord:name":ODOptionData, - "opendiscord:description":ODOptionData, - - "opendiscord:button-emoji":ODOptionData, - "opendiscord:button-label":ODOptionData, - - "opendiscord:url":ODOptionData, -} - /**## ODWebsiteOption `class` * This is an Open Ticket website option. * @@ -249,32 +273,11 @@ export interface ODWebsiteOptionIds { * * Use this option to create a button which links to a website! */ -export class ODWebsiteOption extends ODOption { - type: "opendiscord:website" = "opendiscord:website" +export class ODWebsiteOption extends ODOption { + readonly type: "opendiscord:website" = "opendiscord:website" - constructor(id:ODValidId, data:ODOptionData[]){ - super(id,"opendiscord:website",data) - } - - get(id:OptionId): ODWebsiteOptionIds[OptionId] - get(id:ODValidId): ODOptionData|null - - get(id:ODValidId): ODOptionData|null { - return super.get(id) - } - - remove(id:OptionId): ODWebsiteOptionIds[OptionId] - remove(id:ODValidId): ODOptionData|null - - remove(id:ODValidId): ODOptionData|null { - return super.remove(id) - } - - exists(id:keyof ODWebsiteOptionIds): boolean - exists(id:ODValidId): boolean - - exists(id:ODValidId): boolean { - return super.exists(id) + constructor(id:api.ODValidId, data:ODOptionData[]){ + super(id,data) } static fromJson(json: ODOptionJson): ODWebsiteOption { @@ -282,24 +285,6 @@ export class ODWebsiteOption extends ODOption { } } -/**## ODRoleOptionIds `type` - * This interface is a list of ids available in the `ODRoleOption` class. - * It's used to generate typescript declarations for this class. - */ -export interface ODRoleOptionIds { - "opendiscord:name":ODOptionData, - "opendiscord:description":ODOptionData, - - "opendiscord:button-emoji":ODOptionData, - "opendiscord:button-label":ODOptionData, - "opendiscord:button-color":ODOptionData, - - "opendiscord:roles":ODOptionData, - "opendiscord:mode":ODOptionData, - "opendiscord:remove-roles-on-add":ODOptionData, - "opendiscord:add-on-join":ODOptionData -} - /**## ODRoleOption `class` * This is an Open Ticket role option. * @@ -307,32 +292,11 @@ export interface ODRoleOptionIds { * * Use this option to create a button for reaction roles! */ -export class ODRoleOption extends ODOption { - type: "opendiscord:role" = "opendiscord:role" +export class ODRoleOption extends ODOption { + readonly type: "opendiscord:role" = "opendiscord:role" - constructor(id:ODValidId, data:ODOptionData[]){ - super(id,"opendiscord:role",data) - } - - get(id:OptionId): ODRoleOptionIds[OptionId] - get(id:ODValidId): ODOptionData|null - - get(id:ODValidId): ODOptionData|null { - return super.get(id) - } - - remove(id:OptionId): ODRoleOptionIds[OptionId] - remove(id:ODValidId): ODOptionData|null - - remove(id:ODValidId): ODOptionData|null { - return super.remove(id) - } - - exists(id:keyof ODRoleOptionIds): boolean - exists(id:ODValidId): boolean - - exists(id:ODValidId): boolean { - return super.exists(id) + constructor(id:api.ODValidId, data:ODOptionData[]){ + super(id,data) } static fromJson(json:ODOptionJson): ODRoleOption { @@ -340,6 +304,25 @@ export class ODRoleOption extends ODOption { } } +/**## ODSubPanelOption `class` + * This is an Open Ticket sub-panel option. + * + * This class contains all data related to an Open Ticket sub-panel option (parsed from the config). + * + * Use this option to create a button for sub-panels! + */ +export class ODSubPanelOption extends ODOption { + readonly type: "opendiscord:sub-panel" = "opendiscord:sub-panel" + + constructor(id:api.ODValidId, data:ODOptionData[]){ + super(id,data) + } + + static fromJson(json:ODOptionJson): ODSubPanelOption { + return new ODSubPanelOption(json.id,json.data.map((data) => new ODOptionData(data.id,data.value))) + } +} + /**## ODOptionSuffixManager `class` * This is an Open Ticket option suffix manager. * @@ -347,8 +330,8 @@ export class ODRoleOption extends ODOption { * * All ticket options should have a corresponding option suffix class. */ -export class ODOptionSuffixManager extends ODManager { - constructor(debug:ODDebugger){ +export class ODOptionSuffixManager extends api.ODManager { + constructor(debug:api.ODDebugger){ super(debug,"ticket suffix") } @@ -357,7 +340,7 @@ export class ODOptionSuffixManager extends ODManager { const suffix = this.getAll().find((suffix) => suffix.option.id.value == option.id.value) if (!suffix) return null try{ - const member = await this.#getMember(guild,user) + const member = await this.getMember(guild,user) if (!member) return null return await suffix.getSuffix(member) }catch(err){ @@ -365,7 +348,7 @@ export class ODOptionSuffixManager extends ODManager { return null } } - async #getMember(guild:discord.Guild,user:discord.User){ + private async getMember(guild:discord.Guild,user:discord.User){ try{ return await guild.members.fetch(user.id) }catch{ @@ -381,19 +364,17 @@ export class ODOptionSuffixManager extends ODManager { * * Use `getSuffix()` to get the new suffix! */ -export class ODOptionSuffix extends ODManagerData { +export abstract class ODOptionSuffix extends api.ODManagerData { /**The option of this suffix. */ option: ODTicketOption - constructor(id:ODValidId, option:ODTicketOption){ + constructor(id:api.ODValidId, option:ODTicketOption){ super(id) this.option = option } /**Get the suffix for a new ticket. */ - async getSuffix(member:discord.GuildMember): Promise { - throw new ODSystemError("Tried to use an unimplemented ODOptionSuffix!") - } + abstract getSuffix(member:discord.GuildMember): Promise } /**## ODOptionUserNameSuffix `class` @@ -444,16 +425,16 @@ export class ODOptionUserIdSuffix extends ODOptionSuffix { */ export class ODOptionCounterDynamicSuffix extends ODOptionSuffix { /**The database where the value of this counter is stored. */ - database: ODDatabase + database: api.ODDatabase - constructor(id:ODValidId, option:ODTicketOption, database:ODDatabase){ + constructor(id:api.ODValidId, option:ODTicketOption, database:api.ODDatabase){ super(id,option) this.database = database - this.#init() + this.init() } /**Initialize the database for this suffix. */ - async #init(){ + private async init(){ if (!await this.database.exists("opendiscord:option-suffix-counter",this.option.id.value)) await this.database.set("opendiscord:option-suffix-counter",this.option.id.value,0) } async getSuffix(member:discord.GuildMember): Promise { @@ -474,16 +455,16 @@ export class ODOptionCounterDynamicSuffix extends ODOptionSuffix { */ export class ODOptionCounterFixedSuffix extends ODOptionSuffix { /**The database where the value of this counter is stored. */ - database: ODDatabase + database: api.ODDatabase - constructor(id:ODValidId, option:ODTicketOption, database:ODDatabase){ + constructor(id:api.ODValidId, option:ODTicketOption, database:api.ODDatabase){ super(id,option) this.database = database - this.#init() + this.init() } /**Initialize the database for this suffix. */ - async #init(){ + private async init(){ if (!await this.database.exists("opendiscord:option-suffix-counter",this.option.id.value)) await this.database.set("opendiscord:option-suffix-counter",this.option.id.value,0) } async getSuffix(member:discord.GuildMember): Promise { @@ -508,33 +489,33 @@ export class ODOptionCounterFixedSuffix extends ODOptionSuffix { */ export class ODOptionRandomNumberSuffix extends ODOptionSuffix { /**The database where previous random numbers are stored. */ - database: ODDatabase + database: api.ODDatabase - constructor(id:ODValidId, option:ODTicketOption, database:ODDatabase){ + constructor(id:api.ODValidId, option:ODTicketOption, database:api.ODDatabase){ super(id,option) this.database = database - this.#init() + this.init() } /**Initialize the database for this suffix. */ - async #init(){ + private async init(){ if (!await this.database.exists("opendiscord:option-suffix-history",this.option.id.value)) await this.database.set("opendiscord:option-suffix-history",this.option.id.value,[]) } /**Get a unique number for this suffix. */ - #generateUniqueValue(history:string[]): string { + protected generateUniqueValue(history:string[]): string { const rawNumber = Math.round(Math.random()*1000).toString() let number = rawNumber if (rawNumber.length == 1) number = "000"+rawNumber else if (rawNumber.length == 2) number = "00"+rawNumber else if (rawNumber.length == 3) number = "0"+rawNumber - if (history.includes(number)) return this.#generateUniqueValue(history) + if (history.includes(number)) return this.generateUniqueValue(history) else return number } async getSuffix(member:discord.GuildMember): Promise { const rawCurrentValues = await this.database.get("opendiscord:option-suffix-history",this.option.id.value) const currentValues = ((Array.isArray(rawCurrentValues)) ? rawCurrentValues : []) as string[] - const newValue = this.#generateUniqueValue(currentValues) + const newValue = this.generateUniqueValue(currentValues) currentValues.push(newValue) if (currentValues.length > 50) currentValues.shift() await this.database.set("opendiscord:option-suffix-history",this.option.id.value,currentValues) @@ -551,28 +532,28 @@ export class ODOptionRandomNumberSuffix extends ODOptionSuffix { */ export class ODOptionRandomHexSuffix extends ODOptionSuffix { /**The database where previous random hexes are stored. */ - database: ODDatabase + database: api.ODDatabase - constructor(id:ODValidId, option:ODTicketOption, database:ODDatabase){ + constructor(id:api.ODValidId, option:ODTicketOption, database:api.ODDatabase){ super(id,option) this.database = database - this.#init() + this.init() } /**Initialize the database for this suffix. */ - async #init(){ + private async init(){ if (!await this.database.exists("opendiscord:option-suffix-history",this.option.id.value)) await this.database.set("opendiscord:option-suffix-history",this.option.id.value,[]) } /**Get a unique hex-string for this suffix. */ - #generateUniqueValue(history:string[]): string { + protected generateUniqueValue(history:string[]): string { const hex = crypto.randomBytes(2).toString("hex") - if (history.includes(hex)) return this.#generateUniqueValue(history) + if (history.includes(hex)) return this.generateUniqueValue(history) else return hex } async getSuffix(member:discord.GuildMember): Promise { const rawCurrentValues = await this.database.get("opendiscord:option-suffix-history",this.option.id.value) const currentValues = ((Array.isArray(rawCurrentValues)) ? rawCurrentValues : []) as string[] - const newValue = this.#generateUniqueValue(currentValues) + const newValue = this.generateUniqueValue(currentValues) currentValues.push(newValue) if (currentValues.length > 50) currentValues.shift() await this.database.set("opendiscord:option-suffix-history",this.option.id.value,currentValues) diff --git a/src/core/api/openticket/panel.ts b/src/core/api/panel.ts similarity index 64% rename from src/core/api/openticket/panel.ts rename to src/core/api/panel.ts index 633a0a8..2180f54 100644 --- a/src/core/api/openticket/panel.ts +++ b/src/core/api/panel.ts @@ -1,9 +1,39 @@ /////////////////////////////////////// //OPENTICKET PANEL MODULE /////////////////////////////////////// -import { ODJsonConfig_DefaultPanelEmbedSettingsType } from "../defaults/config" -import { ODId, ODManager, ODValidJsonType, ODValidId, ODVersion, ODValidButtonColor, ODManagerData } from "../modules/base" -import { ODDebugger } from "../modules/console" +import * as api from "@open-discord-bots/framework/api" +import { ODPanelsJsonConfig_PanelEmbedSettings } from "../mappings/config.js" + + +/**## ODPanelIdConstraint `type` + * The constraint/layout for id mappings/interfaces of the `ODPanel` class. + */ +export type ODPanelIdConstraint = Record> + +/**## ODPanelIdMappings `interface` + * A list of all available IDs in the default `ODPanel` class in `opendiscord`. + * It's used to generate typescript declarations for this class. + */ +export interface ODPanelIdMappings extends ODPanelIdConstraint { + "opendiscord:name":ODPanelData, + "opendiscord:options":ODPanelData, + "opendiscord:dropdown":ODPanelData, + + "opendiscord:text":ODPanelData, + "opendiscord:embed":ODPanelData, + + "opendiscord:dropdown-placeholder":ODPanelData, + "opendiscord:maximum-buttons-per-row":ODPanelData, + + "opendiscord:enable-max-tickets-warning-text":ODPanelData, + "opendiscord:enable-max-tickets-warning-embed":ODPanelData, + + "opendiscord:describe-options-layout":ODPanelData<"simple"|"normal"|"detailed">, + "opendiscord:describe-options-custom-title":ODPanelData, + "opendiscord:describe-options-in-text":ODPanelData, + "opendiscord:describe-options-in-embed-fields":ODPanelData, + "opendiscord:describe-options-in-embed-description":ODPanelData +} /**## ODPanelManager `class` * This is an Open Ticket panel manager. @@ -12,17 +42,13 @@ import { ODDebugger } from "../modules/console" * * Panels are not stored in the database and will be parsed from the config every startup. */ -export class ODPanelManager extends ODManager { - /**A reference to the Open Ticket debugger. */ - #debug: ODDebugger - - constructor(debug:ODDebugger){ +export class ODPanelManager extends api.ODManager { + constructor(debug:api.ODDebugger){ super(debug,"option") - this.#debug = debug } add(data:ODPanel, overwrite?:boolean): boolean { - data.useDebug(this.#debug,"option data") + data.useDebug(this.debug,"option data") return super.add(data,overwrite) } } @@ -34,7 +60,7 @@ export interface ODPanelDataJson { /**The id of this property. */ id:string, /**The value of this property. */ - value:ODValidJsonType + value:api.ODValidJsonType } /**## ODPanelDataJson `interface` @@ -49,49 +75,25 @@ export interface ODPanelJson { data:ODPanelDataJson[] } -/**## ODPanelIds `type` - * This interface is a list of ids available in the `ODPanel` class. - * It's used to generate typescript declarations for this class. - */ -export interface ODPanelIds { - "opendiscord:name":ODPanelData, - "opendiscord:options":ODPanelData, - "opendiscord:dropdown":ODPanelData, - - "opendiscord:text":ODPanelData, - "opendiscord:embed":ODPanelData, - - "opendiscord:dropdown-placeholder":ODPanelData, - - "opendiscord:enable-max-tickets-warning-text":ODPanelData, - "opendiscord:enable-max-tickets-warning-embed":ODPanelData, - - "opendiscord:describe-options-layout":ODPanelData<"simple"|"normal"|"detailed">, - "opendiscord:describe-options-custom-title":ODPanelData, - "opendiscord:describe-options-in-text":ODPanelData, - "opendiscord:describe-options-in-embed-fields":ODPanelData, - "opendiscord:describe-options-in-embed-description":ODPanelData -} - /**## ODPanel `class` * This is an Open Ticket panel. * * This class contains all data related to this panel (parsed from the config). */ -export class ODPanel extends ODManager> { +export class ODPanel extends api.ODManager> { /**The id of this panel. (from the config) */ - id:ODId + id:api.ODId - constructor(id:ODValidId, data:ODPanelData[]){ + constructor(id:api.ODValidId, data:ODPanelData[]){ super() - this.id = new ODId(id) + this.id = new api.ODId(id) data.forEach((data) => { this.add(data) }) } /**Convert this panel to a JSON object for storing this panel in the database. */ - toJson(version:ODVersion): ODPanelJson { + toJson(version:api.ODVersion): ODPanelJson { const data = this.getAll().map((data) => { return { id:data.id.toString(), @@ -111,24 +113,24 @@ export class ODPanel extends ODManager> { return new ODPanel(json.id,json.data.map((data) => new ODPanelData(data.id,data.value))) } - get(id:PanelId): ODPanelIds[PanelId] - get(id:ODValidId): ODPanelData|null + get>(id:PanelId): ODPanelIdMappings[PanelId] + get(id:api.ODValidId): ODPanelData|null - get(id:ODValidId): ODPanelData|null { + get(id:api.ODValidId): ODPanelData|null { return super.get(id) } - remove(id:PanelId): ODPanelIds[PanelId] - remove(id:ODValidId): ODPanelData|null + remove>(id:PanelId): ODPanelIdMappings[PanelId] + remove(id:api.ODValidId): ODPanelData|null - remove(id:ODValidId): ODPanelData|null { + remove(id:api.ODValidId): ODPanelData|null { return super.remove(id) } - exists(id:keyof ODPanelIds): boolean - exists(id:ODValidId): boolean + exists(id:keyof api.ODNoGeneric): boolean + exists(id:api.ODValidId): boolean - exists(id:ODValidId): boolean { + exists(id:api.ODValidId): boolean { return super.exists(id) } } @@ -140,22 +142,22 @@ export class ODPanel extends ODManager> { * * When this property is edited, the database will be updated automatically. */ -export class ODPanelData extends ODManagerData { +export class ODPanelData extends api.ODManagerData { /**The value of this property. */ - #value: DataType + private rawValue: DataType - constructor(id:ODValidId, value:DataType){ + constructor(id:api.ODValidId, value:DataType){ super(id) - this.#value = value + this.rawValue = value } /**The value of this property. */ set value(value:DataType){ - this.#value = value + this.rawValue = value this._change() } get value(): DataType { - return this.#value + return this.rawValue } /**Refresh the database. Is only required to be used when updating `ODPanelData` with an object/array as value. */ refreshDatabase(){ diff --git a/src/core/api/openticket/priority.ts b/src/core/api/priority.ts similarity index 59% rename from src/core/api/openticket/priority.ts rename to src/core/api/priority.ts index 90ea38a..1b4dc91 100644 --- a/src/core/api/openticket/priority.ts +++ b/src/core/api/priority.ts @@ -1,41 +1,18 @@ /////////////////////////////////////// //OPENTICKET PRIORITY MODULE /////////////////////////////////////// -import { ODId, ODManager, ODValidJsonType, ODValidId, ODVersion, ODManagerData } from "../modules/base" -import { ODDebugger } from "../modules/console" -import * as discord from "discord.js" +import * as api from "@open-discord-bots/framework/api" -/**## ODPriorityManager `class` - * This is an Open Ticket priority manager. - * - * This class manages all registered priority levels in the bot. - * - * Priorities levels can be changed/updated/translated by plugins to allow for more customisability. +/**## ODPriorityManagerIdConstraint `type` + * The constraint/layout for id mappings/interfaces of the `ODPriorityManager` class. */ -export class ODPriorityManager extends ODManager { - /**A reference to the Open Ticket debugger. */ - #debug: ODDebugger +export type ODPriorityManagerIdConstraint = Record - constructor(debug:ODDebugger){ - super(debug,"priority") - this.#debug = debug - } - - /**Get an `ODPriorityLevel` from the priority level value. Returns a dummy value when the level doesn't exist. */ - getFromPriorityLevel(level:number){ - return this.getAll().find((lvl) => lvl.priority === level) ?? new ODPriorityLevel("opendiscord:unknown",0,"unknown","UNKNOWN_PRIORITY","🚫","🚫") - } - /**List the available priority levels. */ - listAvailableLevels(){ - return this.getAll().map((lvl) => lvl.priority) - } -} - -/**## ODPriorityManagerIds `type` - * This interface is a list of ids available in the `ODPriorityManager` class. +/**## ODPriorityManagerIdMappings `interface` + * A list of all available IDs in the default `ODPriorityManager` class in `opendiscord`. * It's used to generate typescript declarations for this class. */ -export interface ODPriorityManagerIds { +export interface ODPriorityManagerIdMappings extends ODPriorityManagerIdConstraint { "opendiscord:urgent":ODPriorityLevel, "opendiscord:very-high":ODPriorityLevel, "opendiscord:high":ODPriorityLevel, @@ -45,35 +22,54 @@ export interface ODPriorityManagerIds { "opendiscord:none":ODPriorityLevel, } -/**## ODPriorityManager_Default `default_class` - * This is a special class that adds type definitions & typescript to the ODPriorityManager class. - * It doesn't add any extra features! +/**## ODPriorityManager `class` + * This is an Open Ticket priority manager. * - * This default class is made for the global variable `opendiscord.priorities`! + * This class manages all registered priority levels in the bot. + * + * Priorities levels can be changed/updated/translated by plugins to allow for more customisability. */ -export class ODPriorityManager_Default extends ODPriorityManager { - get(id:PriorityId): ODPriorityManagerIds[PriorityId] - get(id:ODValidId): ODPriorityLevel|null +export class ODPriorityManager extends api.ODManager { + constructor(debug:api.ODDebugger){ + super(debug,"priority") + } + + /**Get an `ODPriorityLevel` from the priority level value. Returns a dummy value when the level doesn't exist. */ + getFromPriorityLevel(level:number){ + return this.getAll().find((lvl) => lvl.priority === level) ?? new ODPriorityLevel("opendiscord:unknown",-1,"unknown","UNKNOWN_PRIORITY","🚫","🚫") + } + /**List the available priority levels. */ + listAvailableLevels(){ + return this.getAll().map((lvl) => lvl.priority) + } + + get>(id:PriorityId): IdList[PriorityId] + get(id:api.ODValidId): ODPriorityLevel|null - get(id:ODValidId): ODPriorityLevel|null { + get(id:api.ODValidId): ODPriorityLevel|null { return super.get(id) } - remove(id:PriorityId): ODPriorityManagerIds[PriorityId] - remove(id:ODValidId): ODPriorityLevel|null + remove>(id:PriorityId): IdList[PriorityId] + remove(id:api.ODValidId): ODPriorityLevel|null - remove(id:ODValidId): ODPriorityLevel|null { + remove(id:api.ODValidId): ODPriorityLevel|null { return super.remove(id) } - exists(id:keyof ODPriorityManagerIds): boolean - exists(id:ODValidId): boolean + exists(id:keyof api.ODNoGeneric): boolean + exists(id:api.ODValidId): boolean - exists(id:ODValidId): boolean { + exists(id:api.ODValidId): boolean { return super.exists(id) } } +/**## ODMappedPriorityManager `class + * A special class with types for the Open Ticket `ODPriorityManager` class. + */ +export class ODMappedPriorityManager extends ODPriorityManager {} + /**## ODPriorityLevel `class` * This is an Open Ticket priority level. * @@ -83,7 +79,7 @@ export class ODPriorityManager_Default extends ODPriorityManager { * * #### 🚨 Negative priorities are treated as `disabled/no-priority`! */ -export class ODPriorityLevel extends ODManagerData { +export class ODPriorityLevel extends api.ODManagerData { /**The priority level itself. A negative number (e.g. `-1`) is treated as `disabled/no-priority`. */ priority:number /**The raw name of the level (used in text/slash command inputs). */ @@ -95,7 +91,7 @@ export class ODPriorityLevel extends ODManagerData { /**The emoji added to the channel name when the level is applied to a ticket. */ channelEmoji:string|null - constructor(id:ODValidId,priority:number,rawName:string,displayName:string,displayEmoji:string|null,channelEmoji:string|null){ + constructor(id:api.ODValidId,priority:number,rawName:string,displayName:string,displayEmoji:string|null,channelEmoji:string|null){ super(id) this.priority = priority this.rawName = rawName diff --git a/src/core/api/question.ts b/src/core/api/question.ts new file mode 100644 index 0000000..1dba0c4 --- /dev/null +++ b/src/core/api/question.ts @@ -0,0 +1,362 @@ +/////////////////////////////////////// +//OPENTICKET OPTION MODULE +/////////////////////////////////////// +import * as api from "@open-discord-bots/framework/api" +import * as discord from "discord.js" +import { ODQuestionsJsonConfig_BaseQuestion, ODQuestionsJsonConfig_DropdownChoice, ODQuestionsJsonConfig_RadioCheckboxChoice } from "../mappings/config.js" + +/**## ODQuestionIdConstraint `type` + * The constraint/layout for id mappings/interfaces of the `ODQuestion` class. + */ +export type ODQuestionIdConstraint = Record> + +/**## ODShortQuestionIdMappings `interface` + * A list of all available IDs in the default `ODShortQuestion` class in `opendiscord`. + * It's used to generate typescript declarations for this class. + */ +export interface ODShortQuestionIdMappings extends ODQuestionIdConstraint { + "opendiscord:name":ODQuestionData, + "opendiscord:description":ODQuestionData, + "opendiscord:required":ODQuestionData, + + "opendiscord:placeholder":ODQuestionData, + "opendiscord:length-enabled":ODQuestionData, + "opendiscord:length-min":ODQuestionData, + "opendiscord:length-max":ODQuestionData +} + +/**## ODParagraphQuestionIdMappings `interface` + * A list of all available IDs in the default `ODParagraphQuestion` class in `opendiscord`. + * It's used to generate typescript declarations for this class. + */ +export interface ODParagraphQuestionIdMappings extends ODQuestionIdConstraint { + "opendiscord:name":ODQuestionData, + "opendiscord:description":ODQuestionData, + "opendiscord:required":ODQuestionData, + + "opendiscord:placeholder":ODQuestionData, + "opendiscord:length-enabled":ODQuestionData, + "opendiscord:length-min":ODQuestionData, + "opendiscord:length-max":ODQuestionData +} + +/**## ODDropdownQuestionIdMappings `interface` + * A list of all available IDs in the default `ODDropdownQuestion` class in `opendiscord`. + * It's used to generate typescript declarations for this class. + */ +export interface ODDropdownQuestionIdMappings extends ODQuestionIdConstraint { + "opendiscord:name":ODQuestionData, + "opendiscord:description":ODQuestionData, + "opendiscord:required":ODQuestionData, + + "opendiscord:placeholder":ODQuestionData, + "opendiscord:choices":ODQuestionData +} + +/**## ODRadioSelectQuestionIdMappings `interface` + * A list of all available IDs in the default `ODRadioSelectQuestion` class in `opendiscord`. + * It's used to generate typescript declarations for this class. + */ +export interface ODRadioSelectQuestionIdMappings extends ODQuestionIdConstraint { + "opendiscord:name":ODQuestionData, + "opendiscord:description":ODQuestionData, + "opendiscord:required":ODQuestionData, + + "opendiscord:choices":ODQuestionData +} + +/**## ODCheckboxSelectQuestionIdMappings `interface` + * A list of all available IDs in the default `ODCheckboxSelectQuestion` class in `opendiscord`. + * It's used to generate typescript declarations for this class. + */ +export interface ODCheckboxSelectQuestionIdMappings extends ODQuestionIdConstraint { + "opendiscord:name":ODQuestionData, + "opendiscord:description":ODQuestionData, + "opendiscord:required":ODQuestionData, + + "opendiscord:limits-enabled":ODQuestionData, + "opendiscord:limits-min":ODQuestionData, + "opendiscord:limits-max":ODQuestionData + "opendiscord:choices":ODQuestionData +} + +/**## ODFileUploadQuestionIdMappings `interface` + * A list of all available IDs in the default `ODFileUploadQuestion` class in `opendiscord`. + * It's used to generate typescript declarations for this class. + */ +export interface ODFileUploadQuestionIdMappings extends ODQuestionIdConstraint { + "opendiscord:name":ODQuestionData, + "opendiscord:description":ODQuestionData, + "opendiscord:required":ODQuestionData, + + "opendiscord:limits-enabled":ODQuestionData, + "opendiscord:limits-min":ODQuestionData, + "opendiscord:limits-max":ODQuestionData +} + +/**## ODTextDisplayQuestionIdMappings `interface` + * A list of all available IDs in the default `ODTextDisplayQuestion` class in `opendiscord`. + * It's used to generate typescript declarations for this class. + */ +export interface ODTextDisplayQuestionIdMappings extends ODQuestionIdConstraint { + "opendiscord:text-contents":ODQuestionData +} + +/**## ODQuestionManager `class` + * This is an Open Ticket question manager. + * + * This class manages all registered questions in the bot. Only questions which are available in this manager can be used in options. + * + * Questions are not stored in the database and will be parsed from the config every startup. + */ +export class ODQuestionManager extends api.ODManager { + constructor(debug:api.ODDebugger){ + super(debug,"question") + } + + add(data:ODQuestion, overwrite?:boolean): boolean { + data.useDebug(this.debug,"question data") + return super.add(data,overwrite) + } +} + +/**## ODQuestionDataJson `interface` + * The JSON representatation from a single question property. + */ +export interface ODQuestionDataJson { + /**The id of this property. */ + id:string, + /**The value of this property. */ + value:api.ODValidJsonType +} + +/**## ODQuestionDataJson `interface` + * The JSON representatation from a single question. + */ +export interface ODQuestionJson { + /**The id of this question. */ + id:string, + /**The type of this question. */ + type:string, + /**The version of Open Ticket used to create this question. */ + version:string, + /**The full list of properties/variables related to this question. */ + data:ODQuestionDataJson[] +} + +/**## ODQuestion `class` + * This is an Open Ticket question. + * + * This class contains all question data parsed from the config. + * + * This is an abstract class. Use instances like `ODShortQuestion` or `ODParagraphQuestion` instead. + */ +export abstract class ODQuestion extends api.ODManager> { + /**The id of this question. (from the config) */ + id:api.ODId + /**The type of this question (e.g. `opendiscord:short` or `opendiscord:paragraph`) */ + abstract readonly type: string + + constructor(id:api.ODValidId, data:ODQuestionData[]){ + super() + this.id = new api.ODId(id) + data.forEach((data) => { + this.add(data) + }) + } + + /**Convert this question to a JSON object for storing this question in the database. */ + toJson(version:api.ODVersion): ODQuestionJson { + const data = this.getAll().map((data) => { + return { + id:data.id.toString(), + value:data.value + } + }) + + return { + id:this.id.toString(), + type:this.type, + version:version.toString(), + data + } + } + + get>(id:QuestionId): IdList[QuestionId] + get(id:api.ODValidId): ODQuestionData|null + + get(id:api.ODValidId): ODQuestionData|null { + return super.get(id) + } + + remove>(id:QuestionId): IdList[QuestionId] + remove(id:api.ODValidId): ODQuestionData|null + + remove(id:api.ODValidId): ODQuestionData|null { + return super.remove(id) + } + + exists(id:keyof api.ODNoGeneric): boolean + exists(id:api.ODValidId): boolean + + exists(id:api.ODValidId): boolean { + return super.exists(id) + } +} + +/**## ODQuestionData `class` + * This is Open Ticket question data. + * + * This class contains a single property for a question. (string, number, boolean, object, array, null) + * + * When this property is edited, the database will be updated automatically. + */ +export class ODQuestionData extends api.ODManagerData { + /**The value of this property. */ + private rawValue: DataType + + constructor(id:api.ODValidId, value:DataType){ + super(id) + this.rawValue = value + } + + /**The value of this property. */ + set value(value:DataType){ + this.rawValue = value + this._change() + } + get value(): DataType { + return this.rawValue + } + /**Refresh the database. Is only required to be used when updating `ODQuestionData` with an object/array as value. */ + refreshDatabase(){ + this._change() + } +} + +/**## ODQuestionAnswer `type` + * A question answer stored in the database. + */ +export type ODQuestionAnswer = { + id:string, + name:string, + type:Exclude, + value:string|null +}|{ + id:string, + name:string, + type:"file-upload", + files:{ + id:string, + url:string, + name:string, + title:string|null, + description:string|null, + contentType:string|null, + }[] +} + +/**## ODShortQuestion `class` + * An Open Ticket short question. It contains all config properties of the short question type. + */ +export class ODShortQuestion extends ODQuestion { + readonly type: "opendiscord:short" = "opendiscord:short" + + constructor(id:api.ODValidId, data:ODQuestionData[]){ + super(id,data) + } + + static fromJson(json: ODQuestionJson): ODShortQuestion { + return new ODShortQuestion(json.id,json.data.map((data) => new ODQuestionData(data.id,data.value))) + } +} + +/**## ODParagraphQuestion `class` + * An Open Ticket paragraph question. It contains all config properties of the paragraph question type. + */ +export class ODParagraphQuestion extends ODQuestion { + readonly type: "opendiscord:paragraph" = "opendiscord:paragraph" + + constructor(id:api.ODValidId, data:ODQuestionData[]){ + super(id,data) + } + + static fromJson(json: ODQuestionJson): ODParagraphQuestion { + return new ODParagraphQuestion(json.id,json.data.map((data) => new ODQuestionData(data.id,data.value))) + } +} + +/**## ODDropdownQuestion `class` + * An Open Ticket dropdown question. It contains all config properties of the dropdown question type. + */ +export class ODDropdownQuestion extends ODQuestion { + readonly type: "opendiscord:dropdown" = "opendiscord:dropdown" + + constructor(id:api.ODValidId, data:ODQuestionData[]){ + super(id,data) + } + + static fromJson(json: ODQuestionJson): ODDropdownQuestion { + return new ODDropdownQuestion(json.id,json.data.map((data) => new ODQuestionData(data.id,data.value))) + } +} + +/**## ODRadioSelectQuestion `class` + * An Open Ticket radio-select question. It contains all config properties of the radio-select question type. + */ +export class ODRadioSelectQuestion extends ODQuestion { + readonly type: "opendiscord:radio-select" = "opendiscord:radio-select" + + constructor(id:api.ODValidId, data:ODQuestionData[]){ + super(id,data) + } + + static fromJson(json: ODQuestionJson): ODRadioSelectQuestion { + return new ODRadioSelectQuestion(json.id,json.data.map((data) => new ODQuestionData(data.id,data.value))) + } +} + +/**## ODCheckboxSelectQuestion `class` + * An Open Ticket checkbox-select question. It contains all config properties of the checkbox-select question type. + */ +export class ODCheckboxSelectQuestion extends ODQuestion { + readonly type: "opendiscord:checkbox-select" = "opendiscord:checkbox-select" + + constructor(id:api.ODValidId, data:ODQuestionData[]){ + super(id,data) + } + + static fromJson(json: ODQuestionJson): ODCheckboxSelectQuestion { + return new ODCheckboxSelectQuestion(json.id,json.data.map((data) => new ODQuestionData(data.id,data.value))) + } +} + +/**## ODFileUploadQuestion `class` + * An Open Ticket file-upload question. It contains all config properties of the file-upload question type. + */ +export class ODFileUploadQuestion extends ODQuestion { + readonly type: "opendiscord:file-upload" = "opendiscord:file-upload" + + constructor(id:api.ODValidId, data:ODQuestionData[]){ + super(id,data) + } + + static fromJson(json: ODQuestionJson): ODFileUploadQuestion { + return new ODFileUploadQuestion(json.id,json.data.map((data) => new ODQuestionData(data.id,data.value))) + } +} + +/**## ODTextDisplayQuestion `class` + * An Open Ticket text-display question. It contains all config properties of the text-display question type. + */ +export class ODTextDisplayQuestion extends ODQuestion { + readonly type: "opendiscord:text-display" = "opendiscord:text-display" + + constructor(id:api.ODValidId, data:ODQuestionData[]){ + super(id,data) + } + + static fromJson(json: ODQuestionJson): ODTextDisplayQuestion { + return new ODTextDisplayQuestion(json.id,json.data.map((data) => new ODQuestionData(data.id,data.value))) + } +} \ No newline at end of file diff --git a/src/core/api/openticket/role.ts b/src/core/api/role.ts similarity index 67% rename from src/core/api/openticket/role.ts rename to src/core/api/role.ts index b61c462..c496ea1 100644 --- a/src/core/api/openticket/role.ts +++ b/src/core/api/role.ts @@ -1,10 +1,25 @@ /////////////////////////////////////// //OPENTICKET ROLE MODULE /////////////////////////////////////// -import { ODId, ODManager, ODValidJsonType, ODValidId, ODVersion, ODManagerData } from "../modules/base" -import { ODDebugger } from "../modules/console" +import * as api from "@open-discord-bots/framework/api" import * as discord from "discord.js" +/**## ODRoleIdConstraint `type` + * The constraint/layout for id mappings/interfaces of the `ODRole` class. + */ +export type ODRoleIdConstraint = Record> + +/**## ODRoleIdMappings `interface` + * A list of all available IDs in the default `ODRole` class in `opendiscord`. + * It's used to generate typescript declarations for this class. + */ +export interface ODRoleIdMappings extends ODRoleIdConstraint { + "opendiscord:roles":ODRoleData, + "opendiscord:mode":ODRoleData, + "opendiscord:remove-roles-on-add":ODRoleData, + "opendiscord:add-on-join":ODRoleData +} + /**## ODRoleManager `class` * This is an Open Ticket role manager. * @@ -12,17 +27,13 @@ import * as discord from "discord.js" * * Roles are not stored in the database and will be parsed from the config every startup. */ -export class ODRoleManager extends ODManager { - /**A reference to the Open Ticket debugger. */ - #debug: ODDebugger - - constructor(debug:ODDebugger){ +export class ODRoleManager extends api.ODManager { + constructor(debug:api.ODDebugger){ super(debug,"role") - this.#debug = debug } add(data:ODRole, overwrite?:boolean): boolean { - data.useDebug(this.#debug,"role data") + data.useDebug(this.debug,"role data") return super.add(data,overwrite) } } @@ -34,7 +45,7 @@ export interface ODRoleDataJson { /**The id of this property. */ id:string, /**The value of this property. */ - value:ODValidJsonType + value:api.ODValidJsonType } /**## ODRoleJson `interface` @@ -49,17 +60,6 @@ export interface ODRoleJson { data:ODRoleDataJson[] } -/**## ODRoleIds `type` - * This interface is a list of ids available in the `ODRole` class. - * It's used to generate typescript declarations for this class. - */ -export interface ODRoleIds { - "opendiscord:roles":ODRoleData, - "opendiscord:mode":ODRoleData, - "opendiscord:remove-roles-on-add":ODRoleData, - "opendiscord:add-on-join":ODRoleData -} - /**## ODRole `class` * This is an Open Ticket role. * @@ -67,20 +67,20 @@ export interface ODRoleIds { * * These properties will be used to handle reaction role options. */ -export class ODRole extends ODManager> { +export class ODRole extends api.ODManager> { /**The id of this role. (from the config) */ - id:ODId + id:api.ODId - constructor(id:ODValidId, data:ODRoleData[]){ + constructor(id:api.ODValidId, data:ODRoleData[]){ super() - this.id = new ODId(id) + this.id = new api.ODId(id) data.forEach((data) => { this.add(data) }) } /**Convert this role to a JSON object for storing this role in the database. */ - toJson(version:ODVersion): ODRoleJson { + toJson(version:api.ODVersion): ODRoleJson { const data = this.getAll().map((data) => { return { id:data.id.toString(), @@ -100,24 +100,24 @@ export class ODRole extends ODManager> { return new ODRole(json.id,json.data.map((data) => new ODRoleData(data.id,data.value))) } - get(id:OptionId): ODRoleIds[OptionId] - get(id:ODValidId): ODRoleData|null + get>(id:OptionId): ODRoleIdMappings[OptionId] + get(id:api.ODValidId): ODRoleData|null - get(id:ODValidId): ODRoleData|null { + get(id:api.ODValidId): ODRoleData|null { return super.get(id) } - remove(id:OptionId): ODRoleIds[OptionId] - remove(id:ODValidId): ODRoleData|null + remove>(id:OptionId): ODRoleIdMappings[OptionId] + remove(id:api.ODValidId): ODRoleData|null - remove(id:ODValidId): ODRoleData|null { + remove(id:api.ODValidId): ODRoleData|null { return super.remove(id) } - exists(id:keyof ODRoleIds): boolean - exists(id:ODValidId): boolean + exists(id:keyof api.ODNoGeneric): boolean + exists(id:api.ODValidId): boolean - exists(id:ODValidId): boolean { + exists(id:api.ODValidId): boolean { return super.exists(id) } } @@ -129,22 +129,22 @@ export class ODRole extends ODManager> { * * When this property is edited, the database will be updated automatically. */ -export class ODRoleData extends ODManagerData { +export class ODRoleData extends api.ODManagerData { /**The value of this property. */ - #value: DataType + private rawValue: DataType - constructor(id:ODValidId, value:DataType){ + constructor(id:api.ODValidId, value:DataType){ super(id) - this.#value = value + this.rawValue = value } /**The value of this property. */ set value(value:DataType){ - this.#value = value + this.rawValue = value this._change() } get value(): DataType { - return this.#value + return this.rawValue } /**Refresh the database. Is only required to be used when updating `ODRoleData` with an object/array as value. */ refreshDatabase(){ diff --git a/src/core/api/openticket/ticket.ts b/src/core/api/ticket.ts similarity index 72% rename from src/core/api/openticket/ticket.ts rename to src/core/api/ticket.ts index 2d8203a..4fd5a24 100644 --- a/src/core/api/openticket/ticket.ts +++ b/src/core/api/ticket.ts @@ -1,163 +1,26 @@ /////////////////////////////////////// //OPENTICKET TICKET MODULE /////////////////////////////////////// -import { ODId, ODManager, ODValidJsonType, ODValidId, ODVersion, ODManagerData } from "../modules/base" -import { ODDebugger } from "../modules/console" -import { ODClientManager_Default } from "../defaults/client" -import { ODTicketOption } from "./option" +import * as api from "@open-discord-bots/framework/api" +import { ODTicketOption } from "./option.js" import * as discord from "discord.js" +import { ODQuestionAnswer } from "./question.js" -/**## ODTicketManager `class` - * This is an Open Ticket ticket manager. - * - * This class manages all currently created tickets in the bot. - * - * All tickets which are added, removed or modified in this manager will be updated automatically in the database. +/**## ODTicketIdConstraint `type` + * The constraint/layout for id mappings/interfaces of the `ODTicket` class. */ -export class ODTicketManager extends ODManager { - /**A reference to the main server of the bot */ - #guild: discord.Guild|null = null - /**A reference to the Open Ticket client manager. */ - #client: ODClientManager_Default - /**A reference to the Open Ticket debugger. */ - #debug: ODDebugger +export type ODTicketIdConstraint = Record> - constructor(debug:ODDebugger, client:ODClientManager_Default){ - super(debug,"ticket") - this.#debug = debug - this.#client = client - } - - add(data:ODTicket, overwrite?:boolean): boolean { - data.useDebug(this.#debug,"ticket data") - return super.add(data,overwrite) - } - /**Use a specific guild in this class for fetching the channel*/ - useGuild(guild:discord.Guild|null){ - this.#guild = guild - } - /**Get the discord channel for a specific ticket. */ - async getTicketChannel(ticket:ODTicket): Promise { - if (!this.#guild) return null - try { - const channel = await this.#guild.channels.fetch(ticket.id.value) - if (!channel || !channel.isTextBased()) return null - return channel - }catch{ - return null - } - } - /**Get the main ticket message of a ticket channel when found. */ - async getTicketMessage(ticket:ODTicket): Promise|null> { - const msgId = ticket.get("opendiscord:ticket-message").value - if (!this.#guild || !msgId) return null - try { - const channel = await this.getTicketChannel(ticket) - if (!channel) return null - return await channel.messages.fetch(msgId) - }catch{ - return null - } - } - /**Shortcut for getting a discord.js user within a ticket. */ - async getTicketUser(ticket:ODTicket, user:"creator"|"closer"|"claimer"|"pinner"): Promise { - if (!this.#guild) return null - try { - if (user == "creator"){ - const creatorId = ticket.get("opendiscord:opened-by").value - if (!creatorId) return null - else return (await this.#guild.client.users.fetch(creatorId)) - - }else if (user == "closer"){ - const closerId = ticket.get("opendiscord:closed-by").value - if (!closerId) return null - else return (await this.#guild.client.users.fetch(closerId)) - - }else if (user == "claimer"){ - const claimerId = ticket.get("opendiscord:claimed-by").value - if (!claimerId) return null - else return (await this.#guild.client.users.fetch(claimerId)) - - }else if (user == "pinner"){ - const pinnerId = ticket.get("opendiscord:pinned-by").value - if (!pinnerId) return null - else return (await this.#guild.client.users.fetch(pinnerId)) - - }else return null - }catch {return null} - } - /**Shortcut for getting all users that are able to view a ticket. */ - async getAllTicketParticipants(ticket:ODTicket): Promise<{user:discord.User,role:"creator"|"participant"|"admin"}[]|null> { - if (!this.#guild) return null - const final: {user:discord.User,role:"creator"|"participant"|"admin"}[] = [] - const channel = await this.getTicketChannel(ticket) - if (!channel) return null - - //add creator - const creatorId = ticket.get("opendiscord:opened-by").value - if (creatorId){ - const creator = await this.#client.fetchUser(creatorId) - if (creator) final.push({user:creator,role:"creator"}) - } - - //add participants - const participants = ticket.get("opendiscord:participants").value.filter((p) => p.type == "user") - for (const p of participants){ - if (!final.find((u) => u.user.id == p.id)){ - const participant = await this.#client.fetchUser(p.id) - if (participant) final.push({user:participant,role:"participant"}) - } - } - - //add admin roles - const roles = ticket.get("opendiscord:participants").value.filter((p) => p.type == "role") - for (const r of roles){ - const role = await this.#client.fetchGuildRole(channel.guild,r.id) - if (role){ - role.members.forEach((member) => { - if (final.find((u) => u.user.id == member.id)) return - final.push({user:member.user,role:"admin"}) - }) - } - } - - return final - } -} - -/**## ODTicketDataJson `interface` - * The JSON representatation from a single ticket property. - */ -export interface ODTicketDataJson { - /**The id of this property. */ - id:string, - /**The value of this property. */ - value:ODValidJsonType -} - -/**## ODTicketDataJson `interface` - * The JSON representatation from a single ticket. - */ -export interface ODTicketJson { - /**The id of this ticket. */ - id:string, - /**The option id related to this ticket. */ - option:string, - /**The version of Open Ticket used to create this ticket. */ - version:string, - /**The full list of properties/variables related to this ticket. */ - data:ODTicketDataJson[] -} - -/**## ODTicketIds `type` - * This interface is a list of ids available in the `ODTicket` class. +/**## ODTicketIdMappings `interface` + * A list of all available IDs in the default `ODTicket` class in `opendiscord`. * It's used to generate typescript declarations for this class. */ -export interface ODTicketIds { +export interface ODTicketIdMappings extends ODTicketIdConstraint { "opendiscord:busy":ODTicketData, "opendiscord:ticket-message":ODTicketData, "opendiscord:participants":ODTicketData<{type:"role"|"user",id:string}[]>, "opendiscord:channel-suffix":ODTicketData, + "opendiscord:channel-renamed":ODTicketData, "opendiscord:previous-creators":ODTicketData, "opendiscord:open":ODTicketData, @@ -178,7 +41,7 @@ export interface ODTicketIds { "opendiscord:for-deletion":ODTicketData, "opendiscord:category":ODTicketData, - "opendiscord:category-mode":ODTicketData, + "opendiscord:category-mode":ODTicketData, "opendiscord:autoclose-enabled":ODTicketData, "opendiscord:autoclose-hours":ODTicketData, @@ -186,13 +49,152 @@ export interface ODTicketIds { "opendiscord:autodelete-enabled":ODTicketData, "opendiscord:autodelete-days":ODTicketData, - "opendiscord:answers":ODTicketData<{id:string,name:string,type:"short"|"paragraph",value:string|null}[]>, + "opendiscord:answers":ODTicketData, "opendiscord:priority":ODTicketData, "opendiscord:topic":ODTicketData, "opendiscord:message-sent":ODTicketData, "opendiscord:admin-message-sent":ODTicketData, } +/**## ODTicketManager `class` + * This is an Open Ticket ticket manager. + * + * This class manages all currently created tickets in the bot. + * + * All tickets which are added, removed or modified in this manager will be updated automatically in the database. + */ +export class ODTicketManager extends api.ODManager { + /**A reference to the main server of the bot */ + private guild: discord.Guild|null = null + /**A reference to the Open Ticket client manager. */ + private client: api.ODClientManager + + constructor(debug:api.ODDebugger, client:api.ODClientManager){ + super(debug,"ticket") + this.client = client + } + + add(data:ODTicket, overwrite?:boolean): boolean { + data.useDebug(this.debug,"ticket data") + return super.add(data,overwrite) + } + /**Use a specific guild in this class for fetching the channel*/ + useGuild(guild:discord.Guild|null){ + this.guild = guild + } + /**Get the discord channel for a specific ticket. */ + async getTicketChannel(ticket:ODTicket): Promise { + if (!this.guild) return null + try { + const channel = await this.guild.channels.fetch(ticket.id.value) + if (!channel || !channel.isTextBased()) return null + return channel + }catch{ + return null + } + } + /**Get the main ticket message of a ticket channel when found. */ + async getTicketMessage(ticket:ODTicket): Promise|null> { + const msgId = ticket.get("opendiscord:ticket-message").value + if (!this.guild || !msgId) return null + try { + const channel = await this.getTicketChannel(ticket) + if (!channel) return null + return await channel.messages.fetch(msgId) + }catch{ + return null + } + } + /**Shortcut for getting a discord.js user within a ticket. */ + async getTicketUser(ticket:ODTicket, user:"creator"|"closer"|"claimer"|"pinner"): Promise { + if (!this.guild) return null + try { + if (user == "creator"){ + const creatorId = ticket.get("opendiscord:opened-by").value + if (!creatorId) return null + else return (await this.guild.client.users.fetch(creatorId)) + + }else if (user == "closer"){ + const closerId = ticket.get("opendiscord:closed-by").value + if (!closerId) return null + else return (await this.guild.client.users.fetch(closerId)) + + }else if (user == "claimer"){ + const claimerId = ticket.get("opendiscord:claimed-by").value + if (!claimerId) return null + else return (await this.guild.client.users.fetch(claimerId)) + + }else if (user == "pinner"){ + const pinnerId = ticket.get("opendiscord:pinned-by").value + if (!pinnerId) return null + else return (await this.guild.client.users.fetch(pinnerId)) + + }else return null + }catch {return null} + } + /**Shortcut for getting all users that are able to view a ticket. */ + async getAllTicketParticipants(ticket:ODTicket): Promise<{user:discord.User,role:"creator"|"participant"|"admin"}[]|null> { + if (!this.guild) return null + const final: {user:discord.User,role:"creator"|"participant"|"admin"}[] = [] + const channel = await this.getTicketChannel(ticket) + if (!channel) return null + + //add creator + const creatorId = ticket.get("opendiscord:opened-by").value + if (creatorId){ + const creator = await this.client.fetchUser(creatorId) + if (creator) final.push({user:creator,role:"creator"}) + } + + //add participants + const participants = ticket.get("opendiscord:participants").value.filter((p) => p.type == "user") + for (const p of participants){ + if (!final.find((u) => u.user.id == p.id)){ + const participant = await this.client.fetchUser(p.id) + if (participant) final.push({user:participant,role:"participant"}) + } + } + + //add admin roles + const roles = ticket.get("opendiscord:participants").value.filter((p) => p.type == "role") + for (const r of roles){ + const role = await this.client.fetchGuildRole(channel.guild,r.id) + if (role){ + role.members.forEach((member) => { + if (final.find((u) => u.user.id == member.id)) return + final.push({user:member.user,role:"admin"}) + }) + } + } + + return final + } +} + +/**## ODTicketDataJson `interface` + * The JSON representatation from a single ticket property. + */ +export interface ODTicketDataJson { + /**The id of this property. */ + id:string, + /**The value of this property. */ + value:api.ODValidJsonType +} + +/**## ODTicketDataJson `interface` + * The JSON representatation from a single ticket. + */ +export interface ODTicketJson { + /**The id of this ticket. */ + id:string, + /**The option id related to this ticket. */ + option:string, + /**The version of Open Ticket used to create this ticket. */ + version:string, + /**The full list of properties/variables related to this ticket. */ + data:ODTicketDataJson[] +} + /**## ODTicket `class` * This is an Open Ticket ticket. * @@ -200,32 +202,32 @@ export interface ODTicketIds { * * These properties contain the current state of the ticket & are used by actions like claiming, pinning, closing, ... */ -export class ODTicket extends ODManager> { +export class ODTicket extends api.ODManager> { /**The id of this ticket. (discord channel id) */ - id:ODId - /**The option related to this ticket. */ - #option: ODTicketOption + id:api.ODId + /**The option this ticket is made of. */ + private rawOption: ODTicketOption - constructor(id:ODValidId, option:ODTicketOption, data:ODTicketData[]){ + constructor(id:api.ODValidId, option:ODTicketOption, data:ODTicketData[]){ super() - this.id = new ODId(id) - this.#option = option + this.id = new api.ODId(id) + this.rawOption = option data.forEach((data) => { this.add(data) }) } - /**The option related to this ticket. */ + /**The option this ticket is made of. */ set option(option:ODTicketOption){ - this.#option = option + this.rawOption = option this._change() } get option(){ - return this.#option + return this.rawOption } /**Convert this ticket to a JSON object for storing this ticket in the database. */ - toJson(version:ODVersion): ODTicketJson { + toJson(version:api.ODVersion): ODTicketJson { const data = this.getAll().map((data) => { return { id:data.id.toString(), @@ -246,24 +248,24 @@ export class ODTicket extends ODManager> { return new ODTicket(json.id,option,json.data.map((data) => new ODTicketData(data.id,data.value))) } - get(id:OptionId): ODTicketIds[OptionId] - get(id:ODValidId): ODTicketData|null + get>(id:OptionId): ODTicketIdMappings[OptionId] + get(id:api.ODValidId): ODTicketData|null - get(id:ODValidId): ODTicketData|null { + get(id:api.ODValidId): ODTicketData|null { return super.get(id) } - remove(id:OptionId): ODTicketIds[OptionId] - remove(id:ODValidId): ODTicketData|null + remove>(id:OptionId): ODTicketIdMappings[OptionId] + remove(id:api.ODValidId): ODTicketData|null - remove(id:ODValidId): ODTicketData|null { + remove(id:api.ODValidId): ODTicketData|null { return super.remove(id) } - exists(id:keyof ODTicketIds): boolean - exists(id:ODValidId): boolean + exists(id:keyof api.ODNoGeneric): boolean + exists(id:api.ODValidId): boolean - exists(id:ODValidId): boolean { + exists(id:api.ODValidId): boolean { return super.exists(id) } } @@ -275,22 +277,22 @@ export class ODTicket extends ODManager> { * * When this property is edited, the database will be updated automatically. */ -export class ODTicketData extends ODManagerData { +export class ODTicketData extends api.ODManagerData { /**The value of this property. */ - #value: DataType + private rawValue: DataType - constructor(id:ODValidId, value:DataType){ + constructor(id:api.ODValidId, value:DataType){ super(id) - this.#value = value + this.rawValue = value } /**The value of this property. */ set value(value:DataType){ - this.#value = value + this.rawValue = value this._change() } get value(): DataType { - return this.#value + return this.rawValue } /**Refresh the database. Is only required to be used when updating `ODTicketData` with an object/array as value. */ refreshDatabase(){ diff --git a/src/core/api/openticket/transcript.ts b/src/core/api/transcript.ts similarity index 87% rename from src/core/api/openticket/transcript.ts rename to src/core/api/transcript.ts index 9234ec9..4bff58d 100644 --- a/src/core/api/openticket/transcript.ts +++ b/src/core/api/transcript.ts @@ -1,13 +1,23 @@ /////////////////////////////////////// //OPENTICKET TRANSCRIPT MODULE /////////////////////////////////////// -import { ODId, ODManager, ODValidJsonType, ODValidId, ODManagerData, ODValidButtonColor } from "../modules/base" -import { ODDebugger } from "../modules/console" -import { ODTicket, ODTicketManager } from "./ticket" -import { ODMessageBuildResult } from "../modules/builder" -import { ODClientManager } from "../modules/client" +import * as api from "@open-discord-bots/framework/api" +import { ODTicket, ODTicketManager } from "./ticket.js" import * as discord from "discord.js" -import { ODPermissionManager_Default } from "#opendiscord-types" + +/**## ODTranscriptManagerIdConstraint `type` + * The constraint/layout for id mappings/interfaces of the `ODTranscriptManager` class. + */ +export type ODTranscriptManagerIdConstraint = Record> + +/**## ODTranscriptManagerIdMappings `interface` + * A list of all available IDs in the default `ODTranscriptManager` class in `opendiscord`. + * It's used to generate typescript declarations for this class. + */ +export interface ODTranscriptManagerIdMappings extends ODTranscriptManagerIdConstraint { + "opendiscord:html-compiler":ODTranscriptCompiler<{url:string,availableUntil:Date},{auth:string}>, + "opendiscord:text-compiler":ODTranscriptCompiler<{contents:string},null>, +} /**## ODTranscriptManager `class` * This is an Open Ticket transcript manager. @@ -16,19 +26,59 @@ import { ODPermissionManager_Default } from "#opendiscord-types" * * The 2 default built-in transcript generators are: `opendiscord:html-compiler` & `opendiscord:text-compiler`. */ -export class ODTranscriptManager extends ODManager> { +export class ODTranscriptManager extends api.ODManager> { /**The manager responsible for collecting all messages in a channel. */ collector: ODTranscriptCollector /**Alias for the client manager. */ - #client: ODClientManager + private client: api.ODClientManager - constructor(debug:ODDebugger, tickets:ODTicketManager, client:ODClientManager, permissions:ODPermissionManager_Default){ + constructor(debug:api.ODDebugger, tickets:ODTicketManager, client:api.ODClientManager, permissions:api.ODPermissionManager){ super(debug,"transcript compiler") - this.#client = client + this.client = client this.collector = new ODTranscriptCollector(tickets,client,permissions) } + + get>(id:CompilerId): IdList[CompilerId] + get(id:api.ODValidId): ODTranscriptCompiler|null + + get(id:api.ODValidId): ODTranscriptCompiler|null { + return super.get(id) + } + + remove>(id:CompilerId): IdList[CompilerId] + remove(id:api.ODValidId): ODTranscriptCompiler|null + + remove(id:api.ODValidId): ODTranscriptCompiler|null { + return super.remove(id) + } + + exists(id:keyof api.ODNoGeneric): boolean + exists(id:api.ODValidId): boolean + + exists(id:api.ODValidId): boolean { + return super.exists(id) + } } +/**## ODTranscriptHistoryData `interface` + * The transcript data stored in the transcripts database of deleted tickets. + */ +export interface ODTranscriptHistoryData { + ticketId:string, + ticketName:string, + ticketCreatorId:string, + ticketCreatedDate:number|null, + ticketDeletedDate:number|null, + transcriptType:"localContents"|"remoteUrl", + transcriptContents:string|null, + transcriptUrl:string|null, +} + +/**## ODMappedTranscriptManager `class + * A special class with types for the Open Ticket `ODTranscriptManager` class. + */ +export class ODMappedTranscriptManager extends ODTranscriptManager {} + /**## ODTranscriptCompilerInitFunction `type` * This function will initiate/prepare the transcript system for an incoming transcript. */ @@ -53,7 +103,7 @@ export interface ODTranscriptCompilerInitResult { /**When not successfull, what was the reason? This will also be shown to the user. */ errorReason:string|null, /**An optional message which will be sent while the transcript is being generated. */ - pendingMessage:ODMessageBuildResult|null, + pendingMessage:api.ODMessageBuildResult|api.ODMessageComponentBuildResult|null, /**An optional object containing data from the init() function which can be used in the compiler. */ initData:InitData, } @@ -83,15 +133,15 @@ export interface ODTranscriptCompilerCompileResult { */ export interface ODTranscriptCompilerReadyResult { /**The message to be sent in the specified channel in the server. */ - channelMessage?:ODMessageBuildResult, + channelMessage?:api.ODMessageBuildResult|api.ODMessageComponentBuildResult, /**The message to be sent to the DM of the ticket creator. */ - creatorDmMessage?:ODMessageBuildResult, + creatorDmMessage?:api.ODMessageBuildResult|api.ODMessageComponentBuildResult, /**The message to be sent to the DM of all participants. */ - participantDmMessage?:ODMessageBuildResult, + participantDmMessage?:api.ODMessageBuildResult|api.ODMessageComponentBuildResult, /**The message to be sent to the DM of all admins who actively participated in the ticket. */ - activeAdminDmMessage?:ODMessageBuildResult, + activeAdminDmMessage?:api.ODMessageBuildResult|api.ODMessageComponentBuildResult, /**The message to be sent to the DM of all admins who were assigned to this ticket. */ - everyAdminDmMessage?:ODMessageBuildResult + everyAdminDmMessage?:api.ODMessageBuildResult|api.ODMessageComponentBuildResult } /**## ODTranscriptCompiler `class` @@ -101,7 +151,7 @@ export interface ODTranscriptCompilerReadyResult { * * These functions should be defined when creating this compiler. Existing compilers already exist for html & text transcripts. */ -export class ODTranscriptCompiler extends ODManagerData { +export class ODTranscriptCompiler extends api.ODManagerData { /*Initialise the system every time a transcript is created. Returns optional "pending" message to display while the transcript is being compiled. */ init: ODTranscriptCompilerInitFunction|null /*Compile or create the transcript. Returns data to give to the ready() function for message creation. */ @@ -109,7 +159,7 @@ export class ODTranscriptCompiler|null - constructor(id:ODValidId, init?:ODTranscriptCompilerInitFunction, compile?:ODTranscriptCompilerCompileFunction, ready?:ODTranscriptCompilerReadyFunction|null){ + constructor(id:api.ODValidId, init?:ODTranscriptCompilerInitFunction, compile?:ODTranscriptCompilerCompileFunction, ready?:ODTranscriptCompilerReadyFunction|null){ super(id) this.init = init ?? null this.compile = compile ?? null @@ -117,44 +167,6 @@ export class ODTranscriptCompiler, - "opendiscord:text-compiler":ODTranscriptCompiler<{contents:string},null>, -} - -/**## ODTranscriptManager_Default `default_class` - * This is a special class that adds type definitions & typescript to the ODTranscriptManager class. - * It doesn't add any extra features! - * - * This default class is made for the global variable `opendiscord.transcripts`! - */ -export class ODTranscriptManager_Default extends ODTranscriptManager { - get(id:CompilerId): ODTranscriptCompilerIds[CompilerId] - get(id:ODValidId): ODTranscriptCompiler|null - - get(id:ODValidId): ODTranscriptCompiler|null { - return super.get(id) - } - - remove(id:CompilerId): ODTranscriptCompilerIds[CompilerId] - remove(id:ODValidId): ODTranscriptCompiler|null - - remove(id:ODValidId): ODTranscriptCompiler|null { - return super.remove(id) - } - - exists(id:keyof ODTranscriptCompilerIds): boolean - exists(id:ODValidId): boolean - - exists(id:ODValidId): boolean { - return super.exists(id) - } -} - /**## ODTranscriptCollector `class` * This is an Open Ticket transcript collector. * @@ -164,22 +176,22 @@ export class ODTranscriptManager_Default extends ODTranscriptManager { */ export class ODTranscriptCollector { /**Alias for the ticket manager. */ - #tickets: ODTicketManager + private tickets: ODTicketManager /**Alias for the client manager. */ - #client: ODClientManager + private client: api.ODClientManager /**Alias for the permissions manager. */ - #permissions: ODPermissionManager_Default + private permissions: api.ODPermissionManager - constructor(tickets:ODTicketManager,client:ODClientManager,permissions:ODPermissionManager_Default){ - this.#tickets = tickets - this.#client = client - this.#permissions = permissions + constructor(tickets:ODTicketManager,client:api.ODClientManager,permissions:api.ODPermissionManager){ + this.tickets = tickets + this.client = client + this.permissions = permissions } /**Collect all messages from a given ticket channel. It may not include all messages depending on the ratelimit. */ async collectAllMessages(ticket:ODTicket, include?:ODTranscriptCollectorIncludeSettings): Promise[]|null> { const newInclude: ODTranscriptCollectorIncludeSettings = include ?? {users:true,bots:true,client:true} - const channel = await this.#tickets.getTicketChannel(ticket) + const channel = await this.tickets.getTicketChannel(ticket) if (!channel) return null const final: discord.Message[] = [] @@ -215,7 +227,7 @@ export class ODTranscriptCollector { const {guild,channel,id,createdTimestamp} = msg //create message author - const author = this.#handleUserData(msg.author,msg.member) + const author = this.handleUserData(msg.author,msg.member) //create message type let type: ODTranscriptMessageType = "default" @@ -271,8 +283,8 @@ export class ODTranscriptCollector { disabled:component.disabled, type:"button", label:component.label, - emoji:this.#handleComponentEmoji(msg,component.emoji), - color:this.#handleButtonComponentStyle(component.style), + emoji:this.handleComponentEmoji(msg,component.emoji), + color:this.handleButtonComponentStyle(component.style), mode:(component.style == discord.ButtonStyle.Link) ? "url" : "button", url:component.url }) @@ -287,7 +299,7 @@ export class ODTranscriptCollector { id:option.value, label:option.label, description:option.description ?? null, - emoji:this.#handleComponentEmoji(msg,option.emoji ?? null) + emoji:this.handleComponentEmoji(msg,option.emoji ?? null) } }) }) @@ -305,7 +317,7 @@ export class ODTranscriptCollector { if (replyChannel && !replyChannel.isDMBased() && replyChannel.isTextBased()){ const replyMessage = await replyChannel.messages.fetch(msg.reference.messageId) if (replyMessage){ - const replyUser = this.#handleUserData(replyMessage.author,replyMessage.member) + const replyUser = this.handleUserData(replyMessage.author,replyMessage.member) reply = { type:"message", @@ -322,7 +334,7 @@ export class ODTranscriptCollector { }else if (msg.interactionMetadata){ try{ //get slash command name from undocumented property in discord REST API - const restMsg = await this.#client.rest.get(discord.Routes.channelMessage(msg.channelId,msg.id)) as discord.APIMessage & {interaction_metadata:{name:string}} + const restMsg = await this.client.rest.get(discord.Routes.channelMessage(msg.channelId,msg.id)) as discord.APIMessage & {interaction_metadata:{name:string}} const commandName = restMsg.interaction_metadata.name ?? "unknown-command" //slash command reply let member: discord.GuildMember|null = null @@ -332,7 +344,7 @@ export class ODTranscriptCollector { reply = { type:"interaction", name:commandName, - user:this.#handleUserData(msg.interactionMetadata.user,member) + user:this.handleUserData(msg.interactionMetadata.user,member) } }catch(err){ process.emit("uncaughtException",err) @@ -397,7 +409,7 @@ export class ODTranscriptCollector { else return {size:Math.round(bytes/(1024*1024*1024*1024)),unit:"TB"} } /**Get the `ODTranscriptEmojiData` from a discord.js component emoji. */ - #handleComponentEmoji(message:discord.Message, rawEmoji:discord.APIMessageComponentEmoji|null): ODTranscriptEmojiData|null { + private handleComponentEmoji(message:discord.Message, rawEmoji:discord.APIMessageComponentEmoji|null): ODTranscriptEmojiData|null { if (!rawEmoji) return null //return built-in emoji if (rawEmoji.name) return { @@ -420,14 +432,14 @@ export class ODTranscriptCollector { } } /**Create the `ODValidButtonColor` from the discord.js button style. */ - #handleButtonComponentStyle(style:discord.ButtonStyle): ODValidButtonColor { + private handleButtonComponentStyle(style:discord.ButtonStyle): api.ODValidButtonColor { if (style == discord.ButtonStyle.Danger) return "red" else if (style == discord.ButtonStyle.Success) return "green" else if (style == discord.ButtonStyle.Primary) return "blue" else return "gray" } /**Create the `ODTranscriptUserData` from a discord.js user. */ - #handleUserData(user:discord.User, member?:discord.GuildMember|null): ODTranscriptUserData { + private handleUserData(user:discord.User, member?:discord.GuildMember|null): ODTranscriptUserData { const userData: ODTranscriptUserData = { id:user.id, username:user.username, @@ -452,10 +464,10 @@ export class ODTranscriptCollector { let adminMessages = 0 for (const msg of parsedMessages){ - if (msg.author.tag || msg.author.id == this.#client.client.user.id) continue - const user = await this.#client.fetchUser(msg.author.id) + if (msg.author.tag || msg.author.id == this.client.client.user.id) continue + const user = await this.client.fetchUser(msg.author.id) if (!user) continue - const isAdmin = this.#permissions.hasPermissions("support",await this.#permissions.getPermissions(user,channel,guild)) + const isAdmin = this.permissions.hasPermissions("support",await this.permissions.getPermissions(user,channel,guild)) if (isAdmin) adminMessages++ else userMessages++ } @@ -622,7 +634,7 @@ export interface ODTranscriptButtonComponentData extends ODTranscriptComponentDa /**The emoji of this button. */ emoji: ODTranscriptEmojiData|null, /**The color of this button. */ - color: ODValidButtonColor, + color: api.ODValidButtonColor, /**Is this button a url or button? */ mode: "url"|"button", /**The url of this button. */ diff --git a/src/core/cli/cli.ts b/src/core/cli/cli.ts index 6fcb2ce..e94853c 100644 --- a/src/core/cli/cli.ts +++ b/src/core/cli/cli.ts @@ -1,8 +1,7 @@ -import {opendiscord, api, utilities} from "../../index" -import {Terminal, terminal} from "terminal-kit" -import ansis from "ansis" +import {opendiscord, api, utilities} from "../../index.js" +import * as cli from "@open-discord-bots/framework/cli" -const logo = [ +export const logo = [ " ██████╗ ██████╗ ███████╗███╗ ██╗ ████████╗██╗ ██████╗██╗ ██╗███████╗████████╗ ", " ██╔═══██╗██╔══██╗██╔════╝████╗ ██║ ╚══██╔══╝██║██╔════╝██║ ██╔╝██╔════╝╚══██╔══╝ ", " ██║ ██║██████╔╝█████╗ ██╔██╗ ██║ ██║ ██║██║ █████╔╝ █████╗ ██║ ", @@ -10,66 +9,16 @@ const logo = [ " ╚██████╔╝██║ ███████╗██║ ╚████║ ██║ ██║╚██████╗██║ ██╗███████╗ ██║ ", " ╚═════╝ ╚═╝ ╚══════╝╚═╝ ╚═══╝ ╚═╝ ╚═╝ ╚═════╝╚═╝ ╚═╝╚══════╝ ╚═╝ " ] - -/**A utility function to center text to a certain width. */ -export function centerText(text:string,width:number){ - if (width < text.length) return text - let newWidth = width-ansis.strip(text).length+1 - let final = " ".repeat(newWidth/2)+text - return final -} - -/**A utility function to terminate the interactive CLI. */ -export async function terminate(){ - terminal.grabInput(false) - terminal.clear() - terminal.green("👋 Exited the Open Ticket Interactive Setup CLI.\n") - process.exit(0) -} -terminal.on("key",(name,matches,data) => { - if (name == "CTRL_C") terminate() -}) - -/**Render the header of the interactive CLI. */ -export function renderHeader(path:(string|number)[]|string){ - terminal.grabInput(true) - terminal.clear().moveTo(1,1) - terminal(ansis.hex("#f8ba00")(logo.join("\n")+"\n")) - terminal.bold(centerText("Interactive Setup CLI - Version: "+opendiscord.versions.get("opendiscord:version").toString()+" - Support: https://discord.dj-dj.be\n",88)) - if (typeof path == "string") terminal.cyan(centerText(path+"\n\n",88)) - else if (path.length < 1) terminal.cyan(centerText("👋 Hi! Welcome to the Open Ticket Interactive Setup CLI! 👋\n\n",88)) - else terminal.cyan(centerText("🌐 Current Location: "+path.map((v,i) => { - if (i == 0) return v.toString() - else if (typeof v == "string") return ".\""+v+"\"" - else if (typeof v == "number") return "."+v - }).join("")+"\n\n",88)) -} - -async function renderCliModeSelector(backFn:(() => api.ODPromiseVoid)){ - renderHeader([]) - terminal(ansis.bold.green("Please select what CLI module you want to use.\n")+ansis.italic.gray("(use arrow keys to navigate, exit using escape)\n")) - - const answer = await terminal.singleColumnMenu([ - "✏️ Edit Config "+ansis.gray("=> Edit the current config, add/remove new tickets/questions/panels & more!"), - "⏱️ Quick Setup "+ansis.gray("=> A quick and easy way of setting up the bot in your Discord server."), - ],{ - leftPadding:"> ", - style:terminal.cyan, - selectedStyle:terminal.bgDefaultColor.bold, - submittedStyle:terminal.bgBlue, - extraLines:2, - cancelable:true - }).promise - - if (answer.canceled) return await backFn() - else if (answer.selectedIndex == 0) await (await import("./editConfig.js")).renderEditConfig(async () => {await renderCliModeSelector(backFn)}) - else if (answer.selectedIndex == 1) await (await import("./quickSetup.js")).renderQuickSetup(async () => {await renderCliModeSelector(backFn)}) +export const headerOpts: cli.ODCliHeaderOpts = { + logo, + projectColor:"#f8ba00", + projectName:"Open Ticket", + projectVersion:opendiscord.versions.get("opendiscord:version") } export async function execute(){ - if (terminal.width < 100 || terminal.height < 35){ - terminal(ansis.red.bold("\n\nMake sure your console or cmd window has a "+ansis.cyan("minimum width & height")+" of "+ansis.cyan("100x35")+" characters.")) - terminal(ansis.red.bold("\nOtherwise the Open Ticket Interactive Setup CLI will be rendered incorrectly.")) - terminal(ansis.red.bold("\nThe current terminal dimensions are: "+ansis.cyan(terminal.width+"x"+terminal.height)+".")) - }else await renderCliModeSelector(terminate) + const editConfig = new cli.ODCliEditConfigInstance(headerOpts,opendiscord) + const renderQuickSetup = (await import("./quickSetup.js")).renderQuickSetup + + await cli.execute(headerOpts,async (backFn) => {return editConfig.renderEditConfig(backFn)},renderQuickSetup) } \ No newline at end of file diff --git a/src/core/cli/editConfig.ts b/src/core/cli/editConfig.ts deleted file mode 100644 index 9eebf29..0000000 --- a/src/core/cli/editConfig.ts +++ /dev/null @@ -1,933 +0,0 @@ -import {opendiscord, api, utilities} from "../../index" -import {Terminal, terminal} from "terminal-kit" -import ansis from "ansis" -import {renderHeader} from "./cli" - -export async function renderEditConfig(backFn:(() => api.ODPromiseVoid)){ - renderHeader([]) - terminal(ansis.bold.green("Please select which config you would like to edit.\n")+ansis.italic.gray("(use arrow keys to navigate, go back using escape)\n")) - - const checkerList = opendiscord.checkers.getAll() - const checkerNameList = checkerList.map((checker) => (checker.options.cliDisplayName ? checker.options.cliDisplayName+" ("+checker.config.file+")" : checker.config.file)) - const checkerNameLength = utilities.getLongestLength(checkerNameList) - const finalCheckerNameList = checkerNameList.map((name,index) => name.padEnd(checkerNameLength+5," ")+ansis.gray(checkerList[index].options.cliDisplayDescription ? "=> "+checkerList[index].options.cliDisplayDescription : "")) - - const answer = await terminal.singleColumnMenu(finalCheckerNameList,{ - leftPadding:"> ", - style:terminal.cyan, - selectedStyle:terminal.bgDefaultColor.bold, - submittedStyle:terminal.bgBlue, - extraLines:2, - cancelable:true - }).promise - - if (answer.canceled) return await backFn() - const checker = checkerList[answer.selectedIndex] - const configData = checker.config.data as api.ODValidJsonType - await chooseConfigStructure(checker,async () => {await renderEditConfig(backFn)},checker.structure,configData,{},NaN,["("+checker.config.path+")"]) -} - -async function chooseConfigStructure(checker:api.ODChecker,backFn:(() => api.ODPromiseVoid),structure:api.ODCheckerStructure,data:api.ODValidJsonType,parent:object|any[],parentIndex:string|number,path:(string|number)[]){ - if (structure instanceof api.ODCheckerObjectStructure && typeof data == "object" && !Array.isArray(data) && data) await renderConfigObjectStructureSelector(checker,backFn,structure,data,parent,parentIndex,path) - else if (structure instanceof api.ODCheckerEnabledObjectStructure && typeof data == "object" && !Array.isArray(data) && data) await renderConfigEnabledObjectStructureSelector(checker,backFn,structure,data,parent,parentIndex,path) - else if (structure instanceof api.ODCheckerObjectSwitchStructure && typeof data == "object" && !Array.isArray(data) && data) await renderConfigObjectSwitchStructureSelector(checker,backFn,structure,data,parent,parentIndex,path) - else if (structure instanceof api.ODCheckerArrayStructure && Array.isArray(data)) await renderConfigArrayStructureSelector(checker,backFn,structure,data,parent,parentIndex,path) - else if (structure instanceof api.ODCheckerBooleanStructure && typeof data == "boolean") await renderConfigBooleanStructureEditor(checker,backFn,structure,data,parent,parentIndex,path) - else if (structure instanceof api.ODCheckerNumberStructure && typeof data == "number") await renderConfigNumberStructureEditor(checker,backFn,structure,data,parent,parentIndex,path) - else if (structure instanceof api.ODCheckerStringStructure && typeof data == "string") await renderConfigStringStructureEditor(checker,backFn,structure,data,parent,parentIndex,path) - else if (structure instanceof api.ODCheckerNullStructure && data === null) await renderConfigNullStructureEditor(checker,backFn,structure,data,parent,parentIndex,path) - else if (structure instanceof api.ODCheckerTypeSwitchStructure) await renderConfigTypeSwitchStructureEditor(checker,backFn,structure,data,parent,parentIndex,path) - else terminal.red.bold("❌ Unable to detect type of variable! Please try to edit this property in the JSON file itself!") -} - -async function renderConfigObjectStructureSelector(checker:api.ODChecker,backFn:(() => api.ODPromiseVoid),structure:api.ODCheckerObjectStructure,data:object,parent:object,parentIndex:string|number,path:(string|number)[]){ - if (typeof data != "object" || Array.isArray(data)) throw new api.ODSystemError("OT CLI => Property is not of the type 'object'. Please check your config for possible errors. (index: "+parentIndex+", path: "+path.join(".")+")") - renderHeader(path) - terminal(ansis.bold.green("Please select which variable you would like to edit.\n")+ansis.italic.gray("(use arrow keys to navigate, go back using escape)\n")) - if (!structure.options.children) return await backFn() - - if (structure.options.cliDisplayName){ - terminal.gray("\nProperty: "+ansis.bold.blue(structure.options.cliDisplayName)+"\n") - terminal.gray("Description: "+ansis.bold(structure.options.cliDisplayDescription ?? "/")+"\n") - } - - const list = structure.options.children.filter((child) => !child.cliHideInEditMode) - const nameList = list.map((child) => (child.checker.options.cliDisplayName ? child.checker.options.cliDisplayName : child.key)) - const nameLength = utilities.getLongestLength(nameList) - const finalnameList = nameList.map((name,index) => name.padEnd(nameLength+5," ")+ansis.gray((!list[index].checker.options.cliHideDescriptionInParent && list[index].checker.options.cliDisplayDescription) ? "=> "+list[index].checker.options.cliDisplayDescription : "")) - - const answer = await terminal.singleColumnMenu(finalnameList,{ - leftPadding:"> ", - style:terminal.cyan, - selectedStyle:terminal.bgDefaultColor.bold.defaultColor, - submittedStyle:terminal.bgBlue, - extraLines:2, - cancelable:true - }).promise - - if (answer.canceled) return await backFn() - const subStructure = list[answer.selectedIndex] - const subData = data[subStructure.key] - await chooseConfigStructure(checker,async () => {await renderConfigObjectStructureSelector(checker,backFn,structure,data,parent,parentIndex,path)},subStructure.checker,subData,data,subStructure.key,[...path,subStructure.key]) -} - -async function renderConfigEnabledObjectStructureSelector(checker:api.ODChecker,backFn:(() => api.ODPromiseVoid),structure:api.ODCheckerEnabledObjectStructure,data:object,parent:object,parentIndex:string|number,path:(string|number)[]){ - if (typeof data != "object" || Array.isArray(data)) throw new api.ODSystemError("OT CLI => Property is not of the type 'object'. Please check your config for possible errors. (index: "+parentIndex+", path: "+path.join(".")+")") - const enabledProperty = structure.options.property - const subStructure = structure.options.checker - if (!enabledProperty || !subStructure || !subStructure.options.children) return await backFn() - - if (!subStructure.options.children.find((child) => child.key === structure.options.property)){ - if (typeof structure.options.enabledValue == "string") subStructure.options.children.unshift({key:enabledProperty,optional:false,priority:1,checker:new api.ODCheckerStringStructure("opendiscord:CLI-checker-enabled-object-structure",{})}) - else if (typeof structure.options.enabledValue == "number") subStructure.options.children.unshift({key:enabledProperty,optional:false,priority:1,checker:new api.ODCheckerNumberStructure("opendiscord:CLI-checker-enabled-object-structure",{})}) - else if (typeof structure.options.enabledValue == "boolean") subStructure.options.children.unshift({key:enabledProperty,optional:false,priority:1,checker:new api.ODCheckerBooleanStructure("opendiscord:CLI-checker-enabled-object-structure",{})}) - } - - await chooseConfigStructure(checker,backFn,subStructure,data,parent,parentIndex,path) -} - -async function renderConfigObjectSwitchStructureSelector(checker:api.ODChecker,backFn:(() => api.ODPromiseVoid),structure:api.ODCheckerObjectSwitchStructure,data:object,parent:object,parentIndex:string|number,path:(string|number)[]){ - if (typeof data != "object" || Array.isArray(data)) throw new api.ODSystemError("OT CLI => Property is not of the type 'object'. Please check your config for possible errors. (index: "+parentIndex+", path: "+path.join(".")+")") - if (!structure.options.objects) return await backFn() - - let didMatch: boolean = false - for (const objectTemplate of structure.options.objects){ - if (objectTemplate.properties.every((prop) => data[prop.key] === prop.value)){ - //object template matches data - const subStructure = objectTemplate.checker - didMatch = true - await chooseConfigStructure(checker,backFn,subStructure,data,parent,parentIndex,path) - } - } - if (!didMatch) throw new api.ODSystemError("OT CLI => Unable to detect type of object in the object switch. Please check your config for possible errors. (index: "+parentIndex+", path: "+path.join(".")+")") -} - -async function renderConfigArrayStructureSelector(checker:api.ODChecker,backFn:(() => api.ODPromiseVoid),structure:api.ODCheckerArrayStructure,data:any[],parent:object,parentIndex:string|number,path:(string|number)[]){ - if (!Array.isArray(data)) throw new api.ODSystemError("OT CLI => Property is not of the type 'array'. Please check your config for possible errors. (index: "+parentIndex+", path: "+path.join(".")+")") - renderHeader(path) - terminal(ansis.bold.green("Please select what you would like to do.\n")+ansis.italic.gray("(use arrow keys to navigate, go back using escape)\n")) - if (!structure.options.propertyChecker) return await backFn() - - if (structure.options.cliDisplayName || typeof parentIndex == "string" || !isNaN(parentIndex)){ - terminal.gray("\nProperty: "+ansis.bold.blue(structure.options.cliDisplayName ?? parentIndex.toString())+"\n") - terminal.gray("Description: "+ansis.bold(structure.options.cliDisplayDescription ?? "/")+"\n") - } - - const propertyName = structure.options.cliDisplayPropertyName ?? "index" - const answer = await terminal.singleColumnMenu(data.length < 1 ? ["Add "+propertyName] : [ - "Add "+propertyName, - "Edit "+propertyName, - "Move "+propertyName, - "Remove "+propertyName, - "Duplicate "+propertyName - ],{ - leftPadding:"> ", - style:terminal.cyan, - selectedStyle:terminal.bgDefaultColor.bold, - submittedStyle:terminal.bgBlue, - extraLines:2, - cancelable:true - }).promise - - const backFnFunc = async () => {await renderConfigArrayStructureSelector(checker,backFn,structure,data,parent,parentIndex,path)} - - if (answer.canceled) return await backFn() - if (answer.selectedIndex == 0) await chooseAdditionConfigStructure(checker,backFnFunc,async (newData) => { - data[data.length] = newData - await checker.config.save() - await backFnFunc() - },structure.options.propertyChecker,data,data.length,path,[]) - else if (answer.selectedIndex == 1) await renderConfigArrayStructureEditSelector(checker,backFnFunc,structure,structure.options.propertyChecker,data,parent,parentIndex,path) - else if (answer.selectedIndex == 2) await renderconfigArrayStructureMoveSelector(checker,backFnFunc,structure,structure.options.propertyChecker,data,parent,parentIndex,path) - else if (answer.selectedIndex == 3) await renderconfigArrayStructureRemoveSelector(checker,backFnFunc,structure,structure.options.propertyChecker,data,parent,parentIndex,path) - else if (answer.selectedIndex == 4) await renderConfigArrayStructureDuplicateSelector(checker,backFnFunc,structure,structure.options.propertyChecker,data,parent,parentIndex,path) -} - -async function renderConfigArrayStructureEditSelector(checker:api.ODChecker,backFn:(() => api.ODPromiseVoid),arrayStructure:api.ODCheckerArrayStructure,structure:api.ODCheckerStructure,data:any[],parent:object,parentIndex:string|number,path:(string|number)[]){ - const propertyName = arrayStructure.options.cliDisplayPropertyName ?? "index" - renderHeader(path) - terminal(ansis.bold.green("Please select the "+propertyName+" you would like to edit.\n")+ansis.italic.gray("(use arrow keys to navigate, go back using escape)\n")) - - const longestDataListName = Math.max(...data.map((d,i) => getArrayPreviewStructureNameLength(structure,d,data,i))) - const dataList = data.map((d,i) => (i+1)+". "+getArrayPreviewFromStructure(structure,d,data,i,longestDataListName)) - const dataAnswer = await terminal.singleColumnMenu(dataList,{ - leftPadding:"> ", - style:terminal.cyan, - selectedStyle:terminal.bgDefaultColor.bold, - submittedStyle:terminal.bgBlue, - extraLines:2, - cancelable:true - }).promise - - if (dataAnswer.canceled) return await backFn() - const subData = data[dataAnswer.selectedIndex] - await chooseConfigStructure(checker,async () => {await renderConfigArrayStructureEditSelector(checker,backFn,arrayStructure,structure,data,parent,parentIndex,path)},structure,subData,data,dataAnswer.selectedIndex,[...path,dataAnswer.selectedIndex]) -} - -async function renderconfigArrayStructureMoveSelector(checker:api.ODChecker,backFn:(() => api.ODPromiseVoid),arrayStructure:api.ODCheckerArrayStructure,structure:api.ODCheckerStructure,data:any[],parent:object,parentIndex:string|number,path:(string|number)[]){ - const propertyName = arrayStructure.options.cliDisplayPropertyName ?? "index" - renderHeader(path) - terminal(ansis.bold.green("Please select the "+propertyName+" you would like to move.\n")+ansis.italic.gray("(use arrow keys to navigate, go back using escape)\n")) - - const longestDataListName = Math.max(...data.map((d,i) => getArrayPreviewStructureNameLength(structure,d,data,i))) - const dataList = data.map((d,i) => (i+1)+". "+getArrayPreviewFromStructure(structure,d,data,i,longestDataListName)) - const dataAnswer = await terminal.singleColumnMenu(dataList,{ - leftPadding:"> ", - style:terminal.cyan, - selectedStyle:terminal.bgDefaultColor.bold, - submittedStyle:terminal.bgBlue, - extraLines:2, - cancelable:true - }).promise - - if (dataAnswer.canceled) return await backFn() - - renderHeader([...path,dataAnswer.selectedIndex]) - terminal(ansis.bold.green("Please select the position you would like to move to.\n")+ansis.italic.gray("(use arrow keys to navigate, go back using escape)\n")) - - const moveAnswer = await terminal.singleColumnMenu(data.map((d,i) => "Position "+(i+1)),{ - leftPadding:"> ", - style:terminal.cyan, - selectedStyle:terminal.bgDefaultColor.bold, - submittedStyle:terminal.bgBlue, - extraLines:2, - cancelable:true - }).promise - - if (moveAnswer.canceled) return await renderconfigArrayStructureMoveSelector(checker,backFn,arrayStructure,structure,data,parent,parentIndex,path) - - const subData = data[dataAnswer.selectedIndex] - const slicedData = [...data.slice(0,dataAnswer.selectedIndex),...data.slice(dataAnswer.selectedIndex+1)] - const insertedData = [...slicedData.slice(0,moveAnswer.selectedIndex),subData,...slicedData.slice(moveAnswer.selectedIndex)] - insertedData.forEach((d,i) => data[i] = d) - - await checker.config.save() - terminal.bold.blue("\n\n✅ Property moved succesfully!") - await utilities.timer(400) - await backFn() -} - -async function renderconfigArrayStructureRemoveSelector(checker:api.ODChecker,backFn:(() => api.ODPromiseVoid),arrayStructure:api.ODCheckerArrayStructure,structure:api.ODCheckerStructure,data:any[],parent:object,parentIndex:string|number,path:(string|number)[]){ - const propertyName = arrayStructure.options.cliDisplayPropertyName ?? "index" - renderHeader(path) - terminal(ansis.bold.green("Please select the "+propertyName+" you would like to delete.\n")+ansis.italic.gray("(use arrow keys to navigate, go back using escape)\n")) - - const longestDataListName = Math.max(...data.map((d,i) => getArrayPreviewStructureNameLength(structure,d,data,i))) - const dataList = data.map((d,i) => (i+1)+". "+getArrayPreviewFromStructure(structure,d,data,i,longestDataListName)) - const dataAnswer = await terminal.singleColumnMenu(dataList,{ - leftPadding:"> ", - style:terminal.cyan, - selectedStyle:terminal.bgDefaultColor.bold, - submittedStyle:terminal.bgBlue, - extraLines:2, - cancelable:true - }).promise - - if (dataAnswer.canceled) return await backFn() - data.splice(dataAnswer.selectedIndex,1) - - await checker.config.save() - terminal.bold.blue("\n\n✅ Property deleted succesfully!") - await utilities.timer(400) - await backFn() -} - -async function renderConfigArrayStructureDuplicateSelector(checker:api.ODChecker,backFn:(() => api.ODPromiseVoid),arrayStructure:api.ODCheckerArrayStructure,structure:api.ODCheckerStructure,data:any[],parent:object,parentIndex:string|number,path:(string|number)[]){ - const propertyName = arrayStructure.options.cliDisplayPropertyName ?? "index" - renderHeader(path) - terminal(ansis.bold.green("Please select the "+propertyName+" you would like to duplicate.\n")+ansis.italic.gray("(use arrow keys to navigate, go back using escape)\n")) - - const longestDataListName = Math.max(...data.map((d,i) => getArrayPreviewStructureNameLength(structure,d,data,i))) - const dataList = data.map((d,i) => (i+1)+". "+getArrayPreviewFromStructure(structure,d,data,i,longestDataListName)) - const dataAnswer = await terminal.singleColumnMenu(dataList,{ - leftPadding:"> ", - style:terminal.cyan, - selectedStyle:terminal.bgDefaultColor.bold, - submittedStyle:terminal.bgBlue, - extraLines:2, - cancelable:true - }).promise - - if (dataAnswer.canceled) return await backFn() - data.push(JSON.parse(JSON.stringify(data[dataAnswer.selectedIndex]))) - - await checker.config.save() - terminal.bold.blue("\n\n✅ Property duplicated succesfully!") - await utilities.timer(400) - await backFn() -} - -async function renderConfigBooleanStructureEditor(checker:api.ODChecker,backFn:(() => api.ODPromiseVoid),structure:api.ODCheckerBooleanStructure,data:boolean,parent:object,parentIndex:string|number,path:(string|number)[]){ - if (typeof data != "boolean") throw new api.ODSystemError("OT CLI => Property is not of the type 'boolean'. Please check your config for possible errors. (index: "+parentIndex+", path: "+path.join(".")+")") - renderHeader(path) - terminal(ansis.bold.green("You are now editing "+(typeof parentIndex == "string" ? "the boolean property "+ansis.blue("\""+parentIndex+"\"") : "boolean property "+ansis.blue("#"+(parentIndex+1)))+".\n")+ansis.italic.gray("(use arrow keys to navigate, go back using escape)\n")) - - terminal.gray("\nCurrent value: "+ansis.bold[data ? "green" : "red"](data.toString())+"\n") - terminal.gray("Description: "+ansis.bold(structure.options.cliDisplayDescription ?? "/")+"\n") - - const answer = await terminal.singleColumnMenu(["false (Disabled)","true (Enabled)"],{ - leftPadding:"> ", - style:terminal.cyan, - selectedStyle:terminal.bgDefaultColor.bold, - submittedStyle:terminal.bgBlue, - extraLines:2, - cancelable:true - }).promise - - if (answer.canceled) return await backFn() - - //run config checker - const newValue = (answer.selectedIndex == 0) ? false : true - const newPath = [...path] - newPath.shift() - checker.messages = [] //manually clear previous messages - const isDataValid = structure.check(checker,newValue,newPath) - - if (isDataValid){ - parent[parentIndex] = newValue - - await checker.config.save() - terminal.bold.blue("\n\n✅ Variable saved succesfully!") - await utilities.timer(400) - await backFn() - }else{ - const messages = checker.messages.map((msg) => "=> ["+msg.type.toUpperCase()+"] "+msg.message).join("\n") - terminal.bold.blue("\n\n❌ Variable is invalid! Please try again!") - terminal.gray("\n"+messages) - await utilities.timer(1000+(2000*checker.messages.length)) - await renderConfigBooleanStructureEditor(checker,backFn,structure,data,parent,parentIndex,path) - } -} - -async function renderConfigNumberStructureEditor(checker:api.ODChecker,backFn:(() => api.ODPromiseVoid),structure:api.ODCheckerNumberStructure,data:number,parent:object,parentIndex:string|number,path:(string|number)[],prefillValue?:string){ - if (typeof data != "number") throw new api.ODSystemError("OT CLI => Property is not of the type 'number'. Please check your config for possible errors. (index: "+parentIndex+", path: "+path.join(".")+")") - renderHeader(path) - terminal(ansis.bold.green("You are now editing "+(typeof parentIndex == "string" ? "the number property "+ansis.blue("\""+parentIndex+"\"") : "number property "+ansis.blue("#"+(parentIndex+1)))+".\n")+ansis.italic.gray("(insert a new value and press enter, go back using escape)\n")) - - terminal.gray("\nCurrent value: "+ansis.bold.blue(data.toString())+"\n") - terminal.gray("Description: "+ansis.bold(structure.options.cliDisplayDescription ?? "/")+"\n") - - const answer = await terminal.inputField({ - default:prefillValue, - style:terminal.cyan, - cancelable:true - }).promise - - if (typeof answer != "string") return await backFn() - - //run config checker - const newValue = Number(answer.replaceAll(",",".")) - const newPath = [...path] - newPath.shift() - checker.messages = [] //manually clear previous messages - const isDataValid = structure.check(checker,newValue,newPath) - - if (isDataValid){ - parent[parentIndex] = newValue - - await checker.config.save() - terminal.bold.blue("\n\n✅ Variable saved succesfully!") - await utilities.timer(400) - await backFn() - }else{ - const messages = checker.messages.map((msg) => "=> ["+msg.type.toUpperCase()+"] "+msg.message).join("\n") - terminal.bold.blue("\n\n❌ Variable is invalid! Please try again!") - terminal.red("\n"+messages) - await utilities.timer(1000+(2000*checker.messages.length)) - await renderConfigNumberStructureEditor(checker,backFn,structure,data,parent,parentIndex,path,answer) - } -} - -async function renderConfigStringStructureEditor(checker:api.ODChecker,backFn:(() => api.ODPromiseVoid),structure:api.ODCheckerStringStructure,data:string,parent:object,parentIndex:string|number,path:(string|number)[],prefillValue?:string){ - if (typeof data != "string") throw new api.ODSystemError("OT CLI => Property is not of the type 'string'. Please check your config for possible errors. (index: "+parentIndex+", path: "+path.join(".")+")") - renderHeader(path) - terminal(ansis.bold.green("You are now editing "+(typeof parentIndex == "string" ? "the string property "+ansis.blue("\""+parentIndex+"\"") : "string property "+ansis.blue("#"+(parentIndex+1)))+".\n")+ansis.italic.gray("(insert a new value and press enter, go back using escape)\n")) - - terminal.gray("\nCurrent value:"+(data.includes("\n") ? "\n" : " \"")+ansis.bold.blue(data)+ansis.gray(!data.includes("\n") ? "\"\n" : "\n")) - terminal.gray("Description: "+ansis.bold(structure.options.cliDisplayDescription ?? "/")+"\n") - - const customExtraOptions = (structure instanceof api.ODCheckerCustomStructure_DiscordId) ? structure.extraOptions : undefined - const customAutocompleteFunc = structure.options.cliAutocompleteFunc ? await structure.options.cliAutocompleteFunc() : null - const autocompleteList = ((customAutocompleteFunc ?? structure.options.cliAutocompleteList) ?? customExtraOptions) ?? structure.options.choices - const autoCompleteMenuOpts: Terminal.SingleLineMenuOptions = { - style:terminal.white, - selectedStyle:terminal.bgBlue.white - } - - const input = terminal.inputField({ - default:prefillValue, - style:terminal.cyan, - hintStyle:terminal.gray, - cancelable:false, - autoComplete:autocompleteList, - autoCompleteHint:(!!autocompleteList), - autoCompleteMenu:(autocompleteList) ? autoCompleteMenuOpts as Terminal.Autocompletion : false - }) - - terminal.on("key",async (name:string,matches:string[],data:object) => { - if (name == "ESCAPE"){ - terminal.removeListener("key","cli-render-string-structure-edit") - input.abort() - await backFn() - } - },({id:"cli-render-string-structure-edit"} as any)) - - const answer = await input.promise - terminal.removeListener("key","cli-render-string-structure-edit") - if (typeof answer != "string") return - - //run config checker - const newValue = answer.replaceAll("\\n","\n") - const newPath = [...path] - newPath.shift() - checker.messages = [] //manually clear previous messages - const isDataValid = structure.check(checker,newValue,newPath) - - if (isDataValid){ - parent[parentIndex] = newValue - - await checker.config.save() - terminal.bold.blue("\n\n✅ Variable saved succesfully!") - await utilities.timer(400) - await backFn() - }else{ - const messages = checker.messages.map((msg) => "=> ["+msg.type.toUpperCase()+"] "+msg.message).join("\n") - terminal.bold.blue("\n\n❌ Variable is invalid! Please try again!") - terminal.red("\n"+messages) - await utilities.timer(1000+(2000*checker.messages.length)) - await renderConfigStringStructureEditor(checker,backFn,structure,data,parent,parentIndex,path,answer) - } -} - -async function renderConfigNullStructureEditor(checker:api.ODChecker,backFn:(() => api.ODPromiseVoid),structure:api.ODCheckerNullStructure,data:null,parent:object,parentIndex:string|number,path:(string|number)[]){ - if (data !== null) throw new api.ODSystemError("OT CLI => Property is not of the type 'null'. Please check your config for possible errors. (index: "+parentIndex+", path: "+path.join(".")+")") - renderHeader(path) - terminal(ansis.bold.green("You are now editing "+(typeof parentIndex == "string" ? "the null property "+ansis.blue("\""+parentIndex+"\"") : "null property "+ansis.blue("#"+(parentIndex+1)))+".\n")+ansis.italic.gray("(use arrow keys to navigate, go back using escape)\n")) - - terminal.gray("\nCurrent value: "+ansis.bold.blue("null")+"\n") - terminal.gray("Description: "+ansis.bold(structure.options.cliDisplayDescription ?? "/")+"\n") - - const answer = await terminal.singleColumnMenu(["null"],{ - leftPadding:"> ", - style:terminal.cyan, - selectedStyle:terminal.bgDefaultColor.bold, - submittedStyle:terminal.bgBlue, - extraLines:2, - cancelable:true - }).promise - - if (answer.canceled) return await backFn() - - //run config checker - const newValue = null - const newPath = [...path] - newPath.shift() - checker.messages = [] //manually clear previous messages - const isDataValid = structure.check(checker,newValue,newPath) - - if (isDataValid){ - parent[parentIndex] = newValue - - await checker.config.save() - terminal.bold.blue("\n\n✅ Variable saved succesfully!") - await utilities.timer(400) - await backFn() - }else{ - const messages = checker.messages.map((msg) => "=> ["+msg.type.toUpperCase()+"] "+msg.message).join("\n") - terminal.bold.blue("\n\n❌ Variable is invalid! Please try again!") - terminal.red("\n"+messages) - await utilities.timer(1000+(2000*checker.messages.length)) - await renderConfigNullStructureEditor(checker,backFn,structure,data,parent,parentIndex,path) - } -} - -async function renderConfigTypeSwitchStructureEditor(checker:api.ODChecker,backFn:(() => api.ODPromiseVoid),structure:api.ODCheckerTypeSwitchStructure,data:any,parent:object,parentIndex:string|number,path:(string|number)[]){ - renderHeader(path) - terminal(ansis.bold.green("You are now editing "+(typeof parentIndex == "string" ? "the property "+ansis.blue("\""+parentIndex+"\"") : "property "+ansis.blue("#"+(parentIndex+1)))+".\n")+ansis.italic.gray("(use arrow keys to navigate, go back using escape)\n")) - - terminal.gray("\nCurrent value: "+ansis.bold.blue(data.toString())+"\n") - terminal.gray("Description: "+ansis.bold(structure.options.cliDisplayDescription ?? "/")+"\n") - - const actionsList: string[] = [] - if (structure.options.boolean) actionsList.push("Edit as boolean") - if (structure.options.string) actionsList.push("Edit as string") - if (structure.options.number) actionsList.push("Edit as number") - if (structure.options.object) actionsList.push("Edit as object") - if (structure.options.array) actionsList.push("Edit as array/list") - if (structure.options.null) actionsList.push("Edit as null") - - const answer = await terminal.singleColumnMenu(actionsList,{ - leftPadding:"> ", - style:terminal.cyan, - selectedStyle:terminal.bgDefaultColor.bold, - submittedStyle:terminal.bgBlue, - extraLines:2, - cancelable:true - }).promise - - if (answer.canceled) return await backFn() - - //run selected structure editor (untested) - if (answer.selectedText.startsWith("Edit as boolean") && structure.options.boolean) await renderConfigBooleanStructureEditor(checker,async () => {await renderConfigTypeSwitchStructureEditor(checker,backFn,structure,data,parent,parentIndex,path)},structure.options.boolean,false,parent,parentIndex,path) - else if (answer.selectedText.startsWith("Edit as string") && structure.options.string) await renderConfigStringStructureEditor(checker,async () => {await renderConfigTypeSwitchStructureEditor(checker,backFn,structure,data,parent,parentIndex,path)},structure.options.string,data.toString(),parent,parentIndex,path) - else if (answer.selectedText.startsWith("Edit as number") && structure.options.number) await renderConfigNumberStructureEditor(checker,async () => {await renderConfigTypeSwitchStructureEditor(checker,backFn,structure,data,parent,parentIndex,path)},structure.options.number,0,parent,parentIndex,path) - else if (answer.selectedText.startsWith("Edit as object") && structure.options.object) await renderConfigObjectStructureSelector(checker,async () => {await renderConfigTypeSwitchStructureEditor(checker,backFn,structure,data,parent,parentIndex,path)},structure.options.object,data,parent,parentIndex,path) - else if (answer.selectedText.startsWith("Edit as array/list") && structure.options.array) await renderConfigArrayStructureSelector(checker,async () => {await renderConfigTypeSwitchStructureEditor(checker,backFn,structure,data,parent,parentIndex,path)},structure.options.array,data,parent,parentIndex,path) - else if (answer.selectedText.startsWith("Edit as null") && structure.options.null) await renderConfigNullStructureEditor(checker,async () => {await renderConfigTypeSwitchStructureEditor(checker,backFn,structure,data,parent,parentIndex,path)},structure.options.null,null,parent,parentIndex,path) -} - -function getArrayPreviewStructureNameLength(structure:api.ODCheckerStructure,data:api.ODValidJsonType,parent:object,parentIndex:string|number): number { - if (structure instanceof api.ODCheckerBooleanStructure && typeof data == "boolean") return data.toString().length - else if (structure instanceof api.ODCheckerNumberStructure && typeof data == "number") return data.toString().length - else if (structure instanceof api.ODCheckerStringStructure && typeof data == "string") return data.length - else if (structure instanceof api.ODCheckerNullStructure && data === null) return "Null".length - else if (structure instanceof api.ODCheckerArrayStructure && Array.isArray(data)) return "Array".length - else if (structure instanceof api.ODCheckerObjectStructure && typeof data == "object" && !Array.isArray(data) && data){ - if (!structure.options.cliDisplayKeyInParentArray) return "Object".length - else return data[structure.options.cliDisplayKeyInParentArray].toString().length - - }else if (structure instanceof api.ODCheckerEnabledObjectStructure && typeof data == "object" && !Array.isArray(data) && data){ - const subStructure = structure.options.checker - if (!subStructure) return "".length - return getArrayPreviewStructureNameLength(subStructure,data,parent,parentIndex) - - }else if (structure instanceof api.ODCheckerObjectSwitchStructure && typeof data == "object" && !Array.isArray(data) && data){ - for (const objectTemplate of (structure.options.objects ?? [])){ - if (objectTemplate.properties.every((prop) => data[prop.key] === prop.value)){ - //object template matches data - const subStructure = objectTemplate.checker - return getArrayPreviewStructureNameLength(subStructure,data,parent,parentIndex) - } - } - return "".length - - }else if (structure instanceof api.ODCheckerTypeSwitchStructure){ - if (typeof data == "boolean" && structure.options.boolean) return getArrayPreviewStructureNameLength(structure.options.boolean,data,parent,parentIndex) - else if (typeof data == "number" && structure.options.number) return getArrayPreviewStructureNameLength(structure.options.number,data,parent,parentIndex) - else if (typeof data == "string" && structure.options.string) return getArrayPreviewStructureNameLength(structure.options.string,data,parent,parentIndex) - else if (typeof data == "object" && !Array.isArray(data) && data && structure.options.object) return getArrayPreviewStructureNameLength(structure.options.object,data,parent,parentIndex) - else if (Array.isArray(data) && structure.options.array) return getArrayPreviewStructureNameLength(structure.options.array,data,parent,parentIndex) - else if (data === null && structure.options.null) return getArrayPreviewStructureNameLength(structure.options.null,data,parent,parentIndex) - else return "".length - }else return "".length -} - -function getArrayPreviewFromStructure(structure:api.ODCheckerStructure,data:api.ODValidJsonType,parent:object,parentIndex:string|number,nameLength:number): string { - if (structure instanceof api.ODCheckerBooleanStructure && typeof data == "boolean") return data.toString() - else if (structure instanceof api.ODCheckerNumberStructure && typeof data == "number") return data.toString() - else if (structure instanceof api.ODCheckerStringStructure && typeof data == "string") return data - else if (structure instanceof api.ODCheckerNullStructure && data === null) return "Null" - else if (structure instanceof api.ODCheckerArrayStructure && Array.isArray(data)) return "Array" - else if (structure instanceof api.ODCheckerObjectStructure && typeof data == "object" && !Array.isArray(data) && data){ - const additionalKeys = (structure.options.cliDisplayAdditionalKeysInParentArray ?? []).map((key) => key+": "+data[key].toString()).join(", ") - if (!structure.options.cliDisplayKeyInParentArray) return "Object" - else return data[structure.options.cliDisplayKeyInParentArray].toString().padEnd(nameLength+5," ")+ansis.gray(additionalKeys.length > 0 ? "("+additionalKeys+")" : "") - - }else if (structure instanceof api.ODCheckerEnabledObjectStructure && typeof data == "object" && !Array.isArray(data) && data){ - const subStructure = structure.options.checker - if (!subStructure) return "" - return getArrayPreviewFromStructure(subStructure,data,parent,parentIndex,nameLength) - - }else if (structure instanceof api.ODCheckerObjectSwitchStructure && typeof data == "object" && !Array.isArray(data) && data){ - for (const objectTemplate of (structure.options.objects ?? [])){ - if (objectTemplate.properties.every((prop) => data[prop.key] === prop.value)){ - //object template matches data - const subStructure = objectTemplate.checker - return getArrayPreviewFromStructure(subStructure,data,parent,parentIndex,nameLength) - } - } - return "" - - }else if (structure instanceof api.ODCheckerTypeSwitchStructure){ - if (typeof data == "boolean" && structure.options.boolean) return getArrayPreviewFromStructure(structure.options.boolean,data,parent,parentIndex,nameLength) - else if (typeof data == "number" && structure.options.number) return getArrayPreviewFromStructure(structure.options.number,data,parent,parentIndex,nameLength) - else if (typeof data == "string" && structure.options.string) return getArrayPreviewFromStructure(structure.options.string,data,parent,parentIndex,nameLength) - else if (typeof data == "object" && !Array.isArray(data) && data && structure.options.object) return getArrayPreviewFromStructure(structure.options.object,data,parent,parentIndex,nameLength) - else if (Array.isArray(data) && structure.options.array) return getArrayPreviewFromStructure(structure.options.array,data,parent,parentIndex,nameLength) - else if (data === null && structure.options.null) return getArrayPreviewFromStructure(structure.options.null,data,parent,parentIndex,nameLength) - else return "" - }else return "" -} - -async function chooseAdditionConfigStructure(checker:api.ODChecker,backFn:(() => api.ODPromiseVoid),nextFn:((data:any) => api.ODPromiseVoid),structure:api.ODCheckerStructure,parent:object|any[],parentIndex:string|number,path:(string|number)[],localPath:(string|number)[]){ - if (structure instanceof api.ODCheckerObjectStructure) await renderAdditionConfigObjectStructure(checker,backFn,nextFn,structure,parent,parentIndex,path,localPath) - else if (structure instanceof api.ODCheckerBooleanStructure) await renderAdditionConfigBooleanStructure(checker,backFn,nextFn,structure,parent,parentIndex,path,localPath) - else if (structure instanceof api.ODCheckerNumberStructure) await renderAdditionConfigNumberStructure(checker,backFn,nextFn,structure,parent,parentIndex,path,localPath) - else if (structure instanceof api.ODCheckerStringStructure) await renderAdditionConfigStringStructure(checker,backFn,nextFn,structure,parent,parentIndex,path,localPath) - else if (structure instanceof api.ODCheckerNullStructure) await renderAdditionConfigNullStructure(checker,backFn,nextFn,structure,parent,parentIndex,path,localPath) - else if (structure instanceof api.ODCheckerEnabledObjectStructure) await renderAdditionConfigEnabledObjectStructure(checker,backFn,nextFn,structure,parent,parentIndex,path,localPath) - else if (structure instanceof api.ODCheckerObjectSwitchStructure) await renderAdditionConfigObjectSwitchStructure(checker,backFn,nextFn,structure,parent,parentIndex,path,localPath) - else if (structure instanceof api.ODCheckerArrayStructure) await renderAdditionConfigArrayStructureSelector(checker,backFn,nextFn,structure,parent,parentIndex,path,localPath) - else if (structure instanceof api.ODCheckerTypeSwitchStructure) await renderAdditionConfigTypeSwitchStructure(checker,backFn,nextFn,structure,parent,parentIndex,path,localPath) - else await backFn() -} - -async function renderAdditionConfigObjectStructure(checker:api.ODChecker,backFn:(() => api.ODPromiseVoid),nextFn:((data:any) => api.ODPromiseVoid),structure:api.ODCheckerObjectStructure,parent:object|any[],parentIndex:string|number,path:(string|number)[],localPath:(string|number)[],localData:object={}){ - const children = structure.options.children ?? [] - const skipKeys = (structure.options.cliInitSkipKeys ?? []) - //add skipped properties - for (const key of skipKeys){ - const childStructure = children.find((c) => c.key == key) - if (childStructure){ - const defaultValue = childStructure.checker.options.cliInitDefaultValue - if (childStructure.checker instanceof api.ODCheckerBooleanStructure) localData[key] = (typeof defaultValue == "boolean" ? defaultValue : false) - else if (childStructure.checker instanceof api.ODCheckerNumberStructure) localData[key] = (typeof defaultValue == "number" ? defaultValue : 0) - else if (childStructure.checker instanceof api.ODCheckerStringStructure) localData[key] = (typeof defaultValue == "string" ? defaultValue : "") - else if (childStructure.checker instanceof api.ODCheckerNullStructure) localData[key] = (defaultValue === null ? defaultValue : null) - else if (childStructure.checker instanceof api.ODCheckerArrayStructure) localData[key] = (Array.isArray(defaultValue) ? JSON.parse(JSON.stringify(defaultValue)) : []) - else if (childStructure.checker instanceof api.ODCheckerObjectStructure) localData[key] = ((typeof defaultValue == "object" && !Array.isArray(defaultValue) && defaultValue) ? JSON.parse(JSON.stringify(defaultValue)) : {}) - else if (childStructure.checker instanceof api.ODCheckerObjectSwitchStructure) localData[key] = ((typeof defaultValue == "object" && !Array.isArray(defaultValue) && defaultValue) ? JSON.parse(JSON.stringify(defaultValue)) : {}) - else if (childStructure.checker instanceof api.ODCheckerEnabledObjectStructure) localData[key] = ((typeof defaultValue == "object" && !Array.isArray(defaultValue) && defaultValue) ? JSON.parse(JSON.stringify(defaultValue)) : {}) - else if (childStructure.checker instanceof api.ODCheckerTypeSwitchStructure && typeof defaultValue != "undefined") localData[key] = JSON.parse(JSON.stringify(defaultValue)) - else throw new api.ODSystemError("OT CLI => Object skip key has an invalid checker structure! key: "+key) - } - } - - //add properties that need to be configured - const configChildren = children.filter((c) => !skipKeys.includes(c.key)).map((c) => {return {key:c.key,checker:c.checker}}) - await configureAdditionObjectProperties(checker,configChildren,0,localData,[...path,parentIndex],(typeof parentIndex == "number") ? [...localPath] : [...localPath,parentIndex],async () => { - //go back to previous screen - await backFn() - },async () => { - //finish setup - terminal.bold.blue("\n\n✅ Variable saved succesfully!") - await utilities.timer(400) - await nextFn(localData) - }) -} - -async function configureAdditionObjectProperties(checker:api.ODChecker,children:{key:string,checker:api.ODCheckerStructure}[],currentIndex:number,localData:object,path:(string|number)[],localPath:(string|number)[],backFn:(() => api.ODPromiseVoid),nextFn:(() => api.ODPromiseVoid)){ - if (children.length < 1) return await nextFn() - - const child = children[currentIndex] - await chooseAdditionConfigStructure(checker,async () => { - if (children[currentIndex-1]) await configureAdditionObjectProperties(checker,children,currentIndex-1,localData,path,localPath,backFn,nextFn) - else await backFn() - },async (data) => { - localData[child.key] = data - if (children[currentIndex+1]) await configureAdditionObjectProperties(checker,children,currentIndex+1,localData,path,localPath,backFn,nextFn) - else await nextFn() - },child.checker,localData,child.key,path,localPath) -} - -async function renderAdditionConfigEnabledObjectStructure(checker:api.ODChecker,backFn:(() => api.ODPromiseVoid),nextFn:((data:any) => api.ODPromiseVoid),structure:api.ODCheckerEnabledObjectStructure,parent:object|any[],parentIndex:string|number,path:(string|number)[],localPath:(string|number)[]){ - const enabledProperty = structure.options.property - const enabledValue = structure.options.enabledValue - const subStructure = structure.options.checker - if (!enabledProperty || !subStructure || !subStructure.options.children) return await backFn() - - let propertyStructure: api.ODCheckerBooleanStructure|api.ODCheckerNumberStructure|api.ODCheckerStringStructure - if (typeof enabledValue == "string") propertyStructure = new api.ODCheckerStringStructure("opendiscord:CLI-checker-enabled-object-structure",{}) - else if (typeof enabledValue == "number") propertyStructure = new api.ODCheckerNumberStructure("opendiscord:CLI-checker-enabled-object-structure",{}) - else if (typeof enabledValue == "boolean") propertyStructure = new api.ODCheckerBooleanStructure("opendiscord:CLI-checker-enabled-object-structure",{}) - else throw new Error("OT CLI => enabled object structure has an invalid type of enabledProperty. It must be a primitive boolean/number/string.") - - const localData = {} - await chooseAdditionConfigStructure(checker,backFn,async (data) => { - if (data === enabledValue) await renderAdditionConfigObjectStructure(checker,async () => {await renderAdditionConfigEnabledObjectStructure(checker,backFn,nextFn,structure,parent,parentIndex,path,localPath)},nextFn,subStructure,parent,parentIndex,path,localPath,localData) - else{ - localData[enabledProperty] = data - //copy old object checker to new object checker => all options get de-referenced (this is needed for the new object skip keys are temporary) - const newStructure = new api.ODCheckerObjectStructure(subStructure.id,{children:[]}) - - //copy all options over to the new checker - newStructure.options.children = [...subStructure.options.children] - newStructure.options.cliInitSkipKeys = subStructure.options.children.map((child) => child.key) - for (const key of Object.keys(subStructure.options)){ - if (key != "children" && key != "cliInitSkipKeys") newStructure.options[key] = subStructure.options[key] - } - - //adds all properties to object as "skipKeys", then continues to next function - await renderAdditionConfigObjectStructure(checker,async () => {await renderAdditionConfigEnabledObjectStructure(checker,backFn,nextFn,structure,parent,parentIndex,path,localPath)},nextFn,newStructure,parent,parentIndex,path,localPath,localData) - await nextFn(localData) - } - },propertyStructure,localData,enabledProperty,[...path,parentIndex],[...localPath,parentIndex]) -} - -async function renderAdditionConfigObjectSwitchStructure(checker:api.ODChecker,backFn:(() => api.ODPromiseVoid),nextFn:((data:any) => api.ODPromiseVoid),structure:api.ODCheckerObjectSwitchStructure,parent:object|any[],parentIndex:string|number,path:(string|number)[],localPath:(string|number)[]){ - renderHeader([...path,parentIndex]) - terminal(ansis.bold.green("What type of object would you like to add?\n")+ansis.italic.gray("(use arrow keys to navigate, go back using escape)\n")) - - const answer = await terminal.singleColumnMenu(structure.options.objects.map((obj) => obj.name),{ - leftPadding:"> ", - style:terminal.cyan, - selectedStyle:terminal.bgDefaultColor.bold, - submittedStyle:terminal.bgBlue, - extraLines:2, - cancelable:true - }).promise - - if (answer.canceled) return await backFn() - const objectTemplate = structure.options.objects[answer.selectedIndex] - - //copy old object checker to new object checker => all options get de-referenced (this is needed for the new object switch properties which are temporary) - const oldStructure = objectTemplate.checker - const newStructure = new api.ODCheckerObjectStructure(oldStructure.id,{children:[]}) - - //copy all options over to the new checker - newStructure.options.children = [...oldStructure.options.children] - newStructure.options.cliInitSkipKeys = [...(oldStructure.options.cliInitSkipKeys ?? [])] - for (const key of Object.keys(oldStructure.options)){ - if (key != "children" && key != "cliInitSkipKeys") newStructure.options[key] = oldStructure.options[key] - } - - //add the keys of the object switch properties to the 'cliInitSkipKeys' because they need to be skipped. - objectTemplate.properties.map((p) => p.key).forEach((p) => { - if (!newStructure.options.cliInitSkipKeys) newStructure.options.cliInitSkipKeys = [p] - else if (!newStructure.options.cliInitSkipKeys.includes(p)) newStructure.options.cliInitSkipKeys.push(p) - }) - - //add structure checkers for all properties - for (const prop of objectTemplate.properties){ - if (!newStructure.options.children.find((child) => child.key === prop.key)){ - if (typeof prop.value == "string") newStructure.options.children.unshift({key:prop.key,optional:false,priority:1,checker:new api.ODCheckerStringStructure("opendiscord:CLI-checker-object-switch-structure",{cliInitDefaultValue:prop.value})}) - else if (typeof prop.value == "number") newStructure.options.children.unshift({key:prop.key,optional:false,priority:1,checker:new api.ODCheckerNumberStructure("opendiscord:CLI-checker-object-switch-structure",{cliInitDefaultValue:prop.value})}) - else if (typeof prop.value == "boolean") newStructure.options.children.unshift({key:prop.key,optional:false,priority:1,checker:new api.ODCheckerBooleanStructure("opendiscord:CLI-checker-object-switch-structure",{cliInitDefaultValue:prop.value})}) - } - } - - await chooseAdditionConfigStructure(checker,backFn,nextFn,newStructure,parent,parentIndex,path,localPath) -} - -async function renderAdditionConfigBooleanStructure(checker:api.ODChecker,backFn:(() => api.ODPromiseVoid),nextFn:((data:any) => api.ODPromiseVoid),structure:api.ODCheckerBooleanStructure,parent:object|any[],parentIndex:string|number,path:(string|number)[],localPath:(string|number)[]){ - renderHeader([...path,parentIndex]) - terminal(ansis.bold.green("You are now creating "+(typeof parentIndex == "string" ? "the boolean property "+ansis.blue("\""+parentIndex+"\"") : "boolean property "+ansis.blue("#"+(parentIndex+1)))+".\n")+ansis.italic.gray("(use arrow keys to navigate, go back using escape)\n")) - - terminal.gray("\nProperty: "+ansis.bold.blue(structure.options.cliDisplayName ?? [...localPath,parentIndex].join("."))+"\n") - terminal.gray("Description: "+ansis.bold(structure.options.cliDisplayDescription ?? "/")+"\n") - - const answer = await terminal.singleColumnMenu(["false (Disabled)","true (Enabled)"],{ - leftPadding:"> ", - style:terminal.cyan, - selectedStyle:terminal.bgDefaultColor.bold, - submittedStyle:terminal.bgBlue, - extraLines:2, - cancelable:true - }).promise - - if (answer.canceled) return await backFn() - - //run config checker - const newValue = (answer.selectedIndex == 0) ? false : true - const newPath = [...path] - newPath.shift() - checker.messages = [] //manually clear previous messages - const isDataValid = structure.check(checker,newValue,newPath) - - if (isDataValid){ - terminal.bold.blue("\n\n✅ Variable saved succesfully!") - await utilities.timer(400) - await nextFn(newValue) - }else{ - const messages = checker.messages.map((msg) => "=> ["+msg.type.toUpperCase()+"] "+msg.message).join("\n") - terminal.bold.blue("\n\n❌ Variable is invalid! Please try again!") - terminal.gray("\n"+messages) - await utilities.timer(1000+(2000*checker.messages.length)) - await renderAdditionConfigBooleanStructure(checker,backFn,nextFn,structure,parent,parentIndex,path,localPath) - } -} - -async function renderAdditionConfigNumberStructure(checker:api.ODChecker,backFn:(() => api.ODPromiseVoid),nextFn:((data:any) => api.ODPromiseVoid),structure:api.ODCheckerNumberStructure,parent:object|any[],parentIndex:string|number,path:(string|number)[],localPath:(string|number)[],prefillValue?:string){ - renderHeader([...path,parentIndex]) - terminal(ansis.bold.green("You are now creating "+(typeof parentIndex == "string" ? "the number property "+ansis.blue("\""+parentIndex+"\"") : "number property "+ansis.blue("#"+(parentIndex+1)))+".\n")+ansis.italic.gray("(insert a new value and press enter, go back using escape)\n")) - - terminal.gray("\nProperty: "+ansis.bold.blue(structure.options.cliDisplayName ?? [...localPath,parentIndex].join("."))+"\n") - terminal.gray("Description: "+ansis.bold(structure.options.cliDisplayDescription ?? "/")+"\n") - - const answer = await terminal.inputField({ - default:prefillValue, - style:terminal.cyan, - cancelable:true - }).promise - - if (typeof answer != "string") return await backFn() - - //run config checker - const newValue = Number(answer.replaceAll(",",".")) - const newPath = [...path] - newPath.shift() - checker.messages = [] //manually clear previous messages - const isDataValid = structure.check(checker,newValue,newPath) - - if (isDataValid){ - terminal.bold.blue("\n\n✅ Variable saved succesfully!") - await utilities.timer(400) - await nextFn(newValue) - }else{ - const messages = checker.messages.map((msg) => "=> ["+msg.type.toUpperCase()+"] "+msg.message).join("\n") - terminal.bold.blue("\n\n❌ Variable is invalid! Please try again!") - terminal.red("\n"+messages) - await utilities.timer(1000+(2000*checker.messages.length)) - await renderAdditionConfigNumberStructure(checker,backFn,nextFn,structure,parent,parentIndex,path,localPath,answer) - } -} - -async function renderAdditionConfigStringStructure(checker:api.ODChecker,backFn:(() => api.ODPromiseVoid),nextFn:((data:any) => api.ODPromiseVoid),structure:api.ODCheckerStringStructure,parent:object|any[],parentIndex:string|number,path:(string|number)[],localPath:(string|number)[],prefillValue?:string){ - renderHeader([...path,parentIndex]) - terminal(ansis.bold.green("You are now creating "+(typeof parentIndex == "string" ? "the string property "+ansis.blue("\""+parentIndex+"\"") : "string property "+ansis.blue("#"+(parentIndex+1)))+".\n")+ansis.italic.gray("(insert a new value and press enter, go back using escape)\n")) - - terminal.gray("\nProperty: "+ansis.bold.blue(structure.options.cliDisplayName ?? [...localPath,parentIndex].join("."))+"\n") - terminal.gray("Description: "+ansis.bold(structure.options.cliDisplayDescription ?? "/")+"\n") - - const customExtraOptions = (structure instanceof api.ODCheckerCustomStructure_DiscordId) ? structure.extraOptions : undefined - const customAutocompleteFunc = structure.options.cliAutocompleteFunc ? await structure.options.cliAutocompleteFunc() : null - const autocompleteList = ((customAutocompleteFunc ?? structure.options.cliAutocompleteList) ?? customExtraOptions) ?? structure.options.choices - const autoCompleteMenuOpts: Terminal.SingleLineMenuOptions = { - style:terminal.white, - selectedStyle:terminal.bgBlue.white - } - - const input = terminal.inputField({ - default:prefillValue, - style:terminal.cyan, - hintStyle:terminal.gray, - cancelable:false, - autoComplete:autocompleteList, - autoCompleteHint:(!!autocompleteList), - autoCompleteMenu:(autocompleteList) ? autoCompleteMenuOpts as Terminal.Autocompletion : false - }) - - terminal.on("key",async (name:string,matches:string[],data:object) => { - if (name == "ESCAPE"){ - terminal.removeListener("key","cli-render-string-structure-add") - input.abort() - await backFn() - } - },({id:"cli-render-string-structure-add"} as any)) - - const answer = await input.promise - terminal.removeListener("key","cli-render-string-structure-add") - if (typeof answer != "string") return - - //run config checker - const newValue = answer.replaceAll("\\n","\n") - const newPath = [...path] - newPath.shift() - checker.messages = [] //manually clear previous messages - const isDataValid = structure.check(checker,newValue,newPath) - - if (isDataValid){ - terminal.bold.blue("\n\n✅ Variable saved succesfully!") - await utilities.timer(400) - await nextFn(newValue) - }else{ - const messages = checker.messages.map((msg) => "=> ["+msg.type.toUpperCase()+"] "+msg.message).join("\n") - terminal.bold.blue("\n\n❌ Variable is invalid! Please try again!") - terminal.red("\n"+messages) - await utilities.timer(1000+(2000*checker.messages.length)) - await renderAdditionConfigStringStructure(checker,backFn,nextFn,structure,parent,parentIndex,path,localPath,answer) - } -} - -async function renderAdditionConfigNullStructure(checker:api.ODChecker,backFn:(() => api.ODPromiseVoid),nextFn:((data:any) => api.ODPromiseVoid),structure:api.ODCheckerNullStructure,parent:object|any[],parentIndex:string|number,path:(string|number)[],localPath:(string|number)[]){ - renderHeader([...path,parentIndex]) - terminal(ansis.bold.green("You are now creating "+(typeof parentIndex == "string" ? "the null property "+ansis.blue("\""+parentIndex+"\"") : "null property "+ansis.blue("#"+(parentIndex+1)))+".\n")+ansis.italic.gray("(use arrow keys to navigate, go back using escape)\n")) - - terminal.gray("\nProperty: "+ansis.bold.blue(structure.options.cliDisplayName ?? [...localPath,parentIndex].join("."))+"\n") - terminal.gray("Description: "+ansis.bold(structure.options.cliDisplayDescription ?? "/")+"\n") - - const answer = await terminal.singleColumnMenu(["null"],{ - leftPadding:"> ", - style:terminal.cyan, - selectedStyle:terminal.bgDefaultColor.bold, - submittedStyle:terminal.bgBlue, - extraLines:2, - cancelable:true - }).promise - - if (answer.canceled) return await backFn() - - //run config checker - const newValue = null - const newPath = [...path] - newPath.shift() - checker.messages = [] //manually clear previous messages - const isDataValid = structure.check(checker,newValue,newPath) - - if (isDataValid){ - terminal.bold.blue("\n\n✅ Variable saved succesfully!") - await utilities.timer(400) - await nextFn(newValue) - }else{ - const messages = checker.messages.map((msg) => "=> ["+msg.type.toUpperCase()+"] "+msg.message).join("\n") - terminal.bold.blue("\n\n❌ Variable is invalid! Please try again!") - terminal.red("\n"+messages) - await utilities.timer(1000+(2000*checker.messages.length)) - await renderAdditionConfigNullStructure(checker,backFn,nextFn,structure,parent,parentIndex,path,localPath) - } -} - -async function renderAdditionConfigArrayStructureSelector(checker:api.ODChecker,backFn:(() => api.ODPromiseVoid),nextFn:((data:any) => api.ODPromiseVoid),structure:api.ODCheckerArrayStructure,parent:object|any[],parentIndex:string|number,path:(string|number)[],localPath:(string|number)[],localData:any[]=[]){ - renderHeader([...path,parentIndex]) - terminal(ansis.bold.green("Please select what you would like to do with the new array.\n")+ansis.italic.gray("(use arrow keys to navigate, go back using escape)\n")) - if (!structure.options.propertyChecker) return await backFn() - - terminal.gray("\nProperty: "+ansis.bold.blue(structure.options.cliDisplayName ?? [...localPath,parentIndex].join("."))+"\n") - terminal.gray("Description: "+ansis.bold(structure.options.cliDisplayDescription ?? "/")+"\n") - - const propertyName = structure.options.cliDisplayPropertyName ?? "index" - const answer = await terminal.singleColumnMenu(localData.length < 1 ? [ansis.magenta("-> Continue to next variable"),"Add "+propertyName] : [ - ansis.magenta("-> Continue to next variable"), - "Add "+propertyName, - "Edit "+propertyName, - "Move "+propertyName, - "Remove "+propertyName, - "Duplicate "+propertyName, - - ],{ - leftPadding:"> ", - style:terminal.cyan, - selectedStyle:terminal.bgDefaultColor.bold, - submittedStyle:terminal.bgBlue, - extraLines:2, - cancelable:true - }).promise - - const backFnFunc = async () => {await renderAdditionConfigArrayStructureSelector(checker,backFn,nextFn,structure,parent,parentIndex,path,localPath,localData)} - - if (answer.canceled) return await backFn() - if (answer.selectedIndex == 0) await nextFn(localData) - else if (answer.selectedIndex == 1) await chooseAdditionConfigStructure(checker,backFnFunc,async (newData) => { - localData[localData.length] = newData - await backFnFunc() - },structure.options.propertyChecker,localData,localData.length,path,[]) - else if (answer.selectedIndex == 2) await renderConfigArrayStructureEditSelector(checker,backFnFunc,structure,structure.options.propertyChecker,localData,parent,parentIndex,path) - else if (answer.selectedIndex == 3) await renderconfigArrayStructureMoveSelector(checker,backFnFunc,structure,structure.options.propertyChecker,localData,parent,parentIndex,path) - else if (answer.selectedIndex == 4) await renderconfigArrayStructureRemoveSelector(checker,backFnFunc,structure,structure.options.propertyChecker,localData,parent,parentIndex,path) - else if (answer.selectedIndex == 5) await renderConfigArrayStructureDuplicateSelector(checker,backFnFunc,structure,structure.options.propertyChecker,localData,parent,parentIndex,path) -} - -async function renderAdditionConfigTypeSwitchStructure(checker:api.ODChecker,backFn:(() => api.ODPromiseVoid),nextFn:((data:any) => api.ODPromiseVoid),structure:api.ODCheckerTypeSwitchStructure,parent:object|any[],parentIndex:string|number,path:(string|number)[],localPath:(string|number)[]){ - renderHeader(path) - terminal(ansis.bold.green("You are now creating "+(typeof parentIndex == "string" ? "the property "+ansis.blue("\""+parentIndex+"\"") : "property "+ansis.blue("#"+(parentIndex+1)))+".\n")+ansis.italic.gray("(use arrow keys to navigate, go back using escape)\n")) - - terminal.gray("\nProperty: "+ansis.bold.blue(structure.options.cliDisplayName ?? [...localPath,parentIndex].join("."))+"\n") - terminal.gray("Description: "+ansis.bold(structure.options.cliDisplayDescription ?? "/")+"\n") - - const actionsList: string[] = [] - if (structure.options.boolean) actionsList.push("Create as boolean") - if (structure.options.string) actionsList.push("Create as string") - if (structure.options.number) actionsList.push("Create as number") - if (structure.options.object) actionsList.push("Create as object") - if (structure.options.array) actionsList.push("Create as array/list") - if (structure.options.null) actionsList.push("Create as null") - - const answer = await terminal.singleColumnMenu(actionsList,{ - leftPadding:"> ", - style:terminal.cyan, - selectedStyle:terminal.bgDefaultColor.bold, - submittedStyle:terminal.bgBlue, - extraLines:2, - cancelable:true - }).promise - - if (answer.canceled) return await backFn() - - //run selected structure editor (untested) - if (answer.selectedText.startsWith("Create as boolean") && structure.options.boolean) await renderAdditionConfigBooleanStructure(checker,async () => {await renderAdditionConfigTypeSwitchStructure(checker,backFn,nextFn,structure,parent,parentIndex,path,localPath)},nextFn,structure.options.boolean,parent,parentIndex,path,localPath) - else if (answer.selectedText.startsWith("Create as string") && structure.options.string) await renderAdditionConfigStringStructure(checker,async () => {await renderAdditionConfigTypeSwitchStructure(checker,backFn,nextFn,structure,parent,parentIndex,path,localPath)},nextFn,structure.options.string,parent,parentIndex,path,localPath) - else if (answer.selectedText.startsWith("Create as number") && structure.options.number) await renderAdditionConfigNumberStructure(checker,async () => {await renderAdditionConfigTypeSwitchStructure(checker,backFn,nextFn,structure,parent,parentIndex,path,localPath)},nextFn,structure.options.number,parent,parentIndex,path,localPath) - else if (answer.selectedText.startsWith("Create as object") && structure.options.object) await renderAdditionConfigObjectStructure(checker,async () => {await renderAdditionConfigTypeSwitchStructure(checker,backFn,nextFn,structure,parent,parentIndex,path,localPath)},nextFn,structure.options.object,parent,parentIndex,path,localPath) - else if (answer.selectedText.startsWith("Create as array/list") && structure.options.array) await renderAdditionConfigArrayStructureSelector(checker,async () => {await renderAdditionConfigTypeSwitchStructure(checker,backFn,nextFn,structure,parent,parentIndex,path,localPath)},nextFn,structure.options.array,parent,parentIndex,path,localPath) - else if (answer.selectedText.startsWith("Create as null") && structure.options.null) await renderAdditionConfigNullStructure(checker,async () => {await renderAdditionConfigTypeSwitchStructure(checker,backFn,nextFn,structure,parent,parentIndex,path,localPath)},nextFn,structure.options.null,parent,parentIndex,path,localPath) -} \ No newline at end of file diff --git a/src/core/cli/quickSetup.ts b/src/core/cli/quickSetup.ts index 22a44d5..34211e9 100644 --- a/src/core/cli/quickSetup.ts +++ b/src/core/cli/quickSetup.ts @@ -1,16 +1,11 @@ -import {opendiscord, api, utilities} from "../../index" -import {Terminal, terminal} from "terminal-kit" +import {opendiscord, api, utilities} from "../../index.js" +import * as cli from "@open-discord-bots/framework/cli" +import terminalKit from "terminal-kit" import ansis from "ansis" import * as discord from "discord.js" -import crypto from "crypto" -import {renderHeader, terminate} from "./cli" +import { headerOpts } from "./cli.js" -function generateUniqueIdFromName(name:string){ - //id only allows a-z, 0-9 & dash characters (& replace spaces with dashes) - const filteredChars = name.toLowerCase().replaceAll(" ","-").split("").filter((ch) => /^[a-zA-Z0-9-]{1}$/.test(ch)) - const randomSuffix = "-"+crypto.randomBytes(4).toString("hex") - return filteredChars.join("")+randomSuffix -} +const terminal = terminalKit.terminal interface ODQuickSetupVariables { client?:api.ODClientManager, @@ -20,7 +15,7 @@ interface ODQuickSetupVariables { language?:string, slashCommands?:boolean, textCommands?:boolean, - status?:api.ODJsonConfig_DefaultStatusType, + status?:api.ODGeneralJsonConfig_Status, logChannel?:string|null, ticketCategory?:string|null, ticketOptions:({ @@ -30,7 +25,7 @@ interface ODQuickSetupVariables { buttonColor:api.ODValidButtonColor, buttonEmoji:string|null, channelPrefix:string, - channelSuffix:api.ODJsonConfig_DefaultOptionTicketChannelType["suffix"] + channelSuffix:api.ODOptionsJsonConfig_TicketOptionChannelSettings["suffix"] }|null)[], optionIdStorage:string[], autocloseHours?:number|null, @@ -38,7 +33,7 @@ interface ODQuickSetupVariables { globalUserLimit?:number|null, removeParticipantsOnClose?:boolean, ticketMessageLayout?:"embed"|"text"|null, - emojiStyle?:api.ODJsonConfig_DefaultSystem["emojiStyle"], + emojiStyle?:api.ODGeneralJsonConfig_TicketSystem["emojiStyle"], panelName?:string, panelDescription?:string, panelDropdown?:boolean, @@ -49,7 +44,7 @@ interface ODQuickSetupVariables { const stepCount = (count:number) => "(Step "+count+"/24) " const quickSetupStorage: ODQuickSetupVariables = {ticketOptions:[],optionIdStorage:[]} -const autoCompleteMenuOpts: Terminal.SingleLineMenuOptions = { +const autoCompleteMenuOpts: terminalKit.Terminal.SingleLineMenuOptions = { style:terminal.white, selectedStyle:terminal.bgBlue.white } @@ -103,17 +98,17 @@ export async function renderQuickSetup(backFn:() => api.ODPromiseVoid){ function quickSetupRequiresReset(): boolean { const generalConfig = opendiscord.configs.get("opendiscord:general") - if (generalConfig.data.token != "your bot token here! (or leave empty when using 'tokenFromENV')") return true + if (generalConfig.data.token != "INSERT_BOT_TOKEN") return true if (generalConfig.data.mainColor != "#f8ba00") return true if (generalConfig.data.language != "english") return true if (generalConfig.data.prefix != "!ticket ") return true - if (generalConfig.data.serverId != "discord server id") return true + if (generalConfig.data.serverId != "DISCORD_SERVER_ID") return true return false } async function renderQuickSetupWarning(backFn:() => api.ODPromiseVoid) { - renderHeader("⏱️ Open Ticket Quick Setup: Warning") + cli.renderHeader(headerOpts,"⏱️ Open Ticket Quick Setup: Warning") terminal.bold(ansis.yellow("WARNING! ")+ansis.red("By using the 'Quick Setup' feature, your current config will be completely resetted!")) terminal.gray("\nAre you sure you want to continue?\n") @@ -135,7 +130,7 @@ async function renderQuickSetupWarning(backFn:() => api.ODPromiseVoid) { } async function renderQuickSetupWelcome(backFn:() => api.ODPromiseVoid){ - renderHeader("⏱️ Open Ticket Quick Setup: Introduction") + cli.renderHeader(headerOpts,"⏱️ Open Ticket Quick Setup: Introduction") terminal.bold.underline.blue("Open Ticket: Quick Setup\n") terminal.gray([ @@ -165,7 +160,7 @@ async function renderQuickSetupWelcome(backFn:() => api.ODPromiseVoid){ } async function renderQuickSetupDevPortal(backFn:() => api.ODPromiseVoid){ - renderHeader("⏱️ Open Ticket Quick Setup: Discord Bot & Developer Portal") + cli.renderHeader(headerOpts,"⏱️ Open Ticket Quick Setup: Discord Bot & Developer Portal") terminal.bold.blue(stepCount(1)+"Have you already created a Discord bot you can use for Open Ticket?\n") @@ -189,7 +184,7 @@ async function renderQuickSetupDevPortal(backFn:() => api.ODPromiseVoid){ } async function renderQuickSetupDevPortalGuide(variation:0|1,backFn:() => api.ODPromiseVoid){ - renderHeader("⏱️ Open Ticket Quick Setup: Discord Bot & Developer Portal") + cli.renderHeader(headerOpts,"⏱️ Open Ticket Quick Setup: Discord Bot & Developer Portal") if (variation == 0){ terminal.bold.blue(stepCount(1.1)+"You've mentioned that you don't know how to create a Discord bot.\n\n") @@ -238,10 +233,10 @@ async function quickSetupLogin(token:string): Promise } async function renderQuickSetupBotToken(backFn:() => api.ODPromiseVoid){ - renderHeader("⏱️ Open Ticket Quick Setup: Bot Token") + cli.renderHeader(headerOpts,"⏱️ Open Ticket Quick Setup: Bot Token") terminal.bold.blue(stepCount(2)+"Please insert the token of your discord bot.\n") - terminal.gray("This is used to configure the bot and is then stored securely in the './config/general.json' file.\n\n> ") + terminal.gray("This is used to configure the bot and is then stored securely in the './config/general.jsonc' file.\n\n> ") const answer = await terminal.inputField({ style:terminal.white, @@ -275,7 +270,7 @@ async function renderQuickSetupServer(backFn:() => api.ODPromiseVoid){ const {client} = quickSetupStorage if (!client) return - renderHeader("⏱️ Open Ticket Quick Setup: Discord Server") + cli.renderHeader(headerOpts,"⏱️ Open Ticket Quick Setup: Discord Server") terminal.bold.blue(stepCount(3)+"Please select a Discord Server to use.\n") terminal.gray("The bot will only work in this server.\n\n") @@ -305,7 +300,7 @@ async function renderQuickSetupAdminRoles(selectedAdmins:string[],backFn:() => a const {client,guild} = quickSetupStorage if (!client || !guild) return - renderHeader("⏱️ Open Ticket Quick Setup: Admin Roles") + cli.renderHeader(headerOpts,"⏱️ Open Ticket Quick Setup: Admin Roles") terminal.bold.blue(stepCount(4)+"Please select all 'Global Admins' roles to use.\n") terminal.gray("Users with one of these roles will be able to access & interact with all tickets.\n\n") @@ -341,7 +336,7 @@ async function renderQuickSetupColorPicker(backFn:() => api.ODPromiseVoid){ const {client,guild,globalAdmins} = quickSetupStorage if (!client || !guild || !globalAdmins) return - renderHeader("⏱️ Open Ticket Quick Setup: Main Color") + cli.renderHeader(headerOpts,"⏱️ Open Ticket Quick Setup: Main Color") terminal.bold.blue(stepCount(5)+"Please insert a valid hex-color to use in all embeds.\n") terminal.gray("You can also choose from existing presets. (e.g. red, green, blue, ...)\n\n> ") @@ -352,7 +347,7 @@ async function renderQuickSetupColorPicker(backFn:() => api.ODPromiseVoid){ cancelable:true, autoComplete:Array.from(presetColors.keys()), autoCompleteHint:true, - autoCompleteMenu:autoCompleteMenuOpts as Terminal.Autocompletion + autoCompleteMenu:autoCompleteMenuOpts as terminalKit.Terminal.Autocompletion }).promise if (typeof answer != "string") return await backFn() @@ -375,7 +370,7 @@ async function renderQuickSetupColorPicker(backFn:() => api.ODPromiseVoid){ } async function renderQuickSetupLanguage(backFn:() => api.ODPromiseVoid){ - renderHeader("⏱️ Open Ticket Quick Setup: Language") + cli.renderHeader(headerOpts,"⏱️ Open Ticket Quick Setup: Language") terminal.bold.blue(stepCount(6)+"What language would you like to use in the bot?\n") terminal.gray("View a list of available languages here: https://otgithub.dj-dj.be#-translators\n\n> ") @@ -384,14 +379,14 @@ async function renderQuickSetupLanguage(backFn:() => api.ODPromiseVoid){ style:terminal.white, hintStyle:terminal.gray, cancelable:true, - autoComplete:opendiscord.defaults.getDefault("languageList"), + autoComplete:opendiscord.sharedFuses.getFuse("languageList"), autoCompleteHint:true, - autoCompleteMenu:autoCompleteMenuOpts as Terminal.Autocompletion + autoCompleteMenu:autoCompleteMenuOpts as terminalKit.Terminal.Autocompletion }).promise if (typeof answer != "string") return await backFn() else{ - if (!opendiscord.defaults.getDefault("languageList").includes(answer.toLowerCase())){ + if (!opendiscord.sharedFuses.getFuse("languageList").includes(answer.toLowerCase())){ terminal.red.bold("\n\n❌ Please insert an available language from the list. (TIP: use tab for autocomplete)\n") await utilities.timer(2000) return await renderQuickSetupLanguage(backFn) @@ -403,7 +398,7 @@ async function renderQuickSetupLanguage(backFn:() => api.ODPromiseVoid){ } async function renderQuickSetupCommandTypes(backFn:() => api.ODPromiseVoid){ - renderHeader("⏱️ Open Ticket Quick Setup: Command Types") + cli.renderHeader(headerOpts,"⏱️ Open Ticket Quick Setup: Command Types") terminal.bold.blue(stepCount(7)+"Would you like to use slash commands, text commands or both?\n") terminal.gray("Slash commands are recommended.\n\n") @@ -436,7 +431,7 @@ async function renderQuickSetupCommandTypes(backFn:() => api.ODPromiseVoid){ } async function renderQuickSetupStatusType(backFn:() => api.ODPromiseVoid){ - renderHeader("⏱️ Open Ticket Quick Setup: Status Type") + cli.renderHeader(headerOpts,"⏱️ Open Ticket Quick Setup: Status Type") terminal.bold.blue(stepCount(8)+"Please select the type of status you want to use.\n") terminal.gray("The status will be shown below the bot name in the userlist.\n\n") @@ -471,7 +466,7 @@ async function renderQuickSetupStatusText(backFn:() => api.ODPromiseVoid){ const {status} = quickSetupStorage if (!status) return - renderHeader("⏱️ Open Ticket Quick Setup: Status Text") + cli.renderHeader(headerOpts,"⏱️ Open Ticket Quick Setup: Status Text") terminal.bold.blue(stepCount(8.1)+"What text would you like to display in the status?\n") terminal.gray("This will be appended after the type you have chosen in the previous question.\n\n> ") @@ -494,7 +489,7 @@ async function renderQuickSetupLogs(backFn:() => api.ODPromiseVoid){ const {client,guild} = quickSetupStorage if (!client || !guild) return - renderHeader("⏱️ Open Ticket Quick Setup: Channel Logs") + cli.renderHeader(headerOpts,"⏱️ Open Ticket Quick Setup: Channel Logs") terminal.bold.blue(stepCount(9)+"Please select the 'Text Channel' to use for logs.\n") terminal.gray("All logs of the bot will be sent here. Make sure only admins can access this channel.\n\n") @@ -531,7 +526,7 @@ async function renderQuickSetupTicketCategory(backFn:() => api.ODPromiseVoid){ const {client,guild} = quickSetupStorage if (!client || !guild) return - renderHeader("⏱️ Open Ticket Quick Setup: Ticket Category") + cli.renderHeader(headerOpts,"⏱️ Open Ticket Quick Setup: Ticket Category") terminal.bold.blue(stepCount(10)+"Please select which 'Category' you would like tickets to be created in.\n") terminal.gray("When no category is selected, tickets will appear at the top of the channel list.\n\n") @@ -564,7 +559,7 @@ async function renderQuickSetupTicketCategory(backFn:() => api.ODPromiseVoid){ } async function renderQuickSetupTicketCount(backFn:() => api.ODPromiseVoid){ - renderHeader("⏱️ Open Ticket Quick Setup: Ticket Configuration") + cli.renderHeader(headerOpts,"⏱️ Open Ticket Quick Setup: Ticket Configuration") terminal.bold.blue(stepCount(11)+"How many ticket options/types would you like to create?\n") terminal.gray("You can always add more ticket options/types in the config afterwards.\n\n") @@ -601,7 +596,7 @@ async function renderQuickSetupTicketCount(backFn:() => api.ODPromiseVoid){ } async function renderQuickSetupCreateTicketName(ticketIndex:number,requiredTickets:number,backFn:() => api.ODPromiseVoid){ - renderHeader("⏱️ Open Ticket Quick Setup: Ticket Configuration (Ticket "+(ticketIndex+1)+"/"+requiredTickets+")") + cli.renderHeader(headerOpts,"⏱️ Open Ticket Quick Setup: Ticket Configuration (Ticket "+(ticketIndex+1)+"/"+requiredTickets+")") terminal.bold.blue("("+utilities.ordinalNumber(ticketIndex+1)+" Ticket) Please insert the name of this ticket option.\n") terminal.gray("Recommendation: Clean, short, obvious name, not more than ±30 characters.\n\n> ") @@ -635,7 +630,7 @@ async function renderQuickSetupCreateTicketName(ticketIndex:number,requiredTicke } async function renderQuickSetupCreateTicketDescription(ticketIndex:number,requiredTickets:number,backFn:() => api.ODPromiseVoid){ - renderHeader("⏱️ Open Ticket Quick Setup: Ticket Configuration (Ticket "+(ticketIndex+1)+"/"+requiredTickets+")") + cli.renderHeader(headerOpts,"⏱️ Open Ticket Quick Setup: Ticket Configuration (Ticket "+(ticketIndex+1)+"/"+requiredTickets+")") terminal.bold.blue("("+utilities.ordinalNumber(ticketIndex+1)+" Ticket) Please insert the description of this ticket option.\n") terminal.gray("Recommendation: Use '\\n' (backslash-n) for a newline.\n\n> ") @@ -655,7 +650,7 @@ async function renderQuickSetupCreateTicketDescription(ticketIndex:number,requir } async function renderQuickSetupCreateTicketButtonType(ticketIndex:number,requiredTickets:number,backFn:() => api.ODPromiseVoid){ - renderHeader("⏱️ Open Ticket Quick Setup: Ticket Configuration (Ticket "+(ticketIndex+1)+"/"+requiredTickets+")") + cli.renderHeader(headerOpts,"⏱️ Open Ticket Quick Setup: Ticket Configuration (Ticket "+(ticketIndex+1)+"/"+requiredTickets+")") terminal.bold.blue("("+utilities.ordinalNumber(ticketIndex+1)+" Ticket) How would you like to display the ticket name in the button/dropdown?\n") terminal.gray("You will be able to choose between dropdown/buttons when configuring panels.\n\n") @@ -686,7 +681,7 @@ async function renderQuickSetupCreateTicketButtonType(ticketIndex:number,require } async function renderQuickSetupCreateTicketButtonEmoji(ticketIndex:number,requiredTickets:number,backFn:() => api.ODPromiseVoid){ - renderHeader("⏱️ Open Ticket Quick Setup: Ticket Configuration (Ticket "+(ticketIndex+1)+"/"+requiredTickets+")") + cli.renderHeader(headerOpts,"⏱️ Open Ticket Quick Setup: Ticket Configuration (Ticket "+(ticketIndex+1)+"/"+requiredTickets+")") terminal.bold.blue("("+utilities.ordinalNumber(ticketIndex+1)+" Ticket) Please insert the button emoji of this ticket option.\n") terminal.gray("Only 1 emoji allowed. Tip: Insert custom emoji's via the following syntax: <:12345678910:emoji_name>\n\n> ") @@ -714,7 +709,7 @@ async function renderQuickSetupCreateTicketButtonEmoji(ticketIndex:number,requir } async function renderQuickSetupCreateTicketButtonColor(ticketIndex:number,requiredTickets:number,backFn:() => api.ODPromiseVoid){ - renderHeader("⏱️ Open Ticket Quick Setup: Ticket Configuration (Ticket "+(ticketIndex+1)+"/"+requiredTickets+")") + cli.renderHeader(headerOpts,"⏱️ Open Ticket Quick Setup: Ticket Configuration (Ticket "+(ticketIndex+1)+"/"+requiredTickets+")") terminal.bold.blue("("+utilities.ordinalNumber(ticketIndex+1)+" Ticket) What color would you like the button to be?\n") terminal.gray("This will not apply when choosing 'dropdown' mode in the panel configuration.\n\n") @@ -742,7 +737,7 @@ async function renderQuickSetupCreateTicketButtonColor(ticketIndex:number,requir } async function renderQuickSetupCreateTicketChannelPrefix(ticketIndex:number,requiredTickets:number,backFn:() => api.ODPromiseVoid){ - renderHeader("⏱️ Open Ticket Quick Setup: Ticket Configuration (Ticket "+(ticketIndex+1)+"/"+requiredTickets+")") + cli.renderHeader(headerOpts,"⏱️ Open Ticket Quick Setup: Ticket Configuration (Ticket "+(ticketIndex+1)+"/"+requiredTickets+")") terminal.bold.blue("("+utilities.ordinalNumber(ticketIndex+1)+" Ticket) Please insert the channel prefix of this ticket option.\n") terminal.gray("Examples: 'ticket-', 'question-', 'test-channel-', ...\n\n> ") @@ -766,7 +761,7 @@ async function renderQuickSetupCreateTicketChannelPrefix(ticketIndex:number,requ } async function renderQuickSetupCreateTicketChannelSuffix(ticketIndex:number,requiredTickets:number,backFn:() => api.ODPromiseVoid){ - renderHeader("⏱️ Open Ticket Quick Setup: Ticket Configuration (Ticket "+(ticketIndex+1)+"/"+requiredTickets+")") + cli.renderHeader(headerOpts,"⏱️ Open Ticket Quick Setup: Ticket Configuration (Ticket "+(ticketIndex+1)+"/"+requiredTickets+")") terminal.bold.blue("("+utilities.ordinalNumber(ticketIndex+1)+" Ticket) Please select the channel suffix mode of this ticket option.\n") terminal.gray("The suffix is appended after the prefix and will be generated on ticket creation.\n\n") @@ -806,7 +801,7 @@ async function renderQuickSetupCreateTicketChannelSuffix(ticketIndex:number,requ } async function renderQuickSetupAutoclose(backFn:() => api.ODPromiseVoid){ - renderHeader("⏱️ Open Ticket Quick Setup: Ticket Autoclose") + cli.renderHeader(headerOpts,"⏱️ Open Ticket Quick Setup: Ticket Autoclose") terminal.bold.blue(stepCount(12)+"Would you like to enable autoclosing tickets?\n") terminal.gray("Applies to all created tickets. You can always change/disable autoclose per ticket-option in the config afterwards.\n\n") @@ -839,7 +834,7 @@ async function renderQuickSetupAutoclose(backFn:() => api.ODPromiseVoid){ } async function renderQuickSetupCooldown(backFn:() => api.ODPromiseVoid){ - renderHeader("⏱️ Open Ticket Quick Setup: Ticket Cooldown") + cli.renderHeader(headerOpts,"⏱️ Open Ticket Quick Setup: Ticket Cooldown") terminal.bold.blue(stepCount(13)+"Would you like to enable ticket creation cooldown?\n") terminal.gray("Applies to all created tickets. You can always change/disable cooldown per ticket-option in the config afterwards.\n\n") @@ -873,7 +868,7 @@ async function renderQuickSetupCooldown(backFn:() => api.ODPromiseVoid){ } async function renderQuickSetupLimits(backFn:() => api.ODPromiseVoid){ - renderHeader("⏱️ Open Ticket Quick Setup: Ticket Limits") + cli.renderHeader(headerOpts,"⏱️ Open Ticket Quick Setup: Ticket Limits") terminal.bold.blue(stepCount(14)+"Would you like to enable user ticket creation limits?\n") terminal.gray("Applies to all created tickets. You can always change/disable limits globally or per ticket-option in the config afterwards.\n\n") @@ -904,7 +899,7 @@ async function renderQuickSetupLimits(backFn:() => api.ODPromiseVoid){ } async function renderQuickSetupCloseParticipants(backFn:() => api.ODPromiseVoid){ - renderHeader("⏱️ Open Ticket Quick Setup: Ticket Close Configuration") + cli.renderHeader(headerOpts,"⏱️ Open Ticket Quick Setup: Ticket Close Configuration") terminal.bold.blue(stepCount(15)+"Would you like to remove all ticket participants when closing the ticket?\n") terminal.gray("When a ticket is closed, only admins can read/write in the ticket. Reopen ticket to restore read/write perms.\n\n") @@ -929,7 +924,7 @@ async function renderQuickSetupCloseParticipants(backFn:() => api.ODPromiseVoid) } async function renderQuickSetupTicketMessageLayout(backFn:() => api.ODPromiseVoid){ - renderHeader("⏱️ Open Ticket Quick Setup: Ticket Message Configuration") + cli.renderHeader(headerOpts,"⏱️ Open Ticket Quick Setup: Ticket Message Configuration") terminal.bold.blue(stepCount(16)+"How would you like the (initial) ticket message to be displayed?\n") terminal.gray("This message is sent by the bot when creating a ticket and contains buttons like closing, claiming & deleting.\n\n") @@ -955,7 +950,7 @@ async function renderQuickSetupTicketMessageLayout(backFn:() => api.ODPromiseVoi } async function renderQuickSetupEmojiStyle(backFn:() => api.ODPromiseVoid){ - renderHeader("⏱️ Open Ticket Quick Setup: Emoji Style") + cli.renderHeader(headerOpts,"⏱️ Open Ticket Quick Setup: Emoji Style") terminal.bold.blue(stepCount(17)+"How would you like emojis to be displayed in messages?\n") terminal.gray("This will affect emojis in all messages of the bot, but does not apply to buttons & dropdowns.\n\n") @@ -982,7 +977,7 @@ async function renderQuickSetupEmojiStyle(backFn:() => api.ODPromiseVoid){ } async function renderQuickSetupPanelName(backFn:() => api.ODPromiseVoid){ - renderHeader("⏱️ Open Ticket Quick Setup: Panel Name") + cli.renderHeader(headerOpts,"⏱️ Open Ticket Quick Setup: Panel Name") terminal.bold.blue(stepCount(18)+"Please insert the name of the ticket panel.\n") terminal.gray("This will be shown as the title of the panel message where all tickets are located.\n\n> ") @@ -1005,7 +1000,7 @@ async function renderQuickSetupPanelName(backFn:() => api.ODPromiseVoid){ } async function renderQuickSetupPanelDescription(backFn:() => api.ODPromiseVoid){ - renderHeader("⏱️ Open Ticket Quick Setup: Panel Description") + cli.renderHeader(headerOpts,"⏱️ Open Ticket Quick Setup: Panel Description") terminal.bold.blue(stepCount(19)+"Please insert the description of the ticket panel.\n") terminal.gray("Shown below the title. Can be used to explain some info/rules about the ticket system.\n\n> ") @@ -1024,7 +1019,7 @@ async function renderQuickSetupPanelDescription(backFn:() => api.ODPromiseVoid){ } async function renderQuickSetupPanelDropdown(backFn:() => api.ODPromiseVoid){ - renderHeader("⏱️ Open Ticket Quick Setup: Panel Mode") + cli.renderHeader(headerOpts,"⏱️ Open Ticket Quick Setup: Panel Mode") terminal.bold.blue(stepCount(20)+"Do you want to show the tickets as buttons or a dropdown?\n") terminal.gray("Dropdown doesn't support colors and cannot contain option types other than 'tickets' (e.g. website/url or reaction roles).\n\n") @@ -1049,7 +1044,7 @@ async function renderQuickSetupPanelDropdown(backFn:() => api.ODPromiseVoid){ } async function renderQuickSetupPanelLayout(backFn:() => api.ODPromiseVoid){ - renderHeader("⏱️ Open Ticket Quick Setup: Panel Layout") + cli.renderHeader(headerOpts,"⏱️ Open Ticket Quick Setup: Panel Layout") terminal.bold.blue(stepCount(21)+"How would you like the panel message to be displayed?\n") terminal.gray("Most of the time embeds are used. But for a simpler solution, you can choose the text layout.\n\n") @@ -1074,7 +1069,7 @@ async function renderQuickSetupPanelLayout(backFn:() => api.ODPromiseVoid){ } async function renderQuickSetupPanelDescribeOptions(backFn:() => api.ODPromiseVoid){ - renderHeader("⏱️ Open Ticket Quick Setup: Panel Option Descriptions") + cli.renderHeader(headerOpts,"⏱️ Open Ticket Quick Setup: Panel Option Descriptions") terminal.bold.blue(stepCount(22)+"Would you like the panel to have auto-generated (ticket-)option descriptions?\n") terminal.gray("It will use the 'name' & 'description' of each ticket option and displays it below the panel description.\n\n") @@ -1101,7 +1096,7 @@ async function renderQuickSetupPanelDescribeOptions(backFn:() => api.ODPromiseVo } async function renderQuickSetupPanelMaxTicketsWarning(backFn:() => api.ODPromiseVoid){ - renderHeader("⏱️ Open Ticket Quick Setup: Ticket Close Configuration") + cli.renderHeader(headerOpts,"⏱️ Open Ticket Quick Setup: Ticket Close Configuration") terminal.bold.blue(stepCount(23)+"Would you like to show the maximum amount of tickets a user can create in the panel?\n") terminal.gray("This will show the amount of tickets a user can create at the same time when limits are enabled.\n\n") @@ -1126,7 +1121,7 @@ async function renderQuickSetupPanelMaxTicketsWarning(backFn:() => api.ODPromise } async function renderQuickSetupReady(backFn:() => api.ODPromiseVoid){ - renderHeader("😎 Open Ticket Quick Setup: Overview") + cli.renderHeader(headerOpts,"😎 Open Ticket Quick Setup: Overview") terminal.bold.blue(stepCount(24)+"This is the overview of your ticket bot configuration!\n") terminal.gray("Press 'Enter' to save the result to the config.\n\n") @@ -1171,7 +1166,7 @@ async function renderQuickSetupReady(backFn:() => api.ODPromiseVoid){ } async function renderQuickSetupFinished(){ - renderHeader("✅ Open Ticket Quick Setup: Ready") + cli.renderHeader(headerOpts,"✅ Open Ticket Quick Setup: Ready") terminal.bold.green("The config has been saved succesfully and the bot is now ready for usage!\n") terminal.gray("Press 'Enter' to exit the Quick Setup CLI.\n\n") @@ -1203,18 +1198,14 @@ async function renderQuickSetupFinished(){ }).promise //stop CLI - return await terminate() + return await cli.terminate(headerOpts) } async function saveQuickSetupConfig(){ //GENERAL CONFIG const generalConfig = opendiscord.configs.get("opendiscord:general") - const generalConfigData: api.ODJsonConfig_DefaultGeneralData = { - _INFO:{ - support:"https://otdocs.dj-dj.be", - discord:"https://discord.dj-dj.be", - version:"open-ticket-"+opendiscord.versions.get("opendiscord:version").toString() - }, + const generalConfigData: api.ODGeneralJsonConfig_GeneralData = { + _CONFIG_VERSION:"open-ticket-"+opendiscord.versions.get("opendiscord:version").toString(), token:quickSetupStorage.client?.token ?? "", tokenFromENV:false, @@ -1229,8 +1220,29 @@ async function saveQuickSetupConfig(){ textCommands:quickSetupStorage.textCommands ?? false, status:quickSetupStorage.status ?? {enabled:false,mode:"online",type:"custom",text:"",state:""}, + logs:{ + enabled:(typeof quickSetupStorage.logChannel == "string"), + channel:quickSetupStorage.logChannel ?? "", + logMessages:{ + creation:{dm:true,logs:true}, + closing:{dm:true,logs:true}, + deleting:{dm:true,logs:true}, + reopening:{dm:false,logs:true}, + claiming:{dm:false,logs:true}, + pinning:{dm:false,logs:true}, + adding:{dm:false,logs:true}, + removing:{dm:false,logs:true}, + renaming:{dm:false,logs:true}, + moving:{dm:true,logs:true}, + blacklisting:{dm:true,logs:true}, + transferring:{dm:true,logs:true}, + topicChange:{dm:false,logs:true}, + priorityChange:{dm:false,logs:true}, + reactionRole:{dm:false,logs:true} + } + }, - system:{ + ticketSystem:{ preferSlashOverText:quickSetupStorage.slashCommands ?? false, sendErrorOnUnknownCommand:true, questionFieldsInCodeBlock:true, @@ -1241,10 +1253,11 @@ async function saveQuickSetupConfig(){ alwaysShowReason:false, emojiStyle:quickSetupStorage.emojiStyle ?? "before", pinEmoji:"📌", + closeEmoji:"", - replyOnTicketCreation:false, + replyOnTicketCreation:true, replyOnReactionRole:true, - askPriorityOnTicketCreation:false, + askPriorityOnTicketCreation:true, removeParticipantsOnClose:quickSetupStorage.removeParticipantsOnClose ?? false, disableAutocloseAfterReopen:true, autodeleteRequiresClosedTicket:true, @@ -1252,7 +1265,7 @@ async function saveQuickSetupConfig(){ allowCloseBeforeMessage:false, allowCloseBeforeAdminMessage:true, useTranslatedConfigChecker:true, - pinFirstTicketMessage:false, + pinFirstTicketMessage:true, enableTicketClaimButtons:true, enableTicketCloseButtons:true, @@ -1260,11 +1273,7 @@ async function saveQuickSetupConfig(){ enableTicketDeleteButtons:true, enableTicketActionWithReason:true, enableDeleteWithoutTranscript:true, - - logs:{ - enabled:(typeof quickSetupStorage.logChannel == "string"), - channel:quickSetupStorage.logChannel ?? "" - }, + enableCreateTicketForOtherUser:true, limits:{ enabled:(typeof quickSetupStorage.globalUserLimit == "number"), @@ -1283,49 +1292,41 @@ async function saveQuickSetupConfig(){ showCreator:false, showParticipants:false }, - - permissions:{ - help:"everyone", - panel:"admin", - ticket:"everyone", - close:"admin", - delete:"admin", - reopen:"admin", - claim:"admin", - unclaim:"admin", - pin:"admin", - unpin:"admin", - move:"admin", - rename:"admin", - add:"admin", - remove:"admin", - blacklist:"admin", - stats:"everyone", - clear:"admin", - autoclose:"admin", - autodelete:"admin", - transfer:"admin", - topic:"admin", - priority:"admin", + + closedCategory:{ + enabled:false, + categoryId:"" }, - - messages:{ - creation:{dm:true,logs:true}, - closing:{dm:true,logs:true}, - deleting:{dm:true,logs:true}, - reopening:{dm:false,logs:true}, - claiming:{dm:false,logs:true}, - pinning:{dm:false,logs:true}, - adding:{dm:false,logs:true}, - removing:{dm:false,logs:true}, - renaming:{dm:false,logs:true}, - moving:{dm:true,logs:true}, - blacklisting:{dm:true,logs:true}, - transferring:{dm:true,logs:true}, - topicChange:{dm:false,logs:true}, - priorityChange:{dm:false,logs:true}, - reactionRole:{dm:false,logs:true} - } + backupCategory:{ + enabled:false, + categoryId:"" + }, + claimedCategories:[], + }, + permissions:{ + help:"everyone", + panel:"admin", + ticket:"everyone", + close:"admin", + delete:"admin", + reopen:"admin", + claim:"admin", + unclaim:"admin", + pin:"admin", + unpin:"admin", + move:"admin", + rename:"admin", + add:"admin", + remove:"admin", + blacklist:"admin", + stats:"everyone", + clear:"admin", + autoclose:"admin", + autodelete:"admin", + transfer:"admin", + topic:"admin", + priority:"admin", + transcripts:"admin" } } generalConfig.data = generalConfigData @@ -1333,14 +1334,15 @@ async function saveQuickSetupConfig(){ //QUESTIONS CONFIG => no configuration needed (coming soonTM) const questionsConfig = opendiscord.configs.get("opendiscord:questions") - const questionsConfigData: api.ODJsonConfig_DefaultQuestionsData = [ + const questionsConfigData: api.ODQuestionsJsonConfig_QuestionsData = [ { id:"example-question-1", name:"Example Question 1", + description:"This is a short text input question.", type:"short", - required:true, - placeholder:"Insert your short answer here!", + + placeholder:"Insert answer...", length:{ enabled:false, min:0, @@ -1350,15 +1352,82 @@ async function saveQuickSetupConfig(){ { id:"example-question-2", name:"Example Question 2", + description:"This is a paragraph text input question.", type:"paragraph", - required:false, - placeholder:"Insert your long answer here!", + + placeholder:"Insert answer...", length:{ enabled:false, min:0, max:1000 } + }, + { + id:"example-question-3", + name:"Example Question 3", + description:"This is a dropdown question.", + type:"dropdown", + required:false, + + placeholder:"Choose your answer...", + choices:[ + {title:"Choice A",description:"Apple",emoji:"🍎"}, + {title:"Choice B",description:"Banana",emoji:"🍌"}, + {title:"Choice C",description:"Orange",emoji:"🍊"}, + {title:"Choice D",description:"Kiwi",emoji:"🥝"} + ] + }, + { + id:"example-question-4", + name:"Example Question 4", + description:"This is a radio select question.", + type:"radio-select", + required:true, + + choices:[ + {title:"Choice A",description:"Up",selectedByDefault:false}, + {title:"Choice B",description:"Down",selectedByDefault:false}, + {title:"Choice C",description:"Left",selectedByDefault:false}, + {title:"Choice D",description:"Right",selectedByDefault:false} + ] + }, + { + id:"example-question-5", + name:"Example Question 5", + description:"This is a checkbox select question.", + type:"checkbox-select", + required:true, + + limits:{ + enabled:false, + min:0, + max:10 + }, + choices:[ + {title:"Choice A",description:"Happiness",selectedByDefault:false}, + {title:"Choice B",description:"Anger",selectedByDefault:false}, + {title:"Choice C",description:"Sadness",selectedByDefault:false}, + {title:"Choice D",description:"Fear",selectedByDefault:false} + ] + }, + { + id:"example-question-6", + name:"Example Question 6", + description:"This is a file upload question.", + type:"file-upload", + required:true, + + limits:{ + enabled:false, + min:0, + max:1 + } + }, + { + id:"example-text-display", + type:"text-display", + textContents:"This is a text display. It isn't a question, but allows you to display a text, explaination or details." } ] questionsConfig.data = questionsConfigData @@ -1366,8 +1435,8 @@ async function saveQuickSetupConfig(){ //OPTIONS CONFIG const optionsConfig = opendiscord.configs.get("opendiscord:options") - const optionsConfigData: api.ODJsonConfig_DefaultOptionsData = quickSetupStorage.ticketOptions.filter((ticket) => ticket !== null).map((ticket) => { - const id = generateUniqueIdFromName(ticket.name) + const optionsConfigData: api.ODOptionsJsonConfig_OptionsData = quickSetupStorage.ticketOptions.filter((ticket) => ticket !== null).map((ticket) => { + const id = cli.generateUniqueIdFromName(ticket.name) quickSetupStorage.optionIdStorage.push(id) return { @@ -1391,9 +1460,6 @@ async function saveQuickSetupConfig(){ prefix:ticket.channelPrefix, suffix:ticket.channelSuffix, category:quickSetupStorage.ticketCategory ?? "", - closedCategory:"", - backupCategory:"", - claimedCategory:[], topic:ticket.description }, @@ -1464,9 +1530,9 @@ async function saveQuickSetupConfig(){ //PANELS CONFIG const panelsConfig = opendiscord.configs.get("opendiscord:panels") - const panelsConfigData: api.ODJsonConfig_DefaultPanelsData = [ + const panelsConfigData: api.ODPanelsJsonConfig_PanelsData = [ { - id:generateUniqueIdFromName(quickSetupStorage.panelName ?? "ticket-panel"), + id:cli.generateUniqueIdFromName(quickSetupStorage.panelName ?? "ticket-panel"), name:quickSetupStorage.panelName ?? "Ticket Panel", dropdown:quickSetupStorage.panelDropdown ?? false, options:quickSetupStorage.optionIdStorage, @@ -1489,6 +1555,7 @@ async function saveQuickSetupConfig(){ }, settings:{ dropdownPlaceholder:"Open a ticket", + maximumButtonsPerRow:5, enableMaxTicketsWarningInText:(quickSetupStorage.panelLayout == "text" && (quickSetupStorage.panelMaxTicketsWarning ?? false)), enableMaxTicketsWarningInEmbed:(quickSetupStorage.panelLayout == "embed" && (quickSetupStorage.panelMaxTicketsWarning ?? false)), @@ -1506,7 +1573,7 @@ async function saveQuickSetupConfig(){ //TRANSCRIPTS CONFIG => no configuration needed (coming soonTM) const transcriptsConfig = opendiscord.configs.get("opendiscord:transcripts") - const transcriptsConfigData: api.ODJsonConfig_DefaultTranscriptsData = { + const transcriptsConfigData: api.ODTranscriptsJsonConfig_TranscriptsData = { general:{ enabled:(typeof quickSetupStorage.logChannel == "string"), diff --git a/src/core/main.ts b/src/core/main.ts new file mode 100644 index 0000000..dabccc8 --- /dev/null +++ b/src/core/main.ts @@ -0,0 +1,144 @@ +/////////////////////////////////////// +//OPEN TICKET MAIN MODULE +/////////////////////////////////////// +import fs from "fs" +import path from "path" +import * as api from "./api.js" +import * as utilities from "@open-discord-bots/framework/utilities" + +export class ODOpenTicketMain extends api.ODMain { + declare versions: api.ODMappedVersionManager + declare events: api.ODMappedEventManager + + declare plugins: api.ODMappedPluginManager + declare flags: api.ODMappedFlagManager + declare progressbars: api.ODMappedProgressBarManager + declare configs: api.ODMappedConfigManager + declare databases: api.ODMappedDatabaseManager + declare sessions: api.ODMappedSessionManager + declare languages: api.ODMappedLanguageManager + + declare checkers: api.ODMappedCheckerManager + declare builders: api.ODMappedBuilderManager + declare components: api.ODMappedComponentManager + declare responders: api.ODMappedResponderManager + declare actions: api.ODMappedActionManager + declare verifybars: api.ODMappedVerifyBarManager + declare permissions: api.ODMappedPermissionManager + declare cooldowns: api.ODMappedCooldownManager + declare helpmenu: api.ODMappedHelpMenuManager + declare statistics: api.ODMappedStatisticManager + declare tasks: api.ODMappedTaskManager + declare posts: api.ODMappedPostManager + declare states: api.ODMappedStateManager + + declare client: api.ODMappedClientManager + declare livestatus: api.ODMappedLiveStatusManager + declare startscreen: api.ODMappedStartScreenManager + + ///////////////////// + //// OPEN TICKET //// + ///////////////////// + + /**Open Ticket specific fuses. With these fuses/switches, you can turn off "default behaviours" from the bot. Useful for replacing default behaviour with a custom implementation. */ + fuses: api.ODFuseManager + /**The manager that manages all the data of questions in the bot. (these are used in options & tickets) */ + questions: api.ODQuestionManager + /**The manager that manages all the data of options in the bot. (these are used for panels, ticket creation, reaction roles) */ + options: api.ODOptionManager + /**The manager that manages all the data of panels in the bot. (panels contain the options) */ + panels: api.ODPanelManager + /**The manager that manages all tickets in the bot. (here, you can get & edit a lot of data from tickets) */ + tickets: api.ODTicketManager + /**The manager that manages the ticket blacklist. (people who are blacklisted can't create a ticket) */ + blacklist: api.ODBlacklistManager + /**The manager that manages the ticket transcripts. (both the history & compilers) */ + transcripts: api.ODMappedTranscriptManager + /**The manager that manages all reaction roles in the bot. (here, you can add additional data to roles) */ + roles: api.ODRoleManager + /**The manager that manages all priority levels in the bot. (register/edit ticket priority levels) */ + priorities: api.ODMappedPriorityManager + + constructor(){ + const version = api.ODVersion.fromString("opendiscord:version","v4.2.0") + const debugfile = new api.ODDebugFileManager("./","otdebug.txt",5000,version) + const console = new api.ODConsoleManager(100,debugfile) + const debug = new api.ODDebugger(console) + const client = new api.ODMappedClientManager(debug) + const livestatus = new api.ODMappedLiveStatusManager(debug,console) + const permissions = new api.ODMappedPermissionManager(debug,client,true) + + super({ + versions:new api.ODMappedVersionManager(), + debugfile,console,debug, + events:new api.ODMappedEventManager(debug), + processStartupDate:new Date(), + readyStartupDate:null, + + plugins:new api.ODMappedPluginManager(debug), + flags:new api.ODMappedFlagManager(debug), + progressbars:new api.ODMappedProgressBarManager(debug), + configs:new api.ODMappedConfigManager(debug), + databases:new api.ODMappedDatabaseManager(debug), + sessions:new api.ODMappedSessionManager(debug), + languages:new api.ODMappedLanguageManager(debug,false), + + checkers:new api.ODMappedCheckerManager(debug, + new api.ODCheckerStorage(), + new api.ODDefaultCheckerRenderer("#f8ba00","https://discord.dj-dj.be","https://otdocs.dj-dj.be"), + new api.ODMappedCheckerTranslationRegister(), + new api.ODMappedCheckerFunctionManager(debug) + ), + builders:new api.ODMappedBuilderManager(debug), + components:new api.ODMappedComponentManager(debug), + client, + responders:new api.ODMappedResponderManager(debug,client), + actions:new api.ODMappedActionManager(debug), + verifybars:new api.ODMappedVerifyBarManager(debug), + permissions, + cooldowns:new api.ODMappedCooldownManager(debug), + helpmenu:new api.ODMappedHelpMenuManager(debug), + statistics:new api.ODMappedStatisticManager(debug), + tasks:new api.ODMappedTaskManager(debug), + posts:new api.ODMappedPostManager(debug), + states:new api.ODMappedStateManager(debug), + + sharedFuses:utilities.sharedFuses, + env:new api.ODEnvHelper(), + livestatus, + startscreen:new api.ODMappedStartScreenManager(debug,livestatus), + },"openticket") + + this.livestatus.useMain(this) + this.versions.add(api.ODVersion.fromString("opendiscord:version",this.readVersionFromPackage())) + this.versions.add(api.ODVersion.fromString("opendiscord:transcripts","v2.1.0")) + + //OPEN TICKET + this.fuses = new api.ODFuseManager({ + priorityLoading:true, + questionLoading:true, + optionLoading:true, + panelLoading:true, + ticketLoading:true, + roleLoading:true, + blacklistLoading:true, + transcriptCompilerLoading:true, + transcriptHistoryLoading:true, + autocloseCheckInterval:300000, //5 minutes + autodeleteCheckInterval:300000 //5 minutes + }) + this.questions = new api.ODQuestionManager(debug) + this.options = new api.ODOptionManager(debug) + this.panels = new api.ODPanelManager(debug) + this.tickets = new api.ODTicketManager(debug,client) + this.blacklist = new api.ODBlacklistManager(debug) + this.transcripts = new api.ODMappedTranscriptManager(debug,this.tickets,client,permissions) + this.roles = new api.ODRoleManager(debug) + this.priorities = new api.ODMappedPriorityManager(debug) + } + + private readVersionFromPackage(): string { + const packageJson: {version:string} = JSON.parse(fs.readFileSync(path.join(process.cwd(),"./package.json")).toString()) + return "v"+packageJson.version + } +} \ No newline at end of file diff --git a/src/core/api/defaults/action.ts b/src/core/mappings/action.ts similarity index 64% rename from src/core/api/defaults/action.ts rename to src/core/mappings/action.ts index c86269b..4b1e354 100644 --- a/src/core/api/defaults/action.ts +++ b/src/core/mappings/action.ts @@ -1,173 +1,153 @@ /////////////////////////////////////// -//DEFAULT ACTION MODULE +//OPEN TICKET ACTION MAPPINGS /////////////////////////////////////// -import { ODValidId } from "../modules/base" -import { ODAction, ODActionManager } from "../modules/action" -import { ODWorkerManager_Default } from "./worker" +import * as api from "@open-discord-bots/framework/api" import * as discord from "discord.js" -import { ODRoleOption, ODTicketOption } from "../openticket/option" -import { ODTicket, ODTicketClearFilter } from "../openticket/ticket" -import { ODTranscriptCompiler, ODTranscriptCompilerCompileResult } from "../openticket/transcript" -import { ODMessageBuildSentResult } from "../modules/builder" -import { ODRole, ODRoleUpdateMode, ODRoleUpdateResult } from "../openticket/role" -import { ODPriorityLevel } from "../openticket/priority" +import { ODRoleOption, ODTicketOption } from "../api/option.js" +import { ODTicket, ODTicketClearFilter } from "../api/ticket.js" +import { ODTranscriptCompiler, ODTranscriptCompilerCompileResult } from "../api/transcript.js" +import { ODRole, ODRoleUpdateMode, ODRoleUpdateResult } from "../api/role.js" +import { ODPriorityLevel } from "../api/priority.js" +import { ODQuestionAnswer } from "../api/question.js" -/**## ODActionManagerIds_Default `interface` - * This interface is a list of ids available in the `ODActionManager_Default` class. +/**## ODActionManagerIdMappings `interface` + * A list of all available IDs in the default `ODActionManager` class in `opendiscord`. * It's used to generate typescript declarations for this class. */ -export interface ODActionManagerIds_Default { +export interface ODActionManagerIdMappings extends api.ODActionManagerIdConstraint { "opendiscord:create-ticket-permissions":{ - source:"panel-button"|"panel-dropdown"|"slash"|"text"|"other", + origin:"panel-button"|"panel-dropdown"|"slash"|"text"|"other", params:{guild:discord.Guild,user:discord.User,option:ODTicketOption}, result:{valid:boolean,reason:"blacklist"|"cooldown"|"global-limit"|"global-user-limit"|"option-limit"|"option-user-limit"|"custom"|null,cooldownUntil?:Date,customReason?:string}, workers:"opendiscord:check-blacklist"|"opendiscord:check-cooldown"|"opendiscord:check-global-limits"|"opendiscord:check-option-limits"|"opendiscord:valid" }, "opendiscord:create-transcript":{ - source:"slash"|"text"|"ticket-message"|"reopen-message"|"close-message"|"autoclose-message"|"autodelete"|"clear"|"other", + origin:"slash"|"text"|"ticket-message"|"reopen-message"|"close-message"|"autoclose-message"|"autodelete"|"clear"|"other", params:{guild:discord.Guild,channel:discord.GuildTextBasedChannel,user:discord.User,ticket:ODTicket}, - result:{compiler:ODTranscriptCompiler, success:boolean, result:ODTranscriptCompilerCompileResult, errorReason:string|null, pendingMessage:ODMessageBuildSentResult|null, initData:object|null, participants:{user:discord.User,role:"creator"|"participant"|"admin"}[]}, + result:{compiler:ODTranscriptCompiler, success:boolean, result:ODTranscriptCompilerCompileResult, errorReason:string|null, pendingMessage:api.ODResponderSendResult|null, initData:object|null, participants:{user:discord.User,role:"creator"|"participant"|"admin"}[]}, workers:"opendiscord:select-compiler"|"opendiscord:init-transcript"|"opendiscord:compile-transcript"|"opendiscord:ready-transcript"|"opendiscord:logs" }, "opendiscord:create-ticket":{ - source:"panel-button"|"panel-dropdown"|"slash"|"text"|"other", - params:{guild:discord.Guild,user:discord.User,option:ODTicketOption,answers:{id:string,name:string,type:"short"|"paragraph",value:string|null}[]}, + origin:"panel-button"|"panel-dropdown"|"slash"|"text"|"other", + params:{guild:discord.Guild,user:discord.User,option:ODTicketOption,answers:ODQuestionAnswer[]}, result:{channel:discord.GuildTextBasedChannel,ticket:ODTicket}, workers:"opendiscord:create-ticket"|"opendiscord:send-ticket-message"|"opendiscord:discord-logs"|"opendiscord:logs" }, "opendiscord:close-ticket":{ - source:"slash"|"text"|"ticket-message"|"reopen-message"|"autoclose"|"other", + origin:"slash"|"text"|"ticket-message"|"reopen-message"|"autoclose"|"other", params:{guild:discord.Guild,channel:discord.GuildTextBasedChannel,user:discord.User,ticket:ODTicket,reason:string|null,sendMessage:boolean,allowCategoryChange?:boolean}, result:{}, workers:"opendiscord:close-ticket"|"opendiscord:discord-logs"|"opendiscord:logs" }, "opendiscord:delete-ticket":{ - source:"slash"|"text"|"ticket-message"|"reopen-message"|"close-message"|"autoclose-message"|"autodelete"|"clear"|"other", + origin:"slash"|"text"|"ticket-message"|"reopen-message"|"close-message"|"autoclose-message"|"autodelete"|"clear"|"other", params:{guild:discord.Guild,channel:discord.GuildTextBasedChannel,user:discord.User,ticket:ODTicket,reason:string|null,sendMessage:boolean,withoutTranscript:boolean}, result:{}, workers:"opendiscord:delete-ticket"|"opendiscord:discord-logs"|"opendiscord:delete-channel"|"opendiscord:logs" }, "opendiscord:reopen-ticket":{ - source:"slash"|"text"|"ticket-message"|"close-message"|"autoclose-message"|"other", + origin:"slash"|"text"|"ticket-message"|"close-message"|"autoclose-message"|"other", params:{guild:discord.Guild,channel:discord.GuildTextBasedChannel,user:discord.User,ticket:ODTicket,reason:string|null,sendMessage:boolean,allowCategoryChange?:boolean}, result:{}, workers:"opendiscord:reopen-ticket"|"opendiscord:discord-logs"|"opendiscord:logs" }, "opendiscord:claim-ticket":{ - source:"slash"|"text"|"ticket-message"|"unclaim-message"|"other", + origin:"slash"|"text"|"ticket-message"|"unclaim-message"|"other", params:{guild:discord.Guild,channel:discord.GuildTextBasedChannel,user:discord.User,ticket:ODTicket,reason:string|null,sendMessage:boolean,allowCategoryChange?:boolean}, result:{}, workers:"opendiscord:claim-ticket"|"opendiscord:discord-logs"|"opendiscord:logs" }, "opendiscord:unclaim-ticket":{ - source:"slash"|"text"|"ticket-message"|"claim-message"|"other", + origin:"slash"|"text"|"ticket-message"|"claim-message"|"other", params:{guild:discord.Guild,channel:discord.GuildTextBasedChannel,user:discord.User,ticket:ODTicket,reason:string|null,sendMessage:boolean,allowCategoryChange?:boolean}, result:{}, workers:"opendiscord:unclaim-ticket"|"opendiscord:discord-logs"|"opendiscord:logs" }, "opendiscord:pin-ticket":{ - source:"slash"|"text"|"ticket-message"|"unpin-message"|"other", + origin:"slash"|"text"|"ticket-message"|"unpin-message"|"other", params:{guild:discord.Guild,channel:discord.GuildTextBasedChannel,user:discord.User,ticket:ODTicket,reason:string|null,sendMessage:boolean}, result:{}, workers:"opendiscord:pin-ticket"|"opendiscord:discord-logs"|"opendiscord:logs" }, "opendiscord:unpin-ticket":{ - source:"slash"|"text"|"ticket-message"|"pin-message"|"other", + origin:"slash"|"text"|"ticket-message"|"pin-message"|"other", params:{guild:discord.Guild,channel:discord.GuildTextBasedChannel,user:discord.User,ticket:ODTicket,reason:string|null,sendMessage:boolean}, result:{}, workers:"opendiscord:unpin-ticket"|"opendiscord:discord-logs"|"opendiscord:logs" }, "opendiscord:rename-ticket":{ - source:"slash"|"text"|"other", + origin:"slash"|"text"|"other", params:{guild:discord.Guild,channel:discord.GuildTextBasedChannel,user:discord.User,ticket:ODTicket,reason:string|null,sendMessage:boolean,data:string}, result:{}, workers:"opendiscord:rename-ticket"|"opendiscord:discord-logs"|"opendiscord:logs" }, "opendiscord:move-ticket":{ - source:"slash"|"text"|"other", + origin:"slash"|"text"|"other", params:{guild:discord.Guild,channel:discord.GuildTextBasedChannel,user:discord.User,ticket:ODTicket,reason:string|null,sendMessage:boolean,data:ODTicketOption}, result:{}, workers:"opendiscord:move-ticket"|"opendiscord:discord-logs"|"opendiscord:logs" }, "opendiscord:add-ticket-user":{ - source:"slash"|"text"|"other", + origin:"slash"|"text"|"other", params:{guild:discord.Guild,channel:discord.GuildTextBasedChannel,user:discord.User,ticket:ODTicket,reason:string|null,sendMessage:boolean,data:discord.User}, result:{}, workers:"opendiscord:add-ticket-user"|"opendiscord:discord-logs"|"opendiscord:logs" }, "opendiscord:remove-ticket-user":{ - source:"slash"|"text"|"other", + origin:"slash"|"text"|"other", params:{guild:discord.Guild,channel:discord.GuildTextBasedChannel,user:discord.User,ticket:ODTicket,reason:string|null,sendMessage:boolean,data:discord.User}, result:{}, workers:"opendiscord:remove-ticket-user"|"opendiscord:discord-logs"|"opendiscord:logs" }, "opendiscord:reaction-role":{ - source:"panel-button"|"other", + origin:"panel-button"|"other", params:{guild:discord.Guild,user:discord.User,option:ODRoleOption,overwriteMode:ODRoleUpdateMode|null}, result:{result:ODRoleUpdateResult[],role:ODRole}, workers:"opendiscord:reaction-role"|"opendiscord:logs" }, "opendiscord:clear-tickets":{ - source:"slash"|"text"|"other", + origin:"slash"|"text"|"other", params:{guild:discord.Guild,channel:discord.GuildTextBasedChannel,user:discord.User,filter:ODTicketClearFilter,list:ODTicket[]}, result:{list:string[]}, workers:"opendiscord:clear-tickets"|"opendiscord:discord-logs"|"opendiscord:logs" }, "opendiscord:update-ticket-topic":{ - source:"slash"|"text"|"ticket-action"|"other", + origin:"slash"|"text"|"ticket-action"|"other", params:{guild:discord.Guild,channel:discord.GuildTextBasedChannel,user:discord.User,ticket:ODTicket,newTopic:string|null,sendMessage:boolean}, result:{}, workers:"opendiscord:update-ticket-topic"|"opendiscord:discord-logs"|"opendiscord:logs" }, "opendiscord:update-ticket-priority":{ - source:"slash"|"text"|"other", + origin:"slash"|"text"|"other", params:{guild:discord.Guild,channel:discord.GuildTextBasedChannel,user:discord.User,ticket:ODTicket,newPriority:ODPriorityLevel,reason:string|null,sendMessage:boolean}, result:{}, workers:"opendiscord:update-ticket-priority"|"opendiscord:discord-logs"|"opendiscord:logs" }, "opendiscord:transfer-ticket":{ - source:"slash"|"text"|"other", + origin:"slash"|"text"|"other", params:{guild:discord.Guild,channel:discord.GuildTextBasedChannel,user:discord.User,ticket:ODTicket,newCreator:discord.User,reason:string|null,sendMessage:boolean}, result:{}, workers:"opendiscord:transfer-ticket"|"opendiscord:discord-logs"|"opendiscord:logs" }, + "opendiscord:calculate-ticket-category":{ + origin:"create-ticket"|"close-ticket"|"reopen-ticket"|"claim-ticket"|"unclaim-ticket"|"move-ticket"|"other", + params:{guild:discord.Guild,channel:discord.GuildTextBasedChannel|null,user:discord.User,option:ODTicketOption,ticket:ODTicket|null,currentCategoryId:string|null}, + result:{newCategoryId:string|null,newCategoryMode:string|null,newCategory:discord.CategoryChannel|null,shouldChangeCategory:boolean}, + workers:"opendiscord:default-category"|"opendiscord:close-category"|"opendiscord:claim-category"|"opendiscord:backup-category" + }, + "opendiscord:calculate-ticket-name":{ + origin:"create-ticket"|"close-ticket"|"reopen-ticket"|"claim-ticket"|"unclaim-ticket"|"move-ticket"|"pin-ticket"|"unpin-ticket"|"rename-ticket"|"transfer-ticket"|"priority-change"|"other", + params:{guild:discord.Guild,channel:discord.GuildTextBasedChannel|null,user:discord.User,option:ODTicketOption,ticket:ODTicket|null,currentChannelName:string|null}, + result:{newChannelName:string,newChannelSuffix:string,shouldChangeName:boolean}, + workers:"opendiscord:calculate-ticket-name" + }, } -/**## ODActionManager_Default `default_class` - * This is a special class that adds type definitions & typescript to the ODActionManager class. - * It doesn't add any extra features! - * - * This default class is made for the global variable `opendiscord.actions`! +///////////////////////////// +////// MAPPED MANAGERS ////// +///////////////////////////// + +/**## ODMappedActionManager `class + * A special class with types for the Open Ticket `ODActionManager` class. */ -export class ODActionManager_Default extends ODActionManager { - get(id:ActionId): ODAction_Default - get(id:ODValidId): ODAction|null - - get(id:ODValidId): ODAction|null { - return super.get(id) - } - - remove(id:ActionId): ODAction_Default - remove(id:ODValidId): ODAction|null - - remove(id:ODValidId): ODAction|null { - return super.remove(id) - } - - exists(id:keyof ODActionManagerIds_Default): boolean - exists(id:ODValidId): boolean - - exists(id:ODValidId): boolean { - return super.exists(id) - } -} - -/**## ODAction_Default `default_class` - * This is a special class that adds type definitions & typescript to the ODAction class. - * It doesn't add any extra features! - * - * This default class is made for the default `ODAction`'s! - */ -export class ODAction_Default extends ODAction { - declare workers: ODWorkerManager_Default -} \ No newline at end of file +export class ODMappedActionManager extends api.ODActionManager {} \ No newline at end of file diff --git a/src/core/mappings/base.ts b/src/core/mappings/base.ts new file mode 100644 index 0000000..73eb270 --- /dev/null +++ b/src/core/mappings/base.ts @@ -0,0 +1,25 @@ +/////////////////////////////////////// +//BASE MAPPINGS +/////////////////////////////////////// +import * as api from "@open-discord-bots/framework/api" + +/**## ODVersionManagerIdMappings `interface` + * A list of all available IDs in the default `ODVersionManager` class in `opendiscord`. + * It's used to generate typescript declarations for this class. + */ +export interface ODVersionManagerIdMappings extends api.ODVersionManagerIdConstraint { + "opendiscord:version":api.ODVersion, + "opendiscord:last-version":api.ODVersion, + "opendiscord:api":api.ODVersion, + "opendiscord:transcripts":api.ODVersion, + "opendiscord:livestatus":api.ODVersion +} + +///////////////////////////// +////// MAPPED MANAGERS ////// +///////////////////////////// + +/**## ODMappedVersionManager `class + * A special class with types for the Open Ticket `ODVersionManager` class. + */ +export class ODMappedVersionManager extends api.ODVersionManager {} \ No newline at end of file diff --git a/src/core/mappings/builder.ts b/src/core/mappings/builder.ts new file mode 100644 index 0000000..bcedef1 --- /dev/null +++ b/src/core/mappings/builder.ts @@ -0,0 +1,282 @@ +/////////////////////////////////////// +//OPEN TICKET BUILDER MAPPINGS +/////////////////////////////////////// +import * as api from "@open-discord-bots/framework/api" +import { ODPermissionEmbedType } from "./permission.js" +import { ODTranscriptCompiler, ODTranscriptCompilerCompileResult, ODTranscriptHistoryData } from "../api/transcript.js" +import { ODOption, ODRoleOption, ODSubPanelOption, ODTicketOption, ODWebsiteOption } from "../api/option.js" +import { ODTicket, ODTicketClearFilter } from "../api/ticket.js" +import { ODRole, ODRoleUpdateResult } from "../api/role.js" +import { ODPriorityLevel } from "../api/priority.js" +import { ODPanel } from "../api/panel.js" +import * as discord from "discord.js" + +/**## ODButtonManagerIdMappings `interface` + * A list of all available IDs in the default `ODButtonManager` class in `opendiscord`. + * It's used to generate typescript declarations for this class. + */ +export interface ODButtonManagerIdMappings extends api.ODButtonManagerIdConstraint { + "opendiscord:verifybar-button":{origin:"verifybar"|"other",params:{guild:discord.Guild|null,channel:discord.TextBasedChannel,user:discord.User,verifybar:api.ODVerifyBar,verifyButtonId:string,defaultButtonType:"✅"|"❌",useDefaultLabels:boolean,customLabel?:string,customColor?:api.ODValidButtonColor,customEmoji?:string},workers:"opendiscord:verifybar-button"}, + + "opendiscord:error-ticket-deprecated-transcript":{origin:"slash"|"text"|"button"|"dropdown"|"modal"|"other",params:{},workers:"opendiscord:error-ticket-deprecated-transcript"}, + + "opendiscord:help-menu-previous":{origin:"text"|"slash"|"button"|"other",params:{mode:"slash"|"text",page:number},workers:"opendiscord:help-menu-previous"}, + "opendiscord:help-menu-next":{origin:"text"|"slash"|"button"|"other",params:{mode:"slash"|"text",page:number},workers:"opendiscord:help-menu-next"}, + "opendiscord:help-menu-page":{origin:"text"|"slash"|"button"|"other",params:{mode:"slash"|"text",page:number},workers:"opendiscord:help-menu-page"} + "opendiscord:help-menu-switch":{origin:"text"|"slash"|"button"|"other",params:{mode:"slash"|"text",page:number},workers:"opendiscord:help-menu-switch"}, + + "opendiscord:ticket-option":{origin:"slash"|"text"|"sub-panel"|"auto-update"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,panel:ODPanel,option:ODTicketOption},workers:"opendiscord:ticket-option"}, + "opendiscord:website-option":{origin:"slash"|"text"|"sub-panel"|"auto-update"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,panel:ODPanel,option:ODWebsiteOption},workers:"opendiscord:website-option"}, + "opendiscord:role-option":{origin:"slash"|"text"|"sub-panel"|"auto-update"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,panel:ODPanel,option:ODRoleOption},workers:"opendiscord:role-option"} + "opendiscord:subpanel-option":{origin:"slash"|"text"|"sub-panel"|"auto-update"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,panel:ODPanel,option:ODSubPanelOption},workers:"opendiscord:subpanel-option"} + + "opendiscord:visit-ticket":{origin:"ticket-created"|"dm"|"logs"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,ticket:ODTicket},workers:"opendiscord:visit-ticket"}, + + "opendiscord:close-ticket":{origin:"ticket-message"|"reopen-message"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,ticket:ODTicket},workers:"opendiscord:close-ticket"}, + "opendiscord:delete-ticket":{origin:"ticket-message"|"close-message"|"autoclose-message"|"reopen-message"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,ticket:ODTicket},workers:"opendiscord:delete-ticket"}, + "opendiscord:reopen-ticket":{origin:"ticket-message"|"close-message"|"autoclose-message"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,ticket:ODTicket},workers:"opendiscord:reopen-ticket"}, + "opendiscord:claim-ticket":{origin:"ticket-message"|"unclaim-message"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,ticket:ODTicket},workers:"opendiscord:claim-ticket"}, + "opendiscord:unclaim-ticket":{origin:"ticket-message"|"claim-message"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,ticket:ODTicket},workers:"opendiscord:unclaim-ticket"}, + "opendiscord:pin-ticket":{origin:"ticket-message"|"unpin-message"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,ticket:ODTicket},workers:"opendiscord:pin-ticket"}, + "opendiscord:unpin-ticket":{origin:"ticket-message"|"pin-message"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,ticket:ODTicket},workers:"opendiscord:unpin-ticket"}, + + "opendiscord:transcript-html-visit":{origin:"channel"|"creator-dm"|"participant-dm"|"active-admin-dm"|"every-admin-dm"|"other",params:{guild:discord.Guild,channel:discord.GuildTextBasedChannel,user:discord.User,ticket:ODTicket,compiler:ODTranscriptCompiler<{url:string,availableUntil:Date},{auth:string}>,result:ODTranscriptCompilerCompileResult<{url:string,availableUntil:Date}>},workers:"opendiscord:transcript-html-visit"}, + "opendiscord:transcript-error-retry":{origin:"slash"|"text"|"ticket-message"|"reopen-message"|"close-message"|"autoclose-message"|"autodelete"|"clear"|"other",params:{guild:discord.Guild,channel:discord.GuildTextBasedChannel,user:discord.User,ticket:ODTicket,compiler:ODTranscriptCompiler,reason:string|null},workers:"opendiscord:transcript-error-retry"}, + "opendiscord:transcript-error-continue":{origin:"slash"|"text"|"ticket-message"|"reopen-message"|"close-message"|"autoclose-message"|"autodelete"|"clear"|"other",params:{guild:discord.Guild,channel:discord.GuildTextBasedChannel,user:discord.User,ticket:ODTicket,compiler:ODTranscriptCompiler,reason:string|null},workers:"opendiscord:transcript-error-continue"}, + + "opendiscord:clear-continue":{origin:"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.GuildTextBasedChannel,user:discord.User,filter:ODTicketClearFilter,list:string[],inProgress:boolean},workers:"opendiscord:clear-continue"}, +} + +/**## ODDropdownManagerIdMappings `interface` + * A list of all available IDs in the default `ODDropdownManager` class in `opendiscord`. + * It's used to generate typescript declarations for this class. + */ +export interface ODDropdownManagerIdMappings extends api.ODDropdownManagerIdConstraint { + "opendiscord:panel-dropdown":{origin:"slash"|"text"|"sub-panel"|"auto-update"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,panel:ODPanel,options:(ODTicketOption|ODRoleOption|ODSubPanelOption)[]},workers:"opendiscord:panel-dropdown"} + "opendiscord:priority-dropdown":{origin:"panel-button"|"panel-dropdown"|"slash"|"text"|"verifybar"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,ticket:ODTicket},workers:"opendiscord:priority-dropdown"} +} + +/**## ODFileManagerIdMappings `interface` + * A list of all available IDs in the default `ODFileManager` class in `opendiscord`. + * It's used to generate typescript declarations for this class. + */ +export interface ODFileManagerIdMappings extends api.ODFileManagerIdConstraint { + "opendiscord:text-transcript":{origin:"channel"|"creator-dm"|"participant-dm"|"active-admin-dm"|"every-admin-dm"|"other",params:{guild:discord.Guild,channel:discord.GuildTextBasedChannel,user:discord.User,ticket:ODTicket,compiler:ODTranscriptCompiler,result:ODTranscriptCompilerCompileResult},workers:"opendiscord:text-transcript"} +} + +/**## ODEmbedManagerIdMappings `interface` + * A list of all available IDs in the default `ODEmbedManager` class in `opendiscord`. + * It's used to generate typescript declarations for this class. + */ +export interface ODEmbedManagerIdMappings extends api.ODEmbedManagerIdConstraint { + "opendiscord:error":{origin:"slash"|"text"|"button"|"dropdown"|"modal"|"other",params:{guild:discord.Guild|null,channel:discord.TextBasedChannel,user:discord.User,error:string,layout:"simple"|"advanced",customTitle?:string},workers:"opendiscord:error"}, + "opendiscord:error-option-missing":{origin:"slash"|"text"|"other",params:{guild:discord.Guild|null,channel:discord.TextBasedChannel,user:discord.User,error:api.ODTextCommandErrorMissingOption},workers:"opendiscord:error-option-missing"}, + "opendiscord:error-option-invalid":{origin:"slash"|"text"|"other",params:{guild:discord.Guild|null,channel:discord.TextBasedChannel,user:discord.User,error:api.ODTextCommandErrorInvalidOption},workers:"opendiscord:error-option-invalid"}, + "opendiscord:error-unknown-command":{origin:"slash"|"text"|"other",params:{guild:discord.Guild|null,channel:discord.TextBasedChannel,user:discord.User,error:api.ODTextCommandErrorUnknownCommand},workers:"opendiscord:error-unknown-command"}, + "opendiscord:error-no-permissions":{origin:"slash"|"text"|"button"|"dropdown"|"modal"|"other",params:{guild:discord.Guild|null,channel:discord.TextBasedChannel,user:discord.User,permissions:ODPermissionEmbedType[]},workers:"opendiscord:error-no-permissions"}, + "opendiscord:error-no-permissions-cooldown":{origin:"slash"|"text"|"button"|"dropdown"|"modal"|"other",params:{guild:discord.Guild|null,channel:discord.TextBasedChannel,user:discord.User,until?:Date},workers:"opendiscord:error-no-permissions-cooldown"}, + "opendiscord:error-no-permissions-blacklisted":{origin:"slash"|"text"|"button"|"dropdown"|"modal"|"other",params:{guild:discord.Guild|null,channel:discord.TextBasedChannel,user:discord.User},workers:"opendiscord:error-no-permissions-blacklisted"}, + "opendiscord:error-no-permissions-limits":{origin:"slash"|"text"|"button"|"dropdown"|"modal"|"other",params:{guild:discord.Guild|null,channel:discord.TextBasedChannel,user:discord.User,limit:"global"|"global-user"|"option"|"option-user"},workers:"opendiscord:error-no-permissions-limits"}, + "opendiscord:error-responder-timeout":{origin:"slash"|"text"|"button"|"dropdown"|"modal"|"other",params:{guild:discord.Guild|null,channel:discord.TextBasedChannel,user:discord.User},workers:"opendiscord:error-responder-timeout"}, + "opendiscord:error-ticket-unknown":{origin:"slash"|"text"|"button"|"dropdown"|"modal"|"other",params:{guild:discord.Guild|null,channel:discord.TextBasedChannel,user:discord.User},workers:"opendiscord:error-ticket-unknown"}, + "opendiscord:error-ticket-deprecated":{origin:"slash"|"text"|"button"|"dropdown"|"modal"|"other",params:{guild:discord.Guild|null,channel:discord.TextBasedChannel,user:discord.User},workers:"opendiscord:error-ticket-deprecated"}, + "opendiscord:error-option-unknown":{origin:"slash"|"text"|"button"|"dropdown"|"modal"|"other",params:{guild:discord.Guild|null,channel:discord.TextBasedChannel,user:discord.User},workers:"opendiscord:error-option-unknown"}, + "opendiscord:error-panel-unknown":{origin:"slash"|"text"|"button"|"dropdown"|"modal"|"other",params:{guild:discord.Guild|null,channel:discord.TextBasedChannel,user:discord.User},workers:"opendiscord:error-panel-unknown"}, + "opendiscord:error-not-in-guild":{origin:"slash"|"text"|"button"|"dropdown"|"modal"|"other",params:{channel:discord.TextBasedChannel,user:discord.User},workers:"opendiscord:error-not-in-guild"}, + "opendiscord:error-channel-rename":{origin:"slash"|"text"|"button"|"dropdown"|"modal"|"ticket-pin"|"ticket-unpin"|"ticket-close"|"ticket-reopen"|"ticket-rename"|"ticket-move"|"ticket-priority"|"ticket-transfer"|"other",params:{guild:discord.Guild|null,channel:discord.TextBasedChannel,user:discord.User,originalName:string,newName:string},workers:"opendiscord:error-channel-rename"}, + "opendiscord:error-channel-category":{origin:"slash"|"text"|"button"|"dropdown"|"modal"|"ticket-create"|"ticket-close"|"ticket-reopen"|"ticket-claim"|"ticket-unclaim"|"ticket-move"|"other",params:{guild:discord.Guild|null,channel:discord.TextBasedChannel,user:discord.User,originalCategory:string,newCategory:string},workers:"opendiscord:error-channel-category"}, + "opendiscord:error-ticket-busy":{origin:"slash"|"text"|"button"|"dropdown"|"modal"|"other",params:{guild:discord.Guild|null,channel:discord.TextBasedChannel,user:discord.User},workers:"opendiscord:error-ticket-busy"}, + + "opendiscord:help-menu":{origin:"text"|"slash"|"button"|"other",params:{mode:"slash"|"text",page:number},workers:"opendiscord:help-menu"}, + + "opendiscord:stats-global":{origin:"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User},workers:"opendiscord:stats-global"}, + "opendiscord:stats-ticket":{origin:"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,scopeData:ODTicket},workers:"opendiscord:stats-ticket"}, + "opendiscord:stats-user":{origin:"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,scopeData:discord.User},workers:"opendiscord:stats-user"|"opendiscord:easter-egg"}, + "opendiscord:stats-reset":{origin:"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,reason:string|null},workers:"opendiscord:stats-reset"}, + "opendiscord:stats-ticket-unknown":{origin:"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,id:string},workers:"opendiscord:stats-ticket-unknown"}, + + "opendiscord:panel":{origin:"slash"|"text"|"sub-panel"|"auto-update"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,panel:ODPanel,isSubPanel:boolean},workers:"opendiscord:panel"}, + "opendiscord:ticket-created":{origin:"panel-button"|"panel-dropdown"|"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,ticket:ODTicket},workers:"opendiscord:ticket-created"}, + "opendiscord:ticket-created-dm":{origin:"panel-button"|"panel-dropdown"|"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,ticket:ODTicket},workers:"opendiscord:ticket-created-dm"}, + "opendiscord:ticket-created-logs":{origin:"panel-button"|"panel-dropdown"|"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,ticket:ODTicket},workers:"opendiscord:ticket-created-logs"}, + "opendiscord:ticket-message":{origin:"panel-button"|"panel-dropdown"|"slash"|"text"|"verifybar"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,ticket:ODTicket},workers:"opendiscord:ticket-message"}, + "opendiscord:close-message":{origin:"slash"|"text"|"ticket-message"|"reopen-message"|"autoclose"|"verifybar"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,ticket:ODTicket,reason:string|null},workers:"opendiscord:close-message"}, + "opendiscord:reopen-message":{origin:"slash"|"text"|"ticket-message"|"close-message"|"autoclose-message"|"verifybar"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,ticket:ODTicket,reason:string|null},workers:"opendiscord:reopen-message"}, + "opendiscord:delete-message":{origin:"slash"|"text"|"ticket-message"|"reopen-message"|"close-message"|"autoclose-message"|"autodelete"|"clear"|"verifybar"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,ticket:ODTicket,reason:string|null},workers:"opendiscord:delete-message"}, + "opendiscord:claim-message":{origin:"slash"|"text"|"ticket-message"|"unclaim-message"|"verifybar"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,ticket:ODTicket,reason:string|null},workers:"opendiscord:claim-message"}, + "opendiscord:unclaim-message":{origin:"slash"|"text"|"ticket-message"|"claim-message"|"verifybar"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,ticket:ODTicket,reason:string|null},workers:"opendiscord:unclaim-message"}, + "opendiscord:pin-message":{origin:"slash"|"text"|"ticket-message"|"unpin-message"|"verifybar"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,ticket:ODTicket,reason:string|null},workers:"opendiscord:pin-message"}, + "opendiscord:unpin-message":{origin:"slash"|"text"|"ticket-message"|"pin-message"|"verifybar"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,ticket:ODTicket,reason:string|null},workers:"opendiscord:unpin-message"}, + "opendiscord:rename-message":{origin:"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,ticket:ODTicket,reason:string|null,data:string},workers:"opendiscord:rename-message"}, + "opendiscord:move-message":{origin:"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,ticket:ODTicket,reason:string|null,data:ODTicketOption},workers:"opendiscord:move-message"}, + "opendiscord:add-message":{origin:"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,ticket:ODTicket,reason:string|null,data:discord.User},workers:"opendiscord:add-message"}, + "opendiscord:remove-message":{origin:"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,ticket:ODTicket,reason:string|null,data:discord.User},workers:"opendiscord:remove-message"}, + "opendiscord:ticket-action-dm":{origin:"slash"|"text"|"ticket-message"|"close-message"|"reopen-message"|"delete-message"|"claim-message"|"unclaim-message"|"pin-message"|"unpin-message"|"autoclose-message"|"priority-message"|"topic-message"|"transfer-message"|"autoclose"|"autodelete"|"clear"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,mode:"close"|"reopen"|"delete"|"claim"|"unclaim"|"pin"|"unpin"|"rename"|"move"|"add"|"remove"|"priority"|"transfer"|"topic",ticket:ODTicket,reason:string|null,additionalData:null|string|discord.User|ODTicketOption,additionalData2?:discord.User},workers:"opendiscord:ticket-action-dm"}, + "opendiscord:ticket-action-logs":{origin:"slash"|"text"|"ticket-message"|"close-message"|"reopen-message"|"delete-message"|"claim-message"|"unclaim-message"|"pin-message"|"unpin-message"|"autoclose-message"|"priority-message"|"topic-message"|"transfer-message"|"autoclose"|"autodelete"|"clear"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,mode:"close"|"reopen"|"delete"|"claim"|"unclaim"|"pin"|"unpin"|"rename"|"move"|"add"|"remove"|"priority"|"transfer"|"topic",ticket:ODTicket,reason:string|null,additionalData:null|string|discord.User|ODTicketOption,additionalData2?:discord.User},workers:"opendiscord:ticket-action-logs"}, + + "opendiscord:blacklist-view":{origin:"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User},workers:"opendiscord:blacklist-view"}, + "opendiscord:blacklist-get":{origin:"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,data:discord.User},workers:"opendiscord:blacklist-get"}, + "opendiscord:blacklist-add":{origin:"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,data:discord.User,reason:string|null},workers:"opendiscord:blacklist-add"}, + "opendiscord:blacklist-remove":{origin:"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,data:discord.User,reason:string|null},workers:"opendiscord:blacklist-remove"} + "opendiscord:blacklist-dm":{origin:"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,mode:"add"|"remove",data:discord.User,reason:string|null},workers:"opendiscord:blacklist-dm"}, + "opendiscord:blacklist-logs":{origin:"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,mode:"add"|"remove",data:discord.User,reason:string|null},workers:"opendiscord:blacklist-logs"}, + + "opendiscord:transcript-text-ready":{origin:"channel"|"creator-dm"|"participant-dm"|"active-admin-dm"|"every-admin-dm"|"other",params:{guild:discord.Guild,channel:discord.GuildTextBasedChannel,user:discord.User,ticket:ODTicket,compiler:ODTranscriptCompiler<{contents:string},null>,result:ODTranscriptCompilerCompileResult<{contents:string}>},workers:"opendiscord:transcript-text-ready"}, + "opendiscord:transcript-html-ready":{origin:"channel"|"creator-dm"|"participant-dm"|"active-admin-dm"|"every-admin-dm"|"other",params:{guild:discord.Guild,channel:discord.GuildTextBasedChannel,user:discord.User,ticket:ODTicket,compiler:ODTranscriptCompiler<{url:string,availableUntil:Date},{auth:string}>,result:ODTranscriptCompilerCompileResult<{url:string,availableUntil:Date}>},workers:"opendiscord:transcript-html-ready"}, + "opendiscord:transcript-html-progress":{origin:"channel"|"other",params:{guild:discord.Guild,channel:discord.GuildTextBasedChannel,user:discord.User,ticket:ODTicket,compiler:ODTranscriptCompiler<{url:string,availableUntil:Date},{auth:string}>,remaining:number},workers:"opendiscord:transcript-html-progress"}, + "opendiscord:transcript-error":{origin:"slash"|"text"|"ticket-message"|"reopen-message"|"close-message"|"autoclose-message"|"autodelete"|"clear"|"other",params:{guild:discord.Guild,channel:discord.GuildTextBasedChannel,user:discord.User,ticket:ODTicket,compiler:ODTranscriptCompiler,reason:string|null},workers:"opendiscord:transcript-error"}, + "opendiscord:transcript-history":{origin:"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.GuildTextBasedChannel,user:discord.User,transcriptUser:discord.User,transcriptList:ODTranscriptHistoryData[]},workers:"opendiscord:transcript-history"}, + + "opendiscord:reaction-role":{origin:"panel-button"|"other",params:{guild:discord.Guild,user:discord.User,role:ODRole,result:ODRoleUpdateResult[]},workers:"opendiscord:reaction-role"}, + "opendiscord:reaction-role-dm":{origin:"panel-button"|"other",params:{guild:discord.Guild,user:discord.User,role:ODRole,result:ODRoleUpdateResult[]},workers:"opendiscord:reaction-role-dm"}, + "opendiscord:reaction-role-logs":{origin:"panel-button"|"other",params:{guild:discord.Guild,user:discord.User,role:ODRole,result:ODRoleUpdateResult[]},workers:"opendiscord:reaction-role-logs"}, + + "opendiscord:clear-verify-message":{origin:"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.GuildTextBasedChannel,user:discord.User,filter:ODTicketClearFilter,list:string[],inProgress:boolean},workers:"opendiscord:clear-verify-message"}, + "opendiscord:clear-message":{origin:"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.GuildTextBasedChannel,user:discord.User,filter:ODTicketClearFilter,list:string[]},workers:"opendiscord:clear-message"}, + "opendiscord:clear-logs":{origin:"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.GuildTextBasedChannel,user:discord.User,filter:ODTicketClearFilter,list:string[]},workers:"opendiscord:clear-logs"}, + + "opendiscord:autoclose-message":{origin:"timeout"|"leave"|"verifybar"|"other",params:{guild:discord.Guild,channel:discord.GuildTextBasedChannel,user:discord.User,ticket:ODTicket},workers:"opendiscord:autoclose-message"}, + "opendiscord:autodelete-message":{origin:"timeout"|"leave"|"other",params:{guild:discord.Guild,channel:discord.GuildTextBasedChannel,user:discord.User,ticket:ODTicket},workers:"opendiscord:autodelete-message"}, + "opendiscord:autoclose-enable":{origin:"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.GuildTextBasedChannel,user:discord.User,ticket:ODTicket,time:number,reason:string|null},workers:"opendiscord:autoclose-enable"}, + "opendiscord:autodelete-enable":{origin:"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.GuildTextBasedChannel,user:discord.User,ticket:ODTicket,time:number,reason:string|null},workers:"opendiscord:autodelete-enable"}, + "opendiscord:autoclose-disable":{origin:"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.GuildTextBasedChannel,user:discord.User,ticket:ODTicket,reason:string|null},workers:"opendiscord:autoclose-disable"}, + "opendiscord:autodelete-disable":{origin:"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.GuildTextBasedChannel,user:discord.User,ticket:ODTicket,reason:string|null},workers:"opendiscord:autodelete-disable"}, + + "opendiscord:topic-set":{origin:"slash"|"text"|"ticket-action"|"other",params:{guild:discord.Guild,channel:discord.GuildTextBasedChannel,user:discord.User,ticket:ODTicket,topic:string},workers:"opendiscord:topic-set"}, + "opendiscord:priority-set":{origin:"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.GuildTextBasedChannel,user:discord.User,ticket:ODTicket,priority:ODPriorityLevel,reason:string|null},workers:"opendiscord:priority-set"}, + "opendiscord:priority-get":{origin:"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.GuildTextBasedChannel,user:discord.User,ticket:ODTicket,priority:ODPriorityLevel},workers:"opendiscord:priority-get"}, + "opendiscord:transfer-message":{origin:"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.GuildTextBasedChannel,user:discord.User,ticket:ODTicket,oldCreator:discord.User,newCreator:discord.User,reason:string|null},workers:"opendiscord:transfer-message"}, +} + +/**## ODMessageManagerIdMappings `interface` + * A list of all available IDs in the default `ODMessageManager` class in `opendiscord`. + * It's used to generate typescript declarations for this class. + */ +export interface ODMessageManagerIdMappings extends api.ODMessageManagerIdConstraint { + "opendiscord:error":{origin:"slash"|"text"|"button"|"dropdown"|"modal"|"other",params:{guild:discord.Guild|null,channel:discord.TextBasedChannel,user:discord.User,error:string,layout:"simple"|"advanced",customTitle?:string},workers:"opendiscord:error"}, + "opendiscord:error-option-missing":{origin:"slash"|"text"|"other",params:{guild:discord.Guild|null,channel:discord.TextBasedChannel,user:discord.User,error:api.ODTextCommandErrorMissingOption},workers:"opendiscord:error-option-missing"}, + "opendiscord:error-option-invalid":{origin:"slash"|"text"|"other",params:{guild:discord.Guild|null,channel:discord.TextBasedChannel,user:discord.User,error:api.ODTextCommandErrorInvalidOption},workers:"opendiscord:error-option-invalid"}, + "opendiscord:error-unknown-command":{origin:"slash"|"text"|"other",params:{guild:discord.Guild|null,channel:discord.TextBasedChannel,user:discord.User,error:api.ODTextCommandErrorUnknownCommand},workers:"opendiscord:error-unknown-command"}, + "opendiscord:error-no-permissions":{origin:"slash"|"text"|"button"|"dropdown"|"modal"|"other",params:{guild:discord.Guild|null,channel:discord.TextBasedChannel,user:discord.User,permissions:ODPermissionEmbedType[]},workers:"opendiscord:error-no-permissions"}, + "opendiscord:error-no-permissions-cooldown":{origin:"slash"|"text"|"button"|"dropdown"|"modal"|"other",params:{guild:discord.Guild|null,channel:discord.TextBasedChannel,user:discord.User,until?:Date},workers:"opendiscord:error-no-permissions-cooldown"}, + "opendiscord:error-no-permissions-blacklisted":{origin:"slash"|"text"|"button"|"dropdown"|"modal"|"other",params:{guild:discord.Guild|null,channel:discord.TextBasedChannel,user:discord.User},workers:"opendiscord:error-no-permissions-blacklisted"}, + "opendiscord:error-no-permissions-limits":{origin:"slash"|"text"|"button"|"dropdown"|"modal"|"other",params:{guild:discord.Guild|null,channel:discord.TextBasedChannel,user:discord.User,limit:"global"|"global-user"|"option"|"option-user"},workers:"opendiscord:error-no-permissions-limits"}, + "opendiscord:error-responder-timeout":{origin:"slash"|"text"|"button"|"dropdown"|"modal"|"other",params:{guild:discord.Guild|null,channel:discord.TextBasedChannel,user:discord.User},workers:"opendiscord:error-responder-timeout"}, + "opendiscord:error-ticket-unknown":{origin:"slash"|"text"|"button"|"dropdown"|"modal"|"other",params:{guild:discord.Guild|null,channel:discord.TextBasedChannel,user:discord.User},workers:"opendiscord:error-ticket-unknown"}, + "opendiscord:error-ticket-deprecated":{origin:"slash"|"text"|"button"|"dropdown"|"modal"|"other",params:{guild:discord.Guild|null,channel:discord.TextBasedChannel,user:discord.User},workers:"opendiscord:error-ticket-deprecated"}, + "opendiscord:error-option-unknown":{origin:"slash"|"text"|"button"|"dropdown"|"modal"|"other",params:{guild:discord.Guild|null,channel:discord.TextBasedChannel,user:discord.User},workers:"opendiscord:error-option-unknown"}, + "opendiscord:error-panel-unknown":{origin:"slash"|"text"|"button"|"dropdown"|"modal"|"other",params:{guild:discord.Guild|null,channel:discord.TextBasedChannel,user:discord.User},workers:"opendiscord:error-panel-unknown"}, + "opendiscord:error-not-in-guild":{origin:"slash"|"text"|"button"|"dropdown"|"modal"|"other",params:{channel:discord.TextBasedChannel,user:discord.User},workers:"opendiscord:error-not-in-guild"}, + "opendiscord:error-channel-rename":{origin:"slash"|"text"|"button"|"dropdown"|"modal"|"ticket-pin"|"ticket-unpin"|"ticket-close"|"ticket-reopen"|"ticket-rename"|"ticket-move"|"ticket-priority"|"ticket-transfer"|"other",params:{guild:discord.Guild|null,channel:discord.TextBasedChannel,user:discord.User,originalName:string,newName:string},workers:"opendiscord:error-channel-rename"}, + "opendiscord:error-channel-category":{origin:"slash"|"text"|"button"|"dropdown"|"modal"|"ticket-create"|"ticket-close"|"ticket-reopen"|"ticket-claim"|"ticket-unclaim"|"ticket-move"|"other",params:{guild:discord.Guild|null,channel:discord.TextBasedChannel,user:discord.User,originalCategory:string,newCategory:string},workers:"opendiscord:error-channel-category"}, + "opendiscord:error-ticket-busy":{origin:"slash"|"text"|"button"|"dropdown"|"modal"|"other",params:{guild:discord.Guild|null,channel:discord.TextBasedChannel,user:discord.User},workers:"opendiscord:error-ticket-busy"}, + + "opendiscord:help-menu":{origin:"slash"|"text"|"button"|"other",params:{mode:"slash"|"text",page:number},workers:"opendiscord:help-menu"}, + + "opendiscord:stats-global":{origin:"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User},workers:"opendiscord:stats-global"}, + "opendiscord:stats-ticket":{origin:"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,scopeData:ODTicket},workers:"opendiscord:stats-ticket"}, + "opendiscord:stats-user":{origin:"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,scopeData:discord.User},workers:"opendiscord:stats-user"|"opendiscord:easter-egg"}, + "opendiscord:stats-reset":{origin:"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,reason:string|null},workers:"opendiscord:stats-reset"}, + "opendiscord:stats-ticket-unknown":{origin:"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,id:string},workers:"opendiscord:stats-ticket-unknown"}, + + "opendiscord:panel":{origin:"slash"|"text"|"sub-panel"|"auto-update"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,panel:ODPanel,isSubPanel:boolean},workers:"opendiscord:panel-layout"|"opendiscord:panel-components"}, + "opendiscord:panel-ready":{origin:"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,panel:ODPanel},workers:"opendiscord:panel-ready"}, + + "opendiscord:ticket-created":{origin:"panel-button"|"panel-dropdown"|"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,ticket:ODTicket},workers:"opendiscord:ticket-created"}, + "opendiscord:ticket-created-dm":{origin:"panel-button"|"panel-dropdown"|"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,ticket:ODTicket},workers:"opendiscord:ticket-created-dm"}, + "opendiscord:ticket-created-logs":{origin:"panel-button"|"panel-dropdown"|"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,ticket:ODTicket},workers:"opendiscord:ticket-created-logs"}, + "opendiscord:ticket-message":{origin:"panel-button"|"panel-dropdown"|"slash"|"text"|"verifybar"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,ticket:ODTicket},workers:"opendiscord:ticket-message-layout"|"opendiscord:ticket-message-components"|"opendiscord:ticket-message-disable-components"}, + "opendiscord:close-message":{origin:"slash"|"text"|"ticket-message"|"reopen-message"|"autoclose"|"verifybar"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,ticket:ODTicket,reason:string|null},workers:"opendiscord:close-message"}, + "opendiscord:reopen-message":{origin:"slash"|"text"|"ticket-message"|"close-message"|"autoclose-message"|"verifybar"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,ticket:ODTicket,reason:string|null},workers:"opendiscord:reopen-message"}, + "opendiscord:delete-message":{origin:"slash"|"text"|"ticket-message"|"reopen-message"|"close-message"|"autoclose-message"|"autodelete"|"clear"|"verifybar"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,ticket:ODTicket,reason:string|null},workers:"opendiscord:delete-message"}, + "opendiscord:claim-message":{origin:"slash"|"text"|"ticket-message"|"unclaim-message"|"verifybar"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,ticket:ODTicket,reason:string|null},workers:"opendiscord:claim-message"}, + "opendiscord:unclaim-message":{origin:"slash"|"text"|"ticket-message"|"claim-message"|"verifybar"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,ticket:ODTicket,reason:string|null},workers:"opendiscord:unclaim-message"}, + "opendiscord:pin-message":{origin:"slash"|"text"|"ticket-message"|"unpin-message"|"verifybar"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,ticket:ODTicket,reason:string|null},workers:"opendiscord:pin-message"}, + "opendiscord:unpin-message":{origin:"slash"|"text"|"ticket-message"|"pin-message"|"verifybar"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,ticket:ODTicket,reason:string|null},workers:"opendiscord:unpin-message"}, + "opendiscord:rename-message":{origin:"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,ticket:ODTicket,reason:string|null,data:string},workers:"opendiscord:rename-message"}, + "opendiscord:move-message":{origin:"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,ticket:ODTicket,reason:string|null,data:ODTicketOption},workers:"opendiscord:move-message"}, + "opendiscord:add-message":{origin:"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,ticket:ODTicket,reason:string|null,data:discord.User},workers:"opendiscord:add-message"}, + "opendiscord:remove-message":{origin:"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,ticket:ODTicket,reason:string|null,data:discord.User},workers:"opendiscord:remove-message"}, + "opendiscord:ticket-action-dm":{origin:"slash"|"text"|"ticket-message"|"close-message"|"reopen-message"|"delete-message"|"claim-message"|"unclaim-message"|"pin-message"|"unpin-message"|"autoclose-message"|"priority-message"|"topic-message"|"transfer-message"|"autoclose"|"autodelete"|"clear"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,mode:"close"|"reopen"|"delete"|"claim"|"unclaim"|"pin"|"unpin"|"rename"|"move"|"add"|"remove"|"priority"|"transfer"|"topic",ticket:ODTicket,reason:string|null,additionalData:null|string|discord.User|ODTicketOption,additionalData2?:discord.User},workers:"opendiscord:ticket-action-dm"}, + "opendiscord:ticket-action-logs":{origin:"slash"|"text"|"ticket-message"|"close-message"|"reopen-message"|"delete-message"|"claim-message"|"unclaim-message"|"pin-message"|"unpin-message"|"autoclose-message"|"priority-message"|"topic-message"|"transfer-message"|"autoclose"|"autodelete"|"clear"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,mode:"close"|"reopen"|"delete"|"claim"|"unclaim"|"pin"|"unpin"|"rename"|"move"|"add"|"remove"|"priority"|"transfer"|"topic",ticket:ODTicket,reason:string|null,additionalData:null|string|discord.User|ODTicketOption,additionalData2?:discord.User},workers:"opendiscord:ticket-action-logs"}, + + "opendiscord:blacklist-view":{origin:"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User},workers:"opendiscord:blacklist-view"}, + "opendiscord:blacklist-get":{origin:"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,data:discord.User},workers:"opendiscord:blacklist-get"}, + "opendiscord:blacklist-add":{origin:"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,data:discord.User,reason:string|null},workers:"opendiscord:blacklist-add"}, + "opendiscord:blacklist-remove":{origin:"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,data:discord.User,reason:string|null},workers:"opendiscord:blacklist-remove"}, + "opendiscord:blacklist-dm":{origin:"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,mode:"add"|"remove",data:discord.User,reason:string|null},workers:"opendiscord:blacklist-dm"}, + "opendiscord:blacklist-logs":{origin:"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,mode:"add"|"remove",data:discord.User,reason:string|null},workers:"opendiscord:blacklist-logs"}, + + "opendiscord:transcript-text-ready":{origin:"channel"|"creator-dm"|"participant-dm"|"active-admin-dm"|"every-admin-dm"|"other",params:{guild:discord.Guild,channel:discord.GuildTextBasedChannel,user:discord.User,ticket:ODTicket,compiler:ODTranscriptCompiler<{contents:string},null>,result:ODTranscriptCompilerCompileResult<{contents:string}>},workers:"opendiscord:transcript-text-ready"}, + "opendiscord:transcript-html-ready":{origin:"channel"|"creator-dm"|"participant-dm"|"active-admin-dm"|"every-admin-dm"|"other",params:{guild:discord.Guild,channel:discord.GuildTextBasedChannel,user:discord.User,ticket:ODTicket,compiler:ODTranscriptCompiler<{url:string,availableUntil:Date},{auth:string}>,result:ODTranscriptCompilerCompileResult<{url:string,availableUntil:Date}>},workers:"opendiscord:transcript-html-ready"}, + "opendiscord:transcript-html-progress":{origin:"channel"|"other",params:{guild:discord.Guild,channel:discord.GuildTextBasedChannel,user:discord.User,ticket:ODTicket,compiler:ODTranscriptCompiler<{url:string,availableUntil:Date},{auth:string}>,remaining:number},workers:"opendiscord:transcript-html-progress"}, + "opendiscord:transcript-error":{origin:"slash"|"text"|"ticket-message"|"reopen-message"|"close-message"|"autoclose-message"|"autodelete"|"clear"|"other",params:{guild:discord.Guild,channel:discord.GuildTextBasedChannel,user:discord.User,ticket:ODTicket,compiler:ODTranscriptCompiler,reason:string|null},workers:"opendiscord:transcript-error"}, + "opendiscord:transcript-history":{origin:"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.GuildTextBasedChannel,user:discord.User,transcriptUser:discord.User,transcriptList:ODTranscriptHistoryData[]},workers:"opendiscord:transcript-history"}, + + "opendiscord:reaction-role":{origin:"panel-button"|"other",params:{guild:discord.Guild,user:discord.User,role:ODRole,result:ODRoleUpdateResult[]},workers:"opendiscord:reaction-role"}, + "opendiscord:reaction-role-dm":{origin:"panel-button"|"other",params:{guild:discord.Guild,user:discord.User,role:ODRole,result:ODRoleUpdateResult[]},workers:"opendiscord:reaction-role-dm"}, + "opendiscord:reaction-role-logs":{origin:"panel-button"|"other",params:{guild:discord.Guild,user:discord.User,role:ODRole,result:ODRoleUpdateResult[]},workers:"opendiscord:reaction-role-logs"}, + + "opendiscord:clear-verify-message":{origin:"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.GuildTextBasedChannel,user:discord.User,filter:ODTicketClearFilter,list:string[],inProgress:boolean},workers:"opendiscord:clear-verify-message"}, + "opendiscord:clear-message":{origin:"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.GuildTextBasedChannel,user:discord.User,filter:ODTicketClearFilter,list:string[]},workers:"opendiscord:clear-message"}, + "opendiscord:clear-logs":{origin:"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.GuildTextBasedChannel,user:discord.User,filter:ODTicketClearFilter,list:string[]},workers:"opendiscord:clear-logs"}, + + "opendiscord:autoclose-message":{origin:"timeout"|"leave"|"verifybar"|"other",params:{guild:discord.Guild,channel:discord.GuildTextBasedChannel,user:discord.User,ticket:ODTicket},workers:"opendiscord:autoclose-message"}, + "opendiscord:autodelete-message":{origin:"timeout"|"leave"|"other",params:{guild:discord.Guild,channel:discord.GuildTextBasedChannel,user:discord.User,ticket:ODTicket},workers:"opendiscord:autodelete-message"}, + "opendiscord:autoclose-enable":{origin:"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.GuildTextBasedChannel,user:discord.User,ticket:ODTicket,time:number,reason:string|null},workers:"opendiscord:autoclose-enable"}, + "opendiscord:autodelete-enable":{origin:"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.GuildTextBasedChannel,user:discord.User,ticket:ODTicket,time:number,reason:string|null},workers:"opendiscord:autodelete-enable"}, + "opendiscord:autoclose-disable":{origin:"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.GuildTextBasedChannel,user:discord.User,ticket:ODTicket,reason:string|null},workers:"opendiscord:autoclose-disable"}, + "opendiscord:autodelete-disable":{origin:"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.GuildTextBasedChannel,user:discord.User,ticket:ODTicket,reason:string|null},workers:"opendiscord:autodelete-disable"}, + + "opendiscord:topic-set":{origin:"slash"|"text"|"ticket-action"|"other",params:{guild:discord.Guild,channel:discord.GuildTextBasedChannel,user:discord.User,ticket:ODTicket,topic:string},workers:"opendiscord:topic-set"}, + "opendiscord:priority-set":{origin:"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.GuildTextBasedChannel,user:discord.User,ticket:ODTicket,priority:ODPriorityLevel,reason:string|null},workers:"opendiscord:priority-set"}, + "opendiscord:priority-get":{origin:"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.GuildTextBasedChannel,user:discord.User,ticket:ODTicket,priority:ODPriorityLevel},workers:"opendiscord:priority-get"}, + "opendiscord:transfer-message":{origin:"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.GuildTextBasedChannel,user:discord.User,ticket:ODTicket,oldCreator:discord.User,newCreator:discord.User,reason:string|null},workers:"opendiscord:transfer-message"}, +} + +/**## ODModalManagerIdMappings `interface` + * A list of all available IDs in the default `ODModalManager` class in `opendiscord`. + * It's used to generate typescript declarations for this class. + */ +export interface ODModalManagerIdMappings extends api.ODModalManagerIdConstraint { + //Deprecated, moved to ODModalComponentManagerIdMappings +} + +///////////////////////////// +////// MAPPED MANAGERS ////// +///////////////////////////// + +/**## ODMappedButtonManager `class + * A special class with types for the Open Ticket `ODButtonManager` class. + */ +export class ODMappedButtonManager extends api.ODButtonManager {} + +/**## ODMappedDropdownManager `class + * A special class with types for the Open Ticket `ODDropdownManager` class. + */ +export class ODMappedDropdownManager extends api.ODDropdownManager {} + +/**## ODMappedFileManager `class + * A special class with types for the Open Ticket `ODFileManager` class. + */ +export class ODMappedFileManager extends api.ODFileManager {} + +/**## ODMappedEmbedManager `class + * A special class with types for the Open Ticket `ODEmbedManager` class. + */ +export class ODMappedEmbedManager extends api.ODEmbedManager {} + +/**## ODMappedMessageManager `class + * A special class with types for the Open Ticket `ODMessageManager` class. + */ +export class ODMappedMessageManager extends api.ODMessageManager {} + +/**## ODMappedModalManager `class + * A special class with types for the Open Ticket `ODModalManager` class. + */ +export class ODMappedModalManager extends api.ODModalManager {} + +/**## ODMappedBuilderManager `class + * A special class with types for the Open Ticket `ODBuilderManager` class. + */ +export class ODMappedBuilderManager extends api.ODBuilderManager {} \ No newline at end of file diff --git a/src/core/mappings/checker.ts b/src/core/mappings/checker.ts new file mode 100644 index 0000000..a676744 --- /dev/null +++ b/src/core/mappings/checker.ts @@ -0,0 +1,149 @@ +/////////////////////////////////////// +//OPEN TICKET CONFIG CHECKER MAPPINGS +/////////////////////////////////////// +import * as api from "@open-discord-bots/framework/api" + +/**## ODBCheckerManagerIdMappings `interface` + * A list of all available IDs in the default `ODBCheckerManager` class in `opendiscord`. + * It's used to generate typescript declarations for this class. + */ +export interface ODCheckerManagerIdMappings extends api.ODCheckerManagerIdConstraint { + "opendiscord:general":api.ODChecker, + "opendiscord:questions":api.ODChecker, + "opendiscord:options":api.ODChecker, + "opendiscord:panels":api.ODChecker, + "opendiscord:transcripts":api.ODChecker +} + +/**## ODCheckerTranslationRegisterOtherIdMappings `type` + * A list of all available IDs in the default `ODCheckerTranslationRegister` class in `opendiscord`. + * It's used to generate typescript declarations for this class. + */ +export type ODCheckerTranslationRegisterOtherIdMappings = ( + "opendiscord:header-openticket"| + "opendiscord:header-configchecker"| + "opendiscord:header-description"| + "opendiscord:type-error"| + "opendiscord:type-warning"| + "opendiscord:type-info"| + "opendiscord:data-path"| + "opendiscord:data-docs"| + "opendiscord:data-message"| + "opendiscord:compact-information"| + "opendiscord:footer-error"| + "opendiscord:footer-warning"| + "opendiscord:footer-support" +) + +/**## ODCheckerTranslationRegisterMessageIdMappings `type` + * A list of all available IDs in the default `ODCheckerTranslationRegister` class in `opendiscord`. + * It's used to generate typescript declarations for this class. + */ +export type ODCheckerTranslationRegisterMessageIdMappings = ( + "opendiscord:invalid-type"| + "opendiscord:property-missing"| + "opendiscord:property-optional"| + "opendiscord:object-disabled"| + "opendiscord:null-invalid"| + "opendiscord:switch-invalid-type"| + "opendiscord:object-switch-invalid-type"| + + "opendiscord:string-too-short"| + "opendiscord:string-too-long"| + "opendiscord:string-length-invalid"| + "opendiscord:string-starts-with"| + "opendiscord:string-ends-with"| + "opendiscord:string-contains"| + "opendiscord:string-inverted-contains"| + "opendiscord:string-choices"| + "opendiscord:string-lowercase"| + "opendiscord:string-uppercase"| + "opendiscord:string-special-characters"| + "opendiscord:string-no-spaces"| + "opendiscord:string-regex"| + "opendiscord:string-capital-word"| + "opendiscord:string-capital-sentence"| + "opendiscord:string-punctuation"| + + "opendiscord:number-nan"| + "opendiscord:number-too-short"| + "opendiscord:number-too-long"| + "opendiscord:number-length-invalid"| + "opendiscord:number-too-small"| + "opendiscord:number-too-large"| + "opendiscord:number-not-equal"| + "opendiscord:number-step"| + "opendiscord:number-step-offset"| + "opendiscord:number-starts-with"| + "opendiscord:number-ends-with"| + "opendiscord:number-contains"| + "opendiscord:number-inverted-contains"| + "opendiscord:number-choices"| + "opendiscord:number-float"| + "opendiscord:number-negative"| + "opendiscord:number-positive"| + "opendiscord:number-zero"| + + "opendiscord:boolean-true"| + "opendiscord:boolean-false"| + + "opendiscord:array-empty-disabled"| + "opendiscord:array-empty-required"| + "opendiscord:array-too-short"| + "opendiscord:array-too-long"| + "opendiscord:array-length-invalid"| + "opendiscord:array-invalid-types"| + "opendiscord:array-double"| + + "opendiscord:discord-invalid-id"| + "opendiscord:discord-invalid-id-options"| + "opendiscord:discord-invalid-token"| + "opendiscord:color-invalid"| + "opendiscord:emoji-too-short"| + "opendiscord:emoji-too-long"| + "opendiscord:emoji-custom"| + "opendiscord:emoji-invalid"| + "opendiscord:url-invalid"| + "opendiscord:url-invalid-http"| + "opendiscord:url-invalid-protocol"| + "opendiscord:url-invalid-hostname"| + "opendiscord:url-invalid-extension"| + "opendiscord:url-invalid-path"| + "opendiscord:id-not-unique"| + "opendiscord:id-non-existent"| + + "opendiscord:invalid-language"| + "opendiscord:invalid-button"| + "opendiscord:unused-option"| + "opendiscord:unused-question"| + "opendiscord:dropdown-option" +) + +/**## ODCheckerFunctionManagerIdMappings `type` + * A list of all available IDs in the default `ODCheckerFunctionManager` class in `opendiscord`. + * It's used to generate typescript declarations for this class. + */ +export interface ODCheckerFunctionManagerIdMappings extends api.ODCheckerFunctionManagerIdConstraint { + "opendiscord:unused-options":api.ODCheckerFunction, + "opendiscord:unused-questions":api.ODCheckerFunction, + "opendiscord:dropdown-options":api.ODCheckerFunction +} + +///////////////////////////// +////// MAPPED MANAGERS ////// +///////////////////////////// + +/**## ODMappedCheckerManager `class + * A special class with types for the Open Ticket `ODCheckerManager` class. + */ +export class ODMappedCheckerManager extends api.ODCheckerManager {} + +/**## ODMappedCheckerFunctionManager `class + * A special class with types for the Open Ticket `ODCheckerFunctionManager` class. + */ +export class ODMappedCheckerFunctionManager extends api.ODCheckerFunctionManager {} + +/**## ODMappedCheckerTranslationRegister `class + * A special class with types for the Open Ticket `ODCheckerTranslationRegister` class. + */ +export class ODMappedCheckerTranslationRegister extends api.ODCheckerTranslationRegister {} \ No newline at end of file diff --git a/src/core/mappings/client.ts b/src/core/mappings/client.ts new file mode 100644 index 0000000..67c48cd --- /dev/null +++ b/src/core/mappings/client.ts @@ -0,0 +1,105 @@ +/////////////////////////////////////// +//OPEN TICKET CLIENT MAPPINGS +/////////////////////////////////////// +import * as api from "@open-discord-bots/framework/api" + +/**## ODSlashCommandManagerIdMappings `interface` + * A list of all available IDs in the default `ODSlashCommandManager` class in `opendiscord`. + * It's used to generate typescript declarations for this class. + */ +export interface ODSlashCommandManagerIdMappings extends api.ODSlashCommandManagerIdConstraint { + "opendiscord:help":api.ODSlashCommand, + "opendiscord:panel":api.ODSlashCommand, + "opendiscord:ticket":api.ODSlashCommand, + "opendiscord:close":api.ODSlashCommand, + "opendiscord:delete":api.ODSlashCommand, + "opendiscord:reopen":api.ODSlashCommand, + "opendiscord:claim":api.ODSlashCommand, + "opendiscord:unclaim":api.ODSlashCommand, + "opendiscord:pin":api.ODSlashCommand, + "opendiscord:unpin":api.ODSlashCommand, + "opendiscord:move":api.ODSlashCommand, + "opendiscord:rename":api.ODSlashCommand, + "opendiscord:add":api.ODSlashCommand, + "opendiscord:remove":api.ODSlashCommand, + "opendiscord:blacklist":api.ODSlashCommand, + "opendiscord:stats":api.ODSlashCommand, + "opendiscord:clear":api.ODSlashCommand, + "opendiscord:autoclose":api.ODSlashCommand, + "opendiscord:autodelete":api.ODSlashCommand, + "opendiscord:topic":api.ODSlashCommand, + "opendiscord:priority":api.ODSlashCommand, + "opendiscord:transfer":api.ODSlashCommand, + "opendiscord:transcripts":api.ODSlashCommand, +} + +/**## ODTextCommandManagerIdMappings `interface` + * A list of all available IDs in the default `ODTextCommandManager` class in `opendiscord`. + * It's used to generate typescript declarations for this class. + */ +export interface ODTextCommandManagerIdMappings extends api.ODTextCommandManagerIdConstraint { + "opendiscord:dump":api.ODTextCommand, + "opendiscord:help":api.ODTextCommand, + "opendiscord:panel":api.ODTextCommand, + "opendiscord:close":api.ODTextCommand, + "opendiscord:delete":api.ODTextCommand, + "opendiscord:reopen":api.ODTextCommand, + "opendiscord:claim":api.ODTextCommand, + "opendiscord:unclaim":api.ODTextCommand, + "opendiscord:pin":api.ODTextCommand, + "opendiscord:unpin":api.ODTextCommand, + "opendiscord:move":api.ODTextCommand, + "opendiscord:rename":api.ODTextCommand, + "opendiscord:add":api.ODTextCommand, + "opendiscord:remove":api.ODTextCommand, + "opendiscord:blacklist-view":api.ODTextCommand, + "opendiscord:blacklist-add":api.ODTextCommand, + "opendiscord:blacklist-remove":api.ODTextCommand, + "opendiscord:blacklist-get":api.ODTextCommand, + "opendiscord:stats-global":api.ODTextCommand, + "opendiscord:stats-reset":api.ODTextCommand, + "opendiscord:stats-ticket":api.ODTextCommand, + "opendiscord:stats-user":api.ODTextCommand, + "opendiscord:clear":api.ODTextCommand, + "opendiscord:autoclose-disable":api.ODTextCommand, + "opendiscord:autoclose-enable":api.ODTextCommand, + "opendiscord:autodelete-disable":api.ODTextCommand, + "opendiscord:autodelete-enable":api.ODTextCommand, + "opendiscord:topic-set":api.ODTextCommand, + "opendiscord:priority-set":api.ODTextCommand, + "opendiscord:priority-get":api.ODTextCommand, + "opendiscord:transfer":api.ODTextCommand, + "opendiscord:transcripts":api.ODTextCommand, +} + +/**## ODContextMenuManagerIdMappings `interface` + * A list of all available IDs in the default `ODContextMenuManager` class in `opendiscord`. + * It's used to generate typescript declarations for this class. + */ +export interface ODContextMenuManagerIdMappings extends api.ODContextMenuManagerIdConstraint { + //"opendiscord:test-menu":ODContextMenu +} + +///////////////////////////// +////// MAPPED MANAGERS ////// +///////////////////////////// + +/**## ODMappedClientManager `class + * A special class with types for the Open Ticket `ODClientManager` class. + */ +export class ODMappedClientManager extends api.ODClientManager {} + +/**## ODMappedSlashCommandManager `class + * A special class with types for the Open Ticket `ODSlashCommandManager` class. + */ +export class ODMappedSlashCommandManager extends api.ODSlashCommandManager {} + +/**## ODMappedTextCommandManager `class + * A special class with types for the Open Ticket `ODTextCommandManager` class. + */ +export class ODMappedTextCommandManager extends api.ODTextCommandManager {} + +/**## ODMappedContextMenuManager `class + * A special class with types for the Open Ticket `ODContextMenuManager` class. + */ +export class ODMappedContextMenuManager extends api.ODContextMenuManager {} diff --git a/src/core/mappings/component.ts b/src/core/mappings/component.ts new file mode 100644 index 0000000..1e542a3 --- /dev/null +++ b/src/core/mappings/component.ts @@ -0,0 +1,81 @@ +/////////////////////////////////////// +//OPEN TICKET COMPONENT MAPPINGS +/////////////////////////////////////// +import * as api from "@open-discord-bots/framework/api" +import * as discord from "discord.js" +import { ODTicketOption } from "../api/option.js" +import { ODTicket } from "../api/ticket.js" + +/**## ODSharedComponentManagerIdMappings `interface` + * A list of all available IDs in the default `ODSharedComponentManager` class in `opendiscord`. + * It's used to generate typescript declarations for this class. + */ +export interface ODSharedComponentManagerIdMappings extends api.ODComponentManagerIdConstraint { + //"opendiscord:example-component":{origin:"slash"|"text"|"other",params:{guild:discord.Guild|null,channel:discord.TextBasedChannel,user:discord.User},workers:"opendiscord:example-component"}, +} + +/**## ODMessageComponentManagerIdMappings `interface` + * A list of all available IDs in the default `ODMessageComponentManager` class in `opendiscord`. + * It's used to generate typescript declarations for this class. + */ +export interface ODMessageComponentManagerIdMappings extends api.ODComponentManagerIdConstraint { + //"opendiscord:example-message":{origin:"slash"|"text"|"other",params:{guild:discord.Guild|null,channel:discord.TextBasedChannel,user:discord.User},workers:"opendiscord:example-message"}, +} + +/**## ODModalComponentManagerIdMappings `interface` + * A list of all available IDs in the default `ODModalComponentManager` class in `opendiscord`. + * It's used to generate typescript declarations for this class. + */ +export interface ODModalComponentManagerIdMappings extends api.ODComponentManagerIdConstraint { + "opendiscord:ticket-questions":{origin:"panel-button"|"panel-dropdown"|"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,option:ODTicketOption},workers:"opendiscord:ticket-questions"}, + "opendiscord:close-ticket-reason":{origin:"ticket-message"|"reopen-message"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,ticket:ODTicket,message:discord.Message},workers:"opendiscord:close-ticket-reason"} + "opendiscord:reopen-ticket-reason":{origin:"ticket-message"|"close-message"|"autoclose-message"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,ticket:ODTicket,message:discord.Message},workers:"opendiscord:reopen-ticket-reason"} + "opendiscord:delete-ticket-reason":{origin:"ticket-message"|"reopen-message"|"close-message"|"autoclose-message"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,ticket:ODTicket,message:discord.Message},workers:"opendiscord:delete-ticket-reason"} + "opendiscord:claim-ticket-reason":{origin:"ticket-message"|"unclaim-message"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,ticket:ODTicket,message:discord.Message},workers:"opendiscord:claim-ticket-reason"} + "opendiscord:unclaim-ticket-reason":{origin:"ticket-message"|"claim-message"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,ticket:ODTicket,message:discord.Message},workers:"opendiscord:unclaim-ticket-reason"} + "opendiscord:pin-ticket-reason":{origin:"ticket-message"|"unpin-message"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,ticket:ODTicket,message:discord.Message},workers:"opendiscord:pin-ticket-reason"} + "opendiscord:unpin-ticket-reason":{origin:"ticket-message"|"pin-message"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,ticket:ODTicket,message:discord.Message},workers:"opendiscord:unpin-ticket-reason"} +} + +/**## ODComponentModifierManagerIdMappings `interface` + * A list of all available IDs in the default `ODComponentModifierManager` class in `opendiscord`. + * It's used to generate typescript declarations for this class. + */ +export interface ODComponentModifierManagerIdMappings extends api.ODComponentModifierManagerIdConstraint { + "opendiscord:close-ticket-verifybar":api.ODMessageComponentModifier<"ticket-message"|"reopen-message",{guild:discord.Guild|null,channel:discord.TextBasedChannel,user:discord.User,verifybar:api.ODVerifyBar},string>, + "opendiscord:reopen-ticket-verifybar":api.ODMessageComponentModifier<"ticket-message"|"close-message"|"autoclose-message",{guild:discord.Guild|null,channel:discord.TextBasedChannel,user:discord.User,verifybar:api.ODVerifyBar},string>, + "opendiscord:delete-ticket-verifybar":api.ODMessageComponentModifier<"ticket-message"|"close-message"|"autoclose-message"|"reopen-message",{guild:discord.Guild|null,channel:discord.TextBasedChannel,user:discord.User,verifybar:api.ODVerifyBar},string>, + "opendiscord:claim-ticket-verifybar":api.ODMessageComponentModifier<"ticket-message"|"unclaim-message",{guild:discord.Guild|null,channel:discord.TextBasedChannel,user:discord.User,verifybar:api.ODVerifyBar},string>, + "opendiscord:unclaim-ticket-verifybar":api.ODMessageComponentModifier<"ticket-message"|"claim-message",{guild:discord.Guild|null,channel:discord.TextBasedChannel,user:discord.User,verifybar:api.ODVerifyBar},string>, + "opendiscord:pin-ticket-verifybar":api.ODMessageComponentModifier<"ticket-message"|"unpin-message",{guild:discord.Guild|null,channel:discord.TextBasedChannel,user:discord.User,verifybar:api.ODVerifyBar},string>, + "opendiscord:unpin-ticket-verifybar":api.ODMessageComponentModifier<"ticket-message"|"pin-message",{guild:discord.Guild|null,channel:discord.TextBasedChannel,user:discord.User,verifybar:api.ODVerifyBar},string>, +} + +///////////////////////////// +////// MAPPED MANAGERS ////// +///////////////////////////// + +/**## ODMappedSharedComponentManager `class + * A special class with types for the Open Ticket `ODSharedComponentManager` class. + */ +export class ODMappedSharedComponentManager extends api.ODSharedComponentManager {} + +/**## ODMappedMessageComponentManager `class + * A special class with types for the Open Ticket `ODMessageComponentManager` class. + */ +export class ODMappedMessageComponentManager extends api.ODMessageComponentManager {} + +/**## ODMappedModalComponentManager `class + * A special class with types for the Open Ticket `ODModalComponentManager` class. + */ +export class ODMappedModalComponentManager extends api.ODModalComponentManager {} + +/**## ODMappedComponentModifierManager `class + * A special class with types for the Open Ticket `ODComponentModifierManager` class. + */ +export class ODMappedComponentModifierManager extends api.ODComponentModifierManager {} + +/**## ODMappedComponentManager `class + * A special class with types for the Open Ticket `ODBuilderManager` class. + */ +export class ODMappedComponentManager extends api.ODComponentManager {} \ No newline at end of file diff --git a/src/core/api/defaults/config.ts b/src/core/mappings/config.ts similarity index 55% rename from src/core/api/defaults/config.ts rename to src/core/mappings/config.ts index 69a1850..51fc926 100644 --- a/src/core/api/defaults/config.ts +++ b/src/core/mappings/config.ts @@ -1,130 +1,74 @@ /////////////////////////////////////// -//DEFAULT CONFIG MODULE +//OPEN TICKET CONFIG MAPPINGS /////////////////////////////////////// -import { ODValidButtonColor, ODValidId } from "../modules/base" +import * as api from "@open-discord-bots/framework/api" import * as discord from "discord.js" -import { ODConfigManager, ODConfig, ODJsonConfig } from "../modules/config" -import { ODClientActivityMode, ODClientActivityType } from "../modules/client" -import { ODRoleUpdateMode } from "../openticket/role" +import { ODRoleUpdateMode } from "../api/role.js" -/** (CONTRIBUTOR GUIDE) HOW TO ADD NEW CONFIG VARIABLES? - * - Make the change to the config file in (./config/) and be aware of the following things: - * - The variable has a clear name and its function is obvious. - * - The variable is in the correct position/category of the config. - * - The variable contains a default placeholder to suggest the contents. - * - If there's a (./devconfig/), also modify this file. - * - Register the config in loadAllConfigs() in (./src/data/framework/configLoader.ts) - * - The variable should be added to the "formatters" in the correct position. - * - Add autocomplete for the variable in ODJsonConfig_Default... in (./src/core/api/defaults/config.ts) - * - Add the variable to the config checker in (./src/data/framework/checkerLoader.ts) - * - Make sure the variable is compatible with the Interactive Setup CLI. - * - The variable should be added by the migration manager (./src/core/startup/migration.ts) when missing. - * - Update the Open Ticket Documentation. - * - * IF VARIABLE IS FROM questions.json, options.json OR panels.json: - * - Check (./src/data/openticket/...) for loading/unloading of data. - * - Check (./src/actions/createTicket.ts) and related files. - * - Check (./src/builders), (./src/actions), (./src/data) & (./src/commands) in general in the areas that were changed. - */ - -/**## ODConfigManagerIds_Default `interface` - * This interface is a list of ids available in the `ODConfigManager_Default` class. +/**## ODConfigManagerIdMappings `interface` + * A list of all available IDs in the default `ODConfigManager` class in `opendiscord`. * It's used to generate typescript declarations for this class. */ -export interface ODConfigManagerIds_Default { - "opendiscord:general":ODJsonConfig_DefaultGeneral, - "opendiscord:questions":ODJsonConfig_DefaultQuestions, - "opendiscord:options":ODJsonConfig_DefaultOptions, - "opendiscord:panels":ODJsonConfig_DefaultPanels, - "opendiscord:transcripts":ODJsonConfig_DefaultTranscripts +export interface ODConfigManagerIdMappings extends api.ODConfigManagerIdConstraint { + "opendiscord:general":ODGeneralJsonCommentsConfig, + "opendiscord:questions":ODQuestionsJsonCommentsConfig, + "opendiscord:options":ODOptionsJsonCommentsConfig, + "opendiscord:panels":ODPanelsJsonCommentsConfig, + "opendiscord:transcripts":ODTranscriptsJsonCommentsConfig } -/**## ODConfigManager_Default `default_class` - * This is a special class that adds type definitions & typescript to the ODConfigManager class. - * It doesn't add any extra features! - * - * This default class is made for the global variable `opendiscord.configs`! - */ -export class ODConfigManager_Default extends ODConfigManager { - get(id:ConfigId): ODConfigManagerIds_Default[ConfigId] - get(id:ODValidId): ODConfig|null - - get(id:ODValidId): ODConfig|null { - return super.get(id) - } - - remove(id:ConfigId): ODConfigManagerIds_Default[ConfigId] - remove(id:ODValidId): ODConfig|null - - remove(id:ODValidId): ODConfig|null { - return super.remove(id) - } +/////////////////////////////////////// +// CONFIG STRUCTURES, VALUES & TYPES +// --> general.jsonc +/////////////////////////////////////// - exists(id:keyof ODConfigManagerIds_Default): boolean - exists(id:ODValidId): boolean - - exists(id:ODValidId): boolean { - return super.exists(id) - } -} - -/**## ODJsonConfig_DefaultStatusType `interface` - * This interface is an object which has all properties for the status object in the `general.json` config! +/**## ODGeneralJsonConfig_Status `interface` + * This interface is an object which has all properties for the status object in the `general.jsonc` config! */ -export interface ODJsonConfig_DefaultStatusType { +export interface ODGeneralJsonConfig_Status { /**Is the status enabled? */ enabled:boolean, /**The type of status (e.g. playing, listening, custom, ...) */ - type:Exclude, + type:Exclude, /**The mode/status of the bot (e.g. online, invisible, idle, do not disturb) */ - mode:ODClientActivityMode + mode:api.ODClientActivityMode /**The text for the status. */ text:string, /**Additional text for the status. (visible below 'text') */ state:string, } -/**## ODJsonConfig_DefaultMessageSettingsType `interface` - * This interface is an object which has all properties for the "system"."messages".... object in the `general.json` config! +/**## ODGeneralJsonConfig_MessageSettings `interface` + * This interface is an object which has all properties for the "system"."messages".... object in the `general.jsonc` config! */ -export interface ODJsonConfig_DefaultMessageSettingsType { +export interface ODGeneralJsonConfig_MessageSettings { /**Enable sending DM logs to the ticket creator for this action. */ dm:boolean, /**Enable sending logsto the log channel for this action. */ logs:boolean } -/**## ODJsonConfig_DefaultCmdPermissionSettingsType `type` - * This type is a collection of command permission settings for the "system"."permissions".... object in the `general.json` config! +/**## ODGeneralJsonConfig_CmdPermissionSettingsType `type` + * This type is a collection of command permission settings for the "system"."permissions".... object in the `general.jsonc` config! */ -export type ODJsonConfig_DefaultCmdPermissionSettingsType = "admin"|"everyone"|"none"|string +export type ODGeneralJsonConfig_CmdPermissionSettingsType = "admin"|"everyone"|"none"|string -/**## ODJsonConfig_DefaultInfo `interface` - * This object contains a few URLs and metadata for the config. - */ -export interface ODJsonConfig_DefaultInfo { - /**A link to the Open Ticket documentation. */ - support:string, - /**A link to the DJdj Development discord server. */ - discord:string, - /**The version of Open Ticket this config is compatible with. */ - version:string -} - -/**## ODJsonConfig_DefaultSystemLogs `interface` +/**## ODGeneralJsonConfig_SystemLogs `interface` * All settings related to the log channel. */ -export interface ODJsonConfig_DefaultSystemLogs { +export interface ODGeneralJsonConfig_SystemLogs { /**Enable logging. Individual actions should still be added via the `"system"."messages"..."logs"` */ enabled:boolean, /**The channel to send logs to. */ - channel:string + channel:string, + /**Configure dm & log messages for all Open Ticket commands & actions. */ + logMessages:ODGeneralJsonConfig_LogMessages } -/**## ODJsonConfig_DefaultSystemLimits `interface` +/**## ODGeneralJsonConfig_SystemLimits `interface` * All settings related to global ticket limits. */ -export interface ODJsonConfig_DefaultSystemLimits { +export interface ODGeneralJsonConfig_SystemLimits { /**Enable global ticket limits. */ enabled:boolean, /**The maximum amount of tickets that are allowed in the server at the same time. */ @@ -133,10 +77,10 @@ export interface ODJsonConfig_DefaultSystemLimits { userMaximum:number } -/**## ODJsonConfig_DefaultSystemChannelTopic `interface` +/**## ODGeneralJsonConfig_SystemChannelTopic `interface` * All global channel topic settings. */ -export interface ODJsonConfig_DefaultSystemChannelTopic { +export interface ODGeneralJsonConfig_SystemChannelTopic { /**Show the option name in the channel topic. */ showOptionName:boolean, /**Show the option description in the channel topic. */ @@ -157,59 +101,60 @@ export interface ODJsonConfig_DefaultSystemChannelTopic { showParticipants:boolean } -/**## ODJsonConfig_DefaultSystemPermissions `interface` +/**## ODGeneralJsonConfig_SystemPermissions `interface` * Configure permissions for all Open Ticket commands & actions. */ -export interface ODJsonConfig_DefaultSystemPermissions { - help:ODJsonConfig_DefaultCmdPermissionSettingsType, - panel:ODJsonConfig_DefaultCmdPermissionSettingsType, - ticket:ODJsonConfig_DefaultCmdPermissionSettingsType, - close:ODJsonConfig_DefaultCmdPermissionSettingsType, - delete:ODJsonConfig_DefaultCmdPermissionSettingsType, - reopen:ODJsonConfig_DefaultCmdPermissionSettingsType, - claim:ODJsonConfig_DefaultCmdPermissionSettingsType, - unclaim:ODJsonConfig_DefaultCmdPermissionSettingsType, - pin:ODJsonConfig_DefaultCmdPermissionSettingsType, - unpin:ODJsonConfig_DefaultCmdPermissionSettingsType, - move:ODJsonConfig_DefaultCmdPermissionSettingsType, - rename:ODJsonConfig_DefaultCmdPermissionSettingsType, - add:ODJsonConfig_DefaultCmdPermissionSettingsType, - remove:ODJsonConfig_DefaultCmdPermissionSettingsType, - blacklist:ODJsonConfig_DefaultCmdPermissionSettingsType, - stats:ODJsonConfig_DefaultCmdPermissionSettingsType, - clear:ODJsonConfig_DefaultCmdPermissionSettingsType, - autoclose:ODJsonConfig_DefaultCmdPermissionSettingsType, - autodelete:ODJsonConfig_DefaultCmdPermissionSettingsType, - transfer:ODJsonConfig_DefaultCmdPermissionSettingsType, - topic:ODJsonConfig_DefaultCmdPermissionSettingsType, - priority:ODJsonConfig_DefaultCmdPermissionSettingsType, +export interface ODGeneralJsonConfig_SystemPermissions { + help:ODGeneralJsonConfig_CmdPermissionSettingsType, + panel:ODGeneralJsonConfig_CmdPermissionSettingsType, + ticket:ODGeneralJsonConfig_CmdPermissionSettingsType, + close:ODGeneralJsonConfig_CmdPermissionSettingsType, + delete:ODGeneralJsonConfig_CmdPermissionSettingsType, + reopen:ODGeneralJsonConfig_CmdPermissionSettingsType, + claim:ODGeneralJsonConfig_CmdPermissionSettingsType, + unclaim:ODGeneralJsonConfig_CmdPermissionSettingsType, + pin:ODGeneralJsonConfig_CmdPermissionSettingsType, + unpin:ODGeneralJsonConfig_CmdPermissionSettingsType, + move:ODGeneralJsonConfig_CmdPermissionSettingsType, + rename:ODGeneralJsonConfig_CmdPermissionSettingsType, + add:ODGeneralJsonConfig_CmdPermissionSettingsType, + remove:ODGeneralJsonConfig_CmdPermissionSettingsType, + blacklist:ODGeneralJsonConfig_CmdPermissionSettingsType, + stats:ODGeneralJsonConfig_CmdPermissionSettingsType, + clear:ODGeneralJsonConfig_CmdPermissionSettingsType, + autoclose:ODGeneralJsonConfig_CmdPermissionSettingsType, + autodelete:ODGeneralJsonConfig_CmdPermissionSettingsType, + transfer:ODGeneralJsonConfig_CmdPermissionSettingsType, + topic:ODGeneralJsonConfig_CmdPermissionSettingsType, + priority:ODGeneralJsonConfig_CmdPermissionSettingsType, + transcripts:ODGeneralJsonConfig_CmdPermissionSettingsType, } -/**## ODJsonConfig_DefaultSystemMessages `interface` +/**## ODGeneralJsonConfig_LogMessages `interface` * Configure dm & log messages for all Open Ticket commands & actions. */ -export interface ODJsonConfig_DefaultSystemMessages { - creation:ODJsonConfig_DefaultMessageSettingsType, - closing:ODJsonConfig_DefaultMessageSettingsType, - deleting:ODJsonConfig_DefaultMessageSettingsType, - reopening:ODJsonConfig_DefaultMessageSettingsType, - claiming:ODJsonConfig_DefaultMessageSettingsType, - pinning:ODJsonConfig_DefaultMessageSettingsType, - adding:ODJsonConfig_DefaultMessageSettingsType, - removing:ODJsonConfig_DefaultMessageSettingsType, - renaming:ODJsonConfig_DefaultMessageSettingsType, - moving:ODJsonConfig_DefaultMessageSettingsType, - blacklisting:ODJsonConfig_DefaultMessageSettingsType, - transferring:ODJsonConfig_DefaultMessageSettingsType, - topicChange:ODJsonConfig_DefaultMessageSettingsType, - priorityChange:ODJsonConfig_DefaultMessageSettingsType, - reactionRole:ODJsonConfig_DefaultMessageSettingsType, +export interface ODGeneralJsonConfig_LogMessages { + creation:ODGeneralJsonConfig_MessageSettings, + closing:ODGeneralJsonConfig_MessageSettings, + deleting:ODGeneralJsonConfig_MessageSettings, + reopening:ODGeneralJsonConfig_MessageSettings, + claiming:ODGeneralJsonConfig_MessageSettings, + pinning:ODGeneralJsonConfig_MessageSettings, + adding:ODGeneralJsonConfig_MessageSettings, + removing:ODGeneralJsonConfig_MessageSettings, + renaming:ODGeneralJsonConfig_MessageSettings, + moving:ODGeneralJsonConfig_MessageSettings, + blacklisting:ODGeneralJsonConfig_MessageSettings, + transferring:ODGeneralJsonConfig_MessageSettings, + topicChange:ODGeneralJsonConfig_MessageSettings, + priorityChange:ODGeneralJsonConfig_MessageSettings, + reactionRole:ODGeneralJsonConfig_MessageSettings, } -/**## ODJsonConfig_DefaultSystem `interface` +/**## ODGeneralJsonConfig_TicketSystem `interface` * All settings related to the ticket system. */ -export interface ODJsonConfig_DefaultSystem { +export interface ODGeneralJsonConfig_TicketSystem { /**Prefer slash-commands over text-commands when displaying them in menu's and messages. */ preferSlashOverText:boolean, /**Reply with "unknown command" when the prefix is used without a valid command. */ @@ -230,6 +175,8 @@ export interface ODJsonConfig_DefaultSystem { emojiStyle:"before"|"after"|"double"|"disabled", /**The emoji used when pinning tickets. This is '📌' by default. */ pinEmoji:string, + /**The emoji used when closing tickets. This is '🔒' by default. */ + closeEmoji:string, /**Reply with an ephemeral message when a ticket is created. */ replyOnTicketCreation:boolean, @@ -266,29 +213,40 @@ export interface ODJsonConfig_DefaultSystem { enableTicketActionWithReason:boolean, /**Enable/disable the delete without transcript feature (button & /delete command). */ enableDeleteWithoutTranscript:boolean, - - /**All settings related to the log channel. */ - logs:ODJsonConfig_DefaultSystemLogs, + /**Enable/disable creating tickets for other users with /ticket . (ADMIN ONLY) */ + enableCreateTicketForOtherUser:boolean, /**All settings related to global ticket limits. */ - limits:ODJsonConfig_DefaultSystemLimits, + limits:ODGeneralJsonConfig_SystemLimits, /**All global channel topic settings. */ - channelTopic:ODJsonConfig_DefaultSystemChannelTopic, + channelTopic:ODGeneralJsonConfig_SystemChannelTopic, - /**Configure permissions for all Open Ticket commands & actions. */ - permissions:ODJsonConfig_DefaultSystemPermissions, - - /**Configure dm & log messages for all Open Ticket commands & actions. */ - messages:ODJsonConfig_DefaultSystemMessages + /**Move closed tickets to this channel category. */ + closedCategory:{ + enabled:boolean + categoryId:string + }, + /**Create tickets in this channel category when the original category is full (max 50 channels). */ + backupCategory:{ + enabled:boolean + categoryId:string + }, + /**Move claimed tickets to the matching channel category of the user that claimed the ticket. */ + claimedCategories:{ + /**The user who claimed the ticket. */ + user:string, + /**The category to move the ticket to. */ + category:string + }[], } -/**## ODJsonConfig_DefaultGeneralData `interface` - * All contents of the `general.json` config file. +/**## ODGeneralJsonConfig_GeneralData `interface` + * All contents of the `general.jsonc` config file. */ -export interface ODJsonConfig_DefaultGeneralData { +export interface ODGeneralJsonConfig_GeneralData { /**This object contains a few URLs and metadata for the config. */ - _INFO:ODJsonConfig_DefaultInfo, + _CONFIG_VERSION:string, /**The token of the bot. (Empty when using `tokenFromENV`) */ token:string, @@ -312,26 +270,27 @@ export interface ODJsonConfig_DefaultGeneralData { textCommands:boolean, /**All settings related to the status of the bot. */ - status:ODJsonConfig_DefaultStatusType, - + status:ODGeneralJsonConfig_Status, + /**All settings related to the ticket system. */ - system:ODJsonConfig_DefaultSystem + ticketSystem:ODGeneralJsonConfig_TicketSystem, + + /**Configure permissions for all Open Ticket commands & actions. */ + permissions:ODGeneralJsonConfig_SystemPermissions, + + /**All settings related to the log channel. */ + logs:ODGeneralJsonConfig_SystemLogs, } -/**## ODJsonConfig_DefaultGeneral `default_class` - * This is a special class that adds type definitions & typescript to the ODJsonConfig class. - * It doesn't add any extra features! - * - * This default class is made for the `general.json` config! - */ -export class ODJsonConfig_DefaultGeneral extends ODJsonConfig { - declare data: ODJsonConfig_DefaultGeneralData -} +/////////////////////////////////////// +// CONFIG STRUCTURES, VALUES & TYPES +// --> options.jsonc +/////////////////////////////////////// -/**## ODJsonConfig_DefaultOptionType `interface` - * This interface is an object which has all basic properties for options in the `options.json` config! +/**## ODOptionsJsonConfig_BaseOption `interface` + * The basic properties for options in the `options.jsonc` config! */ -export interface ODJsonConfig_DefaultOptionType { +export interface ODOptionsJsonConfig_BaseOption { /**The id of this option. */ id:string, /**The name of this option. */ @@ -339,7 +298,7 @@ export interface ODJsonConfig_DefaultOptionType { /**The description of this option. */ description:string, /**The type of this option. This type also determines the other option-specific variables. */ - type:"ticket"|"website"|"role", + type:"ticket"|"website"|"role"|"sub-panel", /**All settings related to the button for the 3 option types. */ button:{ /**The emoji of the button. (can also be empty) */ @@ -349,22 +308,22 @@ export interface ODJsonConfig_DefaultOptionType { } } -/**## ODJsonConfig_DefaultOptionButtonSettingsType `interface` - * This interface is an object which has all button settings for ticket & reaction role options in the `options.json` config! +/**## ODOptionsJsonConfig_OptionButtonSettings `interface` + * The button settings for ticket, sub-panel & reaction role options in the `options.jsonc` config! */ -export interface ODJsonConfig_DefaultOptionButtonSettingsType { +export interface ODOptionsJsonConfig_OptionButtonSettings { /**The emoji of the button. (can also be empty) */ emoji:string, /**The label of the button (can also be empty) */ label:string, /**The color of the button (not available in options with the 'website' type!) */ - color:ODValidButtonColor + color:api.ODValidButtonColor } -/**## ODJsonConfig_DefaultOptionEmbedSettingsType `interface` - * This interface is an object which has all message embed settings for ticket options in the `options.json` config! +/**## ODOptionsJsonConfig_TicketOptionEmbedSettings `interface` + * The message embed settings for ticket options in the `options.jsonc` config! */ -export interface ODJsonConfig_DefaultOptionEmbedSettingsType { +export interface ODOptionsJsonConfig_TicketOptionEmbedSettings { /**Is this embed enabled? */ enabled:boolean, /**The title of the embed. */ @@ -390,10 +349,10 @@ export interface ODJsonConfig_DefaultOptionEmbedSettingsType { timestamp:boolean } -/**## ODJsonConfig_DefaultOptionPingSettingsType `interface` - * This interface is an object which has all message ping settings for ticket options in the `options.json` config! +/**## ODOptionsJsonConfig_TicketOptionPingSettings `interface` + * The message ping settings for ticket options in the `options.jsonc` config! */ -export interface ODJsonConfig_DefaultOptionPingSettingsType { +export interface ODOptionsJsonConfig_TicketOptionPingSettings { /**Ping `@here`. */ "@here":boolean, /**Ping `@everyone`. */ @@ -402,47 +361,36 @@ export interface ODJsonConfig_DefaultOptionPingSettingsType { custom:string[] } -/**## ODJsonConfig_DefaultOptionTicketChannelType `interface` +/**## ODOptionsJsonConfig_TicketOptionChannelSettings `interface` * All settings related to the ticket channel itself in a ticket option. */ -export interface ODJsonConfig_DefaultOptionTicketChannelType { +export interface ODOptionsJsonConfig_TicketOptionChannelSettings { /**The prefix used in the name of this ticket channel. */ prefix:string, /**The type of suffix used in the name of this ticket channel. */ suffix:"user-name"|"user-nickname"|"user-id"|"random-number"|"random-hex"|"counter-dynamic"|"counter-fixed", /**An optional discord category id to create this ticket in. */ category:string, - /**An optional discord category id to move this ticket to when closed. */ - closedCategory:string, - /**An optional discord category id to create this ticket in when the primary one is full (max. 50 tickets). */ - backupCategory:string, - /**A list of discord category ids to move this ticket to when claimed by a specific user. */ - claimedCategory:{ - /**The user which claimed the ticket. */ - user:string, - /**The category to move the ticket to when claimed by this user. */ - category:string - }[], /**The channel topic shown at the top of the channel in discord. */ topic:string } -/**## ODJsonConfig_DefaultOptionTicketType `interface` - * This interface is an object which has all ticket properties for options in the `options.json` config! +/**## ODOptionsJsonConfig_TicketOption `interface` + * All properties for ticket options in the `options.jsonc` config! */ -export interface ODJsonConfig_DefaultOptionTicketType extends ODJsonConfig_DefaultOptionType { +export interface ODOptionsJsonConfig_TicketOption extends ODOptionsJsonConfig_BaseOption { type:"ticket", - button:ODJsonConfig_DefaultOptionButtonSettingsType, + button:ODOptionsJsonConfig_OptionButtonSettings, /**A list of discord role ids which are able to access this ticket type & use commands. */ ticketAdmins:string[], /**A list of discord role ids which are able to access this ticket type but can't write in the chat. */ readonlyAdmins:string[], /**When enabled, blacklisted users can still create this ticket type. (used for appeals, etc) */ allowCreationByBlacklistedUsers:boolean, - /**A list of valid question ids from the `questions.json` config. */ + /**A list of valid question ids from the `questions.jsonc` config. */ questions:string[], /**All settings related to the ticket channel itself. */ - channel:ODJsonConfig_DefaultOptionTicketChannelType, + channel:ODOptionsJsonConfig_TicketOptionChannelSettings, /**All settings related to the message sent in DM to the creator when the ticket is created. */ dmMessage:{ /**Enable this message. */ @@ -450,7 +398,7 @@ export interface ODJsonConfig_DefaultOptionTicketType extends ODJsonConfig_Defau /**The raw text contents of this message. (empty for embed only) */ text:string, /**The embed of this message. */ - embed:ODJsonConfig_DefaultOptionEmbedSettingsType + embed:ODOptionsJsonConfig_TicketOptionEmbedSettings }, /**All settings related to the message sent in the ticket channel when the ticket is created. */ ticketMessage:{ @@ -459,9 +407,9 @@ export interface ODJsonConfig_DefaultOptionTicketType extends ODJsonConfig_Defau /**The raw text contents of this message. (empty for embed only) */ text:string, /**The embed of this message. */ - embed:ODJsonConfig_DefaultOptionEmbedSettingsType, + embed:ODOptionsJsonConfig_TicketOptionEmbedSettings, /**Additional ping/mention settings for this ticket channel. */ - ping:ODJsonConfig_DefaultOptionPingSettingsType + ping:ODOptionsJsonConfig_TicketOptionPingSettings }, /**All settings related to autoclosing this ticket type. */ autoclose:{ @@ -510,21 +458,21 @@ export interface ODJsonConfig_DefaultOptionTicketType extends ODJsonConfig_Defau } } -/**## ODJsonConfig_DefaultOptionWebsiteType `interface` - * This interface is an object which has all website properties for options in the `options.json` config! +/**## ODOptionsJsonConfig_WebsiteOption `interface` + * All properties for website options in the `options.jsonc` config! */ -export interface ODJsonConfig_DefaultOptionWebsiteType extends ODJsonConfig_DefaultOptionType { +export interface ODOptionsJsonConfig_WebsiteOption extends ODOptionsJsonConfig_BaseOption { type:"website", /**The URL this button will point to. */ url:string } -/**## ODJsonConfig_DefaultOptionRoleType `interface` - * This interface is an object which has all reaction role properties for options in the `options.json` config! +/**## ODOptionsJsonConfig_RoleOption `interface` + * All properties for reaction-role options in the `options.jsonc` config! */ -export interface ODJsonConfig_DefaultOptionRoleType extends ODJsonConfig_DefaultOptionType { +export interface ODOptionsJsonConfig_RoleOption extends ODOptionsJsonConfig_BaseOption { type:"role", - button:ODJsonConfig_DefaultOptionButtonSettingsType, + button:ODOptionsJsonConfig_OptionButtonSettings, /**All roles which will be affected by this button. */ roles:string[], /**The mode determines what will happen with the affected roles on the user. */ @@ -535,25 +483,30 @@ export interface ODJsonConfig_DefaultOptionRoleType extends ODJsonConfig_Default addOnMemberJoin:boolean } -/**## ODJsonConfig_DefaultOptionsData `type` - * All contents of the `options.json` config file. +/**## ODOptionsJsonConfig_SubPanelOption `interface` + * All properties for sub-panel options in the `options.jsonc` config! */ -export type ODJsonConfig_DefaultOptionsData = (ODJsonConfig_DefaultOptionTicketType|ODJsonConfig_DefaultOptionWebsiteType|ODJsonConfig_DefaultOptionRoleType)[] - -/**## ODJsonConfig_DefaultOptions `default_class` - * This is a special class that adds type definitions & typescript to the ODJsonConfig class. - * It doesn't add any extra features! - * - * This default class is made for the `options.json` config! - */ -export class ODJsonConfig_DefaultOptions extends ODJsonConfig { - declare data: ODJsonConfig_DefaultOptionsData +export interface ODOptionsJsonConfig_SubPanelOption extends ODOptionsJsonConfig_BaseOption { + type:"sub-panel", + button:ODOptionsJsonConfig_OptionButtonSettings, + /**The panel ID of the sub-panel to show when the button is clicked. */ + subPanelId:string } -/**## ODJsonConfig_DefaultPanelEmbedSettingsType `interface` - * This interface is an object which has all message embed settings for panels in the `panels.json` config! +/**## ODOptionsJsonConfig_OptionsData `type` + * All contents of the `options.jsonc` config file. */ -export interface ODJsonConfig_DefaultPanelEmbedSettingsType { +export type ODOptionsJsonConfig_OptionsData = (ODOptionsJsonConfig_TicketOption|ODOptionsJsonConfig_WebsiteOption|ODOptionsJsonConfig_RoleOption|ODOptionsJsonConfig_SubPanelOption)[] + +/////////////////////////////////////// +// CONFIG STRUCTURES, VALUES & TYPES +// --> panels.jsonc +/////////////////////////////////////// + +/**## ODPanelsJsonConfig_PanelEmbedSettings `interface` + * This interface is an object which has all message embed settings for panels in the `panels.jsonc` config! + */ +export interface ODPanelsJsonConfig_PanelEmbedSettings { /**Is this embed enabled? */ enabled:boolean, /**The title of the embed. */ @@ -585,12 +538,14 @@ export interface ODJsonConfig_DefaultPanelEmbedSettingsType { timestamp:boolean } -/**## ODJsonConfig_DefaultPanelSettingsType `interface` +/**## ODPanelsJsonConfig_PanelSettings `interface` * This interface is a collection of additional settings for extra customisation in a panel. */ -export interface ODJsonConfig_DefaultPanelSettingsType { +export interface ODPanelsJsonConfig_PanelSettings { /**The placeholder used in the dropdown when enabled. */ dropdownPlaceholder:string, + /**The maximum amount of option buttons before starting a new row. */ + maximumButtonsPerRow:number /**Enable a max tickets warning in the text contents. */ enableMaxTicketsWarningInText:boolean, @@ -609,46 +564,41 @@ export interface ODJsonConfig_DefaultPanelSettingsType { describeOptionsInEmbedDescription:boolean } -/**## ODJsonConfig_DefaultPanelType `interface` - * This interface is an object which has all properties for panels in the `panels.json` config! +/**## ODPanelsJsonConfig_Panel `interface` + * This interface is an object which has all properties for panels in the `panels.jsonc` config! */ -export interface ODJsonConfig_DefaultPanelType { +export interface ODPanelsJsonConfig_Panel { /**The id of this panel. */ id:string, /**The name of this panel. */ name:string, /**When enabled, the panel uses a dropdown instead of buttons. */ dropdown:boolean, - /**A list of valid options ids from the `options.json` config. */ + /**A list of valid options ids from the `options.jsonc` config. */ options:string[], /**The raw text contents of this panel. (empty for embed only) */ text:string, /**The embed of this panel. */ - embed:ODJsonConfig_DefaultPanelEmbedSettingsType, + embed:ODPanelsJsonConfig_PanelEmbedSettings, /**A collection of additional settings for extra customisation in a panel. */ - settings:ODJsonConfig_DefaultPanelSettingsType + settings:ODPanelsJsonConfig_PanelSettings } -/**## ODJsonConfig_DefaultPanelsData `type` - * All contents of the `panels.json` config file. +/**## ODPanelsJsonConfig_PanelsData `type` + * All contents of the `panels.jsonc` config file. */ -export type ODJsonConfig_DefaultPanelsData = ODJsonConfig_DefaultPanelType[] +export type ODPanelsJsonConfig_PanelsData = ODPanelsJsonConfig_Panel[] -/**## ODJsonConfig_DefaultPanels `default_class` - * This is a special class that adds type definitions & typescript to the ODJsonConfig class. - * It doesn't add any extra features! - * - * This default class is made for the `panels.json` config! - */ -export class ODJsonConfig_DefaultPanels extends ODJsonConfig { - declare data: ODJsonConfig_DefaultPanelsData -} +/////////////////////////////////////// +// CONFIG STRUCTURES, VALUES & TYPES +// --> questions.jsonc +/////////////////////////////////////// -/**## ODJSonConfig_DefaultQuestionLengthSettings `interface` +/**## ODQuestionsJsonConfig_TextLengthLimits `interface` * This interface is a collection of settings related to length validation in a question. */ -export interface ODJSonConfig_DefaultQuestionLengthSettings { +export interface ODQuestionsJsonConfig_TextLengthLimits { /**Enable text length verification. */ enabled:boolean, /**The minimum text input length. */ @@ -657,63 +607,164 @@ export interface ODJSonConfig_DefaultQuestionLengthSettings { max:number } -/**## ODJsonConfig_DefaultShortQuestionType `interface` - * This interface is an object which has all properties for short questions in the `questions.json` config! +/**## ODQuestionsJsonConfig_CheckboxLimits `interface` + * The required amount of checkboxes validation in a question. */ -export interface ODJsonConfig_DefaultShortQuestionType { +export interface ODQuestionsJsonConfig_CheckboxLimits { + /**Enable checkbox limits. */ + enabled:boolean, + /**The minimum amount of selected checkboxes. */ + min:number, + /**The maximum amount of selected checkboxes. */ + max:number +} + +/**## ODQuestionsJsonConfig_FileUploadLimits `interface` + * Verify the amount of uploaded files in a question. + */ +export interface ODQuestionsJsonConfig_FileUploadLimits { + /**Enable file limits. */ + enabled:boolean, + /**The minimum amount of uploaded files. */ + min:number, + /**The maximum amount of uploaded files. */ + max:number +} + +/**## ODQuestionsJsonConfig_DropdownChoice `interface` + * A dropdown choice used in `ODQuestionsJsonConfig_DropdownQuestion` + */ +export interface ODQuestionsJsonConfig_DropdownChoice { + /**The title of the choice. */ + title:string, + /**The optional description of the choice. (Leave empty for none) */ + description:string, + /**The optional emoji of the choice. (Leave empty for none) */ + emoji:string +} + +/**## ODQuestionsJsonConfig_RadioCheckboxChoice `interface` + * A radio/checkbox choice used in `ODQuestionsJsonConfig_RadioSelectQuestion` & `ODQuestionsJsonConfig_CheckboxSelectQuestion` + */ +export interface ODQuestionsJsonConfig_RadioCheckboxChoice { + /**The title of the choice. */ + title:string, + /**The optional description of the choice. (Leave empty for none) */ + description:string, + /**Is this choice selected by default? */ + selectedByDefault:boolean +} + +/**## ODQuestionsJsonConfig_BaseQuestion `interface` + * This interface is an object which has all universal properties for questions in the `questions.jsonc` config! + */ +export interface ODQuestionsJsonConfig_BaseQuestion { /**The id of this question. */ id:string, /**The name of this question. */ name:string, + /**The description of this question. (Leave empty for none) */ + description:string, /**The type of this question. */ + type:"short"|"paragraph"|"text-display"|"dropdown"|"radio-select"|"checkbox-select"|"file-upload", + /**Is this question required? */ + required:boolean, +} + +/**## ODQuestionsJsonConfig_TextDisplayQuestion `interface` + * All properties for a text-display in the `questions.jsonc` config! + */ +export interface ODQuestionsJsonConfig_TextDisplayQuestion extends Omit { + /**The type of this question. */ + type:"text-display", + /**The text contents to show in the modal. */ + textContents:string +} + +/**## ODQuestionsJsonConfig_ShortQuestion `interface` + * All properties for short questions in the `questions.jsonc` config! + */ +export interface ODQuestionsJsonConfig_ShortQuestion extends ODQuestionsJsonConfig_BaseQuestion { type:"short", - - /**Is this question required? */ - required:boolean, /**A placeholder for the question. */ placeholder:string, /**A collection of settings related to length validation in a question. */ - length:ODJSonConfig_DefaultQuestionLengthSettings + length:ODQuestionsJsonConfig_TextLengthLimits } -/**## ODJsonConfig_DefaultParagraphQuestionType `interface` - * This interface is an object which has all properties for paragraph questions in the `questions.json` config! +/**## ODQuestionsJsonConfig_ParagraphQuestion `interface` + * All properties for paragraph questions in the `questions.jsonc` config! */ -export interface ODJsonConfig_DefaultParagraphQuestionType { - /**The id of this question. */ - id:string, - /**The name of this question. */ - name:string, - /**The type of this question. */ +export interface ODQuestionsJsonConfig_ParagraphQuestion extends ODQuestionsJsonConfig_BaseQuestion { type:"paragraph", - - /**Is this question required? */ - required:boolean, /**A placeholder for the question. */ placeholder:string, /**A collection of settings related to length validation in a question. */ - length:ODJSonConfig_DefaultQuestionLengthSettings + length:ODQuestionsJsonConfig_TextLengthLimits } -/**## ODJsonConfig_DefaultQuestionsData `type` - * All contents of the `questions.json` config file. +/**## ODQuestionsJsonConfig_DropdownQuestion `interface` + * All properties for dropdown questions in the `questions.jsonc` config! */ -export type ODJsonConfig_DefaultQuestionsData = (ODJsonConfig_DefaultShortQuestionType|ODJsonConfig_DefaultParagraphQuestionType)[] - -/**## ODJsonConfig_DefaultQuestions `default_class` - * This is a special class that adds type definitions & typescript to the ODJsonConfig class. - * It doesn't add any extra features! - * - * This default class is made for the `questions.json` config! - */ -export class ODJsonConfig_DefaultQuestions extends ODJsonConfig { - declare data: ODJsonConfig_DefaultQuestionsData +export interface ODQuestionsJsonConfig_DropdownQuestion extends ODQuestionsJsonConfig_BaseQuestion { + type:"dropdown", + /**A placeholder for the dropdown. */ + placeholder:string, + /**A list of maximum 25 dropdown choices. */ + choices:ODQuestionsJsonConfig_DropdownChoice[] } -/**## ODJsonConfig_DefaultTranscriptsTextLayout `interface` +/**## ODQuestionsJsonConfig_RadioSelectQuestion `interface` + * All properties for radio select questions in the `questions.jsonc` config! + */ +export interface ODQuestionsJsonConfig_RadioSelectQuestion extends ODQuestionsJsonConfig_BaseQuestion { + type:"radio-select", + /**A list of minimum 2, maximum 10 radio choices. */ + choices:ODQuestionsJsonConfig_RadioCheckboxChoice[] +} + +/**## ODQuestionsJsonConfig_CheckboxSelectQuestion `interface` + * All properties for checkbox select questions in the `questions.jsonc` config! + */ +export interface ODQuestionsJsonConfig_CheckboxSelectQuestion extends ODQuestionsJsonConfig_BaseQuestion { + type:"checkbox-select", + /**Verify the checked amount of checkboxes with a minimum & maximum. */ + limits:ODQuestionsJsonConfig_CheckboxLimits + /**A list of minimum 1, maximum 10 checkbox choices. */ + choices:ODQuestionsJsonConfig_RadioCheckboxChoice[] +} + +/**## ODQuestionsJsonConfig_FileUploadQuestion `interface` + * All properties for checkbox select questions in the `questions.jsonc` config! + */ +export interface ODQuestionsJsonConfig_FileUploadQuestion extends ODQuestionsJsonConfig_BaseQuestion { + type:"file-upload", + /**Verify the amount of uploaded files (minimum/maximum). */ + limits:ODQuestionsJsonConfig_FileUploadLimits +} + +/**## ODQuestionsJsonConfig_QuestionsData `type` + * All contents of the `questions.jsonc` config file. + */ +export type ODQuestionsJsonConfig_QuestionsData = ( + ODQuestionsJsonConfig_ShortQuestion| + ODQuestionsJsonConfig_ParagraphQuestion| + ODQuestionsJsonConfig_TextDisplayQuestion| + ODQuestionsJsonConfig_DropdownQuestion| + ODQuestionsJsonConfig_RadioSelectQuestion| + ODQuestionsJsonConfig_CheckboxSelectQuestion| + ODQuestionsJsonConfig_FileUploadQuestion +)[] + +/////////////////////////////////////// +// CONFIG STRUCTURES, VALUES & TYPES +// --> transcripts.jsonc +/////////////////////////////////////// + +/**## ODTranscriptsJsonConfig_TranscriptsTextLayout `interface` * This interface contains the layout of the text transcripts. */ -export interface ODJsonConfig_DefaultTranscriptsTextLayout { +export interface ODTranscriptsJsonConfig_TranscriptsTextLayout { /**The layout/complexity of the text transcripts. */ layout:"simple"|"normal"|"detailed", /**Include stats in the transcript. */ @@ -733,10 +784,10 @@ export interface ODJsonConfig_DefaultTranscriptsTextLayout { customFileName:string } -/**## ODJsonConfig_DefaultTranscriptsHtmlLayout `interface` +/**## ODTranscriptsJsonConfig_TranscriptsHtmlLayout `interface` * This interface contains the layout of the HTML transcripts. */ -export interface ODJsonConfig_DefaultTranscriptsHtmlLayout { +export interface ODTranscriptsJsonConfig_TranscriptsHtmlLayout { /**Settings related to the background. */ background:{ /**Enable a custom background. */ @@ -781,10 +832,10 @@ export interface ODJsonConfig_DefaultTranscriptsHtmlLayout { } } -/**## ODJsonConfig_DefaultTranscriptsData `interface` - * All contents of the `transcripts.json` config file. +/**## ODTranscriptsJsonConfig_TranscriptsData `interface` + * All contents of the `transcripts.jsonc` config file. */ -export interface ODJsonConfig_DefaultTranscriptsData { +export interface ODTranscriptsJsonConfig_TranscriptsData { /**All general settings related to transcripts. */ general:{ /**Are transcripts enabled? */ @@ -816,18 +867,41 @@ export interface ODJsonConfig_DefaultTranscriptsData { includeTicketStats:boolean }, /**The layout of the text transcripts. */ - textTranscriptStyle:ODJsonConfig_DefaultTranscriptsTextLayout, + textTranscriptStyle:ODTranscriptsJsonConfig_TranscriptsTextLayout, /**The layout of the HTML transcripts. */ - htmlTranscriptStyle:ODJsonConfig_DefaultTranscriptsHtmlLayout + htmlTranscriptStyle:ODTranscriptsJsonConfig_TranscriptsHtmlLayout } +///////////////////////////// +////// MAPPED MANAGERS ////// +///////////////////////////// -/**## ODJsonConfig_DefaultTranscripts `default_class` - * This is a special class that adds type definitions & typescript to the ODJsonConfig class. - * It doesn't add any extra features! - * - * This default class is made for the `transcripts.json` config! +/**## ODMappedConfigManager `class + * A special class with types for the Open Ticket `ODConfigManager` class. */ -export class ODJsonConfig_DefaultTranscripts extends ODJsonConfig { - declare data: ODJsonConfig_DefaultTranscriptsData -} \ No newline at end of file +export class ODMappedConfigManager extends api.ODConfigManager {} + +/**## ODGeneralJsonCommentsConfig `class + * A special class with types for the Open Ticket `config/general.jsonc` config file + */ +export class ODGeneralJsonCommentsConfig extends api.ODJsonCommentsConfig {} + +/**## ODQuestionsJsonCommentsConfig `class + * A special class with types for the Open Ticket `config/questions.jsonc` config file + */ +export class ODQuestionsJsonCommentsConfig extends api.ODJsonCommentsConfig {} + +/**## ODOptionsJsonCommentsConfig `class + * A special class with types for the Open Ticket `config/options.jsonc` config file + */ +export class ODOptionsJsonCommentsConfig extends api.ODJsonCommentsConfig {} + +/**## ODPanelsJsonCommentsConfig `class + * A special class with types for the Open Ticket `config/panels.jsonc` config file + */ +export class ODPanelsJsonCommentsConfig extends api.ODJsonCommentsConfig {} + +/**## ODTranscriptsJsonCommentsConfig `class + * A special class with types for the Open Ticket `config/transcripts.jsonc` config file + */ +export class ODTranscriptsJsonCommentsConfig extends api.ODJsonCommentsConfig {} \ No newline at end of file diff --git a/src/core/mappings/console.ts b/src/core/mappings/console.ts new file mode 100644 index 0000000..362d0bb --- /dev/null +++ b/src/core/mappings/console.ts @@ -0,0 +1,21 @@ +/////////////////////////////////////// +//OPEN TICKET CONSOLE MAPPINGS +/////////////////////////////////////// +import * as api from "@open-discord-bots/framework/api" + +/**## ODLiveStatusManagerIdMappings `interface` + * A list of all available IDs in the default `ODLiveStatusManager` class in `opendiscord`. + * It's used to generate typescript declarations for this class. + */ +export interface ODLiveStatusManagerIdMappings extends api.ODLiveStatusManagerIdConstraint { + "opendiscord:default-djdj-dev":api.ODLiveStatusUrlSource +} + +///////////////////////////// +////// MAPPED MANAGERS ////// +///////////////////////////// + +/**## ODMappedLiveStatusManager `class + * A special class with types for the Open Ticket `ODLiveStatusManager` class. + */ +export class ODMappedLiveStatusManager extends api.ODLiveStatusManager {} \ No newline at end of file diff --git a/src/core/mappings/cooldown.ts b/src/core/mappings/cooldown.ts new file mode 100644 index 0000000..49cc921 --- /dev/null +++ b/src/core/mappings/cooldown.ts @@ -0,0 +1,21 @@ +/////////////////////////////////////// +//OPEN TICKET COOLDOWN MAPPINGS +/////////////////////////////////////// +import * as api from "@open-discord-bots/framework/api" + +/**## ODCooldownManagerIdMappings `interface` + * A list of all available IDs in the default `ODCooldownManager` class in `opendiscord`. + * It's used to generate typescript declarations for this class. + */ +export interface ODCooldownManagerIdMappings extends api.ODCooldownManagerIdConstraint { + //"opendiscord:cooldown":api.ODCooldown +} + +///////////////////////////// +////// MAPPED MANAGERS ////// +///////////////////////////// + +/**## ODMappedCooldownManager `class + * A special class with types for the Open Ticket `ODCooldownManager` class. + */ +export class ODMappedCooldownManager extends api.ODCooldownManager {} \ No newline at end of file diff --git a/src/core/mappings/database.ts b/src/core/mappings/database.ts new file mode 100644 index 0000000..026a794 --- /dev/null +++ b/src/core/mappings/database.ts @@ -0,0 +1,114 @@ +/////////////////////////////////////// +//OPEN TICKET DATABASE MAPPINGS +/////////////////////////////////////// +import * as api from "@open-discord-bots/framework/api" +import { ODTicketJson } from "../api/ticket.js" +import { ODOptionJson } from "../api/option.js" +import { ODTranscriptHistoryData } from "../api/transcript.js" + +/**## ODDatabaseManagerIdMappings `interface` + * A list of all available IDs in the default `ODDatabaseManager` class in `opendiscord`. + * It's used to generate typescript declarations for this class. + */ +export interface ODDatabaseManagerIdMappings extends api.ODDatabaseManagerIdConstraint { + "opendiscord:global":ODGlobalDatabase, + "opendiscord:stats":ODStatsDatabase, + "opendiscord:tickets":ODTicketsDatabase, + "opendiscord:users":ODUsersDatabase, + "opendiscord:options":ODOptionsDatabase, + "opendiscord:transcripts":ODTranscriptsDatabase, + "opendiscord:message-states":ODMessageStatesDatabase, +} + +///////////////////////////////////////// +// DATABASE MAPPINGS, CATEGORIES & TYPES +///////////////////////////////////////// + +/**## ODGlobalDatabaseIdMappings `interface` + * A list of all available IDs in the default `ODGlobalDatabase` class in `opendiscord`. + * It's used to generate typescript declarations for this class. + */ +export interface ODGlobalDatabaseIdMappings extends api.ODDatabaseIdConstraint { + "opendiscord:panel-message":string, + "opendiscord:panel-update":string, + "opendiscord:option-suffix-counter":number, + "opendiscord:option-suffix-history":string[], + "opendiscord:last-version":string +} + +/**## ODTicketsDatabaseIdMappings `interface` + * A list of all available IDs in the default `ODTicketsDatabase` class in `opendiscord`. + * It's used to generate typescript declarations for this class. + */ +export interface ODTicketsDatabaseIdMappings extends api.ODDatabaseIdConstraint { + "opendiscord:ticket":ODTicketJson +} + +/**## ODUsersDatabaseIdMappings `interface` + * A list of all available IDs in the default `ODUsersDatabase` class in `opendiscord`. + * It's used to generate typescript declarations for this class. + */ +export interface ODUsersDatabaseIdMappings extends api.ODDatabaseIdConstraint { + "opendiscord:blacklist":ODTicketJson +} + +/**## ODOptionsDatabaseIdMappings `interface` + * A list of all available IDs in the default `ODOptionsDatabase` class in `opendiscord`. + * It's used to generate typescript declarations for this class. + */ +export interface ODOptionsDatabaseIdMappings extends api.ODDatabaseIdConstraint { + "opendiscord:used-option":ODOptionJson +} + +/**## ODTranscriptsDatabaseIdMappings `interface` + * A list of all available IDs in the default `ODTranscriptsDatabase` class in `opendiscord`. + * It's used to generate typescript declarations for this class. + */ +export interface ODTranscriptsDatabaseIdMappings extends api.ODDatabaseIdConstraint { + "opendiscord:transcript":ODTranscriptHistoryData, +} + + +///////////////////////////// +////// MAPPED MANAGERS ////// +///////////////////////////// + +/**## ODMappedDatabaseManager `class + * A special class with types for the Open Ticket `ODDatabaseManager` class. + */ +export class ODMappedDatabaseManager extends api.ODDatabaseManager {} + +/**## ODGlobalDatabase `class + * A special class with types for the Open Ticket `database/global.json` database file + */ +export class ODGlobalDatabase extends api.ODFormattedJsonDatabase {} + +/**## ODStatsDatabase `class + * A special class with types for the Open Ticket `database/stats.json` database file + */ +export class ODStatsDatabase extends api.ODFormattedJsonDatabase {} + +/**## ODTicketsDatabase `class + * A special class with types for the Open Ticket `database/tickets.json` database file + */ +export class ODTicketsDatabase extends api.ODFormattedJsonDatabase {} + +/**## ODUsersDatabase `class + * A special class with types for the Open Ticket `database/users.json` database file + */ +export class ODUsersDatabase extends api.ODFormattedJsonDatabase {} + +/**## ODOptionsDatabase `class + * A special class with types for the Open Ticket `database/options.json` database file + */ +export class ODOptionsDatabase extends api.ODFormattedJsonDatabase {} + +/**## ODTranscriptsDatabase `class + * A special class with types for the Open Ticket `database/transcripts.json` database file + */ +export class ODTranscriptsDatabase extends api.ODFormattedJsonDatabase {} + +/**## ODMessageStatesDatabase `class + * A special class with types for the Open Ticket `database/states.json` database file + */ +export class ODMessageStatesDatabase extends api.ODFormattedJsonDatabase {} \ No newline at end of file diff --git a/src/core/mappings/event.ts b/src/core/mappings/event.ts new file mode 100644 index 0000000..a59c6e8 --- /dev/null +++ b/src/core/mappings/event.ts @@ -0,0 +1,361 @@ +/////////////////////////////////////// +//OPEN TICKET EVENT MAPPINGS +/////////////////////////////////////// + +//BASE MAPPINGSS +import * as api from "@open-discord-bots/framework/api" +import * as discord from "discord.js" + +//OPEN TICKET MAPPINGS +import { ODMappedPluginClassManager, ODMappedPluginManager } from "./plugin.js" +import { ODMappedConfigManager} from "./config.js" +import { ODMappedDatabaseManager } from "./database.js" +import { ODMappedFlagManager } from "./flag.js" +import { ODMappedSessionManager } from "./session.js" +import { ODMappedLanguageManager } from "./language.js" +import { ODMappedCheckerFunctionManager, ODMappedCheckerManager, ODMappedCheckerTranslationRegister } from "./checker.js" +import { ODMappedClientManager, ODMappedContextMenuManager, ODMappedSlashCommandManager, ODMappedTextCommandManager } from "./client.js" +import { ODMappedBuilderManager, ODMappedButtonManager, ODMappedDropdownManager, ODMappedEmbedManager, ODMappedFileManager, ODMappedMessageManager, ODMappedModalManager } from "./builder.js" +import { ODMappedAutocompleteResponderManager, ODMappedButtonResponderManager, ODMappedCommandResponderManager, ODMappedContextMenuResponderManager, ODMappedDropdownResponderManager, ODMappedModalResponderManager, ODMappedResponderManager } from "./responder.js" +import { ODMappedActionManager } from "./action.js" +import { ODMappedPermissionManager } from "./permission.js" +import { ODMappedHelpMenuManager } from "./helpmenu.js" +import { ODMappedStatisticManager } from "./statistic.js" +import { ODMappedTaskManager } from "./task.js" +import { ODMappedCooldownManager } from "./cooldown.js" +import { ODMappedPostManager } from "./post.js" +import { ODMappedVerifyBarManager } from "./verifybar.js" +import { ODMappedStartScreenManager } from "./startscreen.js" +import { ODMappedLiveStatusManager } from "./console.js" +import { ODMappedProgressBarManager, ODMappedProgressBarRendererManager } from "./progressbar.js" +import { ODMappedComponentManager, ODMappedComponentModifierManager, ODMappedMessageComponentManager, ODMappedModalComponentManager, ODMappedSharedComponentManager } from "./component.js" +import { ODMappedStateManager } from "./state.js" + +//OPEN TICKET MAPPINGSS +import { ODOptionManager, ODTicketOption } from "../api/option.js" +import { ODPanel, ODPanelManager } from "../api/panel.js" +import { ODTicket, ODTicketClearFilter, ODTicketManager } from "../api/ticket.js" +import { ODQuestionManager } from "../api/question.js" +import { ODBlacklistManager } from "../api/blacklist.js" +import { ODMappedTranscriptManager } from "../api/transcript.js" +import { ODRole, ODRoleManager } from "../api/role.js" +import { ODMappedPriorityManager, ODPriorityLevel } from "../api/priority.js" + +/**## ODEventManagerIdMappings `interface` + * A list of all available IDs in the default `ODEventManager` class in `opendiscord`. + * It's used to generate typescript declarations for this class. + */ +export interface ODEventManagerIdMappings extends api.ODEventManagerIdConstraint { + //error handling + "onErrorHandling": api.ODEvent<(error:Error, origin:NodeJS.UncaughtExceptionOrigin) => api.ODPromiseVoid> + "afterErrorHandling": api.ODEvent<(error:Error, origin:NodeJS.UncaughtExceptionOrigin, message:api.ODError) => api.ODPromiseVoid> + + //plugins + "afterPluginsLoaded": api.ODEvent<(plugins:ODMappedPluginManager) => api.ODPromiseVoid> + "onPluginClassLoad": api.ODEvent<(classes:ODMappedPluginClassManager, plugins:ODMappedPluginManager) => api.ODPromiseVoid> + "afterPluginClassesLoaded": api.ODEvent<(classes:ODMappedPluginClassManager, plugins:ODMappedPluginManager) => api.ODPromiseVoid> + + //flags + "onFlagLoad": api.ODEvent<(flags:ODMappedFlagManager) => api.ODPromiseVoid> + "afterFlagsLoaded": api.ODEvent<(flags:ODMappedFlagManager) => api.ODPromiseVoid> + "onFlagInit": api.ODEvent<(flags:ODMappedFlagManager) => api.ODPromiseVoid> + "afterFlagsInitiated": api.ODEvent<(flags:ODMappedFlagManager) => api.ODPromiseVoid> + + //progress bars + "onProgressBarRendererLoad": api.ODEvent<(renderers:ODMappedProgressBarRendererManager) => api.ODPromiseVoid> + "afterProgressBarRenderersLoaded": api.ODEvent<(renderers:ODMappedProgressBarRendererManager) => api.ODPromiseVoid> + "onProgressBarLoad": api.ODEvent<(progressbars:ODMappedProgressBarManager) => api.ODPromiseVoid> + "afterProgressBarsLoaded": api.ODEvent<(progressbars:ODMappedProgressBarManager) => api.ODPromiseVoid> + + //configs + "onConfigLoad": api.ODEvent<(configs:ODMappedConfigManager) => api.ODPromiseVoid> + "afterConfigsLoaded": api.ODEvent<(configs:ODMappedConfigManager) => api.ODPromiseVoid> + "onConfigInit": api.ODEvent<(configs:ODMappedConfigManager) => api.ODPromiseVoid> + "afterConfigsInitiated": api.ODEvent<(configs:ODMappedConfigManager) => api.ODPromiseVoid> + + //databases + "onDatabaseLoad": api.ODEvent<(databases:ODMappedDatabaseManager) => api.ODPromiseVoid> + "afterDatabasesLoaded": api.ODEvent<(databases:ODMappedDatabaseManager) => api.ODPromiseVoid> + "onDatabaseInit": api.ODEvent<(databases:ODMappedDatabaseManager) => api.ODPromiseVoid> + "afterDatabasesInitiated": api.ODEvent<(databases:ODMappedDatabaseManager) => api.ODPromiseVoid> + + //languages + "onLanguageLoad": api.ODEvent<(languages:ODMappedLanguageManager) => api.ODPromiseVoid> + "afterLanguagesLoaded": api.ODEvent<(languages:ODMappedLanguageManager) => api.ODPromiseVoid> + "onLanguageInit": api.ODEvent<(languages:ODMappedLanguageManager) => api.ODPromiseVoid> + "afterLanguagesInitiated": api.ODEvent<(languages:ODMappedLanguageManager) => api.ODPromiseVoid> + "onLanguageSelect": api.ODEvent<(languages:ODMappedLanguageManager) => api.ODPromiseVoid> + "afterLanguagesSelected": api.ODEvent<(main:api.ODLanguage|null, backup:api.ODLanguage|null, languages:ODMappedLanguageManager) => api.ODPromiseVoid> + + //sessions + "onSessionLoad": api.ODEvent<(languages:ODMappedSessionManager) => api.ODPromiseVoid> + "afterSessionsLoaded": api.ODEvent<(languages:ODMappedSessionManager) => api.ODPromiseVoid> + + //config checkers + "onCheckerLoad": api.ODEvent<(checkers:ODMappedCheckerManager) => api.ODPromiseVoid> + "afterCheckersLoaded": api.ODEvent<(checkers:ODMappedCheckerManager) => api.ODPromiseVoid> + "onCheckerFunctionLoad": api.ODEvent<(functions:ODMappedCheckerFunctionManager, checkers:ODMappedCheckerManager) => api.ODPromiseVoid> + "afterCheckerFunctionsLoaded": api.ODEvent<(functions:ODMappedCheckerFunctionManager, checkers:ODMappedCheckerManager) => api.ODPromiseVoid> + "onCheckerExecute": api.ODEvent<(checkers:ODMappedCheckerManager) => api.ODPromiseVoid> + "afterCheckersExecuted": api.ODEvent<(result:api.ODCheckerResult, checkers:ODMappedCheckerManager) => api.ODPromiseVoid> + "onCheckerTranslationLoad": api.ODEvent<(translations:ODMappedCheckerTranslationRegister, enabled:boolean, checkers:ODMappedCheckerManager) => api.ODPromiseVoid> + "afterCheckerTranslationsLoaded": api.ODEvent<(translations:ODMappedCheckerTranslationRegister, checkers:ODMappedCheckerManager) => api.ODPromiseVoid> + "onCheckerRender": api.ODEvent<(renderer:api.ODCheckerRenderer, checkers:ODMappedCheckerManager) => api.ODPromiseVoid> + "afterCheckersRendered": api.ODEvent<(renderer:api.ODCheckerRenderer, checkers:ODMappedCheckerManager) => api.ODPromiseVoid> + "onCheckerQuit": api.ODEvent<(checkers:ODMappedCheckerManager) => api.ODPromiseVoid> + + //plugin loading before client + "onPluginBeforeClientLoad": api.ODEvent<() => api.ODPromiseVoid>, + "afterPluginBeforeClientLoaded": api.ODEvent<() => api.ODPromiseVoid>, + + //client configuration + "onClientLoad": api.ODEvent<(client:ODMappedClientManager) => api.ODPromiseVoid> + "afterClientLoaded": api.ODEvent<(client:ODMappedClientManager) => api.ODPromiseVoid> + "onClientInit": api.ODEvent<(client:ODMappedClientManager) => api.ODPromiseVoid> + "afterClientInitiated": api.ODEvent<(client:ODMappedClientManager) => api.ODPromiseVoid> + "onClientReady": api.ODEvent<(client:ODMappedClientManager) => api.ODPromiseVoid> + "afterClientReady": api.ODEvent<(client:ODMappedClientManager) => api.ODPromiseVoid> + "onClientActivityLoad": api.ODEvent<(activity:api.ODClientActivityManager, client:ODMappedClientManager) => api.ODPromiseVoid> + "afterClientActivityLoaded": api.ODEvent<(activity:api.ODClientActivityManager, client:ODMappedClientManager) => api.ODPromiseVoid> + "onClientActivityInit": api.ODEvent<(activity:api.ODClientActivityManager, client:ODMappedClientManager) => api.ODPromiseVoid> + "afterClientActivityInitiated": api.ODEvent<(activity:api.ODClientActivityManager, client:ODMappedClientManager) => api.ODPromiseVoid> + + //priority levels + "onPriorityLoad": api.ODEvent<(priorities:ODMappedPriorityManager) => api.ODPromiseVoid> + "afterPrioritiesLoaded": api.ODEvent<(priorities:ODMappedPriorityManager) => api.ODPromiseVoid> + + //client slash commands + "onSlashCommandLoad": api.ODEvent<(slash:ODMappedSlashCommandManager, client:ODMappedClientManager) => api.ODPromiseVoid> + "afterSlashCommandsLoaded": api.ODEvent<(slash:ODMappedSlashCommandManager, client:ODMappedClientManager) => api.ODPromiseVoid> + "onSlashCommandRegister": api.ODEvent<(slash:ODMappedSlashCommandManager, client:ODMappedClientManager) => api.ODPromiseVoid> + "afterSlashCommandsRegistered": api.ODEvent<(slash:ODMappedSlashCommandManager, client:ODMappedClientManager) => api.ODPromiseVoid> + + //client context menus + "onContextMenuLoad": api.ODEvent<(menu:ODMappedContextMenuManager, client:ODMappedClientManager) => api.ODPromiseVoid> + "afterContextMenusLoaded": api.ODEvent<(menu:ODMappedContextMenuManager, client:ODMappedClientManager) => api.ODPromiseVoid> + "onContextMenuRegister": api.ODEvent<(menu:ODMappedContextMenuManager, client:ODMappedClientManager) => api.ODPromiseVoid> + "afterContextMenusRegistered": api.ODEvent<(menu:ODMappedContextMenuManager, client:ODMappedClientManager) => api.ODPromiseVoid> + + //client text commands + "onTextCommandLoad": api.ODEvent<(text:ODMappedTextCommandManager, client:ODMappedClientManager) => api.ODPromiseVoid> + "afterTextCommandsLoaded": api.ODEvent<(text:ODMappedTextCommandManager, client:ODMappedClientManager) => api.ODPromiseVoid> + + //states + "onStateLoad": api.ODEvent<(posts:ODMappedStateManager) => api.ODPromiseVoid> + "afterStatesLoaded": api.ODEvent<(posts:ODMappedStateManager) => api.ODPromiseVoid> + "onStateInit": api.ODEvent<(posts:ODMappedStateManager) => api.ODPromiseVoid> + "afterStatesInitiated": api.ODEvent<(posts:ODMappedStateManager) => api.ODPromiseVoid> + + //plugin loading before managers + "onPluginBeforeManagerLoad": api.ODEvent<() => api.ODPromiseVoid>, + "afterPluginBeforeManagerLoaded": api.ODEvent<() => api.ODPromiseVoid>, + + //questions + "onQuestionLoad": api.ODEvent<(questions:ODQuestionManager) => api.ODPromiseVoid> + "afterQuestionsLoaded": api.ODEvent<(questions:ODQuestionManager) => api.ODPromiseVoid> + + //options + "onOptionLoad": api.ODEvent<(options:ODOptionManager) => api.ODPromiseVoid> + "afterOptionsLoaded": api.ODEvent<(options:ODOptionManager) => api.ODPromiseVoid> + + //panels + "onPanelLoad": api.ODEvent<(panels:ODPanelManager) => api.ODPromiseVoid> + "afterPanelsLoaded": api.ODEvent<(panels:ODPanelManager) => api.ODPromiseVoid> + "onPanelSpawn": api.ODEvent<(panel:ODPanel) => api.ODPromiseVoid> + "afterPanelSpawned": api.ODEvent<(panel:ODPanel) => api.ODPromiseVoid> + + //tickets + "onTicketLoad": api.ODEvent<(tickets:ODTicketManager) => api.ODPromiseVoid> + "afterTicketsLoaded": api.ODEvent<(tickets:ODTicketManager) => api.ODPromiseVoid> + + //ticket creation + "onTicketChannelCreation": api.ODEvent<(option:ODTicketOption, user:discord.User) => api.ODPromiseVoid> + "afterTicketChannelCreated": api.ODEvent<(option:ODTicketOption, channel:discord.GuildTextBasedChannel, user:discord.User) => api.ODPromiseVoid> + "onTicketChannelDeletion": api.ODEvent<(ticket:ODTicket, channel:discord.GuildTextBasedChannel, user:discord.User) => api.ODPromiseVoid> + "afterTicketChannelDeleted": api.ODEvent<(ticket:ODTicket, user:discord.User) => api.ODPromiseVoid> + "onTicketPermissionsCreated": api.ODEvent<(option:ODTicketOption, permissions:ODMappedPermissionManager, channel:discord.GuildTextBasedChannel, user:discord.User) => api.ODPromiseVoid> + "afterTicketPermissionsCreated": api.ODEvent<(option:ODTicketOption, permissions:ODMappedPermissionManager, channel:discord.GuildTextBasedChannel, user:discord.User) => api.ODPromiseVoid> + "onTicketMainMessageCreated": api.ODEvent<(ticket:ODTicket, channel:discord.GuildTextBasedChannel, user:discord.User) => api.ODPromiseVoid> + "afterTicketMainMessageCreated": api.ODEvent<(ticket:ODTicket, message:discord.Message, channel:discord.GuildTextBasedChannel, user:discord.User) => api.ODPromiseVoid> + + //ticket actions + "onTicketCreate": api.ODEvent<(creator:discord.User) => api.ODPromiseVoid> + "afterTicketCreated": api.ODEvent<(ticket:ODTicket, creator:discord.User, channel:discord.GuildTextBasedChannel) => api.ODPromiseVoid> + "onTicketClose": api.ODEvent<(ticket:ODTicket, closer:discord.User, channel:discord.GuildTextBasedChannel, reason:string|null) => api.ODPromiseVoid> + "afterTicketClosed": api.ODEvent<(ticket:ODTicket, closer:discord.User, channel:discord.GuildTextBasedChannel, reason:string|null) => api.ODPromiseVoid> + "onTicketReopen": api.ODEvent<(ticket:ODTicket, reopener:discord.User, channel:discord.GuildTextBasedChannel, reason:string|null) => api.ODPromiseVoid> + "afterTicketReopened": api.ODEvent<(ticket:ODTicket, reopener:discord.User, channel:discord.GuildTextBasedChannel, reason:string|null) => api.ODPromiseVoid> + "onTicketDelete": api.ODEvent<(ticket:ODTicket, deleter:discord.User, channel:discord.GuildTextBasedChannel, reason:string|null) => api.ODPromiseVoid> + "afterTicketDeleted": api.ODEvent<(ticket:ODTicket, deleter:discord.User, reason:string|null) => api.ODPromiseVoid> + "onTicketMove": api.ODEvent<(ticket:ODTicket, mover:discord.User, channel:discord.GuildTextBasedChannel, reason:string|null) => api.ODPromiseVoid> + "afterTicketMoved": api.ODEvent<(ticket:ODTicket, mover:discord.User, channel:discord.GuildTextBasedChannel, reason:string|null) => api.ODPromiseVoid> + "onTicketClaim": api.ODEvent<(ticket:ODTicket, claimer:discord.User, channel:discord.GuildTextBasedChannel, reason:string|null) => api.ODPromiseVoid> + "afterTicketClaimed": api.ODEvent<(ticket:ODTicket, claimer:discord.User, channel:discord.GuildTextBasedChannel, reason:string|null) => api.ODPromiseVoid> + "onTicketUnclaim": api.ODEvent<(ticket:ODTicket, unclaimer:discord.User, channel:discord.GuildTextBasedChannel, reason:string|null) => api.ODPromiseVoid> + "afterTicketUnclaimed": api.ODEvent<(ticket:ODTicket, unclaimer:discord.User, channel:discord.GuildTextBasedChannel, reason:string|null) => api.ODPromiseVoid> + "onTicketPin": api.ODEvent<(ticket:ODTicket, pinner:discord.User, channel:discord.GuildTextBasedChannel, reason:string|null) => api.ODPromiseVoid> + "afterTicketPinned": api.ODEvent<(ticket:ODTicket, pinner:discord.User, channel:discord.GuildTextBasedChannel, reason:string|null) => api.ODPromiseVoid> + "onTicketUnpin": api.ODEvent<(ticket:ODTicket, unpinner:discord.User, channel:discord.GuildTextBasedChannel, reason:string|null) => api.ODPromiseVoid> + "afterTicketUnpinned": api.ODEvent<(ticket:ODTicket, unpinner:discord.User, channel:discord.GuildTextBasedChannel, reason:string|null) => api.ODPromiseVoid> + "onTicketUserAdd": api.ODEvent<(ticket:ODTicket, adder:discord.User, user:discord.User, channel:discord.GuildTextBasedChannel, reason:string|null) => api.ODPromiseVoid> + "afterTicketUserAdded": api.ODEvent<(ticket:ODTicket, adder:discord.User, user:discord.User, channel:discord.GuildTextBasedChannel, reason:string|null) => api.ODPromiseVoid> + "onTicketUserRemove": api.ODEvent<(ticket:ODTicket, remover:discord.User, user:discord.User, channel:discord.GuildTextBasedChannel, reason:string|null) => api.ODPromiseVoid> + "afterTicketUserRemoved": api.ODEvent<(ticket:ODTicket, remover:discord.User, user:discord.User, channel:discord.GuildTextBasedChannel, reason:string|null) => api.ODPromiseVoid> + "onTicketRename": api.ODEvent<(ticket:ODTicket, renamer:discord.User, channel:discord.GuildTextBasedChannel, reason:string|null) => api.ODPromiseVoid> + "afterTicketRenamed": api.ODEvent<(ticket:ODTicket, renamer:discord.User, channel:discord.GuildTextBasedChannel, reason:string|null) => api.ODPromiseVoid> + "onTicketsClear": api.ODEvent<(tickets:ODTicket[], clearer:discord.User, channel:discord.GuildTextBasedChannel, filter:ODTicketClearFilter) => api.ODPromiseVoid> + "afterTicketsCleared": api.ODEvent<(tickets:ODTicket[], clearer:discord.User, channel:discord.GuildTextBasedChannel, filter:ODTicketClearFilter) => api.ODPromiseVoid> + "onTicketTopicChange": api.ODEvent<(ticket:ODTicket, changer:discord.User, channel:discord.GuildTextBasedChannel, oldTopic:string, newTopic:string) => api.ODPromiseVoid> + "afterTicketTopicChanged": api.ODEvent<(ticket:ODTicket, changer:discord.User, channel:discord.GuildTextBasedChannel, oldTopic:string, newTopic:string) => api.ODPromiseVoid> + "onTicketPriorityChange": api.ODEvent<(ticket:ODTicket, changer:discord.User, channel:discord.GuildTextBasedChannel, oldPriority:ODPriorityLevel, newPriority:ODPriorityLevel, reason:string|null) => api.ODPromiseVoid> + "afterTicketPriorityChanged": api.ODEvent<(ticket:ODTicket, changer:discord.User, channel:discord.GuildTextBasedChannel, oldPriority:ODPriorityLevel, newPriority:ODPriorityLevel, reason:string|null) => api.ODPromiseVoid> + "onTicketTransfer": api.ODEvent<(ticket:ODTicket, changer:discord.User, channel:discord.GuildTextBasedChannel, oldCreator:discord.User, newCreator:discord.User, reason:string|null) => api.ODPromiseVoid> + "afterTicketTransferred": api.ODEvent<(ticket:ODTicket, changer:discord.User, channel:discord.GuildTextBasedChannel, oldCreator:discord.User, newCreator:discord.User, reason:string|null) => api.ODPromiseVoid> + + //roles + "onRoleLoad": api.ODEvent<(roles:ODRoleManager) => api.ODPromiseVoid> + "afterRolesLoaded": api.ODEvent<(roles:ODRoleManager) => api.ODPromiseVoid> + "onRoleUpdate": api.ODEvent<(user:discord.User,role:ODRole) => api.ODPromiseVoid> + "afterRolesUpdated": api.ODEvent<(user:discord.User,role:ODRole) => api.ODPromiseVoid> + + //blacklist + "onBlacklistLoad": api.ODEvent<(blacklist:ODBlacklistManager) => api.ODPromiseVoid> + "afterBlacklistLoaded": api.ODEvent<(blacklist:ODBlacklistManager) => api.ODPromiseVoid> + + //transcripts + "onTranscriptCompilerLoad": api.ODEvent<(transcripts:ODMappedTranscriptManager) => api.ODPromiseVoid> + "afterTranscriptCompilersLoaded": api.ODEvent<(transcripts:ODMappedTranscriptManager) => api.ODPromiseVoid> + "onTranscriptHistoryLoad": api.ODEvent<(transcripts:ODMappedTranscriptManager) => api.ODPromiseVoid> + "afterTranscriptHistoryLoaded": api.ODEvent<(transcripts:ODMappedTranscriptManager) => api.ODPromiseVoid> + + //transcript creation + "onTranscriptCreate": api.ODEvent<(transcripts:ODMappedTranscriptManager,ticket:ODTicket,channel:discord.TextChannel,user:discord.User) => api.ODPromiseVoid> + "afterTranscriptCreated": api.ODEvent<(transcripts:ODMappedTranscriptManager,ticket:ODTicket,channel:discord.TextChannel,user:discord.User) => api.ODPromiseVoid> + "onTranscriptInit": api.ODEvent<(transcripts:ODMappedTranscriptManager,ticket:ODTicket,channel:discord.TextChannel,user:discord.User) => api.ODPromiseVoid> + "afterTranscriptInitiated": api.ODEvent<(transcripts:ODMappedTranscriptManager,ticket:ODTicket,channel:discord.TextChannel,user:discord.User) => api.ODPromiseVoid> + "onTranscriptCompile": api.ODEvent<(transcripts:ODMappedTranscriptManager,ticket:ODTicket,channel:discord.TextChannel,user:discord.User) => api.ODPromiseVoid> + "afterTranscriptCompiled": api.ODEvent<(transcripts:ODMappedTranscriptManager,ticket:ODTicket,channel:discord.TextChannel,user:discord.User) => api.ODPromiseVoid> + "onTranscriptReady": api.ODEvent<(transcripts:ODMappedTranscriptManager,ticket:ODTicket,channel:discord.TextChannel,user:discord.User) => api.ODPromiseVoid> + "afterTranscriptReady": api.ODEvent<(transcripts:ODMappedTranscriptManager,ticket:ODTicket,channel:discord.TextChannel,user:discord.User) => api.ODPromiseVoid> + + //plugin loading before builders + "onPluginBeforeBuilderLoad": api.ODEvent<() => api.ODPromiseVoid>, + "afterPluginBeforeBuilderLoaded": api.ODEvent<() => api.ODPromiseVoid>, + + //builders + "onButtonBuilderLoad": api.ODEvent<(buttons:ODMappedButtonManager, builders:ODMappedBuilderManager, actions:ODMappedActionManager) => api.ODPromiseVoid> + "afterButtonBuildersLoaded": api.ODEvent<(buttons:ODMappedButtonManager, builders:ODMappedBuilderManager, actions:ODMappedActionManager) => api.ODPromiseVoid> + "onDropdownBuilderLoad": api.ODEvent<(dropdowns:ODMappedDropdownManager, builders:ODMappedBuilderManager, actions:ODMappedActionManager) => api.ODPromiseVoid> + "afterDropdownBuildersLoaded": api.ODEvent<(dropdowns:ODMappedDropdownManager, builders:ODMappedBuilderManager, actions:ODMappedActionManager) => api.ODPromiseVoid> + "onFileBuilderLoad": api.ODEvent<(files:ODMappedFileManager, builders:ODMappedBuilderManager, actions:ODMappedActionManager) => api.ODPromiseVoid> + "afterFileBuildersLoaded": api.ODEvent<(files:ODMappedFileManager, builders:ODMappedBuilderManager, actions:ODMappedActionManager) => api.ODPromiseVoid> + "onEmbedBuilderLoad": api.ODEvent<(embeds:ODMappedEmbedManager, builders:ODMappedBuilderManager, actions:ODMappedActionManager) => api.ODPromiseVoid> + "afterEmbedBuildersLoaded": api.ODEvent<(embeds:ODMappedEmbedManager, builders:ODMappedBuilderManager, actions:ODMappedActionManager) => api.ODPromiseVoid> + "onMessageBuilderLoad": api.ODEvent<(messages:ODMappedMessageManager, builders:ODMappedBuilderManager, actions:ODMappedActionManager) => api.ODPromiseVoid> + "afterMessageBuildersLoaded": api.ODEvent<(messages:ODMappedMessageManager, builders:ODMappedBuilderManager, actions:ODMappedActionManager) => api.ODPromiseVoid> + "onModalBuilderLoad": api.ODEvent<(modals:ODMappedModalManager, builders:ODMappedBuilderManager, actions:ODMappedActionManager) => api.ODPromiseVoid> + "afterModalBuildersLoaded": api.ODEvent<(modals:ODMappedModalManager, builders:ODMappedBuilderManager, actions:ODMappedActionManager) => api.ODPromiseVoid> + + //components + "onSharedComponentLoad": api.ODEvent<(shared:ODMappedSharedComponentManager, components:ODMappedComponentManager, actions:ODMappedActionManager) => api.ODPromiseVoid> + "afterSharedComponentsLoaded": api.ODEvent<(shared:ODMappedSharedComponentManager, components:ODMappedComponentManager, actions:ODMappedActionManager) => api.ODPromiseVoid> + "onMessageComponentLoad": api.ODEvent<(shared:ODMappedMessageComponentManager, components:ODMappedComponentManager, actions:ODMappedActionManager) => api.ODPromiseVoid> + "afterMessageComponentsLoaded": api.ODEvent<(shared:ODMappedMessageComponentManager, components:ODMappedComponentManager, actions:ODMappedActionManager) => api.ODPromiseVoid> + "onModalComponentLoad": api.ODEvent<(shared:ODMappedModalComponentManager, components:ODMappedComponentManager, actions:ODMappedActionManager) => api.ODPromiseVoid> + "afterModalComponentsLoaded": api.ODEvent<(shared:ODMappedModalComponentManager, components:ODMappedComponentManager, actions:ODMappedActionManager) => api.ODPromiseVoid> + "onComponentModifierLoad": api.ODEvent<(modifiers:ODMappedComponentModifierManager, msgComponents:ODMappedMessageComponentManager, msgBuilders:ODMappedMessageManager) => api.ODPromiseVoid> + "afterComponentModifiersLoaded": api.ODEvent<(modifiers:ODMappedComponentModifierManager, msgComponents:ODMappedMessageComponentManager, msgBuilders:ODMappedMessageManager) => api.ODPromiseVoid> + + //plugin loading before responders + "onPluginBeforeResponderLoad": api.ODEvent<() => api.ODPromiseVoid>, + "afterPluginBeforeResponderLoaded": api.ODEvent<() => api.ODPromiseVoid>, + + //responders + "onCommandResponderLoad": api.ODEvent<(commands:ODMappedCommandResponderManager, responders:ODMappedResponderManager, actions:ODMappedActionManager) => api.ODPromiseVoid> + "afterCommandRespondersLoaded": api.ODEvent<(commands:ODMappedCommandResponderManager, responders:ODMappedResponderManager, actions:ODMappedActionManager) => api.ODPromiseVoid> + "onButtonResponderLoad": api.ODEvent<(buttons:ODMappedButtonResponderManager, responders:ODMappedResponderManager, actions:ODMappedActionManager) => api.ODPromiseVoid> + "afterButtonRespondersLoaded": api.ODEvent<(buttons:ODMappedButtonResponderManager, responders:ODMappedResponderManager, actions:ODMappedActionManager) => api.ODPromiseVoid> + "onDropdownResponderLoad": api.ODEvent<(dropdowns:ODMappedDropdownResponderManager, responders:ODMappedResponderManager, actions:ODMappedActionManager) => api.ODPromiseVoid> + "afterDropdownRespondersLoaded": api.ODEvent<(dropdowns:ODMappedDropdownResponderManager, responders:ODMappedResponderManager, actions:ODMappedActionManager) => api.ODPromiseVoid> + "onModalResponderLoad": api.ODEvent<(modals:ODMappedModalResponderManager, responders:ODMappedResponderManager, actions:ODMappedActionManager) => api.ODPromiseVoid> + "afterModalRespondersLoaded": api.ODEvent<(modals:ODMappedModalResponderManager, responders:ODMappedResponderManager, actions:ODMappedActionManager) => api.ODPromiseVoid> + "onContextMenuResponderLoad": api.ODEvent<(menus:ODMappedContextMenuResponderManager, responders:ODMappedResponderManager, actions:ODMappedActionManager) => api.ODPromiseVoid> + "afterContextMenuRespondersLoaded": api.ODEvent<(menus:ODMappedContextMenuResponderManager, responders:ODMappedResponderManager, actions:ODMappedActionManager) => api.ODPromiseVoid> + "onAutocompleteResponderLoad": api.ODEvent<(autocomplete:ODMappedAutocompleteResponderManager, responders:ODMappedResponderManager, actions:ODMappedActionManager) => api.ODPromiseVoid> + "afterAutocompleteRespondersLoaded": api.ODEvent<(autocomplete:ODMappedAutocompleteResponderManager, responders:ODMappedResponderManager, actions:ODMappedActionManager) => api.ODPromiseVoid> + + //plugin loading before finalizations + "onPluginBeforeFinalizationLoad": api.ODEvent<() => api.ODPromiseVoid>, + "afterPluginBeforeFinalizationLoaded": api.ODEvent<() => api.ODPromiseVoid>, + + //actions + "onActionLoad": api.ODEvent<(actions:ODMappedActionManager) => api.ODPromiseVoid> + "afterActionsLoaded": api.ODEvent<(actions:ODMappedActionManager) => api.ODPromiseVoid> + + //verifybars + "onVerifyBarLoad": api.ODEvent<(verifybars:ODMappedVerifyBarManager) => api.ODPromiseVoid> + "afterVerifyBarsLoaded": api.ODEvent<(verifybars:ODMappedVerifyBarManager) => api.ODPromiseVoid> + + //permissions + "onPermissionLoad": api.ODEvent<(permissions:ODMappedPermissionManager) => api.ODPromiseVoid> + "afterPermissionsLoaded": api.ODEvent<(permissions:ODMappedPermissionManager) => api.ODPromiseVoid> + + //posts + "onPostLoad": api.ODEvent<(posts:ODMappedPostManager) => api.ODPromiseVoid> + "afterPostsLoaded": api.ODEvent<(posts:ODMappedPostManager) => api.ODPromiseVoid> + "onPostInit": api.ODEvent<(posts:ODMappedPostManager) => api.ODPromiseVoid> + "afterPostsInitiated": api.ODEvent<(posts:ODMappedPostManager) => api.ODPromiseVoid> + + //cooldowns + "onCooldownLoad": api.ODEvent<(cooldowns:ODMappedCooldownManager) => api.ODPromiseVoid> + "afterCooldownsLoaded": api.ODEvent<(cooldowns:ODMappedCooldownManager) => api.ODPromiseVoid> + "onCooldownInit": api.ODEvent<(cooldowns:ODMappedCooldownManager) => api.ODPromiseVoid> + "afterCooldownsInitiated": api.ODEvent<(cooldowns:ODMappedCooldownManager) => api.ODPromiseVoid> + + //help menu + "onHelpMenuCategoryLoad": api.ODEvent<(menu:ODMappedHelpMenuManager) => api.ODPromiseVoid> + "afterHelpMenuCategoriesLoaded": api.ODEvent<(menu:ODMappedHelpMenuManager) => api.ODPromiseVoid> + "onHelpMenuComponentLoad": api.ODEvent<(menu:ODMappedHelpMenuManager) => api.ODPromiseVoid> + "afterHelpMenuComponentsLoaded": api.ODEvent<(menu:ODMappedHelpMenuManager) => api.ODPromiseVoid> + + //stats + "onStatisticScopeLoad": api.ODEvent<(stats:ODMappedStatisticManager) => api.ODPromiseVoid> + "afterStatisticScopesLoaded": api.ODEvent<(stats:ODMappedStatisticManager) => api.ODPromiseVoid> + "onStatisticLoad": api.ODEvent<(stats:ODMappedStatisticManager) => api.ODPromiseVoid> + "afterStatisticsLoaded": api.ODEvent<(stats:ODMappedStatisticManager) => api.ODPromiseVoid> + "onStatisticInit": api.ODEvent<(stats:ODMappedStatisticManager) => api.ODPromiseVoid> + "afterStatisticsInitiated": api.ODEvent<(stats:ODMappedStatisticManager) => api.ODPromiseVoid> + + //plugin loading before tasks + "onPluginBeforeTaskLoad": api.ODEvent<() => api.ODPromiseVoid>, + "afterPluginBeforeTaskLoaded": api.ODEvent<() => api.ODPromiseVoid>, + + //background tasks + "onTaskLoad": api.ODEvent<(tasks:ODMappedTaskManager) => api.ODPromiseVoid> + "afterTasksLoaded": api.ODEvent<(tasks:ODMappedTaskManager) => api.ODPromiseVoid> + "onTaskExecute": api.ODEvent<(tasks:ODMappedTaskManager) => api.ODPromiseVoid> + "afterTasksExecuted": api.ODEvent<(tasks:ODMappedTaskManager) => api.ODPromiseVoid> + + //livestatus + "onLiveStatusSourceLoad": api.ODEvent<(livestatus:ODMappedLiveStatusManager) => api.ODPromiseVoid> + "afterLiveStatusSourcesLoaded": api.ODEvent<(livestatus:ODMappedLiveStatusManager) => api.ODPromiseVoid> + + //startscreen + "onStartScreenLoad": api.ODEvent<(startscreen:ODMappedStartScreenManager) => api.ODPromiseVoid> + "afterStartScreensLoaded": api.ODEvent<(startscreen:ODMappedStartScreenManager) => api.ODPromiseVoid> + "onStartScreenRender": api.ODEvent<(startscreen:ODMappedStartScreenManager) => api.ODPromiseVoid> + "afterStartScreensRendered": api.ODEvent<(startscreen:ODMappedStartScreenManager) => api.ODPromiseVoid> + + //ready + "beforeReadyForUsage": api.ODEvent<() => api.ODPromiseVoid> + "onReadyForUsage": api.ODEvent<() => api.ODPromiseVoid> +} + +///////////////////////////// +////// MAPPED MANAGERS ////// +///////////////////////////// + +/**## ODMappedEventManager `class + * A special class with types for the Open Ticket `ODEventManager` class. + */ +export class ODMappedEventManager extends api.ODEventManager {} \ No newline at end of file diff --git a/src/core/mappings/flag.ts b/src/core/mappings/flag.ts new file mode 100644 index 0000000..47b9bee --- /dev/null +++ b/src/core/mappings/flag.ts @@ -0,0 +1,36 @@ +/////////////////////////////////////// +//OPEN TICKET PROCESS MAPPINGS +/////////////////////////////////////// +import * as api from "@open-discord-bots/framework/api" + +/**## ODFlagManagerIdMappings `interface` + * A list of all available IDs in the default `ODFlagManager` class in `opendiscord`. + * It's used to generate typescript declarations for this class. + */ +export interface ODFlagManagerIdMappings extends api.ODFlagManagerIdConstraint { + "opendiscord:no-migration":api.ODFlag, + "opendiscord:dev-config":api.ODFlag, + "opendiscord:dev-database":api.ODFlag, + "opendiscord:debug":api.ODFlag, + "opendiscord:crash":api.ODFlag, + "opendiscord:no-transcripts":api.ODFlag, + "opendiscord:no-checker":api.ODFlag, + "opendiscord:checker":api.ODFlag, + "opendiscord:no-easter":api.ODFlag, + "opendiscord:no-plugins":api.ODFlag, + "opendiscord:soft-plugins":api.ODFlag, + "opendiscord:force-slash-update":api.ODFlag, + "opendiscord:no-compile":api.ODFlag, + "opendiscord:compile-only":api.ODFlag, + "opendiscord:silent":api.ODFlag, + "opendiscord:cli":api.ODFlag, +} + +///////////////////////////// +////// MAPPED MANAGERS ////// +///////////////////////////// + +/**## ODMappedFlagManager `class + * A special class with types for the Open Ticket `ODFlagManager` class. + */ +export class ODMappedFlagManager extends api.ODFlagManager {} \ No newline at end of file diff --git a/src/core/mappings/fuse.ts b/src/core/mappings/fuse.ts new file mode 100644 index 0000000..6e1ae00 --- /dev/null +++ b/src/core/mappings/fuse.ts @@ -0,0 +1,29 @@ +/////////////////////////////////////// +//OPEN TICKET FUSE MAPPINGS +/////////////////////////////////////// +import * as api from "@open-discord-bots/framework/api" + +export interface ODOpenTicketFuseList { + /**Load the default Open Ticket ticket priority levels. */ + priorityLoading:boolean, + /**Load the default Open Ticket questions (from `config/questions.jsonc`) */ + questionLoading:boolean, + /**Load the default Open Ticket options (from `config/options.jsonc`) */ + optionLoading:boolean, + /**Load the default Open Ticket panels (from `config/panels.jsonc`) */ + panelLoading:boolean, + /**Load the default Open Ticket tickets (from `database/tickets.json`) */ + ticketLoading:boolean, + /**Load the default Open Ticket reaction roles (from `config/options.jsonc`) */ + roleLoading:boolean, + /**Load the default Open Ticket blacklist (from `database/users.json`) */ + blacklistLoading:boolean, + /**Load the default Open Ticket transcript compilers. */ + transcriptCompilerLoading:boolean, + /**Load the default Open Ticket transcript history (from `database/transcripts.jsonc`) */ + transcriptHistoryLoading:boolean, + /**The interval in milliseconds that are between autoclose timeout checkers. */ + autocloseCheckInterval:number, + /**The interval in milliseconds that are between autodelete timeout checkers. */ + autodeleteCheckInterval:number, +} \ No newline at end of file diff --git a/src/core/mappings/helpmenu.ts b/src/core/mappings/helpmenu.ts new file mode 100644 index 0000000..d8316df --- /dev/null +++ b/src/core/mappings/helpmenu.ts @@ -0,0 +1,146 @@ +/////////////////////////////////////// +//OPEN TICKET HELP MAPPINGS +/////////////////////////////////////// +import * as api from "@open-discord-bots/framework/api" + +/**## ODHelpMenuManagerIdMappings `interface` + * A list of all available IDs in the default `ODHelpMenuManager` class in `opendiscord`. + * It's used to generate typescript declarations for this class. + */ +export interface ODHelpMenuManagerIdMappings extends api.ODHelpMenuManagerIdConstraint { + "opendiscord:general":ODGeneralHelpMenuCategory, + "opendiscord:ticket-basic":ODBasicTicketHelpMenuCategory, + "opendiscord:ticket-advanced":ODAdvancedTicketHelpMenuCategory, + "opendiscord:ticket-user":ODUserTicketHelpMenuCategory, + "opendiscord:admin":ODAdminHelpMenuCategory, + "opendiscord:advanced":ODAdvancedHelpMenuCategory, + "opendiscord:extra":ODExtraHelpMenuCategory +} + +///////////////////////////////////////// +// HELP MENU MAPPINGS, CATEGORIES & TYPES +///////////////////////////////////////// + +/**## ODGeneralHelpMenuCategoryIdMappings `interface` + * A list of all available IDs in the default `ODGeneralHelpMenuCategory` class in `opendiscord`. + * It's used to generate typescript declarations for this class. + */ +export interface ODGeneralHelpMenuCategoryIdMappings extends api.ODHelpMenuCategoryIdConstraint { + "opendiscord:help":api.ODHelpMenuCommandComponent, + "opendiscord:ticket":api.ODHelpMenuCommandComponent|null +} + +/**## ODBasicTicketHelpMenuCategoryIdMappings `interface` + * A list of all available IDs in the default `ODBasicTicketHelpMenuCategory` class in `opendiscord`. + * It's used to generate typescript declarations for this class. + */ +export interface ODBasicTicketHelpMenuCategoryIdMappings extends api.ODHelpMenuCategoryIdConstraint { + "opendiscord:close":api.ODHelpMenuCommandComponent, + "opendiscord:delete":api.ODHelpMenuCommandComponent, + "opendiscord:reopen":api.ODHelpMenuCommandComponent +} + +/**## ODAdvancedTicketHelpMenuCategoryIdMappings `interface` + * A list of all available IDs in the default `ODAdvancedTicketHelpMenuCategory` class in `opendiscord`. + * It's used to generate typescript declarations for this class. + */ +export interface ODAdvancedTicketHelpMenuCategoryIdMappings extends api.ODHelpMenuCategoryIdConstraint { + "opendiscord:pin":api.ODHelpMenuCommandComponent, + "opendiscord:unpin":api.ODHelpMenuCommandComponent, + "opendiscord:move":api.ODHelpMenuCommandComponent, + "opendiscord:rename":api.ODHelpMenuCommandComponent +} + +/**## ODUserTicketHelpMenuCategoryIdMappings `interface` + * A list of all available IDs in the default `ODUserTicketHelpMenuCategory` class in `opendiscord`. + * It's used to generate typescript declarations for this class. + */ +export interface ODUserTicketHelpMenuCategoryIdMappings extends api.ODHelpMenuCategoryIdConstraint { + "opendiscord:claim":api.ODHelpMenuCommandComponent, + "opendiscord:unclaim":api.ODHelpMenuCommandComponent, + "opendiscord:add":api.ODHelpMenuCommandComponent, + "opendiscord:remove":api.ODHelpMenuCommandComponent, + "opendiscord:transfer":api.ODHelpMenuCommandComponent, +} + +/**## ODAdminHelpMenuCategoryIdMappings `interface` + * A list of all available IDs in the default `ODAdminHelpMenuCategory` class in `opendiscord`. + * It's used to generate typescript declarations for this class. + */ +export interface ODAdminHelpMenuCategoryIdMappings extends api.ODHelpMenuCategoryIdConstraint { + "opendiscord:panel":api.ODHelpMenuCommandComponent, + "opendiscord:blacklist-view":api.ODHelpMenuCommandComponent, + "opendiscord:blacklist-add":api.ODHelpMenuCommandComponent, + "opendiscord:blacklist-remove":api.ODHelpMenuCommandComponent, + "opendiscord:blacklist-get":api.ODHelpMenuCommandComponent +} + +/**## ODAdvancedHelpMenuCategoryIdMappings `interface` + * A list of all available IDs in the default `ODAdvancedHelpMenuCategory` class in `opendiscord`. + * It's used to generate typescript declarations for this class. + */ +export interface ODAdvancedHelpMenuCategoryIdMappings extends api.ODHelpMenuCategoryIdConstraint { + "opendiscord:stats-global":api.ODHelpMenuCommandComponent, + "opendiscord:stats-reset":api.ODHelpMenuCommandComponent, + "opendiscord:stats-ticket":api.ODHelpMenuCommandComponent, + "opendiscord:stats-user":api.ODHelpMenuCommandComponent, + "opendiscord:autoclose-disable":api.ODHelpMenuCommandComponent, + "opendiscord:autoclose-enable":api.ODHelpMenuCommandComponent, + "opendiscord:autodelete-disable":api.ODHelpMenuCommandComponent, + "opendiscord:autodelete-enable":api.ODHelpMenuCommandComponent, + "opendiscord:topic-set":api.ODHelpMenuCommandComponent, + "opendiscord:priority-set":api.ODHelpMenuCommandComponent, + "opendiscord:transcripts":api.ODHelpMenuCommandComponent, +} + +/**## ODExtraHelpMenuCategoryIdMappings `interface` + * A list of all available IDs in the default `ODExtraHelpMenuCategory` class in `opendiscord`. + * It's used to generate typescript declarations for this class. + */ +export interface ODExtraHelpMenuCategoryIdMappings extends api.ODHelpMenuCategoryIdConstraint { + //"opendiscord:help-component":api.ODHelpMenuCommandComponent +} + +///////////////////////////// +////// MAPPED MANAGERS ////// +///////////////////////////// + +/**## ODMappedHelpMenuManager `class + * A special class with types for the Open Ticket `ODHelpMenuManager` class. + */ +export class ODMappedHelpMenuManager extends api.ODHelpMenuManager {} + +/**## ODGeneralHelpMenuCategory `class + * A special class with types for the Open Ticket `General Commands` help menu category. + */ +export class ODGeneralHelpMenuCategory extends api.ODHelpMenuCategory {} + +/**## ODBasicTicketHelpMenuCategory `class + * A special class with types for the Open Ticket `Basic Ticket Commands` help menu category. + */ +export class ODBasicTicketHelpMenuCategory extends api.ODHelpMenuCategory {} + +/**## ODAdvancedTicketHelpMenuCategory `class + * A special class with types for the Open Ticket `Advanced Ticket Commands` help menu category. + */ +export class ODAdvancedTicketHelpMenuCategory extends api.ODHelpMenuCategory {} + +/**## ODUserTicketHelpMenuCategory `class + * A special class with types for the Open Ticket `User ticket Commands` help menu category. + */ +export class ODUserTicketHelpMenuCategory extends api.ODHelpMenuCategory {} + +/**## ODAdminHelpMenuCategory `class + * A special class with types for the Open Ticket `Admin Commands` help menu category. + */ +export class ODAdminHelpMenuCategory extends api.ODHelpMenuCategory {} + +/**## ODAdvancedHelpMenuCategory `class + * A special class with types for the Open Ticket `Advanced Commands` help menu category. + */ +export class ODAdvancedHelpMenuCategory extends api.ODHelpMenuCategory {} + +/**## ODExtraHelpMenuCategory `class + * A special class with types for the Open Ticket `Extra Commands` help menu category. + */ +export class ODExtraHelpMenuCategory extends api.ODHelpMenuCategory {} \ No newline at end of file diff --git a/src/core/api/defaults/language.ts b/src/core/mappings/language.ts similarity index 80% rename from src/core/api/defaults/language.ts rename to src/core/mappings/language.ts index 64270e2..48decc9 100644 --- a/src/core/api/defaults/language.ts +++ b/src/core/mappings/language.ts @@ -1,68 +1,60 @@ /////////////////////////////////////// -//DEFAULT LANGUAGE MODULE +//OPEN TICKET LANGUAGE MAPPINGS /////////////////////////////////////// -import { ODValidId } from "../modules/base" -import { ODLanguageManager, ODLanguage } from "../modules/language" +import * as api from "@open-discord-bots/framework/api" -/** (CONTRIBUTOR GUIDE) HOW TO ADD NEW LANGUAGES? - * - Add the file to (./languages/) and make sure the metadata is valid. - * - Register the language in loadAllLanguages() in (./src/data/framework/languageLoader.ts). - * - Add autocomplete for the language in ODLanguageManagerIds_Default in (./src/core/api/defaults/language.ts). - * - Update the language list in the README.md translator list. - * - Update the 2 language counters in the README.md features list. - * - Update the Open Ticket Documentation. - */ - -/**## ODLanguageManagerIds_Default `interface` - * This interface is a list of ids available in the `ODLanguageManager_Default` class. +/**## ODLanguageManagerIdMappings `interface` + * A list of all available IDs in the default `ODLanguageManager` class in `opendiscord`. * It's used to generate typescript declarations for this class. */ -export interface ODLanguageManagerIds_Default { - "opendiscord:custom":ODLanguage, - "opendiscord:english":ODLanguage, - "opendiscord:dutch":ODLanguage, - "opendiscord:portuguese":ODLanguage, - "opendiscord:czech":ODLanguage, - "opendiscord:german":ODLanguage, - "opendiscord:catalan":ODLanguage, - "opendiscord:hungarian":ODLanguage, - "opendiscord:spanish":ODLanguage, - "opendiscord:romanian":ODLanguage, - "opendiscord:ukrainian":ODLanguage, - "opendiscord:indonesian":ODLanguage, - "opendiscord:italian":ODLanguage, - "opendiscord:estonian":ODLanguage, - "opendiscord:finnish":ODLanguage, - "opendiscord:danish":ODLanguage, - "opendiscord:thai":ODLanguage, - "opendiscord:turkish":ODLanguage, - "opendiscord:french":ODLanguage, - "opendiscord:arabic":ODLanguage, - "opendiscord:hindi":ODLanguage, - "opendiscord:lithuanian":ODLanguage, - "opendiscord:polish":ODLanguage, - "opendiscord:latvian":ODLanguage, - "opendiscord:norwegian":ODLanguage, - "opendiscord:russian":ODLanguage, - "opendiscord:swedish":ODLanguage, - "opendiscord:vietnamese":ODLanguage, - "opendiscord:persian":ODLanguage, - "opendiscord:bengali":ODLanguage, - "opendiscord:greek":ODLanguage, - "opendiscord:japanese":ODLanguage, - "opendiscord:korean":ODLanguage, - "opendiscord:kurdish":ODLanguage, - "opendiscord:simplified-chinese":ODLanguage, - "opendiscord:slovenian":ODLanguage, - "opendiscord:tamil":ODLanguage, +export interface ODLanguageManagerIdMappings extends api.ODLanguageManagerIdConstraint { + "opendiscord:custom":api.ODLanguage, + "opendiscord:english":api.ODLanguage, + "opendiscord:dutch":api.ODLanguage, + "opendiscord:portuguese":api.ODLanguage, + "opendiscord:czech":api.ODLanguage, + "opendiscord:german":api.ODLanguage, + "opendiscord:catalan":api.ODLanguage, + "opendiscord:hungarian":api.ODLanguage, + "opendiscord:spanish":api.ODLanguage, + "opendiscord:romanian":api.ODLanguage, + "opendiscord:ukrainian":api.ODLanguage, + "opendiscord:indonesian":api.ODLanguage, + "opendiscord:italian":api.ODLanguage, + "opendiscord:estonian":api.ODLanguage, + "opendiscord:finnish":api.ODLanguage, + "opendiscord:danish":api.ODLanguage, + "opendiscord:thai":api.ODLanguage, + "opendiscord:turkish":api.ODLanguage, + "opendiscord:french":api.ODLanguage, + "opendiscord:arabic":api.ODLanguage, + "opendiscord:hindi":api.ODLanguage, + "opendiscord:lithuanian":api.ODLanguage, + "opendiscord:polish":api.ODLanguage, + "opendiscord:latvian":api.ODLanguage, + "opendiscord:norwegian":api.ODLanguage, + "opendiscord:russian":api.ODLanguage, + "opendiscord:swedish":api.ODLanguage, + "opendiscord:vietnamese":api.ODLanguage, + "opendiscord:persian":api.ODLanguage, + "opendiscord:bengali":api.ODLanguage, + "opendiscord:greek":api.ODLanguage, + "opendiscord:japanese":api.ODLanguage, + "opendiscord:korean":api.ODLanguage, + "opendiscord:kurdish":api.ODLanguage, + "opendiscord:simplified-chinese":api.ODLanguage, + "opendiscord:traditional-chinese":api.ODLanguage, + "opendiscord:slovenian":api.ODLanguage, + "opendiscord:tamil":api.ODLanguage, + "opendiscord:khmer ":api.ODLanguage, //ADD NEW LANGUAGES HERE!!! } -/**## ODLanguageManagerTranslations_Default `type` - * This interface is a list of ids available in the `ODLanguageManager_Default` class. +/**## ODLanguageManagerTranslationIdMappings `type` + * A list of all available translation IDs in the default `ODLanguageManager` class in `opendiscord`. * It's used to generate typescript declarations for this class. */ -export type ODLanguageManagerTranslations_Default = ( +export type ODLanguageManagerTranslationIdMappings = ( "checker.system.headerOpenTicket"| "checker.system.typeError"| "checker.system.typeWarning"| @@ -169,6 +161,8 @@ export type ODLanguageManagerTranslations_Default = ( "actions.buttons.helpPage"| "actions.buttons.withReason"| "actions.buttons.withoutTranscript"| + "actions.buttons.blacklistAdd"| + "actions.buttons.blacklistRemove"| "actions.titles.created"| "actions.titles.close"| @@ -206,6 +200,7 @@ export type ODLanguageManagerTranslations_Default = ( "actions.titles.prioritySet"| "actions.titles.priorityGet"| "actions.titles.transfer"| + "actions.titles.transcripts"| "actions.descriptions.create"| "actions.descriptions.close"| @@ -296,7 +291,9 @@ export type ODLanguageManagerTranslations_Default = ( "actions.logs.prioritySetDm"| "actions.logs.roleUpdateLog"| "actions.logs.roleUpdateDm"| - + "actions.logs.topicSetLog"| + "actions.logs.topicSetDm"| + "transcripts.success.visit"| "transcripts.success.ready"| "transcripts.success.textFileDescription"| @@ -314,7 +311,9 @@ export type ODLanguageManagerTranslations_Default = ( "transcripts.errors.backup"| "transcripts.errors.error"| "transcripts.errors.title"| - + "transcripts.errors.noHistory"| + "transcripts.errors.historyNotSupported"| + "transcripts.text.messagesTitle"| "transcripts.text.embedTitle"| "transcripts.text.fileTitle"| @@ -336,6 +335,7 @@ export type ODLanguageManagerTranslations_Default = ( "errors.titles.unknownPanel"| "errors.titles.notInGuild"| "errors.titles.channelRename"| + "errors.titles.channelCategory"| "errors.titles.busy"| "errors.titles.permissionError"| @@ -358,12 +358,16 @@ export type ODLanguageManagerTranslations_Default = ( "errors.descriptions.deprecatedTicket"| "errors.descriptions.notInGuild"| "errors.descriptions.channelRename"| + "errors.descriptions.channelCategory"| "errors.descriptions.channelRenameSource"| "errors.descriptions.busy"| "errors.descriptions.closeBeforeMessage"| "errors.descriptions.closeBeforeAdminMessage"| "errors.descriptions.unableToCreateTicket"| - + "errors.descriptions.messageMissing"| + "errors.descriptions.stateExpired"| + "errors.descriptions.panelStateExpired"| + "errors.optionInvalidReasons.stringRegex"| "errors.optionInvalidReasons.stringMinLength"| "errors.optionInvalidReasons.stringMaxLength"| @@ -418,6 +422,8 @@ export type ODLanguageManagerTranslations_Default = ( "params.uppercase.syntax"| "params.uppercase.originalName"| "params.uppercase.newName"| + "params.uppercase.originalCategory"| + "params.uppercase.newCategory"| "params.uppercase.until"| "params.uppercase.validOptions"| "params.uppercase.validPanels"| @@ -440,6 +446,8 @@ export type ODLanguageManagerTranslations_Default = ( "params.uppercase.participants"| "params.uppercase.yes"| "params.uppercase.no"| + "params.uppercase.accept"| + "params.uppercase.cancel"| "params.uppercase.option"| "params.uppercase.topic"| "params.uppercase.uptime"| @@ -468,6 +476,7 @@ export type ODLanguageManagerTranslations_Default = ( "commands.panelAutoUpdate"| "commands.ticket"| "commands.ticketId"| + "commands.ticketOtherUser"| "commands.close"| "commands.delete"| "commands.deleteNoTranscript"| @@ -530,6 +539,8 @@ export type ODLanguageManagerTranslations_Default = ( "commands.priorityList"| "commands.transfer"| "commands.transferUser"| + "commands.transcripts"| + "commands.transcriptsUser"| "helpMenu.help"| "helpMenu.ticket"| @@ -612,6 +623,7 @@ export type ODLanguageManagerTranslations_Default = ( "panel.selectTicket"| "panel.selectRole"| "panel.selectOption"| + "panel.selectPriorityLevel"| "priorities.urgent"| "priorities.veryHigh"| @@ -622,59 +634,11 @@ export type ODLanguageManagerTranslations_Default = ( "priorities.none" ) -/**## ODLanguageManager_Default `default_class` - * This is a special class that adds type definitions & typescript to the ODLanguageManager class. - * It doesn't add any extra features! - * - * This default class is made for the global variable `opendiscord.languages`! +///////////////////////////// +////// MAPPED MANAGERS ////// +///////////////////////////// + +/**## ODMappedLanguageManager `class + * A special class with types for the Open Ticket `ODLanguageManager` class. */ -export class ODLanguageManager_Default extends ODLanguageManager { - get(id:LanguageId): ODLanguageManagerIds_Default[LanguageId] - get(id:ODValidId): ODLanguage|null - - get(id:ODValidId): ODLanguage|null { - return super.get(id) - } - - remove(id:LanguageId): ODLanguageManagerIds_Default[LanguageId] - remove(id:ODValidId): ODLanguage|null - - remove(id:ODValidId): ODLanguage|null { - return super.remove(id) - } - - exists(id:keyof ODLanguageManagerIds_Default): boolean - exists(id:ODValidId): boolean - - exists(id:ODValidId): boolean { - return super.exists(id) - } - - getTranslation(id:ODLanguageManagerTranslations_Default): string - getTranslation(id:string): string|null - - getTranslation(id:string): string|null { - return super.getTranslation(id) - } - - setCurrentLanguage(id:keyof ODLanguageManagerIds_Default): void - setCurrentLanguage(id:ODValidId): void - - setCurrentLanguage(id:ODValidId): void { - return super.setCurrentLanguage(id) - } - - setBackupLanguage(id:keyof ODLanguageManagerIds_Default): void - setBackupLanguage(id:ODValidId): void - - setBackupLanguage(id:ODValidId): void { - return super.setBackupLanguage(id) - } - - getTranslationWithParams(id:ODLanguageManagerTranslations_Default, params:string[]): string - getTranslationWithParams(id:string, params:string[]): string|null - - getTranslationWithParams(id:string, params:string[]): string|null { - return super.getTranslationWithParams(id,params) - } -} \ No newline at end of file +export class ODMappedLanguageManager extends api.ODLanguageManager {} \ No newline at end of file diff --git a/src/core/mappings/permission.ts b/src/core/mappings/permission.ts new file mode 100644 index 0000000..050b225 --- /dev/null +++ b/src/core/mappings/permission.ts @@ -0,0 +1,34 @@ +/////////////////////////////////////// +//OPEN TICKET PERMISSION MAPPINGS +/////////////////////////////////////// +import * as api from "@open-discord-bots/framework/api" + +/**## ODPermissionManagerIdMappings `interface` + * A list of all available IDs in the default `ODPermissionManager` class in `opendiscord`. + * It's used to generate typescript declarations for this class. + */ +export interface ODPermissionManagerIdMappings extends api.ODPermissionManagerIdConstraint { + //"opendiscord:test-permission":api.ODPermission +} + +/**## ODPermissionEmbedType `type` + * A collection of all types available in the `opendiscord:no-permissions` embed. + */ +export type ODPermissionEmbedType = ( + "developer"| + "owner"| + "admin"| + "moderator"| + "support"| + "member"| + "discord-administrator" +) + +///////////////////////////// +////// MAPPED MANAGERS ////// +///////////////////////////// + +/**## ODMappedPermissionManager `class + * A special class with types for the Open Ticket `ODPermissionManager` class. + */ +export class ODMappedPermissionManager extends api.ODPermissionManager {} \ No newline at end of file diff --git a/src/core/mappings/plugin.ts b/src/core/mappings/plugin.ts new file mode 100644 index 0000000..ccf204d --- /dev/null +++ b/src/core/mappings/plugin.ts @@ -0,0 +1,34 @@ +/////////////////////////////////////// +//OPEN TICKET POST MAPPINGS +/////////////////////////////////////// +import * as api from "@open-discord-bots/framework/api" + +/**## ODPluginManagerIdMappings `interface` + * A list of all available IDs in the default `ODPluginManager` class in `opendiscord`. + * It's used to generate typescript declarations for this class. + */ +export interface ODPluginManagerIdMappings extends api.ODPluginManagerIdConstraint { + //"opendiscord:example-plugin":api.ODPlugin +} + +/**## ODPluginClassManagerIdMappings `interface` + * A list of all available IDs in the default `ODPluginClassManager` class in `opendiscord`. + * It's used to generate typescript declarations for this class. + */ +export interface ODPluginClassManagerIdMappings extends api.ODPluginClassManagerIdConstraint { + //"opendiscord:example-plugin":any +} + +///////////////////////////// +////// MAPPED MANAGERS ////// +///////////////////////////// + +/**## ODMappedPluginManager `class + * A special class with types for the Open Ticket `ODPluginManager` class. + */ +export class ODMappedPluginManager extends api.ODPluginManager {} + +/**## ODMappedPluginClassManager `class + * A special class with types for the Open Ticket `ODPluginClassManager` class. + */ +export class ODMappedPluginClassManager extends api.ODPluginClassManager {} \ No newline at end of file diff --git a/src/core/mappings/post.ts b/src/core/mappings/post.ts new file mode 100644 index 0000000..a1abe2e --- /dev/null +++ b/src/core/mappings/post.ts @@ -0,0 +1,23 @@ +/////////////////////////////////////// +//OPEN TICKET POST MAPPINGS +/////////////////////////////////////// +import * as api from "@open-discord-bots/framework/api" +import * as discord from "discord.js" + +/**## ODPostManagerIdMappings `interface` + * A list of all available IDs in the default `ODPostManager` class in `opendiscord`. + * It's used to generate typescript declarations for this class. + */ +export interface ODPostManagerIdMappings extends api.ODPostManagerIdConstraint { + "opendiscord:logs":api.ODPost|null, + "opendiscord:transcripts":api.ODPost|null +} + +///////////////////////////// +////// MAPPED MANAGERS ////// +///////////////////////////// + +/**## ODMappedPostManager `class + * A special class with types for the Open Ticket `ODPostManager` class. + */ +export class ODMappedPostManager extends api.ODPostManager {} \ No newline at end of file diff --git a/src/core/mappings/progressbar.ts b/src/core/mappings/progressbar.ts new file mode 100644 index 0000000..68a2a3f --- /dev/null +++ b/src/core/mappings/progressbar.ts @@ -0,0 +1,44 @@ +/////////////////////////////////////// +//OPEN TICKET PROGRESS BAR MAPPINGS +/////////////////////////////////////// +import * as api from "@open-discord-bots/framework/api" + +/**## ODProgressBarManagerIdMappings `interface` + * A list of all available IDs in the default `ODProgressBarManager` class in `opendiscord`. + * It's used to generate typescript declarations for this class. + */ +export interface ODProgressBarManagerIdMappings extends api.ODProgressBarManagerIdConstraint { + "opendiscord:slash-command-remove":api.ODManualProgressBar, + "opendiscord:slash-command-create":api.ODManualProgressBar, + "opendiscord:slash-command-update":api.ODManualProgressBar, + "opendiscord:context-menu-remove":api.ODManualProgressBar, + "opendiscord:context-menu-create":api.ODManualProgressBar, + "opendiscord:context-menu-update":api.ODManualProgressBar, +} + +/**## ODProgressBarRendererManagerIdMappings `interface` + * A list of all available IDs in the default `ODProgressBarRendererManager` class in `opendiscord`. + * It's used to generate typescript declarations for this class. + */ +export interface ODProgressBarRendererManagerIdMappings extends api.ODProgressBarRendererManagerIdConstraint { + "opendiscord:value-renderer":api.ODDefaultProgressBarRenderer, + "opendiscord:fraction-renderer":api.ODDefaultProgressBarRenderer, + "opendiscord:percentage-renderer":api.ODDefaultProgressBarRenderer, + "opendiscord:time-ms-renderer":api.ODDefaultProgressBarRenderer, + "opendiscord:time-sec-renderer":api.ODDefaultProgressBarRenderer, + "opendiscord:time-min-renderer":api.ODDefaultProgressBarRenderer, +} + +///////////////////////////// +////// MAPPED MANAGERS ////// +///////////////////////////// + +/**## ODMappedProgressBarManager `class + * A special class with types for the Open Ticket `ODProgressBarManager` class. + */ +export class ODMappedProgressBarManager extends api.ODProgressBarManager {} + +/**## ODMappedProgressBarRendererManager `class + * A special class with types for the Open Ticket `ODProgressBarRendererManager` class. + */ +export class ODMappedProgressBarRendererManager extends api.ODProgressBarRendererManager {} \ No newline at end of file diff --git a/src/core/mappings/responder.ts b/src/core/mappings/responder.ts new file mode 100644 index 0000000..0b5832b --- /dev/null +++ b/src/core/mappings/responder.ts @@ -0,0 +1,145 @@ +/////////////////////////////////////// +//OPEN TICKET RESPONDER MAPPINGS +/////////////////////////////////////// +import * as api from "@open-discord-bots/framework/api" + +/**## ODCommandResponderManagerIdMappings `interface` + * A list of all available IDs in the default `ODCommandResponderManager` class in `opendiscord`. + * It's used to generate typescript declarations for this class. + */ +export interface ODCommandResponderManagerIdMappings extends api.ODCommandResponderManagerIdConstraint { + "opendiscord:help":{origin:"slash"|"text",params:{},workers:"opendiscord:help"|"opendiscord:logs"}, + "opendiscord:stats":{origin:"slash"|"text",params:{},workers:"opendiscord:stats"|"opendiscord:logs"}, + "opendiscord:panel":{origin:"slash"|"text",params:{},workers:"opendiscord:panel"|"opendiscord:logs"}, + "opendiscord:ticket":{origin:"slash"|"text",params:{},workers:"opendiscord:ticket"|"opendiscord:logs"}, + "opendiscord:blacklist":{origin:"slash"|"text",params:{},workers:"opendiscord:blacklist"|"opendiscord:discord-logs"|"opendiscord:logs"}, + + "opendiscord:close":{origin:"slash"|"text",params:{},workers:"opendiscord:close"|"opendiscord:logs"}, + "opendiscord:reopen":{origin:"slash"|"text",params:{},workers:"opendiscord:reopen"|"opendiscord:logs"}, + "opendiscord:delete":{origin:"slash"|"text",params:{},workers:"opendiscord:delete"|"opendiscord:logs"}, + "opendiscord:claim":{origin:"slash"|"text",params:{},workers:"opendiscord:claim"|"opendiscord:logs"}, + "opendiscord:unclaim":{origin:"slash"|"text",params:{},workers:"opendiscord:unclaim"|"opendiscord:logs"}, + "opendiscord:pin":{origin:"slash"|"text",params:{},workers:"opendiscord:pin"|"opendiscord:logs"}, + "opendiscord:unpin":{origin:"slash"|"text",params:{},workers:"opendiscord:unpin"|"opendiscord:logs"}, + + "opendiscord:rename":{origin:"slash"|"text",params:{},workers:"opendiscord:rename"|"opendiscord:logs"}, + "opendiscord:move":{origin:"slash"|"text",params:{},workers:"opendiscord:move"|"opendiscord:logs"}, + "opendiscord:add":{origin:"slash"|"text",params:{},workers:"opendiscord:add"|"opendiscord:logs"}, + "opendiscord:remove":{origin:"slash"|"text",params:{},workers:"opendiscord:remove"|"opendiscord:logs"}, + "opendiscord:clear":{origin:"slash"|"text",params:{},workers:"opendiscord:clear"|"opendiscord:logs"}, + "opendiscord:topic":{origin:"slash"|"text",params:{},workers:"opendiscord:topic"|"opendiscord:logs"}, + "opendiscord:priority":{origin:"slash"|"text",params:{},workers:"opendiscord:priority"|"opendiscord:logs"}, + "opendiscord:transfer":{origin:"slash"|"text",params:{},workers:"opendiscord:transfer"|"opendiscord:logs"}, + "opendiscord:transcripts":{origin:"slash"|"text",params:{},workers:"opendiscord:transcripts"|"opendiscord:logs"}, + + "opendiscord:autoclose":{origin:"slash"|"text",params:{},workers:"opendiscord:autoclose"|"opendiscord:logs"}, + "opendiscord:autodelete":{origin:"slash"|"text",params:{},workers:"opendiscord:autodelete"|"opendiscord:logs"}, +} + +/**## ODButtonResponderManagerIdMappings `interface` + * A list of all available IDs in the default `ODButtonResponderManager` class in `opendiscord`. + * It's used to generate typescript declarations for this class. + */ +export interface ODButtonResponderManagerIdMappings extends api.ODButtonResponderManagerIdConstraint { + "opendiscord:verifybar-button":{origin:"button",params:{},workers:"opendiscord:verifybar-button"}, + + "opendiscord:help-menu-switch":{origin:"button",params:{},workers:"opendiscord:update-help-menu"}, + "opendiscord:help-menu-previous":{origin:"button",params:{},workers:"opendiscord:update-help-menu"}, + "opendiscord:help-menu-next":{origin:"button",params:{},workers:"opendiscord:update-help-menu"}, + + "opendiscord:ticket-option":{origin:"button",params:{},workers:"opendiscord:ticket-option"}, + "opendiscord:role-option":{origin:"button",params:{},workers:"opendiscord:role-option"}, + "opendiscord:subpanel-option":{origin:"button",params:{},workers:"opendiscord:subpanel-option"}, + + "opendiscord:claim-ticket":{origin:"button",params:{},workers:"opendiscord:claim-ticket"}, + "opendiscord:unclaim-ticket":{origin:"button",params:{},workers:"opendiscord:unclaim-ticket"}, + "opendiscord:pin-ticket":{origin:"button",params:{},workers:"opendiscord:pin-ticket"}, + "opendiscord:unpin-ticket":{origin:"button",params:{},workers:"opendiscord:unpin-ticket"}, + "opendiscord:close-ticket":{origin:"button",params:{},workers:"opendiscord:close-ticket"}, + "opendiscord:reopen-ticket":{origin:"button",params:{},workers:"opendiscord:reopen-ticket"}, + "opendiscord:delete-ticket":{origin:"button",params:{},workers:"opendiscord:delete-ticket"}, + + "opendiscord:transcript-error-retry":{origin:"button",params:{},workers:"opendiscord:delete-ticket"|"opendiscord:logs"}, + "opendiscord:transcript-error-continue":{origin:"button",params:{},workers:"opendiscord:delete-ticket"|"opendiscord:logs"}, + "opendiscord:clear-continue":{origin:"button",params:{},workers:"opendiscord:clear-continue"}, +} + +/**## ODDropdownResponderManagerIdMappings `interface` + * A list of all available IDs in the default `ODDropdownResponderManager` class in `opendiscord`. + * It's used to generate typescript declarations for this class. + */ +export interface ODDropdownResponderManagerIdMappings extends api.ODDropdownResponderManagerIdConstraint { + "opendiscord:panel-dropdown":{origin:"dropdown",params:{},workers:"opendiscord:dropdown-ticket"|"opendiscord:dropdown-role"|"opendiscord:dropdown-subpanel"}, + "opendiscord:priority-dropdown":{origin:"dropdown",params:{},workers:"opendiscord:priority-dropdown"}, +} + +/**## ODModalResponderManagerIdMappings `interface` + * A list of all available IDs in the default `ODModalResponderManager` class in `opendiscord`. + * It's used to generate typescript declarations for this class. + */ +export interface ODModalResponderManagerIdMappings extends api.ODModalResponderManagerIdConstraint { + "opendiscord:ticket-questions":{origin:"modal",params:{},workers:"opendiscord:ticket-questions"}, + "opendiscord:close-ticket-reason":{origin:"modal",params:{},workers:"opendiscord:close-ticket-reason"}, + "opendiscord:reopen-ticket-reason":{origin:"modal",params:{},workers:"opendiscord:reopen-ticket-reason"}, + "opendiscord:delete-ticket-reason":{origin:"modal",params:{},workers:"opendiscord:delete-ticket-reason"}, + "opendiscord:claim-ticket-reason":{origin:"modal",params:{},workers:"opendiscord:claim-ticket-reason"}, + "opendiscord:unclaim-ticket-reason":{origin:"modal",params:{},workers:"opendiscord:unclaim-ticket-reason"}, + "opendiscord:pin-ticket-reason":{origin:"modal",params:{},workers:"opendiscord:pin-ticket-reason"}, + "opendiscord:unpin-ticket-reason":{origin:"modal",params:{},workers:"opendiscord:unpin-ticket-reason"}, +} + +/**## ODContextMenuResponderManagerIdMappings `interface` + * A list of all available IDs in the default `ODContextMenuResponderManager` class in `opendiscord`. + * It's used to generate typescript declarations for this class. + */ +export interface ODContextMenuResponderManagerIdMappings extends api.ODContextMenuResponderManagerIdConstraint { + //"opendiscord:example":{origin:"context-menu",params:{},workers:"opendiscord:example"}, +} + +/**## ODAutocompleteResponderManagerIdMappings `interface` + * A list of all available IDs in the default `ODAutocompleteResponderManager` class in `opendiscord`. + * It's used to generate typescript declarations for this class. + */ +export interface ODAutocompleteResponderManagerIdMappings extends api.ODAutocompleteResponderManagerIdConstraint { + "opendiscord:panel-id":{origin:"autocomplete",params:{},workers:"opendiscord:panel-id"}, + "opendiscord:option-id":{origin:"autocomplete",params:{},workers:"opendiscord:option-id"} +} + +///////////////////////////// +////// MAPPED MANAGERS ////// +///////////////////////////// + +/**## ODMappedCommandResponderManager `class + * A special class with types for the Open Ticket `ODCommandResponderManager` class. + */ +export class ODMappedCommandResponderManager extends api.ODCommandResponderManager {} + +/**## ODMappedButtonResponderManager `class + * A special class with types for the Open Ticket `ODButtonResponderManager` class. + */ +export class ODMappedButtonResponderManager extends api.ODButtonResponderManager {} + +/**## ODMappedDropdownResponderManager `class + * A special class with types for the Open Ticket `ODDropdownResponderManager` class. + */ +export class ODMappedDropdownResponderManager extends api.ODDropdownResponderManager {} + +/**## ODMappedModalResponderManager `class + * A special class with types for the Open Ticket `ODModalResponderManager` class. + */ +export class ODMappedModalResponderManager extends api.ODModalResponderManager {} + +/**## ODMappedContextMenuResponderManager `class + * A special class with types for the Open Ticket `ODContextMenuResponderManager` class. + */ +export class ODMappedContextMenuResponderManager extends api.ODContextMenuResponderManager {} + +/**## ODMappedAutocompleteResponderManager `class + * A special class with types for the Open Ticket `ODAutocompleteResponderManager` class. + */ +export class ODMappedAutocompleteResponderManager extends api.ODAutocompleteResponderManager {} + +/**## ODMappedResponderManager `class + * A special class with types for the Open Ticket `ODResponderManager` class. + */ +export class ODMappedResponderManager extends api.ODResponderManager {} \ No newline at end of file diff --git a/src/core/mappings/session.ts b/src/core/mappings/session.ts new file mode 100644 index 0000000..f9a9411 --- /dev/null +++ b/src/core/mappings/session.ts @@ -0,0 +1,21 @@ +/////////////////////////////////////// +//OPEN TICKET SESSION MAPPINGS +/////////////////////////////////////// +import * as api from "@open-discord-bots/framework/api" + +/**## ODSessionManagerIdMappings `interface` + * A list of all available IDs in the default `ODSessionManager` class in `opendiscord`. + * It's used to generate typescript declarations for this class. + */ +export interface ODSessionManagerIdMappings extends api.ODSessionManagerIdConstraint { + //"opendiscord:example-session":api.ODSession +} + +///////////////////////////// +////// MAPPED MANAGERS ////// +///////////////////////////// + +/**## ODMappedSessionManager `class + * A special class with types for the Open Ticket `ODSessionManager` class. + */ +export class ODMappedSessionManager extends api.ODSessionManager {} \ No newline at end of file diff --git a/src/core/mappings/startscreen.ts b/src/core/mappings/startscreen.ts new file mode 100644 index 0000000..1214828 --- /dev/null +++ b/src/core/mappings/startscreen.ts @@ -0,0 +1,28 @@ +/////////////////////////////////////// +//OPEN TICKET STARTSCREEN MAPPINGS +/////////////////////////////////////// +import * as api from "@open-discord-bots/framework/api" +import { ODLiveStatusManagerIdMappings } from "./console.js" + +/**## ODStartScreenManagerIdMappings `interface` + * A list of all available IDs in the default `ODStartScreenManager` class in `opendiscord`. + * It's used to generate typescript declarations for this class. + */ +export interface ODStartScreenManagerIdMappings extends api.ODStartScreenManagerIdConstraint { + "opendiscord:logo":api.ODStartScreenLogoComponent, + "opendiscord:header":api.ODStartScreenHeaderComponent, + "opendiscord:flags":api.ODStartScreenFlagsCategoryComponent, + "opendiscord:plugins":api.ODStartScreenPluginsCategoryComponent, + "opendiscord:stats":api.ODStartScreenPropertiesCategoryComponent, + "opendiscord:livestatus":api.ODStartScreenLiveStatusCategoryComponent>, + "opendiscord:logs":api.ODStartScreenCategoryComponent +} + +///////////////////////////// +////// MAPPED MANAGERS ////// +///////////////////////////// + +/**## ODMappedStartScreenManager `class + * A special class with types for the Open Ticket `ODStartScreenManager` class. + */ +export class ODMappedStartScreenManager extends api.ODStartScreenManager {} \ No newline at end of file diff --git a/src/core/mappings/state.ts b/src/core/mappings/state.ts new file mode 100644 index 0000000..e426828 --- /dev/null +++ b/src/core/mappings/state.ts @@ -0,0 +1,83 @@ +/////////////////////////////////////// +//OPEN TICKET STATE MAPPINGS +/////////////////////////////////////// +import * as api from "@open-discord-bots/framework/api" +import * as discord from "discord.js" +import { ODTicketClearFilter } from "../api/ticket.js" + +/**## ODStateManagerIdMappings `interface` + * A list of all available IDs in the default `ODStateManager` class in `opendiscord`. + * It's used to generate typescript declarations for this class. + */ +export interface ODStateManagerIdMappings extends api.ODStateManagerIdConstraint { + "opendiscord:interactive-message":ODInteractiveMessageState, + "opendiscord:clear-message":ODClearMessageState, + "opendiscord:panel-message":ODPanelMessageState, +} + +///////////////////////////// +////// MAPPED MANAGERS ////// +///////////////////////////// + +/**## ODMappedStateManager `class + * A special class with types for the Open Ticket `ODStateManager` class. + */ +export class ODMappedStateManager extends api.ODStateManager {} + +/**## ODInteractiveMessageState `class + * A special class with state types for interactive Open Ticket message. + */ +export class ODInteractiveMessageState extends api.ODState<{ + /**The method this message was generated with. */ + messageOrigin:"slash"|"text"|"button"|"dropdown"|"modal"|"other", + /**The type of message. Used when editing messages. */ + messageType:"ticket-message"|"close-message"|"reopen-message"|"autoclose-message"|"claim-message"|"unclaim-message"|"pin-message"|"unpin-message", + /**A reason this interactive message was generated. */ + messageReason?:string|null, + /**The original author of this interactive message. */ + messageAuthor?:string, + /**Additional data of this interactive message. */ + messageExtraData?:any, +},false,false> { + constructor(id:api.ODValidId,client:api.ODClientManager,database:api.ODDatabase){ + super(id,client,database,{}) + } +} + +/**## ODClearMessageState `class + * A special class with state types for the Open Ticket clear tickets message. + */ +export class ODClearMessageState extends api.ODState<{ + /**The method this message was generated with. */ + messageOrigin:"slash"|"text"|"other", + /**The clear filters. */ + clearFilter:ODTicketClearFilter, + /**The list of ticket channel names (e.g. `#ticket-1`) to be cleared. */ + clearChannelNameList:string[] +},false,true> { + constructor(id:api.ODValidId,client:api.ODClientManager,database:api.ODDatabase){ + super(id,client,database,{ + autodeleteOnRestart:true + }) + } +} + +/**## ODPanelMessageState `class + * A special class with state types for the Open Ticket panel message. + */ +export class ODPanelMessageState extends api.ODState<{ + /**The method this message was generated with. */ + messageOrigin:"slash"|"text"|"sub-panel"|"auto-update"|"other", + /**The Id of the panel associated with this message. */ + panelId:string, + /**A list of options available in this panel. (buttons or dropdown) */ + panelOptionIds:string[] + /**Should this panel be auto-updated on restart? */ + panelAutoUpdate:boolean, + /**Is this panel a sub-panel? */ + isSubPanel:boolean +},false,false> { + constructor(id:api.ODValidId,client:api.ODClientManager,database:api.ODDatabase){ + super(id,client,database,{}) + } +} \ No newline at end of file diff --git a/src/core/mappings/statistic.ts b/src/core/mappings/statistic.ts new file mode 100644 index 0000000..51b3f12 --- /dev/null +++ b/src/core/mappings/statistic.ts @@ -0,0 +1,143 @@ +/////////////////////////////////////// +//OPEN TICKET STATISTICS MAPPINGS +/////////////////////////////////////// +import * as api from "@open-discord-bots/framework/api" + +/**## ODStatisticManagerIdMappings `interface` + * A list of all available IDs in the default `ODStatisticManager` class in `opendiscord`. + * It's used to generate typescript declarations for this class. + */ +export interface ODStatisticManagerIdMappings extends api.ODStatisticManagerIdConstraint { + "opendiscord:global":ODGlobalStatisticScope, + "opendiscord:system":ODSystemStatisticScope, + "opendiscord:user":ODUserStatisticScope, + "opendiscord:ticket":ODTicketStatisticScope, + "opendiscord:participants":ODParticipantsStatisticScope, + "opendiscord:messages":ODMessagesStatisticScope, +} + +///////////////////////////////////////// +// STATISTICS MAPPINGS, CATEGORIES & TYPES +///////////////////////////////////////// + +/**## ODGlobalStatisticScopeIdMappings `interface` + * A list of all available IDs in the default `ODGlobalStatisticScope` class in `opendiscord`. + * It's used to generate typescript declarations for this class. + */ +export interface ODGlobalStatisticScopeIdMappings extends api.ODStatisticScopeIdConstraint { + "opendiscord:tickets-created":api.ODBaseStatistic, + "opendiscord:tickets-closed":api.ODBaseStatistic, + "opendiscord:tickets-deleted":api.ODBaseStatistic, + "opendiscord:tickets-reopened":api.ODBaseStatistic, + "opendiscord:tickets-autoclosed":api.ODBaseStatistic, + "opendiscord:tickets-autodeleted":api.ODBaseStatistic, + "opendiscord:tickets-claimed":api.ODBaseStatistic, + "opendiscord:tickets-pinned":api.ODBaseStatistic, + "opendiscord:tickets-moved":api.ODBaseStatistic, + "opendiscord:tickets-transferred":api.ODBaseStatistic, + "opendiscord:users-blacklisted":api.ODBaseStatistic, + "opendiscord:transcripts-created":api.ODBaseStatistic, + "opendiscord:ticket-volume":api.ODDynamicStatistic, + "opendiscord:average-tickets":api.ODDynamicStatistic, +} + +/**## ODSystemStatisticScopeIdMappings `interface` + * A list of all available IDs in the default `ODSystemStatisticScope` class in `opendiscord`. + * It's used to generate typescript declarations for this class. + */ +export interface ODSystemStatisticScopeIdMappings extends api.ODStatisticScopeIdConstraint { + "opendiscord:startup-date":api.ODDynamicStatistic, + "opendiscord:system-uptime":api.ODDynamicStatistic, + "opendiscord:version":api.ODDynamicStatistic +} + +/**## ODUserStatisticScopeIdMappings `interface` + * A list of all available IDs in the default `ODUserStatisticScope` class in `opendiscord`. + * It's used to generate typescript declarations for this class. + */ +export interface ODUserStatisticScopeIdMappings extends api.ODStatisticScopeIdConstraint { + "opendiscord:name":api.ODDynamicStatistic, + "opendiscord:role":api.ODDynamicStatistic, + "opendiscord:tickets-created":api.ODBaseStatistic, + "opendiscord:tickets-closed":api.ODBaseStatistic, + "opendiscord:tickets-deleted":api.ODBaseStatistic, + "opendiscord:tickets-reopened":api.ODBaseStatistic, + "opendiscord:tickets-claimed":api.ODBaseStatistic, + "opendiscord:tickets-pinned":api.ODBaseStatistic, + "opendiscord:tickets-moved":api.ODBaseStatistic, + "opendiscord:tickets-transferred":api.ODBaseStatistic, + "opendiscord:users-blacklisted":api.ODBaseStatistic, + "opendiscord:transcripts-created":api.ODBaseStatistic, + "opendiscord:current-tickets":api.ODDynamicStatistic, +} + +/**## ODTicketStatisticScopeIdMappings `interface` + * A list of all available IDs in the default `ODTicketStatisticScope` class in `opendiscord`. + * It's used to generate typescript declarations for this class. + */ +export interface ODTicketStatisticScopeIdMappings extends api.ODStatisticScopeIdConstraint { + "opendiscord:name":api.ODDynamicStatistic, + "opendiscord:status":api.ODDynamicStatistic, + "opendiscord:claimed":api.ODDynamicStatistic, + "opendiscord:pinned":api.ODDynamicStatistic, + "opendiscord:creation-date":api.ODDynamicStatistic, + "opendiscord:creator":api.ODDynamicStatistic, + "opendiscord:ticket-age":api.ODDynamicStatistic, + "opendiscord:response-time":api.ODDynamicStatistic, + "opendiscord:resolution-time":api.ODDynamicStatistic, +} + +/**## ODParticipantsStatisticScopeIdMappings `interface` + * A list of all available IDs in the default `ODParticipantsStatisticScope` class in `opendiscord`. + * It's used to generate typescript declarations for this class. + */ +export interface ODParticipantsStatisticScopeIdMappings extends api.ODStatisticScopeIdConstraint { + "opendiscord:participants":api.ODDynamicStatistic +} + +/**## ODMessagesStatisticScopeIdMappings `interface` + * A list of all available IDs in the default `ODMessagesStatisticScope` class in `opendiscord`. + * It's used to generate typescript declarations for this class. + */ +export interface ODMessagesStatisticScopeIdMappings extends api.ODStatisticScopeIdConstraint { + "opendiscord:count":api.ODDynamicStatistic +} + +///////////////////////////// +////// MAPPED MANAGERS ////// +///////////////////////////// + +/**## ODMappedStatisticManager `class + * A special class with types for the Open Ticket `ODStatisticManager` class. + */ +export class ODMappedStatisticManager extends api.ODStatisticManager {} + +/**## ODGlobalStatisticScope `class + * A special class with types for the Open Ticket `Global` statistics category/scope. + */ +export class ODGlobalStatisticScope extends api.ODStatisticGlobalScope {} + +/**## ODSystemStatisticScope `class + * A special class with types for the Open Ticket `System` statistics category/scope. + */ +export class ODSystemStatisticScope extends api.ODStatisticGlobalScope {} + +/**## ODUserStatisticScope `class + * A special class with types for the Open Ticket `User` statistics category/scope. + */ +export class ODUserStatisticScope extends api.ODStatisticScope {} + +/**## ODTicketStatisticScope `class + * A special class with types for the Open Ticket `Ticket` statistics category/scope. + */ +export class ODTicketStatisticScope extends api.ODStatisticScope {} + +/**## ODParticipantsStatisticScope `class + * A special class with types for the Open Ticket `Participants` statistics category/scope. + */ +export class ODParticipantsStatisticScope extends api.ODStatisticScope {} + +/**## ODMessagesStatisticScope `class + * A special class with types for the Open Ticket `Messages` statistics category/scope. + */ +export class ODMessagesStatisticScope extends api.ODStatisticScope {} \ No newline at end of file diff --git a/src/core/mappings/task.ts b/src/core/mappings/task.ts new file mode 100644 index 0000000..ab9d79e --- /dev/null +++ b/src/core/mappings/task.ts @@ -0,0 +1,37 @@ +/////////////////////////////////////// +//OPEN TICKET TASK MAPPINGS +/////////////////////////////////////// +import * as api from "@open-discord-bots/framework/api" + +/**## ODTaskManagerIdMappings `interface` + * A list of all available IDs in the default `ODTaskManager` class in `opendiscord`. + * It's used to generate typescript declarations for this class. + */ +export interface ODTaskManagerIdMappings extends api.ODTaskManagerIdConstraint { + "opendiscord:command-error-handling":api.ODTask, + "opendiscord:start-listening-interactions":api.ODTask, + "opendiscord:panel-database-cleaner":api.ODTask, + "opendiscord:suffix-database-cleaner":api.ODTask, + "opendiscord:option-database-cleaner":api.ODTask, + "opendiscord:user-database-cleaner":api.ODTask, + "opendiscord:ticket-database-cleaner":api.ODTask, + "opendiscord:transcript-database-cleaner":api.ODTask, + "opendiscord:panel-auto-update":api.ODTask, + "opendiscord:ticket-saver":api.ODTask, + "opendiscord:blacklist-saver":api.ODTask, + "opendiscord:auto-role-on-join":api.ODTask, + "opendiscord:autoclose-timeout":api.ODTask, + "opendiscord:autoclose-leave":api.ODTask, + "opendiscord:autodelete-timeout":api.ODTask, + "opendiscord:autodelete-leave":api.ODTask, + "opendiscord:ticket-anti-busy":api.ODTask, +} + +///////////////////////////// +////// MAPPED MANAGERS ////// +///////////////////////////// + +/**## ODMappedTaskManager `class + * A special class with types for the Open Ticket `ODTaskManager` class. + */ +export class ODMappedTaskManager extends api.ODTaskManager {} \ No newline at end of file diff --git a/src/core/mappings/verifybar.ts b/src/core/mappings/verifybar.ts new file mode 100644 index 0000000..267d422 --- /dev/null +++ b/src/core/mappings/verifybar.ts @@ -0,0 +1,37 @@ +/////////////////////////////////////// +//OPEN TICKET VERIFYBAR MAPPINGS +/////////////////////////////////////// +import * as api from "@open-discord-bots/framework/api" + +/**## ODVerifyBarManagerIdMappings `interface` + * A list of all available IDs in the default `ODVerifyBarManager` class in `opendiscord`. + * It's used to generate typescript declarations for this class. + */ +export interface ODVerifyBarManagerIdMappings extends api.ODVerifyBarManagerIdConstraint { + "opendiscord:close-ticket":api.ODVerifyBar<"accept"|"cancel"|"accept-with-reason","opendiscord:close-ticket">, + "opendiscord:reopen-ticket":api.ODVerifyBar<"accept"|"cancel"|"accept-with-reason","opendiscord:reopen-ticket">, + "opendiscord:delete-ticket":api.ODVerifyBar<"accept"|"cancel"|"accept-with-reason"|"accept-without-transcript","opendiscord:delete-ticket">, + "opendiscord:claim-ticket":api.ODVerifyBar<"accept"|"cancel"|"accept-with-reason","opendiscord:claim-ticket">, + "opendiscord:unclaim-ticket":api.ODVerifyBar<"accept"|"cancel"|"accept-with-reason","opendiscord:unclaim-ticket">, + "opendiscord:pin-ticket":api.ODVerifyBar<"accept"|"cancel"|"accept-with-reason","opendiscord:pin-ticket">, + "opendiscord:unpin-ticket":api.ODVerifyBar<"accept"|"cancel"|"accept-with-reason","opendiscord:unpin-ticket">, +} + +/**## ODVerifyButtonId `enum` + * Frequently used button ids in Open Ticket verify bars. + */ +export enum ODVerifyButtonId { + Cancel="cancel", + Accept="accept", + AcceptWithReason="accept-with-reason", + AcceptWithoutTranscript="accept-without-transcript" +} + +///////////////////////////// +////// MAPPED MANAGERS ////// +///////////////////////////// + +/**## ODMappedVerifyBarManager `class + * A special class with types for the Open Ticket `ODVerifyBarManager` class. + */ +export class ODMappedVerifyBarManager extends api.ODVerifyBarManager {} \ No newline at end of file diff --git a/src/core/startup/dump.ts b/src/core/startup/dump.ts deleted file mode 100644 index 5ec486b..0000000 --- a/src/core/startup/dump.ts +++ /dev/null @@ -1,49 +0,0 @@ -import {opendiscord, api, utilities} from "../../index" -import * as discord from "discord.js" -import * as fs from "fs" - - -/** WHAT IS THIS?? - * This is the '!OPENTICKET:dump' command. - * It's a utility command which can only be used by the creator of Open Ticket or the owner of the bot. - * This command will send the `otdebug.txt` file in DM. It's not dangerous as the `otdebug.txt` file doesn't contain any sensitive data (only logs). - * - * WHY DOES IT EXIST?? - * This command can be used to quickly get the `otdebug.txt` file without having access to the hosting - * in case you're helping someone with setting up (or debugging) Open Ticket. - * - * CAN I DISABLE IT?? - * If you want to turn it off, you can always do it below this message! - */ - -///////// DISABLE DUMP COMMAND ///////// -const disableDumpCommand = false -//////////////////////////////////////// - -export const loadDumpCommand = () => { - if (disableDumpCommand) return - opendiscord.client.textCommands.add(new api.ODTextCommand("opendiscord:dump",{ - allowBots:false, - guildPermission:true, - dmPermission:true, - name:"dump", - prefix:"!OPENTICKET:" - })) - - opendiscord.client.textCommands.onInteraction("!OPENTICKET:","dump",async (msg) => { - if (msg.author.id == "779742674932072469" || opendiscord.permissions.hasPermissions("developer",await opendiscord.permissions.getPermissions(msg.author,msg.channel,null))){ - //user is bot owner OR creator of Open Ticket :) - opendiscord.log("Dumped otdebug.txt!","system",[ - {key:"user",value:msg.author.username}, - {key:"id",value:msg.author.id} - ]) - const debug = fs.readFileSync("./otdebug.txt") - - if (msg.channel.type != discord.ChannelType.GroupDM) msg.channel.send({content:"## The `otdebug.txt` dump is available!",files:[ - new discord.AttachmentBuilder(debug) - .setName("otdebug.txt") - .setDescription("The Open Ticket debug dump!") - ]}) - } - }) -} \ No newline at end of file diff --git a/src/core/startup/init.ts b/src/core/startup/init.ts deleted file mode 100644 index 18bb6e5..0000000 --- a/src/core/startup/init.ts +++ /dev/null @@ -1,281 +0,0 @@ -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(1) - } - 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("@discordjs/rest",true) -moduleInstalled("discord.js",true) -moduleInstalled("ansis",true) -moduleInstalled("formatted-json-stringify",true) -moduleInstalled("typescript",true) -moduleInstalled("terminal-kit",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 -import ansis from "ansis" //import ansis for usage in initialization - -export const opendiscord = new api.ODMain() -console.log("\n--------------------------- OPEN TICKET STARTUP ---------------------------") -opendiscord.log("Logging system activated!","system") -opendiscord.debug.debug("Using Node.js "+process.version+"!") - -try{ - const packageJson = JSON.parse(fs.readFileSync("./package.json").toString()) - opendiscord.debug.debug("Using discord.js "+packageJson.dependencies["discord.js"]+"!") - opendiscord.debug.debug("Using @discordjs/rest "+packageJson.dependencies["@discordjs/rest"]+"!") - opendiscord.debug.debug("Using ansis "+packageJson.dependencies["ansis"]+"!") - opendiscord.debug.debug("Using formatted-json-stringify "+packageJson.dependencies["formatted-json-stringify"]+"!") - opendiscord.debug.debug("Using terminal-kit "+packageJson.dependencies["terminal-kit"]+"!") - opendiscord.debug.debug("Using typescript "+packageJson.dependencies["typescript"]+"!") -}catch{ - opendiscord.debug.debug("Failed to fetch module versions!") -} - -const timer = (ms:number): Promise => { - return new Promise((resolve) => { - setTimeout(() => { - resolve() - },ms) - }) -} - -export 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 - /**## emojiTitle `utility function` - * Use this function to create a title with an emoji before/after the text. The style & divider are set in `opendiscord.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 snippet of code asyncronous without creating a separate function for it! - */ - runAsync(func:() => Promise): void - /**## timedAwait `utility function` - * Use this function to await a promise but reject after the certain timeout has been reached. - */ - timedAwait>(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): Promise - /**## getLongestLength `utility function` - * Get the length of the longest string in the array. - */ - getLongestLength(text:string[]): number - /**## 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,afterInitFunc:() => void|Promise) => ODVersionMigration, - /**## ordinalNumber `utility function` - * Get a human readable ordinal number (e.g. 1st, 2nd, 3rd, 4th, ...) from a Javascript number. - */ - ordinalNumber(num:number): string, - /**## trimEmojis `utility function` - * Trim/remove all emoji's from a Javascript string. - */ - trimEmojis(text:string): string, -} - -/**## 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! - */ -export class ODVersionMigration { - /**The version to migrate data to */ - version: api.ODVersion - /**The migration function */ - #func: () => void|Promise - /**The migration function */ - #afterInitFunc: () => void|Promise - - constructor(version:api.ODVersion,func:() => void|Promise,afterInitFunc:() => void|Promise){ - this.version = version - this.#func = func - this.#afterInitFunc = afterInitFunc - } - /**Run this version migration as a plugin. Returns `false` when something goes wrong. */ - async migrate(): Promise { - try{ - await this.#func() - return true - }catch(err){ - process.emit("uncaughtException",err) - return false - } - } - /**Run this version migration as a plugin (after other plugins have loaded). Returns `false` when something goes wrong. */ - async migrateAfterInit(): Promise { - try{ - await this.#afterInitFunc() - return true - }catch(err){ - process.emit("uncaughtException",err) - return false - } - } -} - -export const utilities: ODUtilities = { - project:"openticket", - isBeta:false, - moduleInstalled:(id:string) => { - return moduleInstalled(id,false) - }, - timer, - emojiTitle(emoji:string, text:string){ - const style = opendiscord.defaults.getDefault("emojiTitleStyle") - const divider = opendiscord.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(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()+1}/${date.getFullYear()} ${date.getHours()}:${date.getMinutes()}:${date.getSeconds()}` - }, - async asyncReplace(text,regex,func): Promise { - const promises: Promise[] = [] - 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 - }, - getLongestLength(texts:string[]): number { - return Math.max(...texts.map((t) => ansis.strip(t).length)) - }, - easterEggs:{ - /* THANK YOU TO ALL OUR CONTRIBUTORS!!! */ - creator:"779742674932072469", //DJj123dj - translators:[ - "779742674932072469", //DJj123dj - "574172558006681601", //Sanke - "540639725300613136", //Guillee.3 - "547231585368539136", //Mods HD - "664934139954331649", //SpyEye - "498055992962187264", //Redactado - "912052735950618705", //T0miiis - "366673202610569227", //johusens - "360780292853858306", //David.3 - "950611418389024809", //Sarcastic - "461603955517161473", //Maurizo - "465111430274875402", //The_Gamer - "586376952470831104", //Erxg - "226695254433202176", //Mkevas - "437695615095275520", //NoOneNook - "530047191222583307", //Anderskiy - "719072181631320145", //ToStam - "1172870906377408512", //Stragar - "1084794575945744445", //Sasanwm - "449613814049275905", //Benzorich - "905373133085741146", //Ronalds - "918504977369018408", //Palestinian - "807970841035145216", //Kornel0706 - "1198883915826475080", //Nova - "669988226819162133", //Danoglez - "1313597620996018271", //Fraden1 - "547809968145956884", //TsgIndrius - "264120132660363267", //Quiradon - "1272034143777329215", //NotMega - "LOREMIPSUM", //TODO - ] - }, - ODVersionMigration, - ordinalNumber(num:number){ - const i = Math.abs(Math.round(num)) - const cent = i % 100 - if (cent >= 10 && cent <= 20) return i+'th' - const dec = i % 10 - if (dec === 1) return i+'st' - if (dec === 2) return i+'nd' - if (dec === 3) return i+'rd' - return i+'th' - }, - trimEmojis(text){ - return text.replace(/(\p{Extended_Pictographic}(?:\uFE0F|\uFE0E)?(?:\u200D\p{Extended_Pictographic}(?:\uFE0F|\uFE0E)?)*)/gu,"") - }, -} \ No newline at end of file diff --git a/src/core/startup/manageMigration.ts b/src/core/startup/manageMigration.ts index cd0467b..5bd5eec 100644 --- a/src/core/startup/manageMigration.ts +++ b/src/core/startup/manageMigration.ts @@ -1,9 +1,24 @@ -import {opendiscord, api, utilities} from "../../index" +import {opendiscord, api, utilities} from "../../index.js" import fs from "fs" +import path from "path" + +/**Check if the no-migration flag is active. */ +function isMigrationAllowedFromFlag(){ + return (!process.argv.includes("--no-migration") && !process.argv.includes("-nm")) +} + +/**Read the global.json database raw to detect the last version of the bot. */ +function getRawLastVersion(){ + const isDevDatabase = process.argv.includes("--dev-database") || process.argv.includes("-dd") + const globalDatabaseLocation = path.join(process.cwd(),(isDevDatabase) ? "./devdatabase/global.json" : "./database/global.json") + const rawData: api.ODJsonDatabaseStructure = JSON.parse(fs.readFileSync(globalDatabaseLocation).toString()) + const lastVersion = rawData.find((d) => d.category == "opendiscord:last-version" && d.key == "opendiscord:version")?.value ?? null + return lastVersion as string|null +} /**Check if migration is required. Returns the last version used in the database. */ async function isMigrationRequired(): Promise { - const rawVersion = await opendiscord.databases.get("opendiscord:global").get("opendiscord:last-version","opendiscord:version") + const rawVersion = getRawLastVersion() if (!rawVersion) return false const version = api.ODVersion.fromString("opendiscord:last-version",rawVersion) if (opendiscord.versions.get("opendiscord:version").compare(version) == "higher"){ @@ -20,47 +35,6 @@ async function saveAllVersionsToDatabase(){ }) } -export const loadVersionMigrationSystem = async () => { - //ENTER MIGRATION CONTEXT - await preloadMigrationContext() - - const lastVersion = await isMigrationRequired() - - //save last version to database (OR set to current version if no migration is required) - opendiscord.versions.add(lastVersion ? lastVersion : api.ODVersion.fromString("opendiscord:last-version",opendiscord.versions.get("opendiscord:version").toString())) - - if (lastVersion && !opendiscord.flags.get("opendiscord:no-migration").value){ - //MIGRATION IS REQUIRED - opendiscord.log("Detected old data!","info") - opendiscord.log("Starting closed API context...","debug") - await utilities.timer(600) - opendiscord.log("Migrating data to new version...","debug") - await loadAllVersionMigrations(lastVersion) - opendiscord.log("Stopping closed API context...","debug") - await utilities.timer(400) - opendiscord.log("All data is now up to date!","info") - await utilities.timer(200) - console.log("---------------------------------------------------------------------") - } - saveAllVersionsToDatabase() - - //DEFAULT FLAGS - if (opendiscord.flags.exists("opendiscord:no-plugins") && opendiscord.flags.get("opendiscord:no-plugins").value) opendiscord.defaults.setDefault("pluginLoading",false) - if (opendiscord.flags.exists("opendiscord:soft-plugins") && opendiscord.flags.get("opendiscord:soft-plugins").value) opendiscord.defaults.setDefault("softPluginLoading",true) - if (opendiscord.flags.exists("opendiscord:crash") && opendiscord.flags.get("opendiscord:crash").value) opendiscord.defaults.setDefault("crashOnError",true) - if (opendiscord.flags.exists("opendiscord:force-slash-update") && opendiscord.flags.get("opendiscord:force-slash-update").value){ - opendiscord.defaults.setDefault("forceSlashCommandRegistration",true) - opendiscord.defaults.setDefault("forceContextMenuRegistration",true) - } - if (opendiscord.flags.exists("opendiscord:silent") && opendiscord.flags.get("opendiscord:silent").value) opendiscord.console.silent = true - - - //LEAVE MIGRATION CONTEXT - await unloadMigrationContext() - - return lastVersion -} - /**Initialize the migration context by loading the built-in flags, configs & databases. */ async function preloadMigrationContext(){ opendiscord.debug.debug("-- MIGRATION CONTEXT START --") @@ -73,6 +47,52 @@ async function preloadMigrationContext(){ opendiscord.debug.visible = true } +export async function loadVersionMigrationSystem(){ + const lastVersion = await isMigrationRequired() + + //save last version in version manager (OR set to current version if no migration is required) + opendiscord.versions.add(lastVersion ? lastVersion : api.ODVersion.fromString("opendiscord:last-version",opendiscord.versions.get("opendiscord:version").toString())) + + //MIGRATION IS REQUIRED + if (lastVersion && isMigrationAllowedFromFlag()){ + //BEFORE STARTUP MIGRATION + opendiscord.log("Detected old data!","info") + await loadBeforeStartupMigrations(lastVersion) + } + + //ENTER MIGRATION CONTEXT (must be separate for flags to work) + await preloadMigrationContext() + + if (lastVersion && isMigrationAllowedFromFlag()){ + //CONTEXT MIGRATION + opendiscord.log("Starting restricted API context...","debug") + await utilities.timer(600) + opendiscord.log("Migrating data to new version...","debug") + await loadContextMigrations(lastVersion) + opendiscord.log("Stopping restricted API context...","debug") + await utilities.timer(400) + opendiscord.log("All data is now up to date!","info") + await utilities.timer(200) + console.log("---------------------------------------------------------------------") + } + saveAllVersionsToDatabase() + + //SET FUSES & PROPERTIES OF SPECIAL FLAGS + if (opendiscord.flags.exists("opendiscord:no-plugins") && opendiscord.flags.get("opendiscord:no-plugins").value) opendiscord.sharedFuses.setFuse("pluginLoading",false) + if (opendiscord.flags.exists("opendiscord:soft-plugins") && opendiscord.flags.get("opendiscord:soft-plugins").value) opendiscord.sharedFuses.setFuse("softPluginLoading",true) + if (opendiscord.flags.exists("opendiscord:crash") && opendiscord.flags.get("opendiscord:crash").value) opendiscord.sharedFuses.setFuse("crashOnError",true) + if (opendiscord.flags.exists("opendiscord:force-slash-update") && opendiscord.flags.get("opendiscord:force-slash-update").value){ + opendiscord.sharedFuses.setFuse("forceSlashCommandRegistration",true) + opendiscord.sharedFuses.setFuse("forceContextMenuRegistration",true) + } + if (opendiscord.flags.exists("opendiscord:silent") && opendiscord.flags.get("opendiscord:silent").value) opendiscord.console.silent = true + + //LEAVE MIGRATION CONTEXT + await unloadMigrationContext() + + return lastVersion +} + /**Unload the migration context to start the bot normally. */ async function unloadMigrationContext(){ opendiscord.debug.visible = false @@ -98,8 +118,8 @@ function createMigrationBackup(){ else fs.cpSync("./database/","./.backup/database/",{force:true,recursive:true}) } -/**Execute all version migration functions which are handled in the restricted migration context. */ -async function loadAllVersionMigrations(lastVersion:api.ODVersion){ +/**Execute all version migration functions which are handled before any flags, configs or databases are loaded. */ +async function loadBeforeStartupMigrations(lastVersion:api.ODVersion){ const migrations = (await import("./migration.js")).migrations migrations.sort((a,b) => { const comparison = a.version.compare(b.version) @@ -114,10 +134,36 @@ async function loadAllVersionMigrations(lastVersion:api.ODVersion){ for (const migration of migrations){ if (migration.version.compare(lastVersion) == "higher"){ - const success = await migration.migrate() + const success = await migration.migrateBeforeStartup() if (success) opendiscord.log("Migrated data to "+migration.version.toString()+"!","debug",[ {key:"success",value:success ? "true" : "false"}, - {key:"afterInit",value:"false"} + {key:"type",value:"before-startup"} + ]) + else throw new api.ODSystemError("Migration Error: Unable to migrate database & config to the new version of the bot.") + } + } +} + +/**Execute all version migration functions which are handled in the restricted migration context. */ +async function loadContextMigrations(lastVersion:api.ODVersion){ + const migrations = (await import("./migration.js")).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 + }) + if (migrations.length > 0){ + //create backup of config & database + createMigrationBackup() + } + + for (const migration of migrations){ + if (migration.version.compare(lastVersion) == "higher"){ + const success = await migration.migrateInContext() + if (success) opendiscord.log("Migrated data to "+migration.version.toString()+"!","debug",[ + {key:"success",value:success ? "true" : "false"}, + {key:"type",value:"restricted-context"} ]) else throw new api.ODSystemError("Migration Error: Unable to migrate database & config to the new version of the bot.") } @@ -125,7 +171,7 @@ async function loadAllVersionMigrations(lastVersion:api.ODVersion){ } /**Execute all version migration functions which are handled in the normal startup sequence. */ -export async function loadAllAfterInitVersionMigrations(lastVersion:api.ODVersion){ +export async function loadAfterStartupMigrations(lastVersion:api.ODVersion){ const migrations = (await import("./migration.js")).migrations migrations.sort((a,b) => { const comparison = a.version.compare(b.version) @@ -140,10 +186,10 @@ export async function loadAllAfterInitVersionMigrations(lastVersion:api.ODVersio for (const migration of migrations){ if (migration.version.compare(lastVersion) == "higher"){ - const success = await migration.migrateAfterInit() + const success = await migration.migrateAfterStartup() if (success) opendiscord.log("Migrated data to "+migration.version.toString()+"!","debug",[ {key:"success",value:success ? "true" : "false"}, - {key:"afterInit",value:"true"} + {key:"type",value:"after-startup"} ]) else throw new api.ODSystemError("Migration Error: Unable to migrate database & config to the new version of the bot.") } diff --git a/src/core/startup/migration.ts b/src/core/startup/migration.ts index 3ea5521..fe929d8 100644 --- a/src/core/startup/migration.ts +++ b/src/core/startup/migration.ts @@ -1,147 +1,421 @@ -import {opendiscord, api, utilities} from "../../index" +import { opendiscord, api, utilities } from "../../index.js" +import fs from "fs" +import path from "path" export const migrations = [ //MIGRATE TO v4.0.0 - new utilities.ODVersionMigration(api.ODVersion.fromString("opendiscord:version","v4.0.0"),async () => {},async () => {}), + new api.ODVersionMigration(api.ODVersion.fromString("opendiscord:version","v4.0.0"),{}), //MIGRATE TO v4.0.1 - new utilities.ODVersionMigration(api.ODVersion.fromString("opendiscord:version","v4.0.1"),async () => {},async () => { - //AFTER INIT MIGRATION - - //add opendiscord:panel-message properties for all existing panels. - const globalDatabase = opendiscord.databases.get("opendiscord:global") - for (const panel of (await globalDatabase.getCategory("opendiscord:panel-update") ?? [])){ - globalDatabase.set("opendiscord:panel-message",panel.key,panel.value) + new api.ODVersionMigration(api.ODVersion.fromString("opendiscord:version","v4.0.1"),{ + afterStartupMigrate:async () => { + //add opendiscord:panel-message properties for all existing panels. + const globalDatabase = opendiscord.databases.get("opendiscord:global") + for (const panel of (await globalDatabase.getCategory("opendiscord:panel-update") ?? [])){ + globalDatabase.set("opendiscord:panel-message",panel.key,panel.value) + } } }), //MIGRATE TO v4.0.2 - new utilities.ODVersionMigration(api.ODVersion.fromString("opendiscord:version","v4.0.2"),async () => {},async () => {}), + new api.ODVersionMigration(api.ODVersion.fromString("opendiscord:version","v4.0.2"),{}), //MIGRATE TO v4.0.3 - new utilities.ODVersionMigration(api.ODVersion.fromString("opendiscord:version","v4.0.3"),async () => {},async () => {}), + new api.ODVersionMigration(api.ODVersion.fromString("opendiscord:version","v4.0.3"),{}), //MIGRATE TO v4.0.4 - new utilities.ODVersionMigration(api.ODVersion.fromString("opendiscord:version","v4.0.4"),async () => {},async () => {}), + new api.ODVersionMigration(api.ODVersion.fromString("opendiscord:version","v4.0.4"),{}), //MIGRATE TO v4.0.5 - new utilities.ODVersionMigration(api.ODVersion.fromString("opendiscord:version","v4.0.5"),async () => {},async () => {}), + new api.ODVersionMigration(api.ODVersion.fromString("opendiscord:version","v4.0.5"),{}), //MIGRATE TO v4.0.6 - new utilities.ODVersionMigration(api.ODVersion.fromString("opendiscord:version","v4.0.6"),async () => {},async () => {}), + new api.ODVersionMigration(api.ODVersion.fromString("opendiscord:version","v4.0.6"),{}), //MIGRATE TO v4.0.7 - new utilities.ODVersionMigration(api.ODVersion.fromString("opendiscord:version","v4.0.7"),async () => {},async () => {}), + new api.ODVersionMigration(api.ODVersion.fromString("opendiscord:version","v4.0.7"),{}), //MIGRATE TO v4.1.0 - new utilities.ODVersionMigration(api.ODVersion.fromString("opendiscord:version","v4.1.0"),async () => {},async () => { - //AFTER INIT MIGRATION + new api.ODVersionMigration(api.ODVersion.fromString("opendiscord:version","v4.1.0"),{ + afterStartupMigrate:async () => { + //migrate config + const generalConfig = opendiscord.configs.get("opendiscord:general") + const optionConfig = opendiscord.configs.get("opendiscord:options") - //migrate config - const generalConfig = opendiscord.configs.get("opendiscord:general") - const optionConfig = opendiscord.configs.get("opendiscord:options") + if (!generalConfig.data.status.state){ + //only migrate config when it hasn't been done manually by the user. - if (!generalConfig.data.status.state){ - //only migrate config when it hasn't been done manually by the user. + if (!generalConfig.data["_INFO"]) throw new api.ODSystemError("Couldn't find general.json '_INFO' category.") + generalConfig.data["_INFO"].version = "open-ticket-v4.1.0" - if (!generalConfig.data._INFO) throw new api.ODSystemError("Couldn't find general.json '_INFO' category.") - generalConfig.data._INFO.version = "open-ticket-v4.1.0" + if (!generalConfig.data.status) throw new api.ODSystemError("Couldn't find general.json 'status' category.") + generalConfig.data.status.mode = generalConfig.data.status["status"] ?? "online" + generalConfig.data.status.state = "" + delete generalConfig.data.status["status"] - if (!generalConfig.data.status) throw new api.ODSystemError("Couldn't find general.json 'status' category.") - generalConfig.data.status.mode = generalConfig.data.status["status"] ?? "online" - generalConfig.data.status.state = "" - delete generalConfig.data.status["status"] + if (!generalConfig.data["system"]) throw new api.ODSystemError("Couldn't find general.json 'system' category.") + generalConfig.data["system"].displayFieldsWithQuestions = false + generalConfig.data["system"].showGlobalAdminsInPanelRoles = false + generalConfig.data["system"].alwaysShowReason = false + generalConfig.data["system"].pinEmoji = "📌" + generalConfig.data["system"].askPriorityOnTicketCreation = false + generalConfig.data["system"].disableAutocloseAfterReopen = true + generalConfig.data["system"].autodeleteRequiresClosedTicket = true + generalConfig.data["system"].adminOnlyDeleteWithoutTranscript = true + generalConfig.data["system"].allowCloseBeforeMessage = false + generalConfig.data["system"].allowCloseBeforeAdminMessage = true + generalConfig.data["system"].pinFirstTicketMessage = false - if (!generalConfig.data.system) throw new api.ODSystemError("Couldn't find general.json 'system' category.") - generalConfig.data.system.displayFieldsWithQuestions = false - generalConfig.data.system.showGlobalAdminsInPanelRoles = false - generalConfig.data.system.alwaysShowReason = false - generalConfig.data.system.pinEmoji = "📌" - generalConfig.data.system.askPriorityOnTicketCreation = false - generalConfig.data.system.disableAutocloseAfterReopen = true - generalConfig.data.system.autodeleteRequiresClosedTicket = true - generalConfig.data.system.adminOnlyDeleteWithoutTranscript = true - generalConfig.data.system.allowCloseBeforeMessage = false - generalConfig.data.system.allowCloseBeforeAdminMessage = true - generalConfig.data.system.pinFirstTicketMessage = false - - generalConfig.data.system.channelTopic = { - showOptionName:true, - showOptionDescription:false, - showOptionTopic:true, - showPriority:false, - showClosed:true, - showClaimed:false, - showPinned:false, - showCreator:false, - showParticipants:false - } - - if (!generalConfig.data.system.permissions) throw new api.ODSystemError("Couldn't find general.json 'system.permissions' category.") - generalConfig.data.system.permissions.transfer = "admin" - generalConfig.data.system.permissions.topic = "admin" - generalConfig.data.system.permissions.priority = "admin" - - if (!generalConfig.data.system.messages) throw new api.ODSystemError("Couldn't find general.json 'system.messages' category.") - generalConfig.data.system.messages.transferring = {dm:false,logs:true} - generalConfig.data.system.messages.topicChange = {dm:false,logs:true} - generalConfig.data.system.messages.priorityChange = {dm:false,logs:true} - generalConfig.data.system.messages.reactionRole = generalConfig.data.system.messages["roleAdding"] ?? {dm:false,logs:true} - delete generalConfig.data.system.messages["roleAdding"] - delete generalConfig.data.system.messages["roleRemoving"] - - for (const option of optionConfig.data){ - if (option.type != "ticket") continue - option.channel.topic = option.channel["description"] ?? "" - delete option.channel["description"] - - option.slowMode = { - enabled:false, - slowModeSeconds:20 + generalConfig.data["system"].channelTopic = { + showOptionName:true, + showOptionDescription:false, + showOptionTopic:true, + showPriority:false, + showClosed:true, + showClaimed:false, + showPinned:false, + showCreator:false, + showParticipants:false } + + if (!generalConfig.data["system"].permissions) throw new api.ODSystemError("Couldn't find general.json 'system.permissions' category.") + generalConfig.data["system"].permissions.transfer = "admin" + generalConfig.data["system"].permissions.topic = "admin" + generalConfig.data["system"].permissions.priority = "admin" + + if (!generalConfig.data["system"].messages) throw new api.ODSystemError("Couldn't find general.json 'system.messages' category.") + generalConfig.data["system"].messages.transferring = {dm:false,logs:true} + generalConfig.data["system"].messages.topicChange = {dm:false,logs:true} + generalConfig.data["system"].messages.priorityChange = {dm:false,logs:true} + generalConfig.data["system"].messages.reactionRole = generalConfig.data["system"].messages["roleAdding"] ?? {dm:false,logs:true} + delete generalConfig.data["system"].messages["roleAdding"] + delete generalConfig.data["system"].messages["roleRemoving"] + + for (const option of optionConfig.data){ + if (option.type != "ticket") continue + option.channel.topic = option.channel["description"] ?? "" + delete option.channel["description"] + + option.slowMode = { + enabled:false, + slowModeSeconds:20 + } + } + + await generalConfig.save() + await optionConfig.save() } - await generalConfig.save() - await optionConfig.save() - } + //migrate database + const optionDatabase = opendiscord.databases.get("opendiscord:options") + const ticketDatabase = opendiscord.databases.get("opendiscord:tickets") - //migrate database - const optionDatabase = opendiscord.databases.get("opendiscord:options") - const ticketDatabase = opendiscord.databases.get("opendiscord:tickets") + for (const option of (await optionDatabase.getCategory("opendiscord:used-option") ?? [])){ + const optionData = option.value + + const topicData = optionData.data.find((d) => d.id == "opendiscord:channel-description") + if (topicData) topicData.id = "opendiscord:channel-topic" + if (!optionData.data.find((d) => d.id == "opendiscord:slowmode-enabled")) optionData.data.push({id:"opendiscord:slowmode-enabled",value:false}) + if (!optionData.data.find((d) => d.id == "opendiscord:slowmode-seconds")) optionData.data.push({id:"opendiscord:slowmode-seconds",value:20}) - for (const option of (await optionDatabase.getCategory("opendiscord:used-option") ?? [])){ - const optionData = option.value - - const topicData = optionData.data.find((d) => d.id == "opendiscord:channel-description") - if (topicData) topicData.id = "opendiscord:channel-topic" - if (!optionData.data.find((d) => d.id == "opendiscord:slowmode-enabled")) optionData.data.push({id:"opendiscord:slowmode-enabled",value:false}) - if (!optionData.data.find((d) => d.id == "opendiscord:slowmode-seconds")) optionData.data.push({id:"opendiscord:slowmode-seconds",value:20}) + optionDatabase.set("opendiscord:used-option",option.key,optionData) + } - optionDatabase.set("opendiscord:used-option",option.key,optionData) - } + for (const ticket of (await ticketDatabase.getCategory("opendiscord:ticket") ?? [])){ + const ticketData = ticket.value + + if (!ticketData.data.find((d) => d.id == "opendiscord:previous-creators")) ticketData.data.push({id:"opendiscord:previous-creators",value:[]}) + if (!ticketData.data.find((d) => d.id == "opendiscord:reopened")) ticketData.data.push({id:"opendiscord:reopened",value:false}) + if (!ticketData.data.find((d) => d.id == "opendiscord:reopened-by")) ticketData.data.push({id:"opendiscord:reopened-by",value:null}) + if (!ticketData.data.find((d) => d.id == "opendiscord:reopened-on")) ticketData.data.push({id:"opendiscord:reopened-on",value:null}) + if (!ticketData.data.find((d) => d.id == "opendiscord:priority")) ticketData.data.push({id:"opendiscord:priority",value:-1}) + if (!ticketData.data.find((d) => d.id == "opendiscord:topic")) ticketData.data.push({id:"opendiscord:topic",value:""}) + if (!ticketData.data.find((d) => d.id == "opendiscord:message-sent")) ticketData.data.push({id:"opendiscord:message-sent",value:true}) + if (!ticketData.data.find((d) => d.id == "opendiscord:admin-message-sent")) ticketData.data.push({id:"opendiscord:admin-message-sent",value:true}) - for (const ticket of (await ticketDatabase.getCategory("opendiscord:ticket") ?? [])){ - const ticketData = ticket.value - - if (!ticketData.data.find((d) => d.id == "opendiscord:previous-creators")) ticketData.data.push({id:"opendiscord:previous-creators",value:[]}) - if (!ticketData.data.find((d) => d.id == "opendiscord:reopened")) ticketData.data.push({id:"opendiscord:reopened",value:false}) - if (!ticketData.data.find((d) => d.id == "opendiscord:reopened-by")) ticketData.data.push({id:"opendiscord:reopened-by",value:null}) - if (!ticketData.data.find((d) => d.id == "opendiscord:reopened-on")) ticketData.data.push({id:"opendiscord:reopened-on",value:null}) - if (!ticketData.data.find((d) => d.id == "opendiscord:priority")) ticketData.data.push({id:"opendiscord:priority",value:-1}) - if (!ticketData.data.find((d) => d.id == "opendiscord:topic")) ticketData.data.push({id:"opendiscord:topic",value:""}) - if (!ticketData.data.find((d) => d.id == "opendiscord:message-sent")) ticketData.data.push({id:"opendiscord:message-sent",value:true}) - if (!ticketData.data.find((d) => d.id == "opendiscord:admin-message-sent")) ticketData.data.push({id:"opendiscord:admin-message-sent",value:true}) - - ticketDatabase.set("opendiscord:ticket",ticket.key,ticketData) + ticketDatabase.set("opendiscord:ticket",ticket.key,ticketData) + } } }), //MIGRATE TO v4.1.1 - new utilities.ODVersionMigration(api.ODVersion.fromString("opendiscord:version","v4.1.1"),async () => {},async () => {}), + new api.ODVersionMigration(api.ODVersion.fromString("opendiscord:version","v4.1.1"),{}), //MIGRATE TO v4.1.2 - new utilities.ODVersionMigration(api.ODVersion.fromString("opendiscord:version","v4.1.2"),async () => {},async () => {}), + new api.ODVersionMigration(api.ODVersion.fromString("opendiscord:version","v4.1.2"),{}), //MIGRATE TO v4.1.3 - new utilities.ODVersionMigration(api.ODVersion.fromString("opendiscord:version","v4.1.3"),async () => {},async () => {}), + new api.ODVersionMigration(api.ODVersion.fromString("opendiscord:version","v4.1.3"),{}), + + //MIGRATE TO v4.2.0 + new api.ODVersionMigration(api.ODVersion.fromString("opendiscord:version","v4.2.0"),{ + beforeStartupMigrate:async () => { + const isDevconfig = (process.argv.includes("--dev-config") || process.argv.includes("-dc")) + + //transfer config files to .jsonc + const configDir = path.join(process.cwd(),(isDevconfig) ? "./devconfig/" : "./config/") + for (const file of fs.readdirSync(configDir).filter((f) => f.endsWith(".json"))){ + try{ + fs.copyFileSync(path.join(configDir,file),path.join(configDir,file.replace(".json",".jsonc"))) + fs.rmSync(path.join(configDir,file)) + }catch(err){ + process.emit("uncaughtException",err) + } + } + }, + afterStartupMigrate:async () => { + //migrate config + const generalConfig = opendiscord.configs.get("opendiscord:general") + const questionConfig = opendiscord.configs.get("opendiscord:questions") + const optionConfig = opendiscord.configs.get("opendiscord:options") + const panelConfig = opendiscord.configs.get("opendiscord:panels") + const transcriptConfig = opendiscord.configs.get("opendiscord:transcripts") + + if (!generalConfig.data.ticketSystem){ + //only migrate config when it hasn't been done manually by the user. + + if (!generalConfig.data["_INFO"]) throw new api.ODSystemError("Couldn't find general.jsonc '_INFO' category.") + delete generalConfig.data["_INFO"] + generalConfig.data._CONFIG_VERSION = "open-ticket-v4.2.0" + + if (!generalConfig.data["system"]) throw new api.ODSystemError("Couldn't find general.jsonc 'system' category.") + generalConfig.data.ticketSystem = generalConfig.data["system"] + generalConfig.data.ticketSystem.closeEmoji = "" + generalConfig.data.ticketSystem.askPriorityOnTicketCreation = true + generalConfig.data.ticketSystem.enableCreateTicketForOtherUser = true + delete generalConfig.data["system"] + generalConfig.data.logs = generalConfig.data.ticketSystem["logs"] + delete generalConfig.data.ticketSystem["logs"] + generalConfig.data.logs.logMessages = generalConfig.data.ticketSystem["messages"] + delete generalConfig.data.ticketSystem["messages"] + generalConfig.data.permissions = generalConfig.data.ticketSystem["permissions"] + delete generalConfig.data.ticketSystem["permissions"] + generalConfig.data.permissions.transcripts = "admin" + + //closed category + const closedCategory = {enabled:false,categoryId:"DISCORD_CATEGORY_ID"} + for (const option of optionConfig.data){ + if (option.type != "ticket") continue + if (option.channel["closedCategory"] && /^\d+$/.test(option.channel["closedCategory"])){ + closedCategory.enabled = true + closedCategory.categoryId = option.channel["closedCategory"] + } + } + generalConfig.data.ticketSystem.closedCategory = closedCategory + + //backup category + const backupCategory = {enabled:false,categoryId:"DISCORD_CATEGORY_ID"} + for (const option of optionConfig.data){ + if (option.type != "ticket") continue + if (option.channel["backupCategory"] && /^\d+$/.test(option.channel["backupCategory"])){ + backupCategory.enabled = true + backupCategory.categoryId = option.channel["backupCategory"] + } + } + generalConfig.data.ticketSystem.backupCategory = backupCategory + + //claimed categories + const claimedCategories: {user:string,category:string}[] = [] + for (const option of optionConfig.data){ + if (option.type != "ticket" || !Array.isArray(option.channel["claimedCategory"])) continue + for (const {user,category} of option.channel["claimedCategory"]){ + if (typeof user == "string" && typeof category == "string" && /^\d+$/.test(user) && /^\d+$/.test(category) && !claimedCategories.find((c) => c.user == user)){ + claimedCategories.push({user,category}) + } + } + } + generalConfig.data.ticketSystem.claimedCategories = claimedCategories + + + //delete properties from options.jsonc + for (const option of optionConfig.data){ + if (option.type != "ticket") continue + delete option.channel["closedCategory"] + delete option.channel["backupCategory"] + delete option.channel["claimedCategory"] + } + + //update panels config: + for (const panel of panelConfig.data){ + panel.settings.maximumButtonsPerRow = 5 + } + + //update questions config: + for (const question of questionConfig.data){ + if (question.type !== "paragraph" && question.type !== "short") continue + question.description = "" + } + + //add new sub-panel option example (for users to try) + optionConfig.data.push({ + id:"example-sub-panel", + name:"Example Sub-Panel", + description:"This is an example of how to implement a sub-panel in Open Ticket.", + type:"sub-panel", + + button:{ + color:"gray", + label:"Sub-Panel Example", + emoji:"📋" + }, + subPanelId:panelConfig.data[0]?.id ?? "example-panel" + }) + + //add new question examples (for users to try) + questionConfig.data.push( + { + id:"example-dropdown-question", + name:"Example Dropdown Question", + description:"This is a dropdown question.", + type:"dropdown", + required:false, + + placeholder:"Choose your answer...", + choices:[ + {title:"Choice A",description:"Apple",emoji:"🍎"}, + {title:"Choice B",description:"Banana",emoji:"🍌"}, + {title:"Choice C",description:"Orange",emoji:"🍊"}, + {title:"Choice D",description:"Kiwi",emoji:"🥝"} + ] + }, + { + id:"example-radio-question", + name:"Example Radio Question", + description:"This is a radio select question.", + type:"radio-select", + required:true, + + choices:[ + {title:"Choice A",description:"Up",selectedByDefault:false}, + {title:"Choice B",description:"Down",selectedByDefault:false}, + {title:"Choice C",description:"Left",selectedByDefault:false}, + {title:"Choice D",description:"Right",selectedByDefault:false} + ] + }, + { + id:"example-checkbox-question", + name:"Example Checkbox Question", + description:"This is a checkbox select question.", + type:"checkbox-select", + required:true, + + limits:{ + enabled:false, + min:0, + max:10 + }, + choices:[ + {title:"Choice A",description:"Happiness",selectedByDefault:false}, + {title:"Choice B",description:"Anger",selectedByDefault:false}, + {title:"Choice C",description:"Sadness",selectedByDefault:false}, + {title:"Choice D",description:"Fear",selectedByDefault:false} + ] + }, + { + id:"example-file-upload", + name:"Example File Upload", + description:"With this question type, users can upload one or multiple files.", + type:"file-upload", + required:true, + + limits:{ + enabled:false, + min:0, + max:1 + } + }, + { + id:"example-text-display-question", + type:"text-display", + textContents:"This is a text display. It isn't a question, but allows you to display a text, explaination or details." + } + ) + + await generalConfig.save() + await questionConfig.save() + await optionConfig.save() + await panelConfig.save() + await transcriptConfig.save() + } + + //migrate database + const globalDatabase = opendiscord.databases.get("opendiscord:global") + const optionDatabase = opendiscord.databases.get("opendiscord:options") + const ticketDatabase = opendiscord.databases.get("opendiscord:tickets") + const stateDatabase = opendiscord.databases.get("opendiscord:message-states") + + for (const option of (await optionDatabase.getCategory("opendiscord:used-option") ?? [])){ + const optionData = option.value + optionData.data = optionData.data.filter((data) => ( + data.id !== "opendiscord:channel-category-closed" && + data.id !== "pendiscord:channel-category-backup" && + data.id !== "opendiscord:channel-categories-claimed" + )) + + optionDatabase.set("opendiscord:used-option",option.key,optionData) + } + + for (const ticket of (await ticketDatabase.getCategory("opendiscord:ticket") ?? [])){ + const ticketData = ticket.value + if (!ticketData.data.find((d) => d.id == "opendiscord:channel-renamed")) ticketData.data.push({id:"opendiscord:channel-renamed",value:null}) + + ticketDatabase.set("opendiscord:ticket",ticket.key,ticketData) + } + + //migrate old panel messages from global.json to the new message states + const migratedPanelStates: {channelId:string,messageId:string,panelId:string,autoUpdate:boolean}[] = [] + opendiscord.events.get("afterClientReady").listen(async () => { + for (const panelMessage of (await globalDatabase.getCategory("opendiscord:panel-message") ?? [])){ + const splittedId = panelMessage.key.split("_") + const channelId = splittedId[0] + const messageId = splittedId[1] + const message = await opendiscord.client.fetchChannelMessage(channelId,messageId) + const autoUpdate = typeof (await globalDatabase.get("opendiscord:panel-update",panelMessage.key)) == "string" + //if message still exists + if (message) migratedPanelStates.push({channelId,messageId,panelId:panelMessage.value,autoUpdate}) + + await globalDatabase.delete("opendiscord:panel-message",panelMessage.key) + await globalDatabase.delete("opendiscord:panel-update",panelMessage.key) + } + }) + opendiscord.events.get("beforeReadyForUsage").listen(async () => { + const panelMsgState = opendiscord.states.get("opendiscord:panel-message") + for (const {channelId,messageId,panelId,autoUpdate} of migratedPanelStates){ + await panelMsgState.setMsgState({channel:channelId,message:messageId},{ + messageOrigin:"auto-update", + panelId, + panelOptionIds:[], + panelAutoUpdate:autoUpdate, + isSubPanel:false + },false) + + if (!autoUpdate){ + //auto update ALL non auto-update panels once to update button changes from v4.1 -> v4.2 + const panel = opendiscord.panels.get(panelId) + if (!panel) continue + + //fetch panel message + const mainServer = opendiscord.client.mainServer + const message = await opendiscord.client.fetchChannelMessage(channelId,messageId) + if (!message || !mainServer || !message.editable || message.flags.has("Ephemeral")) continue + + const panelMessage = await message.edit((await opendiscord.builders.messages.getSafe("opendiscord:panel").build("auto-update",{guild:mainServer,channel:message.channel,user:opendiscord.client.client.user,panel,isSubPanel:false})).message) + if (panelMessage) await panelMsgState.setMsgState({channel:message.channel,message:panelMessage},{ + messageOrigin:"auto-update", + panelId:panel.id.value, + panelOptionIds:panel.get("opendiscord:options").value, + panelAutoUpdate:false, + isSubPanel:false + },panelMessage.flags.has("Ephemeral")) + + opendiscord.log("Panel in server got updated to v4.2!","info",[ + {key:"channelid",value:channelId}, + {key:"messageid",value:messageId}, + {key:"panel",value:panel.id.value} + ]) + } + } + }) + } + }), ] \ No newline at end of file diff --git a/src/core/startup/pluginLauncher.ts b/src/core/startup/pluginLauncher.ts deleted file mode 100644 index 9cbad25..0000000 --- a/src/core/startup/pluginLauncher.ts +++ /dev/null @@ -1,260 +0,0 @@ -import {opendiscord, api, utilities} from "../../index" -import fs from "fs" - -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 api.ODPluginError("Failed to load plugin.json") - if (typeof rawplugindata.id != "string") throw new api.ODPluginError("Failed to load plugin.json/id") - if (typeof rawplugindata.name != "string") throw new api.ODPluginError("Failed to load plugin.json/name") - if (typeof rawplugindata.version != "string") throw new api.ODPluginError("Failed to load plugin.json/version") - if (typeof rawplugindata.startFile != "string") throw new api.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 api.ODPluginError("Failed to load plugin.json/supportedVersions (must be array)") - for (const version of rawplugindata.supportedVersions){ - if (typeof version !== "string"){ - throw new api.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 api.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 api.ODPluginError("Failed to load plugin.json/enabled") - if (typeof rawplugindata.priority != "number") throw new api.ODPluginError("Failed to load plugin.json/priority") - if (!Array.isArray(rawplugindata.events)) throw new api.ODPluginError("Failed to load plugin.json/events") - - if (!Array.isArray(rawplugindata.npmDependencies)) throw new api.ODPluginError("Failed to load plugin.json/npmDependencies") - if (!Array.isArray(rawplugindata.requiredPlugins)) throw new api.ODPluginError("Failed to load plugin.json/requiredPlugins") - if (!Array.isArray(rawplugindata.incompatiblePlugins)) throw new api.ODPluginError("Failed to load plugin.json/incompatiblePlugins") - - if (typeof rawplugindata.details != "object") throw new api.ODPluginError("Failed to load plugin.json/details") - if (typeof rawplugindata.details.author != "string") throw new api.ODPluginError("Failed to load plugin.json/details/author") - - //only check "contributors" if it exists (should be array) - if (rawplugindata.details.contributors && !Array.isArray(rawplugindata.details.contributors)) throw new api.ODPluginError("Failed to load plugin.json/details/contributors (must be array)") - - if (typeof rawplugindata.details.shortDescription != "string") throw new api.ODPluginError("Failed to load plugin.json/details/shortDescription") - if (typeof rawplugindata.details.longDescription != "string") throw new api.ODPluginError("Failed to load plugin.json/details/longDescription") - if (typeof rawplugindata.details.imageUrl != "string") throw new api.ODPluginError("Failed to load plugin.json/details/imageUrl") - if (typeof rawplugindata.details.projectUrl != "string") throw new api.ODPluginError("Failed to load plugin.json/details/projectUrl") - if (!Array.isArray(rawplugindata.details.tags)) throw new api.ODPluginError("Failed to load plugin.json/details/tags") - - if (rawplugindata.id != p) throw new api.ODPluginError("Failed to load plugin, directory name is required to match the id") - - if (opendiscord.plugins.exists(rawplugindata.id)) throw new api.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(", ") ?? "" - 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()} - ]) - } - } -} \ No newline at end of file diff --git a/src/data/framework/checkerLoader.ts b/src/data/framework/checkerLoader.ts index 38e1145..a32a13e 100644 --- a/src/data/framework/checkerLoader.ts +++ b/src/data/framework/checkerLoader.ts @@ -1,28 +1,8 @@ -import {opendiscord, api, utilities} from "../../index" +import {opendiscord, api, utilities} from "../../index.js" const generalConfig = opendiscord.configs.get("opendiscord:general") -/** (CONTRIBUTOR GUIDE) HOW TO ADD NEW CONFIG VARIABLES? - * - Make the change to the config file in (./config/) and be aware of the following things: - * - The variable has a clear name and its function is obvious. - * - The variable is in the correct position/category of the config. - * - The variable contains a default placeholder to suggest the contents. - * - If there's a (./devconfig/), also modify this file. - * - Register the config in loadAllConfigs() in (./src/data/framework/configLoader.ts) - * - The variable should be added to the "formatters" in the correct position. - * - Add autocomplete for the variable in ODJsonConfig_Default... in (./src/core/api/defaults/config.ts) - * - Add the variable to the config checker in (./src/data/framework/checkerLoader.ts) - * - Make sure the variable is compatible with the Interactive Setup CLI. - * - The variable should be added by the migration manager (./src/core/startup/migration.ts) when missing. - * - Update the Open Ticket Documentation. - * - * IF VARIABLE IS FROM questions.json, options.json OR panels.json: - * - Check (./src/data/openticket/...) for loading/unloading of data. - * - Check (./src/actions/createTicket.ts) and related files. - * - Check (./src/builders), (./src/actions), (./src/data) & (./src/commands) in general in the areas that were changed. - */ - -export const loadAllConfigCheckers = async () => { +export async function loadAllConfigCheckers(){ opendiscord.checkers.add(new api.ODChecker("opendiscord:general",opendiscord.checkers.storage,0,opendiscord.configs.get("opendiscord:general"),defaultGeneralStructure,{cliDisplayName:"General Config",cliDisplayDescription:"Configure the bot token, status, colors, permissions & more."})) opendiscord.checkers.add(new api.ODChecker("opendiscord:questions",opendiscord.checkers.storage,2,opendiscord.configs.get("opendiscord:questions"),defaultQuestionsStructure,{cliDisplayName:"Questions Config",cliDisplayDescription:"Create, modify & delete questions which are used in options."})) opendiscord.checkers.add(new api.ODChecker("opendiscord:options",opendiscord.checkers.storage,1,opendiscord.configs.get("opendiscord:options"),defaultOptionsStructure,{cliDisplayName:"Options Config",cliDisplayDescription:"Create, modify & delete options which are used in panels."})) @@ -30,14 +10,14 @@ export const loadAllConfigCheckers = async () => { opendiscord.checkers.add(new api.ODChecker("opendiscord:transcripts",opendiscord.checkers.storage,0,opendiscord.configs.get("opendiscord:transcripts"),defaultTranscriptsStructure,{cliDisplayName:"Transcript Config",cliDisplayDescription:"Configure everything related to transcripts."})) } -export const loadAllConfigCheckerFunctions = async () => { +export async function loadAllConfigCheckerFunctions(){ opendiscord.checkers.functions.add(new api.ODCheckerFunction("opendiscord:unused-options",defaultUnusedOptionsFunction)) opendiscord.checkers.functions.add(new api.ODCheckerFunction("opendiscord:unused-questions",defaultUnusedQuestionsFunction)) opendiscord.checkers.functions.add(new api.ODCheckerFunction("opendiscord:dropdown-options",defaultDropdownOptionsFunction)) } -export const loadAllConfigCheckerTranslations = async () => { - if ((generalConfig && generalConfig.data.system && generalConfig.data.system.useTranslatedConfigChecker) ? generalConfig.data.system.useTranslatedConfigChecker : false){ +export async function loadAllConfigCheckerTranslations(){ + if ((generalConfig && generalConfig.data.ticketSystem && generalConfig.data.ticketSystem.useTranslatedConfigChecker) ? generalConfig.data.ticketSystem.useTranslatedConfigChecker : false){ registerDefaultCheckerSystemTranslations(opendiscord.checkers.translation,opendiscord.languages) //translate checker system text registerDefaultCheckerMessageTranslations(opendiscord.checkers.translation,opendiscord.languages) //translate checker messages registerDefaultCheckerCustomTranslations(opendiscord.checkers.translation,opendiscord.languages) //translate custom checker messages @@ -45,9 +25,9 @@ export const loadAllConfigCheckerTranslations = async () => { } //GLOBAL FUNCTIONS -export const registerDefaultCheckerSystemTranslations = (tm:api.ODCheckerTranslationRegister_Default,lm:api.ODLanguageManager_Default) => { +export function registerDefaultCheckerSystemTranslations(tm:api.ODMappedCheckerTranslationRegister,lm:api.ODMappedLanguageManager){ //SYSTEM - //tm.quickTranslate(lm,"checker.system.headerOpenTicket","other","opendiscord:header-openticket") //OPEN TICKET (ignore) + tm.quickTranslate(lm,"checker.system.headerOpenTicket","other","opendiscord:header-projectname") //OPEN TICKET tm.quickTranslate(lm,"checker.system.typeError","other","opendiscord:type-error") // [ERROR] (ignore) tm.quickTranslate(lm,"checker.system.typeWarning","other","opendiscord:type-warning") // [WARNING] (ignore) tm.quickTranslate(lm,"checker.system.typeInfo","other","opendiscord:type-info") // [INFO] (ignore) @@ -62,7 +42,7 @@ export const registerDefaultCheckerSystemTranslations = (tm:api.ODCheckerTransla tm.quickTranslate(lm,"checker.system.dataMessages","other","opendiscord:data-message") // message } -export const registerDefaultCheckerMessageTranslations = (tm:api.ODCheckerTranslationRegister_Default,lm:api.ODLanguageManager_Default) => { +export function registerDefaultCheckerMessageTranslations(tm:api.ODMappedCheckerTranslationRegister,lm:api.ODMappedLanguageManager){ //STRUCTURES tm.quickTranslate(lm,"checker.messages.invalidType","message","opendiscord:invalid-type") // This property needs to be the type: {0}! tm.quickTranslate(lm,"checker.messages.propertyMissing","message","opendiscord:property-missing") // The property {0} is missing from this object! @@ -137,24 +117,41 @@ export const registerDefaultCheckerMessageTranslations = (tm:api.ODCheckerTransl tm.quickTranslate(lm,"checker.messages.idNonExistent","message","opendiscord:id-non-existent") // The id {0} doesn't exist! } -export const registerDefaultCheckerCustomTranslations = (tm:api.ODCheckerTranslationRegister_Default,lm:api.ODLanguageManager_Default) => { +export function registerDefaultCheckerCustomTranslations(tm:api.ODMappedCheckerTranslationRegister,lm:api.ODMappedLanguageManager){ //CUSTOM tm.quickTranslate(lm,"checker.messages.invalidLanguage","message","opendiscord:invalid-language") // This is an invalid language! tm.quickTranslate(lm,"checker.messages.invalidButton","message","opendiscord:invalid-button") // This button needs to have at least an {0} or {1}! tm.quickTranslate(lm,"checker.messages.unusedOption","message","opendiscord:unused-option") // The option {0} isn't used anywhere! tm.quickTranslate(lm,"checker.messages.unusedQuestion","message","opendiscord:unused-question") // The question {0} isn't used anywhere! - tm.quickTranslate(lm,"checker.messages.dropdownOption","message","opendiscord:dropdown-option") // A panel with dropdown enabled can only contain options of the 'ticket' type! + tm.quickTranslate(lm,"checker.messages.dropdownOption","message","opendiscord:dropdown-option") // A panel with dropdown can only contain options of the types: 'ticket', 'role' or 'sub-panel'. tm.quickTranslate(lm,"checker.messages.customInvalidVersion","message","opendiscord:invalid-version") // The version specified in your config does not match! Make sure you have updated the config to the latest version! } //UTILITY FUNCTIONS -const createMsgStructure = (id:api.ODValidId,displayName:string) => { +/**Get the panel ids from `panels.jsonc` before it has been checked by the config checker. */ +function getUnsafePanelIds(): string[] { + const panelsConfig = opendiscord.configs.get("opendiscord:panels") + if (!Array.isArray(panelsConfig.data)) return [] + + const panelIds: string[] = [] + for (const unsafePanel of panelsConfig.data){ + if (unsafePanel["id"]) panelIds.push(unsafePanel["id"]) + } + return panelIds +} + +function createMsgStructure(id:api.ODValidId,displayName:string){ return new api.ODCheckerObjectStructure(id,{children:[ {key:"dm",checker:new api.ODCheckerBooleanStructure("opendiscord:msg-dm",{cliInitDefaultValue:false,cliDisplayName:"DM Enabled",cliDisplayDescription:"Will this action be sent in DM to the creator of the ticket?"})}, {key:"logs",checker:new api.ODCheckerBooleanStructure("opendiscord:msg-logs",{cliInitDefaultValue:true,cliDisplayName:"Logs Enabled",cliDisplayDescription:"Will this action be sent in the Discord log channel?"})}, ],cliDisplayName:displayName,cliDisplayDescription:"Configure which places this action gets logged/sent to."}) } -const createTicketEmbedStructure = (id:api.ODValidId) => { + +function createPermissionStructure(id:api.ODValidId,displayName:string){ + return new api.ODCheckerCustomStructure_DiscordId(id,"role",false,["admin","everyone","none"],{cliDisplayName:displayName,cliHideDescriptionInParent:true,cliDisplayDescription:"Set the permissions to 'everyone' for everyone, 'admin' for admin only, 'none' to disable or a custom discord role ID."}) +} + +function createTicketEmbedStructure(id:api.ODValidId){ return new api.ODCheckerEnabledObjectStructure(id,{property:"enabled",enabledValue:true,checker:new api.ODCheckerObjectStructure(id,{children:[ {key:"enabled",checker:new api.ODCheckerBooleanStructure("opendiscord:ticket-embed-enabled",{cliDisplayName:"Enabled",cliDisplayDescription:"Enable/disable the embed of this message."})}, {key:"title",checker:new api.ODCheckerStringStructure("opendiscord:ticket-embed-text",{maxLength:256,cliDisplayName:"Title",cliDisplayDescription:"The title of this embed."})}, @@ -171,14 +168,16 @@ const createTicketEmbedStructure = (id:api.ODValidId) => { {key:"timestamp",checker:new api.ODCheckerBooleanStructure("opendiscord:ticket-embed-timestamp",{cliDisplayName:"Timestamp",cliDisplayDescription:"Add a timestamp to the embed."})} ],cliDisplayName:"Message Embed",cliDisplayDescription:"Configure the embed of this message."}),cliInitDefaultValue:{enabled:false,title:"",description:"",customColor:"",image:"",thumbnail:"",fields:[],timestamp:false},cliDisplayName:"Message Embed",cliDisplayDescription:"Configure the embed of this message."}) } -const createTicketPingStructure = (id:api.ODValidId) => { + +function createTicketPingStructure(id:api.ODValidId){ return new api.ODCheckerObjectStructure(id,{children:[ {key:"@here",checker:new api.ODCheckerBooleanStructure("opendiscord:ticket-ping-here",{cliDisplayName:"@here Ping",cliDisplayDescription:"Enable/disable an '@here' ping."})}, {key:"@everyone",checker:new api.ODCheckerBooleanStructure("opendiscord:ticket-ping-everyone",{cliDisplayName:"@everyone Ping",cliDisplayDescription:"Enable/disable an '@everyone' ping."})}, {key:"custom",checker:new api.ODCheckerCustomStructure_DiscordIdArray("opendiscord:ticket-ping-custom","role",[],{allowDoubles:false,cliDisplayPropertyName:"custom role id",cliDisplayName:"Custom Role Ping",cliDisplayDescription:"Choose your own roles to ping in this message."},{cliDisplayName:"Custom Role",cliDisplayDescription:"The discord role ID of a custom mention/ping."})}, ],cliInitDefaultValue:{"@here":true,"@everyone":false,custom:[],cliDisplayName:"Message Pings",cliDisplayDescription:"Configure the pings/mentions of this message."}}) } -const createPanelEmbedStructure = (id:api.ODValidId) => { + +function createPanelEmbedStructure(id:api.ODValidId){ return new api.ODCheckerEnabledObjectStructure(id,{property:"enabled",enabledValue:true,checker:new api.ODCheckerObjectStructure(id,{children:[ {key:"enabled",checker:new api.ODCheckerBooleanStructure("opendiscord:panel-embed-enabled",{cliDisplayName:"Enabled",cliDisplayDescription:"Enable/disable the embed of this panel."})}, {key:"title",checker:new api.ODCheckerStringStructure("opendiscord:panel-embed-text",{maxLength:256,cliDisplayName:"Title",cliDisplayDescription:"The title of this embed."})}, @@ -208,35 +207,31 @@ function loadFromEnv(){ //STRUCTURES export const defaultGeneralStructure = new api.ODCheckerObjectStructure("opendiscord:general",{children:[ //INFO - {key:"_INFO",cliHideInEditMode:true,checker:new api.ODCheckerObjectStructure("opendiscord:info",{children:[ - {key:"support",checker:new api.ODCheckerStringStructure("opendiscord:info-support",{choices:["https://otdocs.dj-dj.be"]})}, - {key:"discord",checker:new api.ODCheckerStringStructure("opendiscord:info-discord",{choices:["https://discord.dj-dj.be"]})}, - {key:"version",checker:new api.ODCheckerStringStructure("opendiscord:info-version",{custom(checker,value,locationTrace,locationId,locationDocs) { - const lt = checker.locationTraceDeref(locationTrace) - - if (typeof value != "string") return false - else if (value != "open-ticket-"+opendiscord.versions.get("opendiscord:version").toString()){ - checker.createMessage("opendiscord:invalid-version","warning","The version specified in your config does not match! Make sure you have updated the config to the latest version!",lt,null,[],locationId,locationDocs) - return false - }else return true - },})}, - ]})}, + {key:"_CONFIG_VERSION",cliHideInEditMode:true,checker:new api.ODCheckerStringStructure("opendiscord:config-version",{custom(checker,value,locationTrace,locationId,locationDocs) { + const lt = checker.locationTraceDeref(locationTrace) + + if (typeof value != "string") return false + else if (value != "open-ticket-"+opendiscord.versions.get("opendiscord:version").toString()){ + checker.createMessage("opendiscord:invalid-version","warning","The version specified in your config does not match! Make sure you have updated the config to the latest version!",lt,null,[],locationId,locationDocs) + return false + }else return true + },})}, //BASIC {key:"token",checker:(loadFromEnv()) ? new api.ODCheckerStringStructure("opendiscord:token-disabled",{cliDisplayName:"Token",cliDisplayDescription:"The token of your discord bot."}) : new api.ODCheckerCustomStructure_DiscordToken("opendiscord:token",{cliDisplayName:"Token",cliDisplayDescription:"The token of your discord bot."})}, - {key:"tokenFromENV",checker:new api.ODCheckerBooleanStructure("opendiscord:token-env",{cliDisplayName:"Token From ENV",cliDisplayDescription:"Use the token from the .env file instead of general.json."})}, + {key:"tokenFromENV",checker:new api.ODCheckerBooleanStructure("opendiscord:token-env",{cliDisplayName:"Token From ENV",cliDisplayDescription:"Use the token from the .env file instead of general.jsonc."})}, {key:"mainColor",checker:new api.ODCheckerCustomStructure_HexColor("opendiscord:main-color",true,false,{cliDisplayName:"Main Color",cliDisplayDescription:"The main color of your bot, used in almost all embeds."})}, {key:"language",checker:new api.ODCheckerStringStructure("opendiscord:language",{ custom:(checker,value,locationTrace,locationId,locationDocs) => { const lt = checker.locationTraceDeref(locationTrace) if (typeof value != "string") return false - else if (!opendiscord.defaults.getDefault("languageList").includes(value)){ + else if (!opendiscord.sharedFuses.getFuse("languageList").includes(value)){ checker.createMessage("opendiscord:invalid-language","error","This is an invalid language!",lt,null,[],locationId,locationDocs) return false }else return true }, - cliAutocompleteList:opendiscord.defaults.getDefault("languageList"), + cliAutocompleteList:opendiscord.sharedFuses.getFuse("languageList"), cliDisplayName:"Language", cliDisplayDescription:"The language of the bot. Visit README.md for a list of available translations." })}, @@ -250,6 +245,7 @@ export const defaultGeneralStructure = new api.ODCheckerObjectStructure("opendis {key:"status",checker:new api.ODCheckerEnabledObjectStructure("opendiscord:status",{ property:"enabled", enabledValue:true, + ignoreCheckIfDisabled:true, checker:new api.ODCheckerObjectStructure("opendiscord:status",{children:[ {key:"enabled",checker:new api.ODCheckerBooleanStructure("opendiscord:status-enabled",{cliDisplayName:"Enabled",cliDisplayDescription:"Enable/disable the status. When disabled, the bot will be online without any status."})}, {key:"type",checker:new api.ODCheckerStringStructure("opendiscord:status-type",{choices:["listening","watching","playing","custom"],cliDisplayName:"Type",cliDisplayDescription:"The type of status: Listening, Watching, Playing or Custom."})}, @@ -261,8 +257,33 @@ export const defaultGeneralStructure = new api.ODCheckerObjectStructure("opendis cliDisplayDescription:"Manage the status of the bot." })}, - //SYSTEM - {key:"system",checker:new api.ODCheckerObjectStructure("opendiscord:system",{children:[ + //LOGS + {key:"logs",checker:new api.ODCheckerEnabledObjectStructure("opendiscord:logs",{property:"enabled",enabledValue:true,ignoreCheckIfDisabled:true,checker:new api.ODCheckerObjectStructure("opendiscord:logs",{children:[ + {key:"enabled",checker:new api.ODCheckerBooleanStructure("opendiscord:logs-enabled",{cliDisplayName:"Enabled",cliDisplayDescription:"Enable/disable discord logs in a discord channel."})}, + {key:"channel",checker:new api.ODCheckerCustomStructure_DiscordId("opendiscord:logs-channel","channel",false,[],{cliDisplayName:"Log Channel",cliDisplayDescription:"The log message channel ID."})}, + + //LOG MESSAGES + {key:"logMessages",checker:new api.ODCheckerObjectStructure("opendiscord:log-messages",{children:[ + {key:"creation",checker:createMsgStructure("opendiscord:msg-creation","Ticket Created")}, + {key:"closing",checker:createMsgStructure("opendiscord:msg-closing","Ticket Closed")}, + {key:"deleting",checker:createMsgStructure("opendiscord:msg-deleting","Ticket Deleted")}, + {key:"reopening",checker:createMsgStructure("opendiscord:msg-reopening","Ticket Reopened")}, + {key:"claiming",checker:createMsgStructure("opendiscord:msg-claiming","Ticket Claimed")}, + {key:"pinning",checker:createMsgStructure("opendiscord:msg-pinning","Ticket Pinned")}, + {key:"adding",checker:createMsgStructure("opendiscord:msg-adding","User Added")}, + {key:"removing",checker:createMsgStructure("opendiscord:msg-removing","User Removed")}, + {key:"renaming",checker:createMsgStructure("opendiscord:msg-renaming","Ticket Renamed")}, + {key:"moving",checker:createMsgStructure("opendiscord:msg-moving","Ticket Moved")}, + {key:"blacklisting",checker:createMsgStructure("opendiscord:msg-blacklisting","User Blacklisted")}, + {key:"transferring",checker:createMsgStructure("opendiscord:msg-transferring","Ticket Transferred")}, + {key:"topicChange",checker:createMsgStructure("opendiscord:msg-topic-change","Topic Changed")}, + {key:"priorityChange",checker:createMsgStructure("opendiscord:msg-priority-change","Priority Changed")}, + {key:"reactionRole",checker:createMsgStructure("opendiscord:msg-reaction-role","Reaction Role")}, + ],cliDisplayName:"Log Messages",cliDisplayDescription:"Manage all messages & DM's for each action of the bot. (Visit docs for more info)"})}, + ],cliDisplayName:"Discord Logs",cliDisplayDescription:"Manage the 'Open Ticket' logs channel."}),cliDisplayName:"Discord Logs",cliDisplayDescription:"Manage the 'Open Ticket' logs channel."})}, + + //TICKET SYSTEM + {key:"ticketSystem",checker:new api.ODCheckerObjectStructure("opendiscord:ticket-system",{children:[ {key:"preferSlashOverText",checker:new api.ODCheckerBooleanStructure("opendiscord:prefer-slash-over-text",{cliDisplayName:"Prefer Slash Over Text",cliDisplayDescription:"Prefer displaying slash commands over text commands in help menus."})}, {key:"sendErrorOnUnknownCommand",checker:new api.ODCheckerBooleanStructure("opendiscord:send-error-on-unknown-command",{cliDisplayName:"Send Error On Unknown Command",cliDisplayDescription:"Send an error when using the text-command prefix without a valid command."})}, {key:"questionFieldsInCodeBlock",checker:new api.ODCheckerBooleanStructure("opendiscord:question-fields-in-code-block",{cliDisplayName:"Questions Fields In Code Blocks",cliDisplayDescription:"Display question fields in code blocks instead of plain text."})}, @@ -272,7 +293,8 @@ export const defaultGeneralStructure = new api.ODCheckerObjectStructure("opendis {key:"useRedErrorEmbeds",checker:new api.ODCheckerBooleanStructure("opendiscord:use-red-error-embeds",{cliDisplayName:"Use Red Error Embeds",cliDisplayDescription:"Display all error messages with a red border instead of the default color of the bot."})}, {key:"alwaysShowReason",checker:new api.ODCheckerBooleanStructure("opendiscord:always-show-reason",{cliDisplayName:"Always Show Reason",cliDisplayDescription:"Always show the reason field in embeds, even when there is no reason provided."})}, {key:"emojiStyle",checker:new api.ODCheckerStringStructure("opendiscord:emoji-style",{choices:["before","after","double","disabled"],cliDisplayName:"Emoji Style",cliDisplayDescription:"Choose how the bot will display emojis in message titles. (Visit docs for more info)"})}, - {key:"pinEmoji",checker:new api.ODCheckerCustomStructure_EmojiString("opendiscord:pin-emoji",1,1,false,{cliDisplayName:"Pin Emoji",cliDisplayDescription:"The emoji used when pinning tickets. This is '📌' by default."})}, + {key:"pinEmoji",checker:new api.ODCheckerCustomStructure_EmojiString("opendiscord:pin-emoji",0,1,false,{cliDisplayName:"Pin Emoji",cliDisplayDescription:"The emoji used when pinning tickets. This is '📌' by default. Leave empty to disable."})}, + {key:"closeEmoji",checker:new api.ODCheckerCustomStructure_EmojiString("opendiscord:close-emoji",0,1,false,{cliDisplayName:"Pin Emoji",cliDisplayDescription:"The emoji used when closing tickets. This is '🔒' by default. Leave empty to disable."})}, {key:"replyOnTicketCreation",checker:new api.ODCheckerBooleanStructure("opendiscord:reply-on-ticket-creation",{cliDisplayName:"Reply On Ticket Creation",cliDisplayDescription:"When enabled, the bot will send an ephemeral reply in the channel of the panel when creating a ticket."})}, {key:"replyOnReactionRole",checker:new api.ODCheckerBooleanStructure("opendiscord:reply-on-reaction-role",{cliDisplayName:"Reply On Reaction Role",cliDisplayDescription:"When enabled, the bot will send an ephemeral reply in the channel of the panel when using a role button."})}, @@ -292,11 +314,7 @@ export const defaultGeneralStructure = new api.ODCheckerObjectStructure("opendis {key:"enableTicketDeleteButtons",checker:new api.ODCheckerBooleanStructure("opendiscord:enable-ticket-delete-buttons",{cliDisplayName:"Enable Ticket Delete Buttons",cliDisplayDescription:"Enable/disable buttons for deleting a ticket. Be aware that this doesn't disable the command!"})}, {key:"enableTicketActionWithReason",checker:new api.ODCheckerBooleanStructure("opendiscord:enable-ticket-action-with-reason",{cliDisplayName:"Enable Ticket Action With Reason",cliDisplayDescription:"Enable/disable buttons to write an additional reason for all ticket actions."})}, {key:"enableDeleteWithoutTranscript",checker:new api.ODCheckerBooleanStructure("opendiscord:enable-delete-without-transcript",{cliDisplayName:"Enable Delete Without Transcript",cliDisplayDescription:"Enable/disable the ability to delete tickets without a transcript."})}, - - {key:"logs",checker:new api.ODCheckerEnabledObjectStructure("opendiscord:system-logs",{property:"enabled",enabledValue:true,checker:new api.ODCheckerObjectStructure("opendiscord:system-logs",{children:[ - {key:"enabled",checker:new api.ODCheckerBooleanStructure("opendiscord:logs-enabled",{cliDisplayName:"Enabled",cliDisplayDescription:"Enable/disable discord logs in a discord channel."})}, - {key:"channel",checker:new api.ODCheckerCustomStructure_DiscordId("opendiscord:logs-channel","channel",false,[],{cliDisplayName:"Log Channel",cliDisplayDescription:"The ID of the discord channel to log messages to. You can configure the messages somewhere else."})}, - ],cliDisplayName:"Discord Logs",cliDisplayDescription:"Manage everything related to logs in a discord channel."}),cliDisplayName:"Discord Logs",cliDisplayDescription:"Manage everything related to logs in a discord channel."})}, + {key:"enableCreateTicketForOtherUser",checker:new api.ODCheckerBooleanStructure("opendiscord:enable-create-for-other-user",{cliDisplayName:"Enable Create ticket for other user",cliDisplayDescription:"Enable/disable the ability for admins to create a ticket for another user."})}, {key:"limits",checker:new api.ODCheckerEnabledObjectStructure("opendiscord:limits",{property:"enabled",enabledValue:true,checker:new api.ODCheckerObjectStructure("opendiscord:limits",{children:[ {key:"enabled",checker:new api.ODCheckerBooleanStructure("opendiscord:limits-enabled",{cliDisplayName:"Enabled",cliDisplayDescription:"Enable/disable global limits."})}, @@ -307,73 +325,72 @@ export const defaultGeneralStructure = new api.ODCheckerObjectStructure("opendis {key:"channelTopic",checker:new api.ODCheckerObjectStructure("opendiscord:channel-topic",{children:[ {key:"showOptionName",checker:new api.ODCheckerBooleanStructure("opendiscord:topic-show-option-name",{cliDisplayName:"Show Option Name",cliDisplayDescription:"Show the option name in the channel topic."})}, {key:"showOptionDescription",checker:new api.ODCheckerBooleanStructure("opendiscord:topic-show-option-description",{cliDisplayName:"Show Option Description",cliDisplayDescription:"Show the option description in the channel topic."})}, - {key:"showOptionTopic",checker:new api.ODCheckerBooleanStructure("opendiscord:topic-show-option-topic",{cliDisplayName:"Show Option Topic",cliDisplayDescription:"Show the option topic text in the channel topic (configured in the options.json config)."})}, + {key:"showOptionTopic",checker:new api.ODCheckerBooleanStructure("opendiscord:topic-show-option-topic",{cliDisplayName:"Show Option Topic",cliDisplayDescription:"Show the option topic text in the channel topic (configured in the options.jsonc config)."})}, {key:"showPriority",checker:new api.ODCheckerBooleanStructure("opendiscord:topic-show-priority",{cliDisplayName:"Show Priority",cliDisplayDescription:"Show the current priority in the channel topic (auto-updated)."})}, {key:"showClosed",checker:new api.ODCheckerBooleanStructure("opendiscord:topic-show-closed",{cliDisplayName:"Show Closed Status",cliDisplayDescription:"Show the current close/reopen status in the channel topic (auto-updated)."})}, {key:"showClaimed",checker:new api.ODCheckerBooleanStructure("opendiscord:topic-show-claimed",{cliDisplayName:"Show Claimed Status",cliDisplayDescription:"Show the current claim status in the channel topic (auto-updated)."})}, {key:"showPinned",checker:new api.ODCheckerBooleanStructure("opendiscord:topic-show-pinned",{cliDisplayName:"Show Pinned Status",cliDisplayDescription:"Show the current pin status in the channel topic (auto-updated)."})}, {key:"showCreator",checker:new api.ODCheckerBooleanStructure("opendiscord:topic-show-creator",{cliDisplayName:"Show Creator",cliDisplayDescription:"Show the creator of the ticket in the channel topic (auto-updated on transfer)."})}, {key:"showParticipants",checker:new api.ODCheckerBooleanStructure("opendiscord:topic-show-participants",{cliDisplayName:"Show Participants",cliDisplayDescription:"Show the first 5 participants of the ticket in the channel topic (auto-updated)."})}, - ],cliDisplayName:"Channel Topic",cliDisplayDescription:"Manage stats and text of ticket channel topics."})}, - {key:"permissions",checker:new api.ODCheckerObjectStructure("opendiscord:system-permissions",{children:[ - {key:"help",checker:new api.ODCheckerCustomStructure_DiscordId("opendiscord:permissions-help","role",false,["admin","everyone","none"],{cliDisplayName:"Help",cliHideDescriptionInParent:true,cliDisplayDescription:"Set the permissions to 'everyone' for everyone, 'admin' for admin only, 'none' to disable or a custom discord role ID."})}, - {key:"panel",checker:new api.ODCheckerCustomStructure_DiscordId("opendiscord:permissions-panel","role",false,["admin","everyone","none"],{cliDisplayName:"Panel",cliHideDescriptionInParent:true,cliDisplayDescription:"Set the permissions to 'everyone' for everyone, 'admin' for admin only, 'none' to disable or a custom discord role ID."})}, - {key:"ticket",checker:new api.ODCheckerCustomStructure_DiscordId("opendiscord:permissions-ticket","role",false,["admin","everyone","none"],{cliDisplayName:"Ticket",cliHideDescriptionInParent:true,cliDisplayDescription:"Set the permissions to 'everyone' for everyone, 'admin' for admin only, 'none' to disable or a custom discord role ID."})}, - {key:"close",checker:new api.ODCheckerCustomStructure_DiscordId("opendiscord:permissions-close","role",false,["admin","everyone","none"],{cliDisplayName:"Close",cliHideDescriptionInParent:true,cliDisplayDescription:"Set the permissions to 'everyone' for everyone, 'admin' for admin only, 'none' to disable or a custom discord role ID."})}, - {key:"delete",checker:new api.ODCheckerCustomStructure_DiscordId("opendiscord:permissions-delete","role",false,["admin","everyone","none"],{cliDisplayName:"Delete",cliHideDescriptionInParent:true,cliDisplayDescription:"Set the permissions to 'everyone' for everyone, 'admin' for admin only, 'none' to disable or a custom discord role ID."})}, - {key:"reopen",checker:new api.ODCheckerCustomStructure_DiscordId("opendiscord:permissions-reopen","role",false,["admin","everyone","none"],{cliDisplayName:"Reopen",cliHideDescriptionInParent:true,cliDisplayDescription:"Set the permissions to 'everyone' for everyone, 'admin' for admin only, 'none' to disable or a custom discord role ID."})}, - {key:"claim",checker:new api.ODCheckerCustomStructure_DiscordId("opendiscord:permissions-claim","role",false,["admin","everyone","none"],{cliDisplayName:"Claim",cliHideDescriptionInParent:true,cliDisplayDescription:"Set the permissions to 'everyone' for everyone, 'admin' for admin only, 'none' to disable or a custom discord role ID."})}, - {key:"unclaim",checker:new api.ODCheckerCustomStructure_DiscordId("opendiscord:permissions-unclaim","role",false,["admin","everyone","none"],{cliDisplayName:"Unclaim",cliHideDescriptionInParent:true,cliDisplayDescription:"Set the permissions to 'everyone' for everyone, 'admin' for admin only, 'none' to disable or a custom discord role ID."})}, - {key:"pin",checker:new api.ODCheckerCustomStructure_DiscordId("opendiscord:permissions-pin","role",false,["admin","everyone","none"],{cliDisplayName:"Pin",cliHideDescriptionInParent:true,cliDisplayDescription:"Set the permissions to 'everyone' for everyone, 'admin' for admin only, 'none' to disable or a custom discord role ID."})}, - {key:"unpin",checker:new api.ODCheckerCustomStructure_DiscordId("opendiscord:permissions-unpin","role",false,["admin","everyone","none"],{cliDisplayName:"Unpin",cliHideDescriptionInParent:true,cliDisplayDescription:"Set the permissions to 'everyone' for everyone, 'admin' for admin only, 'none' to disable or a custom discord role ID."})}, - {key:"move",checker:new api.ODCheckerCustomStructure_DiscordId("opendiscord:permissions-move","role",false,["admin","everyone","none"],{cliDisplayName:"Move",cliHideDescriptionInParent:true,cliDisplayDescription:"Set the permissions to 'everyone' for everyone, 'admin' for admin only, 'none' to disable or a custom discord role ID."})}, - {key:"rename",checker:new api.ODCheckerCustomStructure_DiscordId("opendiscord:permissions-rename","role",false,["admin","everyone","none"],{cliDisplayName:"Rename",cliHideDescriptionInParent:true,cliDisplayDescription:"Set the permissions to 'everyone' for everyone, 'admin' for admin only, 'none' to disable or a custom discord role ID."})}, - {key:"add",checker:new api.ODCheckerCustomStructure_DiscordId("opendiscord:permissions-add","role",false,["admin","everyone","none"],{cliDisplayName:"Add User",cliHideDescriptionInParent:true,cliDisplayDescription:"Set the permissions to 'everyone' for everyone, 'admin' for admin only, 'none' to disable or a custom discord role ID."})}, - {key:"remove",checker:new api.ODCheckerCustomStructure_DiscordId("opendiscord:permissions-remove","role",false,["admin","everyone","none"],{cliDisplayName:"Remove User",cliHideDescriptionInParent:true,cliDisplayDescription:"Set the permissions to 'everyone' for everyone, 'admin' for admin only, 'none' to disable or a custom discord role ID."})}, - {key:"blacklist",checker:new api.ODCheckerCustomStructure_DiscordId("opendiscord:permissions-blacklist","role",false,["admin","everyone","none"],{cliDisplayName:"Blacklist",cliHideDescriptionInParent:true,cliDisplayDescription:"Set the permissions to 'everyone' for everyone, 'admin' for admin only, 'none' to disable or a custom discord role ID."})}, - {key:"stats",checker:new api.ODCheckerCustomStructure_DiscordId("opendiscord:permissions-stats","role",false,["admin","everyone","none"],{cliDisplayName:"Stats",cliHideDescriptionInParent:true,cliDisplayDescription:"Set the permissions to 'everyone' for everyone, 'admin' for admin only, 'none' to disable or a custom discord role ID."})}, - {key:"clear",checker:new api.ODCheckerCustomStructure_DiscordId("opendiscord:permissions-clear","role",false,["admin","everyone","none"],{cliDisplayName:"Clear Tickets",cliHideDescriptionInParent:true,cliDisplayDescription:"Set the permissions to 'everyone' for everyone, 'admin' for admin only, 'none' to disable or a custom discord role ID."})}, - {key:"autoclose",checker:new api.ODCheckerCustomStructure_DiscordId("opendiscord:permissions-autoclose","role",false,["admin","everyone","none"],{cliDisplayName:"Autoclose",cliHideDescriptionInParent:true,cliDisplayDescription:"Set the permissions to 'everyone' for everyone, 'admin' for admin only, 'none' to disable or a custom discord role ID."})}, - {key:"autodelete",checker:new api.ODCheckerCustomStructure_DiscordId("opendiscord:permissions-autodelete","role",false,["admin","everyone","none"],{cliDisplayName:"Autodelete",cliHideDescriptionInParent:true,cliDisplayDescription:"Set the permissions to 'everyone' for everyone, 'admin' for admin only, 'none' to disable or a custom discord role ID."})}, - {key:"transfer",checker:new api.ODCheckerCustomStructure_DiscordId("opendiscord:permissions-transfer","role",false,["admin","everyone","none"],{cliDisplayName:"Transfer",cliHideDescriptionInParent:true,cliDisplayDescription:"Set the permissions to 'everyone' for everyone, 'admin' for admin only, 'none' to disable or a custom discord role ID."})}, - {key:"topic",checker:new api.ODCheckerCustomStructure_DiscordId("opendiscord:permissions-topic","role",false,["admin","everyone","none"],{cliDisplayName:"Topic",cliHideDescriptionInParent:true,cliDisplayDescription:"Set the permissions to 'everyone' for everyone, 'admin' for admin only, 'none' to disable or a custom discord role ID."})}, - {key:"priority",checker:new api.ODCheckerCustomStructure_DiscordId("opendiscord:permissions-priority","role",false,["admin","everyone","none"],{cliDisplayName:"Priority",cliHideDescriptionInParent:true,cliDisplayDescription:"Set the permissions to 'everyone' for everyone, 'admin' for admin only, 'none' to disable or a custom discord role ID."})}, - ],cliDisplayName:"Permissions",cliDisplayDescription:"Manage all button & command permissions in the bot. (Visit docs for more info)"})}, + {key:"closedCategory",checker:new api.ODCheckerEnabledObjectStructure("opendiscord:closed-category",{property:"enabled",enabledValue:true,ignoreCheckIfDisabled:true,checker:new api.ODCheckerObjectStructure("opendiscord:closed-category",{children:[ + {key:"enabled",checker:new api.ODCheckerBooleanStructure("opendiscord:closed-category-enabled",{cliDisplayName:"Enabled",cliDisplayDescription:"Enable/disable closed category."})}, + {key:"categoryId",checker:new api.ODCheckerCustomStructure_DiscordId("opendiscord:closed-category","category",true,[],{cliDisplayName:"Closed Category",cliDisplayDescription:"An additional category where tickets will be moved to when closed."})}, + ],cliDisplayName:"Closed Category",cliDisplayDescription:"An additional category where tickets will be moved to when closed."}),cliDisplayName:"Closed Category",cliDisplayDescription:"An additional category where tickets will be moved to when closed."})}, + + {key:"backupCategory",checker:new api.ODCheckerEnabledObjectStructure("opendiscord:backup-category",{property:"enabled",enabledValue:true,ignoreCheckIfDisabled:true,checker:new api.ODCheckerObjectStructure("opendiscord:backup-category",{children:[ + {key:"enabled",checker:new api.ODCheckerBooleanStructure("opendiscord:backup-category-enabled",{cliDisplayName:"Enabled",cliDisplayDescription:"Enable/disable backup category."})}, + {key:"categoryId",checker:new api.ODCheckerCustomStructure_DiscordId("opendiscord:backup-category","category",true,[],{cliDisplayName:"Backup Category",cliDisplayDescription:"An additional category where tickets will be created in when the original category is full (50 channels)."})}, + ],cliDisplayName:"Backup Category",cliDisplayDescription:"An additional category where tickets will be created in when the original category is full (50 channels)."}),cliDisplayName:"Backup Category",cliDisplayDescription:"An additional category where tickets will be created in when the original category is full (50 channels)."})}, + + {key:"claimedCategories",checker:new api.ODCheckerArrayStructure("opendiscord:claimed-categories",{allowDoubles:false,allowedTypes:["object"],cliDisplayPropertyName:"claim category",propertyChecker:new api.ODCheckerObjectStructure("opendiscord:claimed-category",{children:[ + {key:"user",checker:new api.ODCheckerCustomStructure_DiscordId("opendiscord:claimed-user","user",false,[],{cliDisplayName:"User",cliDisplayDescription:"The discord user ID of a ticket claimer."})}, + {key:"category",checker:new api.ODCheckerCustomStructure_DiscordId("opendiscord:claimed-category","category",false,[],{cliDisplayName:"Category",cliDisplayDescription:"The discord category ID to move the ticket to."})} + ],cliDisplayName:"Claimed Category",cliDisplayDescription:"Move claimed tickets to the matching channel category of the user that claimed the ticket.",cliDisplayKeyInParentArray:"user",cliDisplayAdditionalKeysInParentArray:["user","category"]}),cliDisplayName:"Claimed Categories",cliDisplayDescription:"Move claimed tickets to the matching channel category of the user that claimed the ticket."})}, + + ],cliDisplayName:"Ticket System",cliDisplayDescription:"Configure the 'Open Ticket' ticket system."})}, - {key:"messages",checker:new api.ODCheckerObjectStructure("opendiscord:system-permissions",{children:[ - {key:"creation",checker:createMsgStructure("opendiscord:msg-creation","Ticket Created")}, - {key:"closing",checker:createMsgStructure("opendiscord:msg-closing","Ticket Closed")}, - {key:"deleting",checker:createMsgStructure("opendiscord:msg-deleting","Ticket Deleted")}, - {key:"reopening",checker:createMsgStructure("opendiscord:msg-reopening","Ticket Reopened")}, - {key:"claiming",checker:createMsgStructure("opendiscord:msg-claiming","Ticket Claimed")}, - {key:"pinning",checker:createMsgStructure("opendiscord:msg-pinning","Ticket Pinned")}, - {key:"adding",checker:createMsgStructure("opendiscord:msg-adding","User Added")}, - {key:"removing",checker:createMsgStructure("opendiscord:msg-removing","User Removed")}, - {key:"renaming",checker:createMsgStructure("opendiscord:msg-renaming","Ticket Renamed")}, - {key:"moving",checker:createMsgStructure("opendiscord:msg-moving","Ticket Moved")}, - {key:"blacklisting",checker:createMsgStructure("opendiscord:msg-blacklisting","User Blacklisted")}, - {key:"transferring",checker:createMsgStructure("opendiscord:msg-transferring","Ticket Transferred")}, - {key:"topicChange",checker:createMsgStructure("opendiscord:msg-topic-change","Topic Changed")}, - {key:"priorityChange",checker:createMsgStructure("opendiscord:msg-priority-change","Priority Changed")}, - {key:"reactionRole",checker:createMsgStructure("opendiscord:msg-reaction-role","Reaction Role")}, - ],cliDisplayName:"Messages",cliDisplayDescription:"Manage all messages & DM's for each action of the bot. (Visit docs for more info)"})}, - ],cliDisplayName:"System",cliDisplayDescription:"Configure everything related to the ticket system."})} + //COMMAND PERMISSIONS + {key:"permissions",checker:new api.ODCheckerObjectStructure("opendiscord:permissions",{children:[ + {key:"help",checker:createPermissionStructure("opendiscord:permissions-help","Help")}, + {key:"panel",checker:createPermissionStructure("opendiscord:permissions-panel","Panel")}, + {key:"ticket",checker:createPermissionStructure("opendiscord:permissions-ticket","Ticket")}, + {key:"close",checker:createPermissionStructure("opendiscord:permissions-close","Close")}, + {key:"delete",checker:createPermissionStructure("opendiscord:permissions-delete","Delete")}, + {key:"reopen",checker:createPermissionStructure("opendiscord:permissions-reopen","Reopen")}, + {key:"claim",checker:createPermissionStructure("opendiscord:permissions-claim","Claim")}, + {key:"unclaim",checker:createPermissionStructure("opendiscord:permissions-unclaim","Unclaim")}, + {key:"pin",checker:createPermissionStructure("opendiscord:permissions-pin","Pin")}, + {key:"unpin",checker:createPermissionStructure("opendiscord:permissions-unpin","Unpin")}, + {key:"move",checker:createPermissionStructure("opendiscord:permissions-move","Move")}, + {key:"rename",checker:createPermissionStructure("opendiscord:permissions-rename","Rename")}, + {key:"add",checker:createPermissionStructure("opendiscord:permissions-add","Add User")}, + {key:"remove",checker:createPermissionStructure("opendiscord:permissions-remove","Remove User")}, + {key:"blacklist",checker:createPermissionStructure("opendiscord:permissions-blacklist","Blacklist")}, + {key:"stats",checker:createPermissionStructure("opendiscord:permissions-stats","Stats")}, + {key:"clear",checker:createPermissionStructure("opendiscord:permissions-clear","Clear Tickets")}, + {key:"autoclose",checker:createPermissionStructure("opendiscord:permissions-autoclose","Autoclose")}, + {key:"autodelete",checker:createPermissionStructure("opendiscord:permissions-autodelete","Autodelete")}, + {key:"transfer",checker:createPermissionStructure("opendiscord:permissions-transfer","Transfer")}, + {key:"topic",checker:createPermissionStructure("opendiscord:permissions-topic","Topic")}, + {key:"priority",checker:createPermissionStructure("opendiscord:permissions-priority","Priority")}, + {key:"transcripts",checker:createPermissionStructure("opendiscord:permissions-transcripts","Transcripts History")}, + ],cliDisplayName:"Permissions",cliDisplayDescription:"Manage all button & command permissions in the bot. (Visit docs for more info)"})}, ],cliDisplayName:"General",cliDisplayDescription:"General settings for the bot."}) export const defaultOptionsStructure = new api.ODCheckerArrayStructure("opendiscord:options",{allowedTypes:["object"],cliDisplayPropertyName:"option",propertyChecker:new api.ODCheckerObjectSwitchStructure("opendiscord:options",{objects:[ //TICKET {name:"Ticket",priority:0,properties:[{key:"type",value:"ticket"}],checker:new api.ODCheckerObjectStructure("opendiscord:ticket",{cliDisplayKeyInParentArray:"name",cliDisplayAdditionalKeysInParentArray:["id","type"],cliInitSkipKeys:["readonlyAdmins"],children:[ - {key:"id",checker:new api.ODCheckerCustomStructure_UniqueId("opendiscord:ticket-id","openticket","option-ids",{regex:/^[A-Za-z0-9-éèçàêâôûî]+$/,minLength:3,maxLength:40,cliDisplayName:"Id",cliDisplayDescription:"The id of this ticket option. Used in panels."})}, - {key:"name",checker:new api.ODCheckerStringStructure("opendiscord:ticket-name",{minLength:2,maxLength:45,cliDisplayName:"Name",cliDisplayDescription:"The name of this ticket option."})}, - {key:"description",checker:new api.ODCheckerStringStructure("opendiscord:ticket-description",{maxLength:256,cliDisplayName:"Description",cliDisplayDescription:"The description of this ticket option."})}, + {key:"id",checker:new api.ODCheckerCustomStructure_UniqueId("opendiscord:id","openticket","option-ids",{regex:/^[A-Za-z0-9-éèçàêâôûî]+$/,minLength:3,maxLength:40,cliDisplayName:"Id",cliDisplayDescription:"The id of this ticket option. Used in panels."})}, + {key:"name",checker:new api.ODCheckerStringStructure("opendiscord:name",{minLength:2,maxLength:45,cliDisplayName:"Name",cliDisplayDescription:"The name of this ticket option."})}, + {key:"description",checker:new api.ODCheckerStringStructure("opendiscord:description",{maxLength:256,cliDisplayName:"Description",cliDisplayDescription:"The description of this ticket option."})}, //TICKET BUTTON - {key:"button",checker:new api.ODCheckerObjectStructure("opendiscord:ticket-button",{children:[ - {key:"emoji",checker:new api.ODCheckerCustomStructure_EmojiString("opendiscord:ticket-button-emoji",0,1,true,{cliDisplayName:"Emoji",cliDisplayDescription:"The emoji of the button. At least 1 of 2 (emoji/label) must be provided."})}, - {key:"label",checker:new api.ODCheckerStringStructure("opendiscord:ticket-button-label",{maxLength:80,cliDisplayName:"Label",cliDisplayDescription:"The label of the button. At least 1 of 2 (emoji/label) must be provided."})}, - {key:"color",checker:new api.ODCheckerStringStructure("opendiscord:ticket-button-color",{choices:["gray","red","green","blue"],cliDisplayName:"Color",cliDisplayDescription:"The color of the button. Does not apply when using a dropdown panel."})}, + {key:"button",checker:new api.ODCheckerObjectStructure("opendiscord:button",{children:[ + {key:"emoji",checker:new api.ODCheckerCustomStructure_EmojiString("opendiscord:button-emoji",0,1,true,{cliDisplayName:"Emoji",cliDisplayDescription:"The emoji of the button. At least 1 of 2 (emoji/label) must be provided."})}, + {key:"label",checker:new api.ODCheckerStringStructure("opendiscord:button-label",{maxLength:80,cliDisplayName:"Label",cliDisplayDescription:"The label of the button. At least 1 of 2 (emoji/label) must be provided."})}, + {key:"color",checker:new api.ODCheckerStringStructure("opendiscord:button-color",{choices:["gray","red","green","blue"],cliDisplayName:"Color",cliDisplayDescription:"The color of the button. Does not apply when using a dropdown panel."})}, ],custom:(checker,value,locationTrace,locationId,locationDocs) => { const lt = checker.locationTraceDeref(locationTrace) //check if emoji & label exists @@ -389,7 +406,7 @@ export const defaultOptionsStructure = new api.ODCheckerArrayStructure("opendisc {key:"ticketAdmins",checker:new api.ODCheckerCustomStructure_DiscordIdArray("opendiscord:ticket-ticket-admins","role",[],{allowDoubles:false,cliDisplayPropertyName:"ticket admin role",cliDisplayName:"Ticket Admin Roles",cliDisplayDescription:"A list of role IDs that are only able to interact with this ticket option."},{cliDisplayName:"Ticket Admin Role",cliDisplayDescription:"The discord role ID of a ticket admin."})}, {key:"readonlyAdmins",checker:new api.ODCheckerCustomStructure_DiscordIdArray("opendiscord:ticket-readonly-admins","role",[],{allowDoubles:false,cliInitDefaultValue:[],cliDisplayPropertyName:"read-only ticket admin role",cliDisplayName:"Readonly Admin Roles",cliDisplayDescription:"A list of role IDs that are only able to read this ticket option."},{cliDisplayName:"Readonly Admin Role",cliDisplayDescription:"The discord role ID of a readonly admin."})}, {key:"allowCreationByBlacklistedUsers",checker:new api.ODCheckerBooleanStructure("opendiscord:ticket-allow-blacklisted-users",{cliDisplayName:"Allow Creation By Blacklisted Users",cliDisplayDescription:"When enabled, the blacklist doesn't apply to this ticket option/type and users are still able to create a ticket."})}, - {key:"questions",checker:new api.ODCheckerCustomStructure_UniqueIdArray("opendiscord:option-questions","openticket","question-ids","question-ids-used",{allowDoubles:false,maxLength:5,cliDisplayPropertyName:"question",cliDisplayName:"Questions",cliDisplayDescription:"A list of valid question IDs to ask before creating this ticket."},{cliDisplayName:"Question ID",cliDisplayDescription:"A valid question ID from the questions.json config.",cliAutocompleteFunc:async () => { + {key:"questions",checker:new api.ODCheckerCustomStructure_UniqueIdArray("opendiscord:option-questions","openticket","question-ids","question-ids-used",{allowDoubles:false,maxLength:5,cliDisplayPropertyName:"question",cliDisplayName:"Questions",cliDisplayDescription:"A list of valid question IDs to ask before creating this ticket."},{cliDisplayName:"Question ID",cliDisplayDescription:"A valid question ID from the questions.jsonc config.",cliAutocompleteFunc:async () => { const uncheckedRawData = opendiscord.configs.get("opendiscord:questions").data if (!Array.isArray(uncheckedRawData)) return null const idList = uncheckedRawData.filter((option) => typeof option == "object" && typeof option["id"] == "string").map((option) => option.id) @@ -400,15 +417,8 @@ export const defaultOptionsStructure = new api.ODCheckerArrayStructure("opendisc {key:"channel",checker:new api.ODCheckerObjectStructure("opendiscord:ticket-channel",{cliInitSkipKeys:["backupCategory","claimedCategory"],children:[ {key:"prefix",checker:new api.ODCheckerStringStructure("opendiscord:ticket-channel-prefix",{maxLength:25,regex:/^[^\s]*$/,cliDisplayName:"Prefix",cliDisplayDescription:"The prefix of the name of the ticket channel. (e.g. 'question-')"})}, {key:"suffix",checker:new api.ODCheckerStringStructure("opendiscord:ticket-channel-suffix",{choices:["user-name","user-nickname","user-id","random-number","random-hex","counter-dynamic","counter-fixed"],cliDisplayName:"Suffix",cliDisplayDescription:"The suffix mode to use. The number/text will be appended after the prefix."})}, - {key:"category",checker:new api.ODCheckerCustomStructure_DiscordId("opendiscord:ticket-channel-category","category",true,[],{cliDisplayName:"Category",cliDisplayDescription:"The category the ticket will be created in. Leave empty for no category."})}, - {key:"closedCategory",checker:new api.ODCheckerCustomStructure_DiscordId("opendiscord:ticket-channel-closed-category","category",true,[],{cliDisplayName:"Closed Category",cliDisplayDescription:"An additional category where the ticket will be moved to when closed."})}, - {key:"backupCategory",checker:new api.ODCheckerCustomStructure_DiscordId("opendiscord:ticket-channel-backup-category","category",true,[],{cliDisplayName:"Backup Category",cliDisplayDescription:"An additional category where the ticket will be created in when the primary category is full (50 channels)."})}, - {key:"claimedCategory",checker:new api.ODCheckerArrayStructure("opendiscord:ticket-channel-claimed-category",{allowDoubles:false,allowedTypes:["object"],cliDisplayPropertyName:"claim category",propertyChecker:new api.ODCheckerObjectStructure("opendiscord:ticket-channel-claimed-category",{children:[ - {key:"user",checker:new api.ODCheckerCustomStructure_DiscordId("opendiscord:ticket-channel-claimed-user","user",false,[],{cliDisplayName:"User",cliDisplayDescription:"A discord user ID of the ticket claimer."})}, - {key:"category",checker:new api.ODCheckerCustomStructure_DiscordId("opendiscord:ticket-channel-claimed-category","category",false,[],{cliDisplayName:"Category",cliDisplayDescription:"A discord category ID to move the ticket to."})} - ],cliDisplayName:"Claimed Category",cliDisplayDescription:"A collection of a user ID and a category ID. The ticket will be moved to the category when this user claims the ticket."}),cliDisplayName:"Claimed Categories",cliDisplayDescription:"Add categories to move the ticket to when a user claims a ticket."})}, - {key:"topic",checker:new api.ODCheckerStringStructure("opendiscord:ticket-channel-topic",{cliDisplayName:"Channel Topic",cliDisplayDescription:"The topic text of the ticket channel. Visible in the discord client when general.json 'channelTopic'.'showOptionTopic' is enabled."})}, + {key:"topic",checker:new api.ODCheckerStringStructure("opendiscord:ticket-channel-topic",{cliDisplayName:"Channel Topic",cliDisplayDescription:"The topic text of the ticket channel. Visible in the discord client when general.jsonc 'channelTopic'.'showOptionTopic' is enabled."})}, ],cliDisplayName:"Channel",cliDisplayDescription:"Manage all settings related to the ticket channel and categories."})}, //DM MESSAGE @@ -465,14 +475,14 @@ export const defaultOptionsStructure = new api.ODCheckerArrayStructure("opendisc //WEBSITE {name:"Website",priority:0,properties:[{key:"type",value:"website"}],checker:new api.ODCheckerObjectStructure("opendiscord:website",{cliDisplayKeyInParentArray:"name",cliDisplayAdditionalKeysInParentArray:["id","type"],children:[ - {key:"id",checker:new api.ODCheckerCustomStructure_UniqueId("opendiscord:website-id","openticket","option-ids",{regex:/^[A-Za-z0-9-éèçàêâôûî]+$/,minLength:3,maxLength:40,cliDisplayName:"Id",cliDisplayDescription:"The id of this website option. Used in panels."})}, - {key:"name",checker:new api.ODCheckerStringStructure("opendiscord:website-name",{minLength:2,maxLength:45,cliDisplayName:"Name",cliDisplayDescription:"The name of this website option."})}, - {key:"description",checker:new api.ODCheckerStringStructure("opendiscord:website-description",{maxLength:256,cliDisplayName:"Description",cliDisplayDescription:"The description of this website option."})}, + {key:"id",checker:new api.ODCheckerCustomStructure_UniqueId("opendiscord:id","openticket","option-ids",{regex:/^[A-Za-z0-9-éèçàêâôûî]+$/,minLength:3,maxLength:40,cliDisplayName:"Id",cliDisplayDescription:"The id of this website option. Used in panels."})}, + {key:"name",checker:new api.ODCheckerStringStructure("opendiscord:name",{minLength:2,maxLength:45,cliDisplayName:"Name",cliDisplayDescription:"The name of this website option."})}, + {key:"description",checker:new api.ODCheckerStringStructure("opendiscord:description",{maxLength:256,cliDisplayName:"Description",cliDisplayDescription:"The description of this website option."})}, //WEBSITE BUTTON - {key:"button",checker:new api.ODCheckerObjectStructure("opendiscord:ticket-button",{children:[ - {key:"emoji",checker:new api.ODCheckerCustomStructure_EmojiString("opendiscord:ticket-button-emoji",0,1,true,{cliDisplayName:"Emoji",cliDisplayDescription:"The emoji of the button. At least 1 of 2 (emoji/label) must be provided."})}, - {key:"label",checker:new api.ODCheckerStringStructure("opendiscord:ticket-button-label",{maxLength:80,cliDisplayName:"Label",cliDisplayDescription:"The label of the button. At least 1 of 2 (emoji/label) must be provided."})}, + {key:"button",checker:new api.ODCheckerObjectStructure("opendiscord:button",{children:[ + {key:"emoji",checker:new api.ODCheckerCustomStructure_EmojiString("opendiscord:button-emoji",0,1,true,{cliDisplayName:"Emoji",cliDisplayDescription:"The emoji of the button. At least 1 of 2 (emoji/label) must be provided."})}, + {key:"label",checker:new api.ODCheckerStringStructure("opendiscord:button-label",{maxLength:80,cliDisplayName:"Label",cliDisplayDescription:"The label of the button. At least 1 of 2 (emoji/label) must be provided."})}, ],custom:(checker,value,locationTrace,locationId,locationDocs) => { const lt = checker.locationTraceDeref(locationTrace) //check if emoji & label exists @@ -490,15 +500,15 @@ export const defaultOptionsStructure = new api.ODCheckerArrayStructure("opendisc //REACTION ROLES {name:"Reaction Role",priority:0,properties:[{key:"type",value:"role"}],checker:new api.ODCheckerObjectStructure("opendiscord:role",{cliDisplayKeyInParentArray:"name",cliDisplayAdditionalKeysInParentArray:["id","type"],children:[ - {key:"id",checker:new api.ODCheckerCustomStructure_UniqueId("opendiscord:role-id","openticket","option-ids",{regex:/^[A-Za-z0-9-éèçàêâôûî]+$/,minLength:3,maxLength:40,cliDisplayName:"Id",cliDisplayDescription:"The id of this role option. Used in panels."})}, - {key:"name",checker:new api.ODCheckerStringStructure("opendiscord:role-name",{minLength:2,maxLength:45,cliDisplayName:"Name",cliDisplayDescription:"The name of this role option."})}, - {key:"description",checker:new api.ODCheckerStringStructure("opendiscord:role-description",{maxLength:256,cliDisplayName:"Description",cliDisplayDescription:"The description of this role option."})}, + {key:"id",checker:new api.ODCheckerCustomStructure_UniqueId("opendiscord:id","openticket","option-ids",{regex:/^[A-Za-z0-9-éèçàêâôûî]+$/,minLength:3,maxLength:40,cliDisplayName:"Id",cliDisplayDescription:"The id of this role option. Used in panels."})}, + {key:"name",checker:new api.ODCheckerStringStructure("opendiscord:name",{minLength:2,maxLength:45,cliDisplayName:"Name",cliDisplayDescription:"The name of this role option."})}, + {key:"description",checker:new api.ODCheckerStringStructure("opendiscord:description",{maxLength:256,cliDisplayName:"Description",cliDisplayDescription:"The description of this role option."})}, //ROLE BUTTON - {key:"button",checker:new api.ODCheckerObjectStructure("opendiscord:ticket-button",{children:[ - {key:"emoji",checker:new api.ODCheckerCustomStructure_EmojiString("opendiscord:ticket-button-emoji",0,1,true,{cliDisplayName:"Emoji",cliDisplayDescription:"The emoji of the button. At least 1 of 2 (emoji/label) must be provided."})}, - {key:"label",checker:new api.ODCheckerStringStructure("opendiscord:ticket-button-label",{maxLength:80,cliDisplayName:"Label",cliDisplayDescription:"The label of the button. At least 1 of 2 (emoji/label) must be provided."})}, - {key:"color",checker:new api.ODCheckerStringStructure("opendiscord:ticket-button-color",{choices:["gray","red","green","blue"],cliDisplayName:"Color",cliDisplayDescription:"The color of the button. Does not apply when using a dropdown panel."})}, + {key:"button",checker:new api.ODCheckerObjectStructure("opendiscord:button",{children:[ + {key:"emoji",checker:new api.ODCheckerCustomStructure_EmojiString("opendiscord:button-emoji",0,1,true,{cliDisplayName:"Emoji",cliDisplayDescription:"The emoji of the button. At least 1 of 2 (emoji/label) must be provided."})}, + {key:"label",checker:new api.ODCheckerStringStructure("opendiscord:button-label",{maxLength:80,cliDisplayName:"Label",cliDisplayDescription:"The label of the button. At least 1 of 2 (emoji/label) must be provided."})}, + {key:"color",checker:new api.ODCheckerStringStructure("opendiscord:button-color",{choices:["gray","red","green","blue"],cliDisplayName:"Color",cliDisplayDescription:"The color of the button. Does not apply when using a dropdown panel."})}, ],custom:(checker,value,locationTrace,locationId,locationDocs) => { const lt = checker.locationTraceDeref(locationTrace) //check if emoji & label exists @@ -516,13 +526,52 @@ export const defaultOptionsStructure = new api.ODCheckerArrayStructure("opendisc {key:"removeRolesOnAdd",checker:new api.ODCheckerCustomStructure_DiscordIdArray("opendiscord:role-remove-roles","role",[],{allowDoubles:false,cliDisplayPropertyName:"role",cliDisplayName:"Remove Roles On Add",cliDisplayDescription:"An additional list of roles to remove when the roles of this option are added. (Can be used to select between roles)"},{cliDisplayName:"Remove Role",cliDisplayDescription:"The discord role ID you want to remove when other roles are added."})}, {key:"addOnMemberJoin",checker:new api.ODCheckerBooleanStructure("opendiscord:role-add-on-join",{cliDisplayName:"Add On Member Join",cliDisplayDescription:"Automatically add these roles to a user when joining the server."})}, ],cliDisplayName:"Reaction Role Option",cliDisplayDescription:"Manage all settings of this reaction role option."})}, + + //SUB-PANEL + {name:"Reaction Role",priority:0,properties:[{key:"type",value:"sub-panel"}],checker:new api.ODCheckerObjectStructure("opendiscord:sub-panel",{cliDisplayKeyInParentArray:"name",cliDisplayAdditionalKeysInParentArray:["id","type"],children:[ + {key:"id",checker:new api.ODCheckerCustomStructure_UniqueId("opendiscord:id","openticket","option-ids",{regex:/^[A-Za-z0-9-éèçàêâôûî]+$/,minLength:3,maxLength:40,cliDisplayName:"Id",cliDisplayDescription:"The id of this sub-panel option. Used in panels."})}, + {key:"name",checker:new api.ODCheckerStringStructure("opendiscord:name",{minLength:2,maxLength:45,cliDisplayName:"Name",cliDisplayDescription:"The name of this sub-panel option."})}, + {key:"description",checker:new api.ODCheckerStringStructure("opendiscord:description",{maxLength:256,cliDisplayName:"Description",cliDisplayDescription:"The description of this sub-panel option."})}, + + //SUB-PANEL BUTTON + {key:"button",checker:new api.ODCheckerObjectStructure("opendiscord:button",{children:[ + {key:"emoji",checker:new api.ODCheckerCustomStructure_EmojiString("opendiscord:button-emoji",0,1,true,{cliDisplayName:"Emoji",cliDisplayDescription:"The emoji of the button. At least 1 of 2 (emoji/label) must be provided."})}, + {key:"label",checker:new api.ODCheckerStringStructure("opendiscord:button-label",{maxLength:80,cliDisplayName:"Label",cliDisplayDescription:"The label of the button. At least 1 of 2 (emoji/label) must be provided."})}, + {key:"color",checker:new api.ODCheckerStringStructure("opendiscord:button-color",{choices:["gray","red","green","blue"],cliDisplayName:"Color",cliDisplayDescription:"The color of the button. Does not apply when using a dropdown panel."})}, + ],custom:(checker,value,locationTrace,locationId,locationDocs) => { + const lt = checker.locationTraceDeref(locationTrace) + //check if emoji & label exists + if (typeof value != "object") return false + else if (value && value["emoji"].length < 1 && value["label"].length < 1){ + //label & emoji are both empty + checker.createMessage("opendiscord:invalid-button","error",`This button needs to have at least an "emoji" or "label"!`,lt,null,[`"emoji"`,`"label"`],locationId,locationDocs) + return false + }else return true + },cliDisplayName:"Button",cliDisplayDescription:"Customise the button layout of this sub-panel option."})}, + + //SUB-PANEL SETTINGS + {key:"subPanelId",checker:new api.ODCheckerStringStructure("ot-footers:panel-id",{custom:(checker,value,locationTrace,locationId,locationDocs) => { + const lt = checker.locationTraceDeref(locationTrace) + if (typeof value != "string") return false + + if (getUnsafePanelIds().includes(value)){ + //exists + return true + }else{ + //doesn't exist + checker.createMessage("opendiscord:id-non-existent","error",`The panel id "${value}" doesn't exist!`,lt,null,[`"${value}"`],locationId,locationDocs) + return false + } + }})}, + ],cliDisplayName:"Sub-Panel Option",cliDisplayDescription:"Manage all settings of this sub-panel option."})}, + ],cliDisplayName:"Option",cliDisplayDescription:"Manage an option of one of the 3 types: ticket, website, role."}),cliDisplayName:"Options",cliDisplayDescription:"A list of all options in the bot. Here you can add, modify & remove ticket types, website buttons & reaction roles!"}) export const defaultPanelsStructure = new api.ODCheckerArrayStructure("opendiscord:panels",{allowedTypes:["object"],cliDisplayPropertyName:"panel",propertyChecker:new api.ODCheckerObjectStructure("opendiscord:panels",{cliDisplayKeyInParentArray:"name",cliDisplayAdditionalKeysInParentArray:["id","dropdown"],children:[ {key:"id",checker:new api.ODCheckerCustomStructure_UniqueId("opendiscord:panel-id","openticket","panel-ids",{regex:/^[A-Za-z0-9-éèçàêâôûî]+$/,minLength:3,maxLength:40,cliDisplayName:"Id",cliDisplayDescription:"The id of this panel. Used in the /panel command."})}, {key:"name",checker:new api.ODCheckerStringStructure("opendiscord:panel-name",{minLength:3,maxLength:50,cliDisplayName:"Name",cliDisplayDescription:"The name of this panel."})}, {key:"dropdown",checker:new api.ODCheckerBooleanStructure("opendiscord:panel-dropdown",{cliDisplayName:"Dropdown",cliDisplayDescription:"Decide whether to use buttons or a dropdown in the panel. Dropdowns only support options of the 'ticket' type!"})}, - {key:"options",checker:new api.ODCheckerCustomStructure_UniqueIdArray("opendiscord:panel-options","openticket","option-ids","option-ids-used",{allowDoubles:false,minLength:1,maxLength:25,cliDisplayPropertyName:"option",cliDisplayName:"Options",cliDisplayDescription:"A list of valid option IDs to show in this panel."},{cliDisplayName:"Option ID",cliDisplayDescription:"A valid option ID from the options.json config.",cliAutocompleteFunc:async () => { + {key:"options",checker:new api.ODCheckerCustomStructure_UniqueIdArray("opendiscord:panel-options","openticket","option-ids","option-ids-used",{allowDoubles:false,minLength:1,maxLength:25,cliDisplayPropertyName:"option",cliDisplayName:"Options",cliDisplayDescription:"A list of valid option IDs to show in this panel."},{cliDisplayName:"Option ID",cliDisplayDescription:"A valid option ID from the options.jsonc config.",cliAutocompleteFunc:async () => { const uncheckedRawData = opendiscord.configs.get("opendiscord:options").data if (!Array.isArray(uncheckedRawData)) return null const idList = uncheckedRawData.filter((option) => typeof option == "object" && typeof option["id"] == "string").map((option) => option.id) @@ -536,6 +585,8 @@ export const defaultPanelsStructure = new api.ODCheckerArrayStructure("opendisco //SETTINGS {key:"settings",checker:new api.ODCheckerObjectStructure("opendiscord:panel-settings",{cliInitSkipKeys:["dropdownPlaceholder","describeOptionsCustomTitle"],children:[ {key:"dropdownPlaceholder",checker:new api.ODCheckerStringStructure("opendiscord:panel-settings-placeholder",{maxLength:100,cliInitDefaultValue:"Create a ticket!",cliDisplayName:"Dropdown Placeholder",cliDisplayDescription:"Configure the text displayed in the dropdown when nothing is selected."})}, + {key:"maximumButtonsPerRow",checker:new api.ODCheckerNumberStructure("opendiscord:panel-settings-row-amount",{min:1,max:5,floatAllowed:false,cliInitDefaultValue:5,cliDisplayName:"Maximum Buttons Per Row",cliDisplayDescription:"Set the maximum amount of buttons in a single row before starting a new one."})}, + {key:"enableMaxTicketsWarningInText",checker:new api.ODCheckerBooleanStructure("opendiscord:panel-settings-maxtickets-text",{cliDisplayName:"Enable Max Tickets Warning (Text)",cliDisplayDescription:"Enable/disable the warning which shows how many tickets you can create in the text contents of the panel."})}, {key:"enableMaxTicketsWarningInEmbed",checker:new api.ODCheckerBooleanStructure("opendiscord:panel-settings-maxtickets-embed",{cliDisplayName:"Enable Max Tickets Warning (Embed)",cliDisplayDescription:"Enable/disable the warning which shows how many tickets you can create in the embed of the panel."})}, @@ -547,20 +598,109 @@ export const defaultPanelsStructure = new api.ODCheckerArrayStructure("opendisco ],cliDisplayName:"Settings",cliDisplayDescription:"Manage additional settings & customisability for this panel."})}, ],cliDisplayName:"Panel",cliDisplayDescription:"Manage, customise and configure a panel to your preference."}),cliDisplayName:"Panels",cliDisplayDescription:"A list of all panels in the bot. Here you can add, modify & remove existing panels or customise them to your preference."}) -export const defaultQuestionsStructure = new api.ODCheckerArrayStructure("opendiscord:questions",{allowedTypes:["object"],cliDisplayPropertyName:"question",propertyChecker:new api.ODCheckerObjectStructure("opendiscord:questions",{cliDisplayKeyInParentArray:"name",cliDisplayAdditionalKeysInParentArray:["id","type"],children:[ - {key:"id",checker:new api.ODCheckerCustomStructure_UniqueId("opendiscord:question-id","openticket","question-ids",{regex:/^[A-Za-z0-9-éèçàêâôûî]+$/,minLength:3,maxLength:40,cliDisplayName:"Id",cliDisplayDescription:"The id of this question. Used in ticket options."})}, - {key:"name",checker:new api.ODCheckerStringStructure("opendiscord:question-name",{minLength:3,maxLength:45,cliDisplayName:"Name",cliDisplayDescription:"The name of this question."})}, - {key:"type",checker:new api.ODCheckerStringStructure("opendiscord:question-type",{choices:["short","paragraph"],cliDisplayName:"Type",cliDisplayDescription:"The type of this question (short/paragraph)."})}, - - {key:"required",checker:new api.ODCheckerBooleanStructure("opendiscord:question-required",{cliDisplayName:"Required",cliDisplayDescription:"Is this question required? If not, it can be left empty."})}, - {key:"placeholder",checker:new api.ODCheckerStringStructure("opendiscord:question-placeholder",{maxLength:100,cliDisplayName:"Placeholder",cliDisplayDescription:"The placeholder to show in the field when nothing has been written yet."})}, - - {key:"length",checker:new api.ODCheckerEnabledObjectStructure("opendiscord:question-length",{property:"enabled",enabledValue:true,checker:new api.ODCheckerObjectStructure("opendiscord:question-length",{children:[ - {key:"enabled",checker:new api.ODCheckerBooleanStructure("opendiscord:question-length-enabled",{cliDisplayName:"Enabled",cliDisplayDescription:"Enable/disable length validation for this question."})}, - {key:"min",checker:new api.ODCheckerNumberStructure("opendiscord:question-length-min",{min:0,max:1024,negativeAllowed:false,floatAllowed:false,cliDisplayName:"Min Length",cliDisplayDescription:"The minimum amount of characters required."})}, - {key:"max",checker:new api.ODCheckerNumberStructure("opendiscord:question-length-max",{min:1,max:1024,negativeAllowed:false,floatAllowed:false,cliInitDefaultValue:100,cliDisplayName:"Max Length",cliDisplayDescription:"The maximum amount of characters allowed."})}, - ],cliDisplayName:"Length Validation",cliDisplayDescription:"Add length validation to the question. This way, the contents must be at least/most ... characters."}),cliDisplayName:"Length Validation",cliDisplayDescription:"Add length validation to the question. This way, the contents must be at least/most ... characters."})}, -],cliDisplayName:"Question",cliDisplayDescription:"Manage, customise and configure a question to your preference."}),cliDisplayName:"Questions",cliDisplayDescription:"A list of all questions in the bot. Here you can add, modify & remove existing questions or customise them to your preference."}) +export const defaultQuestionsStructure = new api.ODCheckerArrayStructure("opendiscord:questions",{allowedTypes:["object"],cliDisplayPropertyName:"question",propertyChecker:new api.ODCheckerObjectSwitchStructure("opendiscord:options",{objects:[ + //SHORT QUESTION + {name:"Short Question",priority:0,properties:[{key:"type",value:"short"}],checker:new api.ODCheckerObjectStructure("opendiscord:short-question",{cliDisplayKeyInParentArray:"name",cliDisplayAdditionalKeysInParentArray:["id","type"],children:[ + {key:"id",checker:new api.ODCheckerCustomStructure_UniqueId("opendiscord:question-id","openticket","question-ids",{regex:/^[A-Za-z0-9-éèçàêâôûî]+$/,minLength:3,maxLength:40,cliDisplayName:"Id",cliDisplayDescription:"The id of this question. Used in ticket options."})}, + {key:"name",checker:new api.ODCheckerStringStructure("opendiscord:question-name",{minLength:3,maxLength:45,cliDisplayName:"Name",cliDisplayDescription:"The name of this question."})}, + {key:"description",checker:new api.ODCheckerStringStructure("opendiscord:question-description",{maxLength:100,cliDisplayName:"Description",cliDisplayDescription:"The description of this question."})}, + {key:"required",checker:new api.ODCheckerBooleanStructure("opendiscord:question-required",{cliDisplayName:"Required",cliDisplayDescription:"Is this question required? If not, it can be left empty."})}, + + {key:"placeholder",checker:new api.ODCheckerStringStructure("opendiscord:question-placeholder",{maxLength:100,cliDisplayName:"Placeholder",cliDisplayDescription:"The placeholder to show in the field when nothing has been written yet."})}, + + {key:"length",checker:new api.ODCheckerEnabledObjectStructure("opendiscord:question-length",{property:"enabled",enabledValue:true,checker:new api.ODCheckerObjectStructure("opendiscord:question-length",{children:[ + {key:"enabled",checker:new api.ODCheckerBooleanStructure("opendiscord:question-length-enabled",{cliDisplayName:"Enabled",cliDisplayDescription:"Enable/disable length validation for this question."})}, + {key:"min",checker:new api.ODCheckerNumberStructure("opendiscord:question-length-min",{min:0,max:1024,negativeAllowed:false,floatAllowed:false,cliDisplayName:"Min Length",cliDisplayDescription:"The minimum amount of characters required."})}, + {key:"max",checker:new api.ODCheckerNumberStructure("opendiscord:question-length-max",{min:1,max:1024,negativeAllowed:false,floatAllowed:false,cliInitDefaultValue:100,cliDisplayName:"Max Length",cliDisplayDescription:"The maximum amount of characters allowed."})}, + ],cliDisplayName:"Length Validation",cliDisplayDescription:"Add length validation to the question. This way, the contents must be at least/most ... characters."}),cliDisplayName:"Length Validation",cliDisplayDescription:"Add length validation to the question. This way, the contents must be at least/most ... characters."})}, + ],cliDisplayName:"Short Question",cliDisplayDescription:"Manage, customise and configure the short question to your preference."})}, + + //PARAGRAPH QUESTION + {name:"Paragraph Question",priority:0,properties:[{key:"type",value:"paragraph"}],checker:new api.ODCheckerObjectStructure("opendiscord:paragraph-question",{cliDisplayKeyInParentArray:"name",cliDisplayAdditionalKeysInParentArray:["id","type"],children:[ + {key:"id",checker:new api.ODCheckerCustomStructure_UniqueId("opendiscord:question-id","openticket","question-ids",{regex:/^[A-Za-z0-9-éèçàêâôûî]+$/,minLength:3,maxLength:40,cliDisplayName:"Id",cliDisplayDescription:"The id of this question. Used in ticket options."})}, + {key:"name",checker:new api.ODCheckerStringStructure("opendiscord:question-name",{minLength:3,maxLength:45,cliDisplayName:"Name",cliDisplayDescription:"The name of this question."})}, + {key:"description",checker:new api.ODCheckerStringStructure("opendiscord:question-description",{maxLength:100,cliDisplayName:"Description",cliDisplayDescription:"The description of this question."})}, + {key:"required",checker:new api.ODCheckerBooleanStructure("opendiscord:question-required",{cliDisplayName:"Required",cliDisplayDescription:"Is this question required? If not, it can be left empty."})}, + + {key:"placeholder",checker:new api.ODCheckerStringStructure("opendiscord:question-placeholder",{maxLength:100,cliDisplayName:"Placeholder",cliDisplayDescription:"The placeholder to show in the field when nothing has been written yet."})}, + + {key:"length",checker:new api.ODCheckerEnabledObjectStructure("opendiscord:question-length",{property:"enabled",enabledValue:true,checker:new api.ODCheckerObjectStructure("opendiscord:question-length",{children:[ + {key:"enabled",checker:new api.ODCheckerBooleanStructure("opendiscord:question-length-enabled",{cliDisplayName:"Enabled",cliDisplayDescription:"Enable/disable length validation for this question."})}, + {key:"min",checker:new api.ODCheckerNumberStructure("opendiscord:question-length-min",{min:0,max:1024,negativeAllowed:false,floatAllowed:false,cliDisplayName:"Min Length",cliDisplayDescription:"The minimum amount of characters required."})}, + {key:"max",checker:new api.ODCheckerNumberStructure("opendiscord:question-length-max",{min:1,max:1024,negativeAllowed:false,floatAllowed:false,cliInitDefaultValue:100,cliDisplayName:"Max Length",cliDisplayDescription:"The maximum amount of characters allowed."})}, + ],cliDisplayName:"Length Validation",cliDisplayDescription:"Add length validation to the question. This way, the contents must be at least/most ... characters."}),cliDisplayName:"Length Validation",cliDisplayDescription:"Add length validation to the question. This way, the contents must be at least/most ... characters."})}, + ],cliDisplayName:"Paragraph Question",cliDisplayDescription:"Manage, customise and configure the paragraph question to your preference."})}, + + //TEXT DISPLAY QUESTION + {name:"Text Display Question",priority:0,properties:[{key:"type",value:"text-display"}],checker:new api.ODCheckerObjectStructure("opendiscord:text-display-question",{cliDisplayKeyInParentArray:"name",cliDisplayAdditionalKeysInParentArray:["id","type"],children:[ + {key:"id",checker:new api.ODCheckerCustomStructure_UniqueId("opendiscord:question-id","openticket","question-ids",{regex:/^[A-Za-z0-9-éèçàêâôûî]+$/,minLength:3,maxLength:40,cliDisplayName:"Id",cliDisplayDescription:"The id of this text display. Used in ticket options."})}, + {key:"textContents",checker:new api.ODCheckerStringStructure("opendiscord:text-contents",{minLength:1,maxLength:2048,cliDisplayName:"Text Contents",cliDisplayDescription:"The text contents to show in the modal."})}, + ],cliDisplayName:"Text Display Question",cliDisplayDescription:"Manage, customise and configure the text display question to your preference."})}, + + //DROPDOWN QUESTION + {name:"Dropdown Question",priority:0,properties:[{key:"type",value:"dropdown"}],checker:new api.ODCheckerObjectStructure("opendiscord:dropdown-question",{cliDisplayKeyInParentArray:"name",cliDisplayAdditionalKeysInParentArray:["id","type"],children:[ + {key:"id",checker:new api.ODCheckerCustomStructure_UniqueId("opendiscord:question-id","openticket","question-ids",{regex:/^[A-Za-z0-9-éèçàêâôûî]+$/,minLength:3,maxLength:40,cliDisplayName:"Id",cliDisplayDescription:"The id of this question. Used in ticket options."})}, + {key:"name",checker:new api.ODCheckerStringStructure("opendiscord:question-name",{minLength:3,maxLength:45,cliDisplayName:"Name",cliDisplayDescription:"The name of this question."})}, + {key:"description",checker:new api.ODCheckerStringStructure("opendiscord:question-description",{maxLength:100,cliDisplayName:"Description",cliDisplayDescription:"The description of this question."})}, + {key:"required",checker:new api.ODCheckerBooleanStructure("opendiscord:question-required",{cliDisplayName:"Required",cliDisplayDescription:"Is this question required? If not, it can be left empty."})}, + + {key:"placeholder",checker:new api.ODCheckerStringStructure("opendiscord:question-placeholder",{maxLength:100,cliDisplayName:"Placeholder",cliDisplayDescription:"The placeholder to show in the field when nothing has been written yet."})}, + {key:"choices",checker:new api.ODCheckerArrayStructure("opendiscord:choices",{allowedTypes:["object"],minLength:1,maxLength:25,propertyChecker:new api.ODCheckerObjectStructure("opendiscord:choice",{children:[ + {key:"title",checker:new api.ODCheckerStringStructure("opendiscord:choice-title",{minLength:1,maxLength:100,cliDisplayName:"Title",cliDisplayDescription:"The title of this choice."})}, + {key:"description",checker:new api.ODCheckerStringStructure("opendiscord:choice-description",{maxLength:100,cliDisplayName:"Description",cliDisplayDescription:"The description of this choice."})}, + {key:"emoji",checker:new api.ODCheckerCustomStructure_EmojiString("opendiscord:choice-emoji",0,1,true,{cliDisplayName:"Emoji",cliDisplayDescription:"The emoji of the choice. Leave empty to disable."})} + ],cliDisplayName:"Choice",cliDisplayDescription:"A choice for this dropdown question."}),cliDisplayName:"Choices",cliDisplayPropertyName:"choice",cliDisplayDescription:"Manage all available choices of this dropdown question."})} + ],cliDisplayName:"Dropdown Question",cliDisplayDescription:"Manage, customise and configure the dropdown question to your preference."})}, + + //RADIO SELECT QUESTION + {name:"Radio Select Question",priority:0,properties:[{key:"type",value:"radio-select"}],checker:new api.ODCheckerObjectStructure("opendiscord:radio-select-question",{cliDisplayKeyInParentArray:"name",cliDisplayAdditionalKeysInParentArray:["id","type"],children:[ + {key:"id",checker:new api.ODCheckerCustomStructure_UniqueId("opendiscord:question-id","openticket","question-ids",{regex:/^[A-Za-z0-9-éèçàêâôûî]+$/,minLength:3,maxLength:40,cliDisplayName:"Id",cliDisplayDescription:"The id of this question. Used in ticket options."})}, + {key:"name",checker:new api.ODCheckerStringStructure("opendiscord:question-name",{minLength:3,maxLength:45,cliDisplayName:"Name",cliDisplayDescription:"The name of this question."})}, + {key:"description",checker:new api.ODCheckerStringStructure("opendiscord:question-description",{maxLength:100,cliDisplayName:"Description",cliDisplayDescription:"The description of this question."})}, + {key:"required",checker:new api.ODCheckerBooleanStructure("opendiscord:question-required",{cliDisplayName:"Required",cliDisplayDescription:"Is this question required? If not, it can be left empty."})}, + + {key:"choices",checker:new api.ODCheckerArrayStructure("opendiscord:choices",{allowedTypes:["object"],minLength:2,maxLength:10,propertyChecker:new api.ODCheckerObjectStructure("opendiscord:choice",{children:[ + {key:"title",checker:new api.ODCheckerStringStructure("opendiscord:choice-title",{minLength:1,maxLength:100,cliDisplayName:"Title",cliDisplayDescription:"The title of this choice."})}, + {key:"description",checker:new api.ODCheckerStringStructure("opendiscord:choice-description",{maxLength:100,cliDisplayName:"Description",cliDisplayDescription:"The description of this choice."})}, + {key:"selectedByDefault",checker:new api.ODCheckerBooleanStructure("opendiscord:choice-default",{cliDisplayName:"Selected By Default",cliDisplayDescription:"Should this choice be selected by default?"})} + ],cliDisplayName:"Choice",cliDisplayDescription:"A choice for this radio select question."}),cliDisplayName:"Choices",cliDisplayPropertyName:"choice",cliDisplayDescription:"Manage all available choices of this radio select question."})} + ],cliDisplayName:"Radio Select Question",cliDisplayDescription:"Manage, customise and configure the radio select question to your preference."})}, + + //CHECKBOX SELECT QUESTION + {name:"Checkbox Select Question",priority:0,properties:[{key:"type",value:"checkbox-select"}],checker:new api.ODCheckerObjectStructure("opendiscord:checkbox-select-question",{cliDisplayKeyInParentArray:"name",cliDisplayAdditionalKeysInParentArray:["id","type"],children:[ + {key:"id",checker:new api.ODCheckerCustomStructure_UniqueId("opendiscord:question-id","openticket","question-ids",{regex:/^[A-Za-z0-9-éèçàêâôûî]+$/,minLength:3,maxLength:40,cliDisplayName:"Id",cliDisplayDescription:"The id of this question. Used in ticket options."})}, + {key:"name",checker:new api.ODCheckerStringStructure("opendiscord:question-name",{minLength:3,maxLength:45,cliDisplayName:"Name",cliDisplayDescription:"The name of this question."})}, + {key:"description",checker:new api.ODCheckerStringStructure("opendiscord:question-description",{maxLength:100,cliDisplayName:"Description",cliDisplayDescription:"The description of this question."})}, + {key:"required",checker:new api.ODCheckerBooleanStructure("opendiscord:question-required",{cliDisplayName:"Required",cliDisplayDescription:"Is this question required? If not, it can be left empty."})}, + + {key:"limits",checker:new api.ODCheckerEnabledObjectStructure("opendiscord:amount",{property:"enabled",enabledValue:true,checker:new api.ODCheckerObjectStructure("opendiscord:amount",{children:[ + {key:"enabled",checker:new api.ODCheckerBooleanStructure("opendiscord:amount-enabled",{cliDisplayName:"Enabled",cliDisplayDescription:"Enable/disable checking the min/max amount of required checkboxes."})}, + {key:"min",checker:new api.ODCheckerNumberStructure("opendiscord:amount-min",{min:0,max:10,floatAllowed:false,cliDisplayName:"Min Amount",cliDisplayDescription:"The minimum amount of checkboxes required."})}, + {key:"max",checker:new api.ODCheckerNumberStructure("opendiscord:amount-max",{min:1,max:10,floatAllowed:false,cliInitDefaultValue:10,cliDisplayName:"Max Amount",cliDisplayDescription:"The maximum amount of checkboxes allowed."})}, + ],cliDisplayName:"Checkbox Limits",cliDisplayDescription:"Verify the minimum or maximum amount of checkboxes required."}),cliDisplayName:"Checkbox Limits",cliDisplayDescription:"Verify the minimum or maximum amount of checkboxes required."})}, + + {key:"choices",checker:new api.ODCheckerArrayStructure("opendiscord:choices",{allowedTypes:["object"],minLength:1,maxLength:10,propertyChecker:new api.ODCheckerObjectStructure("opendiscord:choice",{children:[ + {key:"title",checker:new api.ODCheckerStringStructure("opendiscord:choice-title",{minLength:1,maxLength:100,cliDisplayName:"Title",cliDisplayDescription:"The title of this choice."})}, + {key:"description",checker:new api.ODCheckerStringStructure("opendiscord:choice-description",{maxLength:100,cliDisplayName:"Description",cliDisplayDescription:"The description of this choice."})}, + {key:"selectedByDefault",checker:new api.ODCheckerBooleanStructure("opendiscord:choice-default",{cliDisplayName:"Selected By Default",cliDisplayDescription:"Should this choice be selected by default?"})} + ],cliDisplayName:"Choice",cliDisplayDescription:"A choice for this checkbox select question."}),cliDisplayName:"Choices",cliDisplayPropertyName:"choice",cliDisplayDescription:"Manage all available choices of this checkbox select question."})} + ],cliDisplayName:"Checkbox Select Question",cliDisplayDescription:"Manage, customise and configure the checkbox select question to your preference."})}, + + //FILE UPLOAD QUESTION + {name:"File Upload Question",priority:0,properties:[{key:"type",value:"file-upload"}],checker:new api.ODCheckerObjectStructure("opendiscord:file-upload-question",{cliDisplayKeyInParentArray:"name",cliDisplayAdditionalKeysInParentArray:["id","type"],children:[ + {key:"id",checker:new api.ODCheckerCustomStructure_UniqueId("opendiscord:question-id","openticket","question-ids",{regex:/^[A-Za-z0-9-éèçàêâôûî]+$/,minLength:3,maxLength:40,cliDisplayName:"Id",cliDisplayDescription:"The id of this question. Used in ticket options."})}, + {key:"name",checker:new api.ODCheckerStringStructure("opendiscord:question-name",{minLength:3,maxLength:45,cliDisplayName:"Name",cliDisplayDescription:"The name of this question."})}, + {key:"description",checker:new api.ODCheckerStringStructure("opendiscord:question-description",{maxLength:100,cliDisplayName:"Description",cliDisplayDescription:"The description of this question."})}, + {key:"required",checker:new api.ODCheckerBooleanStructure("opendiscord:question-required",{cliDisplayName:"Required",cliDisplayDescription:"Is this question required? If not, it can be left empty."})}, + + {key:"limits",checker:new api.ODCheckerEnabledObjectStructure("opendiscord:amount",{property:"enabled",enabledValue:true,checker:new api.ODCheckerObjectStructure("opendiscord:amount",{children:[ + {key:"enabled",checker:new api.ODCheckerBooleanStructure("opendiscord:amount-enabled",{cliDisplayName:"Enabled",cliDisplayDescription:"Enable/disable checking the min/max amount of uploaded files."})}, + {key:"min",checker:new api.ODCheckerNumberStructure("opendiscord:amount-min",{min:0,max:10,floatAllowed:false,cliDisplayName:"Min Amount",cliDisplayDescription:"The minimum amount of uploaded files required."})}, + {key:"max",checker:new api.ODCheckerNumberStructure("opendiscord:amount-max",{min:1,max:10,floatAllowed:false,cliInitDefaultValue:10,cliDisplayName:"Max Amount",cliDisplayDescription:"The maximum amount of uploaded files allowed."})}, + ],cliDisplayName:"Upload Limits",cliDisplayDescription:"Verify the minimum or maximum amount of uploaded files."}),cliDisplayName:"Checkbox Limits",cliDisplayDescription:"Verify the minimum or maximum amount of uploaded files."})}, + ],cliDisplayName:"Checkbox Select Question",cliDisplayDescription:"Manage, customise and configure the file upload question to your preference."})}, + +],cliDisplayName:"Question",cliDisplayDescription:"Manage a question of one of the 7 types: short, paragraph, text-display, dropdown, radio-select, checkbox-select, file-upload."}),cliDisplayName:"Questions",cliDisplayDescription:"A list of all questions in the bot. Here you can add, modify & remove existing questions or customise them to your preference."}) export const defaultTranscriptsStructure = new api.ODCheckerObjectStructure("opendiscord:transcripts",{children:[ //GENERAL @@ -688,11 +828,11 @@ export const defaultDropdownOptionsFunction = (manager:api.ODCheckerManager, fun if (panel.options.some((optId) => { const option = optionConfig.data.find((option) => option.id == optId) if (!option) return false - if (option.type != "ticket") return true + if (option.type != "ticket" && option.type != "role" && option.type != "sub-panel") return true else return false })){ //give error when non-ticket options exist in dropdown panel! - final.push(functions.createMessage("opendiscord:panels","opendiscord:dropdown-option",panelConfig.file,"error","A panel with dropdown enabled can only contain options of the 'ticket' type!",[index,"options"],null,[],new api.ODId("opendiscord:dropdown-options"),null)) + final.push(functions.createMessage("opendiscord:panels","opendiscord:dropdown-option",panelConfig.file,"error","A panel with dropdown can only contain options of the types: 'ticket', 'role' or 'sub-panel'.",[index,"options"],null,[],new api.ODId("opendiscord:dropdown-options"),null)) } }) diff --git a/src/data/framework/commandLoader.ts b/src/data/framework/commandLoader.ts index b598e79..818e004 100644 --- a/src/data/framework/commandLoader.ts +++ b/src/data/framework/commandLoader.ts @@ -1,19 +1,9 @@ -import {opendiscord, api, utilities} from "../../index" +import {opendiscord, api, utilities} from "../../index.js" import * as discord from "discord.js" const lang = opendiscord.languages -/** (CONTRIBUTOR GUIDE) HOW TO ADD NEW COMMANDS? - * - Register the command in loadAllSlashCommands() & loadAllTextCommands() in (./src/data/framework/commandLoader.ts) - * - Add autocomplete for the command in OD(Slash/Text)CommandManagerIds_Default in (./src/core/api/defaults/client.ts) - * - Add the command to the help menu in (./src/data/framework/helpMenuLoader.ts) - * - If required, new config variables should be added (incl. logs, dm-logs & permissions). - * - Update the Open Ticket Documentation. - * - If the command contains complex logic or can be executed from a button/dropdown, it should be placed inside an `ODAction`. - * - Check all files, test the bot carefully & try a lot of different scenario's with different settings. - */ - -export const loadAllSlashCommands = async () => { +export async function loadAllSlashCommands(){ const commands = opendiscord.client.slashCommands const generalConfig = opendiscord.configs.get("opendiscord:general") if (!generalConfig) return @@ -24,8 +14,8 @@ export const loadAllSlashCommands = async () => { if (!generalConfig.data.slashCommands) return const allowedCommands: string[] = [] - for (const key in generalConfig.data.system.permissions){ - if (generalConfig.data.system.permissions[key] != "none") allowedCommands.push(key) + for (const key in generalConfig.data.permissions){ + if (generalConfig.data.permissions[key] != "none") allowedCommands.push(key) } //HELP @@ -68,7 +58,21 @@ export const loadAllSlashCommands = async () => { description:lang.getTranslation("commands.ticket"), contexts:[discord.InteractionContextType.Guild], integrationTypes:[discord.ApplicationIntegrationType.GuildInstall], - options:[ + options:(generalConfig.data.ticketSystem.enableCreateTicketForOtherUser) ? [ + { + name:"id", + description:lang.getTranslation("commands.ticketId"), + type:acot.String, + required:true, + autocomplete:true + }, + { + name:"user", + description:lang.getTranslation("commands.ticketOtherUser"), + type:acot.User, + required:false, + } + ] : [ { name:"id", description:lang.getTranslation("commands.ticketId"), @@ -97,13 +101,13 @@ export const loadAllSlashCommands = async () => { })) //DELETE - if (allowedCommands.includes("delete") && generalConfig.data.system.enableDeleteWithoutTranscript) commands.add(new api.ODSlashCommand("opendiscord:delete",{ + if (allowedCommands.includes("delete")) commands.add(new api.ODSlashCommand("opendiscord:delete",{ type:act.ChatInput, name:"delete", description:lang.getTranslation("commands.delete"), contexts:[discord.InteractionContextType.Guild], integrationTypes:[discord.ApplicationIntegrationType.GuildInstall], - options:[ + options:(generalConfig.data.ticketSystem.enableDeleteWithoutTranscript) ? [ { name:"reason", description:lang.getTranslation("commands.reason"), @@ -116,15 +120,7 @@ export const loadAllSlashCommands = async () => { type:acot.Boolean, required:false } - ] - })) - else if (allowedCommands.includes("delete")) commands.add(new api.ODSlashCommand("opendiscord:delete",{ - type:act.ChatInput, - name:"delete", - description:lang.getTranslation("commands.delete"), - contexts:[discord.InteractionContextType.Guild], - integrationTypes:[discord.ApplicationIntegrationType.GuildInstall], - options:[ + ] : [ { name:"reason", description:lang.getTranslation("commands.reason"), @@ -640,9 +636,26 @@ export const loadAllSlashCommands = async () => { } ] })) + + //TRANSCRIPTS + if (allowedCommands.includes("transcripts")) commands.add(new api.ODSlashCommand("opendiscord:transcripts",{ + type:act.ChatInput, + name:"transcripts", + description:lang.getTranslation("commands.transcripts"), + contexts:[discord.InteractionContextType.Guild], + integrationTypes:[discord.ApplicationIntegrationType.GuildInstall], + options:[ + { + name:"user", + description:lang.getTranslation("commands.transcriptsUser"), + type:acot.User, + required:true + } + ] + })) } -export const loadAllTextCommands = async () => { +export async function loadAllTextCommands(){ const commands = opendiscord.client.textCommands const generalConfig = opendiscord.configs.get("opendiscord:general") if (!generalConfig) return @@ -664,8 +677,8 @@ export const loadAllTextCommands = async () => { }) const allowedCommands: string[] = [] - for (const key in generalConfig.data.system.permissions){ - if (generalConfig.data.system.permissions[key] != "none") allowedCommands.push(key) + for (const key in generalConfig.data.permissions){ + if (generalConfig.data.permissions[key] != "none") allowedCommands.push(key) } //HELP @@ -1214,9 +1227,25 @@ export const loadAllTextCommands = async () => { } ] })) + + //TRANSCRIPTS + if (allowedCommands.includes("transcripts")) commands.add(new api.ODTextCommand("opendiscord:transcripts",{ + name:"transcripts", + prefix, + dmPermission:false, + guildPermission:true, + allowBots:false, + options:[ + { + name:"user", + type:"user", + required:true + } + ] + })) } -export const loadAllContextMenus = async () => { +export async function loadAllContextMenus(){ const menus = opendiscord.client.contextMenus const generalConfig = opendiscord.configs.get("opendiscord:general") if (!generalConfig) return diff --git a/src/data/framework/configLoader.ts b/src/data/framework/configLoader.ts index 98945d0..19e3fe5 100644 --- a/src/data/framework/configLoader.ts +++ b/src/data/framework/configLoader.ts @@ -1,145 +1,67 @@ -import {opendiscord, api, utilities} from "../../index" +import {opendiscord, api, utilities} from "../../index.js" import * as fjs from "formatted-json-stringify" -/** (CONTRIBUTOR GUIDE) HOW TO ADD NEW CONFIG VARIABLES? - * - Make the change to the config file in (./config/) and be aware of the following things: - * - The variable has a clear name and its function is obvious. - * - The variable is in the correct position/category of the config. - * - The variable contains a default placeholder to suggest the contents. - * - If there's a (./devconfig/), also modify this file. - * - Register the config in loadAllConfigs() in (./src/data/framework/configLoader.ts) - * - The variable should be added to the "formatters" in the correct position. - * - Add autocomplete for the variable in ODJsonConfig_Default... in (./src/core/api/defaults/config.ts) - * - Add the variable to the config checker in (./src/data/framework/checkerLoader.ts) - * - Make sure the variable is compatible with the Interactive Setup CLI. - * - The variable should be added by the migration manager (./src/core/startup/migration.ts) when missing. - * - Update the Open Ticket Documentation. - * - * IF VARIABLE IS FROM questions.json, options.json OR panels.json: - * - Check (./src/data/openticket/...) for loading/unloading of data. - * - Check (./src/actions/createTicket.ts) and related files. - * - Check (./src/builders), (./src/actions), (./src/data) & (./src/commands) in general in the areas that were changed. - */ - -export const loadAllConfigs = async () => { +export async function loadAllConfigs(){ const devconfigFlag = opendiscord.flags.get("opendiscord:dev-config") const isDevconfig = devconfigFlag ? devconfigFlag.value : false - opendiscord.configs.add(new api.ODJsonConfig("opendiscord:general","general.json",(isDevconfig) ? "./devconfig/" : "./config/",defaultGeneralFormatter)) - opendiscord.configs.add(new api.ODJsonConfig("opendiscord:questions","questions.json",(isDevconfig) ? "./devconfig/" : "./config/",defaultQuestionsFormatter)) - opendiscord.configs.add(new api.ODJsonConfig("opendiscord:options","options.json",(isDevconfig) ? "./devconfig/" : "./config/",defaultOptionsFormatter)) - opendiscord.configs.add(new api.ODJsonConfig("opendiscord:panels","panels.json",(isDevconfig) ? "./devconfig/" : "./config/",defaultPanelsFormatter)) - opendiscord.configs.add(new api.ODJsonConfig("opendiscord:transcripts","transcripts.json",(isDevconfig) ? "./devconfig/" : "./config/",defaultTranscriptsFormatter)) + opendiscord.configs.add(new api.ODJsonCommentsConfig("opendiscord:general","general.jsonc",(isDevconfig) ? "./devconfig/" : "./config/",defaultGeneralFormatter)) + opendiscord.configs.add(new api.ODJsonCommentsConfig("opendiscord:questions","questions.jsonc",(isDevconfig) ? "./devconfig/" : "./config/",defaultQuestionsFormatter)) + opendiscord.configs.add(new api.ODJsonCommentsConfig("opendiscord:options","options.jsonc",(isDevconfig) ? "./devconfig/" : "./config/",defaultOptionsFormatter)) + opendiscord.configs.add(new api.ODJsonCommentsConfig("opendiscord:panels","panels.jsonc",(isDevconfig) ? "./devconfig/" : "./config/",defaultPanelsFormatter)) + opendiscord.configs.add(new api.ODJsonCommentsConfig("opendiscord:transcripts","transcripts.jsonc",(isDevconfig) ? "./devconfig/" : "./config/",defaultTranscriptsFormatter)) } //FORMATTERS -export const defaultGeneralFormatter = new fjs.ObjectFormatter(null,true,[ - new fjs.ObjectFormatter("_INFO",true,[ - new fjs.PropertyFormatter("support"), - new fjs.PropertyFormatter("discord"), - new fjs.PropertyFormatter("version"), - ]), +export const defaultGeneralFormatter = new fjs.TopLevelCommentFormatter(new fjs.MultiCommentFormatter([ + "Hi there! Thank you for installing Open Ticket.", + "----------------------------------------------", + "If you need any assistance with configuring the bot,", + "feel free to use the documentation or join our Discord server:", + "https://otdocs.dj-dj.be", + "https://discord.dj-dj.be", + "----------------------------------------------", + "SETUP:", + "1. Install the required dependencies using the command: \"npm install\"", + "2. Configure the bot in one of the following ways:", + " a. (easy) Using the Quick Setup CLI Tool", + " b. (difficult) Using the JSON files in `./config/`", + "", + "Start the Quick Setup CLI Tool using the command: \"npm run setup\"", + "After configuration, start the bot using the command: \"npm start\"", + "", + "Good luck! DJj123dj & contributors", + ].join("\n")),new fjs.ObjectFormatter(null,true,[ + new fjs.PropertyFormatter("_CONFIG_VERSION"), new fjs.TextFormatter(""), + new fjs.MultiCommentFormatter("Load the bot token from .env or the \"token\" field below. Leave \"token\" empty if using \"tokenFromENV\"."), new fjs.PropertyFormatter("token"), new fjs.PropertyFormatter("tokenFromENV"), new fjs.TextFormatter(""), - new fjs.PropertyFormatter("mainColor"), - new fjs.PropertyFormatter("language"), - new fjs.PropertyFormatter("prefix"), + new fjs.PropertyFormatter("mainColor",new fjs.SingleCommentFormatter("Hex color used in most embeds")), + new fjs.PropertyFormatter("language",new fjs.SingleCommentFormatter("Visit README.md for list")), + new fjs.PropertyFormatter("prefix",new fjs.SingleCommentFormatter("Prefix used in text commands")), new fjs.PropertyFormatter("serverId"), - new fjs.ArrayFormatter("globalAdmins",false,new fjs.PropertyFormatter(null)), + new fjs.ArrayFormatter("globalAdmins",false,new fjs.PropertyFormatter(null),undefined,undefined,new fjs.SingleCommentFormatter("Have access to all commands")), new fjs.TextFormatter(""), + new fjs.MultiCommentFormatter("Enable/disable text or slash commands."), new fjs.PropertyFormatter("slashCommands"), new fjs.PropertyFormatter("textCommands"), new fjs.TextFormatter(""), + new fjs.MultiCommentFormatter("Configure the status of the bot."), new fjs.ObjectFormatter("status",true,[ new fjs.PropertyFormatter("enabled"), - new fjs.PropertyFormatter("type"), - new fjs.PropertyFormatter("mode"), + new fjs.PropertyFormatter("type",new fjs.SingleCommentFormatter("Choices: listening, watching, playing, custom")), + new fjs.PropertyFormatter("mode",new fjs.SingleCommentFormatter("Choices: online, invisible, idle, dnd")), new fjs.PropertyFormatter("text"), - new fjs.PropertyFormatter("state"), + new fjs.PropertyFormatter("state",new fjs.SingleCommentFormatter("Additional text (Leave empty to disable)")), ]), new fjs.TextFormatter(""), - new fjs.ObjectFormatter("system",true,[ - new fjs.PropertyFormatter("preferSlashOverText"), - new fjs.PropertyFormatter("sendErrorOnUnknownCommand"), - new fjs.PropertyFormatter("questionFieldsInCodeBlock"), - new fjs.PropertyFormatter("displayFieldsWithQuestions"), - new fjs.PropertyFormatter("showGlobalAdminsInPanelRoles"), - new fjs.PropertyFormatter("disableVerifyBars"), - new fjs.PropertyFormatter("useRedErrorEmbeds"), - new fjs.PropertyFormatter("alwaysShowReason"), - new fjs.PropertyFormatter("emojiStyle"), - new fjs.PropertyFormatter("pinEmoji"), - new fjs.TextFormatter(""), - new fjs.PropertyFormatter("replyOnTicketCreation"), - new fjs.PropertyFormatter("replyOnReactionRole"), - new fjs.PropertyFormatter("askPriorityOnTicketCreation"), - new fjs.PropertyFormatter("removeParticipantsOnClose"), - new fjs.PropertyFormatter("disableAutocloseAfterReopen"), - new fjs.PropertyFormatter("autodeleteRequiresClosedTicket"), - new fjs.PropertyFormatter("adminOnlyDeleteWithoutTranscript"), - new fjs.PropertyFormatter("allowCloseBeforeMessage"), - new fjs.PropertyFormatter("allowCloseBeforeAdminMessage"), - new fjs.PropertyFormatter("useTranslatedConfigChecker"), - new fjs.PropertyFormatter("pinFirstTicketMessage"), - new fjs.TextFormatter(""), - new fjs.PropertyFormatter("enableTicketClaimButtons"), - new fjs.PropertyFormatter("enableTicketCloseButtons"), - new fjs.PropertyFormatter("enableTicketPinButtons"), - new fjs.PropertyFormatter("enableTicketDeleteButtons"), - new fjs.PropertyFormatter("enableTicketActionWithReason"), - new fjs.PropertyFormatter("enableDeleteWithoutTranscript"), - new fjs.TextFormatter(""), - new fjs.ObjectFormatter("logs",true,[ - new fjs.PropertyFormatter("enabled"), - new fjs.PropertyFormatter("channel"), - ]), - new fjs.TextFormatter(""), - new fjs.ObjectFormatter("limits",true,[ - new fjs.PropertyFormatter("enabled"), - new fjs.PropertyFormatter("globalMaximum"), - new fjs.PropertyFormatter("userMaximum"), - ]), - new fjs.TextFormatter(""), - new fjs.ObjectFormatter("channelTopic",true,[ - new fjs.PropertyFormatter("showOptionName"), - new fjs.PropertyFormatter("showOptionDescription"), - new fjs.PropertyFormatter("showOptionTopic"), - new fjs.PropertyFormatter("showPriority"), - new fjs.PropertyFormatter("showClosed"), - new fjs.PropertyFormatter("showClaimed"), - new fjs.PropertyFormatter("showPinned"), - new fjs.PropertyFormatter("showCreator"), - new fjs.PropertyFormatter("showParticipants"), - ]), - new fjs.TextFormatter(""), - new fjs.ObjectFormatter("permissions",true,[ - new fjs.PropertyFormatter("help"), - new fjs.PropertyFormatter("panel"), - new fjs.PropertyFormatter("ticket"), - new fjs.PropertyFormatter("close"), - new fjs.PropertyFormatter("delete"), - new fjs.PropertyFormatter("reopen"), - new fjs.PropertyFormatter("claim"), - new fjs.PropertyFormatter("unclaim"), - new fjs.PropertyFormatter("pin"), - new fjs.PropertyFormatter("unpin"), - new fjs.PropertyFormatter("move"), - new fjs.PropertyFormatter("rename"), - new fjs.PropertyFormatter("add"), - new fjs.PropertyFormatter("remove"), - new fjs.PropertyFormatter("blacklist"), - new fjs.PropertyFormatter("stats"), - new fjs.PropertyFormatter("clear"), - new fjs.PropertyFormatter("autoclose"), - new fjs.PropertyFormatter("autodelete"), - new fjs.PropertyFormatter("transfer"), - new fjs.PropertyFormatter("topic"), - new fjs.PropertyFormatter("priority"), - ]), - new fjs.TextFormatter(""), - new fjs.ObjectFormatter("messages",true,[ + new fjs.MultiCommentFormatter("Send ticket logs to a channel or in DM of the ticket creator."), + new fjs.ObjectFormatter("logs",true,[ + new fjs.PropertyFormatter("enabled"), + new fjs.PropertyFormatter("channel"), + new fjs.ObjectFormatter("logMessages",true,[ new fjs.DefaultFormatter("creation",false), new fjs.DefaultFormatter("closing",false), new fjs.DefaultFormatter("deleting",false), @@ -157,80 +79,282 @@ export const defaultGeneralFormatter = new fjs.ObjectFormatter(null,true,[ new fjs.DefaultFormatter("reactionRole",false) ]), ]), -]) + new fjs.TextFormatter(""), + new fjs.MultiCommentFormatter("A large collection of settings for the ticket system."), + new fjs.ObjectFormatter("ticketSystem",true,[ + new fjs.PropertyFormatter("preferSlashOverText",new fjs.SingleCommentFormatter("Show slashcmds in help menu's")), + new fjs.PropertyFormatter("sendErrorOnUnknownCommand",new fjs.SingleCommentFormatter("Send error when command not found")), + new fjs.PropertyFormatter("questionFieldsInCodeBlock",new fjs.SingleCommentFormatter("Put question answers in code blocks")), + new fjs.PropertyFormatter("displayFieldsWithQuestions",new fjs.SingleCommentFormatter("Display embed fields together with question answers")), + new fjs.PropertyFormatter("showGlobalAdminsInPanelRoles",new fjs.SingleCommentFormatter("Include \"globalAdmins\" in panel admin lists")), + new fjs.PropertyFormatter("disableVerifyBars",new fjs.SingleCommentFormatter("Disable the (❌/✅) buttons")), + new fjs.PropertyFormatter("useRedErrorEmbeds",new fjs.SingleCommentFormatter("Make errors embeds always red")), + new fjs.PropertyFormatter("alwaysShowReason",new fjs.SingleCommentFormatter("Show reason even if none is provided")), + new fjs.PropertyFormatter("emojiStyle",new fjs.SingleCommentFormatter("The style of emoji's in embeds. Choices: before, after, double, disabled")), + new fjs.PropertyFormatter("pinEmoji",new fjs.SingleCommentFormatter("Channel emoji of pinned tickets (Leave empty to disable)")), + new fjs.PropertyFormatter("closeEmoji",new fjs.SingleCommentFormatter("Channel emoji of closed tickets (Leave empty to disable)")), + new fjs.TextFormatter(""), + new fjs.PropertyFormatter("replyOnTicketCreation",new fjs.SingleCommentFormatter("Reply with a msg when a ticket is created")), + new fjs.PropertyFormatter("replyOnReactionRole",new fjs.SingleCommentFormatter("Reply with a msg when a reaction role is used")), + new fjs.PropertyFormatter("askPriorityOnTicketCreation",new fjs.SingleCommentFormatter("Show a dropdown to select priority")), + new fjs.PropertyFormatter("removeParticipantsOnClose",new fjs.SingleCommentFormatter("Remove non-admins when ticket is closed")), + new fjs.PropertyFormatter("disableAutocloseAfterReopen",new fjs.SingleCommentFormatter("Disable autoclose after ticket got reopened")), + new fjs.PropertyFormatter("autodeleteRequiresClosedTicket",new fjs.SingleCommentFormatter("A ticket must be closed before autodelete works")), + new fjs.PropertyFormatter("adminOnlyDeleteWithoutTranscript",new fjs.SingleCommentFormatter("Only allow \"globalAdmins\" to delete a ticket without transcript")), + new fjs.PropertyFormatter("allowCloseBeforeMessage",new fjs.SingleCommentFormatter("Allow closing before a message is sent")), + new fjs.PropertyFormatter("allowCloseBeforeAdminMessage",new fjs.SingleCommentFormatter("Allow closing before an admin has sent a message")), + new fjs.PropertyFormatter("useTranslatedConfigChecker",new fjs.SingleCommentFormatter("Translate config errors in the console")), + new fjs.PropertyFormatter("pinFirstTicketMessage",new fjs.SingleCommentFormatter("Pin the ticket message to the channel")), + new fjs.TextFormatter(""), + new fjs.MultiCommentFormatter("Enable/disable certain buttons & features of the bot."), + new fjs.PropertyFormatter("enableTicketClaimButtons"), + new fjs.PropertyFormatter("enableTicketCloseButtons"), + new fjs.PropertyFormatter("enableTicketPinButtons"), + new fjs.PropertyFormatter("enableTicketDeleteButtons"), + new fjs.PropertyFormatter("enableTicketActionWithReason"), + new fjs.PropertyFormatter("enableDeleteWithoutTranscript",new fjs.SingleCommentFormatter("Allow deleting tickets without transcript")), + new fjs.PropertyFormatter("enableCreateTicketForOtherUser",new fjs.SingleCommentFormatter("Allow creating tickets for other users")), + new fjs.TextFormatter(""), + new fjs.MultiCommentFormatter("Set the maximum amount of simultaneous tickets."), + new fjs.ObjectFormatter("limits",true,[ + new fjs.PropertyFormatter("enabled"), + new fjs.PropertyFormatter("globalMaximum"), + new fjs.PropertyFormatter("userMaximum"), + ]), + new fjs.TextFormatter(""), + new fjs.MultiCommentFormatter("Choose which data is shown in the channel topic."), + new fjs.ObjectFormatter("channelTopic",true,[ + new fjs.PropertyFormatter("showOptionName"), + new fjs.PropertyFormatter("showOptionDescription"), + new fjs.PropertyFormatter("showOptionTopic"), + new fjs.PropertyFormatter("showPriority"), + new fjs.PropertyFormatter("showClosed"), + new fjs.PropertyFormatter("showClaimed"), + new fjs.PropertyFormatter("showPinned"), + new fjs.PropertyFormatter("showCreator"), + new fjs.PropertyFormatter("showParticipants"), + ]), + new fjs.TextFormatter(""), + new fjs.MultiCommentFormatter("Move closed tickets to a separate category."), + new fjs.ObjectFormatter("closedCategory",true,[ + new fjs.PropertyFormatter("enabled"), + new fjs.PropertyFormatter("categoryId") + ]), + new fjs.TextFormatter(""), + new fjs.MultiCommentFormatter("Create tickets in a backup category when the original category exceeds 50 channels."), + new fjs.ObjectFormatter("backupCategory",true,[ + new fjs.PropertyFormatter("enabled"), + new fjs.PropertyFormatter("categoryId") + ]), + new fjs.TextFormatter(""), + new fjs.MultiCommentFormatter("Move claimed tickets to a matching category of the user that claimed the ticket. Set to empty list [] to disable."), + new fjs.ArrayFormatter("claimedCategories",true,new fjs.ObjectFormatter(null,false,[ + new fjs.PropertyFormatter("user"), + new fjs.PropertyFormatter("category"), + ])), + ]), + new fjs.TextFormatter(""), + new fjs.MultiCommentFormatter([ + "Set permissions for each individual command, button or action.", + "CHOICES:", + ">> \"none\" -> Command disabled", + ">> \"everyone\" -> Allowed for everyone", + ">> \"admin\" -> Global & ticket admins only", + ">> \"DISCORD_ROLE_ID\" -> Custom role only", + ].join("\n")), + new fjs.ObjectFormatter("permissions",true,[ + new fjs.PropertyFormatter("help"), + new fjs.PropertyFormatter("panel"), + new fjs.PropertyFormatter("ticket"), + new fjs.PropertyFormatter("close"), + new fjs.PropertyFormatter("delete"), + new fjs.PropertyFormatter("reopen"), + new fjs.PropertyFormatter("claim"), + new fjs.PropertyFormatter("unclaim"), + new fjs.PropertyFormatter("pin"), + new fjs.PropertyFormatter("unpin"), + new fjs.PropertyFormatter("move"), + new fjs.PropertyFormatter("rename"), + new fjs.PropertyFormatter("add"), + new fjs.PropertyFormatter("remove"), + new fjs.PropertyFormatter("blacklist"), + new fjs.PropertyFormatter("stats"), + new fjs.PropertyFormatter("clear"), + new fjs.PropertyFormatter("autoclose"), + new fjs.PropertyFormatter("autodelete"), + new fjs.PropertyFormatter("transfer"), + new fjs.PropertyFormatter("topic"), + new fjs.PropertyFormatter("priority"), + new fjs.PropertyFormatter("transcripts"), + ]), +])) -export const defaultQuestionsFormatter = new fjs.ArrayFormatter(null,true,new fjs.ObjectSwitchFormatter(null,[ +export const defaultQuestionsFormatter = new fjs.TopLevelCommentFormatter(new fjs.MultiCommentFormatter([ + "OPEN TICKET MODAL QUESTIONS", + "----------------------------------------------", + "Create customizable modal questions that will be shown before creating a ticket.", + "Each ticket option (config/options.jsonc) can contain a maximum of 5 questions.", + "There are 6 types of questions available: short, paragraph, dropdown, radio-select, checkbox-select, text-display", + "", + "TIP: Create new questions by copying everything between and including the {...} brackets of a question. Paste it after the last question and make sure that they are seperated by a comma." + ].join("\n")),new fjs.ArrayFormatter(null,true,new fjs.ObjectSwitchFormatter(null,[ {key:"type",value:"short",formatter:new fjs.ObjectFormatter(null,true,[ + new fjs.MultiCommentFormatter("A short text input modal question."), new fjs.PropertyFormatter("id"), new fjs.PropertyFormatter("name"), + new fjs.PropertyFormatter("description",new fjs.SingleCommentFormatter("Leave empty to disable")), new fjs.PropertyFormatter("type"), - new fjs.TextFormatter(""), new fjs.PropertyFormatter("required"), + new fjs.TextFormatter(""), new fjs.PropertyFormatter("placeholder"), new fjs.ObjectFormatter("length",true,[ + new fjs.MultiCommentFormatter("Configure length limits for the answer."), new fjs.PropertyFormatter("enabled"), new fjs.PropertyFormatter("min"), new fjs.PropertyFormatter("max"), ]), ])}, {key:"type",value:"paragraph",formatter:new fjs.ObjectFormatter(null,true,[ + new fjs.MultiCommentFormatter("A paragraph text input modal question."), new fjs.PropertyFormatter("id"), new fjs.PropertyFormatter("name"), + new fjs.PropertyFormatter("description",new fjs.SingleCommentFormatter("Leave empty to disable")), new fjs.PropertyFormatter("type"), - new fjs.TextFormatter(""), new fjs.PropertyFormatter("required"), + new fjs.TextFormatter(""), new fjs.PropertyFormatter("placeholder"), new fjs.ObjectFormatter("length",true,[ + new fjs.MultiCommentFormatter("Configure length limits for the answer."), new fjs.PropertyFormatter("enabled"), new fjs.PropertyFormatter("min"), new fjs.PropertyFormatter("max"), ]), - ])} -])) - - -export const defaultOptionsFormatter = new fjs.ArrayFormatter(null,true,new fjs.ObjectSwitchFormatter(null,[ - {key:"type",value:"ticket",formatter:new fjs.ObjectFormatter(null,true,[ + ])}, + {key:"type",value:"text-display",formatter:new fjs.ObjectFormatter(null,true,[ + new fjs.MultiCommentFormatter("Show text in a modal to provide extra details or explain questions."), + new fjs.PropertyFormatter("id"), + new fjs.PropertyFormatter("type"), + new fjs.TextFormatter(""), + new fjs.PropertyFormatter("textContents"), + ])}, + {key:"type",value:"dropdown",formatter:new fjs.ObjectFormatter(null,true,[ + new fjs.MultiCommentFormatter("A dropdown menu input modal question with up to 25 choices. \"emoji\" & \"description\" fields are optional."), new fjs.PropertyFormatter("id"), new fjs.PropertyFormatter("name"), - new fjs.PropertyFormatter("description"), + new fjs.PropertyFormatter("description",new fjs.SingleCommentFormatter("Leave empty to disable")), + new fjs.PropertyFormatter("type"), + new fjs.PropertyFormatter("required"), + new fjs.TextFormatter(""), + new fjs.PropertyFormatter("placeholder"), + new fjs.ArrayFormatter("choices",true,new fjs.ObjectFormatter(null,false,[ + new fjs.PropertyFormatter("title"), + new fjs.PropertyFormatter("description"), + new fjs.PropertyFormatter("emoji"), + ])), + ])}, + {key:"type",value:"radio-select",formatter:new fjs.ObjectFormatter(null,true,[ + new fjs.MultiCommentFormatter("A radio select input modal question with up to 10 choices."), + new fjs.PropertyFormatter("id"), + new fjs.PropertyFormatter("name"), + new fjs.PropertyFormatter("description",new fjs.SingleCommentFormatter("Leave empty to disable")), + new fjs.PropertyFormatter("type"), + new fjs.PropertyFormatter("required"), + new fjs.TextFormatter(""), + new fjs.ArrayFormatter("choices",true,new fjs.ObjectFormatter(null,false,[ + new fjs.PropertyFormatter("title"), + new fjs.PropertyFormatter("description"), + new fjs.PropertyFormatter("selectedByDefault"), + ])), + ])}, + {key:"type",value:"checkbox-select",formatter:new fjs.ObjectFormatter(null,true,[ + new fjs.MultiCommentFormatter("A checkbox select input modal question with up to 10 choices."), + new fjs.PropertyFormatter("id"), + new fjs.PropertyFormatter("name"), + new fjs.PropertyFormatter("description",new fjs.SingleCommentFormatter("Leave empty to disable")), + new fjs.PropertyFormatter("type"), + new fjs.PropertyFormatter("required"), + new fjs.TextFormatter(""), + new fjs.ObjectFormatter("limits",true,[ + new fjs.MultiCommentFormatter("Configure checkbox amount limits for the answer."), + new fjs.PropertyFormatter("enabled"), + new fjs.PropertyFormatter("min"), + new fjs.PropertyFormatter("max"), + ]), + new fjs.ArrayFormatter("choices",true,new fjs.ObjectFormatter(null,false,[ + new fjs.PropertyFormatter("title"), + new fjs.PropertyFormatter("description"), + new fjs.PropertyFormatter("selectedByDefault"), + ])), + ])}, + {key:"type",value:"file-upload",formatter:new fjs.ObjectFormatter(null,true,[ + new fjs.MultiCommentFormatter("A file upload modal question where users can upload one or more files."), + new fjs.PropertyFormatter("id"), + new fjs.PropertyFormatter("name"), + new fjs.PropertyFormatter("description",new fjs.SingleCommentFormatter("Leave empty to disable")), + new fjs.PropertyFormatter("type"), + new fjs.PropertyFormatter("required"), + new fjs.TextFormatter(""), + new fjs.ObjectFormatter("limits",true,[ + new fjs.MultiCommentFormatter("Configure minimum/maximum amount of files to upload."), + new fjs.PropertyFormatter("enabled"), + new fjs.PropertyFormatter("min"), + new fjs.PropertyFormatter("max"), + ]), + ])}, +]))) + + +export const defaultOptionsFormatter = new fjs.TopLevelCommentFormatter(new fjs.MultiCommentFormatter([ + "OPEN TICKET BUTTON OPTIONS", + "----------------------------------------------", + "Create customizable ticket, website, reaction-role or sub-panel button options.", + "Up to 25 options can be added to each panel in (config/panels.jsonc)", + "There are 4 types of options available: ticket, website, role, sub-panel", + "", + "TIP: Create new options by copying everything between and including the {...} brackets of an option. Paste it after the last option and make sure that they are seperated by a comma." + ].join("\n")),new fjs.ArrayFormatter(null,true,new fjs.ObjectSwitchFormatter(null,[ + {key:"type",value:"ticket",formatter:new fjs.ObjectFormatter(null,true,[ + new fjs.MultiCommentFormatter("A ticket option creates a button to open a ticket."), + new fjs.PropertyFormatter("id"), + new fjs.PropertyFormatter("name"), + new fjs.PropertyFormatter("description",new fjs.SingleCommentFormatter("Leave empty to disable")), new fjs.PropertyFormatter("type"), new fjs.TextFormatter(""), new fjs.ObjectFormatter("button",true,[ + new fjs.MultiCommentFormatter("Configure the button style of this option. At least one of \"emoji\" or \"label\" must be provided."), new fjs.PropertyFormatter("emoji"), new fjs.PropertyFormatter("label"), - new fjs.PropertyFormatter("color"), + new fjs.PropertyFormatter("color",new fjs.SingleCommentFormatter("Choices: gray, red, green, blue")), ]), new fjs.TextFormatter(""), + new fjs.MultiCommentFormatter("Add up to 5 modal questions IDs from (config/questions.jsonc)."), + new fjs.ArrayFormatter("questions",false,new fjs.PropertyFormatter(null)), + new fjs.TextFormatter(""), new fjs.ArrayFormatter("ticketAdmins",false,new fjs.PropertyFormatter(null)), new fjs.ArrayFormatter("readonlyAdmins",false,new fjs.PropertyFormatter(null)), new fjs.PropertyFormatter("allowCreationByBlacklistedUsers"), - new fjs.ArrayFormatter("questions",false,new fjs.PropertyFormatter(null)), new fjs.TextFormatter(""), new fjs.ObjectFormatter("channel",true,[ + new fjs.MultiCommentFormatter("Configure the name, topic and category of the ticket option."), new fjs.PropertyFormatter("prefix"), - new fjs.PropertyFormatter("suffix"), - new fjs.PropertyFormatter("category"), - new fjs.PropertyFormatter("backupCategory"), - new fjs.PropertyFormatter("closedCategory"), - new fjs.ArrayFormatter("claimedCategory",true,new fjs.ObjectFormatter(null,false,[ - new fjs.PropertyFormatter("user"), - new fjs.PropertyFormatter("category"), - ])), - new fjs.PropertyFormatter("topic"), + new fjs.PropertyFormatter("suffix",new fjs.SingleCommentFormatter("Choices: user-name, user-id, random-number, random-hex, counter-dynamic, counter-fixed")), + new fjs.PropertyFormatter("category",new fjs.SingleCommentFormatter("Leave empty to disable")), + new fjs.PropertyFormatter("topic",new fjs.SingleCommentFormatter("Leave empty to disable")), ]), new fjs.TextFormatter(""), new fjs.ObjectFormatter("dmMessage",true,[ + new fjs.MultiCommentFormatter("Send a customisable message in DM when creating a ticket."), new fjs.PropertyFormatter("enabled"), - new fjs.PropertyFormatter("text"), + new fjs.PropertyFormatter("text",new fjs.SingleCommentFormatter("Leave empty to disable")), new fjs.ObjectFormatter("embed",true,[ new fjs.PropertyFormatter("enabled"), - new fjs.PropertyFormatter("title"), - new fjs.PropertyFormatter("description"), - new fjs.PropertyFormatter("customColor"), + new fjs.PropertyFormatter("title",new fjs.SingleCommentFormatter("Leave empty to disable")), + new fjs.PropertyFormatter("description",new fjs.SingleCommentFormatter("Leave empty to disable")), + new fjs.PropertyFormatter("customColor",new fjs.SingleCommentFormatter("Leave empty to use default color")), new fjs.TextFormatter(""), - new fjs.PropertyFormatter("image"), - new fjs.PropertyFormatter("thumbnail"), + new fjs.PropertyFormatter("image",new fjs.SingleCommentFormatter("Image URL. Leave empty to disable")), + new fjs.PropertyFormatter("thumbnail",new fjs.SingleCommentFormatter("Image URL. Leave empty to disable")), + new fjs.MultiCommentFormatter("Embed fields. Set to empty list [] to disable."), new fjs.ArrayFormatter("fields",true,new fjs.ObjectFormatter(null,false,[ new fjs.PropertyFormatter("name"), new fjs.PropertyFormatter("value"), @@ -240,16 +364,18 @@ export const defaultOptionsFormatter = new fjs.ArrayFormatter(null,true,new fjs. ]), ]), new fjs.ObjectFormatter("ticketMessage",true,[ + new fjs.MultiCommentFormatter("Send a customisable message in the ticket with close, claim, delete, ... buttons."), new fjs.PropertyFormatter("enabled"), - new fjs.PropertyFormatter("text"), + new fjs.PropertyFormatter("text",new fjs.SingleCommentFormatter("Leave empty to disable")), new fjs.ObjectFormatter("embed",true,[ new fjs.PropertyFormatter("enabled"), - new fjs.PropertyFormatter("title"), - new fjs.PropertyFormatter("description"), - new fjs.PropertyFormatter("customColor"), + new fjs.PropertyFormatter("title",new fjs.SingleCommentFormatter("Leave empty to disable")), + new fjs.PropertyFormatter("description",new fjs.SingleCommentFormatter("Leave empty to disable")), + new fjs.PropertyFormatter("customColor",new fjs.SingleCommentFormatter("Leave empty to use default color")), new fjs.TextFormatter(""), - new fjs.PropertyFormatter("image"), - new fjs.PropertyFormatter("thumbnail"), + new fjs.PropertyFormatter("image",new fjs.SingleCommentFormatter("Image URL. Leave empty to disable")), + new fjs.PropertyFormatter("thumbnail",new fjs.SingleCommentFormatter("Image URL. Leave empty to disable")), + new fjs.MultiCommentFormatter("Embed fields. Set to empty list [] to disable."), new fjs.ArrayFormatter("fields",true,new fjs.ObjectFormatter(null,false,[ new fjs.PropertyFormatter("name"), new fjs.PropertyFormatter("value"), @@ -258,44 +384,52 @@ export const defaultOptionsFormatter = new fjs.ArrayFormatter(null,true,new fjs. new fjs.PropertyFormatter("timestamp"), ]), new fjs.ObjectFormatter("ping",true,[ + new fjs.MultiCommentFormatter("Customise the user & role mentions of this ticket message."), new fjs.PropertyFormatter("@here"), new fjs.PropertyFormatter("@everyone"), - new fjs.ArrayFormatter("custom",true,new fjs.PropertyFormatter(null)), + new fjs.ArrayFormatter("custom",false,new fjs.PropertyFormatter(null)), ]), ]), new fjs.ObjectFormatter("autoclose",true,[ + new fjs.MultiCommentFormatter("Autoclose this ticket after a period of inactivity or when the creator leaves the server."), new fjs.PropertyFormatter("enableInactiveHours"), new fjs.PropertyFormatter("inactiveHours"), new fjs.PropertyFormatter("enableUserLeave"), new fjs.PropertyFormatter("disableOnClaim"), ]), new fjs.ObjectFormatter("autodelete",true,[ + new fjs.MultiCommentFormatter("Autodelete this ticket after a period of inactivity or when the creator leaves the server."), new fjs.PropertyFormatter("enableInactiveDays"), new fjs.PropertyFormatter("inactiveDays"), new fjs.PropertyFormatter("enableUserLeave"), new fjs.PropertyFormatter("disableOnClaim"), ]), new fjs.ObjectFormatter("cooldown",true,[ + new fjs.MultiCommentFormatter("Users must wait a certain period before being able to create another ticket of this type."), new fjs.PropertyFormatter("enabled"), new fjs.PropertyFormatter("cooldownMinutes"), ]), new fjs.ObjectFormatter("limits",true,[ + new fjs.MultiCommentFormatter("Set the maximum amount of simultaneous tickets of this option."), new fjs.PropertyFormatter("enabled"), new fjs.PropertyFormatter("globalMaximum"), new fjs.PropertyFormatter("userMaximum"), ]), new fjs.ObjectFormatter("slowMode",true,[ + new fjs.MultiCommentFormatter("Enable slow-mode in the ticket channel."), new fjs.PropertyFormatter("enabled"), new fjs.PropertyFormatter("slowModeSeconds"), ]), ])}, {key:"type",value:"website",formatter:new fjs.ObjectFormatter(null,true,[ + new fjs.MultiCommentFormatter("A website option creates a button with a URL to an external website."), new fjs.PropertyFormatter("id"), new fjs.PropertyFormatter("name"), - new fjs.PropertyFormatter("description"), + new fjs.PropertyFormatter("description",new fjs.SingleCommentFormatter("Leave empty to disable")), new fjs.PropertyFormatter("type"), new fjs.TextFormatter(""), new fjs.ObjectFormatter("button",true,[ + new fjs.MultiCommentFormatter("Configure the button style of this option. At least one of \"emoji\" or \"label\" must be provided."), new fjs.PropertyFormatter("emoji"), new fjs.PropertyFormatter("label"), ]), @@ -303,43 +437,75 @@ export const defaultOptionsFormatter = new fjs.ArrayFormatter(null,true,new fjs. new fjs.PropertyFormatter("url"), ])}, {key:"type",value:"role",formatter:new fjs.ObjectFormatter(null,true,[ + new fjs.MultiCommentFormatter("A reaction-role option creates a button for members to choose roles."), new fjs.PropertyFormatter("id"), new fjs.PropertyFormatter("name"), - new fjs.PropertyFormatter("description"), + new fjs.PropertyFormatter("description",new fjs.SingleCommentFormatter("Leave empty to disable")), new fjs.PropertyFormatter("type"), new fjs.TextFormatter(""), new fjs.ObjectFormatter("button",true,[ + new fjs.MultiCommentFormatter("Configure the button style of this option. At least one of \"emoji\" or \"label\" must be provided."), new fjs.PropertyFormatter("emoji"), new fjs.PropertyFormatter("label"), - new fjs.PropertyFormatter("color"), + new fjs.PropertyFormatter("color",new fjs.SingleCommentFormatter("Choices: gray, red, green, blue")), ]), new fjs.TextFormatter(""), new fjs.ArrayFormatter("roles",false,new fjs.PropertyFormatter(null)), - new fjs.PropertyFormatter("mode"), - new fjs.ArrayFormatter("removeRolesOnAdd",false,new fjs.PropertyFormatter(null)), - new fjs.PropertyFormatter("addOnMemberJoin"), + new fjs.PropertyFormatter("mode",new fjs.SingleCommentFormatter("What to do with the roles. Choices: add&remove, add, remove")), + new fjs.ArrayFormatter("removeRolesOnAdd",false,new fjs.PropertyFormatter(null),undefined,undefined,new fjs.SingleCommentFormatter("Remove these old roles when new roles are added.")), + new fjs.PropertyFormatter("addOnMemberJoin",new fjs.SingleCommentFormatter("Add these roles automatically when joining the server.")), + ])}, + {key:"type",value:"sub-panel",formatter:new fjs.ObjectFormatter(null,true,[ + new fjs.MultiCommentFormatter("A sub-panel option creates a button which sends another panel for additional options."), + new fjs.PropertyFormatter("id"), + new fjs.PropertyFormatter("name"), + new fjs.PropertyFormatter("description",new fjs.SingleCommentFormatter("Leave empty to disable")), + new fjs.PropertyFormatter("type"), + new fjs.TextFormatter(""), + new fjs.ObjectFormatter("button",true,[ + new fjs.MultiCommentFormatter("Configure the button style of this option. At least one of \"emoji\" or \"label\" must be provided."), + new fjs.PropertyFormatter("emoji"), + new fjs.PropertyFormatter("label"), + new fjs.PropertyFormatter("color",new fjs.SingleCommentFormatter("Choices: gray, red, green, blue")), + ]), + new fjs.TextFormatter(""), + new fjs.PropertyFormatter("subPanelId",new fjs.SingleCommentFormatter("Choose a panel ID from (config/panels.jsonc)")), ])} -])) +]))) -export const defaultPanelsFormatter = new fjs.ArrayFormatter(null,true,new fjs.ObjectFormatter(null,true,[ +export const defaultPanelsFormatter = new fjs.TopLevelCommentFormatter(new fjs.MultiCommentFormatter([ + "OPEN TICKET PANELS", + "----------------------------------------------", + "Create customizable panel messages with buttons or a dropdown.", + "Add up to 25 options to this panel from (config/options.jsonc)", + "Panels can be customised with text, images, colors and more.", + "", + "Spawn the panel in Discord using: /panel ", + "", + "TIP: Create new panels by copying everything between and including the {...} brackets of an panel. Paste it after the last panel and make sure that they are seperated by a comma." + ].join("\n")),new fjs.ArrayFormatter(null,true,new fjs.ObjectFormatter(null,true,[ + new fjs.MultiCommentFormatter("A panel is creates a message with up to 25 options as buttons or dropdown."), new fjs.PropertyFormatter("id"), new fjs.PropertyFormatter("name"), new fjs.PropertyFormatter("dropdown"), + new fjs.TextFormatter(""), + new fjs.MultiCommentFormatter("Add up to 5 option IDs from (config/options.jsonc)."), new fjs.ArrayFormatter("options",false,new fjs.PropertyFormatter(null)), new fjs.TextFormatter(""), - new fjs.PropertyFormatter("text"), + new fjs.PropertyFormatter("text",new fjs.SingleCommentFormatter("Leave empty to disable")), new fjs.ObjectFormatter("embed",true,[ new fjs.PropertyFormatter("enabled"), - new fjs.PropertyFormatter("title"), - new fjs.PropertyFormatter("description"), + new fjs.PropertyFormatter("title",new fjs.SingleCommentFormatter("Leave empty to disable")), + new fjs.PropertyFormatter("description",new fjs.SingleCommentFormatter("Leave empty to disable")), new fjs.TextFormatter(""), - new fjs.PropertyFormatter("customColor"), - new fjs.PropertyFormatter("url"), + new fjs.PropertyFormatter("customColor",new fjs.SingleCommentFormatter("Leave empty to use default color")), + new fjs.PropertyFormatter("url",new fjs.SingleCommentFormatter("URL. Leave empty to disable")), new fjs.TextFormatter(""), - new fjs.PropertyFormatter("image"), - new fjs.PropertyFormatter("thumbnail"), + new fjs.PropertyFormatter("image",new fjs.SingleCommentFormatter("Image URL. Leave empty to disable")), + new fjs.PropertyFormatter("thumbnail",new fjs.SingleCommentFormatter("Image URL. Leave empty to disable")), new fjs.TextFormatter(""), - new fjs.PropertyFormatter("footer"), + new fjs.PropertyFormatter("footer",new fjs.SingleCommentFormatter("Leave empty to disable")), + new fjs.MultiCommentFormatter("Embed fields. Set to empty list [] to disable."), new fjs.ArrayFormatter("fields",true,new fjs.ObjectFormatter(null,false,[ new fjs.PropertyFormatter("name"), new fjs.PropertyFormatter("value"), @@ -348,53 +514,70 @@ export const defaultPanelsFormatter = new fjs.ArrayFormatter(null,true,new fjs.O new fjs.PropertyFormatter("timestamp"), ]), new fjs.ObjectFormatter("settings",true,[ - new fjs.PropertyFormatter("dropdownPlaceholder"), + new fjs.PropertyFormatter("dropdownPlaceholder",new fjs.SingleCommentFormatter("Leave empty to use default.")), + new fjs.PropertyFormatter("maximumButtonsPerRow"), new fjs.TextFormatter(""), + new fjs.MultiCommentFormatter("Display the maximum amount of tickets per user."), new fjs.PropertyFormatter("enableMaxTicketsWarningInText"), new fjs.PropertyFormatter("enableMaxTicketsWarningInEmbed"), new fjs.TextFormatter(""), - new fjs.PropertyFormatter("describeOptionsLayout"), + new fjs.MultiCommentFormatter("Automatically generate option descriptions from (config/options.jsonc)."), + new fjs.PropertyFormatter("describeOptionsLayout",new fjs.SingleCommentFormatter("Choices: simple, normal, detailed")), new fjs.PropertyFormatter("describeOptionsCustomTitle"), new fjs.PropertyFormatter("describeOptionsInText"), new fjs.PropertyFormatter("describeOptionsInEmbedFields"), new fjs.PropertyFormatter("describeOptionsInEmbedDescription"), ]), -])) +]))) -export const defaultTranscriptsFormatter = new fjs.ObjectFormatter(null,true,[ +export const defaultTranscriptsFormatter = new fjs.TopLevelCommentFormatter(new fjs.MultiCommentFormatter([ + "OPEN TICKET TRANSCRIPTS", + "----------------------------------------------", + "Enable transcript creation when tickets are deleted. There are 2 available transcript types: HTML & Text", + "", + "HTML Transcripts (recommended):", + "Generate transcripts as HTML files to view in the browser. No server or domain required. HTML Transcripts use an external service to process and host the transcripts.", + "", + "Text Transcripts:", + "Generate transcripts as simple .txt files with limited details. Processing happens fully local.", + ].join("\n")),new fjs.ObjectFormatter(null,true,[ new fjs.ObjectFormatter("general",true,[ new fjs.PropertyFormatter("enabled"), new fjs.TextFormatter(""), + new fjs.MultiCommentFormatter("Choose which users and channel get the generated transcript."), new fjs.PropertyFormatter("enableChannel"), new fjs.PropertyFormatter("enableCreatorDM"), new fjs.PropertyFormatter("enableParticipantDM"), new fjs.PropertyFormatter("enableActiveAdminDM"), new fjs.PropertyFormatter("enableEveryAdminDM"), new fjs.TextFormatter(""), - new fjs.PropertyFormatter("channel"), - new fjs.PropertyFormatter("mode"), + new fjs.PropertyFormatter("channel",new fjs.SingleCommentFormatter("Where to send transcripts. Leave empty when disabled.")), + new fjs.PropertyFormatter("mode",new fjs.SingleCommentFormatter("The type of transcript to use. Choices: html, text")), ]), new fjs.ObjectFormatter("embedSettings",true,[ - new fjs.PropertyFormatter("customColor"), + new fjs.MultiCommentFormatter("Customise the embed which contains the generated transcript file or URL."), + new fjs.PropertyFormatter("customColor",new fjs.SingleCommentFormatter("Leave empty to use default color")), new fjs.PropertyFormatter("listAllParticipants"), new fjs.PropertyFormatter("includeTicketStats"), ]), new fjs.ObjectFormatter("textTranscriptStyle",true,[ - new fjs.PropertyFormatter("layout"), + new fjs.MultiCommentFormatter("Customise layout of the text transcripts."), + new fjs.PropertyFormatter("layout",new fjs.SingleCommentFormatter("Choices: simple, normal, detailed")), new fjs.PropertyFormatter("includeStats"), new fjs.PropertyFormatter("includeIds"), new fjs.PropertyFormatter("includeEmbeds"), new fjs.PropertyFormatter("includeFiles"), new fjs.PropertyFormatter("includeBotMessages"), new fjs.TextFormatter(""), - new fjs.PropertyFormatter("fileMode"), - new fjs.PropertyFormatter("customFileName"), + new fjs.PropertyFormatter("fileMode",new fjs.SingleCommentFormatter("How to name the transcript file? Choices: custom, channel-name, channel-id, user-name, user-id")), + new fjs.PropertyFormatter("customFileName",new fjs.SingleCommentFormatter("Custom filename without extension")), ]), new fjs.ObjectFormatter("htmlTranscriptStyle",true,[ + new fjs.MultiCommentFormatter("Customise layout of the HTML transcripts."), new fjs.ObjectFormatter("background",true,[ new fjs.PropertyFormatter("enableCustomBackground"), - new fjs.PropertyFormatter("backgroundColor"), - new fjs.PropertyFormatter("backgroundImage"), + new fjs.PropertyFormatter("backgroundColor",new fjs.SingleCommentFormatter("Leave empty to use Open Ticket color (#f8ba00)")), + new fjs.PropertyFormatter("backgroundImage",new fjs.SingleCommentFormatter("Image URL to fill entire background. Leave empty to disable")), ]), new fjs.ObjectFormatter("header",true,[ new fjs.PropertyFormatter("enableCustomHeader"), @@ -415,4 +598,4 @@ export const defaultTranscriptsFormatter = new fjs.ObjectFormatter(null,true,[ new fjs.PropertyFormatter("imageUrl"), ]), ]), -]) \ No newline at end of file +])) \ No newline at end of file diff --git a/src/data/framework/cooldownLoader.ts b/src/data/framework/cooldownLoader.ts index 2a431ac..e043b9f 100644 --- a/src/data/framework/cooldownLoader.ts +++ b/src/data/framework/cooldownLoader.ts @@ -1,6 +1,6 @@ -import {opendiscord, api, utilities} from "../../index" +import {opendiscord, api, utilities} from "../../index.js" -export const loadAllCooldowns = async () => { +export async function loadAllCooldowns(){ await opendiscord.options.loopAll((option) => { if (!(option instanceof api.ODTicketOption)) return loadTicketOptionCooldown(option) diff --git a/src/data/framework/databaseLoader.ts b/src/data/framework/databaseLoader.ts index 65baeef..0f46a55 100644 --- a/src/data/framework/databaseLoader.ts +++ b/src/data/framework/databaseLoader.ts @@ -1,15 +1,17 @@ -import {opendiscord, api, utilities} from "../../index" +import {opendiscord, api, utilities} from "../../index.js" import * as fjs from "formatted-json-stringify" const devdatabaseFlag = opendiscord.flags.get("opendiscord:dev-database") const isDevdatabase = devdatabaseFlag ? devdatabaseFlag.value : false -export const loadAllDatabases = async () => { +export async function loadAllDatabases(){ opendiscord.databases.add(defaultGlobalDatabase) opendiscord.databases.add(defaultStatsDatabase) opendiscord.databases.add(defaultTicketsDatabase) opendiscord.databases.add(defaultUsersDatabase) opendiscord.databases.add(defaultOptionsDatabase) + opendiscord.databases.add(defaultTranscriptsDatabase) + opendiscord.databases.add(defaultMessageStatesDatabase) } const defaultInlineFormatter = new fjs.ArrayFormatter(null,true,new fjs.ObjectFormatter(null,false,[ @@ -46,8 +48,10 @@ const defaultOptionFormatter = new fjs.ArrayFormatter(null,true,new fjs.ObjectFo ]) ])) -export const defaultGlobalDatabase = new api.ODFormattedJsonDatabase("opendiscord:global","global.json",defaultInlineFormatter,(isDevdatabase) ? "./devdatabase/" : "./database/") -export const defaultStatsDatabase = new api.ODFormattedJsonDatabase("opendiscord:stats","stats.json",defaultInlineFormatter,(isDevdatabase) ? "./devdatabase/" : "./database/") -export const defaultTicketsDatabase = new api.ODFormattedJsonDatabase("opendiscord:tickets","tickets.json",defaultTicketFormatter,(isDevdatabase) ? "./devdatabase/" : "./database/") -export const defaultUsersDatabase = new api.ODFormattedJsonDatabase("opendiscord:users","users.json",defaultInlineFormatter,(isDevdatabase) ? "./devdatabase/" : "./database/") -export const defaultOptionsDatabase = new api.ODFormattedJsonDatabase("opendiscord:options","options.json",defaultOptionFormatter,(isDevdatabase) ? "./devdatabase/" : "./database/") \ No newline at end of file +export const defaultGlobalDatabase = new api.ODGlobalDatabase("opendiscord:global","global.json",defaultInlineFormatter,(isDevdatabase) ? "./devdatabase/" : "./database/") +export const defaultStatsDatabase = new api.ODStatsDatabase("opendiscord:stats","stats.json",defaultInlineFormatter,(isDevdatabase) ? "./devdatabase/" : "./database/") +export const defaultTicketsDatabase = new api.ODTicketsDatabase("opendiscord:tickets","tickets.json",defaultTicketFormatter,(isDevdatabase) ? "./devdatabase/" : "./database/") +export const defaultUsersDatabase = new api.ODUsersDatabase("opendiscord:users","users.json",defaultInlineFormatter,(isDevdatabase) ? "./devdatabase/" : "./database/") +export const defaultOptionsDatabase = new api.ODOptionsDatabase("opendiscord:options","options.json",defaultOptionFormatter,(isDevdatabase) ? "./devdatabase/" : "./database/") +export const defaultTranscriptsDatabase = new api.ODTranscriptsDatabase("opendiscord:transcripts","transcripts.json",defaultInlineFormatter,(isDevdatabase) ? "./devdatabase/" : "./database/") +export const defaultMessageStatesDatabase = new api.ODMessageStatesDatabase("opendiscord:message-states","states.json",defaultInlineFormatter,(isDevdatabase) ? "./devdatabase/" : "./database/") \ No newline at end of file diff --git a/src/data/framework/eventLoader.ts b/src/data/framework/eventLoader.ts index cd195c5..9c170a3 100644 --- a/src/data/framework/eventLoader.ts +++ b/src/data/framework/eventLoader.ts @@ -1,7 +1,7 @@ -import {opendiscord, api, utilities} from "../../index" +import {opendiscord, api, utilities} from "../../index.js" export const loadAllEvents = () => { - const eventList: (keyof api.ODEventIds_Default)[] = [ + const eventList: (keyof api.ODNoGeneric)[] = [ //error handling "onErrorHandling", "afterErrorHandling", @@ -96,6 +96,12 @@ export const loadAllEvents = () => { "onTextCommandLoad", "afterTextCommandsLoaded", + //states + "onStateLoad", + "afterStatesLoaded", + "onStateInit", + "afterStatesInitiated", + //plugin loading before managers "onPluginBeforeManagerLoad", "afterPluginBeforeManagerLoaded", @@ -206,6 +212,16 @@ export const loadAllEvents = () => { "onModalBuilderLoad", "afterModalBuildersLoaded", + //components + "onSharedComponentLoad", + "afterSharedComponentsLoaded", + "onMessageComponentLoad", + "afterMessageComponentsLoaded", + "onModalComponentLoad", + "afterModalComponentsLoaded", + "onComponentModifierLoad", + "afterComponentModifiersLoaded", + //plugin loading before responders "onPluginBeforeResponderLoad", "afterPluginBeforeResponderLoaded", @@ -258,23 +274,23 @@ export const loadAllEvents = () => { "onHelpMenuComponentLoad", "afterHelpMenuComponentsLoaded", - //stats - "onStatScopeLoad", - "afterStatScopesLoaded", - "onStatLoad", - "afterStatsLoaded", - "onStatInit", - "afterStatsInitiated", + //statistics + "onStatisticScopeLoad", + "afterStatisticScopesLoaded", + "onStatisticLoad", + "afterStatisticsLoaded", + "onStatisticInit", + "afterStatisticsInitiated", - //plugin loading before code - "onPluginBeforeCodeLoad", - "afterPluginBeforeCodeLoaded", + //plugin loading before tasks + "onPluginBeforeTaskLoad", + "afterPluginBeforeTaskLoaded", - //code - "onCodeLoad", - "afterCodeLoaded", - "onCodeExecute", - "afterCodeExecuted", + //background tasks + "onTaskLoad", + "afterTasksLoaded", + "onTaskExecute", + "afterTasksExecuted", //livestatus "onLiveStatusSourceLoad", diff --git a/src/data/framework/flagLoader.ts b/src/data/framework/flagLoader.ts index 9c80a99..31e7054 100644 --- a/src/data/framework/flagLoader.ts +++ b/src/data/framework/flagLoader.ts @@ -1,6 +1,6 @@ -import {opendiscord, api, utilities} from "../../index" +import {opendiscord, api, utilities} from "../../index.js" -export const loadAllFlags = async () => { +export async function loadAllFlags(){ opendiscord.flags.add(new api.ODFlag("opendiscord:no-migration","No Migration","Disable Open Ticket data migration on update!","--no-migration",["-nm"])) opendiscord.flags.add(new api.ODFlag("opendiscord:dev-config","Developer Config","Use the configs in /devconfig/ instead of /config/!","--dev-config",["-dc"])) opendiscord.flags.add(new api.ODFlag("opendiscord:dev-database","Developer Database","Use the databases in /devdatabase/ instead of /database/!","--dev-database",["-dd"])) diff --git a/src/data/framework/helpMenuLoader.ts b/src/data/framework/helpMenuLoader.ts index 92c96bb..ccae3a4 100644 --- a/src/data/framework/helpMenuLoader.ts +++ b/src/data/framework/helpMenuLoader.ts @@ -1,18 +1,8 @@ -import {opendiscord, api, utilities} from "../../index" +import {opendiscord, api, utilities} from "../../index.js" const lang = opendiscord.languages -/** (CONTRIBUTOR GUIDE) HOW TO ADD NEW COMMANDS? - * - Register the command in loadAllSlashCommands() & loadAllTextCommands() in (./src/data/framework/commandLoader.ts) - * - Add autocomplete for the command in OD(Slash/Text)CommandManagerIds_Default in (./src/core/api/defaults/client.ts) - * - Add the command to the help menu in (./src/data/framework/helpMenuLoader.ts) - * - If required, new config variables should be added (incl. logs, dm-logs & permissions). - * - Update the Open Ticket Documentation. - * - If the command contains complex logic or can be executed from a button/dropdown, it should be placed inside an `ODAction`. - * - Check all files, test the bot carefully & try a lot of different scenario's with different settings. - */ - -export const loadAllHelpMenuCategories = async () => { +export async function loadAllHelpMenuCategories(){ const helpmenu = opendiscord.helpmenu helpmenu.add(new api.ODHelpMenuCategory("opendiscord:general",5,utilities.emojiTitle("📎",lang.getTranslation("helpMenu.categories.general")))) @@ -24,17 +14,17 @@ export const loadAllHelpMenuCategories = async () => { helpmenu.add(new api.ODHelpMenuCategory("opendiscord:extra",0,utilities.emojiTitle("✨",lang.getTranslation("helpMenu.categories.extra")))) } -export const loadAllHelpMenuComponents = async () => { +export async function loadAllHelpMenuComponents(){ const helpmenu = opendiscord.helpmenu const generalConfig = opendiscord.configs.get("opendiscord:general") if (!generalConfig) return const prefix = generalConfig.data.prefix - const enableDeleteWithoutTranscript = generalConfig.data.system.enableDeleteWithoutTranscript + const enableDeleteWithoutTranscript = generalConfig.data.ticketSystem.enableDeleteWithoutTranscript const allowedCommands: string[] = [] - for (const key in generalConfig.data.system.permissions){ - if (generalConfig.data.system.permissions[key] != "none") allowedCommands.push(key) + for (const key in generalConfig.data.permissions){ + if (generalConfig.data.permissions[key] != "none") allowedCommands.push(key) } const general = helpmenu.get("opendiscord:general") @@ -293,5 +283,13 @@ export const loadAllHelpMenuComponents = async () => { textOptions:[{name:"priority",optional:false},{name:"reason",optional:true}], slashOptions:[{name:"priority",optional:false},{name:"reason",optional:true}] })) + if (allowedCommands.includes("transcripts")) advanced.add(new api.ODHelpMenuCommandComponent("opendiscord:transcripts",0,{ + textName:prefix+"transcripts", + textDescription:lang.getTranslation("commands.transcripts"), + slashName:"/transcripts", + slashDescription:lang.getTranslation("commands.transcripts"), + textOptions:[{name:"user",optional:false}], + slashOptions:[{name:"user",optional:false}] + })) } } \ No newline at end of file diff --git a/src/data/framework/languageLoader.ts b/src/data/framework/languageLoader.ts index de1ef2c..3a91ac8 100644 --- a/src/data/framework/languageLoader.ts +++ b/src/data/framework/languageLoader.ts @@ -1,15 +1,6 @@ -import {opendiscord, api, utilities} from "../../index" +import {opendiscord, api, utilities} from "../../index.js" -/** (CONTRIBUTOR GUIDE) HOW TO ADD NEW LANGUAGES? - * - Add the file to (./languages/) and make sure the metadata is valid. - * - Register the language in loadAllLanguages() in (./src/data/framework/languageLoader.ts). - * - Add autocomplete for the language in ODLanguageManagerIds_Default in (./src/core/api/defaults/language.ts). - * - Update the language list in the README.md translator list. - * - Update the 2 language counters in the README.md features list. - * - Update the Open Ticket Documentation. - */ - -export const loadAllLanguages = async () => { +export async function loadAllLanguages(){ //register languages opendiscord.languages.add(new api.ODJsonLanguage("opendiscord:custom","custom.json")) opendiscord.languages.add(new api.ODJsonLanguage("opendiscord:english","english.json")) @@ -46,6 +37,8 @@ export const loadAllLanguages = async () => { opendiscord.languages.add(new api.ODJsonLanguage("opendiscord:korean","korean.json")) opendiscord.languages.add(new api.ODJsonLanguage("opendiscord:kurdish","kurdish.json")) opendiscord.languages.add(new api.ODJsonLanguage("opendiscord:simplified-chinese","simplified-chinese.json")) + opendiscord.languages.add(new api.ODJsonLanguage("opendiscord:traditional-chinese","traditional-chinese.json")) opendiscord.languages.add(new api.ODJsonLanguage("opendiscord:slovenian","slovenian.json")) opendiscord.languages.add(new api.ODJsonLanguage("opendiscord:tamil","tamil.json")) -} \ No newline at end of file + opendiscord.languages.add(new api.ODJsonLanguage("opendiscord:khmer","khmer.json")) +} diff --git a/src/data/framework/liveStatusLoader.ts b/src/data/framework/liveStatusLoader.ts index 647f63e..26f95ee 100644 --- a/src/data/framework/liveStatusLoader.ts +++ b/src/data/framework/liveStatusLoader.ts @@ -1,6 +1,6 @@ -import {opendiscord, api, utilities} from "../../index" +import {opendiscord, api, utilities} from "../../index.js" -export const loadAllLiveStatusSources = async () => { +export async function loadAllLiveStatusSources(){ //DEFAULT DJDJ DEV - opendiscord.livestatus.add(new api.ODLiveStatusUrlSource("opendiscord:default-djdj-dev","https://raw.githubusercontent.com/open-discord-bots/open-ticket/refs/heads/dev/src/livestatus.json")) + opendiscord.livestatus.add(new api.ODLiveStatusUrlSource(opendiscord,"opendiscord:default-djdj-dev","https://raw.githubusercontent.com/open-discord-bots/open-ticket/refs/heads/dev/src/livestatus.json")) } \ No newline at end of file diff --git a/src/data/framework/permissionLoader.ts b/src/data/framework/permissionLoader.ts index 7601113..b091ca1 100644 --- a/src/data/framework/permissionLoader.ts +++ b/src/data/framework/permissionLoader.ts @@ -1,7 +1,7 @@ -import {opendiscord, api, utilities} from "../../index" +import {opendiscord, api, utilities} from "../../index.js" import * as discord from "discord.js" -export const loadAllPermissions = async () => { +export async function loadAllPermissions(){ const generalConfig = opendiscord.configs.get("opendiscord:general") if (!generalConfig) return const mainServer = opendiscord.client.mainServer diff --git a/src/data/framework/postLoader.ts b/src/data/framework/postLoader.ts index 4098ee1..06cfce4 100644 --- a/src/data/framework/postLoader.ts +++ b/src/data/framework/postLoader.ts @@ -1,13 +1,13 @@ -import {opendiscord, api, utilities} from "../../index" +import {opendiscord, api, utilities} from "../../index.js" -export const loadAllPosts = async () => { +export async function loadAllPosts(){ const generalConfig = opendiscord.configs.get("opendiscord:general") if (!generalConfig) return const transcriptConfig = opendiscord.configs.get("opendiscord:transcripts") if (!transcriptConfig) return //LOGS CHANNEL - if (generalConfig.data.system.logs.enabled) opendiscord.posts.add(new api.ODPost("opendiscord:logs",generalConfig.data.system.logs.channel)) + if (generalConfig.data.logs.enabled) opendiscord.posts.add(new api.ODPost("opendiscord:logs",generalConfig.data.logs.channel)) //TRANSCRIPTS CHANNEL if (transcriptConfig.data.general.enabled && transcriptConfig.data.general.enableChannel) opendiscord.posts.add(new api.ODPost("opendiscord:transcripts",transcriptConfig.data.general.channel)) diff --git a/src/data/framework/progressBarLoader.ts b/src/data/framework/progressBarLoader.ts index 63eacb1..538ee42 100644 --- a/src/data/framework/progressBarLoader.ts +++ b/src/data/framework/progressBarLoader.ts @@ -1,7 +1,7 @@ -import {opendiscord, api, utilities} from "../../index" +import {opendiscord, api, utilities} from "../../index.js" -export const loadAllProgressBarRenderers = async () => { - const defaultSettings: api.ODProgressBarRenderer_DefaultSettings = { +export async function loadAllProgressBarRenderers(){ + const defaultSettings: api.ODDefaultProgressBarRendererSettings = { borderColor:"gray", filledBarColor:"openticket", emptyBarColor:"gray", @@ -23,37 +23,37 @@ export const loadAllProgressBarRenderers = async () => { } //VALUE RENDERER - const valueRendererSettings: api.ODProgressBarRenderer_DefaultSettings = {...defaultSettings} + const valueRendererSettings: api.ODDefaultProgressBarRendererSettings = {...defaultSettings} valueRendererSettings.labelType = "value" - opendiscord.progressbars.renderers.add(new api.ODProgressBarRenderer_Default("opendiscord:value-renderer",valueRendererSettings)) + opendiscord.progressbars.renderers.add(new api.ODDefaultProgressBarRenderer("opendiscord:value-renderer",valueRendererSettings)) //FRACTION RENDERER - const fractionRendererSettings: api.ODProgressBarRenderer_DefaultSettings = {...defaultSettings} + const fractionRendererSettings: api.ODDefaultProgressBarRendererSettings = {...defaultSettings} fractionRendererSettings.labelType = "fraction" - opendiscord.progressbars.renderers.add(new api.ODProgressBarRenderer_Default("opendiscord:fraction-renderer",fractionRendererSettings)) + opendiscord.progressbars.renderers.add(new api.ODDefaultProgressBarRenderer("opendiscord:fraction-renderer",fractionRendererSettings)) //PERCENTAGE RENDERER - const percentageRendererSettings: api.ODProgressBarRenderer_DefaultSettings = {...defaultSettings} + const percentageRendererSettings: api.ODDefaultProgressBarRendererSettings = {...defaultSettings} percentageRendererSettings.labelType = "percentage" - opendiscord.progressbars.renderers.add(new api.ODProgressBarRenderer_Default("opendiscord:percentage-renderer",percentageRendererSettings)) + opendiscord.progressbars.renderers.add(new api.ODDefaultProgressBarRenderer("opendiscord:percentage-renderer",percentageRendererSettings)) //TIME MS RENDERER - const timeMsRendererSettings: api.ODProgressBarRenderer_DefaultSettings = {...defaultSettings} + const timeMsRendererSettings: api.ODDefaultProgressBarRendererSettings = {...defaultSettings} timeMsRendererSettings.labelType = "time-ms" - opendiscord.progressbars.renderers.add(new api.ODProgressBarRenderer_Default("opendiscord:time-ms-renderer",timeMsRendererSettings)) + opendiscord.progressbars.renderers.add(new api.ODDefaultProgressBarRenderer("opendiscord:time-ms-renderer",timeMsRendererSettings)) //TIME SEC RENDERER - const timeSecRendererSettings: api.ODProgressBarRenderer_DefaultSettings = {...defaultSettings} + const timeSecRendererSettings: api.ODDefaultProgressBarRendererSettings = {...defaultSettings} timeSecRendererSettings.labelType = "time-sec" - opendiscord.progressbars.renderers.add(new api.ODProgressBarRenderer_Default("opendiscord:time-sec-renderer",timeSecRendererSettings)) + opendiscord.progressbars.renderers.add(new api.ODDefaultProgressBarRenderer("opendiscord:time-sec-renderer",timeSecRendererSettings)) //TIME MIN RENDERER - const timeMinRendererSettings: api.ODProgressBarRenderer_DefaultSettings = {...defaultSettings} + const timeMinRendererSettings: api.ODDefaultProgressBarRendererSettings = {...defaultSettings} timeMinRendererSettings.labelType = "time-min" - opendiscord.progressbars.renderers.add(new api.ODProgressBarRenderer_Default("opendiscord:time-min-renderer",timeMinRendererSettings)) + opendiscord.progressbars.renderers.add(new api.ODDefaultProgressBarRenderer("opendiscord:time-min-renderer",timeMinRendererSettings)) } -export const loadAllProgressBars = async () => { +export async function loadAllProgressBars(){ const fractRenderer = opendiscord.progressbars.renderers.get("opendiscord:fraction-renderer") //SLASH COMMAND REMOVE (doesn't have correct amount yet) diff --git a/src/data/framework/startScreenLoader.ts b/src/data/framework/startScreenLoader.ts index 328e01b..5220d17 100644 --- a/src/data/framework/startScreenLoader.ts +++ b/src/data/framework/startScreenLoader.ts @@ -1,7 +1,7 @@ -import {opendiscord, api, utilities} from "../../index" +import {opendiscord, api, utilities} from "../../index.js" import ansis from "ansis" -export const loadAllStartScreenComponents = async () => { +export async function loadAllStartScreenComponents(){ //LOGO opendiscord.startscreen.add(new api.ODStartScreenLogoComponent("opendiscord:logo",1000,[ " ██████╗ ██████╗ ███████╗███╗ ██╗ ████████╗██╗ ██████╗██╗ ██╗███████╗████████╗ ", diff --git a/src/data/framework/stateLoader.ts b/src/data/framework/stateLoader.ts new file mode 100644 index 0000000..15167f5 --- /dev/null +++ b/src/data/framework/stateLoader.ts @@ -0,0 +1,9 @@ +import {opendiscord, api, utilities} from "../../index.js" + +export async function loadAllStates(){ + const stateDatabase = opendiscord.databases.get("opendiscord:message-states") + + opendiscord.states.add(new api.ODInteractiveMessageState("opendiscord:interactive-message",opendiscord.client,stateDatabase)) + opendiscord.states.add(new api.ODClearMessageState("opendiscord:clear-message",opendiscord.client,stateDatabase)) + opendiscord.states.add(new api.ODPanelMessageState("opendiscord:panel-message",opendiscord.client,stateDatabase)) +} \ No newline at end of file diff --git a/src/data/framework/statLoader.ts b/src/data/framework/statisticLoader.ts similarity index 56% rename from src/data/framework/statLoader.ts rename to src/data/framework/statisticLoader.ts index 9392c5f..97d785a 100644 --- a/src/data/framework/statLoader.ts +++ b/src/data/framework/statisticLoader.ts @@ -1,41 +1,41 @@ -import {opendiscord, api, utilities} from "../../index" +import {opendiscord, api, utilities} from "../../index.js" import * as discord from "discord.js" -const stats = opendiscord.stats +const stats = opendiscord.statistics const lang = opendiscord.languages -export const loadAllStatScopes = async () => { - stats.add(new api.ODStatGlobalScope("opendiscord:global",utilities.emojiTitle("📊",lang.getTranslation("stats.scopes.global")))) - stats.add(new api.ODStatGlobalScope("opendiscord:system",utilities.emojiTitle("⚙️",lang.getTranslation("stats.scopes.system")))) - stats.add(new api.ODStatScope("opendiscord:user",utilities.emojiTitle("📊",lang.getTranslation("stats.scopes.user")))) - stats.add(new api.ODStatScope("opendiscord:ticket",utilities.emojiTitle("📊",lang.getTranslation("stats.scopes.ticket")))) - stats.add(new api.ODStatScope("opendiscord:participants",utilities.emojiTitle("👥",lang.getTranslation("stats.scopes.participants")))) - stats.add(new api.ODStatScope("opendiscord:messages",utilities.emojiTitle("💬",lang.getTranslation("stats.scopes.messages")))) +export async function loadAllStatisticScopes(){ + stats.add(new api.ODStatisticGlobalScope("opendiscord:global",utilities.emojiTitle("📊",lang.getTranslation("stats.scopes.global")))) + stats.add(new api.ODStatisticGlobalScope("opendiscord:system",utilities.emojiTitle("⚙️",lang.getTranslation("stats.scopes.system")))) + stats.add(new api.ODStatisticScope("opendiscord:user",utilities.emojiTitle("📊",lang.getTranslation("stats.scopes.user")))) + stats.add(new api.ODStatisticScope("opendiscord:ticket",utilities.emojiTitle("📊",lang.getTranslation("stats.scopes.ticket")))) + stats.add(new api.ODStatisticScope("opendiscord:participants",utilities.emojiTitle("👥",lang.getTranslation("stats.scopes.participants")))) + stats.add(new api.ODStatisticScope("opendiscord:messages",utilities.emojiTitle("💬",lang.getTranslation("stats.scopes.messages")))) } -export const loadAllStats = async () => { +export async function loadAllStatistics(){ const generalConfig = opendiscord.configs.get("opendiscord:general") if (!generalConfig) return const global = stats.get("opendiscord:global") if (global){ - global.add(new api.ODBasicStat("opendiscord:tickets-created",13,lang.getTranslation("stats.properties.ticketsCreated"),0)) - global.add(new api.ODBasicStat("opendiscord:tickets-closed",12,lang.getTranslation("stats.properties.ticketsClosed"),0)) - global.add(new api.ODBasicStat("opendiscord:tickets-deleted",11,lang.getTranslation("stats.properties.ticketsDeleted"),0)) - global.add(new api.ODBasicStat("opendiscord:tickets-reopened",10,lang.getTranslation("stats.properties.ticketsReopened"),0)) - global.add(new api.ODBasicStat("opendiscord:tickets-autoclosed",9,lang.getTranslation("stats.properties.ticketsAutoclosed"),0)) - global.add(new api.ODBasicStat("opendiscord:tickets-autodeleted",8,lang.getTranslation("stats.properties.ticketsAutodeleted"),0)) - global.add(new api.ODBasicStat("opendiscord:tickets-claimed",7,lang.getTranslation("stats.properties.ticketsClaimed"),0)) - global.add(new api.ODBasicStat("opendiscord:tickets-pinned",6,lang.getTranslation("stats.properties.ticketsPinned"),0)) - global.add(new api.ODBasicStat("opendiscord:tickets-moved",5,lang.getTranslation("stats.properties.ticketsMoved"),0)) - global.add(new api.ODBasicStat("opendiscord:tickets-transferred",4,lang.getTranslation("stats.properties.ticketsTransferred"),0)) - global.add(new api.ODBasicStat("opendiscord:users-blacklisted",3,lang.getTranslation("stats.properties.usersBlacklisted"),0)) - global.add(new api.ODBasicStat("opendiscord:transcripts-created",2,lang.getTranslation("stats.properties.transcriptsCreated"),0)) - global.add(new api.ODDynamicStat("opendiscord:ticket-volume",1,() => { + global.add(new api.ODBaseStatistic("opendiscord:tickets-created",13,lang.getTranslation("stats.properties.ticketsCreated"),0)) + global.add(new api.ODBaseStatistic("opendiscord:tickets-closed",12,lang.getTranslation("stats.properties.ticketsClosed"),0)) + global.add(new api.ODBaseStatistic("opendiscord:tickets-deleted",11,lang.getTranslation("stats.properties.ticketsDeleted"),0)) + global.add(new api.ODBaseStatistic("opendiscord:tickets-reopened",10,lang.getTranslation("stats.properties.ticketsReopened"),0)) + global.add(new api.ODBaseStatistic("opendiscord:tickets-autoclosed",9,lang.getTranslation("stats.properties.ticketsAutoclosed"),0)) + global.add(new api.ODBaseStatistic("opendiscord:tickets-autodeleted",8,lang.getTranslation("stats.properties.ticketsAutodeleted"),0)) + global.add(new api.ODBaseStatistic("opendiscord:tickets-claimed",7,lang.getTranslation("stats.properties.ticketsClaimed"),0)) + global.add(new api.ODBaseStatistic("opendiscord:tickets-pinned",6,lang.getTranslation("stats.properties.ticketsPinned"),0)) + global.add(new api.ODBaseStatistic("opendiscord:tickets-moved",5,lang.getTranslation("stats.properties.ticketsMoved"),0)) + global.add(new api.ODBaseStatistic("opendiscord:tickets-transferred",4,lang.getTranslation("stats.properties.ticketsTransferred"),0)) + global.add(new api.ODBaseStatistic("opendiscord:users-blacklisted",3,lang.getTranslation("stats.properties.usersBlacklisted"),0)) + global.add(new api.ODBaseStatistic("opendiscord:transcripts-created",2,lang.getTranslation("stats.properties.transcriptsCreated"),0)) + global.add(new api.ODDynamicStatistic("opendiscord:ticket-volume",1,() => { return lang.getTranslation("stats.properties.ticketVolume")+": `"+opendiscord.tickets.getLength()+"`" })) - global.add(new api.ODDynamicStat("opendiscord:average-tickets",0,async () => { - const userTicketsCreated = await opendiscord.stats.get("opendiscord:user").getAllStats("opendiscord:tickets-created") + global.add(new api.ODDynamicStatistic("opendiscord:average-tickets",0,async () => { + const userTicketsCreated = await opendiscord.statistics.get("opendiscord:user").getAllStats("opendiscord:tickets-created") const average = userTicketsCreated.map((s) => s.value as number).filter((t) => t > 0).reduce((prev,curr) => prev+curr,0)/userTicketsCreated.length const roundedAverage = Math.round(average*1000)/1000 return lang.getTranslation("stats.properties.averageTickets")+": `"+roundedAverage+"`" @@ -44,23 +44,23 @@ export const loadAllStats = async () => { const system = stats.get("opendiscord:system") if (system){ - system.add(new api.ODDynamicStat("opendiscord:startup-date",2,() => { + system.add(new api.ODDynamicStatistic("opendiscord:startup-date",2,() => { return lang.getTranslation("params.uppercase.startupDate")+": "+discord.time(opendiscord.processStartupDate,"f") })) - system.add(new api.ODDynamicStat("opendiscord:system-uptime",1,() => { + system.add(new api.ODDynamicStatistic("opendiscord:system-uptime",1,() => { return lang.getTranslation("params.uppercase.uptime")+": "+discord.time(opendiscord.processStartupDate,"R") })) - system.add(new api.ODDynamicStat("opendiscord:version",0,() => { + system.add(new api.ODDynamicStatistic("opendiscord:version",0,() => { return lang.getTranslation("params.uppercase.version")+": `"+opendiscord.versions.get("opendiscord:version").toString()+"`" })) } const user = stats.get("opendiscord:user") if (user){ - user.add(new api.ODDynamicStat("opendiscord:name",11,async (scopeId,guild,channel,user) => { + user.add(new api.ODDynamicStatistic("opendiscord:name",11,async (scopeId,guild,channel,user) => { return lang.getTranslation("params.uppercase.name")+": "+discord.userMention(scopeId) })) - user.add(new api.ODDynamicStat("opendiscord:role",10,async (scopeId,guild,channel,user) => { + user.add(new api.ODDynamicStatistic("opendiscord:role",10,async (scopeId,guild,channel,user) => { const scopeMember = await opendiscord.client.fetchGuildMember(guild,scopeId) if (!scopeMember) return "" @@ -72,27 +72,27 @@ export const loadAllStats = async () => { if (permissions.type == "support") return lang.getTranslation("params.uppercase.role")+": 💬 `"+lang.getTranslation("stats.roles.support")+"`" else return lang.getTranslation("params.uppercase.role")+": 👤 `"+lang.getTranslation("stats.roles.member")+"`" })) - user.add(new api.ODBasicStat("opendiscord:tickets-created",10,lang.getTranslation("stats.properties.ticketsCreated"),0)) - user.add(new api.ODBasicStat("opendiscord:tickets-closed",9,lang.getTranslation("stats.properties.ticketsClosed"),0)) - user.add(new api.ODBasicStat("opendiscord:tickets-deleted",8,lang.getTranslation("stats.properties.ticketsDeleted"),0)) - user.add(new api.ODBasicStat("opendiscord:tickets-reopened",7,lang.getTranslation("stats.properties.ticketsReopened"),0)) - user.add(new api.ODBasicStat("opendiscord:tickets-claimed",6,lang.getTranslation("stats.properties.ticketsClaimed"),0)) - user.add(new api.ODBasicStat("opendiscord:tickets-pinned",5,lang.getTranslation("stats.properties.ticketsPinned"),0)) - user.add(new api.ODBasicStat("opendiscord:tickets-moved",4,lang.getTranslation("stats.properties.ticketsMoved"),0)) - user.add(new api.ODBasicStat("opendiscord:tickets-transferred",3,lang.getTranslation("stats.properties.ticketsTransferred"),0)) - user.add(new api.ODBasicStat("opendiscord:users-blacklisted",2,lang.getTranslation("stats.properties.usersBlacklisted"),0)) - user.add(new api.ODBasicStat("opendiscord:transcripts-created",1,lang.getTranslation("stats.properties.transcriptsCreated"),0)) - user.add(new api.ODDynamicStat("opendiscord:current-tickets",0,async (scopeId,guild,channel,user) => { + user.add(new api.ODBaseStatistic("opendiscord:tickets-created",10,lang.getTranslation("stats.properties.ticketsCreated"),0)) + user.add(new api.ODBaseStatistic("opendiscord:tickets-closed",9,lang.getTranslation("stats.properties.ticketsClosed"),0)) + user.add(new api.ODBaseStatistic("opendiscord:tickets-deleted",8,lang.getTranslation("stats.properties.ticketsDeleted"),0)) + user.add(new api.ODBaseStatistic("opendiscord:tickets-reopened",7,lang.getTranslation("stats.properties.ticketsReopened"),0)) + user.add(new api.ODBaseStatistic("opendiscord:tickets-claimed",6,lang.getTranslation("stats.properties.ticketsClaimed"),0)) + user.add(new api.ODBaseStatistic("opendiscord:tickets-pinned",5,lang.getTranslation("stats.properties.ticketsPinned"),0)) + user.add(new api.ODBaseStatistic("opendiscord:tickets-moved",4,lang.getTranslation("stats.properties.ticketsMoved"),0)) + user.add(new api.ODBaseStatistic("opendiscord:tickets-transferred",3,lang.getTranslation("stats.properties.ticketsTransferred"),0)) + user.add(new api.ODBaseStatistic("opendiscord:users-blacklisted",2,lang.getTranslation("stats.properties.usersBlacklisted"),0)) + user.add(new api.ODBaseStatistic("opendiscord:transcripts-created",1,lang.getTranslation("stats.properties.transcriptsCreated"),0)) + user.add(new api.ODDynamicStatistic("opendiscord:current-tickets",0,async (scopeId,guild,channel,user) => { return lang.getTranslation("stats.properties.currentTickets")+": `"+opendiscord.tickets.getFiltered((t) => t.get("opendiscord:opened-by").value === scopeId).length+"`" })) } const ticket = stats.get("opendiscord:ticket") if (ticket){ - ticket.add(new api.ODDynamicStat("opendiscord:name",5,async (scopeId,guild,channel,user) => { + ticket.add(new api.ODDynamicStatistic("opendiscord:name",5,async (scopeId,guild,channel,user) => { return lang.getTranslation("params.uppercase.ticket")+": "+discord.channelMention(scopeId) })) - ticket.add(new api.ODDynamicStat("opendiscord:status",4,async (scopeId,guild,channel,user) => { + ticket.add(new api.ODDynamicStatistic("opendiscord:status",4,async (scopeId,guild,channel,user) => { const ticket = opendiscord.tickets.get(scopeId) if (!ticket) return "" @@ -100,35 +100,35 @@ export const loadAllStats = async () => { return closed ? lang.getTranslation("params.uppercase.status")+": 🔒 `"+lang.getTranslation("params.uppercase.closed")+"`" : lang.getTranslation("params.uppercase.status")+": 🔓 `"+lang.getTranslation("params.uppercase.open")+"`" })) - ticket.add(new api.ODDynamicStat("opendiscord:claimed",3,async (scopeId,guild,channel,user) => { + ticket.add(new api.ODDynamicStatistic("opendiscord:claimed",3,async (scopeId,guild,channel,user) => { const ticket = opendiscord.tickets.get(scopeId) if (!ticket) return "" const claimed = ticket.exists("opendiscord:claimed") ? ticket.get("opendiscord:claimed").value : false return claimed ? lang.getTranslation("params.uppercase.claimed")+": 🟢 `"+lang.getTranslation("params.uppercase.yes")+"`" : lang.getTranslation("params.uppercase.claimed")+": 🔴 `"+lang.getTranslation("params.uppercase.no")+"`" })) - ticket.add(new api.ODDynamicStat("opendiscord:pinned",2,async (scopeId,guild,channel,user) => { + ticket.add(new api.ODDynamicStatistic("opendiscord:pinned",2,async (scopeId,guild,channel,user) => { const ticket = opendiscord.tickets.get(scopeId) if (!ticket) return "" const pinned = ticket.exists("opendiscord:pinned") ? ticket.get("opendiscord:pinned").value : false return pinned ? lang.getTranslation("params.uppercase.pinned")+": 🟢 `"+lang.getTranslation("params.uppercase.yes")+"`" : lang.getTranslation("params.uppercase.pinned")+": 🔴 `"+lang.getTranslation("params.uppercase.no")+"`" })) - ticket.add(new api.ODDynamicStat("opendiscord:creation-date",1,async (scopeId,guild,channel,user) => { + ticket.add(new api.ODDynamicStatistic("opendiscord:creation-date",1,async (scopeId,guild,channel,user) => { const ticket = opendiscord.tickets.get(scopeId) if (!ticket) return "" const rawDate = ticket.get("opendiscord:opened-on").value ?? new Date().getTime() return lang.getTranslation("params.uppercase.creationDate")+": "+discord.time(new Date(rawDate),"f") })) - ticket.add(new api.ODDynamicStat("opendiscord:creator",0,async (scopeId,guild,channel,user) => { + ticket.add(new api.ODDynamicStatistic("opendiscord:creator",0,async (scopeId,guild,channel,user) => { const ticket = opendiscord.tickets.get(scopeId) if (!ticket) return "" const creator = ticket.get("opendiscord:opened-by").value return lang.getTranslation("params.uppercase.creator")+": "+ (creator ? discord.userMention(creator) : "`unknown`") })) - ticket.add(new api.ODDynamicStat("opendiscord:ticket-age",-1,async (scopeId,guild,channel,user) => { + ticket.add(new api.ODDynamicStatistic("opendiscord:ticket-age",-1,async (scopeId,guild,channel,user) => { const ticket = opendiscord.tickets.get(scopeId) if (!ticket) return "" @@ -142,7 +142,7 @@ export const loadAllStats = async () => { const participants = stats.get("opendiscord:participants") if (participants){ - participants.add(new api.ODDynamicStat("opendiscord:participants",0,async (scopeId,guild,channel,user) => { + participants.add(new api.ODDynamicStatistic("opendiscord:participants",0,async (scopeId,guild,channel,user) => { const ticket = opendiscord.tickets.get(scopeId) if (!ticket) return "" @@ -157,7 +157,7 @@ export const loadAllStats = async () => { const messages = stats.get("opendiscord:messages") if (messages){ - messages.add(new api.ODDynamicStat("opendiscord:count",0,async (scopeId,guild,channel,user) => { + messages.add(new api.ODDynamicStatistic("opendiscord:count",0,async (scopeId,guild,channel,user) => { const ticket = opendiscord.tickets.get(scopeId) if (!ticket) return "" diff --git a/src/data/framework/codeLoader.ts b/src/data/framework/taskLoader.ts similarity index 70% rename from src/data/framework/codeLoader.ts rename to src/data/framework/taskLoader.ts index 08b4cd0..bc74ec7 100644 --- a/src/data/framework/codeLoader.ts +++ b/src/data/framework/taskLoader.ts @@ -1,4 +1,4 @@ -import {opendiscord, api, utilities} from "../../index" +import {opendiscord, api, utilities} from "../../index.js" import * as discord from "discord.js" const generalConfig = opendiscord.configs.get("opendiscord:general") @@ -7,22 +7,23 @@ const userDatabase = opendiscord.databases.get("opendiscord:users") const ticketDatabase = opendiscord.databases.get("opendiscord:tickets") const statsDatabase = opendiscord.databases.get("opendiscord:stats") const optionDatabase = opendiscord.databases.get("opendiscord:options") +const transcriptsDatabase = opendiscord.databases.get("opendiscord:transcripts") const mainServer = opendiscord.client.mainServer -export const loadAllCode = async () => { +export async function loadAllTasks(){ if (!generalConfig || !mainServer || !globalDatabase || !userDatabase || !ticketDatabase || !statsDatabase || !optionDatabase) return - loadCommandErrorHandlingCode() - loadStartListeningInteractionsCode() - loadDatabaseCleanersCode() - loadPanelAutoUpdateCode() - loadDatabaseSaversCode() - loadAutoCode() + loadCommandErrorHandlingTasks() + loadStartListeningInteractionsTasks() + loadDatabaseCleanersTasks() + loadPanelAutoUpdateTasks() + loadDatabaseSaversTasks() + loadAutoTasks() } -export const loadCommandErrorHandlingCode = async () => { +export async function loadCommandErrorHandlingTasks(){ //COMMAND ERROR HANDLING - opendiscord.code.add(new api.ODCode("opendiscord:command-error-handling",14,() => { + opendiscord.tasks.add(new api.ODTask("opendiscord:command-error-handling",14,() => { //invalid/missing options opendiscord.client.textCommands.onError(async (error) => { if (error.msg.channel.type == discord.ChannelType.GroupDM) return @@ -30,36 +31,35 @@ export const loadCommandErrorHandlingCode = async () => { error.msg.channel.send((await opendiscord.builders.messages.getSafe("opendiscord:error-option-invalid").build("text",{guild:error.msg.guild,channel:error.msg.channel,user:error.msg.author,error})).message) }else if (error.type == "missing_option"){ error.msg.channel.send((await opendiscord.builders.messages.getSafe("opendiscord:error-option-missing").build("text",{guild:error.msg.guild,channel:error.msg.channel,user:error.msg.author,error})).message) - }else if (error.type == "unknown_command" && generalConfig.data.system.sendErrorOnUnknownCommand){ + }else if (error.type == "unknown_command" && generalConfig.data.ticketSystem.sendErrorOnUnknownCommand){ error.msg.channel.send((await opendiscord.builders.messages.getSafe("opendiscord:error-unknown-command").build("text",{guild:error.msg.guild,channel:error.msg.channel,user:error.msg.author,error})).message) } }) //responder timeout - opendiscord.responders.commands.setTimeoutErrorCallback(async (instance,source) => { - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-responder-timeout").build(source,{guild:instance.guild,channel:instance.channel,user:instance.user})) + opendiscord.responders.commands.setTimeoutErrorCallback(async (instance,origin) => { + return await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-responder-timeout").build(origin,{guild:instance.guild,channel:instance.channel,user:instance.user})) },null) - opendiscord.responders.buttons.setTimeoutErrorCallback(async (instance,source) => { - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-responder-timeout").build(source,{guild:instance.guild,channel:instance.channel,user:instance.user})) + opendiscord.responders.buttons.setTimeoutErrorCallback(async (instance,origin) => { + return await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-responder-timeout").build(origin,{guild:instance.guild,channel:instance.channel,user:instance.user})) },null) - opendiscord.responders.dropdowns.setTimeoutErrorCallback(async (instance,source) => { - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-responder-timeout").build(source,{guild:instance.guild,channel:instance.channel,user:instance.user})) + opendiscord.responders.dropdowns.setTimeoutErrorCallback(async (instance,origin) => { + return await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-responder-timeout").build(origin,{guild:instance.guild,channel:instance.channel,user:instance.user})) },null) - opendiscord.responders.modals.setTimeoutErrorCallback(async (instance,source) => { + opendiscord.responders.modals.setTimeoutErrorCallback(async (instance,origin) => { if (!instance.channel){ - instance.reply({id:new api.ODId("looks-like-we-got-an-error-here"), ephemeral:true, message:{ + return await instance.reply({id:new api.ODId("opendiscord:unknown-error"), ephemeral:true, message:{ content:":x: **Something went wrong while replying to this modal!**" }}) - return } - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-responder-timeout").build(source,{guild:instance.guild,channel:instance.channel,user:instance.user})) + return await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-responder-timeout").build(origin,{guild:instance.guild,channel:instance.channel,user:instance.user})) },null) })) } -export const loadStartListeningInteractionsCode = async () => { +export async function loadStartListeningInteractionsTasks(){ //START LISTENING TO INTERACTIONS - opendiscord.code.add(new api.ODCode("opendiscord:start-listening-interactions",13,() => { + opendiscord.tasks.add(new api.ODTask("opendiscord:start-listening-interactions",13,() => { opendiscord.client.slashCommands.startListeningToInteractions() opendiscord.client.textCommands.startListeningToInteractions() opendiscord.client.contextMenus.startListeningToInteractions() @@ -67,43 +67,11 @@ export const loadStartListeningInteractionsCode = async () => { })) } -export const loadDatabaseCleanersCode = async () => { +export async function loadDatabaseCleanersTasks(){ if (!mainServer) return - - //PANEL DATABASE CLEANER - opendiscord.code.add(new api.ODCode("opendiscord:panel-database-cleaner",12,async () => { - const validPanels: string[] = [] - - //check global database for valid panel embeds - for (const panel of (await globalDatabase.getCategory("opendiscord:panel-message") ?? [])){ - if (!validPanels.includes(panel.key)){ - try{ - const splittedId = panel.key.split("_") - const message = await opendiscord.client.fetchGuildChannelMessage(mainServer,splittedId[0],splittedId[1]) - if (message) validPanels.push(panel.key) - }catch{} - } - } - - //remove all unused panels - for (const panel of (await globalDatabase.getCategory("opendiscord:panel-message") ?? [])){ - if (!validPanels.includes(panel.key)){ - await globalDatabase.delete("opendiscord:panel-message",panel.key) - await globalDatabase.delete("opendiscord:panel-update",panel.key) - } - } - - //delete panel from database on delete - opendiscord.client.client.on("messageDelete",async (msg) => { - if (await globalDatabase.exists("opendiscord:panel-message",msg.channel.id+"_"+msg.id)){ - await globalDatabase.delete("opendiscord:panel-message",msg.channel.id+"_"+msg.id) - await globalDatabase.delete("opendiscord:panel-update",msg.channel.id+"_"+msg.id) - } - }) - })) - + //SUFFIX DATABASE CLEANER - opendiscord.code.add(new api.ODCode("opendiscord:suffix-database-cleaner",11,async () => { + opendiscord.tasks.add(new api.ODTask("opendiscord:suffix-database-cleaner",11,async () => { const validSuffixCounters: string[] = [] const validSuffixHistories: string[] = [] @@ -137,7 +105,7 @@ export const loadDatabaseCleanersCode = async () => { })) //OPTION DATABASE CLEANER - opendiscord.code.add(new api.ODCode("opendiscord:option-database-cleaner",10,async () => { + opendiscord.tasks.add(new api.ODTask("opendiscord:option-database-cleaner",10,async () => { //delete all unused options (async) for (const option of (await optionDatabase.getCategory("opendiscord:used-option") ?? [])){ if (!opendiscord.options.exists(option.key)){ @@ -151,7 +119,7 @@ export const loadDatabaseCleanersCode = async () => { })) //USER DATABASE CLEANER (full async/parallel because it takes a lot of time) - opendiscord.code.add(new api.ODCode("opendiscord:user-database-cleaner",9,() => { + opendiscord.tasks.add(new api.ODTask("opendiscord:user-database-cleaner",9,() => { utilities.runAsync(async () => { const validUsers: string[] = [] @@ -217,7 +185,7 @@ export const loadDatabaseCleanersCode = async () => { })) //TICKET DATABASE CLEANER - opendiscord.code.add(new api.ODCode("opendiscord:ticket-database-cleaner",8,async () => { + opendiscord.tasks.add(new api.ODTask("opendiscord:ticket-database-cleaner",8,async () => { const validTickets: string[] = [] //check ticket database for valid tickets @@ -281,45 +249,67 @@ export const loadDatabaseCleanersCode = async () => { } }) })) -} -export const loadPanelAutoUpdateCode = async () => { - //PANEL AUTO UPDATE - opendiscord.code.add(new api.ODCode("opendiscord:panel-auto-update",7,async () => { - const globalDatabase = opendiscord.databases.get("opendiscord:global") - const panelIds = await globalDatabase.getCategory("opendiscord:panel-update") ?? [] - if (!mainServer) return - - for (const panelId of panelIds){ - const panel = opendiscord.panels.get(panelId.value) - - //panel doesn't exist anymore in config and needs to be removed - if (!panel){ - globalDatabase.delete("opendiscord:panel-update",panelId.key) - return - } - - try{ - const splittedId = panelId.key.split("_") - const channel = await opendiscord.client.fetchGuildTextChannel(mainServer,splittedId[0]) - if (!channel) return - const message = await opendiscord.client.fetchGuildChannelMessage(mainServer,channel,splittedId[1]) - if (!message || !message.editable) return - - message.edit((await opendiscord.builders.messages.getSafe("opendiscord:panel").build("auto-update",{guild:mainServer,channel,user:opendiscord.client.client.user,panel})).message) - opendiscord.log("Panel in server got auto-updated!","info",[ - {key:"channelid",value:splittedId[0]}, - {key:"messageid",value:splittedId[1]}, - {key:"panel",value:panelId.value} - ]) - }catch{} + //TRANSCRIPT DATABASE CLEANER + opendiscord.tasks.add(new api.ODTask("opendiscord:transcript-database-cleaner",8,async () => { + //preserve max 20 transcripts per user (async) + const userCount: Map = new Map() + for (const {key,value:transcript} of (await transcriptsDatabase.getCategory("opendiscord:transcript") ?? []).sort((a,b) => (b.value.ticketDeletedDate ?? 0)-(a.value.ticketDeletedDate ?? 0))){ + const currentAmount = userCount.get(transcript.ticketCreatorId) ?? 0 + userCount.set(transcript.ticketCreatorId,currentAmount+1) + + if (currentAmount > 20) transcriptsDatabase.delete("opendiscord:transcript",key) } })) } -export const loadDatabaseSaversCode = async () => { +export async function loadPanelAutoUpdateTasks(){ + //PANEL AUTO UPDATE + const panelMsgState = opendiscord.states.get("opendiscord:panel-message") + opendiscord.tasks.add(new api.ODTask("opendiscord:panel-auto-update",7,async () => { + if (!mainServer) return + + for (const panelState of (await panelMsgState.listMsgStates()).map((rawState) => rawState.value)){ + try{ + //fetch panel (& check if auto-update is required) + const panel = opendiscord.panels.get(panelState.data.panelId) + if (!panel || !panelState.data.panelAutoUpdate) continue + + //fetch panel channel + const channel = await opendiscord.client.fetchGuildTextChannel(mainServer,panelState.channelId) + if (!channel) continue + + //fetch panel message + const message = await opendiscord.client.fetchChannelMessage(channel,panelState.messageId) + if (!message || !message.editable || message.flags.has("Ephemeral")) continue + + const panelMessage = await message.edit((await opendiscord.builders.messages.getSafe("opendiscord:panel").build("auto-update",{guild:mainServer,channel,user:opendiscord.client.client.user,panel,isSubPanel:false})).message) + if (panelMessage) await panelMsgState.setMsgState({channel,message:panelMessage},{ + messageOrigin:"auto-update", + panelId:panel.id.value, + panelOptionIds:panel.get("opendiscord:options").value, + panelAutoUpdate:true, + isSubPanel:false + },panelMessage.flags.has("Ephemeral")) + + opendiscord.log("Panel in server got auto-updated!","info",[ + {key:"channelid",value:panelState.channelId}, + {key:"messageid",value:panelState.messageId}, + {key:"panel",value:panel.id.value} + ]) + }catch{ + opendiscord.log("Failed to auto-update panel","error",[ + {key:"channelid",value:panelState.channelId}, + {key:"messageid",value:panelState.messageId} + ]) + } + } + })) +} + +export async function loadDatabaseSaversTasks(){ //TICKET SAVER - opendiscord.code.add(new api.ODCode("opendiscord:ticket-saver",6,() => { + opendiscord.tasks.add(new api.ODTask("opendiscord:ticket-saver",6,() => { const mainVersion = opendiscord.versions.get("opendiscord:version") opendiscord.tickets.onAdd(async (ticket) => { @@ -356,7 +346,7 @@ export const loadDatabaseSaversCode = async () => { })) //BLACKLIST SAVER - opendiscord.code.add(new api.ODCode("opendiscord:blacklist-saver",5,() => { + opendiscord.tasks.add(new api.ODTask("opendiscord:blacklist-saver",5,() => { opendiscord.blacklist.onAdd(async (blacklist) => { await userDatabase.set("opendiscord:blacklist",blacklist.id.value,blacklist.reason) }) @@ -369,7 +359,7 @@ export const loadDatabaseSaversCode = async () => { })) //AUTO ROLE ON JOIN - opendiscord.code.add(new api.ODCode("opendiscord:auto-role-on-join",4,() => { + opendiscord.tasks.add(new api.ODTask("opendiscord:auto-role-on-join",4,() => { opendiscord.client.client.on("guildMemberAdd",async (member) => { for (const option of opendiscord.options.getAll()){ if (option instanceof api.ODRoleOption && option.get("opendiscord:add-on-join").value){ @@ -381,9 +371,11 @@ export const loadDatabaseSaversCode = async () => { })) } -const loadAutoCode = () => { +export async function loadAutoTasks(){ + const interactiveMsgState = opendiscord.states.get("opendiscord:interactive-message") + //AUTOCLOSE TIMEOUT - opendiscord.code.add(new api.ODCode("opendiscord:autoclose-timeout",3,() => { + opendiscord.tasks.add(new api.ODTask("opendiscord:autoclose-timeout",3,() => { setInterval(async () => { let count = 0 for (const ticket of opendiscord.tickets.getAll()){ @@ -400,21 +392,27 @@ const loadAutoCode = () => { if (enabled && (new Date().getTime() - lastMessage.createdTimestamp) >= time){ //autoclose ticket await opendiscord.actions.get("opendiscord:close-ticket").run("autoclose",{guild:channel.guild,channel,user:opendiscord.client.client.user,ticket,reason:"Autoclose",sendMessage:false}) - await channel.send((await opendiscord.builders.messages.getSafe("opendiscord:autoclose-message").build("timeout",{guild:channel.guild,channel,user:opendiscord.client.client.user,ticket})).message) + const sentMsg = await channel.send((await opendiscord.builders.messages.getSafe("opendiscord:autoclose-message").build("timeout",{guild:channel.guild,channel,user:opendiscord.client.client.user,ticket})).message) + await interactiveMsgState.setMsgState({channel,message:sentMsg},{ + messageType:"autoclose-message", + messageOrigin:"other", + messageAuthor:opendiscord.client.client.user.id, + messageReason:"Autoclose" + },false) count++ - await opendiscord.stats.get("opendiscord:global").setStat("opendiscord:tickets-autoclosed",1,"increase") + await opendiscord.statistics.get("opendiscord:global").setStat("opendiscord:tickets-autoclosed",1,"increase") } } } opendiscord.debug.debug("Finished autoclose timeout cycle!",[ - {key:"interval",value:opendiscord.defaults.getDefault("autocloseCheckInterval").toString()}, + {key:"interval",value:opendiscord.fuses.getFuse("autocloseCheckInterval").toString()}, {key:"closed",value:count.toString()} ]) - },opendiscord.defaults.getDefault("autocloseCheckInterval")) + },opendiscord.fuses.getFuse("autocloseCheckInterval")) })) //AUTOCLOSE LEAVE - opendiscord.code.add(new api.ODCode("opendiscord:autoclose-leave",2,() => { + opendiscord.tasks.add(new api.ODTask("opendiscord:autoclose-leave",2,() => { opendiscord.client.client.on("guildMemberRemove",async (member) => { for (const ticket of opendiscord.tickets.getAll()){ if (ticket.get("opendiscord:opened-by").value == member.id){ @@ -427,8 +425,14 @@ const loadAutoCode = () => { if (enabled){ //autoclose ticket await opendiscord.actions.get("opendiscord:close-ticket").run("autoclose",{guild:channel.guild,channel,user:opendiscord.client.client.user,ticket,reason:"Autoclose",sendMessage:false}) - await channel.send((await opendiscord.builders.messages.getSafe("opendiscord:autoclose-message").build("leave",{guild:channel.guild,channel,user:opendiscord.client.client.user,ticket})).message) - await opendiscord.stats.get("opendiscord:global").setStat("opendiscord:tickets-autoclosed",1,"increase") + const sentMsg = await channel.send((await opendiscord.builders.messages.getSafe("opendiscord:autoclose-message").build("leave",{guild:channel.guild,channel,user:opendiscord.client.client.user,ticket})).message) + await interactiveMsgState.setMsgState({channel,message:sentMsg},{ + messageType:"autoclose-message", + messageOrigin:"other", + messageAuthor:opendiscord.client.client.user.id, + messageReason:"Autoclose" + },false) + await opendiscord.statistics.get("opendiscord:global").setStat("opendiscord:tickets-autoclosed",1,"increase") } } } @@ -436,7 +440,7 @@ const loadAutoCode = () => { })) //AUTODELETE TIMEOUT - opendiscord.code.add(new api.ODCode("opendiscord:autodelete-timeout",1,() => { + opendiscord.tasks.add(new api.ODTask("opendiscord:autodelete-timeout",1,() => { setInterval(async () => { let count = 0 for (const ticket of opendiscord.tickets.getAll()){ @@ -446,7 +450,7 @@ const loadAutoCode = () => { if (lastMessage){ //ticket has last message const disableOnClaim = ticket.option.get("opendiscord:autodelete-disable-claim").value && ticket.get("opendiscord:claimed").value - const disableWhenNotClosed = generalConfig.data.system.autodeleteRequiresClosedTicket && !ticket.get("opendiscord:closed").value + const disableWhenNotClosed = generalConfig.data.ticketSystem.autodeleteRequiresClosedTicket && !ticket.get("opendiscord:closed").value const enabled = (disableOnClaim || disableWhenNotClosed) ? false : ticket.get("opendiscord:autodelete-enabled").value const days = ticket.get("opendiscord:autodelete-days").value @@ -457,19 +461,19 @@ const loadAutoCode = () => { await channel.send((await opendiscord.builders.messages.getSafe("opendiscord:autodelete-message").build("timeout",{guild:channel.guild,channel,user:opendiscord.client.client.user,ticket})).message) await opendiscord.actions.get("opendiscord:delete-ticket").run("autodelete",{guild:channel.guild,channel,user:opendiscord.client.client.user,ticket,reason:"Autodelete",sendMessage:false,withoutTranscript:false}) count++ - await opendiscord.stats.get("opendiscord:global").setStat("opendiscord:tickets-autodeleted",1,"increase") + await opendiscord.statistics.get("opendiscord:global").setStat("opendiscord:tickets-autodeleted",1,"increase") } } } opendiscord.debug.debug("Finished autodelete timeout cycle!",[ - {key:"interval",value:opendiscord.defaults.getDefault("autodeleteCheckInterval").toString()}, + {key:"interval",value:opendiscord.fuses.getFuse("autodeleteCheckInterval").toString()}, {key:"deleted",value:count.toString()} ]) - },opendiscord.defaults.getDefault("autodeleteCheckInterval")) + },opendiscord.fuses.getFuse("autodeleteCheckInterval")) })) //AUTODELETE LEAVE - opendiscord.code.add(new api.ODCode("opendiscord:autodelete-leave",0,() => { + opendiscord.tasks.add(new api.ODTask("opendiscord:autodelete-leave",0,() => { opendiscord.client.client.on("guildMemberRemove",async (member) => { for (const ticket of opendiscord.tickets.getAll()){ if (ticket.get("opendiscord:opened-by").value == member.id){ @@ -477,14 +481,14 @@ const loadAutoCode = () => { if (!channel) return //ticket has been created by this user const disableOnClaim = ticket.option.get("opendiscord:autodelete-disable-claim").value && ticket.get("opendiscord:claimed").value - const disableWhenNotClosed = generalConfig.data.system.autodeleteRequiresClosedTicket && !ticket.get("opendiscord:closed").value + const disableWhenNotClosed = generalConfig.data.ticketSystem.autodeleteRequiresClosedTicket && !ticket.get("opendiscord:closed").value const enabled = (disableOnClaim || disableWhenNotClosed || !ticket.get("opendiscord:autodelete-enabled").value) ? false : ticket.option.get("opendiscord:autodelete-enable-leave") if (enabled){ //autodelete ticket await channel.send((await opendiscord.builders.messages.getSafe("opendiscord:autodelete-message").build("leave",{guild:channel.guild,channel,user:opendiscord.client.client.user,ticket})).message) await opendiscord.actions.get("opendiscord:delete-ticket").run("autodelete",{guild:channel.guild,channel,user:opendiscord.client.client.user,ticket,reason:"Autodelete",sendMessage:false,withoutTranscript:false}) - await opendiscord.stats.get("opendiscord:global").setStat("opendiscord:tickets-autodeleted",1,"increase") + await opendiscord.statistics.get("opendiscord:global").setStat("opendiscord:tickets-autodeleted",1,"increase") } } } @@ -492,7 +496,7 @@ const loadAutoCode = () => { })) //TICKET ANTI BUSY (+ sync version of tickets with latest OT version in database) - opendiscord.code.add(new api.ODCode("opendiscord:ticket-anti-busy",-1,() => { + opendiscord.tasks.add(new api.ODTask("opendiscord:ticket-anti-busy",-1,() => { for (const ticket of opendiscord.tickets.getAll()){ //free tickets from corruption due to opendiscord:busy variable ticket.get("opendiscord:busy").value = false diff --git a/src/data/openticket/blacklistLoader.ts b/src/data/openticket/blacklistLoader.ts index 6e8ed6a..dcd95d2 100644 --- a/src/data/openticket/blacklistLoader.ts +++ b/src/data/openticket/blacklistLoader.ts @@ -1,6 +1,6 @@ -import {opendiscord, api, utilities} from "../../index" +import {opendiscord, api, utilities} from "../../index.js" -export const loadAllBlacklistedUsers = async () => { +export async function loadAllBlacklistedUsers(){ const userDatabase = opendiscord.databases.get("opendiscord:users") if (!userDatabase) return diff --git a/src/data/openticket/optionLoader.ts b/src/data/openticket/optionLoader.ts index 38dd5de..d39cb77 100644 --- a/src/data/openticket/optionLoader.ts +++ b/src/data/openticket/optionLoader.ts @@ -1,6 +1,6 @@ -import {opendiscord, api, utilities} from "../../index" +import {opendiscord, api, utilities} from "../../index.js" -export const loadAllOptions = async () => { +export async function loadAllOptions(){ const optionConfig = opendiscord.configs.get("opendiscord:options") if (!optionConfig) return @@ -9,11 +9,9 @@ export const loadAllOptions = async () => { const loadedOption = loadTicketOption(option) opendiscord.options.add(loadedOption) opendiscord.options.suffix.add(loadTicketOptionSuffix(loadedOption)) - }else if (option.type == "website"){ - opendiscord.options.add(loadWebsiteOption(option)) - }else if (option.type == "role"){ - opendiscord.options.add(loadRoleOption(option)) - } + }else if (option.type == "website") opendiscord.options.add(loadWebsiteOption(option)) + else if (option.type == "role") opendiscord.options.add(loadRoleOption(option)) + else if (option.type == "sub-panel") opendiscord.options.add(loadSubPanelOption(option)) }) //update options on config reload @@ -23,17 +21,15 @@ export const loadAllOptions = async () => { await opendiscord.options.suffix.loopAll((data,id) => {opendiscord.options.suffix.remove(id)}) //add new options - optionConfig.data.forEach((option) => { + for (const option of optionConfig.data){ if (option.type == "ticket"){ const loadedOption = loadTicketOption(option) opendiscord.options.add(loadedOption) opendiscord.options.suffix.add(loadTicketOptionSuffix(loadedOption)) - }else if (option.type == "website"){ - opendiscord.options.add(loadWebsiteOption(option)) - }else if (option.type == "role"){ - opendiscord.options.add(loadRoleOption(option)) - } - }) + }else if (option.type == "website") opendiscord.options.add(loadWebsiteOption(option)) + else if (option.type == "role") opendiscord.options.add(loadRoleOption(option)) + else if (option.type == "sub-panel") opendiscord.options.add(loadSubPanelOption(option)) + } //update options in tickets await opendiscord.tickets.loopAll((ticket) => { @@ -54,7 +50,7 @@ export const loadAllOptions = async () => { }) } -export const loadTicketOption = (option:api.ODJsonConfig_DefaultOptionTicketType): api.ODTicketOption => { +export const loadTicketOption = (option:api.ODOptionsJsonConfig_TicketOption): api.ODTicketOption => { return new api.ODTicketOption(option.id,[ new api.ODOptionData("opendiscord:name",option.name), new api.ODOptionData("opendiscord:description",option.description), @@ -71,9 +67,6 @@ export const loadTicketOption = (option:api.ODJsonConfig_DefaultOptionTicketType new api.ODOptionData("opendiscord:channel-prefix",option.channel.prefix), new api.ODOptionData("opendiscord:channel-suffix",option.channel.suffix), new api.ODOptionData("opendiscord:channel-category",option.channel.category), - new api.ODOptionData("opendiscord:channel-category-closed",option.channel.closedCategory), - new api.ODOptionData("opendiscord:channel-category-backup",option.channel.backupCategory), - new api.ODOptionData("opendiscord:channel-categories-claimed",option.channel.claimedCategory), new api.ODOptionData("opendiscord:channel-topic",option.channel.topic), new api.ODOptionData("opendiscord:dm-message-enabled",option.dmMessage.enabled), @@ -107,7 +100,7 @@ export const loadTicketOption = (option:api.ODJsonConfig_DefaultOptionTicketType ]) } -export const loadWebsiteOption = (opt:api.ODJsonConfig_DefaultOptionWebsiteType): api.ODWebsiteOption => { +export const loadWebsiteOption = (opt:api.ODOptionsJsonConfig_WebsiteOption): api.ODWebsiteOption => { return new api.ODWebsiteOption(opt.id,[ new api.ODOptionData("opendiscord:name",opt.name), new api.ODOptionData("opendiscord:description",opt.description), @@ -119,7 +112,7 @@ export const loadWebsiteOption = (opt:api.ODJsonConfig_DefaultOptionWebsiteType) ]) } -export const loadRoleOption = (opt:api.ODJsonConfig_DefaultOptionRoleType): api.ODRoleOption => { +export const loadRoleOption = (opt:api.ODOptionsJsonConfig_RoleOption): api.ODRoleOption => { return new api.ODRoleOption(opt.id,[ new api.ODOptionData("opendiscord:name",opt.name), new api.ODOptionData("opendiscord:description",opt.description), @@ -135,6 +128,19 @@ export const loadRoleOption = (opt:api.ODJsonConfig_DefaultOptionRoleType): api. ]) } +export const loadSubPanelOption = (opt:api.ODOptionsJsonConfig_SubPanelOption): api.ODSubPanelOption => { + return new api.ODSubPanelOption(opt.id,[ + new api.ODOptionData("opendiscord:name",opt.name), + new api.ODOptionData("opendiscord:description",opt.description), + + new api.ODOptionData("opendiscord:button-emoji",opt.button.emoji), + new api.ODOptionData("opendiscord:button-label",opt.button.label), + new api.ODOptionData("opendiscord:button-color",opt.button.color), + + new api.ODOptionData("opendiscord:panel-id",opt.subPanelId) + ]) +} + export const loadTicketOptionSuffix = (option:api.ODTicketOption): api.ODOptionSuffix => { const mode = option.get("opendiscord:channel-suffix").value const globalDatabase = opendiscord.databases.get("opendiscord:global") diff --git a/src/data/openticket/panelLoader.ts b/src/data/openticket/panelLoader.ts index 7d902cf..d1a883a 100644 --- a/src/data/openticket/panelLoader.ts +++ b/src/data/openticket/panelLoader.ts @@ -1,9 +1,9 @@ -import {opendiscord, api, utilities} from "../../index" +import {opendiscord, api, utilities} from "../../index.js" import * as discord from "discord.js" const lang = opendiscord.languages -export const loadAllPanels = async () => { +export async function loadAllPanels(){ const panelConfig = opendiscord.configs.get("opendiscord:panels") if (!panelConfig) return @@ -23,7 +23,7 @@ export const loadAllPanels = async () => { }) } -export const loadPanel = (panel:api.ODJsonConfig_DefaultPanelType) => { +export const loadPanel = (panel:api.ODPanelsJsonConfig_Panel) => { return new api.ODPanel(panel.id,[ new api.ODPanelData("opendiscord:name",panel.name), new api.ODPanelData("opendiscord:options",panel.options), @@ -33,8 +33,11 @@ export const loadPanel = (panel:api.ODJsonConfig_DefaultPanelType) => { new api.ODPanelData("opendiscord:embed",panel.embed), new api.ODPanelData("opendiscord:dropdown-placeholder",panel.settings.dropdownPlaceholder), + new api.ODPanelData("opendiscord:maximum-buttons-per-row",panel.settings.maximumButtonsPerRow), + new api.ODPanelData("opendiscord:enable-max-tickets-warning-text",panel.settings.enableMaxTicketsWarningInText), new api.ODPanelData("opendiscord:enable-max-tickets-warning-embed",panel.settings.enableMaxTicketsWarningInEmbed), + new api.ODPanelData("opendiscord:describe-options-layout",panel.settings.describeOptionsLayout), new api.ODPanelData("opendiscord:describe-options-custom-title",panel.settings.describeOptionsCustomTitle), new api.ODPanelData("opendiscord:describe-options-in-text",panel.settings.describeOptionsInText), @@ -68,11 +71,16 @@ export function describePanelOptions(mode:"fields"|"text", panel:api.ODPanel): { hasWebsite = true ticketOnly = false roleOnly = false - }else if (!dropdownMode && opt instanceof api.ODRoleOption){ + }else if (opt instanceof api.ODRoleOption){ options.push(opt) hasRole = true ticketOnly = false websiteOnly = false + }else if (opt instanceof api.ODSubPanelOption){ + options.push(opt) + ticketOnly = false + websiteOnly = false + roleOnly = false } } }) @@ -93,7 +101,7 @@ export function describePanelOptions(mode:"fields"|"text", panel:api.ODPanel): { } if (layout == "detailed"){ const optionAdmins = [...opt.get("opendiscord:admins").value] - if (generalConfig.data.system.showGlobalAdminsInPanelRoles){ + if (generalConfig.data.ticketSystem.showGlobalAdminsInPanelRoles){ for (const admin of generalConfig.data.globalAdmins){ if (!optionAdmins.includes(admin)) optionAdmins.push(admin) } @@ -127,7 +135,7 @@ export function describePanelOptions(mode:"fields"|"text", panel:api.ODPanel): { return {name:utilities.emojiTitle(emoji,name),value:description} }else{ - //auto-generated plugin option + //auto-generated option (+ sub-panels) const emoji = opt.get("opendiscord:button-emoji") as api.ODOptionData|null const name = opt.get("opendiscord:name") as api.ODOptionData|null const description = opt.get("opendiscord:description") as api.ODOptionData|null @@ -147,7 +155,7 @@ export function describePanelOptions(mode:"fields"|"text", panel:api.ODPanel): { } if (layout == "detailed"){ const optionAdmins = [...opt.get("opendiscord:admins").value] - if (generalConfig.data.system.showGlobalAdminsInPanelRoles){ + if (generalConfig.data.ticketSystem.showGlobalAdminsInPanelRoles){ for (const admin of generalConfig.data.globalAdmins){ if (!optionAdmins.includes(admin)) optionAdmins.push(admin) } @@ -181,7 +189,7 @@ export function describePanelOptions(mode:"fields"|"text", panel:api.ODPanel): { else return "**"+utilities.emojiTitle(emoji,name)+"**\n"+description }else{ - //auto-generated plugin option + //auto-generated option (+ sub-panels) const emoji = opt.get("opendiscord:button-emoji") as api.ODOptionData|null const name = opt.get("opendiscord:name") as api.ODOptionData|null const description = opt.get("opendiscord:description") as api.ODOptionData|null diff --git a/src/data/openticket/priorityLoader.ts b/src/data/openticket/priorityLoader.ts index c568fca..a61f04b 100644 --- a/src/data/openticket/priorityLoader.ts +++ b/src/data/openticket/priorityLoader.ts @@ -1,8 +1,8 @@ -import {opendiscord, api, utilities} from "../../index" +import {opendiscord, api, utilities} from "../../index.js" const lang = opendiscord.languages -export const loadAllPriorityLevels = async () => { +export async function loadAllPriorityLevels(){ opendiscord.priorities.add(new api.ODPriorityLevel("opendiscord:urgent",5,"urgent",lang.getTranslation("priorities.urgent"),"🔴","🔴")) opendiscord.priorities.add(new api.ODPriorityLevel("opendiscord:very-high",4,"very-high",lang.getTranslation("priorities.veryHigh"),"🟠","🟠")) opendiscord.priorities.add(new api.ODPriorityLevel("opendiscord:high",3,"high",lang.getTranslation("priorities.high"),"🟡","🟡")) diff --git a/src/data/openticket/questionLoader.ts b/src/data/openticket/questionLoader.ts index 5a2f0be..0f5bb15 100644 --- a/src/data/openticket/questionLoader.ts +++ b/src/data/openticket/questionLoader.ts @@ -1,16 +1,18 @@ -import {opendiscord, api, utilities} from "../../index" +import {opendiscord, api, utilities} from "../../index.js" -export const loadAllQuestions = async () => { +export async function loadAllQuestions(){ const questionConfig = opendiscord.configs.get("opendiscord:questions") if (!questionConfig) return - questionConfig.data.forEach((question) => { - if (question.type == "short"){ - opendiscord.questions.add(loadShortQuestion(question)) - }else if (question.type == "paragraph"){ - opendiscord.questions.add(loadParagraphQuestion(question)) - } - }) + for (const question of questionConfig.data){ + if (question.type === "short") opendiscord.questions.add(loadShortQuestion(question)) + else if (question.type === "paragraph") opendiscord.questions.add(loadParagraphQuestion(question)) + else if (question.type === "dropdown") opendiscord.questions.add(loadDropdownQuestion(question)) + else if (question.type === "radio-select") opendiscord.questions.add(loadRadioSelectQuestion(question)) + else if (question.type === "checkbox-select") opendiscord.questions.add(loadCheckboxSelectQuestion(question)) + else if (question.type === "file-upload") opendiscord.questions.add(loadFileUploadQuestion(question)) + else if (question.type === "text-display") opendiscord.questions.add(loadTextDisplayQuestion(question)) + } //update questions on config reload questionConfig.onReload(async () => { @@ -18,36 +20,92 @@ export const loadAllQuestions = async () => { await opendiscord.questions.loopAll((data,id) => {opendiscord.questions.remove(id)}) //add new questions - questionConfig.data.forEach((question) => { - if (question.type == "short"){ - opendiscord.questions.add(loadShortQuestion(question)) - }else if (question.type == "paragraph"){ - opendiscord.questions.add(loadParagraphQuestion(question)) - } - }) + for (const question of questionConfig.data){ + if (question.type === "short") opendiscord.questions.add(loadShortQuestion(question)) + else if (question.type === "paragraph") opendiscord.questions.add(loadParagraphQuestion(question)) + else if (question.type === "dropdown") opendiscord.questions.add(loadDropdownQuestion(question)) + else if (question.type === "radio-select") opendiscord.questions.add(loadRadioSelectQuestion(question)) + else if (question.type === "checkbox-select") opendiscord.questions.add(loadCheckboxSelectQuestion(question)) + else if (question.type === "file-upload") opendiscord.questions.add(loadFileUploadQuestion(question)) + else if (question.type === "text-display") opendiscord.questions.add(loadTextDisplayQuestion(question)) + } }) } -export const loadShortQuestion = (option:api.ODJsonConfig_DefaultShortQuestionType) => { +export const loadShortQuestion = (option:api.ODQuestionsJsonConfig_ShortQuestion) => { return new api.ODShortQuestion(option.id,[ new api.ODQuestionData("opendiscord:name",option.name), + new api.ODQuestionData("opendiscord:description",option.description), new api.ODQuestionData("opendiscord:required",option.required), + new api.ODQuestionData("opendiscord:placeholder",option.placeholder), - new api.ODQuestionData("opendiscord:length-enabled",option.length.enabled), new api.ODQuestionData("opendiscord:length-min",option.length.min), new api.ODQuestionData("opendiscord:length-max",option.length.max), ]) } -export const loadParagraphQuestion = (option:api.ODJsonConfig_DefaultParagraphQuestionType) => { +export const loadParagraphQuestion = (option:api.ODQuestionsJsonConfig_ParagraphQuestion) => { return new api.ODParagraphQuestion(option.id,[ new api.ODQuestionData("opendiscord:name",option.name), + new api.ODQuestionData("opendiscord:description",option.description), new api.ODQuestionData("opendiscord:required",option.required), + new api.ODQuestionData("opendiscord:placeholder",option.placeholder), - new api.ODQuestionData("opendiscord:length-enabled",option.length.enabled), new api.ODQuestionData("opendiscord:length-min",option.length.min), new api.ODQuestionData("opendiscord:length-max",option.length.max), ]) +} + +export const loadDropdownQuestion = (option:api.ODQuestionsJsonConfig_DropdownQuestion) => { + return new api.ODDropdownQuestion(option.id,[ + new api.ODQuestionData("opendiscord:name",option.name), + new api.ODQuestionData("opendiscord:description",option.description), + new api.ODQuestionData("opendiscord:required",option.required), + + new api.ODQuestionData("opendiscord:placeholder",option.placeholder), + new api.ODQuestionData("opendiscord:choices",option.choices) + ]) +} + +export const loadRadioSelectQuestion = (option:api.ODQuestionsJsonConfig_RadioSelectQuestion) => { + return new api.ODRadioSelectQuestion(option.id,[ + new api.ODQuestionData("opendiscord:name",option.name), + new api.ODQuestionData("opendiscord:description",option.description), + new api.ODQuestionData("opendiscord:required",option.required), + + new api.ODQuestionData("opendiscord:choices",option.choices) + ]) +} + +export const loadCheckboxSelectQuestion = (option:api.ODQuestionsJsonConfig_CheckboxSelectQuestion) => { + return new api.ODCheckboxSelectQuestion(option.id,[ + new api.ODQuestionData("opendiscord:name",option.name), + new api.ODQuestionData("opendiscord:description",option.description), + new api.ODQuestionData("opendiscord:required",option.required), + + new api.ODQuestionData("opendiscord:limits-enabled",option.limits.enabled), + new api.ODQuestionData("opendiscord:limits-min",option.limits.min), + new api.ODQuestionData("opendiscord:limits-max",option.limits.max), + new api.ODQuestionData("opendiscord:choices",option.choices) + ]) +} + +export const loadFileUploadQuestion = (option:api.ODQuestionsJsonConfig_FileUploadQuestion) => { + return new api.ODFileUploadQuestion(option.id,[ + new api.ODQuestionData("opendiscord:name",option.name), + new api.ODQuestionData("opendiscord:description",option.description), + new api.ODQuestionData("opendiscord:required",option.required), + + new api.ODQuestionData("opendiscord:limits-enabled",option.limits.enabled), + new api.ODQuestionData("opendiscord:limits-min",option.limits.min), + new api.ODQuestionData("opendiscord:limits-max",option.limits.max), + ]) +} + +export const loadTextDisplayQuestion = (option:api.ODQuestionsJsonConfig_TextDisplayQuestion) => { + return new api.ODTextDisplayQuestion(option.id,[ + new api.ODQuestionData("opendiscord:text-contents",option.textContents) + ]) } \ No newline at end of file diff --git a/src/data/openticket/roleLoader.ts b/src/data/openticket/roleLoader.ts index 1db2c7a..5205e32 100644 --- a/src/data/openticket/roleLoader.ts +++ b/src/data/openticket/roleLoader.ts @@ -1,6 +1,6 @@ -import {opendiscord, api, utilities} from "../../index" +import {opendiscord, api, utilities} from "../../index.js" -export const loadAllRoles = async () => { +export async function loadAllRoles(){ await opendiscord.options.loopAll((opt) => { if (opt instanceof api.ODRoleOption){ opendiscord.roles.add(loadRole(opt)) diff --git a/src/data/openticket/ticketLoader.ts b/src/data/openticket/ticketLoader.ts index 86b88ca..0d1dfa6 100644 --- a/src/data/openticket/ticketLoader.ts +++ b/src/data/openticket/ticketLoader.ts @@ -1,8 +1,8 @@ -import {opendiscord, api, utilities} from "../../index" +import {opendiscord, api, utilities} from "../../index.js" const optionDatabase = opendiscord.databases.get("opendiscord:options") -export const loadAllTickets = async () => { +export async function loadAllTickets(){ const ticketDatabase = opendiscord.databases.get("opendiscord:tickets") if (!ticketDatabase) return @@ -11,7 +11,7 @@ export const loadAllTickets = async () => { for (const ticket of tickets){ try { opendiscord.tickets.add(await loadTicket(ticket.value)) - }catch (err){ + }catch (err:any){ process.emit("uncaughtException",err) process.emit("uncaughtException",new api.ODSystemError("Failed to load ticket from database! => id: "+ticket.key+"\n ===> "+err)) } diff --git a/src/data/openticket/transcriptLoader.ts b/src/data/openticket/transcriptLoader.ts index 6d6afd2..adc3e22 100644 --- a/src/data/openticket/transcriptLoader.ts +++ b/src/data/openticket/transcriptLoader.ts @@ -1,4 +1,4 @@ -import {opendiscord, api, utilities} from "../../index" +import {opendiscord, api, utilities} from "../../index.js" import * as discord from "discord.js" const collector = opendiscord.transcripts.collector @@ -46,10 +46,10 @@ function transcriptAuth(_A:{salt:number,secret:string}){ const _B = Buffer.from(_A.secret,"hex");const _C = Math["floor"](new Date().getTime()/(30*1000)).toString();const _D = Buffer.from(_C);const _E = Buffer.alloc(_D["length"]);_D.forEach((v,i) => {_E[i] = v ^ _B[i % _B["length"]];});return btoa(JSON.stringify({salt:_A.salt,secret:_A.secret,token:_E.toString("hex")})) } -export const loadAllTranscriptCompilers = async () => { +export async function loadAllTranscriptCompilers(){ class ODHTTPHtmlPostRequest extends api.ODHTTPPostRequest { constructor(transcriptAuth:string,htmlFinal:api.ODTranscriptHtmlV2Data){ - super("https://"+htmlDomain+"/api/v2/upload?auth="+htmlVersion+"&token="+transcriptAuth,true,{ + super(opendiscord,"https://"+htmlDomain+"/api/v2/upload?auth="+htmlVersion+"&token="+transcriptAuth,true,{ body:JSON.stringify(htmlFinal), headers:{ "Content-Type":"application/json" @@ -178,7 +178,7 @@ export const loadAllTranscriptCompilers = async () => { //HTML COMPILER opendiscord.transcripts.add(new api.ODTranscriptCompiler<{url:string,availableUntil:Date},{auth:string}|null>("opendiscord:html-compiler",async (ticket,channel,user) => { //INIT - const req = new api.ODHTTPGetRequest(atob("aHR0cHM6Ly90LmRqLWRqLmJlL2FwaS92Mi9pbml0"),false) + const req = new api.ODHTTPGetRequest(opendiscord,atob("aHR0cHM6Ly90LmRqLWRqLmJlL2FwaS92Mi9pbml0"),false) const res = await req.run() //PENDING MESSAGE (not required anymore) => await messages.getSafe("opendiscord:transcript-html-progress").build("channel",{guild:channel.guild,channel,user,ticket,compiler:opendiscord.transcripts.get("opendiscord:html-compiler"),remaining:16000}) if (res.status == 200 && res.body){ @@ -552,6 +552,6 @@ export const loadAllTranscriptCompilers = async () => { })) } -export const loadTranscriptHistory = async () => { +export async function loadTranscriptHistory(){ //UNIMPLEMENTED (made for html transcripts v3 update) } \ No newline at end of file diff --git a/src/index.ts b/src/index.ts index 5a067b3..8d5f8b0 100644 --- a/src/index.ts +++ b/src/index.ts @@ -20,7 +20,7 @@ INFORMATION: ============ - Open Ticket v4.1.3 - © DJdj Development + Open Ticket v4.2.0 - © DJdj Development support us: https://github.com/sponsors/DJj123dj discord: https://discord.dj-dj.be @@ -35,59 +35,49 @@ */ //initialize API & check npm libraries -import { api, opendiscord, utilities } from "./core/startup/init" -export { api, opendiscord, utilities } from "./core/startup/init" +import { loadDumpCommand, loadAllPlugins, loadErrorHandling } from "@open-discord-bots/framework" +import * as utilities from "@open-discord-bots/framework/utilities" +import * as api from "./core/api.js" import ansis from "ansis" +export * as utilities from "@open-discord-bots/framework/utilities" +export * as api from "./core/api.js" + +utilities.checkNodeVersion("openticket") + +utilities.moduleInstalled("@open-discord-bots/framework",true) +utilities.moduleInstalled("@discordjs/rest",true) +utilities.moduleInstalled("discord.js",true) +utilities.moduleInstalled("ansis",true) +utilities.moduleInstalled("formatted-json-stringify",true) +utilities.moduleInstalled("typescript",true) +utilities.moduleInstalled("terminal-kit",true) + +export const opendiscord: api.ODOpenTicketMain = new api.ODOpenTicketMain() +export * as openticketUtils from "./actions/utilities.js" + +utilities.initialStartupLogs(opendiscord,"openticket") + /**The main sequence of Open Ticket. Runs `async` */ const main = async () => { //load all events (await import("./data/framework/eventLoader.js")).loadAllEvents() //error handling system - process.on("uncaughtException",async (error,origin) => { - try{ - await opendiscord.events.get("onErrorHandling").emit([error,origin]) - if (opendiscord.defaults.getDefault("errorHandling")){ - //custom error messages for known errors - if (error.message.toLowerCase().includes("used disallowed intents")){ - //invalid intents - opendiscord.log("Open Ticket doesn't work without Privileged Gateway Intents enabled!","error") - opendiscord.log("Enable them in the discord developer portal!","info") - console.log("\n") - process.exit(1) - }else if (error.message.toLowerCase().includes("invalid discord bot token provided")){ - //invalid token - opendiscord.log("An invalid discord auth token was provided!","error") - opendiscord.log("Check the config if you have inserted the bot token correctly!","info") - console.log("\n") - process.exit(1) - }else{ - //unknown error - const errmsg = new api.ODError(error,origin) - opendiscord.log(errmsg) - if (opendiscord.defaults.getDefault("crashOnError")) process.exit(1) - await opendiscord.events.get("afterErrorHandling").emit([error,origin,errmsg]) - } - } - - }catch(err){ - console.log("[ERROR HANDLER ERROR]:",err) - } - }) + loadErrorHandling(opendiscord,"openticket") //handle data migration (PART 1) const lastVersion = await (await import("./core/startup/manageMigration.js")).loadVersionMigrationSystem() //load plugins - if (opendiscord.defaults.getDefault("pluginLoading")){ - await (await import("./core/startup/pluginLauncher.js")).loadAllPlugins() + if (opendiscord.sharedFuses.getFuse("pluginLoading")){ + await loadAllPlugins(opendiscord) } await opendiscord.events.get("afterPluginsLoaded").emit([opendiscord.plugins]) //load plugin classes opendiscord.log("Loading plugin classes...","system") - if (opendiscord.defaults.getDefault("pluginClassLoading")){ + if (opendiscord.sharedFuses.getFuse("pluginClassLoading")){ } await opendiscord.events.get("onPluginClassLoad").emit([opendiscord.plugins.classes,opendiscord.plugins]) @@ -95,7 +85,7 @@ const main = async () => { //load flags opendiscord.log("Loading flags...","system") - if (opendiscord.defaults.getDefault("flagLoading")){ + if (opendiscord.sharedFuses.getFuse("flagLoading")){ await (await import("./data/framework/flagLoader.js")).loadAllFlags() } await opendiscord.events.get("onFlagLoad").emit([opendiscord.flags]) @@ -103,20 +93,20 @@ const main = async () => { //initiate flags await opendiscord.events.get("onFlagInit").emit([opendiscord.flags]) - if (opendiscord.defaults.getDefault("flagInitiating")){ + if (opendiscord.sharedFuses.getFuse("flagInitiating")){ await opendiscord.flags.init() opendiscord.debugfile.writeText("\n[ENABLED FLAGS]:\n"+opendiscord.flags.getFiltered((flag) => (flag.value == true)).map((flag) => flag.id.value).join("\n")+"\n") await opendiscord.events.get("afterFlagsInitiated").emit([opendiscord.flags]) } //load debug - if (opendiscord.defaults.getDefault("debugLoading")){ + if (opendiscord.sharedFuses.getFuse("debugLoading")){ const debugFlag = opendiscord.flags.get("opendiscord:debug") opendiscord.debug.visible = (debugFlag) ? debugFlag.value : false } //load silent mode - if (opendiscord.defaults.getDefault("silentLoading")){ + if (opendiscord.sharedFuses.getFuse("silentLoading")){ const silentFlag = opendiscord.flags.get("opendiscord:silent") opendiscord.console.silent = (silentFlag) ? silentFlag.value : false if (opendiscord.console.silent){ @@ -128,14 +118,14 @@ const main = async () => { //load progress bar renderers opendiscord.log("Loading progress bars...","system") - if (opendiscord.defaults.getDefault("progressBarRendererLoading")){ + if (opendiscord.sharedFuses.getFuse("progressBarRendererLoading")){ await (await import("./data/framework/progressBarLoader.js")).loadAllProgressBarRenderers() } await opendiscord.events.get("onProgressBarRendererLoad").emit([opendiscord.progressbars.renderers]) await opendiscord.events.get("afterProgressBarRenderersLoaded").emit([opendiscord.progressbars.renderers]) //load progress bars - if (opendiscord.defaults.getDefault("progressBarLoading")){ + if (opendiscord.sharedFuses.getFuse("progressBarLoading")){ await (await import("./data/framework/progressBarLoader.js")).loadAllProgressBars() } await opendiscord.events.get("onProgressBarLoad").emit([opendiscord.progressbars]) @@ -143,7 +133,7 @@ const main = async () => { //load config opendiscord.log("Loading configs...","system") - if (opendiscord.defaults.getDefault("configLoading")){ + if (opendiscord.sharedFuses.getFuse("configLoading")){ await (await import("./data/framework/configLoader.js")).loadAllConfigs() } await opendiscord.events.get("onConfigLoad").emit([opendiscord.configs]) @@ -151,7 +141,7 @@ const main = async () => { //initiate config await opendiscord.events.get("onConfigInit").emit([opendiscord.configs]) - if (opendiscord.defaults.getDefault("configInitiating")){ + if (opendiscord.sharedFuses.getFuse("configInitiating")){ await opendiscord.configs.init() await opendiscord.events.get("afterConfigsInitiated").emit([opendiscord.configs]) } @@ -159,14 +149,15 @@ const main = async () => { //UTILITY CONFIG const generalConfig = opendiscord.configs.get("opendiscord:general") - if (opendiscord.defaults.getDefault("emojiTitleStyleLoading")){ + if (opendiscord.sharedFuses.getFuse("emojiTitleStyleLoading")){ //set emoji style based on config - opendiscord.defaults.setDefault("emojiTitleStyle",generalConfig.data.system.emojiStyle) + const emojiStyle = (generalConfig.data && generalConfig.data.ticketSystem && generalConfig.data.ticketSystem.emojiStyle) ? generalConfig.data.ticketSystem.emojiStyle : "before" + opendiscord.sharedFuses.setFuse("emojiTitleStyle",emojiStyle) } //load database opendiscord.log("Loading databases...","system") - if (opendiscord.defaults.getDefault("databaseLoading")){ + if (opendiscord.sharedFuses.getFuse("databaseLoading")){ await (await import("./data/framework/databaseLoader.js")).loadAllDatabases() } await opendiscord.events.get("onDatabaseLoad").emit([opendiscord.databases]) @@ -174,14 +165,14 @@ const main = async () => { //initiate database await opendiscord.events.get("onDatabaseInit").emit([opendiscord.databases]) - if (opendiscord.defaults.getDefault("databaseInitiating")){ + if (opendiscord.sharedFuses.getFuse("databaseInitiating")){ await opendiscord.databases.init() await opendiscord.events.get("afterDatabasesInitiated").emit([opendiscord.databases]) } //load sessions opendiscord.log("Loading sessions...","system") - if (opendiscord.defaults.getDefault("sessionLoading")){ + if (opendiscord.sharedFuses.getFuse("sessionLoading")){ } await opendiscord.events.get("onSessionLoad").emit([opendiscord.sessions]) @@ -189,7 +180,7 @@ const main = async () => { //load language opendiscord.log("Loading languages...","system") - if (opendiscord.defaults.getDefault("languageLoading")){ + if (opendiscord.sharedFuses.getFuse("languageLoading")){ await (await import("./data/framework/languageLoader.js")).loadAllLanguages() } await opendiscord.events.get("onLanguageLoad").emit([opendiscord.languages]) @@ -197,12 +188,12 @@ const main = async () => { //initiate language await opendiscord.events.get("onLanguageInit").emit([opendiscord.languages]) - if (opendiscord.defaults.getDefault("languageInitiating")){ + if (opendiscord.sharedFuses.getFuse("languageInitiating")){ await opendiscord.languages.init() await opendiscord.events.get("afterLanguagesInitiated").emit([opendiscord.languages]) //add available languages to list for config checker - const languageList = opendiscord.defaults.getDefault("languageList") + const languageList = opendiscord.sharedFuses.getFuse("languageList") const languageIds = opendiscord.languages.getIds().map((id) => { if (id.value.startsWith("opendiscord:")){ //is open ticket language => return without prefix @@ -210,12 +201,12 @@ const main = async () => { }else return id.value }) languageList.push(...languageIds) - opendiscord.defaults.setDefault("languageList",languageList) + opendiscord.sharedFuses.setFuse("languageList",languageList) } //select language await opendiscord.events.get("onLanguageSelect").emit([opendiscord.languages]) - if (opendiscord.defaults.getDefault("languageSelection")){ + if (opendiscord.sharedFuses.getFuse("languageSelection")){ //set current language const languageId = (generalConfig?.data?.language) ? generalConfig.data.language : "english" if (languageId.includes(":")){ @@ -225,7 +216,7 @@ const main = async () => { } //set backup language - const backupLanguageId = opendiscord.defaults.getDefault("backupLanguage") + const backupLanguageId = opendiscord.sharedFuses.getFuse("backupLanguage") if (opendiscord.languages.exists(backupLanguageId)){ opendiscord.languages.setBackupLanguage(backupLanguageId) @@ -235,18 +226,18 @@ const main = async () => { } //handle data migration (PART 2) - if (lastVersion) await (await import("./core/startup/manageMigration.js")).loadAllAfterInitVersionMigrations(lastVersion) + if (lastVersion) await (await import("./core/startup/manageMigration.js")).loadAfterStartupMigrations(lastVersion) //load config checker opendiscord.log("Loading config checker...","system") - if (opendiscord.defaults.getDefault("checkerLoading")){ + if (opendiscord.sharedFuses.getFuse("checkerLoading")){ await (await import("./data/framework/checkerLoader.js")).loadAllConfigCheckers() } await opendiscord.events.get("onCheckerLoad").emit([opendiscord.checkers]) await opendiscord.events.get("afterCheckersLoaded").emit([opendiscord.checkers]) //load config checker functions - if (opendiscord.defaults.getDefault("checkerFunctionLoading")){ + if (opendiscord.sharedFuses.getFuse("checkerFunctionLoading")){ await (await import("./data/framework/checkerLoader.js")).loadAllConfigCheckerFunctions() } await opendiscord.events.get("onCheckerFunctionLoad").emit([opendiscord.checkers.functions,opendiscord.checkers]) @@ -254,16 +245,16 @@ const main = async () => { //execute config checker await opendiscord.events.get("onCheckerExecute").emit([opendiscord.checkers]) - if (opendiscord.defaults.getDefault("checkerExecution")){ + if (opendiscord.sharedFuses.getFuse("checkerExecution")){ const result = opendiscord.checkers.checkAll(true) await opendiscord.events.get("afterCheckersExecuted").emit([result,opendiscord.checkers]) } //load config checker translations - if (opendiscord.defaults.getDefault("checkerTranslationLoading")){ + if (opendiscord.sharedFuses.getFuse("checkerTranslationLoading")){ await (await import("./data/framework/checkerLoader.js")).loadAllConfigCheckerTranslations() } - await opendiscord.events.get("onCheckerTranslationLoad").emit([opendiscord.checkers.translation,((generalConfig && generalConfig.data.system && generalConfig.data.system.useTranslatedConfigChecker) ? generalConfig.data.system.useTranslatedConfigChecker : false),opendiscord.checkers]) + await opendiscord.events.get("onCheckerTranslationLoad").emit([opendiscord.checkers.translation,((generalConfig && generalConfig.data.ticketSystem && generalConfig.data.ticketSystem.useTranslatedConfigChecker) ? generalConfig.data.ticketSystem.useTranslatedConfigChecker : false),opendiscord.checkers]) await opendiscord.events.get("afterCheckerTranslationsLoaded").emit([opendiscord.checkers.translation,opendiscord.checkers]) //render config checker @@ -272,13 +263,13 @@ const main = async () => { const useCliFlag = opendiscord.flags.get("opendiscord:cli") await opendiscord.events.get("onCheckerRender").emit([opendiscord.checkers.renderer,opendiscord.checkers]) - if (opendiscord.defaults.getDefault("checkerRendering") && !(disableCheckerFlag ? disableCheckerFlag.value : false) && !(useCliFlag ? useCliFlag.value : false)){ + if (opendiscord.sharedFuses.getFuse("checkerRendering") && !(disableCheckerFlag ? disableCheckerFlag.value : false) && !(useCliFlag ? useCliFlag.value : false)){ //check if there is a result (otherwise throw minor error) const result = opendiscord.checkers.lastResult if (!result) return opendiscord.log("Failed to render Config Checker! (couldn't fetch result)","error") //get components & check if full mode enabled - const components = opendiscord.checkers.renderer.getComponents(!(advancedCheckerFlag ? advancedCheckerFlag.value : false),opendiscord.defaults.getDefault("checkerRenderEmpty"),opendiscord.checkers.translation,result) + const components = opendiscord.checkers.renderer.getComponents(!(advancedCheckerFlag ? advancedCheckerFlag.value : false),opendiscord.sharedFuses.getFuse("checkerRenderEmpty"),opendiscord.checkers.translation,result) //render opendiscord.debugfile.writeText("\n[CONFIG CHECKER RESULT]:\n"+ansis.strip(components.join("\n"))+"\n") @@ -293,7 +284,7 @@ const main = async () => { //quit config checker (when required) if (opendiscord.checkers.lastResult && !opendiscord.checkers.lastResult.valid && !(disableCheckerFlag ? disableCheckerFlag.value : false) && !(useCliFlag ? useCliFlag.value : false)){ await opendiscord.events.get("onCheckerQuit").emit([opendiscord.checkers]) - if (opendiscord.defaults.getDefault("checkerQuit")){ + if (opendiscord.sharedFuses.getFuse("checkerQuit")){ process.exit(1) //there is no afterCheckerQuitted event :) } @@ -313,7 +304,7 @@ const main = async () => { //client configuration opendiscord.log("Loading client...","system") - if (opendiscord.defaults.getDefault("clientLoading")){ + if (opendiscord.sharedFuses.getFuse("clientLoading")){ //add intents (for basic permissions) opendiscord.client.intents.push( "Guilds", @@ -369,7 +360,7 @@ const main = async () => { opendiscord.client.readyListener = async () => { opendiscord.log("Loading client setup...","system") await opendiscord.events.get("onClientReady").emit([opendiscord.client]) - if (opendiscord.defaults.getDefault("clientReady")){ + if (opendiscord.sharedFuses.getFuse("clientReady")){ const client = opendiscord.client //check if all servers are valid @@ -391,26 +382,27 @@ const main = async () => { //throw if bot doesn't have permissions in main server if (!client.checkGuildPerms(mainServer)){ console.log("\n") - opendiscord.log("The bot doesn't have the correct permissions in the server provided in the config!","error") + opendiscord.log("The bot doesn’t have the required permissions for the server specified in the configuration.","error") opendiscord.log("Please give the bot \"Administrator\" permissions or visit the documentation!","info") console.log("\n") process.exit(1) } - if (opendiscord.defaults.getDefault("clientMultiGuildWarning")){ + if (opendiscord.sharedFuses.getFuse("clientMultiGuildWarning")){ //warn if bot is in multiple servers if (botServers.length > 1){ - opendiscord.log("This bot is part of multiple servers, but Open Ticket doesn't provide support for this!","warning") - opendiscord.log("As a result, the bot may crash & glitch when used in the additional servers!","info") + opendiscord.log("This bot is part of multiple servers, but Open Ticket does not support this.","warning") + opendiscord.log("The bot may have weird behaviour when used in external servers!","info") + await utilities.timer(2000) } botServers.forEach((server) => { //warn if bot doesn't have permissions in multiple servers - if (!client.checkGuildPerms(server)) opendiscord.log(`The bot doesn't have the correct permissions in the server "${server.name}"!`,"warning") + if (!client.checkGuildPerms(server)) opendiscord.log(`The bot doesn’t have the required permissions for the server "${server.name}".`,"warning") }) } //load client activity opendiscord.log("Loading client activity...","system") - if (opendiscord.defaults.getDefault("clientActivityLoading")){ + if (opendiscord.sharedFuses.getFuse("clientActivityLoading")){ //load config status if (generalConfig.data.status && generalConfig.data.status.enabled) opendiscord.client.activity.setStatus(generalConfig.data.status.type,generalConfig.data.status.text,generalConfig.data.status.mode,generalConfig.data.status.state) } @@ -419,14 +411,14 @@ const main = async () => { //initiate client activity await opendiscord.events.get("onClientActivityInit").emit([opendiscord.client.activity,opendiscord.client]) - if (opendiscord.defaults.getDefault("clientActivityInitiating")){ + if (opendiscord.sharedFuses.getFuse("clientActivityInitiating")){ opendiscord.client.activity.initStatus() await opendiscord.events.get("afterClientActivityInitiated").emit([opendiscord.client.activity,opendiscord.client]) } //load priority levels opendiscord.log("Loading prioritiy levels...","system") - if (opendiscord.defaults.getDefault("priorityLoading")){ + if (opendiscord.fuses.getFuse("priorityLoading")){ await (await import("./data/openticket/priorityLoader.js")).loadAllPriorityLevels() } await opendiscord.events.get("onPriorityLoad").emit([opendiscord.priorities]) @@ -434,22 +426,22 @@ const main = async () => { //load slash commands opendiscord.log("Loading slash commands...","system") - if (opendiscord.defaults.getDefault("slashCommandLoading")){ + if (opendiscord.sharedFuses.getFuse("slashCommandLoading")){ await (await import("./data/framework/commandLoader.js")).loadAllSlashCommands() } await opendiscord.events.get("onSlashCommandLoad").emit([opendiscord.client.slashCommands,opendiscord.client]) await opendiscord.events.get("afterSlashCommandsLoaded").emit([opendiscord.client.slashCommands,opendiscord.client]) //register slash commands (create, update & remove) - if (opendiscord.defaults.getDefault("forceSlashCommandRegistration")) opendiscord.log("Forcing all slash commands to be re-registered...","system") + if (opendiscord.sharedFuses.getFuse("forceSlashCommandRegistration")) opendiscord.log("Forcing all slash commands to be re-registered...","system") opendiscord.log("Registering slash commands... (this can take up to 2 minutes)","system") await opendiscord.events.get("onSlashCommandRegister").emit([opendiscord.client.slashCommands,opendiscord.client]) - if (opendiscord.defaults.getDefault("slashCommandRegistering")){ + if (opendiscord.sharedFuses.getFuse("slashCommandRegistering")){ //get all commands that are already registered in the bot const cmds = await opendiscord.client.slashCommands.getAllRegisteredCommands() const removableCmds = cmds.unused.map((cmd) => cmd.cmd) const newCmds = cmds.unregistered.map((cmd) => cmd.instance) - const updatableCmds = cmds.registered.filter((cmd) => cmd.requiresUpdate || opendiscord.defaults.getDefault("forceSlashCommandRegistration")).map((cmd) => cmd.instance) + const updatableCmds = cmds.registered.filter((cmd) => cmd.requiresUpdate || opendiscord.sharedFuses.getFuse("forceSlashCommandRegistration")).map((cmd) => cmd.instance) //init progress bars const removeProgress = opendiscord.progressbars.get("opendiscord:slash-command-remove") @@ -457,7 +449,7 @@ const main = async () => { const updateProgress = opendiscord.progressbars.get("opendiscord:slash-command-update") //remove unused cmds, create new cmds & update existing cmds - if (opendiscord.defaults.getDefault("allowSlashCommandRemoval")) await opendiscord.client.slashCommands.removeUnusedCommands(removableCmds,undefined,removeProgress) + if (opendiscord.sharedFuses.getFuse("allowSlashCommandRemoval")) await opendiscord.client.slashCommands.removeUnusedCommands(removableCmds,undefined,removeProgress) await opendiscord.client.slashCommands.createNewCommands(newCmds,createProgress) await opendiscord.client.slashCommands.updateExistingCommands(updatableCmds,updateProgress) @@ -466,22 +458,22 @@ const main = async () => { //load context menus opendiscord.log("Loading context menus...","system") - if (opendiscord.defaults.getDefault("contextMenuLoading")){ + if (opendiscord.sharedFuses.getFuse("contextMenuLoading")){ await (await import("./data/framework/commandLoader.js")).loadAllContextMenus() } await opendiscord.events.get("onContextMenuLoad").emit([opendiscord.client.contextMenus,opendiscord.client]) await opendiscord.events.get("afterContextMenusLoaded").emit([opendiscord.client.contextMenus,opendiscord.client]) //register context menus (create, update & remove) - if (opendiscord.defaults.getDefault("forceContextMenuRegistration")) opendiscord.log("Forcing all context menus to be re-registered...","system") + if (opendiscord.sharedFuses.getFuse("forceContextMenuRegistration")) opendiscord.log("Forcing all context menus to be re-registered...","system") opendiscord.log("Registering context menus... (this can take up to a minute)","system") await opendiscord.events.get("onContextMenuRegister").emit([opendiscord.client.contextMenus,opendiscord.client]) - if (opendiscord.defaults.getDefault("contextMenuRegistering")){ + if (opendiscord.sharedFuses.getFuse("contextMenuRegistering")){ //get all context menus that are already registered in the bot const menus = await opendiscord.client.contextMenus.getAllRegisteredMenus() const removableMenus = menus.unused.map((menu) => menu.menu) const newMenus = menus.unregistered.map((menu) => menu.instance) - const updatableMenus = menus.registered.filter((menu) => menu.requiresUpdate || opendiscord.defaults.getDefault("forceContextMenuRegistration")).map((menu) => menu.instance) + const updatableMenus = menus.registered.filter((menu) => menu.requiresUpdate || opendiscord.sharedFuses.getFuse("forceContextMenuRegistration")).map((menu) => menu.instance) //init progress bars const removeProgress = opendiscord.progressbars.get("opendiscord:context-menu-remove") @@ -489,7 +481,7 @@ const main = async () => { const updateProgress = opendiscord.progressbars.get("opendiscord:context-menu-update") //remove unused menus, create new menus & update existing menus - if (opendiscord.defaults.getDefault("allowContextMenuRemoval")) await opendiscord.client.contextMenus.removeUnusedMenus(removableMenus,undefined,removeProgress) + if (opendiscord.sharedFuses.getFuse("allowContextMenuRemoval")) await opendiscord.client.contextMenus.removeUnusedMenus(removableMenus,undefined,removeProgress) await opendiscord.client.contextMenus.createNewMenus(newMenus,createProgress) await opendiscord.client.contextMenus.updateExistingMenus(updatableMenus,updateProgress) @@ -498,10 +490,10 @@ const main = async () => { //load text commands opendiscord.log("Loading text commands...","system") - if (opendiscord.defaults.getDefault("allowDumpCommand")){ - (await import("./core/startup/dump.js")).loadDumpCommand() + if (opendiscord.sharedFuses.getFuse("allowDumpCommand")){ + loadDumpCommand(opendiscord) } - if (opendiscord.defaults.getDefault("textCommandLoading")){ + if (opendiscord.sharedFuses.getFuse("textCommandLoading")){ await (await import("./data/framework/commandLoader.js")).loadAllTextCommands() } await opendiscord.events.get("onTextCommandLoad").emit([opendiscord.client.textCommands,opendiscord.client]) @@ -515,7 +507,7 @@ const main = async () => { //client init (login) opendiscord.log("Logging in...","system") await opendiscord.events.get("onClientInit").emit([opendiscord.client]) - if (opendiscord.defaults.getDefault("clientInitiating")){ + if (opendiscord.sharedFuses.getFuse("clientInitiating")){ //init client opendiscord.client.initClient() await opendiscord.events.get("afterClientInitiated").emit([opendiscord.client]) @@ -525,13 +517,30 @@ const main = async () => { opendiscord.log("discord.js client ready!","info") } + //load states + opendiscord.log("Loading states...","system") + if (opendiscord.sharedFuses.getFuse("stateLoading")){ + await (await import("./data/framework/stateLoader.js")).loadAllStates() + } + await opendiscord.events.get("onStateLoad").emit([opendiscord.states]) + await opendiscord.events.get("afterStatesLoaded").emit([opendiscord.states]) + + //init states (async to prevent blocking startup) + opendiscord.log("Initiating states... (may take a while for many tickets)","system") + await opendiscord.events.get("onStateInit").emit([opendiscord.states]) + if (opendiscord.sharedFuses.getFuse("stateInitiating")){ + await opendiscord.states.init() + await opendiscord.events.get("afterStatesInitiated").emit([opendiscord.states]) + } + opendiscord.log("Message states ready!","info") + //plugin loading before managers await opendiscord.events.get("onPluginBeforeManagerLoad").emit([]) await opendiscord.events.get("afterPluginBeforeManagerLoaded").emit([]) //load questions opendiscord.log("Loading questions...","system") - if (opendiscord.defaults.getDefault("questionLoading")){ + if (opendiscord.fuses.getFuse("questionLoading")){ await (await import("./data/openticket/questionLoader.js")).loadAllQuestions() } await opendiscord.events.get("onQuestionLoad").emit([opendiscord.questions]) @@ -539,7 +548,7 @@ const main = async () => { //load options opendiscord.log("Loading options...","system") - if (opendiscord.defaults.getDefault("optionLoading")){ + if (opendiscord.fuses.getFuse("optionLoading")){ await (await import("./data/openticket/optionLoader.js")).loadAllOptions() } await opendiscord.events.get("onOptionLoad").emit([opendiscord.options]) @@ -547,7 +556,7 @@ const main = async () => { //load panels opendiscord.log("Loading panels...","system") - if (opendiscord.defaults.getDefault("panelLoading")){ + if (opendiscord.fuses.getFuse("panelLoading")){ await (await import("./data/openticket/panelLoader.js")).loadAllPanels() } await opendiscord.events.get("onPanelLoad").emit([opendiscord.panels]) @@ -555,7 +564,7 @@ const main = async () => { //load tickets opendiscord.log("Loading tickets...","system") - if (opendiscord.defaults.getDefault("ticketLoading")){ + if (opendiscord.fuses.getFuse("ticketLoading")){ opendiscord.tickets.useGuild(opendiscord.client.mainServer) await (await import("./data/openticket/ticketLoader.js")).loadAllTickets() } @@ -564,7 +573,7 @@ const main = async () => { //load roles opendiscord.log("Loading roles...","system") - if (opendiscord.defaults.getDefault("roleLoading")){ + if (opendiscord.fuses.getFuse("roleLoading")){ await (await import("./data/openticket/roleLoader.js")).loadAllRoles() } await opendiscord.events.get("onRoleLoad").emit([opendiscord.roles]) @@ -572,7 +581,7 @@ const main = async () => { //load blacklist opendiscord.log("Loading blacklist...","system") - if (opendiscord.defaults.getDefault("blacklistLoading")){ + if (opendiscord.fuses.getFuse("blacklistLoading")){ await (await import("./data/openticket/blacklistLoader.js")).loadAllBlacklistedUsers() } await opendiscord.events.get("onBlacklistLoad").emit([opendiscord.blacklist]) @@ -580,14 +589,14 @@ const main = async () => { //load transcript compilers opendiscord.log("Loading transcripts...","system") - if (opendiscord.defaults.getDefault("transcriptCompilerLoading")){ + if (opendiscord.fuses.getFuse("transcriptCompilerLoading")){ await (await import("./data/openticket/transcriptLoader.js")).loadAllTranscriptCompilers() } await opendiscord.events.get("onTranscriptCompilerLoad").emit([opendiscord.transcripts]) await opendiscord.events.get("afterTranscriptCompilersLoaded").emit([opendiscord.transcripts]) //load transcript history - if (opendiscord.defaults.getDefault("transcriptHistoryLoading")){ + if (opendiscord.fuses.getFuse("transcriptHistoryLoading")){ await (await import("./data/openticket/transcriptLoader.js")).loadTranscriptHistory() } await opendiscord.events.get("onTranscriptHistoryLoad").emit([opendiscord.transcripts]) @@ -599,7 +608,7 @@ const main = async () => { //load button builders opendiscord.log("Loading buttons...","system") - if (opendiscord.defaults.getDefault("buttonBuildersLoading")){ + if (opendiscord.sharedFuses.getFuse("buttonBuildersLoading")){ await (await import("./builders/buttons.js")).registerAllButtons() } await opendiscord.events.get("onButtonBuilderLoad").emit([opendiscord.builders.buttons,opendiscord.builders,opendiscord.actions]) @@ -607,7 +616,7 @@ const main = async () => { //load dropdown builders opendiscord.log("Loading dropdowns...","system") - if (opendiscord.defaults.getDefault("dropdownBuildersLoading")){ + if (opendiscord.sharedFuses.getFuse("dropdownBuildersLoading")){ await (await import("./builders/dropdowns.js")).registerAllDropdowns() } await opendiscord.events.get("onDropdownBuilderLoad").emit([opendiscord.builders.dropdowns,opendiscord.builders,opendiscord.actions]) @@ -615,7 +624,7 @@ const main = async () => { //load file builders opendiscord.log("Loading files...","system") - if (opendiscord.defaults.getDefault("fileBuildersLoading")){ + if (opendiscord.sharedFuses.getFuse("fileBuildersLoading")){ await (await import("./builders/files.js")).registerAllFiles() } await opendiscord.events.get("onFileBuilderLoad").emit([opendiscord.builders.files,opendiscord.builders,opendiscord.actions]) @@ -623,7 +632,7 @@ const main = async () => { //load embed builders opendiscord.log("Loading embeds...","system") - if (opendiscord.defaults.getDefault("embedBuildersLoading")){ + if (opendiscord.sharedFuses.getFuse("embedBuildersLoading")){ await (await import("./builders/embeds.js")).registerAllEmbeds() } await opendiscord.events.get("onEmbedBuilderLoad").emit([opendiscord.builders.embeds,opendiscord.builders,opendiscord.actions]) @@ -631,7 +640,7 @@ const main = async () => { //load message builders opendiscord.log("Loading messages...","system") - if (opendiscord.defaults.getDefault("messageBuildersLoading")){ + if (opendiscord.sharedFuses.getFuse("messageBuildersLoading")){ await (await import("./builders/messages.js")).registerAllMessages() } await opendiscord.events.get("onMessageBuilderLoad").emit([opendiscord.builders.messages,opendiscord.builders,opendiscord.actions]) @@ -639,19 +648,51 @@ const main = async () => { //load modal builders opendiscord.log("Loading modals...","system") - if (opendiscord.defaults.getDefault("modalBuildersLoading")){ - await (await import("./builders/modals.js")).registerAllModals() + if (opendiscord.sharedFuses.getFuse("modalBuildersLoading")){ + //Deprecated, moved to "modal components" } await opendiscord.events.get("onModalBuilderLoad").emit([opendiscord.builders.modals,opendiscord.builders,opendiscord.actions]) await opendiscord.events.get("afterModalBuildersLoaded").emit([opendiscord.builders.modals,opendiscord.builders,opendiscord.actions]) + //load shared components + opendiscord.log("Loading shared components...","system") + if (opendiscord.sharedFuses.getFuse("sharedComponentsLoading")){ + //TODO!! + } + await opendiscord.events.get("onSharedComponentLoad").emit([opendiscord.components.shared,opendiscord.components,opendiscord.actions]) + await opendiscord.events.get("afterSharedComponentsLoaded").emit([opendiscord.components.shared,opendiscord.components,opendiscord.actions]) + + //load message components + opendiscord.log("Loading message components...","system") + if (opendiscord.sharedFuses.getFuse("messageComponentsLoading")){ + //TODO!! + } + await opendiscord.events.get("onMessageComponentLoad").emit([opendiscord.components.messages,opendiscord.components,opendiscord.actions]) + await opendiscord.events.get("afterMessageComponentsLoaded").emit([opendiscord.components.messages,opendiscord.components,opendiscord.actions]) + + //load modal components + opendiscord.log("Loading modal components...","system") + if (opendiscord.sharedFuses.getFuse("modalComponentsLoading")){ + await (await import("./components/modals.js")).registerModalComponents() + } + await opendiscord.events.get("onModalComponentLoad").emit([opendiscord.components.modals,opendiscord.components,opendiscord.actions]) + await opendiscord.events.get("afterModalComponentsLoaded").emit([opendiscord.components.modals,opendiscord.components,opendiscord.actions]) + + //load component modifiers + opendiscord.log("Loading component modifiers...","system") + if (opendiscord.sharedFuses.getFuse("componentModifiersLoading")){ + await (await import("./components/verifybarModifiers.js")).registerAllVerifyBarModifiers() + } + await opendiscord.events.get("onComponentModifierLoad").emit([opendiscord.components.modifiers,opendiscord.components.messages,opendiscord.builders.messages]) + await opendiscord.events.get("afterComponentModifiersLoaded").emit([opendiscord.components.modifiers,opendiscord.components.messages,opendiscord.builders.messages]) + //plugin loading before responders await opendiscord.events.get("onPluginBeforeResponderLoad").emit([]) await opendiscord.events.get("afterPluginBeforeResponderLoaded").emit([]) //load command responders opendiscord.log("Loading command responders...","system") - if (opendiscord.defaults.getDefault("commandRespondersLoading")){ + if (opendiscord.sharedFuses.getFuse("commandRespondersLoading")){ await (await import("./commands/help.js")).registerCommandResponders() await (await import("./commands/stats.js")).registerCommandResponders() await (await import("./commands/panel.js")).registerCommandResponders() @@ -674,16 +715,18 @@ const main = async () => { await (await import("./commands/topic.js")).registerCommandResponders() await (await import("./commands/priority.js")).registerCommandResponders() await (await import("./commands/transfer.js")).registerCommandResponders() + await (await import("./commands/transcripts.js")).registerCommandResponders() } await opendiscord.events.get("onCommandResponderLoad").emit([opendiscord.responders.commands,opendiscord.responders,opendiscord.actions]) await opendiscord.events.get("afterCommandRespondersLoaded").emit([opendiscord.responders.commands,opendiscord.responders,opendiscord.actions]) //load button responders opendiscord.log("Loading button responders...","system") - if (opendiscord.defaults.getDefault("buttonRespondersLoading")){ + if (opendiscord.sharedFuses.getFuse("buttonRespondersLoading")){ await (await import("./actions/handleVerifyBar.js")).registerButtonResponders() await (await import("./actions/handleTranscriptErrors.js")).registerButtonResponders() await (await import("./commands/help.js")).registerButtonResponders() + await (await import("./commands/panel.js")).registerButtonResponders() await (await import("./commands/ticket.js")).registerButtonResponders() await (await import("./commands/close.js")).registerButtonResponders() await (await import("./commands/reopen.js")).registerButtonResponders() @@ -700,15 +743,16 @@ const main = async () => { //load dropdown responders opendiscord.log("Loading dropdown responders...","system") - if (opendiscord.defaults.getDefault("dropdownRespondersLoading")){ - await (await import("./commands/ticket.js")).registerDropdownResponders() + if (opendiscord.sharedFuses.getFuse("dropdownRespondersLoading")){ + await (await import("./commands/panel.js")).registerDropdownResponders() + await (await import("./commands/priority.js")).registerDropdownResponders() } await opendiscord.events.get("onDropdownResponderLoad").emit([opendiscord.responders.dropdowns,opendiscord.responders,opendiscord.actions]) await opendiscord.events.get("afterDropdownRespondersLoaded").emit([opendiscord.responders.dropdowns,opendiscord.responders,opendiscord.actions]) //load modal responders opendiscord.log("Loading modal responders...","system") - if (opendiscord.defaults.getDefault("modalRespondersLoading")){ + if (opendiscord.sharedFuses.getFuse("modalRespondersLoading")){ await (await import("./commands/ticket.js")).registerModalResponders() await (await import("./commands/close.js")).registerModalResponders() await (await import("./commands/reopen.js")).registerModalResponders() @@ -723,7 +767,7 @@ const main = async () => { //load context menu responders opendiscord.log("Loading context menu responders...","system") - if (opendiscord.defaults.getDefault("contextMenuRespondersLoading")){ + if (opendiscord.sharedFuses.getFuse("contextMenuRespondersLoading")){ //TODO!! } await opendiscord.events.get("onContextMenuResponderLoad").emit([opendiscord.responders.contextMenus,opendiscord.responders,opendiscord.actions]) @@ -731,7 +775,7 @@ const main = async () => { //load autocomplete responders opendiscord.log("Loading autocomplete responders...","system") - if (opendiscord.defaults.getDefault("autocompleteRespondersLoading")){ + if (opendiscord.sharedFuses.getFuse("autocompleteRespondersLoading")){ await (await import("./commands/autocomplete.js")).registerAutocompleteResponders() } await opendiscord.events.get("onAutocompleteResponderLoad").emit([opendiscord.responders.autocomplete,opendiscord.responders,opendiscord.actions]) @@ -743,8 +787,10 @@ const main = async () => { //load actions opendiscord.log("Loading actions...","system") - if (opendiscord.defaults.getDefault("actionsLoading")){ + if (opendiscord.sharedFuses.getFuse("actionsLoading")){ await (await import("./actions/createTicketPermissions.js")).registerActions() + await (await import("./actions/calculateTicketCategory.js")).registerActions() + await (await import("./actions/calculateTicketName.js")).registerActions() await (await import("./actions/createTranscript.js")).registerActions() await (await import("./actions/createTicket.js")).registerActions() await (await import("./actions/closeTicket.js")).registerActions() @@ -769,21 +815,21 @@ const main = async () => { //load verifybars opendiscord.log("Loading verifybars...","system") - if (opendiscord.defaults.getDefault("verifyBarsLoading")){ - await (await import("./actions/closeTicket.js")).registerVerifyBars() - await (await import("./actions/deleteTicket.js")).registerVerifyBars() - await (await import("./actions/reopenTicket.js")).registerVerifyBars() - await (await import("./actions/claimTicket.js")).registerVerifyBars() - await (await import("./actions/unclaimTicket.js")).registerVerifyBars() - await (await import("./actions/pinTicket.js")).registerVerifyBars() - await (await import("./actions/unpinTicket.js")).registerVerifyBars() + if (opendiscord.sharedFuses.getFuse("verifyBarsLoading")){ + await (await import("./commands/close.js")).registerVerifyBars() + await (await import("./commands/delete.js")).registerVerifyBars() + await (await import("./commands/reopen.js")).registerVerifyBars() + await (await import("./commands/claim.js")).registerVerifyBars() + await (await import("./commands/unclaim.js")).registerVerifyBars() + await (await import("./commands/pin.js")).registerVerifyBars() + await (await import("./commands/unpin.js")).registerVerifyBars() } await opendiscord.events.get("onVerifyBarLoad").emit([opendiscord.verifybars]) await opendiscord.events.get("afterVerifyBarsLoaded").emit([opendiscord.verifybars]) //load permissions opendiscord.log("Loading permissions...","system") - if (opendiscord.defaults.getDefault("permissionsLoading")){ + if (opendiscord.sharedFuses.getFuse("permissionsLoading")){ await (await import("./data/framework/permissionLoader.js")).loadAllPermissions() } await opendiscord.events.get("onPermissionLoad").emit([opendiscord.permissions]) @@ -791,7 +837,7 @@ const main = async () => { //load posts opendiscord.log("Loading posts...","system") - if (opendiscord.defaults.getDefault("postsLoading")){ + if (opendiscord.sharedFuses.getFuse("postsLoading")){ await (await import("./data/framework/postLoader.js")).loadAllPosts() } await opendiscord.events.get("onPostLoad").emit([opendiscord.posts]) @@ -799,14 +845,14 @@ const main = async () => { //init posts await opendiscord.events.get("onPostInit").emit([opendiscord.posts]) - if (opendiscord.defaults.getDefault("postsInitiating")){ - if (opendiscord.client.mainServer) opendiscord.posts.init(opendiscord.client.mainServer) + if (opendiscord.sharedFuses.getFuse("postsInitiating")){ + if (opendiscord.client.mainServer) await opendiscord.posts.init(opendiscord.client.mainServer) await opendiscord.events.get("afterPostsInitiated").emit([opendiscord.posts]) } //load cooldowns opendiscord.log("Loading cooldowns...","system") - if (opendiscord.defaults.getDefault("cooldownsLoading")){ + if (opendiscord.sharedFuses.getFuse("cooldownsLoading")){ await (await import("./data/framework/cooldownLoader.js")).loadAllCooldowns() } await opendiscord.events.get("onCooldownLoad").emit([opendiscord.cooldowns]) @@ -814,66 +860,66 @@ const main = async () => { //init cooldowns await opendiscord.events.get("onCooldownInit").emit([opendiscord.cooldowns]) - if (opendiscord.defaults.getDefault("cooldownsInitiating")){ + if (opendiscord.sharedFuses.getFuse("cooldownsInitiating")){ await opendiscord.cooldowns.init() await opendiscord.events.get("afterCooldownsInitiated").emit([opendiscord.cooldowns]) } //load help menu categories opendiscord.log("Loading help menu...","system") - if (opendiscord.defaults.getDefault("helpMenuCategoryLoading")){ + if (opendiscord.sharedFuses.getFuse("helpMenuCategoryLoading")){ await (await import("./data/framework/helpMenuLoader.js")).loadAllHelpMenuCategories() } await opendiscord.events.get("onHelpMenuCategoryLoad").emit([opendiscord.helpmenu]) await opendiscord.events.get("afterHelpMenuCategoriesLoaded").emit([opendiscord.helpmenu]) //load help menu components - if (opendiscord.defaults.getDefault("helpMenuComponentLoading")){ + if (opendiscord.sharedFuses.getFuse("helpMenuComponentLoading")){ await (await import("./data/framework/helpMenuLoader.js")).loadAllHelpMenuComponents() } await opendiscord.events.get("onHelpMenuComponentLoad").emit([opendiscord.helpmenu]) await opendiscord.events.get("afterHelpMenuComponentsLoaded").emit([opendiscord.helpmenu]) - //load stat scopes - opendiscord.log("Loading stats...","system") - if (opendiscord.defaults.getDefault("statScopesLoading")){ - opendiscord.stats.useDatabase(opendiscord.databases.get("opendiscord:stats")) - await (await import("./data/framework/statLoader.js")).loadAllStatScopes() + //load statistic scopes + opendiscord.log("Loading statistics...","system") + if (opendiscord.sharedFuses.getFuse("statisticScopesLoading")){ + opendiscord.statistics.useDatabase(opendiscord.databases.get("opendiscord:stats")) + await (await import("./data/framework/statisticLoader.js")).loadAllStatisticScopes() } - await opendiscord.events.get("onStatScopeLoad").emit([opendiscord.stats]) - await opendiscord.events.get("afterStatScopesLoaded").emit([opendiscord.stats]) + await opendiscord.events.get("onStatisticScopeLoad").emit([opendiscord.statistics]) + await opendiscord.events.get("afterStatisticScopesLoaded").emit([opendiscord.statistics]) - //load stats - if (opendiscord.defaults.getDefault("statLoading")){ - await (await import("./data/framework/statLoader.js")).loadAllStats() + //load statistics + if (opendiscord.sharedFuses.getFuse("statisticLoading")){ + await (await import("./data/framework/statisticLoader.js")).loadAllStatistics() } - await opendiscord.events.get("onStatLoad").emit([opendiscord.stats]) - await opendiscord.events.get("afterStatsLoaded").emit([opendiscord.stats]) + await opendiscord.events.get("onStatisticLoad").emit([opendiscord.statistics]) + await opendiscord.events.get("afterStatisticsLoaded").emit([opendiscord.statistics]) - //init stats - await opendiscord.events.get("onStatInit").emit([opendiscord.stats]) - if (opendiscord.defaults.getDefault("statInitiating")){ - await opendiscord.stats.init() - await opendiscord.events.get("afterStatsInitiated").emit([opendiscord.stats]) + //init statistics + await opendiscord.events.get("onStatisticInit").emit([opendiscord.statistics]) + if (opendiscord.sharedFuses.getFuse("statisticInitiating")){ + await opendiscord.statistics.init() + await opendiscord.events.get("afterStatisticsInitiated").emit([opendiscord.statistics]) } //plugin loading before code - await opendiscord.events.get("onPluginBeforeCodeLoad").emit([]) - await opendiscord.events.get("afterPluginBeforeCodeLoaded").emit([]) + await opendiscord.events.get("onPluginBeforeTaskLoad").emit([]) + await opendiscord.events.get("afterPluginBeforeTaskLoaded").emit([]) - //load code - opendiscord.log("Loading code...","system") - if (opendiscord.defaults.getDefault("codeLoading")){ - await (await import("./data/framework/codeLoader.js")).loadAllCode() + //load background tasks + opendiscord.log("Loading background tasks...","system") + if (opendiscord.sharedFuses.getFuse("taskLoading")){ + await (await import("./data/framework/taskLoader.js")).loadAllTasks() } - await opendiscord.events.get("onCodeLoad").emit([opendiscord.code]) - await opendiscord.events.get("afterCodeLoaded").emit([opendiscord.code]) + await opendiscord.events.get("onTaskLoad").emit([opendiscord.tasks]) + await opendiscord.events.get("afterTasksLoaded").emit([opendiscord.tasks]) - //execute code - await opendiscord.events.get("onCodeExecute").emit([opendiscord.code]) - if (opendiscord.defaults.getDefault("codeExecution")){ - await opendiscord.code.execute() - await opendiscord.events.get("afterCodeExecuted").emit([opendiscord.code]) + //execute background tasks + await opendiscord.events.get("onTaskExecute").emit([opendiscord.tasks]) + if (opendiscord.sharedFuses.getFuse("taskExecution")){ + await opendiscord.tasks.execute() + await opendiscord.events.get("afterTasksExecuted").emit([opendiscord.tasks]) } //finish setup @@ -881,7 +927,7 @@ const main = async () => { //load livestatus sources opendiscord.log("Loading livestatus...","system") - if (opendiscord.defaults.getDefault("liveStatusLoading")){ + if (opendiscord.sharedFuses.getFuse("liveStatusLoading")){ await (await import("./data/framework/liveStatusLoader.js")).loadAllLiveStatusSources() } await opendiscord.events.get("onLiveStatusSourceLoad").emit([opendiscord.livestatus]) @@ -889,7 +935,7 @@ const main = async () => { //load startscreen opendiscord.log("Loading startscreen...","system") - if (opendiscord.defaults.getDefault("startScreenLoading")){ + if (opendiscord.sharedFuses.getFuse("startScreenLoading")){ await (await import("./data/framework/startScreenLoader.js")).loadAllStartScreenComponents() } await opendiscord.events.get("onStartScreenLoad").emit([opendiscord.startscreen]) @@ -897,7 +943,7 @@ const main = async () => { //render startscreen await opendiscord.events.get("onStartScreenRender").emit([opendiscord.startscreen]) - if (opendiscord.defaults.getDefault("startScreenRendering")){ + if (opendiscord.sharedFuses.getFuse("startScreenRendering")){ await opendiscord.startscreen.renderAllComponents() if (opendiscord.languages.getLanguageMetadata(false)?.automated){ console.log("===================") diff --git a/tsconfig.json b/tsconfig.json index 95a1996..c833325 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,6 +1,6 @@ { "compilerOptions": { - "target": "ES2022", + "target": "es2024", "rootDir":"./", "outDir": "./dist/", "module": "NodeNext", @@ -13,6 +13,7 @@ "allowJs": false, "checkJs": false, "declaration": true, + "strict": false, "strictNullChecks": true, "strictPropertyInitialization": true, "skipLibCheck": true,