diff --git a/.docs/mergeTranslations.js b/.docs/mergeTranslations.js
new file mode 100644
index 0000000..7025dee
--- /dev/null
+++ b/.docs/mergeTranslations.js
@@ -0,0 +1,649 @@
+//@ts-check
+const fjs = require("formatted-json-stringify")
+const fs = require("fs")
+const formatter = new fjs.ObjectFormatter(null,true,[
+ new fjs.ObjectFormatter("_TRANSLATION",true,[
+ new fjs.PropertyFormatter("otversion"),
+ new fjs.ArrayFormatter("translators",false,new fjs.PropertyFormatter(null)),
+ new fjs.PropertyFormatter("lastedited"),
+ new fjs.PropertyFormatter("language"),
+ new fjs.PropertyFormatter("automated"),
+ ]),
+ new fjs.ObjectFormatter("checker",true,[
+ new fjs.ObjectFormatter("system",true,[
+ new fjs.PropertyFormatter("typeError"),
+ new fjs.PropertyFormatter("headerOpenTicket"),
+ new fjs.PropertyFormatter("typeWarning"),
+ new fjs.PropertyFormatter("typeInfo"),
+ new fjs.PropertyFormatter("headerConfigChecker"),
+ new fjs.PropertyFormatter("headerDescription"),
+ new fjs.PropertyFormatter("footerError"),
+ new fjs.PropertyFormatter("footerWarning"),
+ new fjs.PropertyFormatter("footerSupport"),
+ new fjs.PropertyFormatter("compactInformation"),
+ new fjs.PropertyFormatter("dataPath"),
+ new fjs.PropertyFormatter("dataDocs"),
+ new fjs.PropertyFormatter("dataMessages"),
+ ]),
+ new fjs.ObjectFormatter("messages",true,[
+ new fjs.PropertyFormatter("stringTooShort"),
+ new fjs.PropertyFormatter("stringTooLong"),
+ new fjs.PropertyFormatter("stringLengthInvalid"),
+ new fjs.PropertyFormatter("stringStartsWith"),
+ new fjs.PropertyFormatter("stringEndsWith"),
+ new fjs.PropertyFormatter("stringContains"),
+ new fjs.PropertyFormatter("stringChoices"),
+ new fjs.PropertyFormatter("stringRegex"),
+
+ new fjs.PropertyFormatter("stringInvertedContains"),
+ new fjs.PropertyFormatter("stringLowercase"),
+ new fjs.PropertyFormatter("stringUppercase"),
+ new fjs.PropertyFormatter("stringSpecialCharacters"),
+ new fjs.PropertyFormatter("stringNoSpaces"),
+ new fjs.PropertyFormatter("stringCapitalWord"),
+ new fjs.PropertyFormatter("stringCapitalSentence"),
+ new fjs.PropertyFormatter("stringPunctuation"),
+ new fjs.TextFormatter(""),
+ new fjs.PropertyFormatter("numberTooShort"),
+ new fjs.PropertyFormatter("numberTooLong"),
+ new fjs.PropertyFormatter("numberLengthInvalid"),
+ new fjs.PropertyFormatter("numberTooSmall"),
+ new fjs.PropertyFormatter("numberTooLarge"),
+ new fjs.PropertyFormatter("numberNotEqual"),
+ new fjs.PropertyFormatter("numberStep"),
+ new fjs.PropertyFormatter("numberStepOffset"),
+ new fjs.PropertyFormatter("numberStartsWith"),
+ new fjs.PropertyFormatter("numberEndsWith"),
+ new fjs.PropertyFormatter("numberContains"),
+ new fjs.PropertyFormatter("numberChoices"),
+ new fjs.PropertyFormatter("numberFloat"),
+ new fjs.PropertyFormatter("numberNegative"),
+ new fjs.PropertyFormatter("numberPositive"),
+ new fjs.PropertyFormatter("numberZero"),
+ new fjs.PropertyFormatter("numberNan"),
+ new fjs.PropertyFormatter("numberInvertedContains"),
+ new fjs.TextFormatter(""),
+ new fjs.PropertyFormatter("booleanTrue"),
+ new fjs.PropertyFormatter("booleanFalse"),
+ new fjs.TextFormatter(""),
+ new fjs.PropertyFormatter("arrayEmptyDisabled"),
+ new fjs.PropertyFormatter("arrayEmptyRequired"),
+ new fjs.PropertyFormatter("arrayTooShort"),
+ new fjs.PropertyFormatter("arrayTooLong"),
+ new fjs.PropertyFormatter("arrayLengthInvalid"),
+ new fjs.PropertyFormatter("arrayInvalidTypes"),
+ new fjs.PropertyFormatter("arrayDouble"),
+ new fjs.TextFormatter(""),
+ new fjs.PropertyFormatter("discordInvalidId"),
+ new fjs.PropertyFormatter("discordInvalidIdOptions"),
+ new fjs.PropertyFormatter("discordInvalidToken"),
+ new fjs.PropertyFormatter("colorInvalid"),
+ new fjs.PropertyFormatter("emojiTooShort"),
+ new fjs.PropertyFormatter("emojiTooLong"),
+ new fjs.PropertyFormatter("emojiCustom"),
+ new fjs.PropertyFormatter("emojiInvalid"),
+ new fjs.PropertyFormatter("urlInvalid"),
+ new fjs.PropertyFormatter("urlInvalidHttp"),
+ new fjs.PropertyFormatter("urlInvalidProtocol"),
+ new fjs.PropertyFormatter("urlInvalidHostname"),
+ new fjs.PropertyFormatter("urlInvalidExtension"),
+ new fjs.PropertyFormatter("urlInvalidPath"),
+ new fjs.PropertyFormatter("idNotUnique"),
+ new fjs.PropertyFormatter("idNonExistent"),
+ new fjs.TextFormatter(""),
+ new fjs.PropertyFormatter("invalidType"),
+ new fjs.PropertyFormatter("propertyMissing"),
+ new fjs.PropertyFormatter("propertyOptional"),
+ new fjs.PropertyFormatter("objectDisabled"),
+ new fjs.PropertyFormatter("nullInvalid"),
+ new fjs.PropertyFormatter("switchInvalidType"),
+ new fjs.PropertyFormatter("objectSwitchInvalid"),
+ new fjs.TextFormatter(""),
+ new fjs.PropertyFormatter("invalidLanguage"),
+ new fjs.PropertyFormatter("invalidButton"),
+ new fjs.PropertyFormatter("unusedOption"),
+ new fjs.PropertyFormatter("unusedQuestion"),
+ new fjs.PropertyFormatter("dropdownOption"),
+ new fjs.PropertyFormatter("customInvalidVersion"),
+ ]),
+ ]),
+ new fjs.ObjectFormatter("actions",true,[
+ new fjs.ObjectFormatter("buttons",true,[
+ new fjs.PropertyFormatter("create"),
+ 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("clear"),
+ new fjs.PropertyFormatter("helpSwitchSlash"),
+ new fjs.PropertyFormatter("helpSwitchText"),
+ new fjs.PropertyFormatter("helpPage"),
+ new fjs.PropertyFormatter("withReason"),
+ new fjs.PropertyFormatter("withoutTranscript"),
+ ]),
+ new fjs.ObjectFormatter("titles",true,[
+ new fjs.PropertyFormatter("created"),
+ 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("rename"),
+ new fjs.PropertyFormatter("move"),
+ new fjs.PropertyFormatter("add"),
+ new fjs.PropertyFormatter("remove"),
+ new fjs.TextFormatter(""),
+ new fjs.PropertyFormatter("help"),
+ new fjs.PropertyFormatter("statsReset"),
+ new fjs.PropertyFormatter("blacklistAdd"),
+ new fjs.PropertyFormatter("blacklistRemove"),
+ new fjs.PropertyFormatter("blacklistGet"),
+ new fjs.PropertyFormatter("blacklistView"),
+ new fjs.PropertyFormatter("blacklistAddDm"),
+ new fjs.PropertyFormatter("blacklistRemoveDm"),
+ new fjs.PropertyFormatter("clear"),
+ new fjs.PropertyFormatter("clearTickets"),
+ new fjs.PropertyFormatter("roles"),
+ new fjs.TextFormatter(""),
+ new fjs.PropertyFormatter("autoclose"),
+ new fjs.PropertyFormatter("autocloseEnabled"),
+ new fjs.PropertyFormatter("autocloseDisabled"),
+ new fjs.PropertyFormatter("autodelete"),
+ new fjs.PropertyFormatter("autodeleteEnabled"),
+ new fjs.PropertyFormatter("autodeleteDisabled"),
+ new fjs.TextFormatter(""),
+ new fjs.PropertyFormatter("topicSet"),
+ new fjs.PropertyFormatter("prioritySet"),
+ new fjs.PropertyFormatter("priorityGet"),
+ new fjs.PropertyFormatter("transfer"),
+ ]),
+ new fjs.ObjectFormatter("descriptions",true,[
+ new fjs.PropertyFormatter("create"),
+ 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("rename"),
+ new fjs.PropertyFormatter("move"),
+ new fjs.PropertyFormatter("add"),
+ new fjs.PropertyFormatter("remove"),
+ new fjs.TextFormatter(""),
+ new fjs.PropertyFormatter("helpExplanation"),
+ new fjs.PropertyFormatter("statsReset"),
+ new fjs.PropertyFormatter("statsError"),
+ new fjs.PropertyFormatter("blacklistAdd"),
+ new fjs.PropertyFormatter("blacklistRemove"),
+ new fjs.PropertyFormatter("blacklistGetSuccess"),
+ new fjs.PropertyFormatter("blacklistGetEmpty"),
+ new fjs.PropertyFormatter("blacklistViewEmpty"),
+ new fjs.PropertyFormatter("blacklistViewTip"),
+ new fjs.PropertyFormatter("clearVerify"),
+ new fjs.PropertyFormatter("clearReady"),
+ new fjs.PropertyFormatter("rolesEmpty"),
+ new fjs.TextFormatter(""),
+ new fjs.PropertyFormatter("autocloseLeave"),
+ new fjs.PropertyFormatter("autocloseTimeout"),
+ new fjs.PropertyFormatter("autodeleteLeave"),
+ new fjs.PropertyFormatter("autodeleteTimeout"),
+ new fjs.PropertyFormatter("autocloseEnabled"),
+ new fjs.PropertyFormatter("autocloseDisabled"),
+ new fjs.PropertyFormatter("autodeleteEnabled"),
+ new fjs.PropertyFormatter("autodeleteDisabled"),
+ new fjs.TextFormatter(""),
+ new fjs.PropertyFormatter("ticketMessageLimit"),
+ new fjs.PropertyFormatter("ticketMessageAutoclose"),
+ new fjs.PropertyFormatter("ticketMessageAutodelete"),
+ new fjs.PropertyFormatter("panelReady"),
+ new fjs.TextFormatter(""),
+ new fjs.PropertyFormatter("topicSet"),
+ new fjs.PropertyFormatter("prioritySet"),
+ new fjs.PropertyFormatter("priorityGet"),
+ new fjs.PropertyFormatter("transfer"),
+ ]),
+ new fjs.ObjectFormatter("modal",true,[
+ new fjs.PropertyFormatter("closePlaceholder"),
+ new fjs.PropertyFormatter("deletePlaceholder"),
+ new fjs.PropertyFormatter("reopenPlaceholder"),
+ new fjs.PropertyFormatter("claimPlaceholder"),
+ new fjs.PropertyFormatter("unclaimPlaceholder"),
+ new fjs.PropertyFormatter("pinPlaceholder"),
+ new fjs.PropertyFormatter("unpinPlaceholder"),
+ ]),
+ new fjs.ObjectFormatter("logs",true,[
+ new fjs.PropertyFormatter("createLog"),
+ new fjs.PropertyFormatter("closeLog"),
+ new fjs.PropertyFormatter("closeDm"),
+ new fjs.PropertyFormatter("deleteLog"),
+ new fjs.PropertyFormatter("deleteDm"),
+ new fjs.PropertyFormatter("reopenLog"),
+ new fjs.PropertyFormatter("reopenDm"),
+ new fjs.PropertyFormatter("claimLog"),
+ new fjs.PropertyFormatter("claimDm"),
+ new fjs.PropertyFormatter("unclaimLog"),
+ new fjs.PropertyFormatter("unclaimDm"),
+ new fjs.PropertyFormatter("pinLog"),
+ new fjs.PropertyFormatter("pinDm"),
+ new fjs.PropertyFormatter("unpinLog"),
+ new fjs.PropertyFormatter("unpinDm"),
+ new fjs.PropertyFormatter("renameLog"),
+ new fjs.PropertyFormatter("renameDm"),
+ new fjs.PropertyFormatter("moveLog"),
+ new fjs.PropertyFormatter("moveDm"),
+ new fjs.PropertyFormatter("addLog"),
+ new fjs.PropertyFormatter("addDm"),
+ new fjs.PropertyFormatter("removeLog"),
+ new fjs.PropertyFormatter("removeDm"),
+ new fjs.TextFormatter(""),
+ new fjs.PropertyFormatter("blacklistAddLog"),
+ new fjs.PropertyFormatter("blacklistRemoveLog"),
+ new fjs.PropertyFormatter("blacklistAddDm"),
+ new fjs.PropertyFormatter("blacklistRemoveDm"),
+ new fjs.PropertyFormatter("clearLog"),
+ new fjs.TextFormatter(""),
+ new fjs.PropertyFormatter("transferLog"),
+ new fjs.PropertyFormatter("transferDm"),
+ new fjs.PropertyFormatter("prioritySetLog"),
+ new fjs.PropertyFormatter("prioritySetDm"),
+ new fjs.PropertyFormatter("roleUpdateLog"),
+ new fjs.PropertyFormatter("roleUpdateDm"),
+ ]),
+ ]),
+ new fjs.ObjectFormatter("transcripts",true,[
+ new fjs.ObjectFormatter("success",true,[
+ new fjs.PropertyFormatter("visit"),
+ new fjs.PropertyFormatter("ready"),
+ new fjs.PropertyFormatter("textFileDescription"),
+ new fjs.PropertyFormatter("htmlProgress"),
+ new fjs.TextFormatter(""),
+ new fjs.PropertyFormatter("createdChannel"),
+ new fjs.PropertyFormatter("createdCreator"),
+ new fjs.PropertyFormatter("createdParticipant"),
+ new fjs.PropertyFormatter("createdActiveAdmin"),
+ new fjs.PropertyFormatter("createdEveryAdmin"),
+ new fjs.PropertyFormatter("createdOther"),
+ ]),
+ new fjs.ObjectFormatter("errors",true,[
+ new fjs.PropertyFormatter("retry"),
+ new fjs.PropertyFormatter("continue"),
+ new fjs.PropertyFormatter("backup"),
+ new fjs.PropertyFormatter("error"),
+ new fjs.PropertyFormatter("title"),
+ ]),
+ new fjs.ObjectFormatter("text",true,[
+ new fjs.PropertyFormatter("messagesTitle"),
+ new fjs.PropertyFormatter("embedTitle"),
+ new fjs.PropertyFormatter("fileTitle"),
+ new fjs.PropertyFormatter("fieldsTitle"),
+ new fjs.PropertyFormatter("reactionsTitle"),
+ new fjs.PropertyFormatter("statsTitle"),
+ new fjs.PropertyFormatter("emptyContent"),
+ new fjs.PropertyFormatter("noTitle"),
+ new fjs.PropertyFormatter("noDesc"),
+ ]),
+ ]),
+ new fjs.ObjectFormatter("errors",true,[
+ new fjs.ObjectFormatter("titles",true,[
+ new fjs.PropertyFormatter("internalError"),
+ new fjs.PropertyFormatter("optionMissing"),
+ new fjs.PropertyFormatter("optionInvalid"),
+ new fjs.PropertyFormatter("unknownCommand"),
+ new fjs.PropertyFormatter("noPermissions"),
+ new fjs.PropertyFormatter("unknownTicket"),
+ new fjs.PropertyFormatter("deprecatedTicket"),
+ new fjs.PropertyFormatter("unknownOption"),
+ new fjs.PropertyFormatter("unknownPanel"),
+ new fjs.PropertyFormatter("notInGuild"),
+ new fjs.PropertyFormatter("channelRename"),
+ new fjs.PropertyFormatter("busy"),
+ new fjs.PropertyFormatter("permissionError"),
+ ]),
+ new fjs.ObjectFormatter("descriptions",true,[
+ new fjs.PropertyFormatter("askForInfo"),
+ new fjs.PropertyFormatter("askForInfoResolve"),
+ new fjs.PropertyFormatter("internalError"),
+ new fjs.PropertyFormatter("optionMissing"),
+ new fjs.PropertyFormatter("optionInvalid"),
+ new fjs.PropertyFormatter("optionInvalidChoose"),
+ new fjs.PropertyFormatter("unknownCommand"),
+ new fjs.PropertyFormatter("noPermissions"),
+ new fjs.PropertyFormatter("noPermissionsList"),
+ new fjs.PropertyFormatter("noPermissionsCooldown"),
+ new fjs.PropertyFormatter("noPermissionsBlacklist"),
+ new fjs.PropertyFormatter("noPermissionsLimitGlobal"),
+ new fjs.PropertyFormatter("noPermissionsLimitGlobalUser"),
+ new fjs.PropertyFormatter("noPermissionsLimitOption"),
+ new fjs.PropertyFormatter("noPermissionsLimitOptionUser"),
+ new fjs.PropertyFormatter("unknownTicket"),
+ new fjs.PropertyFormatter("deprecatedTicket"),
+ new fjs.PropertyFormatter("notInGuild"),
+ new fjs.PropertyFormatter("channelRename"),
+ new fjs.PropertyFormatter("channelRenameSource"),
+ new fjs.PropertyFormatter("busy"),
+ new fjs.PropertyFormatter("closeBeforeMessage"),
+ new fjs.PropertyFormatter("closeBeforeAdminMessage"),
+ new fjs.PropertyFormatter("unableToCreateTicket"),
+ ]),
+ new fjs.ObjectFormatter("optionInvalidReasons",true,[
+ new fjs.PropertyFormatter("stringRegex"),
+ new fjs.PropertyFormatter("stringMinLength"),
+ new fjs.PropertyFormatter("stringMaxLength"),
+ new fjs.PropertyFormatter("numberInvalid"),
+ new fjs.PropertyFormatter("numberMin"),
+ new fjs.PropertyFormatter("numberMax"),
+ new fjs.PropertyFormatter("numberDecimal"),
+ new fjs.PropertyFormatter("numberNegative"),
+ new fjs.PropertyFormatter("numberPositive"),
+ new fjs.PropertyFormatter("numberZero"),
+ new fjs.PropertyFormatter("channelNotFound"),
+ new fjs.PropertyFormatter("userNotFound"),
+ new fjs.PropertyFormatter("roleNotFound"),
+ new fjs.PropertyFormatter("memberNotFound"),
+ new fjs.PropertyFormatter("mentionableNotFound"),
+ new fjs.PropertyFormatter("channelType"),
+ new fjs.PropertyFormatter("notInGuild"),
+ ]),
+ new fjs.ObjectFormatter("permissions",true,[
+ new fjs.PropertyFormatter("developer"),
+ new fjs.PropertyFormatter("owner"),
+ new fjs.PropertyFormatter("admin"),
+ new fjs.PropertyFormatter("moderator"),
+ new fjs.PropertyFormatter("support"),
+ new fjs.PropertyFormatter("member"),
+ new fjs.PropertyFormatter("discord-administrator"),
+ ]),
+ new fjs.ObjectFormatter("actionInvalid",true,[
+ new fjs.PropertyFormatter("close"),
+ new fjs.PropertyFormatter("reopen"),
+ new fjs.PropertyFormatter("claim"),
+ new fjs.PropertyFormatter("unclaim"),
+ new fjs.PropertyFormatter("pin"),
+ new fjs.PropertyFormatter("unpin"),
+ new fjs.PropertyFormatter("add"),
+ new fjs.PropertyFormatter("remove"),
+ ]),
+ ]),
+ new fjs.ObjectFormatter("params",true,[
+ new fjs.ObjectFormatter("uppercase",true,[
+ new fjs.PropertyFormatter("ticket"),
+ new fjs.PropertyFormatter("tickets"),
+ new fjs.PropertyFormatter("reason"),
+ new fjs.PropertyFormatter("creator"),
+ new fjs.PropertyFormatter("remaining"),
+ new fjs.PropertyFormatter("added"),
+ new fjs.PropertyFormatter("removed"),
+ new fjs.PropertyFormatter("filter"),
+ new fjs.PropertyFormatter("method"),
+ new fjs.PropertyFormatter("type"),
+ new fjs.PropertyFormatter("blacklisted"),
+ new fjs.PropertyFormatter("panel"),
+ new fjs.PropertyFormatter("command"),
+ new fjs.PropertyFormatter("system"),
+ new fjs.PropertyFormatter("true"),
+ new fjs.PropertyFormatter("false"),
+ new fjs.PropertyFormatter("syntax"),
+ new fjs.PropertyFormatter("originalName"),
+ new fjs.PropertyFormatter("newName"),
+ new fjs.PropertyFormatter("until"),
+ new fjs.PropertyFormatter("validOptions"),
+ new fjs.PropertyFormatter("validPanels"),
+ new fjs.PropertyFormatter("autoclose"),
+ new fjs.PropertyFormatter("autodelete"),
+ new fjs.PropertyFormatter("startupDate"),
+ new fjs.PropertyFormatter("version"),
+ new fjs.PropertyFormatter("name"),
+ new fjs.PropertyFormatter("role"),
+ new fjs.PropertyFormatter("status"),
+ new fjs.PropertyFormatter("claimed"),
+ new fjs.PropertyFormatter("pinned"),
+ new fjs.PropertyFormatter("creationDate"),
+ new fjs.TextFormatter(""),
+ new fjs.PropertyFormatter("noone"),
+ new fjs.PropertyFormatter("open"),
+ new fjs.PropertyFormatter("closed"),
+ new fjs.PropertyFormatter("priority"),
+ new fjs.PropertyFormatter("participants"),
+ new fjs.PropertyFormatter("yes"),
+ new fjs.PropertyFormatter("no"),
+ new fjs.PropertyFormatter("option"),
+ new fjs.PropertyFormatter("topic"),
+ new fjs.PropertyFormatter("uptime"),
+ new fjs.PropertyFormatter("messages"),
+ new fjs.PropertyFormatter("embeds"),
+ new fjs.PropertyFormatter("files"),
+ new fjs.PropertyFormatter("components"),
+ new fjs.PropertyFormatter("cooldown"),
+ new fjs.PropertyFormatter("maxTickets"),
+ new fjs.PropertyFormatter("admins"),
+ new fjs.PropertyFormatter("roles"),
+ new fjs.PropertyFormatter("size"),
+ ]),
+ new fjs.ObjectFormatter("lowercase",true,[
+ new fjs.PropertyFormatter("text"),
+ new fjs.PropertyFormatter("html"),
+ new fjs.PropertyFormatter("command"),
+ new fjs.PropertyFormatter("modal"),
+ new fjs.PropertyFormatter("button"),
+ new fjs.PropertyFormatter("dropdown"),
+ new fjs.PropertyFormatter("method"),
+ ]),
+ ]),
+ new fjs.ObjectFormatter("commands",true,[
+ new fjs.PropertyFormatter("reason"),
+ new fjs.PropertyFormatter("help"),
+ new fjs.PropertyFormatter("panel"),
+ new fjs.PropertyFormatter("panelId"),
+ new fjs.PropertyFormatter("panelAutoUpdate"),
+ new fjs.PropertyFormatter("ticket"),
+ new fjs.PropertyFormatter("ticketId"),
+ new fjs.PropertyFormatter("close"),
+ new fjs.PropertyFormatter("delete"),
+ new fjs.PropertyFormatter("deleteNoTranscript"),
+ new fjs.PropertyFormatter("reopen"),
+ new fjs.PropertyFormatter("claim"),
+ new fjs.PropertyFormatter("claimUser"),
+ new fjs.PropertyFormatter("unclaim"),
+ new fjs.PropertyFormatter("pin"),
+ new fjs.PropertyFormatter("unpin"),
+ new fjs.TextFormatter(""),
+ new fjs.PropertyFormatter("move"),
+ new fjs.PropertyFormatter("moveId"),
+ new fjs.PropertyFormatter("rename"),
+ new fjs.PropertyFormatter("renameName"),
+ new fjs.PropertyFormatter("add"),
+ new fjs.PropertyFormatter("addUser"),
+ new fjs.PropertyFormatter("remove"),
+ new fjs.PropertyFormatter("removeUser"),
+ new fjs.TextFormatter(""),
+ new fjs.PropertyFormatter("blacklist"),
+ new fjs.PropertyFormatter("blacklistView"),
+ new fjs.PropertyFormatter("blacklistAdd"),
+ new fjs.PropertyFormatter("blacklistRemove"),
+ new fjs.PropertyFormatter("blacklistGet"),
+ new fjs.PropertyFormatter("blacklistGetUser"),
+ new fjs.PropertyFormatter("stats"),
+ new fjs.PropertyFormatter("statsReset"),
+ new fjs.PropertyFormatter("statsGlobal"),
+ new fjs.PropertyFormatter("statsUser"),
+ new fjs.PropertyFormatter("statsUserUser"),
+ new fjs.PropertyFormatter("statsTicket"),
+ new fjs.PropertyFormatter("statsTicketTicket"),
+ new fjs.TextFormatter(""),
+ new fjs.PropertyFormatter("clear"),
+ new fjs.PropertyFormatter("clearFilter"),
+ new fjs.ObjectFormatter("clearFilters",true,[
+ new fjs.PropertyFormatter("all"),
+ new fjs.PropertyFormatter("open"),
+ new fjs.PropertyFormatter("close"),
+ new fjs.PropertyFormatter("claim"),
+ new fjs.PropertyFormatter("unclaim"),
+ new fjs.PropertyFormatter("pin"),
+ new fjs.PropertyFormatter("unpin"),
+ new fjs.PropertyFormatter("autoclose"),
+ ]),
+ new fjs.TextFormatter(""),
+ new fjs.PropertyFormatter("autoclose"),
+ new fjs.PropertyFormatter("autocloseDisable"),
+ new fjs.PropertyFormatter("autocloseEnable"),
+ new fjs.PropertyFormatter("autocloseEnableTime"),
+ new fjs.PropertyFormatter("autodelete"),
+ new fjs.PropertyFormatter("autodeleteDisable"),
+ new fjs.PropertyFormatter("autodeleteEnable"),
+ new fjs.PropertyFormatter("autodeleteEnableTime"),
+ new fjs.TextFormatter(""),
+ new fjs.PropertyFormatter("topic"),
+ new fjs.PropertyFormatter("topicSet"),
+ new fjs.PropertyFormatter("topicValue"),
+ new fjs.PropertyFormatter("topicList"),
+ new fjs.PropertyFormatter("priority"),
+ new fjs.PropertyFormatter("prioritySet"),
+ new fjs.PropertyFormatter("priorityValue"),
+ new fjs.PropertyFormatter("priorityGet"),
+ new fjs.PropertyFormatter("priorityList"),
+ new fjs.PropertyFormatter("transfer"),
+ new fjs.PropertyFormatter("transferUser"),
+ ]),
+ new fjs.ObjectFormatter("helpMenu",true,[
+ new fjs.PropertyFormatter("help"),
+ new fjs.PropertyFormatter("ticket"),
+ new fjs.PropertyFormatter("close"),
+ new fjs.PropertyFormatter("delete"),
+ new fjs.PropertyFormatter("reopen"),
+ new fjs.PropertyFormatter("pin"),
+ new fjs.PropertyFormatter("unpin"),
+ new fjs.PropertyFormatter("move"),
+ new fjs.PropertyFormatter("rename"),
+ new fjs.PropertyFormatter("claim"),
+ new fjs.PropertyFormatter("unclaim"),
+ new fjs.PropertyFormatter("add"),
+ new fjs.PropertyFormatter("remove"),
+ new fjs.PropertyFormatter("panel"),
+ new fjs.PropertyFormatter("blacklistView"),
+ new fjs.PropertyFormatter("blacklistAdd"),
+ new fjs.PropertyFormatter("blacklistRemove"),
+ new fjs.PropertyFormatter("blacklistGet"),
+ new fjs.PropertyFormatter("statsGlobal"),
+ new fjs.PropertyFormatter("statsTicket"),
+ new fjs.PropertyFormatter("statsUser"),
+ new fjs.PropertyFormatter("statsReset"),
+ new fjs.PropertyFormatter("autocloseDisable"),
+ new fjs.PropertyFormatter("autocloseEnable"),
+ new fjs.PropertyFormatter("autodeleteDisable"),
+ new fjs.PropertyFormatter("autodeleteEnable"),
+ new fjs.ObjectFormatter("categories",true,[
+ new fjs.PropertyFormatter("general"),
+ new fjs.PropertyFormatter("basicTicket"),
+ new fjs.PropertyFormatter("advancedTicket"),
+ new fjs.PropertyFormatter("userTicket"),
+ new fjs.PropertyFormatter("admin"),
+ new fjs.PropertyFormatter("advanced"),
+ new fjs.PropertyFormatter("extra"),
+ ])
+ ]),
+ new fjs.ObjectFormatter("stats",true,[
+ new fjs.ObjectFormatter("scopes",true,[
+ new fjs.PropertyFormatter("global"),
+ new fjs.PropertyFormatter("system"),
+ new fjs.PropertyFormatter("user"),
+ new fjs.PropertyFormatter("ticket"),
+ new fjs.PropertyFormatter("participants"),
+ new fjs.PropertyFormatter("messages"),
+ ]),
+ new fjs.ObjectFormatter("properties",true,[
+ new fjs.PropertyFormatter("ticketsCreated"),
+ new fjs.PropertyFormatter("ticketsClosed"),
+ new fjs.PropertyFormatter("ticketsDeleted"),
+ new fjs.PropertyFormatter("ticketsReopened"),
+ new fjs.PropertyFormatter("ticketsAutoclosed"),
+ new fjs.PropertyFormatter("ticketsClaimed"),
+ new fjs.PropertyFormatter("ticketsPinned"),
+ new fjs.PropertyFormatter("ticketsMoved"),
+ new fjs.PropertyFormatter("usersBlacklisted"),
+ new fjs.PropertyFormatter("transcriptsCreated"),
+ new fjs.PropertyFormatter("ticketsAutodeleted"),
+ new fjs.PropertyFormatter("ticketsTransferred"),
+ new fjs.PropertyFormatter("ticketVolume"),
+ new fjs.PropertyFormatter("averageTickets"),
+ new fjs.PropertyFormatter("currentTickets"),
+ new fjs.PropertyFormatter("age"),
+ new fjs.PropertyFormatter("responseTime"),
+ new fjs.PropertyFormatter("resolutionTime"),
+ new fjs.PropertyFormatter("createdOn"),
+ new fjs.PropertyFormatter("createdBy"),
+ new fjs.PropertyFormatter("closedOn"),
+ new fjs.PropertyFormatter("closedBy"),
+ new fjs.PropertyFormatter("claimedOn"),
+ new fjs.PropertyFormatter("claimedBy"),
+ new fjs.PropertyFormatter("pinnedOn"),
+ new fjs.PropertyFormatter("pinnedBy"),
+ new fjs.PropertyFormatter("deletedOn"),
+ new fjs.PropertyFormatter("deletedBy"),
+ ]),
+ new fjs.ObjectFormatter("roles",true,[
+ new fjs.PropertyFormatter("developer"),
+ new fjs.PropertyFormatter("serverOwner"),
+ new fjs.PropertyFormatter("serverAdmin"),
+ new fjs.PropertyFormatter("moderator"),
+ new fjs.PropertyFormatter("support"),
+ new fjs.PropertyFormatter("member"),
+ ])
+ ]),
+ new fjs.ObjectFormatter("panel",true,[
+ new fjs.PropertyFormatter("selectTicket"),
+ new fjs.PropertyFormatter("selectRole"),
+ new fjs.PropertyFormatter("selectOption"),
+ ]),
+ new fjs.ObjectFormatter("priorities",true,[
+ new fjs.PropertyFormatter("urgent"),
+ new fjs.PropertyFormatter("veryHigh"),
+ new fjs.PropertyFormatter("high"),
+ new fjs.PropertyFormatter("normal"),
+ new fjs.PropertyFormatter("low"),
+ new fjs.PropertyFormatter("veryLow"),
+ new fjs.PropertyFormatter("none"),
+ ]),
+])
+
+for (const language of fs.readdirSync(".docs/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())
+
+ for (const key of Object.keys(newSentences)){
+ if (key.startsWith("_")) continue
+ try{
+ const splitted = key.split(".")
+ let currentObject = original
+ splitted.forEach((property,index) => {
+ let shouldBeObject = (splitted.length-1 !== index)
+ if (shouldBeObject && typeof currentObject[property] == "object"){
+ currentObject = currentObject[property]
+ }else if (shouldBeObject && typeof currentObject[property] == "undefined"){
+ currentObject[property] = {}
+ currentObject = currentObject[property]
+ }else if (typeof currentObject[property] == "string" || typeof currentObject[property] == "undefined"){
+ currentObject[property] = newSentences[key]
+ }else{
+ console.log("Failed to merge key:",key,"in file:",language,"--> Invalid type:",typeof currentObject[property])
+ }
+ })
+ }catch(err){
+ console.log("Failed to merge key:",key,"in file:",language)
+ }
+ }
+ original["_TRANSLATION"]["lastedited"] = new Date().toLocaleDateString("nl-BE",{day:"2-digit",month:"2-digit",year:"numeric"})
+ original["_TRANSLATION"]["otversion"] = "v4.1.0"
+ const finalText = formatter.stringify(original)
+ fs.writeFileSync("./languages/"+language,finalText)
+}
\ No newline at end of file
diff --git a/.eggs/README.md b/.eggs/README.md
index e439378..11f0ae8 100644
--- a/.eggs/README.md
+++ b/.eggs/README.md
@@ -2,7 +2,7 @@
[](https://discord.com/invite/26vT9wt3n3)
-[](https://github.com/open-discord-bots/open-ticket/releases/tag/v4.0.7)
+[](https://github.com/open-discord-bots/open-ticket/releases/tag/v4.1.0)
[](https://github.com/sponsors/DJj123dj)
[](.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.1.0.json`**](openticket-egg-v4.1.0.json)
+- This egg will always use Open Ticket `v4.1.0`. Open Ticket updates will not have an effect on this egg.
+
[**`openticket-egg-v4.0.7.json`**](openticket-egg-v4.0.7.json)
- This egg will always use Open Ticket `v4.0.7`. Open Ticket updates will not have an effect on this egg.
@@ -54,6 +57,6 @@ It's recommended to provide at least `1GB` of **Memory/RAM** and `5GB` of **disk
**Pterodactyl Eggs**
-[changelog](https://otgithub.dj-dj.be/releases) - [documentation](https://otdocs.dj-dj.be) - [tutorial](https://www.youtube.com/watch?v=2jK9kAf6ASU) - [website](https://openticket.dj-dj.be) - [discord](https://discord.dj-dj.be)
+[Changelog](https://otgithub.dj-dj.be/releases) - [Documentation](https://otdocs.dj-dj.be) - [Website](https://openticket.dj-dj.be) - [Support Server](https://discord.dj-dj.be) - [License](./LICENSE.md)
-ยฉ 2025 - [DJdj Development](https://www.dj-dj.be) - [Terms](https://www.dj-dj.be/terms#terms) - [Privacy Policy](https://www.dj-dj.be/terms#privacy)
\ No newline at end of file
+ยฉ 2025 - [DJdj Development](https://www.dj-dj.be) - [Terms](https://www.dj-dj.be/terms) - [Privacy Policy](https://www.dj-dj.be/privacy) - [Support Us](https://github.com/sponsors/DJj123dj)
\ No newline at end of file
diff --git a/.eggs/openticket-egg-v4.1.0.json b/.eggs/openticket-egg-v4.1.0.json
new file mode 100644
index 0000000..bb91ba9
--- /dev/null
+++ b/.eggs/openticket-egg-v4.1.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.1.0)",
+ "author": "support@dj-dj.be",
+ "description": "This is the official Pterodactyl egg for Open Ticket v4.1.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.1.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.1.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/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md
index 087a2c9..c2241d5 100644
--- a/.github/CONTRIBUTING.md
+++ b/.github/CONTRIBUTING.md
@@ -1,7 +1,7 @@
# Contributing Guidelines
-[](https://discord.com/invite/26vT9wt3n3) [](https://github.com/open-discord-bots/open-ticket/releases/tag/v4.0.7) [](https://github.com/sponsors/DJj123dj)
+[](https://discord.com/invite/26vT9wt3n3) [](https://github.com/open-discord-bots/open-ticket/releases/tag/v4.1.0) [](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.
@@ -87,6 +87,6 @@ you are **REQUIRED to send the bug privately via one of the following methods:**
**Contributing Guidelines**
-[changelog](https://otgithub.dj-dj.be/releases) - [documentation](https://otdocs.dj-dj.be) - [tutorial](https://www.youtube.com/watch?v=2jK9kAf6ASU) - [website](https://openticket.dj-dj.be) - [discord](https://discord.dj-dj.be)
+[Changelog](https://otgithub.dj-dj.be/releases) - [Documentation](https://otdocs.dj-dj.be) - [Website](https://openticket.dj-dj.be) - [Support Server](https://discord.dj-dj.be) - [License](./LICENSE.md)
-ยฉ 2025 - [DJdj Development](https://www.dj-dj.be) - [Terms](https://www.dj-dj.be/terms#terms) - [Privacy Policy](https://www.dj-dj.be/terms#privacy)
\ No newline at end of file
+ยฉ 2025 - [DJdj Development](https://www.dj-dj.be) - [Terms](https://www.dj-dj.be/terms) - [Privacy Policy](https://www.dj-dj.be/privacy) - [Support Us](https://github.com/sponsors/DJj123dj)
\ No newline at end of file
diff --git a/.github/SECURITY.md b/.github/SECURITY.md
index 9e7a2b7..36e9aaf 100644
--- a/.github/SECURITY.md
+++ b/.github/SECURITY.md
@@ -17,18 +17,17 @@ This list will be updated on every release.
- ๐ง Deprecated **(docs)**
- โ Fully Deprecated
-| Version | Supported | Notes |
-|------------|-----------|------------------------------------------|
-| 4.1.0 | ๐ฆ | |
-| 4.0.7 | โ
| |
-| 4.0.6 | โ
| |
-| 4.0.5 | โ
| |
-| 4.0.4 | โ
| Supported until July 2025 (LTS) |
-| 4.0.3 | ๐ง | Transcripts v2.0 (Offline August 2025) |
-| 4.0.2 | ๐ง | Documentation Only (discord.js bug) |
-| 4.0.1 | ๐ง | Documentation Only |
-| 4.0.0 | ๐ง | Documentation Only |
-| < 4.0.0 | โ | |
+| Version | Supported | Notes |
+|------------|-----------|---------------------------------------------------------------|
+| 4.2.0 | ๐ฆ | Transcripts v2.0 will be taken offline when v4.2 is released. |
+| 4.1.1 | ๐ฆ | |
+| 4.1.0 | โ
| |
+| 4.0.7 | โ
| Supported Until October 2025 (LTS) |
+| 4.0.6 | ๐ง | |
+| 4.0.5 | ๐ง | |
+| 4.0.4 | ๐ง | |
+| < 4.0.4 | ๐ง | Deprecated Transcripts v2.0, Documentation Only |
+| < 4.0.0 | โ | |
### ๐ท๏ธ Reporting Vulnerabilities
You can report vulnerabilities, errors & bugs using one of the following methods:
@@ -48,6 +47,6 @@ If possible, try to provide screenshots!
**Security Policy**
-[changelog](https://otgithub.dj-dj.be/releases) - [documentation](https://otdocs.dj-dj.be) - [tutorial](https://www.youtube.com/watch?v=2jK9kAf6ASU) - [website](https://openticket.dj-dj.be) - [discord](https://discord.dj-dj.be)
+[Changelog](https://otgithub.dj-dj.be/releases) - [Documentation](https://otdocs.dj-dj.be) - [Website](https://openticket.dj-dj.be) - [Support Server](https://discord.dj-dj.be) - [License](./LICENSE.md)
-ยฉ 2025 - [DJdj Development](https://www.dj-dj.be) - [Terms](https://www.dj-dj.be/terms#terms) - [Privacy Policy](https://www.dj-dj.be/terms#privacy)
\ No newline at end of file
+ยฉ 2025 - [DJdj Development](https://www.dj-dj.be) - [Terms](https://www.dj-dj.be/terms) - [Privacy Policy](https://www.dj-dj.be/privacy) - [Support Us](https://github.com/sponsors/DJj123dj)
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
index 3a10e77..243efae 100644
--- a/.gitignore
+++ b/.gitignore
@@ -19,4 +19,5 @@ otdebug.txt
.docs/*
!.docs/createDocs.js
+!.docs/mergeTranslations.js
!.docs/typedoc-config.json
\ No newline at end of file
diff --git a/LICENSE.md b/LICENSE.md
index a79bc68..c40fc44 100644
--- a/LICENSE.md
+++ b/LICENSE.md
@@ -1,6 +1,12 @@
+
# Open Ticket (License)
ยฉ 2025 - DJj123dj & Contributors
+#### Table Of Contents
+- [General License](#general-license)
+- [Additional Terms for Open Ticket](#additional-terms-for-open-ticket)
+
+### General License
```
GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
@@ -679,11 +685,28 @@ Public License instead of this License. But first, please read
```
### Additional Terms for Open Ticket
+#### Terminology:
+1. **โSoftwareโ**
+Refers to the Open Ticket source code, documentation, and any associated files distributed under this license, including any modifications or derivative works thereof.
+
+1. **โPublic Discord Bot**
+A Discord bot that is made publicly available for use by multiple, unrelated Discord servers or users, where the botโs functionality is provided as a service rather than for private or personal use.
+
+1. **โMonetized Serviceโ**
+Refers to any service or deployment that generates direct or indirect revenue through the use of the Software. This includes, but is not limited to, subscriptions, premium features, paid hosting, advertisements.
+
+1. **โFor-Profit Discord Serverโ**
+Means a Discord server that generates income, revenue, or other material benefits, whether through memberships, donations, or other monetization strategies.
+
+1. **โPrivate Useโ**
+Refers to using the Software on a limited number of Discord servers controlled or operated by a single individual or organization, not made available as a public service to multiple unrelated users.
+
+#### Additional Terms:
The following additional terms apply to the use of this software:
-1. This software may not be used as part of a public Discord bot service that is monetized (e.g., generating revenue through subscriptions, donations, or other forms of payment) without the prior written permission of the original author.
+1. This software may not be used as (or part of) a public Discord bot that is monetized (e.g., generating revenue through subscriptions, donations, or other forms of payment) without the prior written permission of the original author.
-2. This software may not be used as part of a public Discord bot service on multiple Discord servers without attribution to the original author.
+2. This software may not be used as (or part of) a public Discord bot on multiple Discord servers without attribution to the original author.
3. You are allowed to use this bot in a Discord server that makes money or is for-profit, provided that the income is not generated directly from the ticket bot or its functionality.
diff --git a/README.md b/README.md
index fd1e683..def8dbc 100644
--- a/README.md
+++ b/README.md
@@ -1,5 +1,5 @@
-> ### ๐ฅ Content Creators ๐ฅ
-> Hey there! We're searching for content creators that would want to create a tutorial or setup guide for Open Ticket!
+> ### ๐ฅ Content Creators
+> Hey there! We're searching for content creators that would like to create a tutorial or setup guide for Open Ticket!
> [๐ More Information](.github/CONTENT_CREATORS.md)
---
@@ -8,7 +8,7 @@
Powered By
-
+
@@ -19,48 +19,50 @@
-Open Ticket is 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!
-You're also able to customise every little aspect of the bot! From embeds to transcripts. Open Ticket is also translated in more than 36 Languages! If you need any help, feel free to join our discord server !
+Open Ticket is the most advanced & customisable discord ticket bot available! You are able to customise up to 300+ settings and aspects! This includes Html Transcripts, Advanced Plugins, Custom Embeds, Questions/Modals, Stats & more!
+The bot is translated in more than 36 Languages and has been battle tested in large Discord servers! If you need any help, feel free to join our discord server !
-โญ๏ธ Help us grow by giving a star! โญ๏ธ
+โญ๏ธ Help us grow by giving a star! โญ๏ธ
### ๐ Features
-- **๐ฆ pterodactyl support** - Open Ticket works perfect on Pterodactyl based panels! [(Download official eggs)](.eggs/README.md)
-- **๐ฉ no bloat** - Your Open Ticket bot doesn't contain any form of bloat or credits!
-- **๐ tested & secure** - Open Ticket has been battletested by thousands of servers and is very secure!
-- **๐ scalable** - Open Ticket is made to handle huge servers! (Already tested in **servers with 100k members**)
-- **๐ HTML transcripts** - Make use of the most customisable, beautiful and easy-to-use HTML Transcripts!
-- **โ
ticket actions** - Close, Reopen, Delete, Claim, Pin, Rename & Move all your tickets!
-- **๐ฌ๐ง translation** - Open Ticket has been translated in more than **36 languages** by our community!
-- **๐จ customisation** - Open Ticket has been created around customisation, everything can be customised!
-- **๐ฅ๏ธ interactions** - The bot has full support for Buttons, Dropdowns, Slash Commands and Modals!
-- **โ unlimited** - Create an infinite amount of tickets & panels!
-- **๐ advanced plugins** - Create advanced plugins or use pre-made ones by our community!
-- **๐ฅ user management** - Add & Remove users from all tickets!
-- **๐ detailed stats** - Open Ticket has ticket, user & global staticstics available for everyone!
-- **๐ซ blacklist** - Blacklist users to prevent them from creating a ticket!
-- **โ questions** - Let users answer questions in a modal before the ticket is created!
-- **๐ฆ slash & text** - Open Ticket supports both slash & text commands!
-- **๐ฅ extra buttons** - The bot also supports Reaction Roles & Url Buttons, because why not ยฏ\\_(ใ)_/ยฏ
-- **โจ commands** - The bot contains more than 25 commands!
+- **โณ 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 more using plugins!
- - **๐ฌ reviews** - Create & customise your own review system!
- - **๐ข feedback** - Collect feedback & create forms for people to answer!
- - **๐ท๏ธ tags** - Create tags & answer questions automatically using keywords!
- - **๐ forms** - Create advanced forms and ask people for additional details!
- - **๐ rotating status** - Create a rotating bot status & use dynamic variables from the bot!
- - **๐พ sqlite database** - Use an `sqlite` database for increased performances!
- - **๐ custom embeds** - Create your own embeds and use them in your server!
- - **โฐ reminders** - Create & manage customisable reminders in your server!
- - **๐จ customisation** - Yep, you heard it right! Even more customisation!
+#### 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...**
> ### ๐ฆ Resources
-> Not all resources are accurate yet! We are working on this.
->
->
+> These resources will help with configuration and usage of the bot:
+>
+>
>
### โค๏ธ Sponsors
@@ -83,19 +85,19 @@ A big thanks to all our sponsors! Without them, it wouldn't be possible to creat
-### โฑ๏ธ Quick Setup
+### โฑ๏ธ Quick Setup (Using CLI)
> 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 `v18`).
+> 2. Make sure node.js & npm are installed using `node -v` (minimum `v20`).
> 3. Install any required dependencies using `npm install`.
-> 4. Configure the bot in the `./config/` directory.
-> 5. Start the bot using `npm start` or `node index.js`
+> 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`
> - The bot will let you know any existing config errors.
> - Fix these errors and restart the bot.
-> 6. Enjoy using Open Ticket!
+> 7. Enjoy using Open Ticket!
> ### [๐ Visit Documentation](https://otdocs.dj-dj.be)
>
> ### ๐ฅ๏ธ Recommended Hostings
-> - [โญ **Peakhosting.nl**](https://peakhosting.nl/) - Official support for Open Ticket pterodactyl eggs.
> - **Any Pterodactyl-Based Panel** - Easy installation & configuration.
> - **A Virtual Private Server (VPS)** - Extra customisation & more stability. Recommended for large servers.
>
@@ -115,64 +117,69 @@ A list of people that contributed or provided the most support for Open Ticket.
-
+
๐ป DJj123dj
๐ฌ smetsliam
๐ฌ Frank Vissers
๐ฌ Sanke
-๐งฉ Roppl3r
+๐งฉ Guillee3
### ๐ฌ Translators
-With the amazing support of our translators, we've been able to translate Open Ticket in more than **36 languages**!
-As a result, you're able to enjoy using Open Ticket in your own native language.
-- **Categories: ๐ข Available - ๐ค Made Using AI - ๐ด Unavailable/Outdated**
+With the amazing support of our translators, we've been able to translate Open Ticket in more than **36 languages**!
+#### Categories:
+- **๐ข Available**
+- **โณ In Progress (Incomplete)**
+- **๐ค Made Using AI**
+- **๐ Incomplete**
+- **๐ด Unavailable/Outdated**
-|๐ |Languages (36) |Maintainer (Github/Discord) |
-|---|---------------------|--------------------------------|
-|๐ข |๐ฌ๐ง English |djj123dj |
-|๐ข |๐ณ๐ฑ Dutch |djj123dj |
-|๐ข |๐ฉ๐ช German |benzorich |
-|๐ข |๐ช๐ธ Spanish |redactado & josuens |
-|๐ข |๐ต๐น Portuguese |quiradon |
-|๐ข |โ Catalan |guillee3 |
-|๐ข |๐จ๐ฟ Czech |spyeye_ |
-|๐ข |๐ญ๐บ Hungarian |kornel0706 |
-|๐ข |๐ท๐ด Romanian |sankedev |
-|๐ข |๐บ๐ฆ Ukrainian |anderskiy |
-|๐ข |๐ฎ๐ฉ Indonesian |erxg |
-|๐ข |๐ฎ๐น Italian |fraden1mvp. |
-|๐ข |๐ฉ๐ฐ Danish |the_gamer |
-|๐ข |๐น๐ญ Thai |modshd |
-|๐ข |๐น๐ท Turkish |palestinian |
-|๐ข |๐ซ๐ท French |guillee.3 |
-|๐ข |๐ฆ๐ช Arabic |palestinian |
-|๐ข |๐ฎ๐ณ Hindi |an_developer |
-|๐ข |๐ฑ๐น Lithuanian |tsgindrius |
-|๐ข |๐ต๐ฑ Polish |danoglez |
-|๐ข |๐ณ๐ด Norwegian |NoOneNook |
-|๐ข |๐ธ๐ช Swedish |NoOneNook |
-|๐ข |๐ฎ๐ท Persian |dysashop & zhavis |
-|๐ข |๐ง๐ฉ Bengali |HanumeshGupta |
-|๐ค |๐ช๐ช Estonian |iamnotmega |
-|๐ค |๐ซ๐ฎ Finnish |iamnotmega |
-|๐ค |๐ท๐บ Russian |NoOneNook |
-|๐ค |๐ฑ๐ป Latvian |NoOneNook |
-|๐ค |๐ป๐ณ Vietnamese |ngocdiep2006 |
-|๐ค |๐ฏ๐ต Japanese |HanumeshGupta |
-|๐ค |๐ฌ๐ท Greek |HanumeshGupta |
-|๐ค |๐ธ๐ฎ Slovenian |HanumeshGupta |
-|๐ค |๐ฐ๐ท Korean |HanumeshGupta |
-|๐ค |๐ฎ๐ณ Tamil |HanumeshGupta |
-|๐ค |๐จ๐ณ Simplified Chinese |HanumeshGupta |
-|๐ค |โ Kurdish |HanumeshGupta |
-|๐ด |๐จ๐ณ Traditional Chinese|[โญ Contribute!](.github/CONTRIBUTING.md)|
+|๐ |Languages (36) |Maintainer (Github/Discord) |
+|----|---------------------|--------------------------------|
+|๐ข |๐ฌ๐ง English |djj123dj |
+|๐ข |๐ณ๐ฑ Dutch |djj123dj |
+|๐ข |โ Catalan |guillee3 |
+|๐ข |๐ฎ๐ฉ Indonesian |erxg |
+|๐ข |๐ฎ๐ณ Hindi |challenger_nova |
+|๐ขโณ |๐ฉ๐ช German |benzorich |
+|๐ขโณ |๐ช๐ธ Spanish |redactado & josuens |
+|๐ขโณ |๐ซ๐ท French |guillee.3 |
+|๐ขโณ |๐ต๐น Portuguese |quiradon |
+|๐ขโณ |๐จ๐ฟ Czech |spyeye_ |
+|๐ขโณ |๐ญ๐บ Hungarian |kornel0706 |
+|๐ขโณ |๐ท๐ด Romanian |sankedev |
+|๐ขโณ |๐บ๐ฆ Ukrainian |anderskiy |
+|๐ขโณ |๐ฎ๐น Italian |fraden1mvp. |
+|๐ขโณ |๐ฉ๐ฐ Danish |the_gamer |
+|๐ขโณ |๐น๐ญ Thai |modshd |
+|๐ขโณ |๐น๐ท Turkish |palestinian |
+|๐ขโณ |๐ฆ๐ช Arabic |palestinian |
+|๐ขโณ |๐ฑ๐น Lithuanian |tsgindrius |
+|๐ขโณ |๐ต๐ฑ Polish |danoglez |
+|๐ขโณ |๐ณ๐ด Norwegian |NoOneNook |
+|๐ขโณ |๐ธ๐ช Swedish |NoOneNook |
+|๐ขโณ |๐ฎ๐ท Persian |dysashop & zhavis |
+|๐ขโณ |๐ง๐ฉ Bengali |HanumeshGupta |
+|๐ค |๐ช๐ช Estonian |iamnotmega |
+|๐ค |๐ซ๐ฎ Finnish |iamnotmega |
+|๐คโณ |๐ท๐บ Russian |NoOneNook |
+|๐คโณ |๐ฑ๐ป Latvian |NoOneNook |
+|๐คโณ |๐ป๐ณ Vietnamese |ngocdiep2006 |
+|๐คโณ |๐ฏ๐ต Japanese |HanumeshGupta |
+|๐คโณ |๐ฌ๐ท Greek |HanumeshGupta |
+|๐คโณ |๐ธ๐ฎ Slovenian |HanumeshGupta |
+|๐คโณ |๐ฐ๐ท Korean |HanumeshGupta |
+|๐คโณ |๐ฎ๐ณ Tamil |HanumeshGupta |
+|๐คโณ |๐จ๐ณ Simplified Chinese |HanumeshGupta |
+|๐คโณ |โ Kurdish |HanumeshGupta |
+|๐ด |๐จ๐ณ Traditional Chinese|[โญ Contribute!](.github/CONTRIBUTING.md)|
+
## โญ๏ธ Star History
-Please give this repository a star if you like it.
+If you enjoy using Open ticket, **consider starring** this repository.
This will help us grow and reach even more people!
@@ -184,13 +191,21 @@ This will help us grow and reach even more people!
## ๐งฉ Plugins
-**Download all plugins in our [Official Plugin Repository](https://github.com/open-discord-bots/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-migrate-v3`](https://github.com/open-discord-bots/plugins/tree/main/open-ticket/ot-migrate-v3/),
> [`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-tags`](https://github.com/open-discord-bots/plugins/tree/main/open-ticket/ot-tags/),
+> [`ot-config-reload`](https://github.com/open-discord-bots/plugins/tree/main/open-ticket/ot-config-reload/)**
+
+#### 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.
### ๐ฆ Official *(made by DJdj Development)*
|Name |Category |Description |
@@ -227,18 +242,10 @@ This will help us grow and reach even more people!
|[`od-reminders`](https://github.com/open-discord-bots/plugins/tree/main/open-ticket/od-reminders/) |guillee.3 |๐ผ Management |Set reminders that will be sent to a channel every specified time. |
|[`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. |
-### ๐ข 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 backend & systems. You won't notice it in discord itself.
-- **๐จ Customisation** - These plugins help you customise the bot even further.
-- **๐ผ Management** - These plugins add features that help you manage your server.
-- **๐ค Client** - These plugins add features affecting the discord client or bot itself.
-
---
**README.md**
-[changelog](https://otgithub.dj-dj.be/releases) - [documentation](https://otdocs.dj-dj.be) - [tutorial](https://www.youtube.com/watch?v=2jK9kAf6ASU) - [website](https://openticket.dj-dj.be) - [discord server](https://discord.dj-dj.be) - [license](./LICENSE.md)
+[Changelog](https://otgithub.dj-dj.be/releases) - [Documentation](https://otdocs.dj-dj.be) - [Website](https://openticket.dj-dj.be) - [Support Server](https://discord.dj-dj.be) - [License](./LICENSE.md)
ยฉ 2025 - [DJdj Development](https://www.dj-dj.be) - [Terms](https://www.dj-dj.be/terms) - [Privacy Policy](https://www.dj-dj.be/privacy) - [Support Us](https://github.com/sponsors/DJj123dj)
diff --git a/config/general.json b/config/general.json
index ec55506..221ecda 100644
--- a/config/general.json
+++ b/config/general.json
@@ -2,10 +2,10 @@
"_INFO":{
"support":"https://otdocs.dj-dj.be",
"discord":"https://discord.dj-dj.be",
- "version":"open-ticket-v4.0.7"
+ "version":"open-ticket-v4.1.0"
},
- "token":"your bot token here! (or leave empty when using 'tokenFromENV')",
+ "token":"insert your bot token here! (or leave empty when using 'tokenFromENV')",
"tokenFromENV":false,
"mainColor":"#f8ba00",
@@ -20,21 +20,34 @@
"status":{
"enabled":true,
"type":"listening OR watching OR playing OR custom",
+ "mode":"online OR invisible OR idle OR dnd",
"text":"/help",
- "status":"online OR invisible OR idle OR dnd"
+ "state":"(additional text or leave empty)"
},
"system":{
- "removeParticipantsOnClose":false,
- "replyOnTicketCreation":true,
- "replyOnReactionRole":true,
- "useTranslatedConfigChecker":true,
"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,
@@ -54,6 +67,18 @@
"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)",
@@ -73,23 +98,28 @@
"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)"
+ "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},
- "roleAdding":{"dm":false, "logs":true},
- "roleRemoving":{"dm":false, "logs":true}
+ "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/options.json b/config/options.json
index 277a5ff..981a1bc 100644
--- a/config/options.json
+++ b/config/options.json
@@ -25,7 +25,7 @@
"claimedCategory":[
{"user":"user id","category":"category id"}
],
- "description":"This is a question ticket (or leave empty)"
+ "topic":"This is the topic of this ticket channel and is visible to everyone! (or leave empty)"
},
"dmMessage":{
@@ -87,6 +87,10 @@
"enabled":false,
"globalMaximum":20,
"userMaximum":3
+ },
+ "slowMode":{
+ "enabled":false,
+ "slowModeSeconds":20
}
},
{
diff --git a/config/panels.json b/config/panels.json
index 8b2d9d5..7c093b0 100644
--- a/config/panels.json
+++ b/config/panels.json
@@ -17,7 +17,7 @@
"image":"https://www.example.com/image.png (or leave empty)",
"thumbnail":"https://www.example.com/image.png (or leave empty)",
- "footer":"Open Ticket v4.0.7 (or leave empty)",
+ "footer":"Open Ticket v4.1.0 (or leave empty)",
"fields":[
{"name":"field name","value":"field value","inline":false}
],
diff --git a/index.js b/index.js
index 8d77b92..c0995a6 100644
--- a/index.js
+++ b/index.js
@@ -14,7 +14,7 @@ process.argv.push(...flags)
โโโ โโโโโโโโโโ โโโโโโ โโโโโโโโโโ โโโ โโโโโโ โโโโโโโ โโโโโโ โโโ
โโโโโโโโโโโโ โโโโโโโโโโโ โโโโโโ โโโ โโโโโโโโโโโโโโ โโโโโโโโโโโ โโโ
โโโโโโโ โโโ โโโโโโโโโโโ โโโโโ โโโ โโโ โโโโโโโโโโ โโโโโโโโโโโ โโโ
- v4.0.7 - Made by DJj123dj & Contributors
+ v4.1.0 - Made by DJj123dj & Contributors
Discord: https://discord.dj-dj.be
Docs: https://otdocs.dj-dj.be
diff --git a/languages/arabic.json b/languages/arabic.json
index 28419b6..82cb98e 100644
--- a/languages/arabic.json
+++ b/languages/arabic.json
@@ -1,6 +1,6 @@
{
"_TRANSLATION": {
- "otversion": "v4.0.7",
+ "otversion": "v4.1.0",
"translators": ["palestinian"],
"lastedited": "06/12/2024",
"language": "Arabic",
diff --git a/languages/bengali.json b/languages/bengali.json
index 096246a..ad3d921 100644
--- a/languages/bengali.json
+++ b/languages/bengali.json
@@ -1,6 +1,6 @@
{
"_TRANSLATION":{
- "otversion":"v4.0.7",
+ "otversion":"v4.1.0",
"translators":["HanumeshGupta"],
"lastedited":"19/06/2025",
"language":"Bengali",
diff --git a/languages/catalan.json b/languages/catalan.json
index 34b078f..5f09dce 100644
--- a/languages/catalan.json
+++ b/languages/catalan.json
@@ -1,478 +1,608 @@
-{
- "_TRANSLATION":{
- "otversion":"v4.0.7",
- "translators":["guillee3"],
- "lastedited":"21/08/2024",
- "language":"Catalan",
- "automated":false
- },
- "checker":{
- "system":{
- "typeError":"[ERROR]",
- "headerOpenTicket":"OPEN TICKET",
- "typeWarning":"[AVรS]",
- "typeInfo":"[INFO]",
- "headerConfigChecker":"VERIFICADOR DE CONFIGURACIร",
- "headerDescription":"comprova si existeixen errors als teus fitxers de configuraciรณ!",
- "footerError":"el bot no s'iniciarร fins que tots els {0} es solucionin!",
- "footerWarning":"es recomana solucionar tots els {0} abans de comenรงar!",
- "footerSupport":"SUPORT: {0} - DOCUMENTACIร: {1}",
- "compactInformation":"utilitza {0} per obtenir mรฉs informaciรณ!",
- "dataPath":"ruta",
- "dataDocs":"documents",
- "dataMessages":"missatge"
- },
- "messages":{
- "stringTooShort":"Aquesta cadena no pot tenir menys de {0} carร cters!",
- "stringTooLong":"Aquesta cadena no pot tenir mรฉs de {0} carร cters!",
- "stringLengthInvalid":"Aquesta cadena ha de tenir {0} carร cters!",
- "stringStartsWith":"Aquesta cadena ha de comenรงar amb {0}!",
- "stringEndsWith":"Aquesta cadena ha d'acabar amb {0}!",
- "stringContains":"Aquesta cadena ha de contenir {0}!",
- "stringChoices":"Aquesta cadena nomรฉs pot ser un dels valors segรผents: {0}!",
- "stringRegex":"Aquesta cadena รฉs invร lida!",
-
- "numberTooShort":"Aquest nombre no pot tenir menys de {0} carร cters!",
- "numberTooLong":"Aquest nombre no pot tenir mรฉs de {0} carร cters!",
- "numberLengthInvalid":"Aquest nombre ha de tenir {0} carร cters!",
- "numberTooSmall":"Aquest nombre ha de ser almenys {0}!",
- "numberTooLarge":"Aquest nombre ha de ser com a mร xim {0}!",
- "numberNotEqual":"Aquest nombre ha de ser {0}!",
- "numberStep":"Aquest nombre ha de ser un mรบltiple de {0}!",
- "numberStepOffset":"Aquest nombre ha de ser un mรบltiple de {0} comenรงant amb {1}!",
- "numberStartsWith":"Aquest nombre ha de comenรงar amb {0}!",
- "numberEndsWith":"Aquest nombre ha d'acabar amb {0}!",
- "numberContains":"Aquest nombre ha de contenir {0}!",
- "numberChoices":"Aquest nombre nomรฉs pot ser un dels valors segรผents: {0}!",
- "numberFloat":"Aquest nombre no pot ser un decimal!",
- "numberNegative":"Aquest nombre no pot ser negatiu!",
- "numberPositive":"Aquest nombre no pot ser positiu!",
- "numberZero":"Aquest nombre no pot ser zero!",
-
- "booleanTrue":"Aquest booleร no pot ser veritable!",
- "booleanFalse":"Aquest booleร no pot ser fals!",
-
- "arrayEmptyDisabled":"Aquest array no pot estar buit!",
- "arrayEmptyRequired":"Aquest array ha d'estar buit!",
- "arrayTooShort":"Aquest array ha de tenir una longitud d'almenys {0}!",
- "arrayTooLong":"Aquest array ha de tenir una longitud mร xima de {0}!",
- "arrayLengthInvalid":"Aquest array ha de tenir una longitud de {0}!",
- "arrayInvalidTypes":"Aquest array nomรฉs pot contenir els tipus segรผents: {0}!",
- "arrayDouble":"Aquest array no permet el mateix valor dues vegades!",
-
- "discordInvalidId":"Aquest รฉs un id de {0} de discord invร lid!",
- "discordInvalidIdOptions":"Aquest รฉs un id de {0} de discord invร lid! Tambรฉ en pots utilitzar un d'aquests: {1}!",
- "discordInvalidToken":"Aquest รฉs un token de discord invร lid (sintร cticament)!",
- "colorInvalid":"Aquest รฉs un color hex invร lid!",
- "emojiTooShort":"Aquesta cadena ha de tenir almenys {0} emojis!",
- "emojiTooLong":"Aquesta cadena ha de tenir com a mร xim {0} emojis!",
- "emojiCustom":"Aquest emoji no pot ser un emoji personalitzat de discord!",
- "emojiInvalid":"Aquest รฉs un emoji invร lid!",
- "urlInvalid":"Aquesta url รฉs invร lida!",
- "urlInvalidHttp":"Aquesta url nomรฉs pot utilitzar el protocol https://!",
- "urlInvalidProtocol":"Aquesta url nomรฉs pot utilitzar els protocols http:// i https://!",
- "urlInvalidHostname":"Aquesta url tรฉ un nom de domini no permรจs!",
- "urlInvalidExtension":"Aquesta url tรฉ una extensiรณ invร lida! Tria entre: {0}!",
- "urlInvalidPath":"Aquesta url tรฉ un camรญ invร lid!",
- "idNotUnique":"Aquest id no รฉs รบnic, utilitza un altre id!",
- "idNonExistent":"L'id {0} no existeix!",
-
- "invalidType":"Aquesta propietat ha de ser del tipus: {0}!",
- "propertyMissing":"La propietat {0} falta en aquest objecte!",
- "propertyOptional":"La propietat {0} รฉs opcional en aquest objecte!",
- "objectDisabled":"Aquest objecte estร desactivat, activa'l utilitzant {0}!",
- "nullInvalid":"Aquesta propietat no pot ser nulยทla!",
- "switchInvalidType":"Aixรฒ ha de ser d'un dels tipus segรผents: {0}!",
- "objectSwitchInvalid":"Aquest objecte ha de ser d'un dels tipus segรผents: {0}!",
-
- "invalidLanguage":"Aquest รฉs un idioma invร lid!",
- "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'!"
- }
- },
- "actions":{
- "buttons":{
- "create":"Veure Ticket",
- "close":"Tancar Ticket",
- "delete":"Eliminar Ticket",
- "reopen":"Reobrir Ticket",
- "claim":"Reclamar Ticket",
- "unclaim":"Alliberar Ticket",
- "pin":"Fixar Ticket",
- "unpin":"Desfixar Ticket",
- "clear":"Eliminar Tickets",
- "helpSwitchSlash":"Veure Comandes Slash",
- "helpSwitchText":"Veure Comandes de Text",
- "helpPage":"Pร gina {0}",
- "withReason":"Amb Motiu",
- "withoutTranscript":"Sense Transcripciรณ"
- },
- "titles":{
- "created":"Ticket Creat",
- "close":"Ticket Tancat",
- "delete":"Ticket Eliminat",
- "reopen":"Ticket Reobert",
- "claim":"Ticket Reclamat",
- "unclaim":"Ticket Alliberat",
- "pin":"Ticket Fixat",
- "unpin":"Ticket Desfixat",
- "rename":"Ticket Renomenat",
- "move":"Ticket Mogut",
- "add":"Usuari Afegit al Ticket",
- "remove":"Usuari Eliminat del Ticket",
-
- "help":"Comandes Disponibles",
- "statsReset":"Restablir Estadรญstiques",
- "blacklistAdd":"Usuari a la Llista Negra",
- "blacklistRemove":"Usuari Alliberat",
- "blacklistGet":"Usuari a la Llista Negra",
- "blacklistView":"Llista Negra Actual",
- "blacklistAddDm":"Afegit a la Llista Negra",
- "blacklistRemoveDm":"Eliminat de la Llista Negra",
- "clear":"Tickets Netejats",
- "roles":"Rols Actualitzats",
-
- "autoclose":"Ticket Tancat Automร ticament",
- "autocloseEnabled":"Tancament Automร tic Activat",
- "autocloseDisabled":"Tancament Automร tic Desactivat",
- "autodelete":"Ticket Eliminat Automร ticament",
- "autodeleteEnabled":"Eliminaciรณ Automร tica Activada",
- "autodeleteDisabled":"Eliminaciรณ Automร tica Desactivada"
- },
- "descriptions":{
- "create":"El teu ticket ha estat creat. Fes clic al botรณ a continuaciรณ per accedir-hi!",
- "close":"El ticket ha estat tancat amb รจxit!",
- "delete":"El ticket ha estat eliminat amb รจxit!",
- "reopen":"El ticket ha estat reobert amb รจxit!",
- "claim":"El ticket ha estat reclamat amb รจxit!",
- "unclaim":"El ticket ha estat alliberat amb รจxit!",
- "pin":"El ticket ha estat fixat amb รจxit!",
- "unpin":"El ticket ha estat desfixat amb รจxit!",
- "rename":"El ticket ha estat renomenat a {0} amb รจxit!",
- "move":"El ticket ha estat mogut a {0} amb รจxit!",
- "add":"{0} ha estat afegit al ticket amb รจxit!",
- "remove":"{0} ha estat eliminat del ticket amb รจxit!",
-
- "helpExplanation":"`` => parร metre requerit\n`[nom]` => parร metre opcional",
- "statsReset":"Les estadรญstiques del bot s'han restablert correctament!",
- "statsError":"No es poden veure les estadรญstiques del ticket!\n{0} no รฉs un ticket!",
- "blacklistAdd":"{0} ha estat afegit a la llista negra amb รจxit!",
- "blacklistRemove":"{0} ha estat alliberat amb รจxit!",
- "blacklistGetSuccess":"{0} estร actualment a la llista negra!",
- "blacklistGetEmpty":"{0} actualment no estร a la llista negra!",
- "blacklistViewEmpty":"Encara no hi ha ningรบ a la llista negra!",
- "blacklistViewTip":"Utilitza \"/blacklist add\" per afegir un usuari a la llista negra!",
- "clearVerify":"Estร s segur que vols eliminar mรบltiples tickets?\nAquesta acciรณ no es pot desfer!",
- "clearReady":"{0} tickets han estat eliminats amb รจxit!",
- "rolesEmpty":"No s'han actualitzat rols!",
-
- "autocloseLeave":"Aquest ticket ha estat tancat automร ticament perquรจ el creador ha abandonat el servidor!",
- "autocloseTimeout":"Aquest ticket ha estat tancat automร ticament perquรจ ha estat inactiu durant mรฉs de `{0}h`!",
- "autodeleteLeave":"Aquest ticket ha estat eliminat automร ticament perquรจ el creador ha abandonat el servidor!",
- "autodeleteTimeout":"Aquest ticket ha estat eliminat automร ticament perquรจ ha estat inactiu durant mรฉs de `{0} dies`!",
- "autocloseEnabled":"El tancament automร tic ha estat activat en aquest ticket!\nEs tancarร quan estigui inactiu durant mรฉs de `{0}h`!",
- "autocloseDisabled":"El tancament automร tic ha estat desactivat en aquest ticket!\nJa no es tancarร automร ticament!",
- "autodeleteEnabled":"L'eliminaciรณ automร tica ha estat activada en aquest ticket!\nS'eliminarร quan estigui inactiu durant mรฉs de `{0} dies`!",
- "autodeleteDisabled":"L'eliminaciรณ automร tica ha estat desactivada en aquest ticket!\nJa no s'eliminarร automร ticament!",
-
- "ticketMessageLimit":"Nomรฉs pots crear {0} ticket(s) al mateix temps!",
- "ticketMessageAutoclose":"Aquest ticket es tancarร automร ticament quan estigui inactiu durant {0}h!",
- "ticketMessageAutodelete":"Aquest ticket es eliminarร automร ticament quan estigui inactiu durant {0} dies!",
- "panelReady":"Pots trobar el panell a continuaciรณ!\nAquest missatge ara es pot eliminar!"
- },
- "modal":{
- "closePlaceholder":"Per quรจ has tancat aquest ticket?",
- "deletePlaceholder":"Per quรจ has eliminat aquest ticket?",
- "reopenPlaceholder":"Per quรจ has reobert aquest ticket?",
- "claimPlaceholder":"Per quรจ has reclamat aquest ticket?",
- "unclaimPlaceholder":"Per quรจ has alliberat aquest ticket?",
- "pinPlaceholder":"Per quรจ has fixat aquest ticket?",
- "unpinPlaceholder":"Per quรจ has desfixat aquest ticket?"
- },
- "logs":{
- "createLog":"Un nou ticket ha estat creat per {0}!",
- "closeLog":"Aquest ticket ha estat tancat per {0}!",
- "closeDm":"El teu ticket ha estat tancat al nostre servidor!",
- "deleteLog":"Aquest ticket ha estat eliminat per {0}!",
- "deleteDm":"El teu ticket ha estat eliminat al nostre servidor!",
- "reopenLog":"Aquest ticket ha estat reobert per {0}!",
- "reopenDm":"El teu ticket ha estat reobert al nostre servidor!",
- "claimLog":"Aquest ticket ha estat reclamat per {0}!",
- "claimDm":"El teu ticket ha estat reclamat al nostre servidor!",
- "unclaimLog":"Aquest ticket ha estat alliberat per {0}!",
- "unclaimDm":"El teu ticket ha estat alliberat al nostre servidor!",
- "pinLog":"Aquest ticket ha estat fixat per {0}!",
- "pinDm":"El teu ticket ha estat fixat al nostre servidor!",
- "unpinLog":"Aquest ticket ha estat desfixat per {0}!",
- "unpinDm":"El teu ticket ha estat desfixat al nostre servidor!",
- "renameLog":"Aquest ticket ha estat renombrat a {0} per {1}!",
- "renameDm":"El teu ticket ha estat renombrat a {0} al nostre servidor!",
- "moveLog":"Aquest ticket ha estat mogut a {0} per {1}!",
- "moveDm":"El teu ticket ha estat mogut a {0} al nostre servidor!",
- "addLog":"{0} ha estat afegit a aquest ticket per {1}!",
- "addDm":"{0} ha estat afegit al teu ticket al nostre servidor!",
- "removeLog":"{0} ha estat eliminat d'aquest ticket per {1}!",
- "removeDm":"{0} ha estat eliminat del teu ticket al nostre servidor!",
-
- "blacklistAddLog":"{0} ha estat afegit a la llista negra per {1}!",
- "blacklistRemoveLog":"{0} ha estat eliminat de la llista negra per {1}!",
- "blacklistAddDm":"Has estat afegit a la llista negra al nostre servidor!\nA partir d'ara, no pots crear cap ticket!",
- "blacklistRemoveDm":"Has estat eliminat de la llista negra al nostre servidor!\nAra pots crear tickets de nou!",
- "clearLog":"{0} tickets han estat eliminats per {1}!"
- }
- },
- "transcripts":{
- "success":{
- "visit":"Veure Transcripciรณ",
- "ready":"Transcripciรณ Creada",
- "textFileDescription":"Aquesta รฉs la transcripciรณ de text d'un ticket eliminat!",
- "htmlProgress":"Si us plau, espera mentre aquesta transcripciรณ html es processa...",
-
- "createdChannel":"S'ha creat una nova transcripciรณ tipus {0} al servidor!",
- "createdCreator":"S'ha creat una nova transcripciรณ tipus {0} per un dels teus tickets!",
- "createdParticipant":"S'ha creat una nova transcripciรณ tipus {0} en un dels tickets on has participat!",
- "createdActiveAdmin":"S'ha creat una nova transcripciรณ tipus {0} en un dels tickets on has participat com a administrador!",
- "createdEveryAdmin":"S'ha creat una nova transcripciรณ tipus {0} en un dels tickets on eres administrador!",
- "createdOther":"S'ha creat una nova transcripciรณ tipus {0}!"
- },
- "errors":{
- "retry":"Torna-ho a intentar",
- "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."
- }
- },
- "errors":{
- "titles":{
- "internalError":"Error Intern",
- "optionMissing":"Opciรณ de Comanda Absent",
- "optionInvalid":"Opciรณ de Comanda Invร lida",
- "unknownCommand":"Comanda Desconeguda",
- "noPermissions":"No Tens Permisos",
- "unknownTicket":"Ticket Desconegut",
- "deprecatedTicket":"Ticket Obsolet",
- "unknownOption":"Opciรณ Desconeguda",
- "unknownPanel":"Panell Desconegut",
- "notInGuild":"No Ets Al Servidor",
- "channelRename":"No Es Pot Canviar El Nom Del Canal",
- "busy":"El Ticket Estร Ocupat"
- },
- "descriptions":{
- "askForInfo":"Contacta amb el propietari d'aquest bot per obtenir mรฉs informaciรณ!",
- "askForInfoResolve":"Contacta amb el propietari del bot si aquest problema no es resol desprรฉs de diversos intents.",
- "internalError":"No s'ha pogut respondre a aquest {0} a causa d'un error intern!",
- "optionMissing":"Falta un parร metre requerit en aquesta comanda!",
- "optionInvalid":"Un parร metre d'aquesta comanda รฉs invร lid!",
- "optionInvalidChoose":"Tria entre",
- "unknownCommand":"Prova de visitar el menรบ d'ajuda per obtenir mรฉs informaciรณ!",
- "noPermissions":"No tens permรญs per utilitzar aquest {0}!",
- "noPermissionsList":"Permisos Requerits: (un d'ells)",
- "noPermissionsCooldown":"No tens permรญs per utilitzar aquest {0} perquรจ tens un temps de refredament!",
- "noPermissionsBlacklist":"No tens permรญs per utilitzar aquest {0} perquรจ estร s a la llista negra!",
- "noPermissionsLimitGlobal":"No tens permรญs per crear un ticket perquรจ el servidor ha arribat al lรญmit mร xim de tickets!",
- "noPermissionsLimitGlobalUser":"No tens permรญs per crear un ticket perquรจ has arribat al lรญmit mร xim de tickets!",
- "noPermissionsLimitOption":"No tens permรญs per crear un ticket perquรจ el servidor ha arribat al lรญmit mร xim de tickets per aquesta opciรณ!",
- "noPermissionsLimitOptionUser":"No tens permรญs per crear un ticket perquรจ has arribat al lรญmit mร xim de tickets per aquesta opciรณ!",
- "unknownTicket":"Torna a intentar aquesta comanda en un ticket vร lid!",
- "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.",
- "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!"
- },
- "optionInvalidReasons":{
- "stringRegex":"El valor no coincideix amb el patrรณ!",
- "stringMinLength":"El valor ha de tenir almenys {0} carร cters!",
- "stringMaxLength":"El valor ha de tenir com a mร xim {0} carร cters!",
- "numberInvalid":"Nombre invร lid!",
- "numberMin":"El nombre ha de ser almenys {0}!",
- "numberMax":"El nombre ha de ser com a mร xim {0}!",
- "numberDecimal":"El nombre no pot ser decimal!",
- "numberNegative":"El nombre no pot ser negatiu!",
- "numberPositive":"El nombre no pot ser positiu!",
- "numberZero":"El nombre no pot ser zero!",
- "channelNotFound":"No es pot trobar el canal!",
- "userNotFound":"No es pot trobar l'usuari!",
- "roleNotFound":"No es pot trobar el rol!",
- "memberNotFound":"No es pot trobar l'usuari!",
- "mentionableNotFound":"No es pot trobar l'usuari o el rol!",
- "channelType":"Tipus de canal invร lid!",
- "notInGuild":"Aquesta opciรณ requereix que siguis en un servidor!"
- },
- "permissions":{
- "developer":"Has de ser el desenvolupador del bot.",
- "owner":"Has de ser el propietari del servidor.",
- "admin":"Has de ser un administrador del servidor.",
- "moderator":"Has de ser un moderador.",
- "support":"Has de formar part de l'equip de suport.",
- "member":"Has de ser un membre.",
- "discord-administrator":"Has de tenir el permรญs `ADMINISTRADOR`."
- },
- "actionInvalid":{
- "close":"El ticket ja estร tancat!",
- "reopen":"El ticket encara no estร tancat!",
- "claim":"El ticket ja estร reclamat!",
- "unclaim":"El ticket encara no estร reclamat!",
- "pin":"El ticket ja estร fixat!",
- "unpin":"El ticket encara no estร fixat!",
- "add":"Aquest usuari ja tรฉ accรฉs al ticket!",
- "remove":"No es pot eliminar aquest usuari del ticket!"
- }
- },
- "params":{
- "uppercase":{
- "ticket":"Ticket",
- "tickets":"Tickets",
- "reason":"Motiu",
- "creator":"Creador",
- "remaining":"Temps Restant",
- "added":"Afegit",
- "removed":"Eliminat",
- "filter":"Filtre",
- "claimedBy":"Reclamat Per {0}",
- "method":"Mรจtode",
- "type":"Tipus",
- "blacklisted":"Llista Negra",
- "panel":"Panell",
- "command":"Comanda",
- "system":"Sistema",
- "true":"Cert",
- "false":"Fals",
- "syntax":"Sintaxi",
- "originalName":"Nom Original",
- "newName":"Nom Nou",
- "until":"Fins a",
- "validOptions":"Opcions Vร lides",
- "validPanels":"Panells Vร lids",
- "autoclose":"Tancament Automร tic",
- "autodelete":"Eliminaciรณ Automร tica",
- "startupDate":"Data d'Inici",
- "version":"Versiรณ",
- "name":"Nom",
- "role":"Rol",
- "status":"Estat",
- "claimed":"Reclamat",
- "pinned":"Fixat",
- "creationDate":"Data de Creaciรณ"
- },
- "lowercase":{
- "text":"text",
- "html":"html",
- "command":"comanda",
- "modal":"modal",
- "button":"botรณ",
- "dropdown":"desplegable",
- "method":"mรจtode"
- }
- },
- "commands":{
- "reason":"Especifica una raรณ opcional que serร visible als registres.",
- "help":"Obtingues una llista de totes les comandes disponibles.",
- "panel":"Genera un missatge amb un desplegable o botons (per a la creaciรณ de tickets).",
- "panelId":"L'identificador del panell que vols generar.",
- "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.",
- "close":"Tanca un ticket.",
- "delete":"Elimina un ticket.",
- "deleteNoTranscript":"Elimina aquest ticket sense crear una transcripciรณ.",
- "reopen":"Reobre un ticket.",
- "claim":"Reclama un ticket.",
- "claimUser":"Reclama aquest ticket per a una altra persona en lloc de tu mateix.",
- "unclaim":"Allibera un ticket.",
- "pin":"Fixa un ticket.",
- "unpin":"Desfixa un ticket.",
- "move":"Mou un ticket.",
- "moveId":"L'identificador de l'opciรณ a la qual vols moure't.",
- "rename":"Renomena un ticket.",
- "renameName":"El nou nom per aquest ticket.",
- "add":"Afegeix un usuari a un ticket.",
- "addUser":"L'usuari a afegir.",
- "remove":"Elimina un usuari d'un ticket.",
- "removeUser":"L'usuari a eliminar.",
- "blacklist":"Gestiona la llista negra de tickets.",
- "blacklistView":"Veure una llista de l'actual llista negra.",
- "blacklistAdd":"Afegeix un usuari a la llista negra.",
- "blacklistRemove":"Elimina un usuari de la llista negra.",
- "blacklistGet":"Obtingues els detalls d'un usuari a la llista negra.",
- "blacklistGetUser":"L'usuari dels qual vols obtenir detalls.",
- "stats":"Veure estadรญstiques del bot, d'un membre o d'un ticket.",
- "statsReset":"Restablir totes les estadรญstiques del bot (i comenรงar a comptar des de zero).",
- "statsGlobal":"Veure les estadรญstiques globals.",
- "statsUser":"Veure les estadรญstiques d'un usuari al servidor.",
- "statsUserUser":"L'usuari del qual vols veure les estadรญstiques.",
- "statsTicket":"Veure les estadรญstiques d'un ticket al servidor.",
- "statsTicketTicket":"El ticket que vols veure.",
- "clear":"Elimina mรบltiples tickets al mateix temps.",
- "clearFilter":"El filtre per a la neteja de tickets.",
- "clearFilters":{
- "all":"Tots",
- "open":"Oberts",
- "close":"Tancats",
- "claim":"Reclamats",
- "unclaim":"Alliberats",
- "pin":"Fixats",
- "unpin":"Desfixats",
- "autoclose":"Tancat Automร ticaments"
- },
- "autoclose":"Gestiona el tancament automร tic en un ticket.",
- "autocloseDisable":"Desactiva el tancament automร tic en aquest ticket.",
- "autocloseEnable":"Activa el tancament automร tic en aquest ticket.",
- "autocloseEnableTime":"La quantitat d'hores que aquest ticket ha d'estar inactiu per tancar-lo.",
- "autodelete":"Gestiona l'eliminaciรณ automร tica en un ticket.",
- "autodeleteDisable":"Desactiva l'eliminaciรณ automร tica en aquest ticket.",
- "autodeleteEnable":"Activa l'eliminaciรณ automร tica en aquest ticket.",
- "autodeleteEnableTime":"La quantitat de dies que aquest ticket ha d'estar inactiu per eliminar-lo."
- },
- "helpMenu":{
- "help":"Obtingues una llista de totes les comandes disponibles.",
- "ticket":"Crea instantร niament un ticket.",
- "close":"Tanca un ticket, aixรฒ desactiva l'escriptura en aquest canal.",
- "delete":"Elimina un ticket, aixรฒ crea una transcripciรณ quan estร activat.",
- "reopen":"Reobre un ticket, aixรฒ permet escriure de nou en aquest canal.",
- "pin":"Fixar un ticket. Aixรฒ mou el ticket a la part superior i afegeix un emoji '๐' al nom.",
- "unpin":"Desfixar un ticket. El ticket romandrร a la seva posiciรณ perรฒ perdrร l'emoji '๐'.",
- "move":"Mou un ticket. Aixรฒ canvia el tipus d'aquest ticket.",
- "rename":"Renomena un ticket. Aixรฒ canvia el nom del canal d'aquest ticket.",
- "claim":"Reclama un ticket. Amb aixรฒ, pots fer saber al teu equip que estร s gestionant aquest ticket.",
- "unclaim":"Allibera un ticket. Amb aixรฒ, pots fer saber al teu equip que aquest ticket estร lliure de nou.",
- "add":"Afegeix un usuari a un ticket. Aixรฒ permetrร a l'usuari llegir i escriure en aquest ticket.",
- "remove":"Elimina un usuari d'un ticket. Aixรฒ eliminarร la capacitat de llegir i escriure per a un usuari en aquest ticket.",
- "panel":"Genera un missatge amb un desplegable o botons (per a la creaciรณ de tickets).",
- "blacklistView":"Veure una la llista negra actual.",
- "blacklistAdd":"Afegeix un usuari a la llista negra.",
- "blacklistRemove":"Elimina un usuari de la llista negra.",
- "blacklistGet":"Obtingues els detalls d'un usuari de la llista negra.",
- "statsGlobal":"Veure les estadรญstiques globals.",
- "statsTicket":"Veure les estadรญstiques d'un ticket al servidor.",
- "statsUser":"Veure les estadรญstiques d'un usuari al servidor.",
- "statsReset":"Restablir totes les estadรญstiques del bot (i comenรงar a comptar des de zero).",
- "autocloseDisable":"Desactiva el tancament automร tic en aquest ticket.",
- "autocloseEnable":"Activa el tancament automร tic en aquest ticket.",
- "autodeleteDisable":"Desactiva l'eliminaciรณ automร tica en aquest ticket.",
- "autodeleteEnable":"Activa l'eliminaciรณ automร tica en aquest ticket."
- },
- "stats":{
- "scopes":{
- "global":"Estadรญstiques Globals",
- "system":"Estadรญstiques del Sistema",
- "user":"Estadรญstiques d'Usuari",
- "ticket":"Estadรญstiques de Ticket",
- "participants":"Participants"
- },
- "properties":{
- "ticketsCreated":"Tickets Creats",
- "ticketsClosed":"Tickets Tancats",
- "ticketsDeleted":"Tickets Eliminats",
- "ticketsReopened":"Tickets Reoberts",
- "ticketsAutoclosed":"Tickets Tancats Automร ticament",
- "ticketsClaimed":"Tickets Reclamats",
- "ticketsPinned":"Tickets Fixats",
- "ticketsMoved":"Tickets Moguts",
- "usersBlacklisted":"Usuaris a la Llista Negra",
- "transcriptsCreated":"Transcripcions Creades"
- }
- }
+{
+ "_TRANSLATION":{
+ "otversion":"v4.1.0",
+ "translators":["guillee3"],
+ "lastedited":"09/11/2025",
+ "language":"Catalan",
+ "automated":false
+ },
+ "checker":{
+ "system":{
+ "typeError":"[ERROR]",
+ "headerOpenTicket":"OPEN TICKET",
+ "typeWarning":"[AVรS]",
+ "typeInfo":"[INFO]",
+ "headerConfigChecker":"VERIFICADOR DE CONFIGURACIร",
+ "headerDescription":"comprova si existeixen errors als teus fitxers de configuraciรณ!",
+ "footerError":"el bot no s'iniciarร fins que tots els {0} es solucionin!",
+ "footerWarning":"es recomana solucionar tots els {0} abans de comenรงar!",
+ "footerSupport":"SUPORT: {0} - DOCUMENTACIร: {1}",
+ "compactInformation":"utilitza {0} per obtenir mรฉs informaciรณ!",
+ "dataPath":"ruta",
+ "dataDocs":"documents",
+ "dataMessages":"missatge"
+ },
+ "messages":{
+ "stringTooShort":"Aquesta cadena no pot tenir menys de {0} carร cters!",
+ "stringTooLong":"Aquesta cadena no pot tenir mรฉs de {0} carร cters!",
+ "stringLengthInvalid":"Aquesta cadena ha de tenir {0} carร cters!",
+ "stringStartsWith":"Aquesta cadena ha de comenรงar amb {0}!",
+ "stringEndsWith":"Aquesta cadena ha d'acabar amb {0}!",
+ "stringContains":"Aquesta cadena ha de contenir {0}!",
+ "stringChoices":"Aquesta cadena nomรฉs pot ser un dels valors segรผents: {0}!",
+ "stringRegex":"Aquesta cadena รฉs invร lida!",
+ "stringInvertedContains":"Aquesta cadena no pot contenir {0}!",
+ "stringLowercase":"Aquesta cadena ha d'estar escrita nomรฉs en minรบscules!",
+ "stringUppercase":"Aquesta cadena ha d'estar escrita nomรฉs en majรบscules!",
+ "stringSpecialCharacters":"Aquesta cadena no pot contenir cap carร cter especial! (nomรฉs a-z, 0-9 i espai)",
+ "stringNoSpaces":"Aquesta cadena no pot contenir espais!",
+ "stringCapitalWord":"Es recomana que cada paraula d'aquesta cadena comenci amb una lletra majรบscula!",
+ "stringCapitalSentence":"Sembla que algunes frases d'aquesta cadena no comencen amb una lletra majรบscula!",
+ "stringPunctuation":"Sembla que la frase d'aquesta cadena no acaba amb un signe de puntuaciรณ!",
+
+ "numberTooShort":"Aquest nombre no pot tenir menys de {0} carร cters!",
+ "numberTooLong":"Aquest nombre no pot tenir mรฉs de {0} carร cters!",
+ "numberLengthInvalid":"Aquest nombre ha de tenir {0} carร cters!",
+ "numberTooSmall":"Aquest nombre ha de ser almenys {0}!",
+ "numberTooLarge":"Aquest nombre ha de ser com a mร xim {0}!",
+ "numberNotEqual":"Aquest nombre ha de ser {0}!",
+ "numberStep":"Aquest nombre ha de ser un mรบltiple de {0}!",
+ "numberStepOffset":"Aquest nombre ha de ser un mรบltiple de {0} comenรงant amb {1}!",
+ "numberStartsWith":"Aquest nombre ha de comenรงar amb {0}!",
+ "numberEndsWith":"Aquest nombre ha d'acabar amb {0}!",
+ "numberContains":"Aquest nombre ha de contenir {0}!",
+ "numberChoices":"Aquest nombre nomรฉs pot ser un dels valors segรผents: {0}!",
+ "numberFloat":"Aquest nombre no pot ser un decimal!",
+ "numberNegative":"Aquest nombre no pot ser negatiu!",
+ "numberPositive":"Aquest nombre no pot ser positiu!",
+ "numberZero":"Aquest nombre no pot ser zero!",
+ "numberNan":"Aquest nรบmero no pot ser NaN (Not A Number)!",
+ "numberInvertedContains":"Aquest nรบmero no pot contenir {0}!",
+
+ "booleanTrue":"Aquest booleร no pot ser veritable!",
+ "booleanFalse":"Aquest booleร no pot ser fals!",
+
+ "arrayEmptyDisabled":"Aquest array no pot estar buit!",
+ "arrayEmptyRequired":"Aquest array ha d'estar buit!",
+ "arrayTooShort":"Aquest array ha de tenir una longitud d'almenys {0}!",
+ "arrayTooLong":"Aquest array ha de tenir una longitud mร xima de {0}!",
+ "arrayLengthInvalid":"Aquest array ha de tenir una longitud de {0}!",
+ "arrayInvalidTypes":"Aquest array nomรฉs pot contenir els tipus segรผents: {0}!",
+ "arrayDouble":"Aquest array no permet el mateix valor dues vegades!",
+
+ "discordInvalidId":"Aquest รฉs un id de {0} de discord invร lid!",
+ "discordInvalidIdOptions":"Aquest รฉs un id de {0} de discord invร lid! Tambรฉ en pots utilitzar un d'aquests: {1}!",
+ "discordInvalidToken":"Aquest รฉs un token de discord invร lid (sintร cticament)!",
+ "colorInvalid":"Aquest รฉs un color hex invร lid!",
+ "emojiTooShort":"Aquesta cadena ha de tenir almenys {0} emojis!",
+ "emojiTooLong":"Aquesta cadena ha de tenir com a mร xim {0} emojis!",
+ "emojiCustom":"Aquest emoji no pot ser un emoji personalitzat de discord!",
+ "emojiInvalid":"Aquest รฉs un emoji invร lid!",
+ "urlInvalid":"Aquesta url รฉs invร lida!",
+ "urlInvalidHttp":"Aquesta url nomรฉs pot utilitzar el protocol https://!",
+ "urlInvalidProtocol":"Aquesta url nomรฉs pot utilitzar els protocols http:// i https://!",
+ "urlInvalidHostname":"Aquesta url tรฉ un nom de domini no permรจs!",
+ "urlInvalidExtension":"Aquesta url tรฉ una extensiรณ invร lida! Tria entre: {0}!",
+ "urlInvalidPath":"Aquesta url tรฉ un camรญ invร lid!",
+ "idNotUnique":"Aquest id no รฉs รบnic, utilitza un altre id!",
+ "idNonExistent":"L'id {0} no existeix!",
+
+ "invalidType":"Aquesta propietat ha de ser del tipus: {0}!",
+ "propertyMissing":"La propietat {0} falta en aquest objecte!",
+ "propertyOptional":"La propietat {0} รฉs opcional en aquest objecte!",
+ "objectDisabled":"Aquest objecte estร desactivat, activa'l utilitzant {0}!",
+ "nullInvalid":"Aquesta propietat no pot ser nulยทla!",
+ "switchInvalidType":"Aixรฒ ha de ser d'un dels tipus segรผents: {0}!",
+ "objectSwitchInvalid":"Aquest objecte ha de ser d'un dels tipus segรผents: {0}!",
+
+ "invalidLanguage":"Aquest รฉs un idioma invร lid!",
+ "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'!",
+ "customInvalidVersion":"La versiรณ especificada a la teva configuraciรณ no coincideix! Assegura't d'haver actualitzat la configuraciรณ a la versiรณ mรฉs recent!"
+ }
+ },
+ "actions":{
+ "buttons":{
+ "create":"Veure Ticket",
+ "close":"Tancar Ticket",
+ "delete":"Eliminar Ticket",
+ "reopen":"Reobrir Ticket",
+ "claim":"Reclamar Ticket",
+ "unclaim":"Alliberar Ticket",
+ "pin":"Fixar Ticket",
+ "unpin":"Desfixar Ticket",
+ "clear":"Eliminar Tickets",
+ "helpSwitchSlash":"Veure Comandes Slash",
+ "helpSwitchText":"Veure Comandes de Text",
+ "helpPage":"Pร gina {0}",
+ "withReason":"Amb Motiu",
+ "withoutTranscript":"Sense Transcripciรณ"
+ },
+ "titles":{
+ "created":"Ticket Creat",
+ "close":"Ticket Tancat",
+ "delete":"Ticket Eliminat",
+ "reopen":"Ticket Reobert",
+ "claim":"Ticket Reclamat",
+ "unclaim":"Ticket Alliberat",
+ "pin":"Ticket Fixat",
+ "unpin":"Ticket Desfixat",
+ "rename":"Ticket Renomenat",
+ "move":"Ticket Mogut",
+ "add":"Usuari Afegit al Ticket",
+ "remove":"Usuari Eliminat del Ticket",
+
+ "help":"Comandes Disponibles",
+ "statsReset":"Restablir Estadรญstiques",
+ "blacklistAdd":"Usuari a la Llista Negra",
+ "blacklistRemove":"Usuari Alliberat",
+ "blacklistGet":"Usuari a la Llista Negra",
+ "blacklistView":"Llista Negra Actual",
+ "blacklistAddDm":"Afegit a la Llista Negra",
+ "blacklistRemoveDm":"Eliminat de la Llista Negra",
+ "clear":"Tickets Netejats",
+ "clearTickets":"Netejar Tickets",
+ "roles":"Rols Actualitzats",
+
+ "autoclose":"Ticket Tancat Automร ticament",
+ "autocloseEnabled":"Tancament Automร tic Activat",
+ "autocloseDisabled":"Tancament Automร tic Desactivat",
+ "autodelete":"Ticket Eliminat Automร ticament",
+ "autodeleteEnabled":"Eliminaciรณ Automร tica Activada",
+ "autodeleteDisabled":"Eliminaciรณ Automร tica Desactivada",
+
+ "topicSet":"Tema Canviat",
+ "prioritySet":"Prioritat Canviada",
+ "priorityGet":"Prioritat del Ticket",
+ "transfer":"Ticket Transferit"
+ },
+ "descriptions":{
+ "create":"El teu ticket ha estat creat. Fes clic al botรณ a continuaciรณ per accedir-hi!",
+ "close":"El ticket ha estat tancat amb รจxit!",
+ "delete":"El ticket ha estat eliminat amb รจxit!",
+ "reopen":"El ticket ha estat reobert amb รจxit!",
+ "claim":"El ticket ha estat reclamat amb รจxit!",
+ "unclaim":"El ticket ha estat alliberat amb รจxit!",
+ "pin":"El ticket ha estat fixat amb รจxit!",
+ "unpin":"El ticket ha estat desfixat amb รจxit!",
+ "rename":"El ticket ha estat renomenat a {0} amb รจxit!",
+ "move":"El ticket ha estat mogut a {0} amb รจxit!",
+ "add":"{0} ha estat afegit al ticket amb รจxit!",
+ "remove":"{0} ha estat eliminat del ticket amb รจxit!",
+
+ "helpExplanation":"`` => parร metre requerit\n`[nom]` => parร metre opcional",
+ "statsReset":"Les estadรญstiques del bot s'han restablert correctament!",
+ "statsError":"No es poden veure les estadรญstiques del ticket!\n{0} no รฉs un ticket!",
+ "blacklistAdd":"{0} ha estat afegit a la llista negra amb รจxit!",
+ "blacklistRemove":"{0} ha estat alliberat amb รจxit!",
+ "blacklistGetSuccess":"{0} estร actualment a la llista negra!",
+ "blacklistGetEmpty":"{0} actualment no estร a la llista negra!",
+ "blacklistViewEmpty":"Encara no hi ha ningรบ a la llista negra!",
+ "blacklistViewTip":"Utilitza \"/blacklist add\" per afegir un usuari a la llista negra!",
+ "clearVerify":"Estร s segur que vols eliminar mรบltiples tickets?\nAquesta acciรณ no es pot desfer!",
+ "clearReady":"{0} tickets han estat eliminats amb รจxit!",
+ "rolesEmpty":"No s'han actualitzat rols!",
+
+ "autocloseLeave":"Aquest ticket ha estat tancat automร ticament perquรจ el creador ha abandonat el servidor!",
+ "autocloseTimeout":"Aquest ticket ha estat tancat automร ticament perquรจ ha estat inactiu durant mรฉs de `{0}h`!",
+ "autodeleteLeave":"Aquest ticket ha estat eliminat automร ticament perquรจ el creador ha abandonat el servidor!",
+ "autodeleteTimeout":"Aquest ticket ha estat eliminat automร ticament perquรจ ha estat inactiu durant mรฉs de `{0} dies`!",
+ "autocloseEnabled":"El tancament automร tic ha estat activat en aquest ticket!\nEs tancarร quan estigui inactiu durant mรฉs de `{0}h`!",
+ "autocloseDisabled":"El tancament automร tic ha estat desactivat en aquest ticket!\nJa no es tancarร automร ticament!",
+ "autodeleteEnabled":"L'eliminaciรณ automร tica ha estat activada en aquest ticket!\nS'eliminarร quan estigui inactiu durant mรฉs de `{0} dies`!",
+ "autodeleteDisabled":"L'eliminaciรณ automร tica ha estat desactivada en aquest ticket!\nJa no s'eliminarร automร ticament!",
+
+ "ticketMessageLimit":"Nomรฉs pots crear {0} ticket(s) al mateix temps!",
+ "ticketMessageAutoclose":"Aquest ticket es tancarร automร ticament quan estigui inactiu durant {0}h!",
+ "ticketMessageAutodelete":"Aquest ticket es eliminarร automร ticament quan estigui inactiu durant {0} dies!",
+ "panelReady":"El panell estร disponible en el missatge de seguiment!\nAquest missatge es pot eliminar ara!",
+
+ "topicSet":"El tema del canal ha estat canviat per {0} amb รจxit!",
+ "prioritySet":"La prioritat del ticket ha estat canviada a {0} per {1} amb รจxit!",
+ "priorityGet":"La prioritat actual d'aquest ticket รฉs {0}.",
+ "transfer":"La propietat del ticket ha estat transferida de {0} a {1} per {2} amb รจxit!"
+ },
+ "modal":{
+ "closePlaceholder":"Per quรจ has tancat aquest ticket?",
+ "deletePlaceholder":"Per quรจ has eliminat aquest ticket?",
+ "reopenPlaceholder":"Per quรจ has reobert aquest ticket?",
+ "claimPlaceholder":"Per quรจ has reclamat aquest ticket?",
+ "unclaimPlaceholder":"Per quรจ has alliberat aquest ticket?",
+ "pinPlaceholder":"Per quรจ has fixat aquest ticket?",
+ "unpinPlaceholder":"Per quรจ has desfixat aquest ticket?"
+ },
+ "logs":{
+ "createLog":"Un nou ticket ha estat creat per {0}!",
+ "closeLog":"Aquest ticket ha estat tancat per {0}!",
+ "closeDm":"El teu ticket ha estat tancat al nostre servidor!",
+ "deleteLog":"Aquest ticket ha estat eliminat per {0}!",
+ "deleteDm":"El teu ticket ha estat eliminat al nostre servidor!",
+ "reopenLog":"Aquest ticket ha estat reobert per {0}!",
+ "reopenDm":"El teu ticket ha estat reobert al nostre servidor!",
+ "claimLog":"Aquest ticket ha estat reclamat per {0}!",
+ "claimDm":"El teu ticket ha estat reclamat al nostre servidor!",
+ "unclaimLog":"Aquest ticket ha estat alliberat per {0}!",
+ "unclaimDm":"El teu ticket ha estat alliberat al nostre servidor!",
+ "pinLog":"Aquest ticket ha estat fixat per {0}!",
+ "pinDm":"El teu ticket ha estat fixat al nostre servidor!",
+ "unpinLog":"Aquest ticket ha estat desfixat per {0}!",
+ "unpinDm":"El teu ticket ha estat desfixat al nostre servidor!",
+ "renameLog":"Aquest ticket ha estat renombrat a {0} per {1}!",
+ "renameDm":"El teu ticket ha estat renombrat a {0} al nostre servidor!",
+ "moveLog":"Aquest ticket ha estat mogut a {0} per {1}!",
+ "moveDm":"El teu ticket ha estat mogut a {0} al nostre servidor!",
+ "addLog":"{0} ha estat afegit a aquest ticket per {1}!",
+ "addDm":"{0} ha estat afegit al teu ticket al nostre servidor!",
+ "removeLog":"{0} ha estat eliminat d'aquest ticket per {1}!",
+ "removeDm":"{0} ha estat eliminat del teu ticket al nostre servidor!",
+
+ "blacklistAddLog":"{0} ha estat afegit a la llista negra per {1}!",
+ "blacklistRemoveLog":"{0} ha estat eliminat de la llista negra per {1}!",
+ "blacklistAddDm":"Has estat afegit a la llista negra al nostre servidor!\nA partir d'ara, no pots crear cap ticket!",
+ "blacklistRemoveDm":"Has estat eliminat de la llista negra al nostre servidor!\nAra pots crear tickets de nou!",
+ "clearLog":"{0} tickets han estat eliminats per {1}!",
+
+ "transferLog":"La propietat d'aquest ticket ha estat transferida de {0} a {1} per {2}!",
+ "transferDm":"La propietat del teu ticket ha estat transferida de {0} a {1} al nostre servidor!",
+ "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!"
+ }
+ },
+ "transcripts":{
+ "success":{
+ "visit":"Veure Transcripciรณ",
+ "ready":"Transcripciรณ Creada",
+ "textFileDescription":"Aquesta รฉs la transcripciรณ de text d'un ticket eliminat!",
+ "htmlProgress":"Si us plau, espera mentre aquesta transcripciรณ html es processa...",
+
+ "createdChannel":"S'ha creat una nova transcripciรณ tipus {0} al servidor!",
+ "createdCreator":"S'ha creat una nova transcripciรณ tipus {0} per un dels teus tickets!",
+ "createdParticipant":"S'ha creat una nova transcripciรณ tipus {0} en un dels tickets on has participat!",
+ "createdActiveAdmin":"S'ha creat una nova transcripciรณ tipus {0} en un dels tickets on has participat com a administrador!",
+ "createdEveryAdmin":"S'ha creat una nova transcripciรณ tipus {0} en un dels tickets on eres administrador!",
+ "createdOther":"S'ha creat una nova transcripciรณ tipus {0}!"
+ },
+ "errors":{
+ "retry":"Torna-ho a intentar",
+ "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รณ"
+ },
+ "text":{
+ "messagesTitle":"MISSATGES",
+ "embedTitle":"EMBED",
+ "fileTitle":"FITXER",
+ "fieldsTitle":"CAMPS",
+ "reactionsTitle":"REACCIONS",
+ "statsTitle":"ESTADรSTIQUES",
+ "emptyContent":"",
+ "noTitle":"",
+ "noDesc":""
+ }
+ },
+ "errors":{
+ "titles":{
+ "internalError":"Error Intern",
+ "optionMissing":"Opciรณ de Comanda Absent",
+ "optionInvalid":"Opciรณ de Comanda Invร lida",
+ "unknownCommand":"Comanda Desconeguda",
+ "noPermissions":"No Tens Permisos",
+ "unknownTicket":"Ticket Desconegut",
+ "deprecatedTicket":"Ticket Obsolet",
+ "unknownOption":"Opciรณ Desconeguda",
+ "unknownPanel":"Panell Desconegut",
+ "notInGuild":"No Ets Al Servidor",
+ "channelRename":"No Es Pot Canviar El Nom Del Canal",
+ "busy":"El Ticket Estร Ocupat",
+ "permissionError":"Error de Permisos"
+ },
+ "descriptions":{
+ "askForInfo":"Contacta amb el propietari d'aquest bot per obtenir mรฉs informaciรณ!",
+ "askForInfoResolve":"Contacta amb el propietari del bot si aquest problema no es resol desprรฉs de diversos intents.",
+ "internalError":"No s'ha pogut respondre a aquest {0} a causa d'un error intern!",
+ "optionMissing":"Falta un parร metre requerit en aquesta comanda!",
+ "optionInvalid":"Un parร metre d'aquesta comanda รฉs invร lid!",
+ "optionInvalidChoose":"Tria entre",
+ "unknownCommand":"Prova de visitar el menรบ d'ajuda per obtenir mรฉs informaciรณ!",
+ "noPermissions":"No tens permรญs per utilitzar aquest {0}!",
+ "noPermissionsList":"Permisos Requerits: (un d'ells)",
+ "noPermissionsCooldown":"No tens permรญs per utilitzar aquest {0} perquรจ tens un temps de refredament!",
+ "noPermissionsBlacklist":"No tens permรญs per utilitzar aquest {0} perquรจ estร s a la llista negra!",
+ "noPermissionsLimitGlobal":"No tens permรญs per crear un ticket perquรจ el servidor ha arribat al lรญmit mร xim de tickets!",
+ "noPermissionsLimitGlobalUser":"No tens permรญs per crear un ticket perquรจ has arribat al lรญmit mร xim de tickets!",
+ "noPermissionsLimitOption":"No tens permรญs per crear un ticket perquรจ el servidor ha arribat al lรญmit mร xim de tickets per aquesta opciรณ!",
+ "noPermissionsLimitOptionUser":"No tens permรญs per crear un ticket perquรจ has arribat al lรญmit mร xim de tickets per aquesta opciรณ!",
+ "unknownTicket":"Torna a intentar aquesta comanda en un ticket vร lid!",
+ "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.",
+ "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."
+ },
+ "optionInvalidReasons":{
+ "stringRegex":"El valor no coincideix amb el patrรณ!",
+ "stringMinLength":"El valor ha de tenir almenys {0} carร cters!",
+ "stringMaxLength":"El valor ha de tenir com a mร xim {0} carร cters!",
+ "numberInvalid":"Nombre invร lid!",
+ "numberMin":"El nombre ha de ser almenys {0}!",
+ "numberMax":"El nombre ha de ser com a mร xim {0}!",
+ "numberDecimal":"El nombre no pot ser decimal!",
+ "numberNegative":"El nombre no pot ser negatiu!",
+ "numberPositive":"El nombre no pot ser positiu!",
+ "numberZero":"El nombre no pot ser zero!",
+ "channelNotFound":"No es pot trobar el canal!",
+ "userNotFound":"No es pot trobar l'usuari!",
+ "roleNotFound":"No es pot trobar el rol!",
+ "memberNotFound":"No es pot trobar l'usuari!",
+ "mentionableNotFound":"No es pot trobar l'usuari o el rol!",
+ "channelType":"Tipus de canal invร lid!",
+ "notInGuild":"Aquesta opciรณ requereix que siguis en un servidor!"
+ },
+ "permissions":{
+ "developer":"Has de ser el desenvolupador del bot.",
+ "owner":"Has de ser el propietari del servidor.",
+ "admin":"Has de ser un administrador del servidor.",
+ "moderator":"Has de ser un moderador.",
+ "support":"Has de formar part de l'equip de suport.",
+ "member":"Has de ser un membre.",
+ "discord-administrator":"Has de tenir el permรญs `ADMINISTRADOR`."
+ },
+ "actionInvalid":{
+ "close":"El ticket ja estร tancat!",
+ "reopen":"El ticket encara no estร tancat!",
+ "claim":"El ticket ja estร reclamat!",
+ "unclaim":"El ticket encara no estร reclamat!",
+ "pin":"El ticket ja estร fixat!",
+ "unpin":"El ticket encara no estร fixat!",
+ "add":"Aquest usuari ja tรฉ accรฉs al ticket!",
+ "remove":"No es pot eliminar aquest usuari del ticket!"
+ }
+ },
+ "params":{
+ "uppercase":{
+ "ticket":"Ticket",
+ "tickets":"Tickets",
+ "reason":"Motiu",
+ "creator":"Creador",
+ "remaining":"Temps Restant",
+ "added":"Afegit",
+ "removed":"Eliminat",
+ "filter":"Filtre",
+ "method":"Mรจtode",
+ "type":"Tipus",
+ "blacklisted":"Llista Negra",
+ "panel":"Panell",
+ "command":"Comanda",
+ "system":"Sistema",
+ "true":"Cert",
+ "false":"Fals",
+ "syntax":"Sintaxi",
+ "originalName":"Nom Original",
+ "newName":"Nom Nou",
+ "until":"Fins a",
+ "validOptions":"Opcions Vร lides",
+ "validPanels":"Panells Vร lids",
+ "autoclose":"Tancament Automร tic",
+ "autodelete":"Eliminaciรณ Automร tica",
+ "startupDate":"Data d'Inici",
+ "version":"Versiรณ",
+ "name":"Nom",
+ "role":"Rol",
+ "status":"Estat",
+ "claimed":"Reclamat",
+ "pinned":"Fixat",
+ "creationDate":"Data de Creaciรณ",
+
+ "noone":"Ningรบ",
+ "open":"Obert",
+ "closed":"Tancat",
+ "priority":"Prioritat",
+ "participants":"Participants",
+ "yes":"Si",
+ "no":"No",
+ "option":"Opciรณ",
+ "topic":"Tema",
+ "uptime":"Temps d'Activitat del Sistema",
+ "messages":"Missatges",
+ "embeds":"Embeds",
+ "files":"Fitxers",
+ "components":"Components",
+ "cooldown":"Refredament",
+ "maxTickets":"Max Tickets",
+ "admins":"Admins",
+ "roles":"Rols",
+ "size":"Mida"
+ },
+ "lowercase":{
+ "text":"text",
+ "html":"html",
+ "command":"comanda",
+ "modal":"modal",
+ "button":"botรณ",
+ "dropdown":"desplegable",
+ "method":"mรจtode"
+ }
+ },
+ "commands":{
+ "reason":"Especifica una raรณ opcional que serร visible als registres.",
+ "help":"Obtingues una llista de totes les comandes disponibles.",
+ "panel":"Genera un missatge amb un desplegable o botons (per a la creaciรณ de tickets).",
+ "panelId":"L'identificador del panell que vols generar.",
+ "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.",
+ "close":"Tanca un ticket.",
+ "delete":"Elimina un ticket.",
+ "deleteNoTranscript":"Elimina aquest ticket sense crear una transcripciรณ.",
+ "reopen":"Reobre un ticket.",
+ "claim":"Reclama un ticket.",
+ "claimUser":"Reclama aquest ticket per a una altra persona en lloc de tu mateix.",
+ "unclaim":"Allibera un ticket.",
+ "pin":"Fixa un ticket.",
+ "unpin":"Desfixa un ticket.",
+
+ "move":"Mou un ticket.",
+ "moveId":"L'identificador de l'opciรณ a la qual vols moure't.",
+ "rename":"Renomena un ticket.",
+ "renameName":"El nou nom per aquest ticket.",
+ "add":"Afegeix un usuari a un ticket.",
+ "addUser":"L'usuari a afegir.",
+ "remove":"Elimina un usuari d'un ticket.",
+ "removeUser":"L'usuari a eliminar.",
+
+ "blacklist":"Gestiona la llista negra de tickets.",
+ "blacklistView":"Veure una llista de l'actual llista negra.",
+ "blacklistAdd":"Afegeix un usuari a la llista negra.",
+ "blacklistRemove":"Elimina un usuari de la llista negra.",
+ "blacklistGet":"Obtingues els detalls d'un usuari a la llista negra.",
+ "blacklistGetUser":"L'usuari dels qual vols obtenir detalls.",
+ "stats":"Veure estadรญstiques del bot, d'un membre o d'un ticket.",
+ "statsReset":"Restablir totes les estadรญstiques del bot (i comenรงar a comptar des de zero).",
+ "statsGlobal":"Veure les estadรญstiques globals.",
+ "statsUser":"Veure les estadรญstiques d'un usuari al servidor.",
+ "statsUserUser":"L'usuari del qual vols veure les estadรญstiques.",
+ "statsTicket":"Veure les estadรญstiques d'un ticket al servidor.",
+ "statsTicketTicket":"El ticket que vols veure.",
+
+ "clear":"Elimina mรบltiples tickets al mateix temps.",
+ "clearFilter":"El filtre per a la neteja de tickets.",
+ "clearFilters":{
+ "all":"Tots",
+ "open":"Oberts",
+ "close":"Tancats",
+ "claim":"Reclamats",
+ "unclaim":"Alliberats",
+ "pin":"Fixats",
+ "unpin":"Desfixats",
+ "autoclose":"Tancat Automร ticaments"
+ },
+
+ "autoclose":"Gestiona el tancament automร tic en un ticket.",
+ "autocloseDisable":"Desactiva el tancament automร tic en aquest ticket.",
+ "autocloseEnable":"Activa el tancament automร tic en aquest ticket.",
+ "autocloseEnableTime":"La quantitat d'hores que aquest ticket ha d'estar inactiu per tancar-lo.",
+ "autodelete":"Gestiona l'eliminaciรณ automร tica en un ticket.",
+ "autodeleteDisable":"Desactiva l'eliminaciรณ automร tica en aquest ticket.",
+ "autodeleteEnable":"Activa l'eliminaciรณ automร tica en aquest ticket.",
+ "autodeleteEnableTime":"La quantitat de dies que aquest ticket ha d'estar inactiu per eliminar-lo.",
+
+ "topic":"Gestiona el tema del canal del ticket.",
+ "topicSet":"Estableix el tema del canal del ticket.",
+ "topicValue":"El nou tema del canal.",
+ "topicList":"Obtรฉ una llista de tots els tickets amb el seu tema i estadรญstiques.",
+ "priority":"Gestiona la prioritat del ticket.",
+ "prioritySet":"Estableix la prioritat del ticket.",
+ "priorityValue":"La prioritat del canal.",
+ "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."
+ },
+ "helpMenu":{
+ "help":"Obtingues una llista de totes les comandes disponibles.",
+ "ticket":"Crea instantร niament un ticket.",
+ "close":"Tanca un ticket, aixรฒ desactiva l'escriptura en aquest canal.",
+ "delete":"Elimina un ticket, aixรฒ crea una transcripciรณ quan estร activat.",
+ "reopen":"Reobre un ticket, aixรฒ permet escriure de nou en aquest canal.",
+ "pin":"Fixar un ticket. Aixรฒ mou el ticket a la part superior i afegeix un emoji '๐' al nom.",
+ "unpin":"Desfixar un ticket. El ticket romandrร a la seva posiciรณ perรฒ perdrร l'emoji '๐'.",
+ "move":"Mou un ticket. Aixรฒ canvia el tipus d'aquest ticket.",
+ "rename":"Renomena un ticket. Aixรฒ canvia el nom del canal d'aquest ticket.",
+ "claim":"Reclama un ticket. Amb aixรฒ, pots fer saber al teu equip que estร s gestionant aquest ticket.",
+ "unclaim":"Allibera un ticket. Amb aixรฒ, pots fer saber al teu equip que aquest ticket estร lliure de nou.",
+ "add":"Afegeix un usuari a un ticket. Aixรฒ permetrร a l'usuari llegir i escriure en aquest ticket.",
+ "remove":"Elimina un usuari d'un ticket. Aixรฒ eliminarร la capacitat de llegir i escriure per a un usuari en aquest ticket.",
+ "panel":"Genera un missatge amb un desplegable o botons (per a la creaciรณ de tickets).",
+ "blacklistView":"Veure una la llista negra actual.",
+ "blacklistAdd":"Afegeix un usuari a la llista negra.",
+ "blacklistRemove":"Elimina un usuari de la llista negra.",
+ "blacklistGet":"Obtingues els detalls d'un usuari de la llista negra.",
+ "statsGlobal":"Veure les estadรญstiques globals.",
+ "statsTicket":"Veure les estadรญstiques d'un ticket al servidor.",
+ "statsUser":"Veure les estadรญstiques d'un usuari al servidor.",
+ "statsReset":"Restablir totes les estadรญstiques del bot (i comenรงar a comptar des de zero).",
+ "autocloseDisable":"Desactiva el tancament automร tic en aquest ticket.",
+ "autocloseEnable":"Activa el tancament automร tic en aquest ticket.",
+ "autodeleteDisable":"Desactiva l'eliminaciรณ automร tica en aquest ticket.",
+ "autodeleteEnable":"Activa l'eliminaciรณ automร tica en aquest ticket.",
+ "categories":{
+ "general":"Comandes Generals",
+ "basicTicket":"Comandes Bร siques de Tickets",
+ "advancedTicket":"Comandes Avanรงades de Tickets",
+ "userTicket":"Comandes de Tickets d'Usuari",
+ "admin":"Comandes d'Admin",
+ "advanced":"Comandes Avanรงades",
+ "extra":"Comandes Extra"
+ }
+ },
+ "stats":{
+ "scopes":{
+ "global":"Estadรญstiques Globals",
+ "system":"Estadรญstiques del Sistema",
+ "user":"Estadรญstiques d'Usuari",
+ "ticket":"Estadรญstiques de Ticket",
+ "participants":"Participants",
+ "messages":"Missatges"
+ },
+ "properties":{
+ "ticketsCreated":"Tickets Creats",
+ "ticketsClosed":"Tickets Tancats",
+ "ticketsDeleted":"Tickets Eliminats",
+ "ticketsReopened":"Tickets Reoberts",
+ "ticketsAutoclosed":"Tickets Tancats Automร ticament",
+ "ticketsClaimed":"Tickets Reclamats",
+ "ticketsPinned":"Tickets Fixats",
+ "ticketsMoved":"Tickets Moguts",
+ "usersBlacklisted":"Usuaris a la Llista Negra",
+ "transcriptsCreated":"Transcripcions Creades",
+ "ticketsAutodeleted":"Tickets Eliminats Automร ticament",
+ "ticketsTransferred":"Tickets Transferits",
+ "ticketVolume":"Volum de Tickets",
+ "averageTickets":"Mitjana de Tickets/Usuari",
+ "currentTickets":"Tickets Actuals",
+ "age":"Edat del Ticket",
+ "responseTime":"Temps de Resposta",
+ "resolutionTime":"Temps de Resoluciรณ",
+ "createdOn":"Creat el",
+ "createdBy":"Creat per",
+ "closedOn":"Tancat el",
+ "closedBy":"Tancat per",
+ "claimedOn":"Reclamat el",
+ "claimedBy":"Reclamat per",
+ "pinnedOn":"Fixat el",
+ "pinnedBy":"Fixat per",
+ "deletedOn":"Eliminat el",
+ "deletedBy":"Eliminat per"
+ },
+ "roles":{
+ "developer":"Desenvolupador",
+ "serverOwner":"Propietari del Servidor",
+ "serverAdmin":"Admin del Servidor",
+ "moderator":"Equip de Moderaciรณ",
+ "support":"Equip de Suport",
+ "member":"Membre"
+ }
+ },
+ "panel":{
+ "selectTicket":"Selecciona el teu ticket",
+ "selectRole":"Selecciona el teu rol",
+ "selectOption":"Selecciona la teva opciรณ"
+ },
+ "priorities":{
+ "urgent":"Urgent",
+ "veryHigh":"Molt Alt",
+ "high":"Alt",
+ "normal":"Normal",
+ "low":"Baix",
+ "veryLow":"Molt Baix",
+ "none":"Cap"
+ }
}
\ No newline at end of file
diff --git a/languages/custom.json b/languages/custom.json
index 95a194b..52cd1f4 100644
--- a/languages/custom.json
+++ b/languages/custom.json
@@ -1,8 +1,8 @@
{
"_TRANSLATION":{
- "otversion":"v4.0.7",
+ "otversion":"v4.1.0",
"translators":["DJj123dj"],
- "lastedited":"21/08/2024",
+ "lastedited":"09/11/2025",
"language":"Custom",
"automated":false
},
@@ -31,7 +31,15 @@
"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!",
+
"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!",
@@ -48,10 +56,12 @@
"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}!",
+
"booleanTrue":"This boolean can't be true!",
"booleanFalse":"This boolean can't be false!",
-
+
"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}!",
@@ -59,7 +69,7 @@
"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!",
-
+
"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)!",
@@ -76,7 +86,7 @@
"urlInvalidPath":"This url has an invalid path!",
"idNotUnique":"This id isn't unique, use another id instead!",
"idNonExistent":"The id {0} doesn't 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!",
@@ -84,12 +94,13 @@
"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}!",
-
+
"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!"
+ "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!"
}
},
"actions":{
@@ -132,6 +143,7 @@
"blacklistAddDm":"Added To Blacklist",
"blacklistRemoveDm":"Removed From Blacklist",
"clear":"Tickets Cleared",
+ "clearTickets":"Clear Tickets",
"roles":"Roles Updated",
"autoclose":"Ticket Autoclosed",
@@ -139,7 +151,12 @@
"autocloseDisabled":"Autoclose Disabled",
"autodelete":"Ticket Autodeleted",
"autodeleteEnabled":"Autodelete Enabled",
- "autodeleteDisabled":"Autodelete Disabled"
+ "autodeleteDisabled":"Autodelete Disabled",
+
+ "topicSet":"Topic Changed",
+ "prioritySet":"Priority Changed",
+ "priorityGet":"Ticket Priority",
+ "transfer":"Ticket Transferred"
},
"descriptions":{
"create":"Your ticket has been created. Click the button below to access it!",
@@ -154,7 +171,7 @@
"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!",
-
+
"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!",
@@ -167,7 +184,7 @@
"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!",
-
+
"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!",
@@ -176,11 +193,16 @@
"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!",
-
+
"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":"You can find the panel below!\nThis message can now be deleted!"
+ "panelReady":"The panel is available in the followup message!\nThis message can now be deleted!",
+
+ "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!"
},
"modal":{
"closePlaceholder":"Why did you close this ticket?",
@@ -215,12 +237,19 @@
"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!",
-
+
"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}!"
+ "clearLog":"{0} tickets 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!"
}
},
"transcripts":{
@@ -229,7 +258,7 @@
"ready":"Transcript Created",
"textFileDescription":"This is the text transcript of a deleted ticket!",
"htmlProgress":"Please wait while this html transcript is getting processed...",
-
+
"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!",
@@ -241,7 +270,19 @@
"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."
+ "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"
+ },
+ "text":{
+ "messagesTitle":"MESSAGES",
+ "embedTitle":"EMBED",
+ "fileTitle":"FILE",
+ "fieldsTitle":"FIELDS",
+ "reactionsTitle":"REACTIONS",
+ "statsTitle":"STATS",
+ "emptyContent":"",
+ "noTitle":"",
+ "noDesc":""
}
},
"errors":{
@@ -257,7 +298,8 @@
"unknownPanel":"Unknown Panel",
"notInGuild":"Not In Server",
"channelRename":"Unable To Rename Channel",
- "busy":"Ticket Is Busy"
+ "busy":"Ticket Is Busy",
+ "permissionError":"Permission Error"
},
"descriptions":{
"askForInfo":"Contact the owner of this bot for more info!",
@@ -280,7 +322,10 @@
"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!"
+ "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."
},
"optionInvalidReasons":{
"stringRegex":"Value doesn't match pattern!",
@@ -331,7 +376,6 @@
"added":"Added",
"removed":"Removed",
"filter":"Filter",
- "claimedBy":"Claimed By {0}",
"method":"Method",
"type":"Type",
"blacklisted":"Blacklisted",
@@ -355,7 +399,27 @@
"status":"Status",
"claimed":"Claimed",
"pinned":"Pinned",
- "creationDate":"Creation Date"
+ "creationDate":"Creation Date",
+
+ "noone":"No-One",
+ "open":"Open",
+ "closed":"Closed",
+ "priority":"Priority",
+ "participants":"Participants",
+ "yes":"Yes",
+ "no":"No",
+ "option":"Option",
+ "topic":"Topic",
+ "uptime":"System Uptime",
+ "messages":"Messages",
+ "embeds":"Embeds",
+ "files":"Files",
+ "components":"Components",
+ "cooldown":"Cooldown",
+ "maxTickets":"Max Tickets",
+ "admins":"Admins",
+ "roles":"Roles",
+ "size":"Size"
},
"lowercase":{
"text":"text",
@@ -384,6 +448,7 @@
"unclaim":"Unclaim a ticket.",
"pin":"Pin a ticket.",
"unpin":"Unpin a ticket.",
+
"move":"Move a ticket.",
"moveId":"The identifier of the option that you want to move to.",
"rename":"Rename a ticket.",
@@ -392,6 +457,7 @@
"addUser":"The user to add.",
"remove":"Remove a user from a ticket.",
"removeUser":"The user to remove.",
+
"blacklist":"Manage the ticket blacklist.",
"blacklistView":"View a list of the current blacklist.",
"blacklistAdd":"Add a user to the blacklist.",
@@ -405,6 +471,7 @@
"statsUserUser":"The user to view.",
"statsTicket":"View the stats of a ticket in the server.",
"statsTicketTicket":"The ticket to view.",
+
"clear":"Delete multiple tickets at the same time.",
"clearFilter":"The filter for clearing tickets.",
"clearFilters":{
@@ -417,6 +484,7 @@
"unpin":"Unpinned",
"autoclose":"Autoclosed"
},
+
"autoclose":"Manage autoclose in a ticket.",
"autocloseDisable":"Disable autoclose in this ticket.",
"autocloseEnable":"Enable autoclose in this ticket.",
@@ -424,7 +492,19 @@
"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."
+ "autodeleteEnableTime":"The amount of days this ticket needs to be inactive to delete it.",
+
+ "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."
},
"helpMenu":{
"help":"Get a list of all the available commands.",
@@ -452,7 +532,16 @@
"autocloseDisable":"Disable autoclose in this ticket.",
"autocloseEnable":"Enable autoclose in this ticket.",
"autodeleteDisable":"Disable autodelete in this ticket.",
- "autodeleteEnable":"Enable autodelete in this ticket."
+ "autodeleteEnable":"Enable autodelete in this ticket.",
+ "categories":{
+ "general":"General Commands",
+ "basicTicket":"Basic Ticket Commands",
+ "advancedTicket":"Advanced Ticket Commands",
+ "userTicket":"User Ticket Commands",
+ "admin":"Admin Commands",
+ "advanced":"Advanced Commands",
+ "extra":"Extra Commands"
+ }
},
"stats":{
"scopes":{
@@ -460,7 +549,8 @@
"system":"System Stats",
"user":"User Stats",
"ticket":"Ticket Stats",
- "participants":"Participants"
+ "participants":"Participants",
+ "messages":"Messages"
},
"properties":{
"ticketsCreated":"Tickets Created",
@@ -472,7 +562,47 @@
"ticketsPinned":"Tickets Pinned",
"ticketsMoved":"Tickets Moved",
"usersBlacklisted":"Users Blacklisted",
- "transcriptsCreated":"Transcripts Created"
+ "transcriptsCreated":"Transcripts Created",
+ "ticketsAutodeleted":"Tickets Autodeleted",
+ "ticketsTransferred":"Tickets Transferred",
+ "ticketVolume":"Ticket Volume",
+ "averageTickets":"Average Tickets/User",
+ "currentTickets":"Current Tickets",
+ "age":"Ticket Age",
+ "responseTime":"Response Time",
+ "resolutionTime":"Resolution Time",
+ "createdOn":"Created On",
+ "createdBy":"Created By",
+ "closedOn":"Closed On",
+ "closedBy":"Closed By",
+ "claimedOn":"Claimed On",
+ "claimedBy":"Claimed By",
+ "pinnedOn":"Pinned On",
+ "pinnedBy":"Pinned By",
+ "deletedOn":"Deleted On",
+ "deletedBy":"Deleted By"
+ },
+ "roles":{
+ "developer":"Developer",
+ "serverOwner":"Server Owner",
+ "serverAdmin":"Server Admin",
+ "moderator":"Moderator Team",
+ "support":"Support Team",
+ "member":"Member"
}
+ },
+ "panel":{
+ "selectTicket":"Select your ticket",
+ "selectRole":"Select your role",
+ "selectOption":"Select your option"
+ },
+ "priorities":{
+ "urgent":"Urgent",
+ "veryHigh":"Very High",
+ "high":"High",
+ "normal":"Normal",
+ "low":"Low",
+ "veryLow":"Very Low",
+ "none":"None"
}
}
\ No newline at end of file
diff --git a/languages/czech.json b/languages/czech.json
index d8ea5a2..e23406f 100644
--- a/languages/czech.json
+++ b/languages/czech.json
@@ -1,6 +1,6 @@
{
"_TRANSLATION":{
- "otversion":"v4.0.7",
+ "otversion":"v4.1.0",
"translators":["spyeye_"],
"lastedited":"21/08/2024",
"language":"Czech",
diff --git a/languages/danish.json b/languages/danish.json
index 5584331..e902380 100644
--- a/languages/danish.json
+++ b/languages/danish.json
@@ -1,6 +1,6 @@
{
"_TRANSLATION":{
- "otversion":"v4.0.7",
+ "otversion":"v4.1.0",
"translators":["the_gamer"],
"lastedited":"26/09/2024",
"language":"Danish",
diff --git a/languages/dutch.json b/languages/dutch.json
index 5bdfa41..aee5be0 100644
--- a/languages/dutch.json
+++ b/languages/dutch.json
@@ -1,8 +1,8 @@
{
"_TRANSLATION":{
- "otversion":"v4.0.7",
+ "otversion":"v4.1.0",
"translators":["DJj123dj"],
- "lastedited":"21/08/2024",
+ "lastedited":"09/11/2025",
"language":"Dutch",
"automated":false
},
@@ -31,7 +31,15 @@
"stringContains":"Deze tekst moet {0} bevatten!",
"stringChoices":"Deze tekst kan alleen de volgende waardes bevatten: {0}!",
"stringRegex":"Deze tekst is ongeldig!",
-
+ "stringInvertedContains":"Deze tekst mag niet {0} bevatten!",
+ "stringLowercase":"Deze tekst moet in kleine letters geschreven zijn!",
+ "stringUppercase":"Deze tekst moet in hoofdletters geschreven zijn!",
+ "stringSpecialCharacters":"Deze tekst mag geen speciale karakters bevatten! (enkel a-z, 0-9 & spatie)",
+ "stringNoSpaces":"Deze tekst mag geen spaties bevatten!",
+ "stringCapitalWord":"Het is aangeraden om ieder woord in de zin te laten starten met een hoofdletter!",
+ "stringCapitalSentence":"Het ziet er naar uit dat sommige zinnen in deze tekst niet met een hoofdletter beginnen!",
+ "stringPunctuation":"Het ziet er naar uit dat de zin in deze tekst niet met een leesteken eindigt!",
+
"numberTooShort":"Dit nummer kan niet korter dan {0} karakters zijn!",
"numberTooLong":"Dit nummer kan niet langer dan {0} karakters zijn!",
"numberLengthInvalid":"Dit nummer moet {0} karakters lang zijn!",
@@ -48,10 +56,12 @@
"numberNegative":"Dit nummer kan niet negatief zijn!",
"numberPositive":"Dit nummer kan niet positief zijn!",
"numberZero":"Dit nummer kan niet nul zijn!",
-
+ "numberNan":"Dit nummer kan geen NaN (Not A Number) zijn!",
+ "numberInvertedContains":"Dit nummer mag niet {0} bevatten!",
+
"booleanTrue":"Deze boolean kan niet true zijn!",
"booleanFalse":"Deze boolean kan niet false zijn!",
-
+
"arrayEmptyDisabled":"Deze array mag niet leeg zijn!",
"arrayEmptyRequired":"Deze array moet leeg zijn!",
"arrayTooShort":"Deze array moet een minimum lengte hebben van {0}!",
@@ -59,7 +69,7 @@
"arrayLengthInvalid":"Deze array moet een lengte hebben van {0}!",
"arrayInvalidTypes":"Deze array kan alleen de volgende waardes bevatten: {0}!",
"arrayDouble":"Deze array staat dubbele waardes niet toe!",
-
+
"discordInvalidId":"Dit is een ongeldig discord {0} id!",
"discordInvalidIdOptions":"Dit is an ongeldig discord {0} id! Je kan ook een van deze gebruiken: {1}!",
"discordInvalidToken":"Dit is een ongeldig discord token (syntactisch)!",
@@ -76,7 +86,7 @@
"urlInvalidPath":"Deze url heeft een ongeldig pad!",
"idNotUnique":"Dit id is niet uniek, gebruik een andere in de plaats!",
"idNonExistent":"Het id {0} bestaat niet!",
-
+
"invalidType":"Het type van deze property moet gelijk zijn aan: {0}!",
"propertyMissing":"De property {0} mist in dit object!",
"propertyOptional":"De property {0} is optioneel in dit object!",
@@ -84,12 +94,13 @@
"nullInvalid":"Deze property kan niet null zijn!",
"switchInvalidType":"Dit moet een van de volgende types zijn: {0}!",
"objectSwitchInvalid":"Dit object moet een van de volgende types zijn: {0}!",
-
+
"invalidLanguage":"Dit is een ongeldige taal!",
"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 mag alleen opties bevaten van het 'ticket' type!",
+ "customInvalidVersion":"De versie gespecificeerd in u configbestand komt niet overeen! Zorg dat u het configbestand geรผpdatet heeft naar de laatste versie!"
}
},
"actions":{
@@ -132,6 +143,7 @@
"blacklistAddDm":"Toegevoegd Aan Blacklist",
"blacklistRemoveDm":"Verwijderd Van Blacklist",
"clear":"Tickets Opgeruimd",
+ "clearTickets":"Tickets Opruimen",
"roles":"Rollen Bijgewerkt",
"autoclose":"Ticket Automatisch Gesloten",
@@ -139,7 +151,12 @@
"autocloseDisabled":"Autoclose Uitgeschakeld",
"autodelete":"Ticket Automatisch Verwijderd",
"autodeleteEnabled":"Autodelete Ingeschakeld",
- "autodeleteDisabled":"Autodelete Uitgeschakeld"
+ "autodeleteDisabled":"Autodelete Uitgeschakeld",
+
+ "topicSet":"Onderwerp Veranderd",
+ "prioritySet":"Prioriteit Veranderd",
+ "priorityGet":"Ticket Prioriteit",
+ "transfer":"Ticket Overgedragen"
},
"descriptions":{
"create":"Je ticket is aangemaakt. Klik op de knop hier onder om er naar toe te gaan!",
@@ -154,7 +171,7 @@
"move":"Het ticket is succesvol verplaatst naar {0}!",
"add":"{0} is succesvol toegevoegd aan het ticket!",
"remove":"{0} is succesvol verwijderd van het ticket!",
-
+
"helpExplanation":"`` => verplichte parameter\n`[naam]` => optionele parameter",
"statsReset":"De bot stats zijn succesvol gereset!",
"statsError":"Kan ticket stats niet bekijken!\n{0} is geen ticket!",
@@ -167,20 +184,25 @@
"clearVerify":"Weet je zeker dat je meerdere tickets wilt verwijderen? Deze actie kan niet teruggedraaid worden!",
"clearReady":"{0} tickets zijn succesvol verwijderd!",
"rolesEmpty":"Geen enkele rollen zijn bijgewerkt!",
-
+
"autocloseLeave":"Dit ticket is automatisch gesloten omdat de maker de server verlaten is!",
- "autocloseTimeout":"Dit ticket is automatisch gesloten omdat het inactief was voor meer dan `{0} uur`!",
+ "autocloseTimeout":"Dit ticket is automatisch gesloten omdat het meer dan `{0} uur` inactief was!",
"autodeleteLeave":"Dit ticket is automatisch verwijderd omdat de maker de server verlaten is!",
- "autodeleteTimeout":"Dit ticket is automatisch verwijderd omdat het inactief was voor meer dan `{0} dagen`!",
- "autocloseEnabled":"Autoclose is ingeschakeld in dit ticket!\nHet wordt gesloten wanneer het inactief is voor meer dan `{0} uur`!",
+ "autodeleteTimeout":"Dit ticket is automatisch verwijderd omdat het meer dan `{0} dagen` inactief was!",
+ "autocloseEnabled":"Autoclose is ingeschakeld in dit ticket!\nHet wordt gesloten na `{0} uur` inactiviteit!",
"autocloseDisabled":"Autoclose is uitgeschakeld in dit ticket!\nHet wordt niet meer automatisch gesloten!",
- "autodeleteEnabled":"Autodelete is ingeschakeld in dit ticket!\nHet wordt verwijderd wanneer het inactief is voor meer dan `{0} dagen`!",
+ "autodeleteEnabled":"Autodelete is ingeschakeld in dit ticket!\nHet wordt verwijderd na `{0} dagen` inactiviteit!",
"autodeleteDisabled":"Autodelete is uitgeschakeld in dit ticket!\nHet wordt niet meer automatisch verwijderd!",
-
+
"ticketMessageLimit":"Je kan maar {0} ticket(s) op het zelfde moment aanmaken!",
"ticketMessageAutoclose":"Dit ticket wordt automatisch gesloten wanneer het inactief is voor {0} uur!",
"ticketMessageAutodelete":"Dit ticket wordt automatisch verwijderd wanneer het inactief is voor {0} dagen!",
- "panelReady":"Je kan het paneel hier onder vinden!\nDit bericht kan nu verwijderd worden!"
+ "panelReady":"Het paneel is beschikbaar in het volgende bericht!\nDit bericht kan verwijderd worden!",
+
+ "topicSet":"Het kanaalsonderwerp is succesvol veranderd naar {0}!",
+ "prioritySet":"De ticket prioriteit is succesvol veranderd naar {0} door {1}!",
+ "priorityGet":"De huidige prioriteit van dit ticket is {0}.",
+ "transfer":"Het ticket is succesvol overgedragen van {0} naar {1} door {2}!"
},
"modal":{
"closePlaceholder":"Waarom heb je dit ticket gesloten?",
@@ -215,12 +237,19 @@
"addDm":"{0} is toegevoegd aan jouw ticket in onze server!",
"removeLog":"{0} is verwijderd van dit ticket door {1}!",
"removeDm":"{0} is verwijderd van jouw ticket in onze server!",
-
+
"blacklistAddLog":"{0} is geblacklist door {1}!",
"blacklistRemoveLog":"{0} is verwijderd van de blacklist door {1}!",
"blacklistAddDm":"Je bent geblacklist in onze server!\nVanaf nu kan je geen tickets meer aanmaken!",
"blacklistRemoveDm":"Je bent verwijderd van de blacklist in onze server!\nNormaal zou je nu terug tickets kunnen aanmaken!",
- "clearLog":"{0} tickets zijn verwijderd door by {1}!"
+ "clearLog":"{0} tickets zijn verwijderd door by {1}!",
+
+ "transferLog":"Het ticket is overgedragen van {0} naar {1} door {2}!",
+ "transferDm":"U ticket is overgedragen van {0} naar {1} in onze server!",
+ "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!"
}
},
"transcripts":{
@@ -229,7 +258,7 @@
"ready":"Transcript Aangemaakt",
"textFileDescription":"Dit is een tekst transcript van een verwijderd ticket!",
"htmlProgress":"Gelieve te wachten terwijl dit html transcript verwerkt wordt...",
-
+
"createdChannel":"Een nieuw {0} transcript is aangemaakt in de server!",
"createdCreator":"Een nieuw {0} transcript is aangemaakt voor een van jouw tickets!",
"createdParticipant":"Een nieuw {0} transcript is aangemaakt in een van de tickets waar jij lid van was!",
@@ -241,7 +270,19 @@
"retry":"Herprobeer",
"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."
+ "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"
+ },
+ "text":{
+ "messagesTitle":"BERICHTEN",
+ "embedTitle":"EMBED",
+ "fileTitle":"BESTAND",
+ "fieldsTitle":"VELDEN",
+ "reactionsTitle":"REACTIES",
+ "statsTitle":"STATS",
+ "emptyContent":"",
+ "noTitle":"",
+ "noDesc":""
}
},
"errors":{
@@ -257,7 +298,8 @@
"unknownPanel":"Onbekend Paneel",
"notInGuild":"Niet In Server",
"channelRename":"Kan Kanaal Niet Hernoemen",
- "busy":"Ticket Is Bezig"
+ "busy":"Ticket Is Bezig",
+ "permissionError":"Permission Error"
},
"descriptions":{
"askForInfo":"Contacteer the eigenaar van deze bot voor meer info!",
@@ -280,7 +322,10 @@
"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.",
"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!"
+ "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."
},
"optionInvalidReasons":{
"stringRegex":"Waarde is niet gelijk aan het patroon!",
@@ -331,7 +376,6 @@
"added":"Toegevoegd",
"removed":"Verwijderd",
"filter":"Filter",
- "claimedBy":"Geclaimd Door {0}",
"method":"Methode",
"type":"Type",
"blacklisted":"Geblacklist",
@@ -355,7 +399,27 @@
"status":"Status",
"claimed":"Geclaimd",
"pinned":"Gepind",
- "creationDate":"Aanmaakdatum"
+ "creationDate":"Aanmaakdatum",
+
+ "noone":"Niemand",
+ "open":"Open",
+ "closed":"Gesloten",
+ "priority":"Prioriteit",
+ "participants":"Deelnemers",
+ "yes":"Ja",
+ "no":"Nee",
+ "option":"Optie",
+ "topic":"Onderwerp",
+ "uptime":"Systeem Uptime",
+ "messages":"Berichten",
+ "embeds":"Embeds",
+ "files":"Bestanden",
+ "components":"Componenten",
+ "cooldown":"Cooldown",
+ "maxTickets":"Max Tickets",
+ "admins":"Admins",
+ "roles":"Rollen",
+ "size":"Grootte"
},
"lowercase":{
"text":"tekst",
@@ -384,6 +448,7 @@
"unclaim":"Ontclaim dit ticket.",
"pin":"Pin dit ticket.",
"unpin":"Maak dit ticket los.",
+
"move":"Verplaats een ticket.",
"moveId":"Het id van de optie waarnaar je wilt verplaatsen.",
"rename":"Hernoem een ticket.",
@@ -392,6 +457,7 @@
"addUser":"De gebruiker om toe te voegen.",
"remove":"Verwijder een user van dit ticket.",
"removeUser":"De gebruiker om te verwijderen.",
+
"blacklist":"Beheer de ticket blacklist.",
"blacklistView":"Bekijk de huidige blacklist.",
"blacklistAdd":"Voeg een gebruiker toe aan de blacklist.",
@@ -405,6 +471,7 @@
"statsUserUser":"De gebruiker om te bekijken.",
"statsTicket":"Bekijk de stats van een ticket in de server.",
"statsTicketTicket":"Het ticket om te bekijken.",
+
"clear":"Verwijder meerdere tickets tegelijkertijd.",
"clearFilter":"De filter om tickets op te ruimen.",
"clearFilters":{
@@ -417,6 +484,7 @@
"unpin":"Losgemaakt (niet gepind)",
"autoclose":"Automatisch Gesloten"
},
+
"autoclose":"Beheer autoclose in een ticket.",
"autocloseDisable":"Schakel autoclose in dit ticket uit.",
"autocloseEnable":"Schakel autoclose in dit ticket in.",
@@ -424,7 +492,19 @@
"autodelete":"Beheer autodelete in een ticket.",
"autodeleteDisable":"Schakel autodelete in dit ticket aan.",
"autodeleteEnable":"Schakel autodelete in dit ticket aan.",
- "autodeleteEnableTime":"De hoeveelheid dagen dat dit ticket inactief moet zijn voor het te verwijderen."
+ "autodeleteEnableTime":"De hoeveelheid dagen dat dit ticket inactief moet zijn voor het te verwijderen.",
+
+ "topic":"Beheer het onderwerp van een ticket kanaal.",
+ "topicSet":"Verander het onderwerp van een ticket kanaal.",
+ "topicValue":"Het nieuwe onderwerp van het kanaal.",
+ "topicList":"Bekijk een lijst van alle tickets met hun onderwerp en stats.",
+ "priority":"Beheer de prioriteit van een ticket.",
+ "prioritySet":"Verander de prioriteit van een ticket.",
+ "priorityValue":"De prioriteit van het kanaal.",
+ "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."
},
"helpMenu":{
"help":"Krijg een lijst van alle beschikbare commands.",
@@ -452,7 +532,16 @@
"autocloseDisable":"Zet autoclose uit in dit ticket.",
"autocloseEnable":"Zet autoclose in aan dit ticket.",
"autodeleteDisable":"Zet autodelete uit in dit ticket.",
- "autodeleteEnable":"Zet autodelete aan in dit ticket."
+ "autodeleteEnable":"Zet autodelete aan in dit ticket.",
+ "categories":{
+ "general":"Algemene Commands",
+ "basicTicket":"Basis Ticket Commands",
+ "advancedTicket":"Geavanceerde Ticket Commands",
+ "userTicket":"Gebruiker Ticket Commands",
+ "admin":"Admin Commands",
+ "advanced":"Geavanceerde Commands",
+ "extra":"Extra Commands"
+ }
},
"stats":{
"scopes":{
@@ -460,7 +549,8 @@
"system":"Systeem Stats",
"user":"User Stats",
"ticket":"Ticket Stats",
- "participants":"Deelnemers"
+ "participants":"Deelnemers",
+ "messages":"Berichten"
},
"properties":{
"ticketsCreated":"Tickets Aangemaakt",
@@ -472,7 +562,47 @@
"ticketsPinned":"Tickets Gepind",
"ticketsMoved":"Tickets Verplaatst",
"usersBlacklisted":"Users Geblacklist",
- "transcriptsCreated":"Transcripts Aangemaakt"
+ "transcriptsCreated":"Transcripts Aangemaakt",
+ "ticketsAutodeleted":"Tickets Autodeleted",
+ "ticketsTransferred":"Tickets Overgedragen",
+ "ticketVolume":"Ticket Volume",
+ "averageTickets":"Gemiddelde Tickets/Gebruiker",
+ "currentTickets":"Huidige Tickets",
+ "age":"Ticket Leeftijd",
+ "responseTime":"Antwoordtijd",
+ "resolutionTime":"Oplostijd",
+ "createdOn":"Gemaakt Op",
+ "createdBy":"Gemaakt Door",
+ "closedOn":"Gesloten Op",
+ "closedBy":"Gesloten Door",
+ "claimedOn":"Geclaimed Op",
+ "claimedBy":"Geclaimed Door",
+ "pinnedOn":"Gepint Op",
+ "pinnedBy":"Gepint Door",
+ "deletedOn":"Verwijderd Op",
+ "deletedBy":"Verwijderd Door"
+ },
+ "roles":{
+ "developer":"Developer",
+ "serverOwner":"Server Eigenaar",
+ "serverAdmin":"Server Admin",
+ "moderator":"Moderator Team",
+ "support":"Support Team",
+ "member":"Serverlid"
}
+ },
+ "panel":{
+ "selectTicket":"Selecteer u ticket",
+ "selectRole":"Selecteer u rol",
+ "selectOption":"Selecteer u optie"
+ },
+ "priorities":{
+ "urgent":"Urgent",
+ "veryHigh":"Zeer Hoog",
+ "high":"Hoog",
+ "normal":"Normaal",
+ "low":"Laag",
+ "veryLow":"Zeer Laag",
+ "none":"Geen"
}
}
\ No newline at end of file
diff --git a/languages/english.json b/languages/english.json
index 886fd42..9b86fa2 100644
--- a/languages/english.json
+++ b/languages/english.json
@@ -1,8 +1,8 @@
{
"_TRANSLATION":{
- "otversion":"v4.0.7",
+ "otversion":"v4.1.0",
"translators":["DJj123dj"],
- "lastedited":"21/08/2024",
+ "lastedited":"09/11/2025",
"language":"English",
"automated":false
},
@@ -31,7 +31,15 @@
"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!",
+
"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!",
@@ -48,10 +56,12 @@
"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}!",
+
"booleanTrue":"This boolean can't be true!",
"booleanFalse":"This boolean can't be false!",
-
+
"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}!",
@@ -59,7 +69,7 @@
"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!",
-
+
"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)!",
@@ -76,7 +86,7 @@
"urlInvalidPath":"This url has an invalid path!",
"idNotUnique":"This id isn't unique, use another id instead!",
"idNonExistent":"The id {0} doesn't 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!",
@@ -84,12 +94,13 @@
"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}!",
-
+
"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!"
+ "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!"
}
},
"actions":{
@@ -132,6 +143,7 @@
"blacklistAddDm":"Added To Blacklist",
"blacklistRemoveDm":"Removed From Blacklist",
"clear":"Tickets Cleared",
+ "clearTickets":"Clear Tickets",
"roles":"Roles Updated",
"autoclose":"Ticket Autoclosed",
@@ -139,7 +151,12 @@
"autocloseDisabled":"Autoclose Disabled",
"autodelete":"Ticket Autodeleted",
"autodeleteEnabled":"Autodelete Enabled",
- "autodeleteDisabled":"Autodelete Disabled"
+ "autodeleteDisabled":"Autodelete Disabled",
+
+ "topicSet":"Topic Changed",
+ "prioritySet":"Priority Changed",
+ "priorityGet":"Ticket Priority",
+ "transfer":"Ticket Transferred"
},
"descriptions":{
"create":"Your ticket has been created. Click the button below to access it!",
@@ -154,7 +171,7 @@
"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!",
-
+
"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!",
@@ -167,7 +184,7 @@
"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!",
-
+
"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!",
@@ -176,11 +193,16 @@
"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!",
-
+
"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":"You can find the panel below!\nThis message can now be deleted!"
+ "panelReady":"The panel is available in the followup message!\nThis message can now be deleted!",
+
+ "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!"
},
"modal":{
"closePlaceholder":"Why did you close this ticket?",
@@ -215,12 +237,19 @@
"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!",
-
+
"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}!"
+ "clearLog":"{0} tickets 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!"
}
},
"transcripts":{
@@ -229,7 +258,7 @@
"ready":"Transcript Created",
"textFileDescription":"This is the text transcript of a deleted ticket!",
"htmlProgress":"Please wait while this html transcript is getting processed...",
-
+
"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!",
@@ -241,7 +270,19 @@
"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."
+ "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"
+ },
+ "text":{
+ "messagesTitle":"MESSAGES",
+ "embedTitle":"EMBED",
+ "fileTitle":"FILE",
+ "fieldsTitle":"FIELDS",
+ "reactionsTitle":"REACTIONS",
+ "statsTitle":"STATS",
+ "emptyContent":"",
+ "noTitle":"",
+ "noDesc":""
}
},
"errors":{
@@ -257,7 +298,8 @@
"unknownPanel":"Unknown Panel",
"notInGuild":"Not In Server",
"channelRename":"Unable To Rename Channel",
- "busy":"Ticket Is Busy"
+ "busy":"Ticket Is Busy",
+ "permissionError":"Permission Error"
},
"descriptions":{
"askForInfo":"Contact the owner of this bot for more info!",
@@ -280,7 +322,10 @@
"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!"
+ "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."
},
"optionInvalidReasons":{
"stringRegex":"Value doesn't match pattern!",
@@ -331,7 +376,6 @@
"added":"Added",
"removed":"Removed",
"filter":"Filter",
- "claimedBy":"Claimed By {0}",
"method":"Method",
"type":"Type",
"blacklisted":"Blacklisted",
@@ -355,7 +399,27 @@
"status":"Status",
"claimed":"Claimed",
"pinned":"Pinned",
- "creationDate":"Creation Date"
+ "creationDate":"Creation Date",
+
+ "noone":"No-One",
+ "open":"Open",
+ "closed":"Closed",
+ "priority":"Priority",
+ "participants":"Participants",
+ "yes":"Yes",
+ "no":"No",
+ "option":"Option",
+ "topic":"Topic",
+ "uptime":"System Uptime",
+ "messages":"Messages",
+ "embeds":"Embeds",
+ "files":"Files",
+ "components":"Components",
+ "cooldown":"Cooldown",
+ "maxTickets":"Max Tickets",
+ "admins":"Admins",
+ "roles":"Roles",
+ "size":"Size"
},
"lowercase":{
"text":"text",
@@ -384,6 +448,7 @@
"unclaim":"Unclaim a ticket.",
"pin":"Pin a ticket.",
"unpin":"Unpin a ticket.",
+
"move":"Move a ticket.",
"moveId":"The identifier of the option that you want to move to.",
"rename":"Rename a ticket.",
@@ -392,6 +457,7 @@
"addUser":"The user to add.",
"remove":"Remove a user from a ticket.",
"removeUser":"The user to remove.",
+
"blacklist":"Manage the ticket blacklist.",
"blacklistView":"View a list of the current blacklist.",
"blacklistAdd":"Add a user to the blacklist.",
@@ -405,6 +471,7 @@
"statsUserUser":"The user to view.",
"statsTicket":"View the stats of a ticket in the server.",
"statsTicketTicket":"The ticket to view.",
+
"clear":"Delete multiple tickets at the same time.",
"clearFilter":"The filter for clearing tickets.",
"clearFilters":{
@@ -417,6 +484,7 @@
"unpin":"Unpinned",
"autoclose":"Autoclosed"
},
+
"autoclose":"Manage autoclose in a ticket.",
"autocloseDisable":"Disable autoclose in this ticket.",
"autocloseEnable":"Enable autoclose in this ticket.",
@@ -424,7 +492,19 @@
"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."
+ "autodeleteEnableTime":"The amount of days this ticket needs to be inactive to delete it.",
+
+ "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."
},
"helpMenu":{
"help":"Get a list of all the available commands.",
@@ -452,7 +532,16 @@
"autocloseDisable":"Disable autoclose in this ticket.",
"autocloseEnable":"Enable autoclose in this ticket.",
"autodeleteDisable":"Disable autodelete in this ticket.",
- "autodeleteEnable":"Enable autodelete in this ticket."
+ "autodeleteEnable":"Enable autodelete in this ticket.",
+ "categories":{
+ "general":"General Commands",
+ "basicTicket":"Basic Ticket Commands",
+ "advancedTicket":"Advanced Ticket Commands",
+ "userTicket":"User Ticket Commands",
+ "admin":"Admin Commands",
+ "advanced":"Advanced Commands",
+ "extra":"Extra Commands"
+ }
},
"stats":{
"scopes":{
@@ -460,7 +549,8 @@
"system":"System Stats",
"user":"User Stats",
"ticket":"Ticket Stats",
- "participants":"Participants"
+ "participants":"Participants",
+ "messages":"Messages"
},
"properties":{
"ticketsCreated":"Tickets Created",
@@ -472,7 +562,47 @@
"ticketsPinned":"Tickets Pinned",
"ticketsMoved":"Tickets Moved",
"usersBlacklisted":"Users Blacklisted",
- "transcriptsCreated":"Transcripts Created"
+ "transcriptsCreated":"Transcripts Created",
+ "ticketsAutodeleted":"Tickets Autodeleted",
+ "ticketsTransferred":"Tickets Transferred",
+ "ticketVolume":"Ticket Volume",
+ "averageTickets":"Average Tickets/User",
+ "currentTickets":"Current Tickets",
+ "age":"Ticket Age",
+ "responseTime":"Response Time",
+ "resolutionTime":"Resolution Time",
+ "createdOn":"Created On",
+ "createdBy":"Created By",
+ "closedOn":"Closed On",
+ "closedBy":"Closed By",
+ "claimedOn":"Claimed On",
+ "claimedBy":"Claimed By",
+ "pinnedOn":"Pinned On",
+ "pinnedBy":"Pinned By",
+ "deletedOn":"Deleted On",
+ "deletedBy":"Deleted By"
+ },
+ "roles":{
+ "developer":"Developer",
+ "serverOwner":"Server Owner",
+ "serverAdmin":"Server Admin",
+ "moderator":"Moderator Team",
+ "support":"Support Team",
+ "member":"Member"
}
+ },
+ "panel":{
+ "selectTicket":"Select your ticket",
+ "selectRole":"Select your role",
+ "selectOption":"Select your option"
+ },
+ "priorities":{
+ "urgent":"Urgent",
+ "veryHigh":"Very High",
+ "high":"High",
+ "normal":"Normal",
+ "low":"Low",
+ "veryLow":"Very Low",
+ "none":"None"
}
}
\ No newline at end of file
diff --git a/languages/estonian.json b/languages/estonian.json
index 4d3ef65..1430e9d 100644
--- a/languages/estonian.json
+++ b/languages/estonian.json
@@ -1,8 +1,8 @@
{
"_TRANSLATION":{
- "otversion":"v4.0.7",
+ "otversion":"v4.1.0",
"translators":["iamnotmega","ChatGPT"],
- "lastedited":"21/10/2024",
+ "lastedited":"09/11/2025",
"language":"Estonian",
"automated":true
},
@@ -31,7 +31,15 @@
"stringContains":"See string peab sisaldama {0}!",
"stringChoices":"See string vรตib olla ainult รผks jรคrgmistest vรครคrtustest: {0}!",
"stringRegex":"See string on kehtetu!",
-
+ "stringInvertedContains":"See string ei tohi sisaldada {0}!",
+ "stringLowercase":"See string peab olema kirjutatud ainult vรคikeste tรคhtedega!",
+ "stringUppercase":"See string peab olema kirjutatud ainult suurte tรคhtedega!",
+ "stringSpecialCharacters":"See string ei tohi sisaldada erimรคrke! (ainult a-z, 0-9 ja tรผhik)",
+ "stringNoSpaces":"See string ei tohi sisaldada tรผhikuid!",
+ "stringCapitalWord":"Soovitatav on, et iga sรตna selles stringis algaks suure tรคhega!",
+ "stringCapitalSentence":"Tundub, et mรตned laused selles stringis ei alga suure tรคhega!",
+ "stringPunctuation":"Tundub, et lause selles stringis ei lรตpe kirjavahemรคrgiga!",
+
"numberTooShort":"See number ei tohi olla lรผhem kui {0} tรคhemรคrki!",
"numberTooLong":"See number ei tohi olla pikem kui {0} tรคhemรคrki!",
"numberLengthInvalid":"See number peab olema {0} tรคhemรคrki pikk!",
@@ -48,10 +56,12 @@
"numberNegative":"See arv ei saa olla negatiivne!",
"numberPositive":"See arv ei saa olla positiivne!",
"numberZero":"See arv ei saa olla null!",
-
+ "numberNan":"See number ei tohi olla NaN (mitte number)!",
+ "numberInvertedContains":"See number ei tohi sisaldada {0}!",
+
"booleanTrue":"See tรตevรครคrtus ei saa olla tรตsi!",
"booleanFalse":"See tรตevรครคrtus ei saa olla vale!",
-
+
"arrayEmptyDisabled":"See massiiv ei tohi olla tรผhi!",
"arrayEmptyRequired":"See massiiv peab olema tรผhi!",
"arrayTooShort":"Selle massiivi pikkus peab olema vรคhemalt {0}!",
@@ -59,7 +69,7 @@
"arrayLengthInvalid":"Selle massiivi pikkus peab olema {0}!",
"arrayInvalidTypes":"See massiiv vรตib sisaldada ainult jรคrgmisi tรผรผpe: {0}!",
"arrayDouble":"See massiiv ei luba sama vรครคrtust kaks korda kasutada!",
-
+
"discordInvalidId":"See on kehtetu Discordi {0} ID!",
"discordInvalidIdOptions":"See on kehtetu Discordi {0} ID! Vรตite kasutada ka รผhte jรคrgmistest: {1}!",
"discordInvalidToken":"See on kehtetu Discordi tooken (sรผntaktiliselt)!",
@@ -76,7 +86,7 @@
"urlInvalidPath":"Sellel URL-il on vale tee!",
"idNotUnique":"See ID ei ole kordumatu, kasutage selle asemel teist ID-d!",
"idNonExistent":"ID-d {0} pole olemas!",
-
+
"invalidType":"Selle atribuudi tรผรผp peab olema: {0}!",
"propertyMissing":"Sellel objektil puudub atribuut {0}!",
"propertyOptional":"Atribuut {0} on selles objektis valikuline!",
@@ -84,12 +94,13 @@
"nullInvalid":"See vara ei saa olla tรผhi!",
"switchInvalidType":"See peab olema รผks jรคrgmistest tรผรผpidest: {0}!",
"objectSwitchInvalid":"See objekt peab olema รผhte jรคrgmistest tรผรผpidest: {0}!",
-
+
"invalidLanguage":"See on sobimatu keel!",
"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":"Lubatud rippmenรผรผga paneel vรตib sisaldada ainult \"pileti\" tรผรผpi valikuid!",
+ "customInvalidVersion":"Teie konfiguratsioonis mรครคratud versioon ei รผhti! Veenduge, et olete konfiguratsiooni uuendanud uusimale versioonile!"
}
},
"actions":{
@@ -122,7 +133,7 @@
"move":"Pilet teisaldatud",
"add":"Pileti kasutaja lisatud",
"remove":"Pileti kasutaja eemaldatud",
-
+
"help":"Saadaolevad kรคsud",
"statsReset":"Lรคhtestage statistika",
"blacklistAdd":"Kasutaja on mustas nimekirjas",
@@ -132,14 +143,20 @@
"blacklistAddDm":"Lisatud musta nimekirja",
"blacklistRemoveDm":"Eemaldatud mustast nimekirjast",
"clear":"Piletid kustutatud",
+ "clearTickets":"Kustuta Piletid",
"roles":"Rollid uuendatud",
-
+
"autoclose":"Pilet automaatselt suletud",
"autocloseEnabled":"Automaatne sulgemine lubatud",
"autocloseDisabled":"Automaatne sulgemine keelatud",
"autodelete":"Pilet on automaatselt kustutatud",
"autodeleteEnabled":"Automaatne kustutamine lubatud",
- "autodeleteDisabled":"Automaatne kustutamine keelatud"
+ "autodeleteDisabled":"Automaatne kustutamine keelatud",
+
+ "topicSet":"Teema Muudetud",
+ "prioritySet":"Prioriteet Muudetud",
+ "priorityGet":"Pileti Prioriteet",
+ "transfer":"Pilet รle Antud"
},
"descriptions":{
"create":"Teie pilet on loodud. Sellele juurdepรครคsuks klรตpsake alloleval nupul!",
@@ -154,7 +171,7 @@
"move":"Pilet teisaldati asukohta {0} edukalt!",
"add":"{0} lisati piletile edukalt!",
"remove":"{0} eemaldati piletist edukalt!",
-
+
"helpExplanation":"`` => nรตutav parameeter\n`[nimi]` => valikuline parameeter",
"statsReset":"Boti statistika on edukalt lรคhtestatud!",
"statsError":"Piletistatistikat ei saa vaadata!\n{0} ei ole pilet!",
@@ -167,7 +184,7 @@
"clearVerify":"Kas soovite kindlasti mitu piletit kustutada?\nSeda toimingut ei saa tagasi vรตtta!",
"clearReady":"{0} piletit on edukalt kustutatud!",
"rolesEmpty":"รhtegi rolli pole vรคrskendatud!",
-
+
"autocloseLeave":"See pilet on automaatselt suletud, kuna looja lahkus serverist!",
"autocloseTimeout":"See pilet on automaatselt suletud, kuna see on olnud passiivne rohkem kui `{0}h`!",
"autodeleteLeave":"See pilet on automaatselt kustutatud, kuna looja lahkus serverist!",
@@ -176,11 +193,16 @@
"autocloseDisabled":"Automaatne sulgemine on sellel piletil keelatud!\nSeda ei suleta enam automaatselt!",
"autodeleteEnabled":"Sellel piletil on automaatne kustutamine lubatud!\nSee kustutatakse, kui see on passiivne rohkem kui `{0} pรคeva`!",
"autodeleteDisabled":"Automaatne kustutamine on sellel piletil keelatud!\nSeda ei kustutata enam automaatselt!",
-
+
"ticketMessageLimit":"Saate korraga luua ainult {0} piletit!",
"ticketMessageAutoclose":"See pilet suletakse automaatselt, kui see on {0} tundi passiivne!",
"ticketMessageAutodelete":"See pilet kustutatakse automaatselt, kui see on {0} pรคeva passiivne!",
- "panelReady":"Paneeli leiate altpoolt!\nSelle sรตnumi saab nรผรผd kustutada!"
+ "panelReady":"Paneel on saadaval jรคrelteates!\nSelle sรตnumi vรตib nรผรผd kustutada!",
+
+ "topicSet":"Kanaliteema on edukalt muudetud kasutaja {0} poolt!",
+ "prioritySet":"Pileti prioriteet on edukalt muudetud vรครคrtuseks {0} kasutaja {1} poolt!",
+ "priorityGet":"Selle pileti praegune prioriteet on {0}.",
+ "transfer":"Pileti omand on edukalt รผle antud kasutajalt {0} kasutajale {1} kasutaja {2} poolt!"
},
"modal":{
"closePlaceholder":"Miks sa selle pileti kinni panid?",
@@ -215,12 +237,19 @@
"addDm":"{0} lisati teie piletile meie serveris!",
"removeLog":"Kasutaja {1} eemaldas sellelt piletilt {0}!",
"removeDm":"{0} on teie piletist meie serveris eemaldatud!",
-
+
"blacklistAddLog":"{1} lisas kasutaja {0} musta nimekirja!",
"blacklistRemoveLog":"Kasutaja {1} eemaldas kasutaja {0} mustast nimekirjast!",
"blacklistAddDm":"Olete meie serveris mustas nimekirjas!\nNรผรผdsest ei saa te piletit luua!",
"blacklistRemoveDm":"Teid on meie serveri mustast nimekirjast eemaldatud!\nNรผรผd saate uuesti pileteid luua!",
- "clearLog":"{1} on kustutanud {0} piletit!"
+ "clearLog":"{1} on kustutanud {0} piletit!",
+
+ "transferLog":"Selle pileti omand on รผle antud kasutajalt {0} kasutajale {1} kasutaja {2} poolt!",
+ "transferDm":"Teie pileti omand on รผle antud kasutajalt {0} kasutajale {1} meie serveris!",
+ "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!"
}
},
"transcripts":{
@@ -229,7 +258,7 @@
"ready":"Transkriptsioon loodud",
"textFileDescription":"See on kustutatud pileti teksti รคrakiri!",
"htmlProgress":"Palun oodake, kuni seda html-i รคrakirja tรถรถdeldakse...",
-
+
"createdChannel":"Serveris on loodud uus {0} รคrakiri!",
"createdCreator":"รhe teie pileti jaoks on loodud uus {0} รคrakiri!",
"createdParticipant":"รhes teie osalenud piletis on loodud uus {0} รคrakiri!",
@@ -241,7 +270,19 @@
"retry":"Proovi uuesti",
"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."
+ "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"
+ },
+ "text":{
+ "messagesTitle":"SรNUMID",
+ "embedTitle":"EMBED",
+ "fileTitle":"FAIL",
+ "fieldsTitle":"VรLJAD",
+ "reactionsTitle":"REAKTSIOONID",
+ "statsTitle":"STATISTIKA",
+ "emptyContent":"",
+ "noTitle":"",
+ "noDesc":""
}
},
"errors":{
@@ -257,7 +298,8 @@
"unknownPanel":"Tundmatu paneel",
"notInGuild":"Pole serveris",
"channelRename":"Kanalit ei saa รผmber nimetada",
- "busy":"Pilet on kinni"
+ "busy":"Pilet on kinni",
+ "permissionError":"รiguste Viga"
},
"descriptions":{
"askForInfo":"Lisateabe saamiseks vรตtke รผhendust selle roboti omanikuga!",
@@ -280,7 +322,10 @@
"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.",
"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!"
+ "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."
},
"optionInvalidReasons":{
"stringRegex":"Vรครคrtus ei vasta mustrile!",
@@ -331,7 +376,6 @@
"added":"Lisatud",
"removed":"Eemaldatud",
"filter":"Filter",
- "claimedBy":"Nรตude esitas {0}",
"method":"Meetod",
"type":"Tรผรผp",
"blacklisted":"Mustas nimekirjas",
@@ -355,7 +399,27 @@
"status":"Olek",
"claimed":"Nรตutud",
"pinned":"Kinnitatud",
- "creationDate":"Loomise kuupรคev"
+ "creationDate":"Loomise kuupรคev",
+
+ "noone":"Mitte Keegi",
+ "open":"Avatud",
+ "closed":"Suletud",
+ "priority":"Prioriteet",
+ "participants":"Osalejad",
+ "yes":"Jah",
+ "no":"Ei",
+ "option":"Valik",
+ "topic":"Teema",
+ "uptime":"Sรผsteemi Tรถรถtamise Aeg",
+ "messages":"Sรตnumid",
+ "embeds":"Embedid",
+ "files":"Failid",
+ "components":"Komponendid",
+ "cooldown":"Jahtumisaeg",
+ "maxTickets":"Maksimaalselt Pileteid",
+ "admins":"Adminid",
+ "roles":"Rollid",
+ "size":"Suurus"
},
"lowercase":{
"text":"tekst",
@@ -384,6 +448,7 @@
"unclaim":"Tรผhista pilet.",
"pin":"Kinnitage pilet.",
"unpin":"Vabastage pilet.",
+
"move":"Liigutage pilet.",
"moveId":"Selle valiku identifikaator, millele soovite liikuda.",
"rename":"Nimetage pilet รผmber.",
@@ -392,6 +457,7 @@
"addUser":"Lisatav kasutaja.",
"remove":"Kasutaja eemaldamine piletist.",
"removeUser":"Eemaldatav kasutaja.",
+
"blacklist":"Hallake piletite musta nimekirja.",
"blacklistView":"Vaadake praeguse musta nimekirja loendit.",
"blacklistAdd":"Lisage kasutaja musta nimekirja.",
@@ -405,6 +471,7 @@
"statsUserUser":"Kasutaja vaatamiseks.",
"statsTicket":"Vaadake serveris pileti statistikat.",
"statsTicketTicket":"Pilet vaatamiseks.",
+
"clear":"Kustutage mitu piletit korraga.",
"clearFilter":"Piletite puhastamise filter.",
"clearFilters":{
@@ -417,6 +484,7 @@
"unpin":"Vabastatud",
"autoclose":"Automaatselt suletud"
},
+
"autoclose":"Hallake piletis automaatset sulgemist.",
"autocloseDisable":"Keela sellel piletil automaatne sulgemine.",
"autocloseEnable":"Luba sellel piletil automaatne sulgemine.",
@@ -424,7 +492,19 @@
"autodelete":"Automaatse kustutamise haldamine piletis.",
"autodeleteDisable":"Keela sellel piletil automaatne kustutamine.",
"autodeleteEnable":"Luba sellel piletil automaatne kustutamine.",
- "autodeleteEnableTime":"Pรคevade arv, mille jooksul see pilet peab olema selle kustutamiseks passiivne."
+ "autodeleteEnableTime":"Pรคevade arv, mille jooksul see pilet peab olema selle kustutamiseks passiivne.",
+
+ "topic":"Halda pileti kanali teemat.",
+ "topicSet":"Mรครคra pileti kanali teema.",
+ "topicValue":"Kanali uus teema.",
+ "topicList":"Hangi nimekiri kรตigist piletitest koos nende teemade ja statistikaga.",
+ "priority":"Halda pileti prioriteeti.",
+ "prioritySet":"Mรครคra pileti prioriteet.",
+ "priorityValue":"Kanali prioriteet.",
+ "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."
},
"helpMenu":{
"help":"Hankige kรตigi saadaolevate kรคskude loend.",
@@ -452,7 +532,16 @@
"autocloseDisable":"Keela sellel piletil automaatne sulgemine.",
"autocloseEnable":"Luba sellel piletil automaatne sulgemine.",
"autodeleteDisable":"Keela sellel piletil automaatne kustutamine.",
- "autodeleteEnable":"Luba sellel piletil automaatne kustutamine."
+ "autodeleteEnable":"Luba sellel piletil automaatne kustutamine.",
+ "categories":{
+ "general":"รldised Kรคsud",
+ "basicTicket":"Pรตhilised Piletikรคsud",
+ "advancedTicket":"Tรคpsemad Piletikรคsud",
+ "userTicket":"Kasutaja Piletikรคsud",
+ "admin":"Admini Kรคsud",
+ "advanced":"Tรคpsemad Kรคsud",
+ "extra":"Lisakรคsud"
+ }
},
"stats":{
"scopes":{
@@ -460,7 +549,8 @@
"system":"Sรผsteemi statistika",
"user":"Kasutaja statistika",
"ticket":"Piletistatistika",
- "participants":"Osalejad"
+ "participants":"Osalejad",
+ "messages":"Sรตnumid"
},
"properties":{
"ticketsCreated":"Pileteid loodud",
@@ -472,7 +562,47 @@
"ticketsPinned":"Pileteid kinnitatud",
"ticketsMoved":"Pileteid teisaldatud",
"usersBlacklisted":"Kasutajaid mustas nimekirjas",
- "transcriptsCreated":"Loodud รคrakirjad"
+ "transcriptsCreated":"Loodud รคrakirjad",
+ "ticketsAutodeleted":"Piletid Automaatselt Kustutatud",
+ "ticketsTransferred":"Piletid รle Antud",
+ "ticketVolume":"Piletite Arv",
+ "averageTickets":"Keskmine Piletite Arv/Kasutaja",
+ "currentTickets":"Praegused Piletid",
+ "age":"Pileti Vanus",
+ "responseTime":"Reageerimisaeg",
+ "resolutionTime":"Lahendusaeg",
+ "createdOn":"Loodud",
+ "createdBy":"Looja",
+ "closedOn":"Suletud",
+ "closedBy":"Sulgenud",
+ "claimedOn":"Vรตetud",
+ "claimedBy":"Vรตtnud",
+ "pinnedOn":"Kinnitatud",
+ "pinnedBy":"Kinnitanud",
+ "deletedOn":"Kustutatud",
+ "deletedBy":"Kustutanud"
+ },
+ "roles":{
+ "developer":"Arendaja",
+ "serverOwner":"Serveri Omanik",
+ "serverAdmin":"Serveri Administraator",
+ "moderator":"Moderaatorite Meeskond",
+ "support":"Tugimeeskond",
+ "member":"Liige"
}
+ },
+ "panel":{
+ "selectTicket":"Vali oma pilet",
+ "selectRole":"Vali oma roll",
+ "selectOption":"Vali oma valik"
+ },
+ "priorities":{
+ "urgent":"Kiireloomuline",
+ "veryHigh":"Vรคga Kรตrge",
+ "high":"Kรตrge",
+ "normal":"Tavaline",
+ "low":"Madal",
+ "veryLow":"Vรคga Madal",
+ "none":"Puudub"
}
}
\ No newline at end of file
diff --git a/languages/finnish.json b/languages/finnish.json
index 67a39c6..3ccba5a 100644
--- a/languages/finnish.json
+++ b/languages/finnish.json
@@ -1,8 +1,8 @@
{
"_TRANSLATION":{
- "otversion":"v4.0.7",
+ "otversion":"v4.1.0",
"translators":["iamnotmega","ChatGPT"],
- "lastedited":"21/10/2024",
+ "lastedited":"09/11/2025",
"language":"Finnish",
"automated":true
},
@@ -31,7 +31,15 @@
"stringContains":"Tรคmรคn merkkijonon tรคytyy sisรคltรครค {0}!",
"stringChoices":"Tรคmรค merkkijono voi olla vain yksi seuraavista arvoista: {0}!",
"stringRegex":"Tรคmรค merkkijono on virheellinen!",
-
+ "stringInvertedContains":"Tรคmรค merkkijono ei saa sisรคltรครค {0}!",
+ "stringLowercase":"Tรคmรค merkkijono on kirjoitettava vain pienillรค kirjaimilla!",
+ "stringUppercase":"Tรคmรค merkkijono on kirjoitettava vain isoilla kirjaimilla!",
+ "stringSpecialCharacters":"Tรคmรค merkkijono ei saa sisรคltรครค erikoismerkkejรค! (vain aโz, 0โ9 ja vรคlilyรถnti)",
+ "stringNoSpaces":"Tรคmรค merkkijono ei saa sisรคltรครค vรคlilyรถntejรค!",
+ "stringCapitalWord":"On suositeltavaa, ettรค jokainen sana tรคssรค merkkijonossa alkaa isolla kirjaimella!",
+ "stringCapitalSentence":"Nรคyttรครค siltรค, ettรค jotkut lauseet tรคssรค merkkijonossa eivรคt ala isolla kirjaimella!",
+ "stringPunctuation":"Nรคyttรครค siltรค, ettรค tรคmรคn merkkijonon lause ei pรครคty vรคlimerkkiin!",
+
"numberTooShort":"Tรคmรค numero ei voi olla lyhyempi kuin {0} merkkiรค!",
"numberTooLong":"Tรคmรค numero ei voi olla pidempi kuin {0} merkkiรค!",
"numberLengthInvalid":"Tรคmรคn numeron on oltava {0} merkkiรค pitkรค!",
@@ -48,10 +56,12 @@
"numberNegative":"Tรคmรค luku ei voi olla negatiivinen!",
"numberPositive":"Tรคmรค luku ei voi olla positiivinen!",
"numberZero":"Tรคmรค luku ei voi olla nolla!",
-
+ "numberNan":"Tรคmรค numero ei voi olla NaN (ei numero)!",
+ "numberInvertedContains":"Tรคmรค numero ei saa sisรคltรครค {0}!",
+
"booleanTrue":"Tรคmรค boolean ei voi olla totta!",
"booleanFalse":"Tรคmรค boolean ei voi olla vรครคrรค!",
-
+
"arrayEmptyDisabled":"Tรคmรค taulukko ei saa olla tyhjรค!",
"arrayEmptyRequired":"Tรคmรคn taulukon on oltava tyhjรค!",
"arrayTooShort":"Tรคmรคn taulukon pituuden on oltava vรคhintรครคn {0}!",
@@ -59,7 +69,7 @@
"arrayLengthInvalid":"Tรคmรคn taulukon pituuden on oltava {0}!",
"arrayInvalidTypes":"Tรคmรค matriisi voi sisรคltรครค vain seuraavat tyypit: {0}!",
"arrayDouble":"Tรคmรค matriisi ei salli samaa arvoa kahdesti!",
-
+
"discordInvalidId":"Tรคmรค on virheellinen discord {0} -tunnus!",
"discordInvalidIdOptions":"Tรคmรค on virheellinen discord {0} -tunnus! Voit myรถs kรคyttรครค jotakin seuraavista: {1}!",
"discordInvalidToken":"Tรคmรค on virheellinen discord tunnus (syntaktisesti)!",
@@ -76,7 +86,7 @@
"urlInvalidPath":"Tรคllรค URL-osoitteella on virheellinen polku!",
"idNotUnique":"Tรคmรค tunnus ei ole ainutlaatuinen, kรคytรค sen sijaan toista tunnusta!",
"idNonExistent":"Tunnusta {0} ei ole olemassa!",
-
+
"invalidType":"Tรคmรคn ominaisuuden on oltava tyyppiรค: {0}!",
"propertyMissing":"Ominaisuus {0} puuttuu tรคstรค objektista!",
"propertyOptional":"Ominaisuus {0} on valinnainen tรคssรค objektissa!",
@@ -84,12 +94,13 @@
"nullInvalid":"Tรคmรค omaisuus ei voi olla tyhjรค!",
"switchInvalidType":"Tรคmรคn on oltava jokin seuraavista: {0}!",
"objectSwitchInvalid":"Tรคmรคn objektin on oltava jokin seuraavista tyypeistรค: {0}!",
-
+
"invalidLanguage":"Tรคmรค on virheellinen kieli!",
"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":"Paneeli, jossa pudotusvalikko on kรคytรถssรค, voi sisรคltรครค vain \"lippu\"-tyyppisiรค vaihtoehtoja!",
+ "customInvalidVersion":"Mรครคritetty versio kokoonpanossasi ei vastaa! Varmista, ettรค olet pรคivittรคnyt kokoonpanon uusimpaan versioon!"
}
},
"actions":{
@@ -122,7 +133,7 @@
"move":"Lippu siirretty",
"add":"Lipun kรคyttรคjรค lisรคtty",
"remove":"Lipun kรคyttรคjรค poistettu",
-
+
"help":"Kรคytettรคvissรค olevat komennot",
"statsReset":"Nollaa tilastot",
"blacklistAdd":"Kรคyttรคjรค mustalla listalla",
@@ -132,14 +143,20 @@
"blacklistAddDm":"Lisรคtty mustalle listalle",
"blacklistRemoveDm":"Poistettu mustalta listalta",
"clear":"Liput tyhjennetty",
+ "clearTickets":"Tyhjennรค Tiketit",
"roles":"Roolit pรคivitetty",
-
+
"autoclose":"Lippu suljettu automaattisesti",
"autocloseEnabled":"Automaattinen sulkeminen kรคytรถssรค",
"autocloseDisabled":"Automaattinen sulkeminen pois kรคytรถstรค",
"autodelete":"Lippu poistettu automaattisesti",
"autodeleteEnabled":"Automaattinen poisto kรคytรถssรค",
- "autodeleteDisabled":"Automaattinen poisto poissa kรคytรถstรค"
+ "autodeleteDisabled":"Automaattinen poisto poissa kรคytรถstรค",
+
+ "topicSet":"Aihe Muutettu",
+ "prioritySet":"Prioriteetti Muutettu",
+ "priorityGet":"Tiketin Prioriteetti",
+ "transfer":"Tiketti Siirretty"
},
"descriptions":{
"create":"Lippusi on luotu. Napsauta alla olevaa painiketta pรครคstรคksesi siihen!",
@@ -154,7 +171,7 @@
"move":"Lippu on siirretty kohteeseen {0} onnistuneesti!",
"add":"{0} on lisรคtty lippuun onnistuneesti!",
"remove":"{0} on poistettu lipusta onnistuneesti!",
-
+
"helpExplanation":"`` => pakollinen parametri\n`[nimi]` => valinnainen parametri",
"statsReset":"Bottitilastot on nollattu onnistuneesti!",
"statsError":"Lipputilastoja ei voi tarkastella!\n{0} ei ole lippu!",
@@ -167,7 +184,7 @@
"clearVerify":"Haluatko varmasti poistaa useita lippuja? Tรคtรค toimintoa ei voi kumota!",
"clearReady":"{0} lippua on poistettu onnistuneesti!",
"rolesEmpty":"Rooleja ei ole pรคivitetty!",
-
+
"autocloseLeave":"Tรคmรค lippu on suljettu automaattisesti, koska luoja poistui palvelimelta!",
"autocloseTimeout":"Tรคmรค lippu on suljettu automaattisesti, koska se on ollut passiivinen yli `{0}h`!",
"autodeleteLeave":"Tรคmรค lippu on poistettu automaattisesti, koska sisรคllรถntuottaja poistui palvelimelta!",
@@ -176,11 +193,16 @@
"autocloseDisabled":"Automaattinen sulkeminen on poistettu kรคytรถstรค tรคssรค lipussa! Se ei enรครค sulkeudu automaattisesti!",
"autodeleteEnabled":"Automaattinen poisto on otettu kรคyttรถรถn tรคssรค lipussa! Se poistetaan, kun se on passiivinen yli `{0} pรคivรครคn`!",
"autodeleteDisabled":"Automaattinen poisto on poistettu kรคytรถstรค tรคssรค lipussa! Sitรค ei enรครค poisteta automaattisesti!",
-
+
"ticketMessageLimit":"Voit luoda vain {0} lippua samanaikaisesti!",
"ticketMessageAutoclose":"Tรคmรค lippu suljetaan automaattisesti, kun se ei ole aktiivinen {0} tuntia!",
"ticketMessageAutodelete":"Tรคmรค lippu poistetaan automaattisesti, kun se ei ole aktiivinen {0} pรคivรครคn!",
- "panelReady":"Lรถydรคt paneelin alta! Tรคmรค viesti voidaan nyt poistaa!"
+ "panelReady":"Paneeli on saatavilla jatkoviestissรค!\nTรคmรค viesti voidaan nyt poistaa!",
+
+ "topicSet":"Kanavan aihe on vaihdettu kรคyttรคjรคn {0} toimesta onnistuneesti!",
+ "prioritySet":"Tiketin prioriteetti on vaihdettu arvoksi {0} kรคyttรคjรคn {1} toimesta onnistuneesti!",
+ "priorityGet":"Tรคmรคn tiketin nykyinen prioriteetti on {0}.",
+ "transfer":"Tiketin omistus on siirretty kรคyttรคjรคltรค {0} kรคyttรคjรคlle {1} kรคyttรคjรคn {2} toimesta onnistuneesti!"
},
"modal":{
"closePlaceholder":"Miksi suljit tรคmรคn lipun?",
@@ -215,12 +237,19 @@
"addDm":"{0} on lisรคtty lippuusi palvelimellamme!",
"removeLog":"{1} on poistanut {0} tรคstรค lipusta!",
"removeDm":"{0} on poistettu lipustasi palvelimellamme!",
-
+
"blacklistAddLog":"{1} lisรคsi kรคyttรคjรคn {0} mustalle listalle!",
"blacklistRemoveLog":"{1} poisti kรคyttรคjรคn {0} mustalta listalta!",
"blacklistAddDm":"Sinut on mustalla listalla palvelimellamme! Tรคstรค eteenpรคin et voi luoda lippua!",
"blacklistRemoveDm":"Sinut on poistettu palvelimemme mustalta listalta! Nyt voit luoda lippuja uudelleen!",
- "clearLog":"{1} on poistanut {0} lippua!"
+ "clearLog":"{1} on poistanut {0} lippua!",
+
+ "transferLog":"Tรคmรคn tiketin omistus on siirretty kรคyttรคjรคltรค {0} kรคyttรคjรคlle {1} kรคyttรคjรคn {2} toimesta!",
+ "transferDm":"Tiketin omistus on siirretty kรคyttรคjรคltรค {0} kรคyttรคjรคlle {1} palvelimellamme!",
+ "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!"
}
},
"transcripts":{
@@ -229,7 +258,7 @@
"ready":"Transkriptio luotu",
"textFileDescription":"Tรคmรค on poistetun lipun tekstikopio!",
"htmlProgress":"Odota, kun tรคtรค html-transkriptiota kรคsitellรครคn...",
-
+
"createdChannel":"Uusi {0}-transkriptio on luotu palvelimelle!",
"createdCreator":"Yhdelle lipullesi on luotu uusi transkriptio {0}!",
"createdParticipant":"Uusi {0}-transkriptio on luotu yhteen lipuista, joihin osallistuit!",
@@ -241,7 +270,19 @@
"retry":"Yritรค uudelleen",
"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. "
+ "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"
+ },
+ "text":{
+ "messagesTitle":"VIESTIT",
+ "embedTitle":"UPOTUS",
+ "fileTitle":"TIEDOSTO",
+ "fieldsTitle":"KENTรT",
+ "reactionsTitle":"REAKTIOT",
+ "statsTitle":"TILASTOT",
+ "emptyContent":"",
+ "noTitle":"",
+ "noDesc":""
}
},
"errors":{
@@ -257,7 +298,8 @@
"unknownPanel":"Tuntematon paneeli",
"notInGuild":"Ei Palvelimessa",
"channelRename":"Kanavaa ei voi nimetรค uudelleen",
- "busy":"Lippu on varattu"
+ "busy":"Lippu on varattu",
+ "permissionError":"Kรคyttรถoikeusvirhe"
},
"descriptions":{
"askForInfo":"Ota yhteyttรค tรคmรคn botin omistajaan saadaksesi lisรคtietoja!",
@@ -280,7 +322,10 @@
"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.",
"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!"
+ "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รค."
},
"optionInvalidReasons":{
"stringRegex":"Arvo ei vastaa mallia!",
@@ -331,7 +376,6 @@
"added":"Lisรคtty",
"removed":"Poistettu",
"filter":"Suodattaa",
- "claimedBy":"Vaatimuksen tekijรค: {0}",
"method":"Menetelmรค",
"type":"Tyyppi",
"blacklisted":"Mustalla listalla",
@@ -352,10 +396,30 @@
"version":"Versio",
"name":"Nimi",
"role":"Rooli",
- "status":"Status",
+ "status":"Tila",
"claimed":"Vรคitetty",
"pinned":"Kiinnitetty",
- "creationDate":"Luontipรคivรคmรครคrรค"
+ "creationDate":"Luontipรคivรคmรครคrรค",
+
+ "noone":"Ei Kukaan",
+ "open":"Avoin",
+ "closed":"Suljettu",
+ "priority":"Prioriteetti",
+ "participants":"Osallistujat",
+ "yes":"Kyllรค",
+ "no":"Ei",
+ "option":"Vaihtoehto",
+ "topic":"Aihe",
+ "uptime":"Jรคrjestelmรคn Kรคyttรถaika",
+ "messages":"Viestit",
+ "embeds":"Upotukset",
+ "files":"Tiedostot",
+ "components":"Komponentit",
+ "cooldown":"Jรครคhy",
+ "maxTickets":"Maksimi Tiketit",
+ "admins":"Yllรคpitรคjรคt",
+ "roles":"Roolit",
+ "size":"Koko"
},
"lowercase":{
"text":"teksti",
@@ -384,6 +448,7 @@
"unclaim":"Peruuta lippu.",
"pin":"Kiinnitรค lippu.",
"unpin":"Irrota lippu.",
+
"move":"Siirrรค lippu.",
"moveId":"Sen vaihtoehdon tunniste, johon haluat siirtyรค.",
"rename":"Nimeรค lippu uudelleen.",
@@ -392,6 +457,7 @@
"addUser":"Lisรคttรคvรค kรคyttรคjรค.",
"remove":"Poista kรคyttรคjรค lipusta.",
"removeUser":"Poistettava kรคyttรคjรค.",
+
"blacklist":"Hallitse lippujen mustaa listaa.",
"blacklistView":"Nรคytรค luettelo nykyisestรค mustasta listasta.",
"blacklistAdd":"Lisรครค kรคyttรคjรค mustalle listalle.",
@@ -405,6 +471,7 @@
"statsUserUser":"Kรคyttรคjรค katsella.",
"statsTicket":"Tarkastele lipun tilastoja palvelimella.",
"statsTicketTicket":"Lippu katsottavaksi.",
+
"clear":"Poista useita lippuja samanaikaisesti.",
"clearFilter":"Lippujen tyhjennyssuodatin.",
"clearFilters":{
@@ -417,6 +484,7 @@
"unpin":"Irrotettu",
"autoclose":"Automaattisuljettu"
},
+
"autoclose":"Hallitse automaattista sulkemista lipussa.",
"autocloseDisable":"Poista automaattinen sulkeminen kรคytรถstรค tรคssรค lipussa.",
"autocloseEnable":"Ota automaattinen sulkeminen kรคyttรถรถn tรคssรค lipussa.",
@@ -424,7 +492,19 @@
"autodelete":"Hallitse automaattista poistoa lipussa.",
"autodeleteDisable":"Poista automaattinen poisto kรคytรถstรค tรคssรค lipussa.",
"autodeleteEnable":"Ota automaattinen poisto kรคyttรถรถn tรคssรค lipussa.",
- "autodeleteEnableTime":"Kuinka monta pรคivรครค tรคmรคn lipun on oltava passiivinen, jotta se voidaan poistaa."
+ "autodeleteEnableTime":"Kuinka monta pรคivรครค tรคmรคn lipun on oltava passiivinen, jotta se voidaan poistaa.",
+
+ "topic":"Hallitse tikettikanavan aihetta.",
+ "topicSet":"Aseta tikettikanavan aihe.",
+ "topicValue":"Kanavan uusi aihe.",
+ "topicList":"Hae lista kaikista tiketeistรค niiden aiheen ja tilastojen kanssa.",
+ "priority":"Hallitse tiketin prioriteettia.",
+ "prioritySet":"Aseta tiketin prioriteetti.",
+ "priorityValue":"Kanavan prioriteetti.",
+ "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."
},
"helpMenu":{
"help":"Hanki luettelo kaikista kรคytettรคvissรค olevista komennoista.",
@@ -452,7 +532,16 @@
"autocloseDisable":"Poista automaattinen sulkeminen kรคytรถstรค tรคssรค lipussa.",
"autocloseEnable":"Ota automaattinen sulkeminen kรคyttรถรถn tรคssรค lipussa.",
"autodeleteDisable":"Poista automaattinen poisto kรคytรถstรค tรคssรค lipussa.",
- "autodeleteEnable":"Ota automaattinen poisto kรคyttรถรถn tรคssรค lipussa."
+ "autodeleteEnable":"Ota automaattinen poisto kรคyttรถรถn tรคssรค lipussa.",
+ "categories":{
+ "general":"Yleiset Komennot",
+ "basicTicket":"Perus Tikettikomennot",
+ "advancedTicket":"Edistyneet Tikettikomennot",
+ "userTicket":"Kรคyttรคjรคn Tikettikomennot",
+ "admin":"Yllรคpitรคjรคkomennot",
+ "advanced":"Edistyneet Komennot",
+ "extra":"Lisรคkomennot"
+ }
},
"stats":{
"scopes":{
@@ -460,7 +549,8 @@
"system":"Jรคrjestelmรคtilastot",
"user":"Kรคyttรคjรคtilastot",
"ticket":"Lipputilastot",
- "participants":"Osallistujat"
+ "participants":"Osallistujat",
+ "messages":"Viestit"
},
"properties":{
"ticketsCreated":"Liput luotu",
@@ -472,7 +562,47 @@
"ticketsPinned":"Liput kiinni",
"ticketsMoved":"Liput siirretty",
"usersBlacklisted":"Kรคyttรคjรคt mustalla listalla",
- "transcriptsCreated":"Transkriptiot luotu"
+ "transcriptsCreated":"Transkriptiot luotu",
+ "ticketsAutodeleted":"Tiketit Automaattisesti Poistettu",
+ "ticketsTransferred":"Tiketit Siirretty",
+ "ticketVolume":"Tikettilukumรครคrรค",
+ "averageTickets":"Keskimรครคrรคiset Tiketit/Kรคyttรคjรค",
+ "currentTickets":"Nykyiset Tiketit",
+ "age":"Tiketin Ikรค",
+ "responseTime":"Vastausaika",
+ "resolutionTime":"Ratkaisuaika",
+ "createdOn":"Luotu",
+ "createdBy":"Luonut",
+ "closedOn":"Suljettu",
+ "closedBy":"Sulkenut",
+ "claimedOn":"Otettu Vastaan",
+ "claimedBy":"Vastaanottanut",
+ "pinnedOn":"Kiinnitetty",
+ "pinnedBy":"Kiinnittรคnyt",
+ "deletedOn":"Poistettu",
+ "deletedBy":"Poistanut"
+ },
+ "roles":{
+ "developer":"Kehittรคjรค",
+ "serverOwner":"Palvelimen Omistaja",
+ "serverAdmin":"Palvelimen Yllรคpitรคjรค",
+ "moderator":"Moderaattoritiimi",
+ "support":"Tukitiimi",
+ "member":"Jรคsen"
}
+ },
+ "panel":{
+ "selectTicket":"Valitse tikettisi",
+ "selectRole":"Valitse roolisi",
+ "selectOption":"Valitse vaihtoehtosi"
+ },
+ "priorities":{
+ "urgent":"Kiireellinen",
+ "veryHigh":"Erittรคin Korkea",
+ "high":"Korkea",
+ "normal":"Normaali",
+ "low":"Matala",
+ "veryLow":"Erittรคin Matala",
+ "none":"Ei Mikรครคn"
}
}
\ No newline at end of file
diff --git a/languages/french.json b/languages/french.json
index dd904aa..cc55ce9 100644
--- a/languages/french.json
+++ b/languages/french.json
@@ -1,6 +1,6 @@
{
"_TRANSLATION":{
- "otversion":"v4.0.7",
+ "otversion":"v4.1.0",
"translators":["guillee3"],
"lastedited":"04/12/2024",
"language":"French",
diff --git a/languages/german.json b/languages/german.json
index 0cc8439..67e9b0e 100644
--- a/languages/german.json
+++ b/languages/german.json
@@ -1,6 +1,6 @@
{
"_TRANSLATION":{
- "otversion":"v4.0.7",
+ "otversion":"v4.1.0",
"translators":["benzorich"],
"lastedited":"06/10/2024",
"language":"German",
diff --git a/languages/greek.json b/languages/greek.json
index ddb4cc9..bfd1dac 100644
--- a/languages/greek.json
+++ b/languages/greek.json
@@ -1,6 +1,6 @@
{
"_TRANSLATION":{
- "otversion":"v4.0.7",
+ "otversion":"v4.1.0",
"translators":["HanumeshGupta"],
"lastedited":"19/06/2025",
"language":"Greek",
diff --git a/languages/hindi.json b/languages/hindi.json
index 8edfd3f..439729f 100644
--- a/languages/hindi.json
+++ b/languages/hindi.json
@@ -1,8 +1,8 @@
{
"_TRANSLATION":{
- "otversion":"v4.0.7",
- "translators":["an_developer"],
- "lastedited":"14/12/2024",
+ "otversion":"v4.1.0",
+ "translators":["challenger_nova"],
+ "lastedited":"09/11/2025",
"language":"Hindi",
"automated":false
},
@@ -31,7 +31,15 @@
"stringContains":"เคเคธ เคธเฅเคเฅเคฐเคฟเคเค เคฎเฅเค {0} เคนเฅเคจเคพ เคเคตเคถเฅเคฏเค เคนเฅ!",
"stringChoices":"เคฏเคน เคธเฅเคเฅเคฐเคฟเคเค เคเฅเคตเคฒ เคจเคฟเคฎเฅเคจเคฒเคฟเคเคฟเคค เคฎเคพเคจเฅเค เคฎเฅเค เคธเฅ เคเค เคนเฅ เคธเคเคคเฅ เคนเฅ: {0}!",
"stringRegex":"เคฏเคน เคธเฅเคเฅเคฐเคฟเคเค เค
เคฎเคพเคจเฅเคฏ เคนเฅ!",
-
+ "stringInvertedContains":"เคเคธ เคธเฅเคเฅเคฐเคฟเคเค เคฎเฅเค {0} เคถเคพเคฎเคฟเคฒ เคจเคนเฅเค เคนเฅเคจเคพ เคเคพเคนเคฟเค!",
+ "stringLowercase":"เคฏเคน เคธเฅเคเฅเคฐเคฟเคเค เคเฅเคตเคฒ เคเฅเคเฅ เค
เคเฅเคทเคฐเฅเค เคฎเฅเค เคฒเคฟเคเฅ เคเคพเคจเฅ เคเคพเคนเคฟเค!",
+ "stringUppercase":"เคฏเคน เคธเฅเคเฅเคฐเคฟเคเค เคเฅเคตเคฒ เคฌเคกเคผเฅ เค
เคเฅเคทเคฐเฅเค เคฎเฅเค เคฒเคฟเคเฅ เคเคพเคจเฅ เคเคพเคนเคฟเค!",
+ "stringSpecialCharacters":"เคเคธ เคธเฅเคเฅเคฐเคฟเคเค เคฎเฅเค เคเฅเค เคตเคฟเคถเฅเคท เค
เคเฅเคทเคฐ เคจเคนเฅเค เคนเฅเคจเคพ เคเคพเคนเคฟเค! (เคเฅเคตเคฒ a-z, 0-9 เคเคฐ เคธเฅเคชเฅเคธ เค
เคจเฅเคฎเคค เคนเฅเค)",
+ "stringNoSpaces":"เคเคธ เคธเฅเคเฅเคฐเคฟเคเค เคฎเฅเค เคธเฅเคชเฅเคธ เคจเคนเฅเค เคนเฅเคจเคพ เคเคพเคนเคฟเค!",
+ "stringCapitalWord":"เคฏเคน เค
เคจเฅเคถเคเคธเคพ เคเฅ เคเคพเคคเฅ เคนเฅ เคเคฟ เคเคธ เคธเฅเคเฅเคฐเคฟเคเค เคเฅ เคชเฅเคฐเคคเฅเคฏเฅเค เคถเคฌเฅเคฆ เคเฅ เคถเฅเคฐเฅเคเคค เคฌเคกเคผเฅ เค
เคเฅเคทเคฐ เคธเฅ เคนเฅ!",
+ "stringCapitalSentence":"เคเคธเคพ เคฒเคเคคเคพ เคนเฅ เคเคฟ เคเคธ เคธเฅเคเฅเคฐเคฟเคเค เคฎเฅเค เคเฅเค เคตเคพเคเฅเคฏ เคฌเคกเคผเฅ เค
เคเฅเคทเคฐ เคธเฅ เคถเฅเคฐเฅ เคจเคนเฅเค เคนเฅเคคเฅ เคนเฅเค!",
+ "stringPunctuation":"เคเคธเคพ เคฒเคเคคเคพ เคนเฅ เคเคฟ เคเคธ เคธเฅเคเฅเคฐเคฟเคเค เคเคพ เคตเคพเคเฅเคฏ เคตเคฟเคฐเคพเคฎ เคเคฟเคนเฅเคจ เคเฅ เคธเคพเคฅ เคธเคฎเคพเคชเฅเคค เคจเคนเฅเค เคนเฅเคคเคพ เคนเฅ!",
+
"numberTooShort":"เคฏเคน เคธเคเคเฅเคฏเคพ {0} เคตเคฐเฅเคฃเฅเค เคธเฅ เคเฅเคเฅ เคจเคนเฅเค เคนเฅ เคธเคเคคเฅ!",
"numberTooLong":"เคฏเคน เคธเคเคเฅเคฏเคพ {0} เคตเคฐเฅเคฃเฅเค เคธเฅ เค
เคงเคฟเค เคฒเคเคฌเฅ เคจเคนเฅเค เคนเฅ เคธเคเคคเฅ!",
"numberLengthInvalid":"เคฏเคน เคธเคเคเฅเคฏเคพ {0} เคตเคฐเฅเคฃ เคฒเคเคฌเฅ เคนเฅเคจเฅ เคเคพเคนเคฟเค!",
@@ -48,10 +56,12 @@
"numberNegative":"เคฏเคน เคธเคเคเฅเคฏเคพ เคเคฃเคพเคคเฅเคฎเค เคจเคนเฅเค เคนเฅ เคธเคเคคเฅ!",
"numberPositive":"เคฏเคน เคธเคเคเฅเคฏเคพ เคธเคเคพเคฐเคพเคคเฅเคฎเค เคจเคนเฅเค เคนเฅ เคธเคเคคเฅ!",
"numberZero":"เคฏเคน เคธเคเคเฅเคฏเคพ เคถเฅเคจเฅเคฏ เคจเคนเฅเค เคนเฅ เคธเคเคคเฅ!",
-
+ "numberNan":"เคฏเคน เคธเคเคเฅเคฏเคพ NaN (Not A Number) เคจเคนเฅเค เคนเฅ เคธเคเคคเฅ!",
+ "numberInvertedContains":"เคเคธ เคธเคเคเฅเคฏเคพ เคฎเฅเค {0} เคถเคพเคฎเคฟเคฒ เคจเคนเฅเค เคนเฅเคจเคพ เคเคพเคนเคฟเค!",
+
"booleanTrue":"เคฏเคน เคฌเฅเคฒเคฟเคฏเคจ เคธเคคเฅเคฏ เคจเคนเฅเค เคนเฅ เคธเคเคคเคพ!",
"booleanFalse":"เคฏเคน เคฌเฅเคฒเคฟเคฏเคจ เคเฅเค เคพ เคจเคนเฅเค เคนเฅ เคธเคเคคเคพ!",
-
+
"arrayEmptyDisabled":"เคเคธ เคธเคฐเคฃเฅ เคเฅ เคเคพเคฒเฅ เคฐเคนเคจเฅ เคเฅ เค
เคจเฅเคฎเคคเคฟ เคจเคนเฅเค เคนเฅ!",
"arrayEmptyRequired":"เคฏเคน เคธเคฐเคฃเฅ เคเคพเคฒเฅ เคนเฅเคจเฅ เคเคตเคถเฅเคฏเค เคนเฅ!",
"arrayTooShort":"เคเคธ เคธเคฐเคฃเฅ เคเฅ เคฒเคเคฌเคพเค เคเคฎ เคธเฅ เคเคฎ {0} เคนเฅเคจเฅ เคเคพเคนเคฟเค!",
@@ -59,7 +69,7 @@
"arrayLengthInvalid":"เคเคธ เคธเคฐเคฃเฅ เคเฅ เคฒเคเคฌเคพเค {0} เคนเฅเคจเฅ เคเคพเคนเคฟเค!",
"arrayInvalidTypes":"เคเคธ เคธเคฐเคฃเฅ เคฎเฅเค เคเฅเคตเคฒ เคจเคฟเคฎเฅเคจเคฒเคฟเคเคฟเคค เคชเฅเคฐเคเคพเคฐ เคนเฅ เคธเคเคคเฅ เคนเฅเค: {0}!",
"arrayDouble":"เคฏเคน เคธเคฐเคฃเฅ เคเค เคนเฅ เคฎเคพเคจ เคเฅ เคฆเฅ เคฌเคพเคฐ เค
เคจเฅเคฎเคคเคฟ เคจเคนเฅเค เคฆเฅเคคเฅ เคนเฅ!",
-
+
"discordInvalidId":"เคฏเคน เคเค เค
เคฎเคพเคจเฅเคฏ เคเคฒเคน {0} เคเคเคกเฅ เคนเฅ!",
"discordInvalidIdOptions":"เคฏเคน เคเค เค
เคฎเคพเคจเฅเคฏ เคเคฒเคน {0} เคเคเคกเฅ เคนเฅ! ",
"discordInvalidToken":"เคฏเคน เคเค เค
เคฎเคพเคจเฅเคฏ เคเคฒเคน เคเฅเคเคจ เคนเฅ (เคตเคพเคเฅเคฏเคตเคฟเคจเฅเคฏเคพเคธ เคเฅ เคฆเฅเคทเฅเคเคฟ เคธเฅ)!",
@@ -76,7 +86,7 @@
"urlInvalidPath":"เคเคธ เคฏเฅเคเคฐเคเคฒ เคฎเฅเค เคเค เค
เคฎเคพเคจเฅเคฏ เคชเคฅ เคนเฅ!",
"idNotUnique":"เคฏเคน เคเคเคกเฅ เค
เคฆเฅเคตเคฟเคคเฅเคฏ เคจเคนเฅเค เคนเฅ, เคเคธเคเฅ เคฌเคเคพเคฏ เคเคฟเคธเฅ เค
เคจเฅเคฏ เคเคเคกเฅ เคเคพ เคเคชเคฏเฅเค เคเคฐเฅเค!",
"idNonExistent":"เคเคเคกเฅ {0} เคฎเฅเคเฅเคฆ เคจเคนเฅเค เคนเฅ!",
-
+
"invalidType":"เคฏเคน เคธเคเคชเคคเฅเคคเคฟ เคเคธ เคชเฅเคฐเคเคพเคฐ เคเฅ เคนเฅเคจเฅ เคเคพเคนเคฟเค: {0}!",
"propertyMissing":"เคเคธ เคเคฌเฅเคเฅเคเฅเค เคฎเฅเค เคธเคเคชเคคเฅเคคเคฟ {0} เคเคพเคฏเคฌ เคนเฅ!",
"propertyOptional":"เคเคธ เคเคฌเฅเคเฅเคเฅเค เคฎเฅเค เคธเคเคชเคคเฅเคคเคฟ {0} เคตเฅเคเคฒเฅเคชเคฟเค เคนเฅ!",
@@ -84,12 +94,13 @@
"nullInvalid":"เคฏเคน เคธเคเคชเคคเฅเคคเคฟ เคถเฅเคจเฅเคฏ เคจเคนเฅเค เคนเฅ เคธเคเคคเฅ!",
"switchInvalidType":"เคเคธเฅ เคจเคฟเคฎเฅเคจ เคชเฅเคฐเคเคพเคฐเฅเค เคฎเฅเค เคธเฅ เคเค เคนเฅเคจเคพ เคเคตเคถเฅเคฏเค เคนเฅ: {0}!",
"objectSwitchInvalid":"เคฏเคน เคเคฌเฅเคเฅเคเฅเค เคจเคฟเคฎเฅเคจ เคชเฅเคฐเคเคพเคฐเฅเค เคฎเฅเค เคธเฅ เคเค เคนเฅเคจเคพ เคเคพเคนเคฟเค: {0}!",
-
+
"invalidLanguage":"เคฏเคน เคเค เค
เคฎเคพเคจเฅเคฏ เคญเคพเคทเคพ เคนเฅ!",
"invalidButton":"เคเคธ เคฌเคเคจ เคชเคฐ เคเคฎ เคธเฅ เคเคฎ {0} เคฏเคพ {1} เคนเฅเคจเคพ เคเคตเคถเฅเคฏเค เคนเฅ!",
"unusedOption":"เคตเคฟเคเคฒเฅเคช {0} เคเคพ เคเคชเคฏเฅเค เคเคนเฅเค เคญเฅ เคจเคนเฅเค เคเคฟเคฏเคพ เคเคพเคคเคพ เคนเฅ!",
"unusedQuestion":"เคชเฅเคฐเคถเฅเคจ {0} เคเคพ เคชเฅเคฐเคฏเฅเค เคเคนเฅเค เคญเฅ เคจเคนเฅเค เคเคฟเคฏเคพ เคเคฏเคพ เคนเฅ!",
- "dropdownOption":"เคกเฅเคฐเฅเคชเคกเคพเคเคจ เคธเคเฅเคทเคฎ เคชเฅเคจเคฒ เคฎเฅเค เคเฅเคตเคฒ 'เคเคฟเคเค' เคชเฅเคฐเคเคพเคฐ เคเฅ เคตเคฟเคเคฒเฅเคช เคนเฅ เคธเคเคคเฅ เคนเฅเค!"
+ "dropdownOption":"เคกเฅเคฐเฅเคชเคกเคพเคเคจ เคธเคเฅเคทเคฎ เคชเฅเคจเคฒ เคฎเฅเค เคเฅเคตเคฒ 'เคเคฟเคเค' เคชเฅเคฐเคเคพเคฐ เคเฅ เคตเคฟเคเคฒเฅเคช เคนเฅ เคธเคเคคเฅ เคนเฅเค!",
+ "customInvalidVersion":"เคเคชเคเฅ เคเฅเคจเฅเคซเคผเคฟเค เคฎเฅเค เคจเคฟเคฐเฅเคฆเคฟเคทเฅเค เคธเคเคธเฅเคเคฐเคฃ เคฎเฅเคฒ เคจเคนเฅเค เคเคพเคคเคพ! เคเฅเคชเคฏเคพ เคธเฅเคจเคฟเคถเฅเคเคฟเคค เคเคฐเฅเค เคเคฟ เคเคชเคจเฅ เคเฅเคจเฅเคซเคผเคฟเค เคเฅ เคจเคตเฅเคจเคคเคฎ เคธเคเคธเฅเคเคฐเคฃ เคฎเฅเค เค
เคชเคกเฅเค เคเคฟเคฏเคพ เคนเฅ!"
}
},
"actions":{
@@ -122,7 +133,7 @@
"move":"เคเคฟเคเค เคธเฅเคฅเคพเคจเคพเคเคคเคฐเคฟเคค",
"add":"เคเคฟเคเค เคเคชเคฏเฅเคเคเคฐเฅเคคเคพ เคเฅเคกเคผเคพ เคเคฏเคพ",
"remove":"เคเคฟเคเค เคเคชเคฏเฅเคเคเคฐเฅเคคเคพ เคนเคเคพ เคฆเคฟเคฏเคพ เคเคฏเคพ",
-
+
"help":"เคเคชเคฒเคฌเฅเคง เคเคฆเฅเคถ",
"statsReset":"เคเคเคเคกเคผเฅ เคฐเฅเคธเฅเค เคเคฐเฅเค",
"blacklistAdd":"เคเคชเคฏเฅเคเคเคฐเฅเคคเคพ เคเฅ เคฌเฅเคฒเฅเคเคฒเคฟเคธเฅเค เคเคฟเคฏเคพ เคเคฏเคพ",
@@ -132,14 +143,20 @@
"blacklistAddDm":"เคฌเฅเคฒเฅเคเคฒเคฟเคธเฅเค เคฎเฅเค เคเฅเคกเคผเคพ เคเคฏเคพ",
"blacklistRemoveDm":"เคเคพเคฒเฅ เคธเฅเคเฅ เคธเฅ เคนเคเคพเคฏเคพ เคเคฏเคพ",
"clear":"เคเคฟเคเค เคธเคพเคซเคผ เคนเฅ เคเค",
+ "clearTickets":"เคเคฟเคเค เคธเคพเคซเคผ เคเคฐเฅเค",
"roles":"เคญเฅเคฎเคฟเคเคพเคเค เค
เคฆเฅเคฏเคคเคจ เคเฅ เคเคเค",
-
+
"autoclose":"เคเคฟเคเค เคธเฅเคตเคคเค เคฌเคเคฆ",
"autocloseEnabled":"เคธเฅเคตเคค: เคฌเคเคฆ เคธเคเฅเคทเคฎ",
"autocloseDisabled":"เคธเฅเคตเคค: เคฌเคเคฆ เค
เคเฅเคทเคฎ",
"autodelete":"เคเคฟเคเค เคธเฅเคตเคคเค เคนเคเคพ เคฆเคฟเคฏเคพ เคเคฏเคพ",
"autodeleteEnabled":"เคธเฅเคตเคคเค เคนเคเคพเคจเคพ เคธเคเฅเคทเคฎ",
- "autodeleteDisabled":"เคธเฅเคตเคคเค เคนเคเคพเคจเคพ เค
เคเฅเคทเคฎ"
+ "autodeleteDisabled":"เคธเฅเคตเคคเค เคนเคเคพเคจเคพ เค
เคเฅเคทเคฎ",
+
+ "topicSet":"เคตเคฟเคทเคฏ เคฌเคฆเคฒเคพ เคเคฏเคพ",
+ "prioritySet":"เคชเฅเคฐเคพเคฅเคฎเคฟเคเคคเคพ เคฌเคฆเคฒเฅ เคเค",
+ "priorityGet":"เคเคฟเคเค เคชเฅเคฐเคพเคฅเคฎเคฟเคเคคเคพ",
+ "transfer":"เคเคฟเคเค เคธเฅเคฅเคพเคจเคพเคเคคเคฐเคฟเคค เคเคฟเคฏเคพ เคเคฏเคพ"
},
"descriptions":{
"create":"เคเคชเคเคพ เคเคฟเคเค เคฌเคจ เคเคฏเคพ เคนเฅ. ",
@@ -154,7 +171,7 @@
"move":"เคเคฟเคเค เคเฅ เคธเคซเคฒเคคเคพเคชเฅเคฐเฅเคตเค {0} เคชเคฐ เคฒเฅ เคเคพเคฏเคพ เคเคฏเคพ เคนเฅ!",
"add":"{0} เคเฅ เคเคฟเคเค เคฎเฅเค เคธเคซเคฒเคคเคพเคชเฅเคฐเฅเคตเค เคเฅเคกเคผ เคฆเคฟเคฏเคพ เคเคฏเคพ เคนเฅ!",
"remove":"{0} เคเฅ เคเคฟเคเค เคธเฅ เคธเคซเคฒเคคเคพเคชเฅเคฐเฅเคตเค เคนเคเคพ เคฆเคฟเคฏเคพ เคเคฏเคพ เคนเฅ!",
-
+
"helpExplanation":"`` => เคเคตเคถเฅเคฏเค เคชเฅเคฐเคพเคฎเฅเคเคฐ\n",
"statsReset":"เคฌเฅเค เคเคเคเคกเคผเฅ เคธเคซเคฒเคคเคพเคชเฅเคฐเฅเคตเค เคฐเฅเคธเฅเค เคเคฐ เคฆเคฟเค เคเค เคนเฅเค!",
"statsError":"เคเคฟเคเค เคเคเคเคกเคผเฅ เคฆเฅเคเคจเฅ เคฎเฅเค เค
เคธเคฎเคฐเฅเคฅ!\n",
@@ -167,7 +184,7 @@
"clearVerify":"เคเฅเคฏเคพ เคเคช เคตเคพเคเค เคเคเคพเคงเคฟเค เคเคฟเคเค เคนเคเคพเคจเคพ เคเคพเคนเคคเฅ เคนเฅเค?\n",
"clearReady":"{0} เคเคฟเคเค เคธเคซเคฒเคคเคพเคชเฅเคฐเฅเคตเค เคนเคเคพ เคฆเคฟเค เคเค เคนเฅเค!",
"rolesEmpty":"เคเฅเค เคญเฅเคฎเคฟเคเคพ เค
เคฆเฅเคฏเคคเคจ เคจเคนเฅเค เคเฅ เคเค เคนเฅ!",
-
+
"autocloseLeave":"เคฏเคน เคเคฟเคเค เคธเฅเคตเคคเค เคฌเคเคฆ เคนเฅ เคเคฏเคพ เคนเฅ เคเฅเคฏเฅเคเคเคฟ เคจเคฟเคฐเฅเคฎเคพเคคเคพ เคจเฅ เคธเคฐเฅเคตเคฐ เคเฅเคกเคผ เคฆเคฟเคฏเคพ เคนเฅ!",
"autocloseTimeout":"เคฏเคน เคเคฟเคเค เคธเฅเคตเคคเค เคฌเคเคฆ เคเคฐ เคฆเคฟเคฏเคพ เคเคฏเคพ เคนเฅ เคเฅเคฏเฅเคเคเคฟ เคฏเคน `{0}h` เคธเฅ เค
เคงเคฟเค เคธเคฎเคฏ เคธเฅ เคจเคฟเคทเฅเคเฅเคฐเคฟเคฏ เคนเฅ!",
"autodeleteLeave":"เคฏเคน เคเคฟเคเค เคธเฅเคตเคคเค เคนเคเคพ เคฆเคฟเคฏเคพ เคเคฏเคพ เคนเฅ เคเฅเคฏเฅเคเคเคฟ เคจเคฟเคฐเฅเคฎเคพเคคเคพ เคจเฅ เคธเคฐเฅเคตเคฐ เคเฅเคกเคผ เคฆเคฟเคฏเคพ เคนเฅ!",
@@ -176,11 +193,16 @@
"autocloseDisabled":"เคเคธ เคเคฟเคเค เคฎเฅเค เคเคเฅเคเฅเคฒเฅเคเคผ เค
เคเฅเคทเคฎ เคเคฐ เคฆเคฟเคฏเคพ เคเคฏเคพ เคนเฅ!\n",
"autodeleteEnabled":"เคเคธ เคเคฟเคเค เคฎเฅเค เคเคเฅเคกเคฟเคฒเฅเค เคธเคเฅเคทเคฎ เคเคฐ เคฆเคฟเคฏเคพ เคเคฏเคพ เคนเฅ!\n",
"autodeleteDisabled":"เคเคธ เคเคฟเคเค เคฎเฅเค เคเคเฅเคกเคฟเคฒเฅเค เค
เคเฅเคทเคฎ เคเคฐ เคฆเคฟเคฏเคพ เคเคฏเคพ เคนเฅ!\n",
-
+
"ticketMessageLimit":"เคเคช เคเค เคนเฅ เคธเคฎเคฏ เคฎเฅเค เคเฅเคตเคฒ {0} เคเคฟเคเค เคฌเคจเคพ เคธเคเคคเฅ เคนเฅเค!",
"ticketMessageAutoclose":"{0}เคเคเคเฅ เคคเค เคจเคฟเคทเฅเคเฅเคฐเคฟเคฏ เคฐเคนเคจเฅ เคชเคฐ เคฏเคน เคเคฟเคเค เคธเฅเคตเคคเค เคฌเคเคฆ เคนเฅ เคเคพเคเคเคพ!",
"ticketMessageAutodelete":"{0} เคฆเคฟเคจเฅเค เคคเค เคจเคฟเคทเฅเคเฅเคฐเคฟเคฏ เคฐเคนเคจเฅ เคชเคฐ เคฏเคน เคเคฟเคเค เคธเฅเคตเคคเค เคนเคเคพ เคฆเคฟเคฏเคพ เคเคพเคเคเคพ!",
- "panelReady":"เคเคช เคจเฅเคเฅ เคชเฅเคจเคฒ เคชเคพ เคธเคเคคเฅ เคนเฅเค!\n"
+ "panelReady":"เคชเฅเคจเคฒ เคซเฅเคฒเฅเค
เคช เคธเคเคฆเฅเคถ เคฎเฅเค เคเคชเคฒเคฌเฅเคง เคนเฅ!\nเคฏเคน เคธเคเคฆเฅเคถ เค
เคฌ เคนเคเคพเคฏเคพ เคเคพ เคธเคเคคเคพ เคนเฅ!",
+
+ "topicSet":"เคเฅเคจเคฒ เคเคพ เคตเคฟเคทเคฏ {0} เคฆเฅเคตเคพเคฐเคพ เคธเคซเคฒเคคเคพเคชเฅเคฐเฅเคตเค เคฌเคฆเคฒ เคฆเคฟเคฏเคพ เคเคฏเคพ เคนเฅ!",
+ "prioritySet":"เคเคฟเคเค เคเฅ เคชเฅเคฐเคพเคฅเคฎเคฟเคเคคเคพ {1} เคฆเฅเคตเคพเคฐเคพ {0} เคฎเฅเค เคธเคซเคฒเคคเคพเคชเฅเคฐเฅเคตเค เคฌเคฆเคฒ เคฆเฅ เคเค เคนเฅ!",
+ "priorityGet":"เคเคธ เคเคฟเคเค เคเฅ เคตเคฐเฅเคคเคฎเคพเคจ เคชเฅเคฐเคพเคฅเคฎเคฟเคเคคเคพ {0} เคนเฅเฅค",
+ "transfer":"เคเคฟเคเค เคเคพ เคธเฅเคตเคพเคฎเคฟเคคเฅเคต {0} เคธเฅ {1} เคฎเฅเค {2} เคฆเฅเคตเคพเคฐเคพ เคธเคซเคฒเคคเคพเคชเฅเคฐเฅเคตเค เคธเฅเคฅเคพเคจเคพเคเคคเคฐเคฟเคค เคเคฐ เคฆเคฟเคฏเคพ เคเคฏเคพ เคนเฅ!"
},
"modal":{
"closePlaceholder":"เคเคชเคจเฅ เคฏเคน เคเคฟเคเค เคเฅเคฏเฅเค เคฌเคเคฆ เคเคฐ เคฆเคฟเคฏเคพ?",
@@ -215,12 +237,19 @@
"addDm":"เคนเคฎเคพเคฐเฅ เคธเคฐเฅเคตเคฐ เคฎเฅเค เคเคชเคเฅ เคเคฟเคเค เคฎเฅเค {0} เคเฅเคกเคผ เคฆเคฟเคฏเคพ เคเคฏเคพ เคนเฅ!",
"removeLog":"{1} เคฆเฅเคตเคพเคฐเคพ เคเคธ เคเคฟเคเค เคธเฅ {0} เคนเคเคพ เคฆเคฟเคฏเคพ เคเคฏเคพ เคนเฅ!",
"removeDm":"เคนเคฎเคพเคฐเฅ เคธเคฐเฅเคตเคฐ เคฎเฅเค เคเคชเคเฅ เคเคฟเคเค เคธเฅ {0} เคนเคเคพ เคฆเคฟเคฏเคพ เคเคฏเคพ เคนเฅ!",
-
+
"blacklistAddLog":"{0} เคเฅ {1} เคฆเฅเคตเคพเคฐเคพ เคเคพเคฒเฅ เคธเฅเคเฅ เคฎเฅเค เคกเคพเคฒ เคฆเคฟเคฏเคพ เคเคฏเคพ เคฅเคพ!",
"blacklistRemoveLog":"{0} เคเฅ {1} เคฆเฅเคตเคพเคฐเคพ เคเคพเคฒเฅ เคธเฅเคเฅ เคธเฅ เคนเคเคพ เคฆเคฟเคฏเคพ เคเคฏเคพ เคฅเคพ!",
"blacklistAddDm":"เคเคชเคเฅ เคนเคฎเคพเคฐเฅ เคธเคฐเฅเคตเคฐ เคฎเฅเค เคเคพเคฒเฅ เคธเฅเคเฅ เคฎเฅเค เคกเคพเคฒ เคฆเคฟเคฏเคพ เคเคฏเคพ เคนเฅ!\n",
"blacklistRemoveDm":"เคเคชเคเฅ เคนเคฎเคพเคฐเฅ เคธเคฐเฅเคตเคฐ เคฎเฅเค เคฌเฅเคฒเฅเคเคฒเคฟเคธเฅเค เคธเฅ เคนเคเคพ เคฆเคฟเคฏเคพ เคเคฏเคพ เคนเฅ!\n",
- "clearLog":"{0} เคเคฟเคเคเฅเค {1} เคฆเฅเคตเคพเคฐเคพ เคนเคเคพ เคฆเฅ เคเค เคนเฅเค!"
+ "clearLog":"{0} เคเคฟเคเคเฅเค {1} เคฆเฅเคตเคพเคฐเคพ เคนเคเคพ เคฆเฅ เคเค เคนเฅเค!",
+
+ "transferLog":"เคเคธ เคเคฟเคเค เคเคพ เคธเฅเคตเคพเคฎเคฟเคคเฅเคต {0} เคธเฅ {1} เคฎเฅเค {2} เคฆเฅเคตเคพเคฐเคพ เคธเฅเคฅเคพเคจเคพเคเคคเคฐเคฟเคค เคเคฟเคฏเคพ เคเคฏเคพ เคนเฅ!",
+ "transferDm":"เคเคชเคเฅ เคเคฟเคเค เคเคพ เคธเฅเคตเคพเคฎเคฟเคคเฅเคต เคนเคฎเคพเคฐเฅ เคธเคฐเฅเคตเคฐ เคฎเฅเค {0} เคธเฅ {1} เคฎเฅเค เคธเฅเคฅเคพเคจเคพเคเคคเคฐเคฟเคค เคเคฐ เคฆเคฟเคฏเคพ เคเคฏเคพ เคนเฅ!",
+ "prioritySetLog":"เคเคธ เคเคฟเคเค เคเฅ เคชเฅเคฐเคพเคฅเคฎเคฟเคเคคเคพ {1} เคฆเฅเคตเคพเคฐเคพ {0} เคฎเฅเค เคฌเคฆเคฒ เคฆเฅ เคเค เคนเฅ!",
+ "prioritySetDm":"เคเคชเคเฅ เคเคฟเคเค เคเฅ เคชเฅเคฐเคพเคฅเคฎเคฟเคเคคเคพ เคนเคฎเคพเคฐเฅ เคธเคฐเฅเคตเคฐ เคฎเฅเค {0} เคฎเฅเค เคฌเคฆเคฒ เคฆเฅ เคเค เคนเฅ!",
+ "roleUpdateLog":"{0} เคจเฅ เค
เคชเคจเฅ เคญเฅเคฎเคฟเคเคพเคเค เค
เคชเคกเฅเค เคเฅ เคนเฅเค!",
+ "roleUpdateDm":"เคเคชเคเฅ เคญเฅเคฎเคฟเคเคพเคเค เคนเคฎเคพเคฐเฅ เคธเคฐเฅเคตเคฐ เคฎเฅเค เค
เคชเคกเฅเค เคเคฐ เคฆเฅ เคเค เคนเฅเค!"
}
},
"transcripts":{
@@ -241,7 +270,19 @@
"retry":"เคชเฅเคจ: เคชเฅเคฐเคฏเคพเคธ เคเคฐเฅเค",
"continue":"เคชเฅเคฐเคคเคฟเคฒเฅเค เคเฅ เคฌเคฟเคจเคพ เคนเคเคพเคเค",
"backup":"เคฌเฅเคเค
เคช เคเฅเคฐเคพเคเคธเฅเคเฅเคฐเคฟเคชเฅเค เคฌเคจเคพเคเค",
- "error":"เคชเฅเคฐเคคเคฟเคฒเฅเค เคฌเคจเคพเคจเฅ เคเคพ เคชเฅเคฐเคฏเคพเคธ เคเคฐเคคเฅ เคธเคฎเคฏ เคเฅเค เคเคฒเคค เคนเฅ เคเคฏเคพเฅค\n"
+ "error":"เคชเฅเคฐเคคเคฟเคฒเฅเค เคฌเคจเคพเคจเฅ เคเคพ เคชเฅเคฐเคฏเคพเคธ เคเคฐเคคเฅ เคธเคฎเคฏ เคเฅเค เคเคฒเคค เคนเฅ เคเคฏเคพเฅค\n",
+ "title":"เคเฅเคฐเคพเคเคธเคเฅเคฐเคฟเคชเฅเค เคคเฅเคฐเฅเคเคฟ"
+ },
+ "text":{
+ "messagesTitle":"เคธเคเคฆเฅเคถ",
+ "embedTitle":"เคเคฎเฅเคฌเฅเคก",
+ "fileTitle":"เคซเคผเคพเคเคฒ",
+ "fieldsTitle":"เคซเคผเฅเคฒเฅเคกเฅเคธ",
+ "reactionsTitle":"เคชเฅเคฐเคคเคฟเคเฅเคฐเคฟเคฏเคพเคเค",
+ "statsTitle":"เคเคเคเคกเคผเฅ",
+ "emptyContent":"<เคธเคพเคฎเคเฅเคฐเฅ เคเคพเคฒเฅ เคนเฅ>",
+ "noTitle":"<เคเฅเค เคถเฅเคฐเฅเคทเค เคจเคนเฅเค>",
+ "noDesc":"<เคเฅเค เคตเคฟเคตเคฐเคฃ เคจเคนเฅเค>"
}
},
"errors":{
@@ -257,7 +298,8 @@
"unknownPanel":"เค
เคเฅเคเคพเคค เคชเฅเคจเคฒ",
"notInGuild":"เคธเคฐเฅเคตเคฐ เคฎเฅเค เคจเคนเฅเค",
"channelRename":"เคเฅเคจเคฒ เคเคพ เคจเคพเคฎ เคฌเคฆเคฒเคจเฅ เคฎเฅเค เค
เคธเคฎเคฐเฅเคฅ",
- "busy":"เคเคฟเคเค เคตเฅเคฏเคธเฅเคค เคนเฅ"
+ "busy":"เคเคฟเคเค เคตเฅเคฏเคธเฅเคค เคนเฅ",
+ "permissionError":"เค
เคจเฅเคฎเคคเคฟ เคคเฅเคฐเฅเคเคฟ"
},
"descriptions":{
"askForInfo":"เค
เคงเคฟเค เคเคพเคจเคเคพเคฐเฅ เคเฅ เคฒเคฟเค เคเคธ เคฌเฅเค เคเฅ เคฎเคพเคฒเคฟเค เคธเฅ เคธเคเคชเคฐเฅเค เคเคฐเฅเค!",
@@ -280,7 +322,10 @@
"notInGuild":"เคฏเคน {0} เคกเฅเคเคฎ เคฎเฅเค เคเคพเคฎ เคจเคนเฅเค เคเคฐเคคเคพ! ",
"channelRename":"เคตเคฟเคตเคพเคฆ เคเฅ เคฆเคฐ เคธเฅเคฎเคพ เคเฅ เคเคพเคฐเคฃ, เคตเคฐเฅเคคเคฎเคพเคจ เคฎเฅเค เคฌเฅเค เคเฅ เคฒเคฟเค เคเฅเคจเคฒ เคเคพ เคจเคพเคฎ เคฌเคฆเคฒเคจเคพ เค
เคธเคเคญเคต เคนเฅเฅค ",
"channelRenameSource":"เคเคธ เคคเฅเคฐเฅเคเคฟ เคเคพ เคธเฅเคฐเฅเคค เคนเฅ: {0}",
- "busy":"เคเคธ {0} เคเคพ เคเคชเคฏเฅเค เคเคฐเคจเฅ เคฎเฅเค เค
เคธเคฎเคฐเฅเคฅ!\n"
+ "busy":"เคเคธ {0} เคเคพ เคเคชเคฏเฅเค เคเคฐเคจเฅ เคฎเฅเค เค
เคธเคฎเคฐเฅเคฅ!\n",
+ "closeBeforeMessage":"เคเคฟเคธเฅ เคเคชเคฏเฅเคเคเคฐเฅเคคเคพ เคฆเฅเคตเคพเคฐเคพ เคธเคเคฆเฅเคถ เคญเฅเคเฅ เคเคพเคจเฅ เคธเฅ เคชเคนเคฒเฅ เคเคธ เคเคฟเคเค เคเฅ เคฌเคเคฆ/เคนเคเคพเคฏเคพ เคจเคนเฅเค เคเคพ เคธเคเคคเคพเฅค",
+ "closeBeforeAdminMessage":"เคเคฟเคเค เคเคกเคฎเคฟเคจ เคฏเคพ เคธเคชเฅเคฐเฅเค เคธเคฆเคธเฅเคฏ เคฆเฅเคตเคพเคฐเคพ เคธเคเคฆเฅเคถ เคญเฅเคเฅ เคเคพเคจเฅ เคธเฅ เคชเคนเคฒเฅ เคเคธ เคเคฟเคเค เคเฅ เคฌเคเคฆ/เคนเคเคพเคฏเคพ เคจเคนเฅเค เคเคพ เคธเคเคคเคพเฅค",
+ "unableToCreateTicket":"เคเคช เคเคฟเคเค เคฌเคจเคพเคจเฅ เคฎเฅเค เค
เคธเคฎเคฐเฅเคฅ เคนเฅเคเฅค"
},
"optionInvalidReasons":{
"stringRegex":"เคฎเคพเคจ เคชเฅเคเคฐเฅเคจ เคธเฅ เคฎเฅเคฒ เคจเคนเฅเค เคเคพเคคเคพ!",
@@ -331,7 +376,6 @@
"added":"เคเฅเคกเคผเคพ",
"removed":"เคจเคฟเคเคพเคฒเคพ เคเคฏเคพ",
"filter":"เคซเคผเคฟเคฒเฅเคเคฐ",
- "claimedBy":"{0} เคฆเฅเคตเคพเคฐเคพ เคฆเคพเคตเคพ เคเคฟเคฏเคพ เคเคฏเคพ",
"method":"เคคเคฐเฅเคเคพ",
"type":"เคชเฅเคฐเคเคพเคฐ",
"blacklisted":"เคฌเฅเคฒเฅเค เคฒเคฟเคธเฅเค เคเคฟเคฏเคพ",
@@ -355,7 +399,27 @@
"status":"เคธเฅเคฅเคฟเคคเคฟ",
"claimed":"เคฆเคพเคตเคพ เคเคฟเคฏเคพ",
"pinned":"เคชเคฟเคจ เคเฅ เคเค",
- "creationDate":"เคจเคฟเคฐเฅเคฎเคพเคฃ เคคเคฟเคฅเคฟ"
+ "creationDate":"เคจเคฟเคฐเฅเคฎเคพเคฃ เคคเคฟเคฅเคฟ",
+
+ "noone":"เคเฅเค เคจเคนเฅเค",
+ "open":"เคเฅเคฒเคพ",
+ "closed":"เคฌเคเคฆ",
+ "priority":"เคชเฅเคฐเคพเคฅเคฎเคฟเคเคคเคพ",
+ "participants":"เคชเฅเคฐเคคเคฟเคญเคพเคเฅ",
+ "yes":"เคนเคพเค",
+ "no":"เคจเคนเฅเค",
+ "option":"เคตเคฟเคเคฒเฅเคช",
+ "topic":"เคตเคฟเคทเคฏ",
+ "uptime":"เคธเคฟเคธเฅเคเคฎ เค
เคชเคเคพเคเคฎ",
+ "messages":"เคธเคเคฆเฅเคถ",
+ "embeds":"เคเคฎเฅเคฌเฅเคกเฅเคธ",
+ "files":"เคซเคผเคพเคเคฒเฅเค",
+ "components":"เคเคเค",
+ "cooldown":"เคเฅเคฒเคกเคพเคเคจ",
+ "maxTickets":"เค
เคงเคฟเคเคคเคฎ เคเคฟเคเค",
+ "admins":"เคเคกเคฎเคฟเคจ",
+ "roles":"เคญเฅเคฎเคฟเคเคพเคเค",
+ "size":"เคเคเคพเคฐ"
},
"lowercase":{
"text":"เคฎเฅเคฒเคชเคพเค ",
@@ -384,6 +448,7 @@
"unclaim":"เคเคฟเคเค เคเคพ เคฆเคพเคตเคพ เคฐเคฆเฅเคฆ เคเคฐเฅเค.",
"pin":"เคเคฟเคเค เคชเคฟเคจ เคเคฐเฅเค.",
"unpin":"เคเคฟเคเค เค
เคจเคชเคฟเคจ เคเคฐเฅเค.",
+
"move":"เคเค เคเคฟเคเค เคฒเฅ เคเคพเคเค.",
"moveId":"เคเคธ เคตเคฟเคเคฒเฅเคช เคเคพ เคชเคนเคเคพเคจเคเคฐเฅเคคเคพ เคเคฟเคธ เคชเคฐ เคเคช เคเคพเคจเคพ เคเคพเคนเคคเฅ เคนเฅเค.",
"rename":"เคเคฟเคเค เคเคพ เคจเคพเคฎ เคฌเคฆเคฒเฅเค.",
@@ -392,6 +457,7 @@
"addUser":"เคเคชเคฏเฅเคเคเคฐเฅเคคเคพ เคเฅ เคเฅเคกเคผเคจเคพ เคนเฅ.",
"remove":"เคเคฟเคธเฅ เคเคชเคฏเฅเคเคเคฐเฅเคคเคพ เคเฅ เคเคฟเคเค เคธเฅ เคนเคเคพเคเค.",
"removeUser":"เคเคชเคฏเฅเคเคเคฐเฅเคคเคพ เคเฅ เคนเคเคพเคจเคพ เคนเฅ.",
+
"blacklist":"เคเคฟเคเค เคฌเฅเคฒเฅเคเคฒเคฟเคธเฅเค เคชเฅเคฐเคฌเคเคงเคฟเคค เคเคฐเฅเค.",
"blacklistView":"เคตเคฐเฅเคคเคฎเคพเคจ เคฌเฅเคฒเฅเคเคฒเคฟเคธเฅเค เคเฅ เคธเฅเคเฅ เคฆเฅเคเฅเค.",
"blacklistAdd":"เคเคฟเคธเฅ เคเคชเคฏเฅเคเคเคฐเฅเคคเคพ เคเฅ เคเคพเคฒเฅ เคธเฅเคเฅ เคฎเฅเค เคเฅเคกเคผเฅเค.",
@@ -405,6 +471,7 @@
"statsUserUser":"เคฆเฅเคเคจเฅ เคเฅ เคฒเคฟเค เคเคชเคฏเฅเคเคเคฐเฅเคคเคพ.",
"statsTicket":"เคธเคฐเฅเคตเคฐ เคฎเฅเค เคเคฟเคเค เคเฅ เคเคเคเคกเคผเฅ เคฆเฅเคเฅเคเฅค",
"statsTicketTicket":"เคฆเฅเคเคจเฅ เคเฅ เคฒเคฟเค เคเคฟเคเค.",
+
"clear":"เคเค เคนเฅ เคธเคฎเคฏ เคฎเฅเค เคเคเคพเคงเคฟเค เคเคฟเคเค เคนเคเคพเคเคเฅค",
"clearFilter":"เคเคฟเคเค เคธเคพเคซเคผ เคเคฐเคจเฅ เคเฅ เคฒเคฟเค เคซเคผเคฟเคฒเฅเคเคฐ.",
"clearFilters":{
@@ -417,6 +484,7 @@
"unpin":"เค
เคจเคชเคฟเคจ เคเคฟเคฏเคพ เคเคฏเคพ",
"autoclose":"เคธเฅเคตเคคเค เคฌเคเคฆ"
},
+
"autoclose":"เคเคฟเคธเฅ เคเคฟเคเค เคฎเฅเค เคเคเฅเคเฅเคฒเฅเคเคผ เคชเฅเคฐเคฌเคเคงเคฟเคค เคเคฐเฅเค.",
"autocloseDisable":"เคเคธ เคเคฟเคเค เคฎเฅเค เคเคเฅเคเฅเคฒเฅเคเคผ เค
เคเฅเคทเคฎ เคเคฐเฅเค.",
"autocloseEnable":"เคเคธ เคเคฟเคเค เคฎเฅเค เคเคเฅเคเฅเคฒเฅเคเคผ เคธเคเฅเคทเคฎ เคเคฐเฅเคเฅค",
@@ -424,7 +492,19 @@
"autodelete":"เคเคฟเคธเฅ เคเคฟเคเค เคฎเฅเค เคธเฅเคตเคค: เคนเคเคพเคจเคพ เคชเฅเคฐเคฌเคเคงเคฟเคค เคเคฐเฅเค.",
"autodeleteDisable":"เคเคธ เคเคฟเคเค เคฎเฅเค เคเคเฅเคกเคฟเคฒเฅเค เค
เคเฅเคทเคฎ เคเคฐเฅเคเฅค",
"autodeleteEnable":"เคเคธ เคเคฟเคเค เคฎเฅเค เคเคเฅเคกเคฟเคฒเฅเค เคธเคเฅเคทเคฎ เคเคฐเฅเคเฅค",
- "autodeleteEnableTime":"เคเคธ เคเคฟเคเค เคเฅ เคนเคเคพเคจเฅ เคเฅ เคฒเคฟเค เคเคคเคจเฅ เคฆเคฟเคจเฅเค เคเฅ เคธเคเคเฅเคฏเคพ เคจเคฟเคทเฅเคเฅเคฐเคฟเคฏ เคนเฅเคจเฅ เคเคพเคนเคฟเคเฅค"
+ "autodeleteEnableTime":"เคเคธ เคเคฟเคเค เคเฅ เคนเคเคพเคจเฅ เคเฅ เคฒเคฟเค เคเคคเคจเฅ เคฆเคฟเคจเฅเค เคเฅ เคธเคเคเฅเคฏเคพ เคจเคฟเคทเฅเคเฅเคฐเคฟเคฏ เคนเฅเคจเฅ เคเคพเคนเคฟเคเฅค",
+
+ "topic":"เคเคฟเคเค เคเฅเคจเคฒ เคเฅ เคตเคฟเคทเคฏ เคเคพ เคชเฅเคฐเคฌเคเคงเคจ เคเคฐเฅเคเฅค",
+ "topicSet":"เคเคฟเคเค เคเฅเคจเคฒ เคเคพ เคตเคฟเคทเคฏ เคธเฅเค เคเคฐเฅเคเฅค",
+ "topicValue":"เคเฅเคจเคฒ เคเคพ เคจเคฏเคพ เคตเคฟเคทเคฏเฅค",
+ "topicList":"เคธเคญเฅ เคเคฟเคเคเฅเค เคเฅ เคธเฅเคเฅ เคชเฅเคฐเคพเคชเฅเคค เคเคฐเฅเค เคเคฟเคจเคเฅ เคตเคฟเคทเคฏ เคเคฐ เคเคเคเคกเคผเฅ เคถเคพเคฎเคฟเคฒ เคนเฅเคเฅค",
+ "priority":"เคเคฟเคเค เคเฅ เคชเฅเคฐเคพเคฅเคฎเคฟเคเคคเคพ เคเคพ เคชเฅเคฐเคฌเคเคงเคจ เคเคฐเฅเคเฅค",
+ "prioritySet":"เคเคฟเคเค เคเฅ เคชเฅเคฐเคพเคฅเคฎเคฟเคเคคเคพ เคธเฅเค เคเคฐเฅเคเฅค",
+ "priorityValue":"เคเฅเคจเคฒ เคเฅ เคชเฅเคฐเคพเคฅเคฎเคฟเคเคคเคพเฅค",
+ "priorityGet":"เคเคฟเคเค เคเฅ เคชเฅเคฐเคพเคฅเคฎเคฟเคเคคเคพ เคชเฅเคฐเคพเคชเฅเคค เคเคฐเฅเคเฅค",
+ "priorityList":"เคธเคญเฅ เคเคฟเคเคเฅเค เคเฅ เคชเฅเคฐเคพเคฅเคฎเคฟเคเคคเคพ เคธเฅเคฅเคฟเคคเคฟ เคเฅ เคธเฅเคเฅ เคชเฅเคฐเคพเคชเฅเคค เคเคฐเฅเคเฅค",
+ "transfer":"เคเคฟเคเค เคเคพ เคธเฅเคตเคพเคฎเคฟเคคเฅเคต เคเค เคเคชเคฏเฅเคเคเคฐเฅเคคเคพ เคธเฅ เคฆเฅเคธเคฐเฅ เคเฅ เคธเฅเคฅเคพเคจเคพเคเคคเคฐเคฟเคค เคเคฐเฅเคเฅค",
+ "transferUser":"เคเคฟเคธ เคเคชเคฏเฅเคเคเคฐเฅเคคเคพ เคเฅ เคธเฅเคฅเคพเคจเคพเคเคคเคฐเคฟเคค เคเคฐเคจเคพ เคนเฅเฅค"
},
"helpMenu":{
"help":"เคธเคญเฅ เคเคชเคฒเคฌเฅเคง เคเคฆเฅเคถเฅเค เคเฅ เคธเฅเคเฅ เคชเฅเคฐเคพเคชเฅเคค เคเคฐเฅเค.",
@@ -452,7 +532,16 @@
"autocloseDisable":"เคเคธ เคเคฟเคเค เคฎเฅเค เคเคเฅเคเฅเคฒเฅเคเคผ เค
เคเฅเคทเคฎ เคเคฐเฅเค.",
"autocloseEnable":"เคเคธ เคเคฟเคเค เคฎเฅเค เคเคเฅเคเฅเคฒเฅเคเคผ เคธเคเฅเคทเคฎ เคเคฐเฅเคเฅค",
"autodeleteDisable":"เคเคธ เคเคฟเคเค เคฎเฅเค เคเคเฅเคกเคฟเคฒเฅเค เค
เคเฅเคทเคฎ เคเคฐเฅเคเฅค",
- "autodeleteEnable":"เคเคธ เคเคฟเคเค เคฎเฅเค เคเคเฅเคกเคฟเคฒเฅเค เคธเคเฅเคทเคฎ เคเคฐเฅเคเฅค"
+ "autodeleteEnable":"เคเคธ เคเคฟเคเค เคฎเฅเค เคเคเฅเคกเคฟเคฒเฅเค เคธเคเฅเคทเคฎ เคเคฐเฅเคเฅค",
+ "categories":{
+ "general":"เคธเคพเคฎเคพเคจเฅเคฏ เคเคฎเคพเคเคกเฅเคธ",
+ "basicTicket":"เคฎเฅเคฒ เคเคฟเคเค เคเคฎเคพเคเคกเฅเคธ",
+ "advancedTicket":"เคเคจเฅเคจเคค เคเคฟเคเค เคเคฎเคพเคเคกเฅเคธ",
+ "userTicket":"เคเคชเคฏเฅเคเคเคฐเฅเคคเคพ เคเคฟเคเค เคเคฎเคพเคเคกเฅเคธ",
+ "admin":"เคเคกเคฎเคฟเคจ เคเคฎเคพเคเคกเฅเคธ",
+ "advanced":"เคเคจเฅเคจเคค เคเคฎเคพเคเคกเฅเคธ",
+ "extra":"เค
เคคเคฟเคฐเคฟเคเฅเคค เคเคฎเคพเคเคกเฅเคธ"
+ }
},
"stats":{
"scopes":{
@@ -460,7 +549,8 @@
"system":"เคธเคฟเคธเฅเคเคฎ เคเคเคเคกเคผเฅ",
"user":"เคเคชเคฏเฅเคเคเคฐเฅเคคเคพ เคเคเคเคกเคผเฅ",
"ticket":"เคเคฟเคเค เคเคเคเคกเคผเฅ",
- "participants":"เคชเฅเคฐเคคเคฟเคญเคพเคเคฟเคฏเฅเค"
+ "participants":"เคชเฅเคฐเคคเคฟเคญเคพเคเคฟเคฏเฅเค",
+ "messages":"เคธเคเคฆเฅเคถ"
},
"properties":{
"ticketsCreated":"เคเคฟเคเค เคฌเคจเคพเคฏเฅ เคเคฏเฅ",
@@ -472,7 +562,47 @@
"ticketsPinned":"เคเคฟเคเค เคชเคฟเคจ เคเคฟเคฏเฅ เคเคฏเฅ",
"ticketsMoved":"เคเคฟเคเค เคฒเฅ เคเคพเคฏเคพ เคเคฏเคพ",
"usersBlacklisted":"เคเคชเคฏเฅเคเคเคฐเฅเคคเคพ เคฌเฅเคฒเฅเคเคฒเคฟเคธเฅเคเฅเคก",
- "transcriptsCreated":"เคชเฅเคฐเคคเคฟเคฒเคฟเคชเคฟเคฏเคพเค เคฌเคจเคพเค เคเคเค"
+ "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":"เค
เคชเคจเคพ เคตเคฟเคเคฒเฅเคช เคเฅเคจเฅเค"
+ },
+ "priorities":{
+ "urgent":"เค
เคคเฅเคฏเคพเคตเคถเฅเคฏเค",
+ "veryHigh":"เคฌเคนเฅเคค เคเคเฅเค",
+ "high":"เคเคเฅเค",
+ "normal":"เคธเคพเคฎเคพเคจเฅเคฏ",
+ "low":"เคจเคฟเคฎเฅเคจ",
+ "veryLow":"เคฌเคนเฅเคค เคจเคฟเคฎเฅเคจ",
+ "none":"เคเฅเค เคจเคนเฅเค"
}
}
\ No newline at end of file
diff --git a/languages/hungarian.json b/languages/hungarian.json
index a2ea9fc..7a0b035 100644
--- a/languages/hungarian.json
+++ b/languages/hungarian.json
@@ -1,6 +1,6 @@
{
"_TRANSLATION":{
- "otversion":"v4.0.7",
+ "otversion":"v4.1.0",
"translators":["Kornel0706"],
"lastedited":"22/08/2024",
"language":"Hungarian",
diff --git a/languages/indonesian.json b/languages/indonesian.json
index f454ef0..44a64b3 100644
--- a/languages/indonesian.json
+++ b/languages/indonesian.json
@@ -1,8 +1,8 @@
{
"_TRANSLATION":{
- "otversion":"v4.0.7",
+ "otversion":"v4.1.0",
"translators":["erxg"],
- "lastedited":"25/08/2024",
+ "lastedited":"09/11/2025",
"language":"Indonesian",
"automated":false
},
@@ -31,7 +31,15 @@
"stringContains":"String ini harus mengandung {0}!",
"stringChoices":"String ini hanya dapat berupa salah satu dari nilai berikut ini: {0}!",
"stringRegex":"String ini tidak valid!",
-
+ "stringInvertedContains":"Teks ini tidak boleh mengandung {0}!",
+ "stringLowercase":"Teks ini harus ditulis dalam huruf kecil saja!",
+ "stringUppercase":"Teks ini harus ditulis dalam huruf kecil besar!",
+ "stringSpecialCharacters":"Teks ini tidak boleh mengandung karakter khusus apa pun! (Hanya huruf a-z, angka 0-9, dan spasi)",
+ "stringNoSpaces":"Teks ini tidak boleh mengandung spasi",
+ "stringCapitalWord":"Disarankan agar setiap kata dalam teks ini dimulai dengan huruf kapital!",
+ "stringCapitalSentence":"Sepertinya beberapa kalimat dalam teks ini tidak dimulai dengan huruf kapital!",
+ "stringPunctuation":"Sepertinya kalimat dalam teks ini tidak diakhiri dengan tanda baca!",
+
"numberTooShort":"Nomor ini tidak boleh lebih pendek dari {0} karakter!",
"numberTooLong":"Nomor ini tidak boleh lebih panjang dari {0} karakter!",
"numberLengthInvalid":"Nomor ini harus setidaknya {0} karakter!",
@@ -48,10 +56,12 @@
"numberNegative":"Nomor ini tidak boleh negatif!",
"numberPositive":"Nomor ini tidak boleh positif!",
"numberZero":"Nomor ini tidak boleh nol!",
-
+ "numberNan":"Nomor ini tidak boleh NaN (Bukan Angka)!",
+ "numberInvertedContains":"Nomor ini tidak boleh mengandung {0}!",
+
"booleanTrue":"Boolean ini tidak boleh benar!",
"booleanFalse":"Boolean ini tidak boleh salah!",
-
+
"arrayEmptyDisabled":"Array ini tidak boleh kosong!",
"arrayEmptyRequired":"Array ini harus kosong!",
"arrayTooShort":"Array ini harus memiliki panjang setidaknya {0}!",
@@ -59,7 +69,7 @@
"arrayLengthInvalid":"Array ini hanya dapat berisi tipe berikut ini: {0}!",
"arrayInvalidTypes":"Array ini hanya dapat berisi tipe berikut: {0}!",
"arrayDouble":"Array ini tidak mengizinkan nilai yang sama dua kali!",
-
+
"discordInvalidId":"Ini adalah id Discord {0} yang tidak valid!",
"discordInvalidIdOptions":"Ini adalah id Discord {0} yang tidak valid! Anda juga dapat menggunakan salah satu dari ini: {1}!",
"discordInvalidToken":"Ini adalah token Discord yang tidak valid (secara sintaksis)!",
@@ -76,7 +86,7 @@
"urlInvalidPath":"URL ini memiliki jalur yang tidak valid!",
"idNotUnique":"Id ini tidak unik, gunakan id lain sebagai gantinya!",
"idNonExistent":"Id {0} tidak ada!",
-
+
"invalidType":"Properti ini harus sesuai dengan jenisnya: {0}!",
"propertyMissing":"Properti {0} tidak ada pada objek ini!",
"propertyOptional":"Properti {0} bersifat opsional dalam objek ini!",
@@ -84,12 +94,13 @@
"nullInvalid":"Properti ini tidak boleh bernilai nol!",
"switchInvalidType":"Ini harus salah satu dari jenis berikut ini: {0}!",
"objectSwitchInvalid":"Objek ini harus merupakan salah satu dari jenis berikut: {0}!",
-
+
"invalidLanguage":"Ini adalah bahasa yang tidak valid!",
"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 menu tarik-ulur yang diaktifkan hanya dapat berisi opsi dengan tipe 'tiket'!",
+ "customInvalidVersion":"Versi yang ditetapkan dalam konfigurasi tidak sesuai! Pastikan konfigurasi Anda telah diperbarui ke versi terbaru!"
}
},
"actions":{
@@ -132,6 +143,7 @@
"blacklistAddDm":"Ditambahkan ke Daftar Hitam",
"blacklistRemoveDm":"Dihapus dari Daftar Hitam",
"clear":"Tiket dibersihkan.",
+ "clearTickets":"Bersihkan Tiket",
"roles":"Peran Diperbarui",
"autoclose":"Tiket Ditutup Otomatis",
@@ -139,7 +151,12 @@
"autocloseDisabled":"Tutup Otomatis Dimatikan",
"autodelete":"Tiket Dihapus Otomatis",
"autodeleteEnabled":"Hapus Otomatis Diaktifkan",
- "autodeleteDisabled":"Hapus Otomatis Dinonaktifkan"
+ "autodeleteDisabled":"Hapus Otomatis Dinonaktifkan",
+
+ "topicSet":"Topik Diganti",
+ "prioritySet":"Prioritas Berganti",
+ "priorityGet":"Prioritas Tiket",
+ "transfer":"Tiket Ditransfer"
},
"descriptions":{
"create":"Tiket kamu telah dibuat. Klik tombol di bawah ini untuk mengaksesnya!",
@@ -154,7 +171,7 @@
"move":"Tiket telah berhasil dipindahkan ke {0}!",
"add":"{0} telah berhasil ditambahkan ke tiket!",
"remove":"{0} telah berhasil dihapus dari tiket!",
-
+
"helpExplanation":"`` => parameter wajib\n`[name]` => parameter opsional",
"statsReset":"Statistik bot telah berhasil disetel ulang!",
"statsError":"Tidak dapat melihat statistik tiket!\n{0} bukan tiket!",
@@ -167,7 +184,7 @@
"clearVerify":"Apakah Anda yakin ingin menghapus beberapa tiket?\nTindakan ini tidak dapat dibatalkan!",
"clearReady":"Tiket {0} telah berhasil dihapus!",
"rolesEmpty":"Tidak ada peran yang telah diperbarui!",
-
+
"autocloseLeave":"tiket ini telah ditutup secara otomatis karena pembuatnya meninggalkan server!",
"autocloseTimeout":"tiket ini telah ditutup otomatis karena tidak aktif selama lebih dari `{0} jam`!",
"autodeleteLeave":"tiket ini telah dihapus secara otomatis karena pembuatnya meninggalkan server!",
@@ -176,11 +193,16 @@
"autocloseDisabled":"Penutupan otomatis telah dinonaktifkan untuk tiket ini!\nIt won't be closed automatically anymore!",
"autodeleteEnabled":"Penghapsan otomatis telah diaktifkan untuk tiket ini!\nIni akan dihapus jika tidak aktif selama lebih dari `{0} hari`!",
"autodeleteDisabled":"Penghapusan otomatis telah dinonaktifkan untuk tiket ini!\nTiket ini tidak akan dihapus secara otomatis lagi!",
-
+
"ticketMessageLimit":"Anda hanya dapat membuat {0} tiket pada saat yang bersamaan!",
"ticketMessageAutoclose":"Tiket ini akan ditutup secara otomatis ketika tidak aktif selama {0} jam!",
"ticketMessageAutodelete":"Tiket ini akan dihapus secara otomatis ketika tidak aktif selama {0} hari!",
- "panelReady":"Anda dapat menemukan panel di bawah ini!\nPesan ini sekarang dapat dihapus!"
+ "panelReady":"Panel ini tersedia dalam pesan tindak lanjut!\nPesan ini sekarang dapat dihapus!",
+
+ "topicSet":"Topik kanal ini telah diubah oleh {0}!",
+ "prioritySet":"Prioritas tiket ini telah diubah menjadi {0} oleh {1}!",
+ "priorityGet":"Prioritas tiket ini pada saat ini adalah {0}.",
+ "transfer":"Status kepemilikan tiket telah berhasil dipindahkan dari {0} ke {1} oleh {2}!"
},
"modal":{
"closePlaceholder":"Mengapa kamu menutup tiket ini?",
@@ -215,12 +237,19 @@
"addDm":"{0} telah ditambahkan ke tiket kamu di server kami!",
"removeLog":"{0} telah dihapus dari tiket ini oleh {1}!",
"removeDm":"{0} telah dihapus dari tiket kamu di server kami!",
-
+
"blacklistAddLog":"{0} dimasukkan ke dalam daftar hitam oleh {1}!",
"blacklistRemoveLog":"{0} dihapus dari daftar hitam oleh {1}!",
"blacklistAddDm":"Anda telah masuk ke dalam daftar hitam di server kami!\nMulai sekarang, Anda tidak dapat membuat tiket!",
"blacklistRemoveDm":"Kamu telah dihapus dari daftar hitam di server kami!\nSekarang Anda dapat membuat tiket lagi!",
- "clearLog":"Tiket {0} telah dihapus oleh {1}!"
+ "clearLog":"Tiket {0} telah dihapus oleh {1}!",
+
+ "transferLog":"Kepemilikan tiket ini telah berhasil dipindahkan dari {0} ke {1} oleh {2}",
+ "transferDm":"Kepemilikan tiket kamu sudah dialihkan dari {0} ke {1} di server kami!",
+ "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!"
}
},
"transcripts":{
@@ -241,7 +270,19 @@
"retry":"Coba Ulang",
"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."
+ "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"
+ },
+ "text":{
+ "messagesTitle":"PESAN",
+ "embedTitle":"SEMATAN",
+ "fileTitle":"BERKAS",
+ "fieldsTitle":"BIDANG",
+ "reactionsTitle":"REAKSI",
+ "statsTitle":"STATUS",
+ "emptyContent":"",
+ "noTitle":"",
+ "noDesc":""
}
},
"errors":{
@@ -257,7 +298,8 @@
"unknownPanel":"Panel Tidak Diketahui",
"notInGuild":"Tidak Berada Di Dalam Server",
"channelRename":"Tidak Dapat Mengganti Nama Saluran",
- "busy":"Tidak Sedang Sibuk"
+ "busy":"Tidak Sedang Sibuk",
+ "permissionError":"Terjadi Kesalahan Hak Akses"
},
"descriptions":{
"askForInfo":"Hubungi pemilik bot ini untuk info lebih lanjut!",
@@ -272,15 +314,18 @@
"noPermissionsCooldown":"Anda tidak diperbolehkan menggunakan {0} karena kamu sedang dalam cooldown!",
"noPermissionsBlacklist":"Anda tidak diperbolehkan menggunakan {0} karena Anda telah masuk dalam daftar hitam!",
"noPermissionsLimitGlobal":"Anda tidak diizinkan untuk membuat tiket karena server telah mencapai batas maksimal tiket!",
- "noPermissionsLimitGlobalPengguna":"Anda tidak dapat membuat tiket karena Anda telah mencapai batas maksimal tiket!",
+ "noPermissionsLimitGlobalUser":"Anda tidak dapat membuat tiket karena Anda telah mencapai batas maksimal tiket!",
"noPermissionsLimitOption":"Anda tidak diizinkan untuk membuat tiket karena server telah mencapai batas maksimal tiket untuk opsi ini!",
- "noPermissionsLimitOptionPengguna":"Anda tidak dapat membuat tiket karena Anda telah mencapai batas maksimal tiket untuk opsi ini!",
+ "noPermissionsLimitOptionUser":"Anda tidak dapat membuat tiket karena Anda telah mencapai batas maksimal tiket untuk opsi ini!",
"unknownTicket":"Gunakan perintah ini lagi dalam tiket yang valid!",
"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.",
"channelRenameSource":"Sumber dari masalah: {0}",
- "busy":"Dapat dapat menggunakan {0}!\nTiket sedang diproses oleh bot.\n\nMohon coba beberapa saat lagi!"
+ "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.."
},
"optionInvalidReasons":{
"stringRegex":"Nilai tidak sesuai pola!",
@@ -331,7 +376,6 @@
"added":"Ditambahkan",
"removed":"Dikeluarkan",
"filter":"Penyaring",
- "claimedBy":"Diklaim oleh {0}",
"method":"Metode",
"type":"Tipe",
"blacklisted":"Daftar hitam",
@@ -355,7 +399,27 @@
"status":"Status",
"claimed":"Diklaim",
"pinned":"Disematkan",
- "creationDate":"Tanggal Pembuatan"
+ "creationDate":"Tanggal Pembuatan",
+
+ "noone":"Tidak Seorangpun",
+ "open":"Buka",
+ "closed":"Tutup",
+ "priority":"Prioritas",
+ "participants":"Para Partisipan",
+ "yes":"Ya",
+ "no":"Tidak",
+ "option":"Opsi",
+ "topic":"Topik",
+ "uptime":"Waktu Aktif Sistem",
+ "messages":"Pesan",
+ "embeds":"Sematan",
+ "files":"Berkas",
+ "components":"Komponen",
+ "cooldown":"Cooldown",
+ "maxTickets":"Tiket Maksimum",
+ "admins":"Admin",
+ "roles":"Hak",
+ "size":"Ukuran"
},
"lowercase":{
"text":"teks",
@@ -384,6 +448,7 @@
"unclaim":"Batalkan klaim tiket.",
"pin":"Sematkan sebuah tiket.",
"unpin":"Batalkan penyematan tiket.",
+
"move":"Pindahkan tiket.",
"moveId":"Penanda opsi yang ingin anda pindahkan.",
"rename":"Ganti nama tiket.",
@@ -392,6 +457,7 @@
"addUser":"Pengguna yang akan ditambahkan.",
"remove":"Keluarkan seseorang dari tiket.",
"removeUser":"Pengguna yang akan dikeluarkan.",
+
"blacklist":"Kelola the Tiket blacklist.",
"blacklistView":"Lihat daftar yang masuk ke dalam daftar hitam.",
"blacklistAdd":"Tambahkan seseorang ke dalam daftar hitam.",
@@ -405,6 +471,7 @@
"statsUserUser":"Pengguna yang akan dilihat.",
"statsTicket":"Lihat status dari sebuah tiket di server.",
"statsTicketTicket":"Tiket yang akan dilihat.",
+
"clear":"Hapus banyak tiket sekaligus.",
"clearFilter":"Filter untuk menghapus tiket.",
"clearFilters":{
@@ -417,6 +484,7 @@
"unpin":"Pembatalan penyematan",
"autoclose":"Ditutup otomatis"
},
+
"autoclose":"Kelola penutupan otomatis tiket.",
"autocloseDisable":"Non-aktifkan penutupan otomatis.",
"autocloseEnable":"Aktifkan penutupan otomatis.",
@@ -424,7 +492,19 @@
"autodelete":"Kelola penghapusan otomatis tiket.",
"autodeleteDisable":"Non-aktifkan penghapusan otomatis.",
"autodeleteEnable":"Aktifkan penghapusan otomatis.",
- "autodeleteEnableTime":"Lama waktu saluran tidak aktif sebelum dihapus."
+ "autodeleteEnableTime":"Lama waktu saluran tidak aktif sebelum dihapus.",
+
+ "topic":"Kelola topik kanal tiket.",
+ "topicSet":"Tentukan topik kanal tiket.",
+ "topicValue":"Topik baru kanal ini.",
+ "topicList":"Dapatkan informasi lengkap tentang semua tiket beserta topik dan statistiknya.",
+ "priority":"Kelola prioritas tiket.",
+ "prioritySet":"Tentukan prioritas tiket.",
+ "priorityValue":"Prioritas kanal.",
+ "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."
},
"helpMenu":{
"help":"Dapatkan daftar perintah yang tersedia.",
@@ -452,7 +532,16 @@
"autocloseDisable":"Nonaktifkan penutupan otomatis.",
"autocloseEnable":"Aktifkan penutupan otomatis.",
"autodeleteDisable":"Non-aktifkan penghapusan otomatis.",
- "autodeleteEnable":"Aktifkan penghapusan otomatis."
+ "autodeleteEnable":"Aktifkan penghapusan otomatis.",
+ "categories":{
+ "general":"Perintah Umum",
+ "basicTicket":"Perintah Dasar Tiket",
+ "advancedTicket":"Perintah Lanjutan Ticket",
+ "userTicket":"Perintah Tiket untuk Pengguna",
+ "admin":"Perintah Admin",
+ "advanced":"Perintah Lanjutan",
+ "extra":"Perintah Tambahan"
+ }
},
"stats":{
"scopes":{
@@ -460,7 +549,8 @@
"system":"Status Sistem",
"user":"Status Pengguna",
"ticket":"Status Tiket",
- "participants":"Partisipan"
+ "participants":"Partisipan",
+ "messages":"Pesan"
},
"properties":{
"ticketsCreated":"Tiket Dibuat",
@@ -472,7 +562,47 @@
"ticketsPinned":"Tiket Disematkan",
"ticketsMoved":"Tickets Dipindahkan",
"usersBlacklisted":"Pengguna Masuk Ke Dalam Daftar Hitam",
- "transcriptsCreated":"Transkrip Dibuat"
+ "transcriptsCreated":"Transkrip Dibuat",
+ "ticketsAutodeleted":"Tiket Dihapus Secara Otomatis",
+ "ticketsTransferred":"Tiket Dipindahkan",
+ "ticketVolume":"Kapasitas Tiket",
+ "averageTickets":"Rata-rata Tiket per Pengguna",
+ "currentTickets":"Tiket Saat Ini",
+ "age":"Umur Tiket",
+ "responseTime":"Waktu Respons",
+ "resolutionTime":"Resolution Time",
+ "createdOn":"Dibuat Pada",
+ "createdBy":"Dibuat Oleh",
+ "closedOn":"Ditutup Pada",
+ "closedBy":"Ditutup Oleh",
+ "claimedOn":"Diklaim Pada",
+ "claimedBy":"Diklaim Oleh",
+ "pinnedOn":"Disematkan Pada",
+ "pinnedBy":"Disematkan Oleh",
+ "deletedOn":"Dihapus Pada",
+ "deletedBy":"Dihapus Oleh"
+ },
+ "roles":{
+ "developer":"Pembuat",
+ "serverOwner":"Pemilik Server",
+ "serverAdmin":"Admin Server",
+ "moderator":"Tim Moderator",
+ "support":"Tim Pendukung",
+ "member":"Anggota"
}
+ },
+ "panel":{
+ "selectTicket":"Pilih tiket kamu",
+ "selectRole":"Pilih status kamu",
+ "selectOption":"Pilih opsi kamu"
+ },
+ "priorities":{
+ "urgent":"Sangat Penting",
+ "veryHigh":"Sangat Tinggi",
+ "high":"Tinggi",
+ "normal":"Normal",
+ "low":"Rendah",
+ "veryLow":"Sangat Rendah",
+ "none":"Tidak Ada"
}
}
\ No newline at end of file
diff --git a/languages/italian.json b/languages/italian.json
index 5de0595..aed6b27 100644
--- a/languages/italian.json
+++ b/languages/italian.json
@@ -1,6 +1,6 @@
{
"_TRANSLATION":{
- "otversion":"v4.0.7",
+ "otversion":"v4.1.0",
"translators":["fraden1mvp."],
"lastedited":"08/10/2024",
"language":"Italian",
diff --git a/languages/japanese.json b/languages/japanese.json
index 4e9ae5e..4b101f6 100644
--- a/languages/japanese.json
+++ b/languages/japanese.json
@@ -1,6 +1,6 @@
{
"_TRANSLATION":{
- "otversion":"v4.0.7",
+ "otversion":"v4.1.0",
"translators":["HanumeshGupta"],
"lastedited":"19/06/2025",
"language":"Japanese",
diff --git a/languages/korean.json b/languages/korean.json
index 97f9db7..d7fcf98 100644
--- a/languages/korean.json
+++ b/languages/korean.json
@@ -1,6 +1,6 @@
{
"_TRANSLATION":{
- "otversion":"v4.0.7",
+ "otversion":"v4.1.0",
"translators":["HanumeshGupta"],
"lastedited":"19/06/2025",
"language":"Korean",
diff --git a/languages/kurdish.json b/languages/kurdish.json
index e39ca75..b995c29 100644
--- a/languages/kurdish.json
+++ b/languages/kurdish.json
@@ -1,6 +1,6 @@
{
"_TRANSLATION":{
- "otversion":"v4.0.7",
+ "otversion":"v4.1.0",
"translators":["HanumeshGupta"],
"lastedited":"19/06/2025",
"language":"Kurdish",
diff --git a/languages/latvian.json b/languages/latvian.json
index b5178bd..c45ca91 100644
--- a/languages/latvian.json
+++ b/languages/latvian.json
@@ -1,6 +1,6 @@
{
"_TRANSLATION": {
- "otversion": "v4.0.7",
+ "otversion": "v4.1.0",
"translators": ["NoOneNook"],
"lastedited": "25/03/2025",
"language": "Latvian",
diff --git a/languages/lithuanian.json b/languages/lithuanian.json
index e2bc2b7..3dca0a4 100644
--- a/languages/lithuanian.json
+++ b/languages/lithuanian.json
@@ -1,6 +1,6 @@
{
"_TRANSLATION":{
- "otversion":"v4.0.7",
+ "otversion":"v4.1.0",
"translators":["TsgIndrius"],
"lastedited":"26/01/2025",
"language":"Lithuanian",
diff --git a/languages/norwegian.json b/languages/norwegian.json
index 7d336d9..59a98e5 100644
--- a/languages/norwegian.json
+++ b/languages/norwegian.json
@@ -1,6 +1,6 @@
{
"_TRANSLATION": {
- "otversion": "v4.0.7",
+ "otversion": "v4.1.0",
"translators": ["NoOneNook"],
"lastedited": "25/03/2025",
"language": "Norwegian",
diff --git a/languages/persian.json b/languages/persian.json
index c297a2a..1f88c1f 100644
--- a/languages/persian.json
+++ b/languages/persian.json
@@ -1,6 +1,6 @@
{
"_TRANSLATION":{
- "otversion":"v4.0.7",
+ "otversion":"v4.1.0",
"translators":["dysashop","zhavis"],
"lastedited":"28/05/2025",
"language":"Persian",
diff --git a/languages/polish.json b/languages/polish.json
index da2b7da..e4df15d 100644
--- a/languages/polish.json
+++ b/languages/polish.json
@@ -1,6 +1,6 @@
{
"_TRANSLATION":{
- "otversion":"v4.0.7",
+ "otversion":"v4.1.0",
"translators":["DanoGlez"],
"lastedited":"28/01/2025",
"language":"Polish",
diff --git a/languages/portuguese.json b/languages/portuguese.json
index f891f35..82ca261 100644
--- a/languages/portuguese.json
+++ b/languages/portuguese.json
@@ -1,6 +1,6 @@
{
"_TRANSLATION":{
- "otversion":"v4.0.7",
+ "otversion":"v4.1.0",
"translators":["quiradon"],
"lastedited":"20/08/2024",
"language":"Portuguese",
diff --git a/languages/romanian.json b/languages/romanian.json
index 9680832..6f471ce 100644
--- a/languages/romanian.json
+++ b/languages/romanian.json
@@ -1,6 +1,6 @@
{
"_TRANSLATION":{
- "otversion":"v4.0.7",
+ "otversion":"v4.1.0",
"translators":["SankeDev"],
"lastedited":"27/08/2024",
"language":"Romanian",
diff --git a/languages/russian.json b/languages/russian.json
index 4dcee03..de15f34 100644
--- a/languages/russian.json
+++ b/languages/russian.json
@@ -1,6 +1,6 @@
{
"_TRANSLATION": {
- "otversion": "v4.0.7",
+ "otversion": "v4.1.0",
"translators": ["NoOneNook"],
"lastedited": "25/03/2025",
"language": "Russian",
diff --git a/languages/simplified-chinese.json b/languages/simplified-chinese.json
index 0bbe699..b6ebe4a 100644
--- a/languages/simplified-chinese.json
+++ b/languages/simplified-chinese.json
@@ -1,6 +1,6 @@
{
"_TRANSLATION":{
- "otversion":"v4.0.7",
+ "otversion":"v4.1.0",
"translators":["HanumeshGupta"],
"lastedited":"19/06/2025",
"language":"Simplified Chainese",
diff --git a/languages/slovenian.json b/languages/slovenian.json
index c82ad2d..37c3fca 100644
--- a/languages/slovenian.json
+++ b/languages/slovenian.json
@@ -1,6 +1,6 @@
{
"_TRANSLATION":{
- "otversion":"v4.0.7",
+ "otversion":"v4.1.0",
"translators":["HanumeshGupta"],
"lastedited":"19/06/2025",
"language":"Solvenian",
diff --git a/languages/spanish.json b/languages/spanish.json
index aa8a9c9..7fb2552 100644
--- a/languages/spanish.json
+++ b/languages/spanish.json
@@ -1,6 +1,6 @@
{
"_TRANSLATION":{
- "otversion":"v4.0.7",
+ "otversion":"v4.1.0",
"translators":["Redactado","Josuens"],
"lastedited":"22/08/2024",
"language":"Spanish",
diff --git a/languages/swedish.json b/languages/swedish.json
index 094963b..f8c24f6 100644
--- a/languages/swedish.json
+++ b/languages/swedish.json
@@ -1,6 +1,6 @@
{
"_TRANSLATION": {
- "otversion": "v4.0.7",
+ "otversion": "v4.1.0",
"translators": ["NoOneNook"],
"lastedited": "25/03/2025",
"language": "Svenska",
diff --git a/languages/tamil.json b/languages/tamil.json
index 7ea4393..69ae29a 100644
--- a/languages/tamil.json
+++ b/languages/tamil.json
@@ -1,6 +1,6 @@
{
"_TRANSLATION":{
- "otversion":"v4.0.7",
+ "otversion":"v4.1.0",
"translators":["HanumeshGupta"],
"lastedited":"19/06/2025",
"language":"Tamil",
diff --git a/languages/thai.json b/languages/thai.json
index 38b733e..f8a633c 100644
--- a/languages/thai.json
+++ b/languages/thai.json
@@ -1,478 +1,608 @@
{
"_TRANSLATION":{
- "otversion":"v4.0.7",
+ "otversion":"v4.1.0",
"translators":["modshd"],
- "lastedited":"13/11/2024",
+ "lastedited":"09/11/2025",
"language":"Thai",
"automated":false
},
"checker":{
"system":{
- "typeError": "[เธเนเธญเธเธดเธเธเธฅเธฒเธ]",
- "headerOpenTicket": "เนเธเธดเธเธซเนเธญเธเธเธดเธเธเนเธญ",
- "typeWarning": "[เธเธณเนเธเธทเธญเธ]",
- "typeInfo": "[เธเนเธญเธกเธนเธฅ]",
- "headerConfigChecker": "เธเธฑเธงเธเธฃเธงเธเธชเธญเธเธเธฒเธฃเธเธฑเนเธเธเนเธฒ",
- "headerDescription": "เธเธฃเธงเธเธชเธญเธเธเนเธญเธเธดเธเธเธฅเธฒเธเนเธเนเธเธฅเนเธเธฒเธฃเธเธฑเนเธเธเนเธฒเธเธญเธเธเธธเธ",
- "footerError": "เธเธญเธเธเธฐเนเธกเนเนเธฃเธดเนเธกเธเธเธเธงเนเธฒเธเธฐเนเธเนเนเธเธเนเธญเธเธดเธเธเธฅเธฒเธเธเธฑเนเธเธซเธกเธเนเธ {0}",
- "footerWarning": "เนเธเธฐเธเธณเนเธซเนเนเธเนเนเธเธเนเธญเธเธดเธเธเธฅเธฒเธเธเธฑเนเธเธซเธกเธเนเธ {0} เธเนเธญเธเนเธฃเธดเนเธกเนเธเนเธเธฒเธ",
- "footerSupport": "เธชเธเธฑเธเธชเธเธธเธ: {0} - เนเธญเธเธชเธฒเธฃ: {1}",
- "compactInformation": "เนเธเน {0} เนเธเธทเนเธญเธเนเธญเธกเธนเธฅเนเธเธดเนเธกเนเธเธดเธก",
- "dataPath": "เธเธตเธญเธขเธนเน",
- "dataDocs": "เนเธญเธเธชเธฒเธฃเธเนเธงเธขเนเธซเธฅเธทเธญ",
- "dataMessages": "เธเนเธญเธเธงเธฒเธก"
+ "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": "เธเนเธญเธเธงเธฒเธกเธเธตเนเนเธกเนเธเธนเธเธเนเธญเธ",
-
- "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": "เธซเธกเธฒเธขเนเธฅเธเธเธตเนเนเธกเนเธชเธฒเธกเธฒเธฃเธเนเธเนเธเธจเธนเธเธขเนเนเธเน",
-
- "booleanTrue": "เธเนเธฒเธเธนเธฅเธตเธเธเธตเนเนเธกเนเธชเธฒเธกเธฒเธฃเธเนเธเนเธ true เนเธเน",
- "booleanFalse": "เธเนเธฒเธเธนเธฅเธตเธเธเธตเนเนเธกเนเธชเธฒเธกเธฒเธฃเธเนเธเนเธ false เนเธเน",
-
- "arrayEmptyDisabled": "เธเนเธฒเธเธฑเธงเนเธเธฃเธเธตเนเนเธกเนเธญเธเธธเธเธฒเธเนเธซเนเธงเนเธฒเธเนเธเธฅเนเธฒ",
- "arrayEmptyRequired": "เธเนเธฒเธเธฑเธงเนเธเธฃเธเธตเนเธเนเธญเธเธงเนเธฒเธเนเธเธฅเนเธฒ",
- "arrayTooShort": "เธเนเธฒเธเธฑเธงเนเธเธฃเธเธตเนเธเนเธญเธเธกเธตเธเธงเธฒเธกเธขเธฒเธงเธญเธขเนเธฒเธเธเนเธญเธข {0}",
- "arrayTooLong": "เธเนเธฒเธเธฑเธงเนเธเธฃเธเธตเนเธเนเธญเธเธกเธตเธเธงเธฒเธกเธขเธฒเธงเนเธกเนเนเธเธดเธ {0}",
- "arrayLengthInvalid": "เธเนเธฒเธเธฑเธงเนเธเธฃเธเธตเนเธเนเธญเธเธกเธตเธเธงเธฒเธกเธขเธฒเธง {0}",
- "arrayInvalidTypes": "เธเนเธฒเธเธฑเธงเนเธเธฃเธเธตเนเธชเธฒเธกเธฒเธฃเธเธเธฃเธฐเธเธญเธเธเนเธงเธขเธเธฃเธฐเนเธ เธเนเธซเธฅเนเธฒเธเธตเนเนเธเนเธฒเธเธฑเนเธ: {0}",
- "arrayDouble": "เธเนเธฒเธเธฑเธงเนเธเธฃเธเธตเนเนเธกเนเธญเธเธธเธเธฒเธเนเธซเนเธกเธตเธเนเธฒเธเนเธณเธชเธญเธเธเธฃเธฑเนเธ",
-
- "discordInvalidId": "เธเธตเนเธเธทเธญเธฃเธซเธฑเธชเนเธญเธเธต {0} discord เธเธตเนเนเธกเนเธเธนเธเธเนเธญเธ",
- "discordInvalidIdOptions": "เธเธตเนเธเธทเธญเธฃเธซเธฑเธชเนเธญเธเธต {0} discord เธเธตเนเนเธกเนเธเธนเธเธเนเธญเธ เธเธธเธเธชเธฒเธกเธฒเธฃเธเนเธเนเธเธฑเธงเนเธฅเธทเธญเธเนเธซเธฅเนเธฒเธเธตเน: {1}",
- "discordInvalidToken": "เธเธตเนเธเธทเธญเนเธเนเธเนเธ discord เธเธตเนเนเธกเนเธเธนเธเธเนเธญเธ (เธเธฒเธเธเนเธฒเธเนเธงเธขเธฒเธเธฃเธเน)",
- "colorInvalid": "เธเธตเนเธเธทเธญเธชเธต hex เธเธตเนเนเธกเนเธเธนเธเธเนเธญเธ",
- "emojiTooShort": "เธเนเธญเธเธงเธฒเธกเธเธตเนเธเนเธญเธเธกเธตเธญเธตเนเธกเธเธดเธญเธขเนเธฒเธเธเนเธญเธข {0} เธเธฑเธง",
- "emojiTooLong": "เธเนเธญเธเธงเธฒเธกเธเธตเนเธเนเธญเธเธกเธตเธญเธตเนเธกเธเธดเนเธกเนเนเธเธดเธ {0} เธเธฑเธง",
- "emojiCustom": "เธญเธตเนเธกเธเธดเธเธตเนเนเธกเนเธชเธฒเธกเธฒเธฃเธเนเธเนเธเธญเธตเนเธกเธเธดเนเธเธเธเธณเธซเธเธเนเธญเธเธเธญเธ discord เนเธเน",
- "emojiInvalid": "เธเธตเนเธเธทเธญเธฅเธณเธเธฑเธเธญเธตเนเธกเธเธดเธเธตเนเนเธกเนเธเธนเธเธเนเธญเธ",
- "urlInvalid": "URL เธเธตเนเนเธกเนเธเธนเธเธเนเธญเธ",
- "urlInvalidHttp": "URL เธเธตเนเธชเธฒเธกเธฒเธฃเธเนเธเนเนเธเนเนเธเธฃเนเธเธเธญเธฅ https:// เนเธเนเธฒเธเธฑเนเธ",
- "urlInvalidProtocol": "URL เธเธตเนเธชเธฒเธกเธฒเธฃเธเนเธเนเนเธเธฃเนเธเธเธญเธฅ http:// เนเธฅเธฐ https:// เนเธเนเธฒเธเธฑเนเธ",
- "urlInvalidHostname": "URL เธเธตเนเธกเธตเธเธทเนเธญเนเธฎเธชเธเนเธเธตเนเนเธกเนเนเธเนเธฃเธฑเธเธญเธเธธเธเธฒเธ",
- "urlInvalidExtension": "URL เธเธตเนเธกเธตเธเธฒเธกเธชเธเธธเธฅเนเธกเนเธเธนเธเธเนเธญเธ เนเธฅเธทเธญเธเธฃเธฐเธซเธงเนเธฒเธ: {0}",
- "urlInvalidPath": "URL เธเธตเนเธกเธตเนเธชเนเธเธเธฒเธเนเธกเนเธเธนเธเธเนเธญเธ",
- "idNotUnique": "เธฃเธซเธฑเธชเธเธตเนเนเธกเนเนเธเนเธเนเธญเธเธฅเธฑเธเธฉเธเน, เนเธเนเธฃเธซเธฑเธชเธญเธทเนเธเนเธเธ",
- "idNonExistent": "เธฃเธซเธฑเธช {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' เนเธเนเธฒเธเธฑเนเธ"
+ "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":"เธเนเธฒเธเธนเธฅเธตเธเธเธตเนเนเธกเนเธชเธฒเธกเธฒเธฃเธเนเธเนเธ true เนเธเน",
+ "booleanFalse":"เธเนเธฒเธเธนเธฅเธตเธเธเธตเนเนเธกเนเธชเธฒเธกเธฒเธฃเธเนเธเนเธ false เนเธเน",
+
+ "arrayEmptyDisabled":"เธเนเธฒเธเธฑเธงเนเธเธฃเธเธตเนเนเธกเนเธญเธเธธเธเธฒเธเนเธซเนเธงเนเธฒเธเนเธเธฅเนเธฒ",
+ "arrayEmptyRequired":"เธเนเธฒเธเธฑเธงเนเธเธฃเธเธตเนเธเนเธญเธเธงเนเธฒเธเนเธเธฅเนเธฒ",
+ "arrayTooShort":"เธเนเธฒเธเธฑเธงเนเธเธฃเธเธตเนเธเนเธญเธเธกเธตเธเธงเธฒเธกเธขเธฒเธงเธญเธขเนเธฒเธเธเนเธญเธข {0}",
+ "arrayTooLong":"เธเนเธฒเธเธฑเธงเนเธเธฃเธเธตเนเธเนเธญเธเธกเธตเธเธงเธฒเธกเธขเธฒเธงเนเธกเนเนเธเธดเธ {0}",
+ "arrayLengthInvalid":"เธเนเธฒเธเธฑเธงเนเธเธฃเธเธตเนเธเนเธญเธเธกเธตเธเธงเธฒเธกเธขเธฒเธง {0}",
+ "arrayInvalidTypes":"เธเนเธฒเธเธฑเธงเนเธเธฃเธเธตเนเธชเธฒเธกเธฒเธฃเธเธเธฃเธฐเธเธญเธเธเนเธงเธขเธเธฃเธฐเนเธ เธเนเธซเธฅเนเธฒเธเธตเนเนเธเนเธฒเธเธฑเนเธ: {0}",
+ "arrayDouble":"เธเนเธฒเธเธฑเธงเนเธเธฃเธเธตเนเนเธกเนเธญเธเธธเธเธฒเธเนเธซเนเธกเธตเธเนเธฒเธเนเธณเธชเธญเธเธเธฃเธฑเนเธ",
+
+ "discordInvalidId":"เธเธตเนเธเธทเธญเธฃเธซเธฑเธชเนเธญเธเธต {0} discord เธเธตเนเนเธกเนเธเธนเธเธเนเธญเธ",
+ "discordInvalidIdOptions":"เธเธตเนเธเธทเธญเธฃเธซเธฑเธชเนเธญเธเธต {0} discord เธเธตเนเนเธกเนเธเธนเธเธเนเธญเธ เธเธธเธเธชเธฒเธกเธฒเธฃเธเนเธเนเธเธฑเธงเนเธฅเธทเธญเธเนเธซเธฅเนเธฒเธเธตเน: {1}",
+ "discordInvalidToken":"เธเธตเนเธเธทเธญเนเธเนเธเนเธ discord เธเธตเนเนเธกเนเธเธนเธเธเนเธญเธ (เธเธฒเธเธเนเธฒเธเนเธงเธขเธฒเธเธฃเธเน)",
+ "colorInvalid":"เธเธตเนเธเธทเธญเธชเธต hex เธเธตเนเนเธกเนเธเธนเธเธเนเธญเธ",
+ "emojiTooShort":"เธเนเธญเธเธงเธฒเธกเธเธตเนเธเนเธญเธเธกเธตเธญเธตเนเธกเธเธดเธญเธขเนเธฒเธเธเนเธญเธข {0} เธเธฑเธง",
+ "emojiTooLong":"เธเนเธญเธเธงเธฒเธกเธเธตเนเธเนเธญเธเธกเธตเธญเธตเนเธกเธเธดเนเธกเนเนเธเธดเธ {0} เธเธฑเธง",
+ "emojiCustom":"เธญเธตเนเธกเธเธดเธเธตเนเนเธกเนเธชเธฒเธกเธฒเธฃเธเนเธเนเธเธญเธตเนเธกเธเธดเนเธเธเธเธณเธซเธเธเนเธญเธเธเธญเธ discord เนเธเน",
+ "emojiInvalid":"เธเธตเนเธเธทเธญเธฅเธณเธเธฑเธเธญเธตเนเธกเธเธดเธเธตเนเนเธกเนเธเธนเธเธเนเธญเธ",
+ "urlInvalid":"URL เธเธตเนเนเธกเนเธเธนเธเธเนเธญเธ",
+ "urlInvalidHttp":"URL เธเธตเนเธชเธฒเธกเธฒเธฃเธเนเธเนเนเธเนเนเธเธฃเนเธเธเธญเธฅ https:// เนเธเนเธฒเธเธฑเนเธ",
+ "urlInvalidProtocol":"URL เธเธตเนเธชเธฒเธกเธฒเธฃเธเนเธเนเนเธเธฃเนเธเธเธญเธฅ http:// เนเธฅเธฐ https:// เนเธเนเธฒเธเธฑเนเธ",
+ "urlInvalidHostname":"URL เธเธตเนเธกเธตเธเธทเนเธญเนเธฎเธชเธเนเธเธตเนเนเธกเนเนเธเนเธฃเธฑเธเธญเธเธธเธเธฒเธ",
+ "urlInvalidExtension":"URL เธเธตเนเธกเธตเธเธฒเธกเธชเธเธธเธฅเนเธกเนเธเธนเธเธเนเธญเธ เนเธฅเธทเธญเธเธฃเธฐเธซเธงเนเธฒเธ: {0}",
+ "urlInvalidPath":"URL เธเธตเนเธกเธตเนเธชเนเธเธเธฒเธเนเธกเนเธเธนเธเธเนเธญเธ",
+ "idNotUnique":"เธฃเธซเธฑเธชเธเธตเนเนเธกเนเนเธเนเธเนเธญเธเธฅเธฑเธเธฉเธเน, เนเธเนเธฃเธซเธฑเธชเธญเธทเนเธเนเธเธ",
+ "idNonExistent":"เธฃเธซเธฑเธช {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' เนเธเนเธฒเธเธฑเนเธ",
+ "customInvalidVersion":"เนเธงเธญเธฃเนเธเธฑเธเธเธตเนเธฃเธฐเธเธธเนเธเนเธเธฅเน config เธเธญเธเธเธธเธเนเธกเนเธเธฃเธเธเธฑเธ เธเธฃเธงเธเธชเธญเธเนเธซเนเนเธเนเนเธเธงเนเธฒเธเธธเธเนเธเนเธญเธฑเธเนเธเธเนเธเธฅเน config เนเธเนเธเนเธงเธญเธฃเนเธเธฑเธเธฅเนเธฒเธชเธธเธเนเธฅเนเธง"
}
},
"actions":{
"buttons":{
- "create": "เธชเธฃเนเธฒเธเธซเนเธญเธเธเธดเธเธเนเธญ",
- "close": "เธเธดเธเธซเนเธญเธเธเธดเธเธเนเธญ",
- "delete": "เธฅเธเธซเนเธญเธเธเธดเธเธเนเธญ",
- "reopen": "เนเธเธดเธเธซเนเธญเธเธเธดเธเธเนเธญเนเธซเธกเนเธญเธตเธเธเธฃเธฑเนเธ",
- "claim": "เธฃเธฑเธเธซเนเธญเธเธเธดเธเธเนเธญ",
- "unclaim": "เธขเธเนเธฅเธดเธเธเธฒเธฃเธฃเธฑเธเธซเนเธญเธเธเธดเธเธเนเธญ",
- "pin": "เธเธฑเธเธซเธกเธธเธเธซเนเธญเธเธเธดเธเธเนเธญ",
- "unpin": "เธขเธเนเธฅเธดเธเธเธฒเธฃเธเธฑเธเธซเธกเธธเธเธซเนเธญเธเธเธดเธเธเนเธญ",
- "clear": "เธฅเธเธซเนเธญเธเธเธดเธเธเนเธญเธเธฑเนเธเธซเธกเธ",
- "helpSwitchSlash": "เธเธนเธเธณเธชเธฑเนเธเนเธเธ Slash",
- "helpSwitchText": "เธเธนเธเธณเธชเธฑเนเธเนเธเธเธเนเธญเธเธงเธฒเธก",
- "helpPage": "เธซเธเนเธฒ {0}",
- "withReason": "เธเนเธงเธขเนเธซเธเธธเธเธฅ",
- "withoutTranscript": "เนเธกเนเธกเธตเธเธฑเธเธเธถเธเธเธฒเธฃเธชเธเธเธเธฒ"
+ "create":"เธชเธฃเนเธฒเธเธซเนเธญเธเธเธดเธเธเนเธญ",
+ "close":"เธเธดเธเธซเนเธญเธเธเธดเธเธเนเธญ",
+ "delete":"เธฅเธเธซเนเธญเธเธเธดเธเธเนเธญ",
+ "reopen":"เนเธเธดเธเธซเนเธญเธเธเธดเธเธเนเธญเนเธซเธกเนเธญเธตเธเธเธฃเธฑเนเธ",
+ "claim":"เธฃเธฑเธเธซเนเธญเธเธเธดเธเธเนเธญ",
+ "unclaim":"เธขเธเนเธฅเธดเธเธเธฒเธฃเธฃเธฑเธเธซเนเธญเธเธเธดเธเธเนเธญ",
+ "pin":"เธเธฑเธเธซเธกเธธเธเธซเนเธญเธเธเธดเธเธเนเธญ",
+ "unpin":"เธขเธเนเธฅเธดเธเธเธฒเธฃเธเธฑเธเธซเธกเธธเธเธซเนเธญเธเธเธดเธเธเนเธญ",
+ "clear":"เธฅเธเธซเนเธญเธเธเธดเธเธเนเธญเธเธฑเนเธเธซเธกเธ",
+ "helpSwitchSlash":"เธเธนเธเธณเธชเธฑเนเธเนเธเธ Slash",
+ "helpSwitchText":"เธเธนเธเธณเธชเธฑเนเธเนเธเธเธเนเธญเธเธงเธฒเธก",
+ "helpPage":"เธซเธเนเธฒ {0}",
+ "withReason":"เธเนเธงเธขเนเธซเธเธธเธเธฅ",
+ "withoutTranscript":"เนเธกเนเธกเธตเธเธฑเธเธเธถเธเธเธฒเธฃเธชเธเธเธเธฒ"
},
"titles":{
- "created": "เธชเธฃเนเธฒเธเธซเนเธญเธเธเธดเธเธเนเธญเนเธฅเนเธง",
- "close": "เธเธดเธเธซเนเธญเธเธเธดเธเธเนเธญเนเธฅเนเธง",
- "delete": "เธฅเธเธซเนเธญเธเธเธดเธเธเนเธญเนเธฅเนเธง",
- "reopen": "เนเธเธดเธเธซเนเธญเธเธเธดเธเธเนเธญเนเธซเธกเนเธญเธตเธเธฃเธญเธเนเธฅเนเธง",
- "claim": "เธฃเธฑเธเธซเนเธญเธเธเธดเธเธเนเธญเนเธฅเนเธง",
- "unclaim": "เธขเธเนเธฅเธดเธเธเธฒเธฃเธฃเธฑเธเธซเนเธญเธเธเธดเธเธเนเธญเนเธฅเนเธง",
- "pin": "เธเธฑเธเธซเธกเธธเธเธซเนเธญเธเธเธดเธเธเนเธญเนเธฅเนเธง",
- "unpin": "เธขเธเนเธฅเธดเธเธเธฒเธฃเธเธฑเธเธซเธกเธธเธเธซเนเธญเธเธเธดเธเธเนเธญเนเธฅเนเธง",
- "rename": "เนเธเธฅเธตเนเธขเธเธเธทเนเธญเนเธฃเธทเนเธญเธเธซเนเธญเธเธเธดเธเธเนเธญเนเธฅเนเธง",
- "move": "เธขเนเธฒเธขเธซเนเธญเธเธเธดเธเธเนเธญเนเธฅเนเธง",
- "add": "เนเธเธดเนเธกเธเธนเนเนเธเนเนเธเธซเนเธญเธเธเธดเธเธเนเธญเนเธฅเนเธง",
- "remove": "เธฅเธเธเธนเนเนเธเนเธเธฒเธเธซเนเธญเธเธเธดเธเธเนเธญเนเธฅเนเธง",
+ "created":"เธชเธฃเนเธฒเธเธซเนเธญเธเธเธดเธเธเนเธญเนเธฅเนเธง",
+ "close":"เธเธดเธเธซเนเธญเธเธเธดเธเธเนเธญเนเธฅเนเธง",
+ "delete":"เธฅเธเธซเนเธญเธเธเธดเธเธเนเธญเนเธฅเนเธง",
+ "reopen":"เนเธเธดเธเธซเนเธญเธเธเธดเธเธเนเธญเนเธซเธกเนเธญเธตเธเธฃเธญเธเนเธฅเนเธง",
+ "claim":"เธฃเธฑเธเธซเนเธญเธเธเธดเธเธเนเธญเนเธฅเนเธง",
+ "unclaim":"เธขเธเนเธฅเธดเธเธเธฒเธฃเธฃเธฑเธเธซเนเธญเธเธเธดเธเธเนเธญเนเธฅเนเธง",
+ "pin":"เธเธฑเธเธซเธกเธธเธเธซเนเธญเธเธเธดเธเธเนเธญเนเธฅเนเธง",
+ "unpin":"เธขเธเนเธฅเธดเธเธเธฒเธฃเธเธฑเธเธซเธกเธธเธเธซเนเธญเธเธเธดเธเธเนเธญเนเธฅเนเธง",
+ "rename":"เนเธเธฅเธตเนเธขเธเธเธทเนเธญเนเธฃเธทเนเธญเธเธซเนเธญเธเธเธดเธเธเนเธญเนเธฅเนเธง",
+ "move":"เธขเนเธฒเธขเธซเนเธญเธเธเธดเธเธเนเธญเนเธฅเนเธง",
+ "add":"เนเธเธดเนเธกเธเธนเนเนเธเนเนเธเธซเนเธญเธเธเธดเธเธเนเธญเนเธฅเนเธง",
+ "remove":"เธฅเธเธเธนเนเนเธเนเธเธฒเธเธซเนเธญเธเธเธดเธเธเนเธญเนเธฅเนเธง",
- "help": "เธเธณเธชเธฑเนเธเธเธตเนเธชเธฒเธกเธฒเธฃเธเนเธเนเธเธฒเธเนเธเน",
- "statsReset": "เธฃเธตเนเธเนเธเธชเธเธดเธเธด",
- "blacklistAdd": "เธเธนเนเนเธเนเธเธนเธเนเธเธดเนเธกเนเธเธเธฑเธเธเธตเธเธณ",
- "blacklistRemove": "เธเธนเนเนเธเนเธเธนเธเธเธญเธเธญเธญเธเธเธฒเธเธเธฑเธเธเธตเธเธณ",
- "blacklistGet": "เธเธนเนเนเธเนเธเธตเนเธญเธขเธนเนเนเธเธเธฑเธเธเธตเธเธณ",
- "blacklistView": "เธเธฑเธเธเธตเธเธณเธเธฑเธเธเธธเธเธฑเธ",
- "blacklistAddDm": "เธเธนเธเนเธเธดเนเธกเนเธเธเธฑเธเธเธตเธเธณ",
- "blacklistRemoveDm": "เธเธนเธเธฅเธเธญเธญเธเธเธฒเธเธเธฑเธเธเธตเธเธณ",
- "clear": "เธฅเธเธซเนเธญเธเธเธดเธเธเนเธญเธเธฑเนเธเธซเธกเธ",
- "roles": "เธญเธฑเธเนเธเธเธเธเธเธฒเธ",
+ "help":"เธเธณเธชเธฑเนเธเธเธตเนเธชเธฒเธกเธฒเธฃเธเนเธเนเธเธฒเธเนเธเน",
+ "statsReset":"เธฃเธตเนเธเนเธเธชเธเธดเธเธด",
+ "blacklistAdd":"เธเธนเนเนเธเนเธเธนเธเนเธเธดเนเธกเนเธเธเธฑเธเธเธตเธเธณ",
+ "blacklistRemove":"เธเธนเนเนเธเนเธเธนเธเธเธญเธเธญเธญเธเธเธฒเธเธเธฑเธเธเธตเธเธณ",
+ "blacklistGet":"เธเธนเนเนเธเนเธเธตเนเธญเธขเธนเนเนเธเธเธฑเธเธเธตเธเธณ",
+ "blacklistView":"เธเธฑเธเธเธตเธเธณเธเธฑเธเธเธธเธเธฑเธ",
+ "blacklistAddDm":"เธเธนเธเนเธเธดเนเธกเนเธเธเธฑเธเธเธตเธเธณ",
+ "blacklistRemoveDm":"เธเธนเธเธฅเธเธญเธญเธเธเธฒเธเธเธฑเธเธเธตเธเธณ",
+ "clear":"เธฅเธเธซเนเธญเธเธเธดเธเธเนเธญเธเธฑเนเธเธซเธกเธ",
+ "clearTickets":"เธฅเนเธฒเธเธซเนเธญเธเธเธดเธเธเนเธญ",
+ "roles":"เธญเธฑเธเนเธเธเธเธเธเธฒเธ",
- "autoclose": "เธซเนเธญเธเธเธดเธเธเนเธญเธเธนเธเธเธดเธเนเธเธขเธญเธฑเธเนเธเธกเธฑเธเธด",
- "autocloseEnabled": "เนเธเธดเธเนเธเนเธเธฒเธเธเธฒเธฃเธเธดเธเธซเนเธญเธเธเธดเธเธเนเธญเธญเธฑเธเนเธเธกเธฑเธเธด",
- "autocloseDisabled": "เธเธดเธเธเธฒเธฃเนเธเนเธเธฒเธเธเธฒเธฃเธเธดเธเธซเนเธญเธเธเธดเธเธเนเธญเธญเธฑเธเนเธเธกเธฑเธเธด",
- "autodelete": "เธซเนเธญเธเธเธดเธเธเนเธญเธเธนเธเธฅเธเนเธเธขเธญเธฑเธเนเธเธกเธฑเธเธด",
- "autodeleteEnabled": "เนเธเธดเธเนเธเนเธเธฒเธเธเธฒเธฃเธฅเธเธซเนเธญเธเธเธดเธเธเนเธญเธญเธฑเธเนเธเธกเธฑเธเธด",
- "autodeleteDisabled": "เธเธดเธเธเธฒเธฃเนเธเนเธเธฒเธเธเธฒเธฃเธฅเธเธซเนเธญเธเธเธดเธเธเนเธญเธญเธฑเธเนเธเธกเธฑเธเธด"
+ "autoclose":"เธซเนเธญเธเธเธดเธเธเนเธญเธเธนเธเธเธดเธเนเธเธขเธญเธฑเธเนเธเธกเธฑเธเธด",
+ "autocloseEnabled":"เนเธเธดเธเนเธเนเธเธฒเธเธเธฒเธฃเธเธดเธเธซเนเธญเธเธเธดเธเธเนเธญเธญเธฑเธเนเธเธกเธฑเธเธด",
+ "autocloseDisabled":"เธเธดเธเธเธฒเธฃเนเธเนเธเธฒเธเธเธฒเธฃเธเธดเธเธซเนเธญเธเธเธดเธเธเนเธญเธญเธฑเธเนเธเธกเธฑเธเธด",
+ "autodelete":"เธซเนเธญเธเธเธดเธเธเนเธญเธเธนเธเธฅเธเนเธเธขเธญเธฑเธเนเธเธกเธฑเธเธด",
+ "autodeleteEnabled":"เนเธเธดเธเนเธเนเธเธฒเธเธเธฒเธฃเธฅเธเธซเนเธญเธเธเธดเธเธเนเธญเธญเธฑเธเนเธเธกเธฑเธเธด",
+ "autodeleteDisabled":"เธเธดเธเธเธฒเธฃเนเธเนเธเธฒเธเธเธฒเธฃเธฅเธเธซเนเธญเธเธเธดเธเธเนเธญเธญเธฑเธเนเธเธกเธฑเธเธด",
+
+ "topicSet":"เนเธเธฅเธตเนเธขเธเธซเธฑเธงเธเนเธญเนเธฅเนเธง",
+ "prioritySet":"เนเธเธฅเธตเนเธขเธเธฅเธณเธเธฑเธเธเธงเธฒเธกเธชเธณเธเธฑเธเนเธฅเนเธง",
+ "priorityGet":"เธฅเธณเธเธฑเธเธเธงเธฒเธกเธชเธณเธเธฑเธเธเธญเธเธซเนเธญเธเธเธดเธเธเนเธญ",
+ "transfer":"เนเธญเธเธซเนเธญเธเธเธดเธเธเนเธญเนเธฅเนเธง"
},
"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} เธเธฑเนเธงเนเธกเธ`",
- "autodeleteLeave": "เธซเนเธญเธเธเธดเธเธเนเธญเธเธตเนเธเธนเธเธฅเธเธญเธฑเธเนเธเธกเธฑเธเธดเนเธเธทเนเธญเธเธเธฒเธเธเธนเนเธชเธฃเนเธฒเธเธญเธญเธเธเธฒเธเนเธเธดเธฃเนเธเนเธงเธญเธฃเนเนเธเนเธฅเนเธง",
- "autodeleteTimeout": "เธซเนเธญเธเธเธดเธเธเนเธญเธเธตเนเธเธนเธเธฅเธเธญเธฑเธเนเธเธกเธฑเธเธดเนเธเธทเนเธญเธเธเธฒเธเนเธกเนเนเธเนเธฃเธฑเธเธเธฒเธฃเนเธเนเธเธฒเธเนเธเธดเธ `{0} เธงเธฑเธ`",
- "autocloseEnabled": "เธเธฒเธฃเธเธดเธเธญเธฑเธเนเธเธกเธฑเธเธดเธเธนเธเนเธเธดเธเนเธเนเธเธฒเธเนเธเธซเนเธญเธเธเธดเธเธเนเธญเธเธตเน\nเธกเธฑเธเธเธฐเธเธนเธเธเธดเธเนเธกเธทเนเธญเนเธกเนเนเธเนเนเธเนเธเธฒเธเนเธเธดเธ `{0} เธเธฑเนเธงเนเธกเธ`",
- "autocloseDisabled": "เธเธฒเธฃเธเธดเธเธญเธฑเธเนเธเธกเธฑเธเธดเธเธนเธเธเธดเธเนเธเนเธเธฒเธเนเธเธซเนเธญเธเธเธดเธเธเนเธญเธเธตเน\nเธกเธฑเธเธเธฐเนเธกเนเธเธนเธเธเธดเธเธญเธฑเธเนเธเธกเธฑเธเธดเธญเธตเธเธเนเธญเนเธ",
- "autodeleteEnabled": "เธเธฒเธฃเธฅเธเธญเธฑเธเนเธเธกเธฑเธเธดเธเธนเธเนเธเธดเธเนเธเนเธเธฒเธเนเธเธซเนเธญเธเธเธดเธเธเนเธญเธเธตเน\nเธกเธฑเธเธเธฐเธเธนเธเธฅเธเนเธกเธทเนเธญเนเธกเนเนเธเนเนเธเนเธเธฒเธเนเธเธดเธ `{0} เธงเธฑเธ`",
- "autodeleteDisabled": "เธเธฒเธฃเธฅเธเธญเธฑเธเนเธเธกเธฑเธเธดเธเธนเธเธเธดเธเนเธเนเธเธฒเธเนเธเธซเนเธญเธเธเธดเธเธเนเธญเธเธตเน\nเธกเธฑเธเธเธฐเนเธกเนเธเธนเธเธฅเธเธญเธฑเธเนเธเธกเธฑเธเธดเธญเธตเธเธเนเธญเนเธ",
-
- "ticketMessageLimit": "เธเธธเธเธชเธฒเธกเธฒเธฃเธเธชเธฃเนเธฒเธเธซเนเธญเธเธเธดเธเธเนเธญเนเธเนเนเธเธตเธขเธ {0} เธซเนเธญเธเธเนเธญเธเธฃเธฑเนเธ",
- "ticketMessageAutoclose": "เธซเนเธญเธเธเธดเธเธเนเธญเธเธตเนเธเธฐเธเธนเธเธเธดเธเธญเธฑเธเนเธเธกเธฑเธเธดเนเธกเธทเนเธญเนเธกเนเนเธเนเนเธเนเธเธฒเธเนเธเธดเธ {0} เธเธฑเนเธงเนเธกเธ",
- "ticketMessageAutodelete": "เธซเนเธญเธเธเธดเธเธเนเธญเธเธตเนเธเธฐเธเธนเธเธฅเธเธญเธฑเธเนเธเธกเธฑเธเธดเนเธกเธทเนเธญเนเธกเนเนเธเนเนเธเนเธเธฒเธเนเธเธดเธ {0} เธงเธฑเธ",
- "panelReady": "เธเธธเธเธชเธฒเธกเธฒเธฃเธเธเธนเนเธเธเธเนเธญเธกเธนเธฅเธเนเธฒเธเธฅเนเธฒเธ\nเธเนเธญเธเธงเธฒเธกเธเธตเนเธชเธฒเธกเธฒเธฃเธเธฅเธเนเธเนเนเธฅเนเธง"
+ "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} เธเธฑเนเธงเนเธกเธ`",
+ "autodeleteLeave":"เธซเนเธญเธเธเธดเธเธเนเธญเธเธตเนเธเธนเธเธฅเธเธญเธฑเธเนเธเธกเธฑเธเธดเนเธเธทเนเธญเธเธเธฒเธเธเธนเนเธชเธฃเนเธฒเธเธญเธญเธเธเธฒเธเนเธเธดเธฃเนเธเนเธงเธญเธฃเนเนเธเนเธฅเนเธง",
+ "autodeleteTimeout":"เธซเนเธญเธเธเธดเธเธเนเธญเธเธตเนเธเธนเธเธฅเธเธญเธฑเธเนเธเธกเธฑเธเธดเนเธเธทเนเธญเธเธเธฒเธเนเธกเนเนเธเนเธฃเธฑเธเธเธฒเธฃเนเธเนเธเธฒเธเนเธเธดเธ `{0} เธงเธฑเธ`",
+ "autocloseEnabled":"เธเธฒเธฃเธเธดเธเธญเธฑเธเนเธเธกเธฑเธเธดเธเธนเธเนเธเธดเธเนเธเนเธเธฒเธเนเธเธซเนเธญเธเธเธดเธเธเนเธญเธเธตเน\nเธกเธฑเธเธเธฐเธเธนเธเธเธดเธเนเธกเธทเนเธญเนเธกเนเนเธเนเนเธเนเธเธฒเธเนเธเธดเธ `{0} เธเธฑเนเธงเนเธกเธ`",
+ "autocloseDisabled":"เธเธฒเธฃเธเธดเธเธญเธฑเธเนเธเธกเธฑเธเธดเธเธนเธเธเธดเธเนเธเนเธเธฒเธเนเธเธซเนเธญเธเธเธดเธเธเนเธญเธเธตเน\nเธกเธฑเธเธเธฐเนเธกเนเธเธนเธเธเธดเธเธญเธฑเธเนเธเธกเธฑเธเธดเธญเธตเธเธเนเธญเนเธ",
+ "autodeleteEnabled":"เธเธฒเธฃเธฅเธเธญเธฑเธเนเธเธกเธฑเธเธดเธเธนเธเนเธเธดเธเนเธเนเธเธฒเธเนเธเธซเนเธญเธเธเธดเธเธเนเธญเธเธตเน\nเธกเธฑเธเธเธฐเธเธนเธเธฅเธเนเธกเธทเนเธญเนเธกเนเนเธเนเนเธเนเธเธฒเธเนเธเธดเธ `{0} เธงเธฑเธ`",
+ "autodeleteDisabled":"เธเธฒเธฃเธฅเธเธญเธฑเธเนเธเธกเธฑเธเธดเธเธนเธเธเธดเธเนเธเนเธเธฒเธเนเธเธซเนเธญเธเธเธดเธเธเนเธญเธเธตเน\nเธกเธฑเธเธเธฐเนเธกเนเธเธนเธเธฅเธเธญเธฑเธเนเธเธกเธฑเธเธดเธญเธตเธเธเนเธญเนเธ",
+
+ "ticketMessageLimit":"เธเธธเธเธชเธฒเธกเธฒเธฃเธเธชเธฃเนเธฒเธเธซเนเธญเธเธเธดเธเธเนเธญเนเธเนเนเธเธตเธขเธ {0} เธซเนเธญเธเธเนเธญเธเธฃเธฑเนเธ",
+ "ticketMessageAutoclose":"เธซเนเธญเธเธเธดเธเธเนเธญเธเธตเนเธเธฐเธเธนเธเธเธดเธเธญเธฑเธเนเธเธกเธฑเธเธดเนเธกเธทเนเธญเนเธกเนเนเธเนเนเธเนเธเธฒเธเนเธเธดเธ {0} เธเธฑเนเธงเนเธกเธ",
+ "ticketMessageAutodelete":"เธซเนเธญเธเธเธดเธเธเนเธญเธเธตเนเธเธฐเธเธนเธเธฅเธเธญเธฑเธเนเธเธกเธฑเธเธดเนเธกเธทเนเธญเนเธกเนเนเธเนเนเธเนเธเธฒเธเนเธเธดเธ {0} เธงเธฑเธ",
+ "panelReady":"เนเธเธเธเธงเธเธเธธเธกเธเธฃเนเธญเธกเนเธเนเธเธฒเธเนเธเธเนเธญเธเธงเธฒเธกเธเธฑเธเนเธ\nเธเธญเธเธเธตเนเธชเธฒเธกเธฒเธฃเธเธฅเธเธเนเธญเธเธงเธฒเธกเธเธตเนเนเธเนเนเธฅเนเธง",
+
+ "topicSet":"เธซเธฑเธงเธเนเธญเธเธญเธเธเนเธญเธเธเธนเธเนเธเธฅเธตเนเธขเธเนเธเธข {0} เนเธฃเธตเธขเธเธฃเนเธญเธขเนเธฅเนเธง",
+ "prioritySet":"เธฅเธณเธเธฑเธเธเธงเธฒเธกเธชเธณเธเธฑเธเธเธญเธเธซเนเธญเธเธเธดเธเธเนเธญเธเธนเธเนเธเธฅเธตเนเธขเธเนเธเนเธ {0} เนเธเธข {1} เนเธฃเธตเธขเธเธฃเนเธญเธขเนเธฅเนเธง",
+ "priorityGet":"เธฅเธณเธเธฑเธเธเธงเธฒเธกเธชเธณเธเธฑเธเธเธฑเธเธเธธเธเธฑเธเธเธญเธเธซเนเธญเธเธเธดเธเธเนเธญเธเธทเธญ {0}",
+ "transfer":"เธเธงเธฒเธกเนเธเนเธเนเธเนเธฒเธเธญเธเธซเนเธญเธเธเธดเธเธเนเธญเธเธนเธเนเธญเธเธเธฒเธ {0} เนเธเธขเธฑเธ {1} เนเธเธข {2} เนเธฃเธตเธขเธเธฃเนเธญเธขเนเธฅเนเธง"
},
"modal":{
- "closePlaceholder": "เธเธณเนเธกเธเธธเธเธเธถเธเธเธดเธเธซเนเธญเธเธเธดเธเธเนเธญเธเธตเน?",
- "deletePlaceholder": "เธเธณเนเธกเธเธธเธเธเธถเธเธฅเธเธซเนเธญเธเธเธดเธเธเนเธญเธเธตเน?",
- "reopenPlaceholder": "เธเธณเนเธกเธเธธเธเธเธถเธเนเธเธดเธเธซเนเธญเธเธเธดเธเธเนเธญเธเธตเนเนเธซเธกเนเธญเธตเธเธฃเธญเธ?",
- "claimPlaceholder": "เธเธณเนเธกเธเธธเธเธเธถเธเธฃเธฑเธเธซเนเธญเธเธเธดเธเธเนเธญเธเธตเน?",
- "unclaimPlaceholder": "เธเธณเนเธกเธเธธเธเธเธถเธเธขเธเนเธฅเธดเธเธเธฒเธฃเธฃเธฑเธเธซเนเธญเธเธเธดเธเธเนเธญเธเธตเน?",
- "pinPlaceholder": "เธเธณเนเธกเธเธธเธเธเธถเธเธเธฑเธเธซเธกเธธเธเธซเนเธญเธเธเธดเธเธเนเธญเธเธตเน?",
- "unpinPlaceholder": "เธเธณเนเธกเธเธธเธเธเธถเธเธขเธเนเธฅเธดเธเธเธฒเธฃเธเธฑเธเธซเธกเธธเธเธซเนเธญเธเธเธดเธเธเนเธญเธเธตเน?"
+ "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} เธเธนเธเธฅเธเธญเธญเธเธเธฒเธเธซเนเธญเธเธเธดเธเธเนเธญเธเธญเธเธเธธเธเนเธเนเธเธดเธฃเนเธเนเธงเธญเธฃเนเธเธญเธเนเธฃเธฒ",
+ "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}"
+ "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":"เธเธเธเธฒเธเธเธญเธเธเธธเธเนเธเนเธเธดเธฃเนเธเนเธงเธญเธฃเนเธเธญเธเนเธฃเธฒเนเธเนเธฃเธฑเธเธเธฒเธฃเธญเธฑเธเนเธเธเนเธฅเนเธง"
}
},
"transcripts":{
"success":{
- "visit": "เนเธเนเธฒเธเธนเธเธฑเธงเนเธเนเธเธเธฃเธฐเธงเธฑเธเธดเธเนเธญเธเธงเธฒเธก",
- "ready": "เธเธฑเธงเนเธเนเธเธเธฃเธฐเธงเธฑเธเธดเธเนเธญเธเธงเธฒเธกเธเธนเธเธชเธฃเนเธฒเธเนเธฅเนเธง",
- "textFileDescription": "เธเธตเนเธเธทเธญเธเธฑเธงเนเธเนเธเธเธฃเธฐเธงเธฑเธเธดเธเนเธญเธเธงเธฒเธกเนเธเธฃเธนเธเนเธเธเธเนเธญเธเธงเธฒเธกเธเธญเธเธซเนเธญเธเธเธดเธเธเนเธญเธเธตเนเธเธนเธเธฅเธ",
- "htmlProgress": "เธเธฃเธธเธเธฒเธฃเธญเธชเธฑเธเธเธฃเธนเน เธเธเธฐเธเธตเนเธเธฑเธงเนเธเนเธเธเธฃเธฐเธงเธฑเธเธดเธเนเธญเธเธงเธฒเธกเนเธเธฃเธนเธเนเธเธ HTML เธเธณเธฅเธฑเธเธเธนเธเธเธฃเธฐเธกเธงเธฅเธเธฅ...",
-
- "createdChannel": "เธเธฑเธงเนเธเนเธเธเธฃเธฐเธงเธฑเธเธดเธเนเธญเธเธงเธฒเธกเนเธซเธกเน {0} เธเธนเธเธชเธฃเนเธฒเธเธเธถเนเธเนเธเนเธเธดเธฃเนเธเนเธงเธญเธฃเน",
- "createdCreator": "เธเธฑเธงเนเธเนเธเธเธฃเธฐเธงเธฑเธเธดเธเนเธญเธเธงเธฒเธกเนเธซเธกเน {0} เธเธนเธเธชเธฃเนเธฒเธเธเธถเนเธเธชเธณเธซเธฃเธฑเธเธซเธเธถเนเธเนเธเธซเนเธญเธเธเธดเธเธเนเธญเธเธญเธเธเธธเธ",
- "createdParticipant": "เธเธฑเธงเนเธเนเธเธเธฃเธฐเธงเธฑเธเธดเธเนเธญเธเธงเธฒเธกเนเธซเธกเน {0} เธเธนเธเธชเธฃเนเธฒเธเธเธถเนเธเนเธเธซเธเธถเนเธเนเธเธซเนเธญเธเธเธดเธเธเนเธญเธเธตเนเธเธธเธเนเธเนเนเธเนเธฒเธฃเนเธงเธก",
- "createdActiveAdmin": "เธเธฑเธงเนเธเนเธเธเธฃเธฐเธงเธฑเธเธดเธเนเธญเธเธงเธฒเธกเนเธซเธกเน {0} เธเธนเธเธชเธฃเนเธฒเธเธเธถเนเธเนเธเธซเธเธถเนเธเนเธเธซเนเธญเธเธเธดเธเธเนเธญเธเธตเนเธเธธเธเนเธเนเนเธเนเธฒเธฃเนเธงเธกเนเธเธเธฒเธเธฐเธเธนเนเธเธนเนเธฅ",
- "createdEveryAdmin": "เธเธฑเธงเนเธเนเธเธเธฃเธฐเธงเธฑเธเธดเธเนเธญเธเธงเธฒเธกเนเธซเธกเน {0} เธเธนเธเธชเธฃเนเธฒเธเธเธถเนเธเนเธเธซเธเธถเนเธเนเธเธซเนเธญเธเธเธดเธเธเนเธญเธเธตเนเธเธธเธเนเธเนเธเธเธนเนเธเธนเนเธฅ",
- "createdOther": "เธเธฑเธงเนเธเนเธเธเธฃเธฐเธงเธฑเธเธดเธเนเธญเธเธงเธฒเธกเนเธซเธกเน {0} เธเธนเธเธชเธฃเนเธฒเธเธเธถเนเธ"
+ "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เธซเนเธญเธเธเธดเธเธเนเธญเธเธตเนเธเธฐเนเธกเนเธเธนเธเธฅเธเธเธเธเธงเนเธฒเธเธธเธเธเธฐเธเธฅเธดเธเธเธธเนเธกเนเธเธเธธเนเธกเธซเธเธถเนเธ"
+ "retry":"เธฅเธญเธเธญเธตเธเธเธฃเธฑเนเธ",
+ "continue":"เธฅเธเนเธเธขเนเธกเนเธชเธฃเนเธฒเธเธเธฑเธงเนเธเนเธเธเธฃเธฐเธงเธฑเธเธดเธเนเธญเธเธงเธฒเธก",
+ "backup":"เธชเธฃเนเธฒเธเธชเธณเธฃเธญเธเธเธฑเธงเนเธเนเธเธเธฃเธฐเธงเธฑเธเธดเธเนเธญเธเธงเธฒเธก",
+ "error":"เนเธเธดเธเธเนเธญเธเธดเธเธเธฅเธฒเธเธเธเธฐเธเธขเธฒเธขเธฒเธกเธชเธฃเนเธฒเธเธเธฑเธงเนเธเนเธเธเธฃเธฐเธงเธฑเธเธดเธเนเธญเธเธงเธฒเธก\nเธเธธเธเธเนเธญเธเธเธฒเธฃเธเธณเธญเธขเนเธฒเธเนเธฃเธเนเธญเนเธ?\n\nเธซเนเธญเธเธเธดเธเธเนเธญเธเธตเนเธเธฐเนเธกเนเธเธนเธเธฅเธเธเธเธเธงเนเธฒเธเธธเธเธเธฐเธเธฅเธดเธเธเธธเนเธกเนเธเธเธธเนเธกเธซเธเธถเนเธ",
+ "title":"เธเนเธญเธเธดเธเธเธฅเธฒเธเนเธเธเธฒเธฃเธเธฑเธเธเธถเธเธเธเธชเธเธเธเธฒ"
+ },
+ "text":{
+ "messagesTitle":"เธเนเธญเธเธงเธฒเธก",
+ "embedTitle":"เธเนเธญเธเธงเธฒเธกเนเธญเนเธกเนเธเธ",
+ "fileTitle":"เนเธเธฅเน",
+ "fieldsTitle":"เธซเธฑเธงเธเนเธญเธขเนเธญเธข",
+ "reactionsTitle":"เธฃเธตเนเธญเธเธเธฑเนเธ",
+ "statsTitle":"เธชเธเธดเธเธด",
+ "emptyContent":"<เนเธเธทเนเธญเธซเธฒเธงเนเธฒเธเนเธเธฅเนเธฒ>",
+ "noTitle":"<เนเธกเนเธกเธตเธซเธฑเธงเธเนเธญ>",
+ "noDesc":"<เนเธกเนเธกเธตเธเธณเธญเธเธดเธเธฒเธข>"
}
},
"errors":{
"titles":{
- "internalError": "เธเนเธญเธเธดเธเธเธฅเธฒเธเธ เธฒเธขเนเธ",
- "optionMissing": "เธเธฒเธเธเธฑเธงเนเธฅเธทเธญเธเธเธณเธชเธฑเนเธ",
- "optionInvalid": "เธเธฑเธงเนเธฅเธทเธญเธเธเธณเธชเธฑเนเธเนเธกเนเธเธนเธเธเนเธญเธ",
- "unknownCommand": "เธเธณเธชเธฑเนเธเธเธตเนเนเธกเนเธฃเธนเนเธเธฑเธ",
- "noPermissions": "เนเธกเนเธกเธตเธชเธดเธเธเธดเน",
- "unknownTicket": "เธซเนเธญเธเธเธดเธเธเนเธญเธเธตเนเนเธกเนเธฃเธนเนเธเธฑเธ",
- "deprecatedTicket": "เธซเนเธญเธเธเธดเธเธเนเธญเธเธตเนเนเธกเนเธฃเธญเธเธฃเธฑเธเนเธฅเนเธง",
- "unknownOption": "เธเธฑเธงเนเธฅเธทเธญเธเธเธตเนเนเธกเนเธฃเธนเนเธเธฑเธ",
- "unknownPanel": "เนเธเธเธเธงเธเธเธธเธกเธเธตเนเนเธกเนเธฃเธนเนเธเธฑเธ",
- "notInGuild": "เนเธกเนเนเธเนเธญเธขเธนเนเนเธเนเธเธดเธฃเนเธเนเธงเธญเธฃเน",
- "channelRename": "เนเธกเนเธชเธฒเธกเธฒเธฃเธเนเธเธฅเธตเนเธขเธเธเธทเนเธญเธเนเธญเธเนเธเน",
- "busy": "เธซเนเธญเธเธเธดเธเธเนเธญเธขเธธเนเธเธญเธขเธนเน"
+ "internalError":"เธเนเธญเธเธดเธเธเธฅเธฒเธเธ เธฒเธขเนเธ",
+ "optionMissing":"เธเธฒเธเธเธฑเธงเนเธฅเธทเธญเธเธเธณเธชเธฑเนเธ",
+ "optionInvalid":"เธเธฑเธงเนเธฅเธทเธญเธเธเธณเธชเธฑเนเธเนเธกเนเธเธนเธเธเนเธญเธ",
+ "unknownCommand":"เธเธณเธชเธฑเนเธเธเธตเนเนเธกเนเธฃเธนเนเธเธฑเธ",
+ "noPermissions":"เนเธกเนเธกเธตเธชเธดเธเธเธดเน",
+ "unknownTicket":"เธซเนเธญเธเธเธดเธเธเนเธญเธเธตเนเนเธกเนเธฃเธนเนเธเธฑเธ",
+ "deprecatedTicket":"เธซเนเธญเธเธเธดเธเธเนเธญเธเธตเนเนเธกเนเธฃเธญเธเธฃเธฑเธเนเธฅเนเธง",
+ "unknownOption":"เธเธฑเธงเนเธฅเธทเธญเธเธเธตเนเนเธกเนเธฃเธนเนเธเธฑเธ",
+ "unknownPanel":"เนเธเธเธเธงเธเธเธธเธกเธเธตเนเนเธกเนเธฃเธนเนเธเธฑเธ",
+ "notInGuild":"เนเธกเนเนเธเนเธญเธขเธนเนเนเธเนเธเธดเธฃเนเธเนเธงเธญเธฃเน",
+ "channelRename":"เนเธกเนเธชเธฒเธกเธฒเธฃเธเนเธเธฅเธตเนเธขเธเธเธทเนเธญเธเนเธญเธเนเธเน",
+ "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 เธเธฒเธฃเนเธเธฅเธตเนเธขเธเธเธทเนเธญเธเนเธญเธเนเธกเนเธชเธฒเธกเธฒเธฃเธเธเธณเนเธเนเนเธเธเธเธฐเธเธตเน เธเนเธญเธเธเธฐเธเธนเธเนเธเธฅเธตเนเธขเธเธเธทเนเธญเนเธเธขเธญเธฑเธเนเธเธกเธฑเธเธดเนเธเนเธงเธฅเธฒ 10 เธเธฒเธเธตเธซเธฒเธเธเธญเธเนเธกเนเธเธนเธเธฃเธตเธเธนเธ",
- "channelRenameSource": "เนเธซเธฅเนเธเธเธตเนเธกเธฒเธเธญเธเธเนเธญเธเธดเธเธเธฅเธฒเธเธเธตเนเธเธทเธญ: {0}",
- "busy": "เนเธกเนเธชเธฒเธกเธฒเธฃเธเนเธเน {0} เธเธตเนเนเธเน\nเธซเนเธญเธเธเธดเธเธเนเธญเธเธเธณเธฅเธฑเธเธเธนเธเธเธฃเธฐเธกเธงเธฅเธเธฅเนเธเธขเธเธญเธ\n\nเธเธฃเธธเธเธฒเธฅเธญเธเนเธซเธกเนเนเธเธญเธตเธเนเธกเนเธเธตเนเธงเธดเธเธฒเธเธต"
+ "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 เธเธฒเธฃเนเธเธฅเธตเนเธขเธเธเธทเนเธญเธเนเธญเธเนเธกเนเธชเธฒเธกเธฒเธฃเธเธเธณเนเธเนเนเธเธเธเธฐเธเธตเน เธเนเธญเธเธเธฐเธเธนเธเนเธเธฅเธตเนเธขเธเธเธทเนเธญเนเธเธขเธญเธฑเธเนเธเธกเธฑเธเธดเนเธเนเธงเธฅเธฒ 10 เธเธฒเธเธตเธซเธฒเธเธเธญเธเนเธกเนเธเธนเธเธฃเธตเธเธนเธ",
+ "channelRenameSource":"เนเธซเธฅเนเธเธเธตเนเธกเธฒเธเธญเธเธเนเธญเธเธดเธเธเธฅเธฒเธเธเธตเนเธเธทเธญ: {0}",
+ "busy":"เนเธกเนเธชเธฒเธกเธฒเธฃเธเนเธเน {0} เธเธตเนเนเธเน\nเธซเนเธญเธเธเธดเธเธเนเธญเธเธเธณเธฅเธฑเธเธเธนเธเธเธฃเธฐเธกเธงเธฅเธเธฅเนเธเธขเธเธญเธ\n\nเธเธฃเธธเธเธฒเธฅเธญเธเนเธซเธกเนเนเธเธญเธตเธเนเธกเนเธเธตเนเธงเธดเธเธฒเธเธต",
+ "closeBeforeMessage":"เนเธกเนเธชเธฒเธกเธฒเธฃเธเธเธดเธ/เธฅเธเธซเนเธญเธเธเธดเธเธเนเธญเธเธตเนเนเธเนเธเนเธญเธเธเธตเนเธเธนเนเนเธเนเธเธฐเธชเนเธเธเนเธญเธเธงเธฒเธก",
+ "closeBeforeAdminMessage":"เนเธกเนเธชเธฒเธกเธฒเธฃเธเธเธดเธ/เธฅเธเธซเนเธญเธเธเธดเธเธเนเธญเธเธตเนเนเธเนเธเนเธญเธเธเธตเนเธเธนเนเธเธนเนเธฅเธซเนเธญเธเธเธดเธเธเนเธญเธซเธฃเธทเธญเธชเธกเธฒเธเธดเธเธเนเธฒเธขเธชเธเธฑเธเธชเธเธธเธเธเธฐเธชเนเธเธเนเธญเธเธงเธฒเธก",
+ "unableToCreateTicket":"เธเธธเธเนเธกเนเธชเธฒเธกเธฒเธฃเธเธชเธฃเนเธฒเธเธซเนเธญเธเธเธดเธเธเนเธญเนเธเน"
},
"optionInvalidReasons":{
- "stringRegex": "เธเนเธฒเธเธตเนเธฃเธฐเธเธธเนเธกเนเธเธฃเธเธเธฑเธเธฃเธนเธเนเธเธ",
- "stringMinLength": "เธเนเธฒเธเธฐเธเนเธญเธเธกเธตเธเธงเธฒเธกเธขเธฒเธงเธญเธขเนเธฒเธเธเนเธญเธข {0} เธเธฑเธงเธญเธฑเธเธฉเธฃ",
- "stringMaxLength": "เธเนเธฒเธเธฐเธเนเธญเธเธกเธตเธเธงเธฒเธกเธขเธฒเธงเนเธกเนเนเธเธดเธ {0} เธเธฑเธงเธญเธฑเธเธฉเธฃ",
- "numberInvalid": "เธซเธกเธฒเธขเนเธฅเธเนเธกเนเธเธนเธเธเนเธญเธ",
- "numberMin": "เธซเธกเธฒเธขเนเธฅเธเธเธฐเธเนเธญเธเธกเธตเธเนเธฒเธกเธฒเธเธเธงเนเธฒเธซเธฃเธทเธญเนเธเนเธฒเธเธฑเธ {0}",
- "numberMax": "เธซเธกเธฒเธขเนเธฅเธเธเธฐเธเนเธญเธเธกเธตเธเนเธฒเธเนเธญเธขเธเธงเนเธฒเธซเธฃเธทเธญเนเธเนเธฒเธเธฑเธ {0}",
- "numberDecimal": "เธซเธกเธฒเธขเนเธฅเธเนเธกเนเธชเธฒเธกเธฒเธฃเธเนเธเนเธเธเธจเธเธดเธขเธกเนเธเน",
- "numberNegative": "เธซเธกเธฒเธขเนเธฅเธเนเธกเนเธชเธฒเธกเธฒเธฃเธเนเธเนเธเธฅเธเนเธเน",
- "numberPositive": "เธซเธกเธฒเธขเนเธฅเธเนเธกเนเธชเธฒเธกเธฒเธฃเธเนเธเนเธเธเธงเธเนเธเน",
- "numberZero": "เธซเธกเธฒเธขเนเธฅเธเนเธกเนเธชเธฒเธกเธฒเธฃเธเนเธเนเธเธจเธนเธเธขเนเนเธเน",
- "channelNotFound": "เนเธกเนเธชเธฒเธกเธฒเธฃเธเธซเธฒเธเนเธญเธเนเธเน",
- "userNotFound": "เนเธกเนเธชเธฒเธกเธฒเธฃเธเธซเธฒเธเธนเนเนเธเนเนเธเน",
- "roleNotFound": "เนเธกเนเธชเธฒเธกเธฒเธฃเธเธซเธฒเธเธเธเธฒเธเนเธเน",
- "memberNotFound": "เนเธกเนเธชเธฒเธกเธฒเธฃเธเธซเธฒเธเธนเนเนเธเนเนเธเน",
- "mentionableNotFound": "เนเธกเนเธชเธฒเธกเธฒเธฃเธเธซเธฒเธเธนเนเนเธเนเธซเธฃเธทเธญเธเธเธเธฒเธเนเธเน",
- "channelType": "เธเธฃเธฐเนเธ เธเธเนเธญเธเนเธกเนเธเธนเธเธเนเธญเธ",
- "notInGuild": "เธเธฑเธงเนเธฅเธทเธญเธเธเธตเนเธเนเธญเธเนเธซเนเธเธธเธเธญเธขเธนเนเนเธเนเธเธดเธฃเนเธเนเธงเธญเธฃเน"
+ "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`"
+ "developer":"เธเธธเธเธเนเธญเธเนเธเนเธเธเธฑเธเธเธฑเธเธเธฒเธเธญเธเธเธญเธเธเธตเน",
+ "owner":"เธเธธเธเธเนเธญเธเนเธเนเธเนเธเนเธฒเธเธญเธเนเธเธดเธฃเนเธเนเธงเธญเธฃเน",
+ "admin":"เธเธธเธเธเนเธญเธเนเธเนเธเธเธนเนเธเธนเนเธฅเนเธเธดเธฃเนเธเนเธงเธญเธฃเน",
+ "moderator":"เธเธธเธเธเนเธญเธเนเธเนเธเธเธนเนเธเธนเนเธฅ",
+ "support":"เธเธธเธเธเนเธญเธเนเธเนเธเธชเธกเธฒเธเธดเธเธเธตเธก",
+ "member":"เธเธธเธเธเนเธญเธเนเธเนเธเธชเธกเธฒเธเธดเธ",
+ "discord-administrator":"เธเธธเธเธเนเธญเธเธกเธตเธชเธดเธเธเธดเน `ADMINISTRATOR`"
},
"actionInvalid":{
- "close": "เธซเนเธญเธเธเธดเธเธเนเธญเธเธตเนเธเธนเธเธเธดเธเนเธฅเนเธง",
- "reopen": "เธซเนเธญเธเธเธดเธเธเนเธญเธเธตเนเธขเธฑเธเนเธกเนเธเธนเธเธเธดเธ",
- "claim": "เธซเนเธญเธเธเธดเธเธเนเธญเธเธตเนเธฃเธฑเธเนเธฅเนเธง",
- "unclaim": "เธซเนเธญเธเธเธดเธเธเนเธญเธเธตเนเธขเธฑเธเนเธกเนเนเธเนเธเธนเธเธฃเธฑเธ",
- "pin": "เธซเนเธญเธเธเธดเธเธเนเธญเธเธตเนเธเธนเธเธเธฑเธเธซเธกเธธเธเนเธฅเนเธง",
- "unpin": "เธซเนเธญเธเธเธดเธเธเนเธญเธเธตเนเธขเธฑเธเนเธกเนเนเธเนเธเธนเธเธเธฑเธเธซเธกเธธเธ",
- "add": "เธเธนเนเนเธเนเธเธตเนเธชเธฒเธกเธฒเธฃเธเนเธเนเธฒเธเธถเธเธซเนเธญเธเธเธดเธเธเนเธญเนเธเนเนเธฅเนเธง",
- "remove": "เนเธกเนเธชเธฒเธกเธฒเธฃเธเธฅเธเธเธนเนเนเธเนเธเธตเนเธญเธญเธเธเธฒเธเธซเนเธญเธเธเธดเธเธเนเธญเนเธเน"
+ "close":"เธซเนเธญเธเธเธดเธเธเนเธญเธเธตเนเธเธนเธเธเธดเธเนเธฅเนเธง",
+ "reopen":"เธซเนเธญเธเธเธดเธเธเนเธญเธเธตเนเธขเธฑเธเนเธกเนเธเธนเธเธเธดเธ",
+ "claim":"เธซเนเธญเธเธเธดเธเธเนเธญเธเธตเนเธฃเธฑเธเนเธฅเนเธง",
+ "unclaim":"เธซเนเธญเธเธเธดเธเธเนเธญเธเธตเนเธขเธฑเธเนเธกเนเนเธเนเธเธนเธเธฃเธฑเธ",
+ "pin":"เธซเนเธญเธเธเธดเธเธเนเธญเธเธตเนเธเธนเธเธเธฑเธเธซเธกเธธเธเนเธฅเนเธง",
+ "unpin":"เธซเนเธญเธเธเธดเธเธเนเธญเธเธตเนเธขเธฑเธเนเธกเนเนเธเนเธเธนเธเธเธฑเธเธซเธกเธธเธ",
+ "add":"เธเธนเนเนเธเนเธเธตเนเธชเธฒเธกเธฒเธฃเธเนเธเนเธฒเธเธถเธเธซเนเธญเธเธเธดเธเธเนเธญเนเธเนเนเธฅเนเธง",
+ "remove":"เนเธกเนเธชเธฒเธกเธฒเธฃเธเธฅเธเธเธนเนเนเธเนเธเธตเนเธญเธญเธเธเธฒเธเธซเนเธญเธเธเธดเธเธเนเธญเนเธเน"
}
},
"params":{
"uppercase":{
- "ticket": "เธซเนเธญเธเธเธดเธเธเนเธญ",
- "tickets": "เธซเนเธญเธเธเธดเธเธเนเธญ",
- "reason": "เนเธซเธเธธเธเธฅ",
- "creator": "เธเธนเนเธชเธฃเนเธฒเธ",
- "remaining": "เนเธงเธฅเธฒเธเธตเนเนเธซเธฅเธทเธญ",
- "added": "เนเธเธดเนเธกเนเธฅเนเธง",
- "removed": "เธฅเธเนเธฅเนเธง",
- "filter": "เธเธฑเธงเธเธฃเธญเธ",
- "claimedBy": "เธฃเธฑเธเนเธเธข {0}",
- "method": "เธงเธดเธเธตเธเธฒเธฃ",
- "type": "เธเธฃเธฐเนเธ เธ",
- "blacklisted": "เธเธนเธเนเธชเนเนเธเธฃเธฒเธขเธเธทเนเธญเธเธณ",
- "panel": "เนเธเธเธเธงเธเธเธธเธก",
- "command": "เธเธณเธชเธฑเนเธ",
- "system": "เธฃเธฐเธเธ",
- "true": "เธเธฃเธดเธ",
- "false": "เนเธเนเธ",
- "syntax": "เนเธงเธขเธฒเธเธฃเธเน",
- "originalName": "เธเธทเนเธญเนเธเธดเธก",
- "newName": "เธเธทเนเธญเนเธซเธกเน",
- "until": "เธเธเธเธถเธ",
- "validOptions": "เธเธฑเธงเนเธฅเธทเธญเธเธเธตเนเธเธนเธเธเนเธญเธ",
- "validPanels": "เนเธเธเธเธงเธเธเธธเธกเธเธตเนเธเธนเธเธเนเธญเธ",
- "autoclose": "เธเธดเธเธญเธฑเธเนเธเธกเธฑเธเธด",
- "autodelete": "เธฅเธเธญเธฑเธเนเธเธกเธฑเธเธด",
- "startupDate": "เธงเธฑเธเธเธตเนเนเธฃเธดเนเธกเธเนเธ",
- "version": "เนเธงเธญเธฃเนเธเธฑเธ",
- "name": "เธเธทเนเธญ",
- "role": "เธเธเธเธฒเธ",
- "status": "เธชเธเธฒเธเธฐ",
- "claimed": "เธฃเธฑเธเนเธฅเนเธง",
- "pinned": "เธเธนเธเธเธฑเธเธซเธกเธธเธ",
- "creationDate": "เธงเธฑเธเธเธตเนเธชเธฃเนเธฒเธ"
+ "ticket":"เธซเนเธญเธเธเธดเธเธเนเธญ",
+ "tickets":"เธซเนเธญเธเธเธดเธเธเนเธญ",
+ "reason":"เนเธซเธเธธเธเธฅ",
+ "creator":"เธเธนเนเธชเธฃเนเธฒเธ",
+ "remaining":"เนเธงเธฅเธฒเธเธตเนเนเธซเธฅเธทเธญ",
+ "added":"เนเธเธดเนเธกเนเธฅเนเธง",
+ "removed":"เธฅเธเนเธฅเนเธง",
+ "filter":"เธเธฑเธงเธเธฃเธญเธ",
+ "method":"เธงเธดเธเธตเธเธฒเธฃ",
+ "type":"เธเธฃเธฐเนเธ เธ",
+ "blacklisted":"เธเธนเธเนเธชเนเนเธเธฃเธฒเธขเธเธทเนเธญเธเธณ",
+ "panel":"เนเธเธเธเธงเธเธเธธเธก",
+ "command":"เธเธณเธชเธฑเนเธ",
+ "system":"เธฃเธฐเธเธ",
+ "true":"เธเธฃเธดเธ",
+ "false":"เนเธเนเธ",
+ "syntax":"เนเธงเธขเธฒเธเธฃเธเน",
+ "originalName":"เธเธทเนเธญเนเธเธดเธก",
+ "newName":"เธเธทเนเธญเนเธซเธกเน",
+ "until":"เธเธเธเธถเธ",
+ "validOptions":"เธเธฑเธงเนเธฅเธทเธญเธเธเธตเนเธเธนเธเธเนเธญเธ",
+ "validPanels":"เนเธเธเธเธงเธเธเธธเธกเธเธตเนเธเธนเธเธเนเธญเธ",
+ "autoclose":"เธเธดเธเธญเธฑเธเนเธเธกเธฑเธเธด",
+ "autodelete":"เธฅเธเธญเธฑเธเนเธเธกเธฑเธเธด",
+ "startupDate":"เธงเธฑเธเธเธตเนเนเธฃเธดเนเธกเธเนเธ",
+ "version":"เนเธงเธญเธฃเนเธเธฑเธ",
+ "name":"เธเธทเนเธญ",
+ "role":"เธเธเธเธฒเธ",
+ "status":"เธชเธเธฒเธเธฐ",
+ "claimed":"เธฃเธฑเธเนเธฅเนเธง",
+ "pinned":"เธเธนเธเธเธฑเธเธซเธกเธธเธ",
+ "creationDate":"เธงเธฑเธเธเธตเนเธชเธฃเนเธฒเธ",
+
+ "noone":"เนเธกเนเธกเธตเนเธเธฃ",
+ "open":"เนเธเธดเธ",
+ "closed":"เธเธดเธ",
+ "priority":"เธฅเธณเธเธฑเธเธเธงเธฒเธกเธชเธณเธเธฑเธ",
+ "participants":"เธเธนเนเธกเธตเธชเนเธงเธเธฃเนเธงเธก",
+ "yes":"เนเธเน",
+ "no":"เนเธกเน",
+ "option":"เธเธฑเธงเนเธฅเธทเธญเธ",
+ "topic":"เธซเธฑเธงเธเนเธญ",
+ "uptime":"เนเธงเธฅเธฒเธเธณเธเธฒเธเธเธญเธเธฃเธฐเธเธ",
+ "messages":"เธเนเธญเธเธงเธฒเธก",
+ "embeds":"เธเนเธญเธเธงเธฒเธกเนเธญเนเธกเนเธเธ",
+ "files":"เธซเธฑเธงเธเนเธญเธขเนเธญเธข",
+ "components":"เธชเนเธงเธเธเธฃเธฐเธเธญเธ",
+ "cooldown":"เธเธนเธฅเธเธฒเธงเธเน",
+ "maxTickets":"เธซเนเธญเธเธเธดเธเธเนเธญเธชเธนเธเธชเธธเธ",
+ "admins":"เธเธนเนเธเธนเนเธฅเธฃเธฐเธเธ",
+ "roles":"เธเธเธเธฒเธ",
+ "size":"เธเธเธฒเธ"
},
"lowercase":{
- "text": "เธเนเธญเธเธงเธฒเธก",
- "html": "HTML",
- "command": "เธเธณเธชเธฑเนเธ",
- "modal": "เธซเธเนเธฒเธเนเธฒเธ",
- "button": "เธเธธเนเธก",
- "dropdown": "เธเธฑเธงเนเธฅเธทเธญเธ",
- "method": "เธงเธดเธเธตเธเธฒเธฃ"
+ "text":"เธเนเธญเธเธงเธฒเธก",
+ "html":"HTML",
+ "command":"เธเธณเธชเธฑเนเธ",
+ "modal":"เธซเธเนเธฒเธเนเธฒเธ",
+ "button":"เธเธธเนเธก",
+ "dropdown":"เธเธฑเธงเนเธฅเธทเธญเธ",
+ "method":"เธงเธดเธเธตเธเธฒเธฃ"
}
},
"commands":{
- "reason": "เธฃเธฐเธเธธเนเธซเธเธธเธเธฅเธเธตเนเนเธกเนเธเธฑเธเธเธฑเธ เธเธถเนเธเธเธฐเธเธฃเธฒเธเธเนเธเธเธฑเธเธเธถเธ",
- "help": "เธฃเธฑเธเธฃเธฒเธขเธเธทเนเธญเธเธณเธชเธฑเนเธเธเธฑเนเธเธซเธกเธเธเธตเนเธกเธต",
- "panel": "เธชเธฃเนเธฒเธเธเนเธญเธเธงเธฒเธกเธเธตเนเธกเธตเธเธฑเธงเนเธฅเธทเธญเธเธซเธฃเธทเธญเธเธธเนเธก (เธชเธณเธซเธฃเธฑเธเธเธฒเธฃเธชเธฃเนเธฒเธเธซเนเธญเธเธเธดเธเธเนเธญ)",
- "panelId": "เธเธฑเธงเธฃเธฐเธเธธเธเธญเธเนเธเธเธเธงเธเธเธธเธกเธเธตเนเธเธธเธเธเนเธญเธเธเธฒเธฃเธชเธฃเนเธฒเธ",
- "panelAutoUpdate": "เธเธธเธเธเนเธญเธเธเธฒเธฃเนเธซเนเนเธเธเธเธตเนเธญเธฑเธเนเธเธเธญเธฑเธเนเธเธกเธฑเธเธดเนเธกเธทเนเธญเธกเธตเธเธฒเธฃเนเธเนเนเธเธซเธฃเธทเธญเนเธกเน?",
- "ticket": "เธชเธฃเนเธฒเธเธซเนเธญเธเธเธดเธเธเนเธญเธเธฑเธเธเธต",
- "ticketId": "เธเธฑเธงเธฃเธฐเธเธธเธเธญเธเธซเนเธญเธเธเธดเธเธเนเธญเธเธตเนเธเธธเธเธเนเธญเธเธเธฒเธฃเธชเธฃเนเธฒเธ",
- "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": "เธเธฑเธงเธเธฃเธญเธเธชเธณเธซเธฃเธฑเธเธเธฒเธฃเธฅเธเธซเนเธญเธเธเธดเธเธเนเธญ",
+ "reason":"เธฃเธฐเธเธธเนเธซเธเธธเธเธฅเธเธตเนเนเธกเนเธเธฑเธเธเธฑเธ เธเธถเนเธเธเธฐเธเธฃเธฒเธเธเนเธเธเธฑเธเธเธถเธ",
+ "help":"เธฃเธฑเธเธฃเธฒเธขเธเธทเนเธญเธเธณเธชเธฑเนเธเธเธฑเนเธเธซเธกเธเธเธตเนเธกเธต",
+ "panel":"เธชเธฃเนเธฒเธเธเนเธญเธเธงเธฒเธกเธเธตเนเธกเธตเธเธฑเธงเนเธฅเธทเธญเธเธซเธฃเธทเธญเธเธธเนเธก (เธชเธณเธซเธฃเธฑเธเธเธฒเธฃเธชเธฃเนเธฒเธเธซเนเธญเธเธเธดเธเธเนเธญ)",
+ "panelId":"เธเธฑเธงเธฃเธฐเธเธธเธเธญเธเนเธเธเธเธงเธเธเธธเธกเธเธตเนเธเธธเธเธเนเธญเธเธเธฒเธฃเธชเธฃเนเธฒเธ",
+ "panelAutoUpdate":"เธเธธเธเธเนเธญเธเธเธฒเธฃเนเธซเนเนเธเธเธเธตเนเธญเธฑเธเนเธเธเธญเธฑเธเนเธเธกเธฑเธเธดเนเธกเธทเนเธญเธกเธตเธเธฒเธฃเนเธเนเนเธเธซเธฃเธทเธญเนเธกเน?",
+ "ticket":"เธชเธฃเนเธฒเธเธซเนเธญเธเธเธดเธเธเนเธญเธเธฑเธเธเธต",
+ "ticketId":"เธเธฑเธงเธฃเธฐเธเธธเธเธญเธเธซเนเธญเธเธเธดเธเธเนเธญเธเธตเนเธเธธเธเธเนเธญเธเธเธฒเธฃเธชเธฃเนเธฒเธ",
+ "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": "เธเธดเธเธญเธฑเธเนเธเธกเธฑเธเธด"
+ "all":"เธเธฑเนเธเธซเธกเธ",
+ "open":"เนเธเธดเธ",
+ "close":"เธเธดเธ",
+ "claim":"เธเธนเธเธฃเธฑเธ",
+ "unclaim":"เธขเธฑเธเนเธกเนเนเธเนเธฃเธฑเธ",
+ "pin":"เธเธนเธเธเธฑเธเธซเธกเธธเธ",
+ "unpin":"เธขเธฑเธเนเธกเนเนเธเนเธเธนเธเธเธฑเธเธซเธกเธธเธ",
+ "autoclose":"เธเธดเธเธญเธฑเธเนเธเธกเธฑเธเธด"
},
- "autoclose": "เธเธฑเธเธเธฒเธฃเธเธฒเธฃเธเธดเธเธญเธฑเธเนเธเธกเธฑเธเธดเนเธเธซเนเธญเธเธเธดเธเธเนเธญ",
- "autocloseDisable": "เธเธดเธเธเธฒเธฃเธเธดเธเธญเธฑเธเนเธเธกเธฑเธเธดเนเธเธซเนเธญเธเธเธดเธเธเนเธญเธเธตเน",
- "autocloseEnable": "เนเธเธดเธเธเธฒเธฃเธเธดเธเธญเธฑเธเนเธเธกเธฑเธเธดเนเธเธซเนเธญเธเธเธดเธเธเนเธญเธเธตเน",
- "autocloseEnableTime": "เธเธณเธเธงเธเธเธฑเนเธงเนเธกเธเธเธตเนเธซเนเธญเธเธเธดเธเธเนเธญเธเธตเนเธเนเธญเธเนเธกเนเนเธเธฅเธทเนเธญเธเนเธซเธงเนเธเธทเนเธญเธเธดเธเธกเธฑเธ",
- "autodelete": "เธเธฑเธเธเธฒเธฃเธเธฒเธฃเธฅเธเธญเธฑเธเนเธเธกเธฑเธเธดเนเธเธซเนเธญเธเธเธดเธเธเนเธญ",
- "autodeleteDisable": "เธเธดเธเธเธฒเธฃเธฅเธเธญเธฑเธเนเธเธกเธฑเธเธดเนเธเธซเนเธญเธเธเธดเธเธเนเธญเธเธตเน",
- "autodeleteEnable": "เนเธเธดเธเธเธฒเธฃเธฅเธเธญเธฑเธเนเธเธกเธฑเธเธดเนเธเธซเนเธญเธเธเธดเธเธเนเธญเธเธตเน",
- "autodeleteEnableTime": "เธเธณเธเธงเธเธงเธฑเธเธเธตเนเธซเนเธญเธเธเธดเธเธเนเธญเธเธตเนเธเนเธญเธเนเธกเนเนเธเธฅเธทเนเธญเธเนเธซเธงเนเธเธทเนเธญเธเธตเนเธเธฐเธฅเธเธกเธฑเธ"
+
+ "autoclose":"เธเธฑเธเธเธฒเธฃเธเธฒเธฃเธเธดเธเธญเธฑเธเนเธเธกเธฑเธเธดเนเธเธซเนเธญเธเธเธดเธเธเนเธญ",
+ "autocloseDisable":"เธเธดเธเธเธฒเธฃเธเธดเธเธญเธฑเธเนเธเธกเธฑเธเธดเนเธเธซเนเธญเธเธเธดเธเธเนเธญเธเธตเน",
+ "autocloseEnable":"เนเธเธดเธเธเธฒเธฃเธเธดเธเธญเธฑเธเนเธเธกเธฑเธเธดเนเธเธซเนเธญเธเธเธดเธเธเนเธญเธเธตเน",
+ "autocloseEnableTime":"เธเธณเธเธงเธเธเธฑเนเธงเนเธกเธเธเธตเนเธซเนเธญเธเธเธดเธเธเนเธญเธเธตเนเธเนเธญเธเนเธกเนเนเธเธฅเธทเนเธญเธเนเธซเธงเนเธเธทเนเธญเธเธดเธเธกเธฑเธ",
+ "autodelete":"เธเธฑเธเธเธฒเธฃเธเธฒเธฃเธฅเธเธญเธฑเธเนเธเธกเธฑเธเธดเนเธเธซเนเธญเธเธเธดเธเธเนเธญ",
+ "autodeleteDisable":"เธเธดเธเธเธฒเธฃเธฅเธเธญเธฑเธเนเธเธกเธฑเธเธดเนเธเธซเนเธญเธเธเธดเธเธเนเธญเธเธตเน",
+ "autodeleteEnable":"เนเธเธดเธเธเธฒเธฃเธฅเธเธญเธฑเธเนเธเธกเธฑเธเธดเนเธเธซเนเธญเธเธเธดเธเธเนเธญเธเธตเน",
+ "autodeleteEnableTime":"เธเธณเธเธงเธเธงเธฑเธเธเธตเนเธซเนเธญเธเธเธดเธเธเนเธญเธเธตเนเธเนเธญเธเนเธกเนเนเธเธฅเธทเนเธญเธเนเธซเธงเนเธเธทเนเธญเธเธตเนเธเธฐเธฅเธเธกเธฑเธ",
+
+ "topic":"เธเธฑเธเธเธฒเธฃเธซเธฑเธงเธเนเธญเธเธญเธเธเนเธญเธเธซเนเธญเธเธเธดเธเธเนเธญ",
+ "topicSet":"เธเธฑเนเธเธเนเธฒเธซเธฑเธงเธเนเธญเธเธญเธเธเนเธญเธเธซเนเธญเธเธเธดเธเธเนเธญ",
+ "topicValue":"เธซเธฑเธงเธเนเธญเนเธซเธกเนเธเธญเธเธเนเธญเธ",
+ "topicList":"เธฃเธฒเธขเธเธฒเธฃเธซเนเธญเธเธเธดเธเธเนเธญเธเธฑเนเธเธซเธกเธเธเธฃเนเธญเธกเธซเธฑเธงเธเนเธญเนเธฅเธฐเธชเธเธดเธเธด",
+ "priority":"เธเธฑเธเธเธฒเธฃเธฅเธณเธเธฑเธเธเธงเธฒเธกเธชเธณเธเธฑเธเธเธญเธเธซเนเธญเธเธเธดเธเธเนเธญ",
+ "prioritySet":"เธเธฑเนเธเธเนเธฒเธฅเธณเธเธฑเธเธเธงเธฒเธกเธชเธณเธเธฑเธเธเธญเธเธซเนเธญเธเธเธดเธเธเนเธญ",
+ "priorityValue":"เธฅเธณเธเธฑเธเธเธงเธฒเธกเธชเธณเธเธฑเธเธเธญเธเธเนเธญเธ",
+ "priorityGet":"เธฃเธฑเธเธฅเธณเธเธฑเธเธเธงเธฒเธกเธชเธณเธเธฑเธเธเธญเธเธซเนเธญเธเธเธดเธเธเนเธญ",
+ "priorityList":"เธฃเธฒเธขเธเธฒเธฃเธซเนเธญเธเธเธดเธเธเนเธญเธเธฑเนเธเธซเธกเธเธเธฃเนเธญเธกเธชเธเธฒเธเธฐเธฅเธณเธเธฑเธเธเธงเธฒเธกเธชเธณเธเธฑเธ",
+ "transfer":"เนเธญเธเธเธงเธฒเธกเนเธเนเธเนเธเนเธฒเธเธญเธเธซเนเธญเธเธเธดเธเธเนเธญเธเธฒเธเธเธนเนเนเธเนเธซเธเธถเนเธเนเธเธขเธฑเธเธญเธตเธเธเธเธซเธเธถเนเธ",
+ "transferUser":"เธเธนเนเนเธเนเธเธตเนเธเธฐเนเธญเธเนเธ"
},
"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": "เนเธเธดเธเธเธฒเธฃเธฅเธเธญเธฑเธเนเธเธกเธฑเธเธดเนเธเธซเนเธญเธเธเธดเธเธเนเธญเธเธตเน"
+ "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": "เธเธนเนเธกเธตเธชเนเธงเธเธฃเนเธงเธก"
+ "global":"เธชเธเธดเธเธดเธฃเธงเธก",
+ "system":"เธชเธเธดเธเธดเนเธเธตเนเธขเธงเธเธฑเธเธฃเธฐเธเธ",
+ "user":"เธชเธเธดเธเธดเนเธเธตเนเธขเธงเธเธฑเธเธเธนเนเนเธเน",
+ "ticket":"เธชเธเธดเธเธดเธซเนเธญเธเธเธดเธเธเนเธญ",
+ "participants":"เธเธนเนเธกเธตเธชเนเธงเธเธฃเนเธงเธก",
+ "messages":"เธเนเธญเธเธงเธฒเธก"
},
"properties":{
- "ticketsCreated": "เธซเนเธญเธเธเธดเธเธเนเธญเธเธตเนเธชเธฃเนเธฒเธ",
- "ticketsClosed": "เธซเนเธญเธเธเธดเธเธเนเธญเธเธตเนเธเธดเธ",
- "ticketsDeleted": "เธซเนเธญเธเธเธดเธเธเนเธญเธเธตเนเธฅเธ",
- "ticketsReopened": "เธซเนเธญเธเธเธดเธเธเนเธญเธเธตเนเนเธเธดเธเนเธซเธกเน",
- "ticketsAutoclosed": "เธซเนเธญเธเธเธดเธเธเนเธญเธเธตเนเธเธดเธเธญเธฑเธเนเธเธกเธฑเธเธด",
- "ticketsClaimed": "เธซเนเธญเธเธเธดเธเธเนเธญเธเธตเนเธเธนเธเธฃเธฑเธ",
- "ticketsPinned": "เธซเนเธญเธเธเธดเธเธเนเธญเธเธตเนเธเธนเธเธเธฑเธเธซเธกเธธเธ",
- "ticketsMoved": "เธซเนเธญเธเธเธดเธเธเนเธญเธเธตเนเธขเนเธฒเธข",
- "usersBlacklisted": "เธเธนเนเนเธเนเธเธตเนเธเธนเธเนเธชเนเนเธเธฃเธฒเธขเธเธทเนเธญเธเธณ",
- "transcriptsCreated": "เธเธฑเธเธเธถเธเธเธฒเธฃเธชเธเธเธเธฒเธเธตเนเธชเธฃเนเธฒเธ"
+ "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":"เนเธฅเธทเธญเธเธเธฑเธงเนเธฅเธทเธญเธเธเธญเธเธเธธเธ"
+ },
+ "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 ec60dda..c327bc8 100644
--- a/languages/turkish.json
+++ b/languages/turkish.json
@@ -1,6 +1,6 @@
{
"_TRANSLATION": {
- "otversion": "v4.0.7",
+ "otversion": "v4.1.0",
"translators": ["palestinian"],
"lastedited": "26/11/2024",
"language": "Turkish",
diff --git a/languages/ukrainian.json b/languages/ukrainian.json
index 2f7d805..9e7831d 100644
--- a/languages/ukrainian.json
+++ b/languages/ukrainian.json
@@ -1,6 +1,6 @@
{
"_TRANSLATION":{
- "otversion":"v4.0.7",
+ "otversion":"v4.1.0",
"translators":["Anderskiy"],
"lastedited":"31/08/2024",
"language":"Ukrainian",
diff --git a/languages/vietnamese.json b/languages/vietnamese.json
index af2b358..6f72614 100644
--- a/languages/vietnamese.json
+++ b/languages/vietnamese.json
@@ -1,6 +1,6 @@
{
"_TRANSLATION":{
- "otversion":"v4.0.7",
+ "otversion":"v4.1.0",
"translators":["ngocdiep2006"],
"lastedited":"07/04/2025",
"language":"Vietnamese",
diff --git a/package.json b/package.json
index 0f8cb65..b80f5ce 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
{
"name": "open-ticket",
"author": "DJdj Development",
- "version": "4.0.7",
+ "version": "4.1.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",
@@ -14,19 +14,24 @@
"scripts": {
"build": "node index.js --compile-only",
"start": "node index.js",
+ "setup": "node index.js --cli",
"startnc": "node index.js --no-compile",
- "test": "node index.js --dev-config --dev-database",
- "testnc": "node index.js --no-compile --dev-config --dev-database",
- "docs": "npx typedoc --options .docs/typedoc-config.json && node .docs/createDocs.js"
+ "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"
},
"type": "commonjs",
"license": "GPL-3.0-only",
"dependencies": {
"@discordjs/rest": "^2.4.2",
"@types/node": "^22.5.0",
+ "@types/terminal-kit": "^2.5.7",
"ansis": "^2.3.0",
- "discord.js": "^14.19.3",
- "formatted-json-stringify": "^1.1.0",
+ "discord.js": "^14.22.1",
+ "formatted-json-stringify": "^1.2.1",
+ "terminal-kit": "^3.1.2",
"typescript": "^5.5.4"
},
"repository": {
diff --git a/src/actions/addTicketUser.ts b/src/actions/addTicketUser.ts
index cf3cf0f..d595597 100644
--- a/src/actions/addTicketUser.ts
+++ b/src/actions/addTicketUser.ts
@@ -54,6 +54,9 @@ export const registerActions = async () => {
if (params.sendMessage) await channel.send((await opendiscord.builders.messages.getSafe("opendiscord:add-message").build(source,{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) => {
const {guild,channel,user,ticket,reason,data} = params
diff --git a/src/actions/claimTicket.ts b/src/actions/claimTicket.ts
index 6729254..502db01 100644
--- a/src/actions/claimTicket.ts
+++ b/src/actions/claimTicket.ts
@@ -65,6 +65,9 @@ export const registerActions = async () => {
if (params.sendMessage) await channel.send((await opendiscord.builders.messages.getSafe("opendiscord:claim-message").build(source,{guild,channel,user,ticket,reason})).message)
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) => {
const {guild,channel,user,ticket,reason} = params
diff --git a/src/actions/closeTicket.ts b/src/actions/closeTicket.ts
index a37bb7d..083a903 100644
--- a/src/actions/closeTicket.ts
+++ b/src/actions/closeTicket.ts
@@ -5,6 +5,7 @@ import {opendiscord, api, utilities} from "../index"
import * as discord from "discord.js"
const generalConfig = opendiscord.configs.get("opendiscord:general")
+const lang = opendiscord.languages
export const registerActions = async () => {
opendiscord.actions.add(new api.ODAction("opendiscord:close-ticket"))
@@ -17,10 +18,15 @@ export const registerActions = async () => {
//update ticket
ticket.get("opendiscord:closed").value = true
- if (source == "autoclose") ticket.get("opendiscord:autoclosed").value = true
- ticket.get("opendiscord:open").value = false
ticket.get("opendiscord:closed-by").value = user.id
ticket.get("opendiscord:closed-on").value = new Date().getTime()
+
+ ticket.get("opendiscord:reopened").value = false
+ ticket.get("opendiscord:reopened-by").value = null
+ ticket.get("opendiscord:reopened-on").value = null
+
+ if (source == "autoclose") ticket.get("opendiscord:autoclosed").value = true
+ ticket.get("opendiscord:open").value = false
ticket.get("opendiscord:busy").value = true
//update stats
@@ -123,6 +129,9 @@ export const registerActions = async () => {
if (params.sendMessage) await channel.send((await opendiscord.builders.messages.getSafe("opendiscord:close-message").build(source,{guild,channel,user,ticket,reason})).message)
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) => {
const {guild,channel,user,ticket,reason} = params
@@ -206,7 +215,7 @@ export const registerVerifyBars = async () => {
}
//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"}))
+ 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
@@ -214,6 +223,18 @@ export const registerVerifyBars = async () => {
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 (!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"){
@@ -293,7 +314,7 @@ export const registerVerifyBars = async () => {
}
//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"}))
+ 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
@@ -301,6 +322,18 @@ export const registerVerifyBars = async () => {
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 (!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"){
diff --git a/src/actions/createTicket.ts b/src/actions/createTicket.ts
index 0974e02..2e62699 100644
--- a/src/actions/createTicket.ts
+++ b/src/actions/createTicket.ts
@@ -5,6 +5,7 @@ import {opendiscord, api, utilities} from "../index"
import * as discord from "discord.js"
const generalConfig = opendiscord.configs.get("opendiscord:general")
+const lang = opendiscord.languages
export const registerActions = async () => {
opendiscord.actions.add(new api.ODAction("opendiscord:create-ticket"))
@@ -19,8 +20,8 @@ export const registerActions = async () => {
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 channelDescription = option.get("opendiscord:channel-description").value
- const channelSuffix = await opendiscord.options.suffix.getSuffixFromOption(option,user)
+ const channelTopicText = option.get("opendiscord:channel-topic").value
+ const channelSuffix = await opendiscord.options.suffix.getSuffixFromOption(option,user,guild)
const channelName = channelPrefix+channelSuffix
//handle category
@@ -73,7 +74,7 @@ export const registerActions = async () => {
permissions.push({
type:discord.OverwriteType.Role,
id:admin,
- allow:["ViewChannel","SendMessages","AddReactions","AttachFiles","SendPolls","ReadMessageHistory","ManageMessages"],
+ allow:["ViewChannel","SendMessages","AddReactions","AttachFiles","SendPolls","ReadMessageHistory","ManageMessages","PinMessages","EmbedLinks"],
deny:[]
})
})
@@ -82,7 +83,7 @@ export const registerActions = async () => {
permissions.push({
type:discord.OverwriteType.Role,
id:admin,
- allow:["ViewChannel","SendMessages","AddReactions","AttachFiles","SendPolls","ReadMessageHistory","ManageMessages"],
+ allow:["ViewChannel","SendMessages","AddReactions","AttachFiles","SendPolls","ReadMessageHistory","ManageMessages","PinMessages","EmbedLinks"],
deny:[]
})
})
@@ -93,28 +94,15 @@ export const registerActions = async () => {
type:discord.OverwriteType.Role,
id:admin,
allow:["ViewChannel","ReadMessageHistory"],
- deny:["SendMessages","AddReactions","AttachFiles","SendPolls"]
+ deny:["SendMessages","AddReactions","AttachFiles","SendPolls","PinMessages"]
})
})
permissions.push({
type:discord.OverwriteType.Member,
id:user.id,
- allow:["ViewChannel","SendMessages","AddReactions","AttachFiles","SendPolls","ReadMessageHistory"],
+ allow:["ViewChannel","SendMessages","AddReactions","AttachFiles","SendPolls","ReadMessageHistory","EmbedLinks","PinMessages"],
deny:[]
})
-
- //create channel
- const channel = await guild.channels.create({
- type:discord.ChannelType.GuildText,
- name:channelName,
- nsfw:false,
- topic:channelDescription,
- parent:category,
- reason:"Ticket Created By "+user.displayName,
- permissionOverwrites:permissions
- })
-
- await opendiscord.events.get("afterTicketChannelCreated").emit([option,channel,user])
//create participants
const participants: {type:"role"|"user",id:string}[] = []
@@ -125,12 +113,42 @@ export const registerActions = async () => {
participants.push({type,id})
})
+ //manage slowmode
+ const slowMode = option.get("opendiscord:slowmode-enabled").value ? option.get("opendiscord:slowmode-seconds").value : undefined
+
+ //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(", "))
+
+ //create channel
+ const channel = await guild.channels.create({
+ type:discord.ChannelType.GuildText,
+ name:channelName,
+ nsfw:false,
+ topic:(channelTopics.length > 0) ? channelTopics.join(" โข ") : undefined,
+ parent:category,
+ reason:"Ticket Created By "+user.displayName,
+ permissionOverwrites:permissions,
+ rateLimitPerUser:slowMode
+ })
+
+ await opendiscord.events.get("afterTicketChannelCreated").emit([option,channel,user])
+
//create ticket
const ticket = new api.ODTicket(channel.id,option,[
new api.ODTicketData("opendiscord:busy",false),
new api.ODTicketData("opendiscord:ticket-message",null),
new api.ODTicketData("opendiscord:participants",participants),
new api.ODTicketData("opendiscord:channel-suffix",channelSuffix),
+ new api.ODTicketData("opendiscord:previous-creators",[]),
new api.ODTicketData("opendiscord:open",true),
new api.ODTicketData("opendiscord:opened-by",user.id),
@@ -138,6 +156,9 @@ export const registerActions = async () => {
new api.ODTicketData("opendiscord:closed",false),
new api.ODTicketData("opendiscord:closed-by",null),
new api.ODTicketData("opendiscord:closed-on",null),
+ new api.ODTicketData("opendiscord:reopened",false),
+ new api.ODTicketData("opendiscord:reopened-by",null),
+ new api.ODTicketData("opendiscord:reopened-on",null),
new api.ODTicketData("opendiscord:claimed",false),
new api.ODTicketData("opendiscord:claimed-by",null),
new api.ODTicketData("opendiscord:claimed-on",null),
@@ -155,7 +176,11 @@ export const registerActions = async () => {
new api.ODTicketData("opendiscord:autodelete-enabled",option.get("opendiscord:autodelete-enable-days").value),
new api.ODTicketData("opendiscord:autodelete-days",(option.get("opendiscord:autodelete-enable-days").value ? option.get("opendiscord:autodelete-days").value : 0)),
- new api.ODTicketData("opendiscord:answers",answers)
+ new api.ODTicketData("opendiscord:answers",answers),
+ new api.ODTicketData("opendiscord:priority",-1),
+ new api.ODTicketData("opendiscord:topic",option.get("opendiscord:channel-topic").value),
+ new api.ODTicketData("opendiscord:message-sent",false),
+ new api.ODTicketData("opendiscord:admin-message-sent",false),
])
//manage stats
@@ -185,6 +210,9 @@ export const registerActions = async () => {
const msg = await channel.send((await opendiscord.builders.messages.getSafe("opendiscord:ticket-message").build(source,{guild,channel,user,ticket})).message)
ticket.get("opendiscord:ticket-message").value = msg.id
+
+ //pin ticket message (if required)
+ if (generalConfig.data.system.pinFirstTicketMessage && msg.pinnable) await msg.pin("Ticket Message")
//manage stats
await opendiscord.stats.get("opendiscord:ticket").setStat("opendiscord:messages-sent",ticket.id.value,1,"increase")
@@ -226,4 +254,4 @@ export const registerActions = async () => {
])
})
])
-}
\ No newline at end of file
+}
diff --git a/src/actions/deleteTicket.ts b/src/actions/deleteTicket.ts
index 4c368b7..07d6996 100644
--- a/src/actions/deleteTicket.ts
+++ b/src/actions/deleteTicket.ts
@@ -5,6 +5,7 @@ import {opendiscord, api, utilities} from "../index"
import * as discord from "discord.js"
const generalConfig = opendiscord.configs.get("opendiscord:general")
+const lang = opendiscord.languages
export const registerActions = async () => {
opendiscord.actions.add(new api.ODAction("opendiscord:delete-ticket"))
@@ -121,6 +122,14 @@ export const registerVerifyBars = async () => {
new api.ODWorker("opendiscord:permissions",1,async (instance,params,source,cancel) => {
const permissionMode = generalConfig.data.system.permissions.delete
+ //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()
+ }
+ }
+
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:[]}))
@@ -167,6 +176,18 @@ export const registerVerifyBars = async () => {
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 (!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"){
@@ -206,6 +227,14 @@ export const registerVerifyBars = async () => {
new api.ODWorker("opendiscord:permissions",1,async (instance,params,source,cancel) => {
const permissionMode = generalConfig.data.system.permissions.delete
+ //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()
+ }
+ }
+
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:[]}))
@@ -252,6 +281,18 @@ export const registerVerifyBars = async () => {
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 (!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"){
@@ -295,6 +336,14 @@ export const registerVerifyBars = async () => {
new api.ODWorker("opendiscord:permissions",1,async (instance,params,source,cancel) => {
const permissionMode = generalConfig.data.system.permissions.delete
+ //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()
+ }
+ }
+
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:[]}))
@@ -341,6 +390,18 @@ export const registerVerifyBars = async () => {
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 (!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"){
@@ -384,6 +445,14 @@ export const registerVerifyBars = async () => {
new api.ODWorker("opendiscord:permissions",1,async (instance,params,source,cancel) => {
const permissionMode = generalConfig.data.system.permissions.delete
+ //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()
+ }
+ }
+
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:[]}))
@@ -430,6 +499,18 @@ export const registerVerifyBars = async () => {
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 (!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"){
diff --git a/src/actions/moveTicket.ts b/src/actions/moveTicket.ts
index 11c21e3..591aef6 100644
--- a/src/actions/moveTicket.ts
+++ b/src/actions/moveTicket.ts
@@ -28,8 +28,7 @@ export const registerActions = async () => {
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 channelDescription = ticket.option.get("opendiscord:channel-description").value
- const channelName = channelPrefix+channelSuffix
+ const channelTopic = ticket.option.get("opendiscord:channel-topic").value
//handle category
let category: string|null = null
@@ -155,17 +154,18 @@ export const registerActions = async () => {
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(channelName),2500,(err) => {
+ 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:channelName})).message)
+ await channel.send((await opendiscord.builders.messages.getSafe("opendiscord:error-channel-rename").build("ticket-move",{guild,channel,user,originalName,newName:newName})).message)
}
- try{
- if (channel.type == discord.ChannelType.GuildText) channel.setTopic(channelDescription)
- }catch{}
//update ticket message
const ticketMessage = await opendiscord.tickets.getTicketMessage(ticket)
@@ -173,7 +173,7 @@ export const registerActions = async () => {
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",[
+ 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},
@@ -187,6 +187,9 @@ export const registerActions = async () => {
if (params.sendMessage) await channel.send((await opendiscord.builders.messages.getSafe("opendiscord:move-message").build(source,{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) => {
const {guild,channel,user,ticket,reason,data} = params
diff --git a/src/actions/pinTicket.ts b/src/actions/pinTicket.ts
index 0ac9446..a388ae0 100644
--- a/src/actions/pinTicket.ts
+++ b/src/actions/pinTicket.ts
@@ -31,8 +31,11 @@ export const registerActions = async () => {
}
//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 = "๐"+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")
@@ -61,6 +64,9 @@ export const registerActions = async () => {
if (params.sendMessage) await channel.send((await opendiscord.builders.messages.getSafe("opendiscord:pin-message").build(source,{guild,channel,user,ticket,reason})).message)
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) => {
const {guild,channel,user,ticket,reason} = params
diff --git a/src/actions/reactionRole.ts b/src/actions/reactionRole.ts
index 80daaad..84760e1 100644
--- a/src/actions/reactionRole.ts
+++ b/src/actions/reactionRole.ts
@@ -4,6 +4,8 @@
import {opendiscord, api, utilities} from "../index"
import * as discord from "discord.js"
+const generalConfig = opendiscord.configs.get("opendiscord:general")
+
export const registerActions = async () => {
opendiscord.actions.add(new api.ODAction("opendiscord:reaction-role"))
opendiscord.actions.get("opendiscord:reaction-role").workers.add([
@@ -81,6 +83,19 @@ 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) => {
+ 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)){
+ 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}))
+ }
+
+ //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}))
+ }),
new api.ODWorker("opendiscord:logs",0,(instance,params,source,cancel) => {
const {guild,user,option} = params
opendiscord.log(user.displayName+" updated his roles!","info",[
diff --git a/src/actions/removeTicketUser.ts b/src/actions/removeTicketUser.ts
index 489ec8b..9a8ef9f 100644
--- a/src/actions/removeTicketUser.ts
+++ b/src/actions/removeTicketUser.ts
@@ -48,6 +48,9 @@ export const registerActions = async () => {
if (params.sendMessage) await channel.send((await opendiscord.builders.messages.getSafe("opendiscord:remove-message").build(source,{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) => {
const {guild,channel,user,ticket,reason,data} = params
diff --git a/src/actions/renameTicket.ts b/src/actions/renameTicket.ts
index ee0a722..a110f16 100644
--- a/src/actions/renameTicket.ts
+++ b/src/actions/renameTicket.ts
@@ -16,9 +16,13 @@ export const registerActions = async () => {
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 ?? ""
+
const originalName = channel.name
+ const newName = pinEmoji+priorityEmoji+utilities.trimEmojis(data)
try{
- await utilities.timedAwait(channel.setName(data),2500,(err) => {
+ await utilities.timedAwait(channel.setName(newName),2500,(err) => {
opendiscord.log("Failed to rename channel on ticket rename","error")
})
}catch(err){
@@ -45,6 +49,9 @@ export const registerActions = async () => {
if (params.sendMessage) await channel.send((await opendiscord.builders.messages.getSafe("opendiscord:rename-message").build(source,{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) => {
const {guild,channel,user,ticket,reason,data} = params
diff --git a/src/actions/reopenTicket.ts b/src/actions/reopenTicket.ts
index 84e01fb..5112f66 100644
--- a/src/actions/reopenTicket.ts
+++ b/src/actions/reopenTicket.ts
@@ -16,13 +16,24 @@ export const registerActions = async () => {
await opendiscord.events.get("onTicketReopen").emit([ticket,user,channel,reason])
//update ticket
+ ticket.get("opendiscord:reopened").value = true
+ ticket.get("opendiscord:reopened-by").value = user.id
+ ticket.get("opendiscord:reopened-on").value = new Date().getTime()
+
ticket.get("opendiscord:closed").value = false
- ticket.get("opendiscord:open").value = true
- ticket.get("opendiscord:autoclosed").value = false
ticket.get("opendiscord:closed-by").value = null
ticket.get("opendiscord:closed-on").value = null
+
+ ticket.get("opendiscord:autoclosed").value = false
+ ticket.get("opendiscord:open").value = true
ticket.get("opendiscord:busy").value = true
+ if (generalConfig.data.system.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")
@@ -149,6 +160,9 @@ export const registerActions = async () => {
if (params.sendMessage) await channel.send((await opendiscord.builders.messages.getSafe("opendiscord:reopen-message").build(source,{guild,channel,user,ticket,reason})).message)
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) => {
const {guild,channel,user,ticket,reason} = params
diff --git a/src/actions/transferTicket.ts b/src/actions/transferTicket.ts
new file mode 100644
index 0000000..85887dc
--- /dev/null
+++ b/src/actions/transferTicket.ts
@@ -0,0 +1,154 @@
+///////////////////////////////////////
+//TICKET TRANSFER SYSTEM
+///////////////////////////////////////
+import {opendiscord, api, utilities} from "../index"
+import * as discord from "discord.js"
+
+const generalConfig = opendiscord.configs.get("opendiscord:general")
+
+export const registerActions = async () => {
+ 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) => {
+ 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!")
+
+ const oldCreator = await opendiscord.tickets.getTicketUser(ticket,"creator") ?? opendiscord.client.client.user
+ await opendiscord.events.get("onTicketTransfer").emit([ticket,user,channel,oldCreator,newCreator,reason])
+
+ //update ticket
+ const oldCreatorId = ticket.get("opendiscord:opened-by").value
+ if (oldCreatorId){
+ ticket.get("opendiscord:previous-creators").value.push(oldCreatorId)
+ ticket.get("opendiscord:previous-creators").refreshDatabase()
+ }
+ if (!ticket.get("opendiscord:participants").value.find((p) => p.type == "user" && p.id == newCreator.id)){
+ ticket.get("opendiscord:participants").value.push({type:"user",id:newCreator.id})
+ ticket.get("opendiscord:participants").refreshDatabase()
+ }
+ ticket.get("opendiscord:opened-by").value = newCreator.id
+ if (["user-name","user-nickname","user-id"].includes(ticket.option.get("opendiscord:channel-suffix").value)){
+ const newSuffix = await opendiscord.options.suffix.getSuffixFromOption(ticket.option,newCreator,guild)
+ if (newSuffix) ticket.get("opendiscord:channel-suffix").value = newSuffix
+ }
+
+ //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
+
+ //handle permissions
+ const permissions: discord.OverwriteResolvable[] = [{
+ type:discord.OverwriteType.Role,
+ id:guild.roles.everyone.id,
+ allow:[],
+ deny:["ViewChannel","SendMessages","ReadMessageHistory"]
+ }]
+ const globalAdmins = opendiscord.configs.get("opendiscord:general").data.globalAdmins
+ const optionAdmins = ticket.option.get("opendiscord:admins").value
+ const readonlyAdmins = ticket.option.get("opendiscord:admins-readonly").value
+
+ globalAdmins.forEach((admin) => {
+ permissions.push({
+ type:discord.OverwriteType.Role,
+ id:admin,
+ allow:["ViewChannel","SendMessages","AddReactions","AttachFiles","SendPolls","ReadMessageHistory","ManageMessages"],
+ deny:[]
+ })
+ })
+ optionAdmins.forEach((admin) => {
+ if (globalAdmins.includes(admin)) return
+ permissions.push({
+ type:discord.OverwriteType.Role,
+ id:admin,
+ allow:["ViewChannel","SendMessages","AddReactions","AttachFiles","SendPolls","ReadMessageHistory","ManageMessages"],
+ deny:[]
+ })
+ })
+ readonlyAdmins.forEach((admin) => {
+ if (globalAdmins.includes(admin)) return
+ if (optionAdmins.includes(admin)) return
+ permissions.push({
+ type:discord.OverwriteType.Role,
+ id:admin,
+ allow:["ViewChannel","ReadMessageHistory"],
+ deny:["SendMessages","AddReactions","AttachFiles","SendPolls"]
+ })
+ })
+ //transfer all old user-participants over to the new ticket (creator & participants)
+ ticket.get("opendiscord:participants").value.forEach((p) => {
+ if (p.type == "user") permissions.push({
+ type:discord.OverwriteType.Member,
+ id:p.id,
+ allow:["ViewChannel","SendMessages","AddReactions","AttachFiles","SendPolls","ReadMessageHistory"],
+ deny:[]
+ })
+ })
+ try{
+ await channel.permissionOverwrites.set(permissions)
+ }catch{
+ 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){
+ 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}
+ ])
+ opendiscord.debugfile.writeErrorMessage(new api.ODError(e,"uncaughtException"))
+ }
+ }
+
+ //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)
+ 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) => {
+ const {guild,channel,user,ticket,newCreator,reason} = params
+ }),
+ new api.ODWorker("opendiscord:logs",0,(instance,params,source,cancel) => {
+ const {guild,channel,user,ticket,newCreator} = params
+
+ opendiscord.log(user.displayName+" transferred a ticket to '"+newCreator.displayName+"'!","info",[
+ {key:"user",value:user.username},
+ {key:"userid",value:user.id,hidden:true},
+ {key:"channel",value:"#"+channel.name},
+ {key:"channelid",value:channel.id,hidden:true},
+ {key:"reason",value:params.reason ?? "/"},
+ {key:"method",value:source}
+ ])
+ })
+ ])
+ opendiscord.actions.get("opendiscord:transfer-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/unclaimTicket.ts b/src/actions/unclaimTicket.ts
index 0d8d8d8..6a04c1e 100644
--- a/src/actions/unclaimTicket.ts
+++ b/src/actions/unclaimTicket.ts
@@ -94,6 +94,9 @@ export const registerActions = async () => {
if (params.sendMessage) await channel.send((await opendiscord.builders.messages.getSafe("opendiscord:unclaim-message").build(source,{guild,channel,user,ticket,reason})).message)
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) => {
const {guild,channel,user,ticket,reason} = params
diff --git a/src/actions/unpinTicket.ts b/src/actions/unpinTicket.ts
index 4274e05..931ef38 100644
--- a/src/actions/unpinTicket.ts
+++ b/src/actions/unpinTicket.ts
@@ -22,16 +22,17 @@ export const registerActions = async () => {
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
- if (originalName.startsWith("๐")){
- const newName = originalName.replace("๐","");
- 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)
- }
+ 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
@@ -54,6 +55,9 @@ export const registerActions = async () => {
if (params.sendMessage) await channel.send((await opendiscord.builders.messages.getSafe("opendiscord:unpin-message").build(source,{guild,channel,user,ticket,reason})).message)
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) => {
const {guild,channel,user,ticket,reason} = params
diff --git a/src/actions/updateTicketPriority.ts b/src/actions/updateTicketPriority.ts
new file mode 100644
index 0000000..de328a7
--- /dev/null
+++ b/src/actions/updateTicketPriority.ts
@@ -0,0 +1,65 @@
+///////////////////////////////////////
+//TICKET TOPIC SYSTEM
+///////////////////////////////////////
+import {opendiscord, api, utilities} from "../index"
+import * as discord from "discord.js"
+
+const generalConfig = opendiscord.configs.get("opendiscord:general")
+
+export const registerActions = async () => {
+ 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) => {
+ 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!")
+
+ const oldPriority = opendiscord.priorities.getFromPriorityLevel(ticket.get("opendiscord:priority").value)
+ await opendiscord.events.get("onTicketPriorityChange").emit([ticket,user,channel,oldPriority,newPriority,reason])
+
+ //update ticket
+ 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)
+ }
+
+ //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)
+ 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})
+ }),
+ new api.ODWorker("opendiscord:discord-logs",1,async (instance,params,source,cancel) => {
+ const {guild,channel,user,ticket} = params
+ }),
+ new api.ODWorker("opendiscord:logs",0,(instance,params,source,cancel) => {
+ const {guild,channel,user,ticket,newPriority} = params
+
+ opendiscord.log(user.displayName+" changed the priority of a ticket!","info",[
+ {key:"user",value:user.username},
+ {key:"userid",value:user.id,hidden:true},
+ {key:"channel",value:"#"+channel.name},
+ {key:"channelid",value:channel.id,hidden:true},
+ {key:"priority",value:newPriority.id.value},
+ {key:"method",value:source}
+ ])
+ })
+ ])
+ opendiscord.actions.get("opendiscord:update-ticket-priority").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/updateTicketTopic.ts b/src/actions/updateTicketTopic.ts
new file mode 100644
index 0000000..1b544b0
--- /dev/null
+++ b/src/actions/updateTicketTopic.ts
@@ -0,0 +1,70 @@
+///////////////////////////////////////
+//TICKET TOPIC SYSTEM
+///////////////////////////////////////
+import {opendiscord, api, utilities} from "../index"
+import * as discord from "discord.js"
+
+const generalConfig = opendiscord.configs.get("opendiscord:general")
+const lang = opendiscord.languages
+
+export const registerActions = async () => {
+ 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) => {
+ 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!")
+
+ const oldTopic = ticket.get("opendiscord:topic").value
+ if (newTopic) await opendiscord.events.get("onTicketTopicChange").emit([ticket,user,channel,oldTopic,newTopic])
+
+ //update ticket
+ ticket.get("opendiscord:busy").value = true
+ if (newTopic) ticket.get("opendiscord:topic").value = newTopic
+
+ //get ticket data
+ const closed = ticket.get("opendiscord:closed").value
+ const claimedBy = ticket.get("opendiscord:claimed-by").value
+ const pinned = ticket.get("opendiscord:pinned").value
+ const creator = ticket.get("opendiscord:opened-by").value ?? opendiscord.client.client.user.id
+
+ //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(", "))
+
+ //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)
+ ticket.get("opendiscord:busy").value = false
+ if (newTopic) await opendiscord.events.get("afterTicketTopicChanged").emit([ticket,user,channel,oldTopic,newTopic])
+ }),
+ new api.ODWorker("opendiscord:discord-logs",1,async (instance,params,source,cancel) => {
+ const {guild,channel,user,ticket} = params
+ }),
+ new api.ODWorker("opendiscord:logs",0,(instance,params,source,cancel) => {
+ const {guild,channel,user,ticket,newTopic} = params
+
+ if (newTopic) opendiscord.log(user.displayName+" changed the topic of a ticket!","info",[
+ {key:"user",value:user.username},
+ {key:"userid",value:user.id,hidden:true},
+ {key:"channel",value:"#"+channel.name},
+ {key:"channelid",value:channel.id,hidden:true},
+ {key:"topic",value:newTopic},
+ {key:"method",value:source}
+ ])
+ })
+ ])
+ opendiscord.actions.get("opendiscord:update-ticket-topic").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/builders/buttons.ts b/src/builders/buttons.ts
index fcdc172..cc9cba9 100644
--- a/src/builders/buttons.ts
+++ b/src/builders/buttons.ts
@@ -277,7 +277,7 @@ const ticketButtons = () => {
instance.setMode("button")
instance.setCustomId("od:pin-ticket_"+source)
instance.setColor("gray")
- instance.setEmoji("๐")
+ instance.setEmoji(generalConfig.data.system.pinEmoji)
instance.setLabel(lang.getTranslation("actions.buttons.pin"))
})
)
@@ -291,7 +291,7 @@ const ticketButtons = () => {
instance.setMode("button")
instance.setCustomId("od:unpin-ticket_"+source)
instance.setColor("gray")
- instance.setEmoji("๐")
+ instance.setEmoji(generalConfig.data.system.pinEmoji)
instance.setLabel(lang.getTranslation("actions.buttons.unpin"))
})
)
diff --git a/src/builders/embeds.ts b/src/builders/embeds.ts
index f06dde2..5d7cc2c 100644
--- a/src/builders/embeds.ts
+++ b/src/builders/embeds.ts
@@ -20,6 +20,7 @@ export const registerAllEmbeds = async () => {
roleEmbeds()
clearEmbeds()
autoEmbeds()
+ extraEmbeds()
}
/**Utility function to get the translated "method" from the source. Mostly used in error embeds. */
@@ -37,12 +38,12 @@ const errorEmbeds = () => {
embeds.add(new api.ODEmbed("opendiscord:error"))
embeds.get("opendiscord:error").workers.add(
new api.ODWorker("opendiscord:error",0,async (instance,params,source) => {
- const {user,error,layout} = params
+ const {user,error,layout,customTitle} = params
const method = getMethodFromSource(source)
instance.setColor(generalConfig.data.system.useRedErrorEmbeds ? "Red" : generalConfig.data.mainColor)
- instance.setTitle(utilities.emojiTitle("โ",lang.getTranslation("errors.titles.internalError")))
+ 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 : "")
if (layout == "advanced" && error) instance.addFields({name:lang.getTranslation("params.uppercase.reason")+":",value:"```"+error+"```"})
@@ -329,7 +330,7 @@ const errorEmbeds = () => {
new api.ODWorker("opendiscord:error-channel-rename",0,async (instance,params,source) => {
const {channel,user,originalName,newName} = params
- const method = (source == "ticket-move" || source == "ticket-pin" || source == "ticket-rename" || source == "ticket-unpin") ? source : getMethodFromSource(source)
+ const method = (source == "ticket-move" || source == "ticket-pin" || source == "ticket-rename" || source == "ticket-unpin" || source == "ticket-priority" || source == "ticket-transfer") ? source : getMethodFromSource(source)
instance.setColor(generalConfig.data.system.useRedErrorEmbeds ? "Red" : generalConfig.data.mainColor)
instance.setTitle(utilities.emojiTitle("โ",lang.getTranslation("errors.titles.channelRename")))
@@ -412,14 +413,17 @@ const statsEmbeds = () => {
const scope = opendiscord.stats.get("opendiscord:ticket")
const participantsScope = opendiscord.stats.get("opendiscord:participants")
- if (!scope || !participantsScope) return
+ const messagesScope = opendiscord.stats.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)
+ const messagesData = await messagesScope.render(scopeData.id.value,guild,channel,user)
instance.setColor(generalConfig.data.mainColor)
instance.setTitle(scope.name)
instance.setDescription(data)
- instance.addFields({name:participantsScope.name,value:participantsData,inline:false})
+ instance.addFields({name:participantsScope.name,value:participantsData,inline:true})
+ instance.addFields({name:messagesScope.name,value:messagesData,inline:true})
})
)
@@ -592,8 +596,11 @@ const ticketEmbeds = () => {
if (embedOptions.image) instance.setImage(embedOptions.image)
if (embedOptions.timestamp) instance.setTimestamp(new Date())
if (embedOptions.description) instance.setDescription(embedOptions.description)
-
+
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)
+
const answers = ticket.get("opendiscord:answers").value
answers.forEach((answer) => {
if (!answer.value || answer.value.length == 0) return
@@ -610,7 +617,7 @@ const ticketEmbeds = () => {
if (ticket.get("opendiscord:claimed").value){
const claimUser = await opendiscord.tickets.getTicketUser(ticket,"claimer")
if (!claimUser) return
- instance.setAuthor(lang.getTranslationWithParams("params.uppercase.claimedBy",[claimUser.displayName]),claimUser.displayAvatarURL())
+ instance.setAuthor(lang.getTranslation("stats.properties.claimedBy")+" "+claimUser.displayName,claimUser.displayAvatarURL())
}
})
)
@@ -625,7 +632,7 @@ const ticketEmbeds = () => {
instance.setColor(generalConfig.data.mainColor)
instance.setTitle(utilities.emojiTitle("๐",lang.getTranslation("actions.titles.close")))
instance.setDescription(lang.getTranslation("actions.descriptions.close"))
- if (reason) instance.addFields({name:lang.getTranslation("params.uppercase.reason")+":",value:"```"+reason+"```"})
+ if (reason || generalConfig.data.system.alwaysShowReason) instance.addFields({name:lang.getTranslation("params.uppercase.reason")+":",value:"```"+(reason ?? "/")+"```"})
})
)
@@ -639,7 +646,7 @@ const ticketEmbeds = () => {
instance.setColor(generalConfig.data.mainColor)
instance.setTitle(utilities.emojiTitle("๐",lang.getTranslation("actions.titles.reopen")))
instance.setDescription(lang.getTranslation("actions.descriptions.reopen"))
- if (reason) instance.addFields({name:lang.getTranslation("params.uppercase.reason")+":",value:"```"+reason+"```"})
+ if (reason || generalConfig.data.system.alwaysShowReason) instance.addFields({name:lang.getTranslation("params.uppercase.reason")+":",value:"```"+(reason ?? "/")+"```"})
})
)
@@ -653,7 +660,7 @@ const ticketEmbeds = () => {
instance.setColor(generalConfig.data.mainColor)
instance.setTitle(utilities.emojiTitle("๐๏ธ",lang.getTranslation("actions.titles.delete")))
instance.setDescription(lang.getTranslation("actions.descriptions.delete"))
- if (reason) instance.addFields({name:lang.getTranslation("params.uppercase.reason")+":",value:"```"+reason+"```"})
+ if (reason || generalConfig.data.system.alwaysShowReason) instance.addFields({name:lang.getTranslation("params.uppercase.reason")+":",value:"```"+(reason ?? "/")+"```"})
})
)
@@ -667,7 +674,7 @@ const ticketEmbeds = () => {
instance.setColor(generalConfig.data.mainColor)
instance.setTitle(utilities.emojiTitle("๐",lang.getTranslation("actions.titles.claim")))
instance.setDescription(lang.getTranslation("actions.descriptions.claim"))
- if (reason) instance.addFields({name:lang.getTranslation("params.uppercase.reason")+":",value:"```"+reason+"```"})
+ if (reason || generalConfig.data.system.alwaysShowReason) instance.addFields({name:lang.getTranslation("params.uppercase.reason")+":",value:"```"+(reason ?? "/")+"```"})
})
)
@@ -681,7 +688,7 @@ const ticketEmbeds = () => {
instance.setColor(generalConfig.data.mainColor)
instance.setTitle(utilities.emojiTitle("โฉ๏ธ",lang.getTranslation("actions.titles.unclaim")))
instance.setDescription(lang.getTranslation("actions.descriptions.unclaim"))
- if (reason) instance.addFields({name:lang.getTranslation("params.uppercase.reason")+":",value:"```"+reason+"```"})
+ if (reason || generalConfig.data.system.alwaysShowReason) instance.addFields({name:lang.getTranslation("params.uppercase.reason")+":",value:"```"+(reason ?? "/")+"```"})
})
)
@@ -693,9 +700,9 @@ const ticketEmbeds = () => {
instance.setAuthor(user.displayName,user.displayAvatarURL())
instance.setColor(generalConfig.data.mainColor)
- instance.setTitle(utilities.emojiTitle("๐",lang.getTranslation("actions.titles.pin")))
+ instance.setTitle(utilities.emojiTitle(generalConfig.data.system.pinEmoji,lang.getTranslation("actions.titles.pin")))
instance.setDescription(lang.getTranslation("actions.descriptions.pin"))
- if (reason) instance.addFields({name:lang.getTranslation("params.uppercase.reason")+":",value:"```"+reason+"```"})
+ if (reason || generalConfig.data.system.alwaysShowReason) instance.addFields({name:lang.getTranslation("params.uppercase.reason")+":",value:"```"+(reason ?? "/")+"```"})
})
)
@@ -707,9 +714,9 @@ const ticketEmbeds = () => {
instance.setAuthor(user.displayName,user.displayAvatarURL())
instance.setColor(generalConfig.data.mainColor)
- instance.setTitle(utilities.emojiTitle("๐",lang.getTranslation("actions.titles.unpin")))
+ instance.setTitle(utilities.emojiTitle(generalConfig.data.system.pinEmoji,lang.getTranslation("actions.titles.unpin")))
instance.setDescription(lang.getTranslation("actions.descriptions.unpin"))
- if (reason) instance.addFields({name:lang.getTranslation("params.uppercase.reason")+":",value:"```"+reason+"```"})
+ if (reason || generalConfig.data.system.alwaysShowReason) instance.addFields({name:lang.getTranslation("params.uppercase.reason")+":",value:"```"+(reason ?? "/")+"```"})
})
)
@@ -723,7 +730,7 @@ const ticketEmbeds = () => {
instance.setColor(generalConfig.data.mainColor)
instance.setTitle(utilities.emojiTitle("๐",lang.getTranslation("actions.titles.rename")))
instance.setDescription(lang.getTranslationWithParams("actions.descriptions.rename",["`#"+data+"`"]))
- if (reason) instance.addFields({name:lang.getTranslation("params.uppercase.reason")+":",value:"```"+reason+"```"})
+ if (reason || generalConfig.data.system.alwaysShowReason) instance.addFields({name:lang.getTranslation("params.uppercase.reason")+":",value:"```"+(reason ?? "/")+"```"})
})
)
@@ -737,7 +744,7 @@ const ticketEmbeds = () => {
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) instance.addFields({name:lang.getTranslation("params.uppercase.reason")+":",value:"```"+reason+"```"})
+ if (reason || generalConfig.data.system.alwaysShowReason) instance.addFields({name:lang.getTranslation("params.uppercase.reason")+":",value:"```"+(reason ?? "/")+"```"})
})
)
@@ -751,7 +758,7 @@ const ticketEmbeds = () => {
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) instance.addFields({name:lang.getTranslation("params.uppercase.reason")+":",value:"```"+reason+"```"})
+ if (reason || generalConfig.data.system.alwaysShowReason) instance.addFields({name:lang.getTranslation("params.uppercase.reason")+":",value:"```"+(reason ?? "/")+"```"})
})
)
@@ -765,7 +772,7 @@ const ticketEmbeds = () => {
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) instance.addFields({name:lang.getTranslation("params.uppercase.reason")+":",value:"```"+reason+"```"})
+ if (reason || generalConfig.data.system.alwaysShowReason) instance.addFields({name:lang.getTranslation("params.uppercase.reason")+":",value:"```"+(reason ?? "/")+"```"})
})
)
@@ -797,10 +804,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("๐",lang.getTranslation("actions.titles.pin")))
+ instance.setTitle(utilities.emojiTitle(generalConfig.data.system.pinEmoji,lang.getTranslation("actions.titles.pin")))
instance.setDescription(lang.getTranslation("actions.logs.pinDm"))
}else if (mode == "unpin"){
- instance.setTitle(utilities.emojiTitle("๐",lang.getTranslation("actions.titles.unpin")))
+ instance.setTitle(utilities.emojiTitle(generalConfig.data.system.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")))
@@ -831,10 +838,9 @@ const ticketEmbeds = () => {
instance.setTimestamp(new Date())
instance.addFields(
{name:lang.getTranslation("params.uppercase.ticket")+":",value:"```#"+(channel ? channel.name : "")+"```",inline:false},
- //TODO TRANSLATION!!!
- {name:"Option"+":",value:"```"+(ticket.option.get("opendiscord:name").value)+"```",inline:false},
+ {name:lang.getTranslation("params.uppercase.option")+":",value:"```"+(ticket.option.get("opendiscord:name").value)+"```",inline:false},
)
- if (reason) instance.addFields({name:lang.getTranslation("params.uppercase.reason")+":",value:"```"+reason+"```",inline:false})
+ if (reason || generalConfig.data.system.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")))
@@ -852,10 +858,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("๐",lang.getTranslation("actions.titles.pin")))
+ instance.setTitle(utilities.emojiTitle(generalConfig.data.system.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("๐",lang.getTranslation("actions.titles.unpin")))
+ instance.setTitle(utilities.emojiTitle(generalConfig.data.system.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")))
@@ -908,7 +914,7 @@ const blacklistEmbeds = () => {
if (blacklist){
instance.setDescription(lang.getTranslationWithParams("actions.descriptions.blacklistGetSuccess",[discord.userMention(data.id)]))
- if (blacklist.reason) instance.addFields({name:lang.getTranslation("params.uppercase.reason")+":",value:"```"+blacklist.reason+"```"})
+ if (blacklist.reason || generalConfig.data.system.alwaysShowReason) instance.addFields({name:lang.getTranslation("params.uppercase.reason")+":",value:"```"+(blacklist.reason ?? "/")+"```"})
}else instance.setDescription("*"+lang.getTranslationWithParams("actions.descriptions.blacklistGetEmpty",[discord.userMention(data.id)])+"*")
})
@@ -924,7 +930,7 @@ const blacklistEmbeds = () => {
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) instance.addFields({name:lang.getTranslation("params.uppercase.reason")+":",value:"```"+reason+"```"})
+ if (reason || generalConfig.data.system.alwaysShowReason) instance.addFields({name:lang.getTranslation("params.uppercase.reason")+":",value:"```"+(reason ?? "/")+"```"})
})
)
@@ -938,7 +944,7 @@ const blacklistEmbeds = () => {
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) instance.addFields({name:lang.getTranslation("params.uppercase.reason")+":",value:"```"+reason+"```"})
+ if (reason || generalConfig.data.system.alwaysShowReason) instance.addFields({name:lang.getTranslation("params.uppercase.reason")+":",value:"```"+(reason ?? "/")+"```"})
})
)
@@ -955,7 +961,7 @@ const blacklistEmbeds = () => {
instance.setTitle(utilities.emojiTitle((mode == "add") ? "๐ซ" : "๐",title))
instance.setTimestamp(new Date())
instance.setDescription(text)
- if (reason) instance.addFields({name:lang.getTranslation("params.uppercase.reason")+":",value:"```"+reason+"```",inline:false})
+ if (reason || generalConfig.data.system.alwaysShowReason) instance.addFields({name:lang.getTranslation("params.uppercase.reason")+":",value:"```"+(reason ?? "/")+"```"})
})
)
@@ -974,7 +980,7 @@ const blacklistEmbeds = () => {
instance.setAuthor(user.displayName,user.displayAvatarURL())
instance.setTimestamp(new Date())
instance.setDescription(text)
- if (reason) instance.addFields({name:lang.getTranslation("params.uppercase.reason")+":",value:"```"+reason+"```",inline:false})
+ if (reason || generalConfig.data.system.alwaysShowReason) instance.addFields({name:lang.getTranslation("params.uppercase.reason")+":",value:"```"+(reason ?? "/")+"```"})
})
)
}
@@ -1074,11 +1080,11 @@ const transcriptEmbeds = () => {
const {guild,channel,user,ticket,compiler,reason} = params
instance.setColor(generalConfig.data.system.useRedErrorEmbeds ? "Red" : generalConfig.data.mainColor)
- instance.setTitle(utilities.emojiTitle("โ","Transcript Error")) //TODO TRANSLATION!!!
+ 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) instance.addFields({name:lang.getTranslation("params.uppercase.reason")+":",value:"```"+reason+"```",inline:false})
+ if (reason || generalConfig.data.system.alwaysShowReason) instance.addFields({name:lang.getTranslation("params.uppercase.reason")+":",value:"```"+(reason ?? "/")+"```"})
})
)
}
@@ -1107,6 +1113,58 @@ const roleEmbeds = () => {
else instance.setDescription(lang.getTranslation("actions.descriptions.rolesEmpty"))
})
)
+
+ //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) => {
+ const {guild,user,role,result} = params
+
+ instance.setColor(generalConfig.data.mainColor)
+ instance.setTitle(utilities.emojiTitle("๐จ",lang.getTranslation("actions.titles.roles")))
+ instance.setAuthor(user.displayName,user.displayAvatarURL())
+ instance.setTimestamp(new Date())
+
+ const newResult = result.filter((r) => r.action != null).sort((a,b) => {
+ if (a.action == "added" && b.action == "removed") return -1
+ else if (a.action == "removed" && b.action == "added") return 1
+ else return 0
+ }).map((r) => {
+ return (r.action == "added") ? "๐ข "+lang.getTranslation("params.uppercase.added")+" @"+r.role.name : "๐ด "+lang.getTranslation("params.uppercase.removed")+" @"+r.role.name
+ })
+
+ const baseDescription = lang.getTranslation("actions.logs.roleUpdateDm")
+
+ if (newResult.length > 0) instance.setDescription(baseDescription+"\n\n"+newResult.join("\n"))
+ else instance.setDescription(baseDescription+"\n"+lang.getTranslation("actions.descriptions.rolesEmpty"))
+ })
+ )
+
+ //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) => {
+ const {guild,user,role,result} = params
+
+ instance.setColor(generalConfig.data.mainColor)
+ instance.setTitle(utilities.emojiTitle("๐จ",lang.getTranslation("actions.titles.roles")))
+ instance.setAuthor(user.displayName,user.displayAvatarURL())
+ instance.setTimestamp(new Date())
+
+ const newResult = result.filter((r) => r.action != null).sort((a,b) => {
+ if (a.action == "added" && b.action == "removed") return -1
+ else if (a.action == "removed" && b.action == "added") return 1
+ else return 0
+ }).map((r) => {
+ return (r.action == "added") ? "๐ข "+lang.getTranslation("params.uppercase.added")+" "+discord.roleMention(r.role.id) : "๐ด "+lang.getTranslation("params.uppercase.removed")+" "+discord.roleMention(r.role.id)
+ })
+
+ const baseDescription = lang.getTranslationWithParams("actions.logs.roleUpdateLog",[discord.userMention(user.id)])
+
+ if (newResult.length > 0) instance.setDescription(baseDescription+"\n\n"+newResult.join("\n"))
+ else instance.setDescription(baseDescription+"\n"+lang.getTranslation("actions.descriptions.rolesEmpty"))
+ })
+ )
}
const clearEmbeds = () => {
@@ -1117,7 +1175,7 @@ const clearEmbeds = () => {
const {guild,channel,user,filter,list} = params
instance.setColor(generalConfig.data.mainColor)
- instance.setTitle(utilities.emojiTitle("โ ๏ธ","Clear Tickets")) //TODO TRANSLATION!!!
+ instance.setTitle(utilities.emojiTitle("โ ๏ธ",lang.getTranslation("actions.titles.clearTickets")))
instance.setAuthor(user.displayName,user.displayAvatarURL())
instance.setTimestamp(new Date())
instance.setDescription(lang.getTranslation("actions.descriptions.clearVerify"))
@@ -1207,7 +1265,7 @@ const autoEmbeds = () => {
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) instance.addFields({name:lang.getTranslation("params.uppercase.reason")+":",value:"```"+reason+"```"})
+ if (reason || generalConfig.data.system.alwaysShowReason) instance.addFields({name:lang.getTranslation("params.uppercase.reason")+":",value:"```"+(reason ?? "/")+"```"})
})
)
@@ -1221,7 +1279,7 @@ const autoEmbeds = () => {
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) instance.addFields({name:lang.getTranslation("params.uppercase.reason")+":",value:"```"+reason+"```"})
+ if (reason || generalConfig.data.system.alwaysShowReason) instance.addFields({name:lang.getTranslation("params.uppercase.reason")+":",value:"```"+(reason ?? "/")+"```"})
})
)
@@ -1235,7 +1293,7 @@ const autoEmbeds = () => {
instance.setColor(generalConfig.data.mainColor)
instance.setTitle(utilities.emojiTitle("โฑ๏ธ",lang.getTranslation("actions.titles.autocloseDisabled")))
instance.setDescription(lang.getTranslation("actions.descriptions.autocloseDisabled"))
- if (reason) instance.addFields({name:lang.getTranslation("params.uppercase.reason")+":",value:"```"+reason+"```"})
+ if (reason || generalConfig.data.system.alwaysShowReason) instance.addFields({name:lang.getTranslation("params.uppercase.reason")+":",value:"```"+(reason ?? "/")+"```"})
})
)
@@ -1249,7 +1307,64 @@ const autoEmbeds = () => {
instance.setColor(generalConfig.data.mainColor)
instance.setTitle(utilities.emojiTitle("โฑ๏ธ",lang.getTranslation("actions.titles.autodeleteDisabled")))
instance.setDescription(lang.getTranslation("actions.descriptions.autodeleteDisabled"))
- if (reason) instance.addFields({name:lang.getTranslation("params.uppercase.reason")+":",value:"```"+reason+"```"})
+ if (reason || generalConfig.data.system.alwaysShowReason) instance.addFields({name:lang.getTranslation("params.uppercase.reason")+":",value:"```"+(reason ?? "/")+"```"})
+ })
+ )
+}
+
+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) => {
+ const {user,topic} = params
+
+ instance.setAuthor(user.displayName,user.displayAvatarURL())
+ instance.setColor(generalConfig.data.mainColor)
+ instance.setTitle(utilities.emojiTitle("โน๏ธ",lang.getTranslation("actions.titles.topicSet")))
+ instance.setDescription(lang.getTranslationWithParams("actions.descriptions.topicSet",[discord.userMention(user.id)]))
+ if (topic) instance.addFields({name:lang.getTranslation("params.uppercase.topic")+":",value:"```"+topic+"```"})
+ })
+ )
+
+ //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) => {
+ 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 ?? "/")+"```"})
+ })
+ )
+
+ //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) => {
+ const {user,priority} = params
+
+ instance.setAuthor(user.displayName,user.displayAvatarURL())
+ instance.setColor(generalConfig.data.mainColor)
+ instance.setTitle(utilities.emojiTitle("๐จ",lang.getTranslation("actions.titles.priorityGet")))
+ instance.setDescription(lang.getTranslationWithParams("actions.descriptions.priorityGet",["**"+priority.renderDisplayName()+"**"]))
+ })
+ )
+
+ //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) => {
+ 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 ?? "/")+"```"})
})
)
}
\ No newline at end of file
diff --git a/src/builders/messages.ts b/src/builders/messages.ts
index 3b1531f..0b89c1b 100644
--- a/src/builders/messages.ts
+++ b/src/builders/messages.ts
@@ -24,6 +24,7 @@ export const registerAllMessages = async () => {
roleMessages()
clearMessages()
autoMessages()
+ extraMessages()
}
const verifyBarMessages = () => {
@@ -332,8 +333,8 @@ const errorMessages = () => {
messages.add(new api.ODMessage("opendiscord:error"))
messages.get("opendiscord:error").workers.add(
new api.ODWorker("opendiscord:error",0,async (instance,params,source) => {
- const {guild,channel,user,error,layout} = params
- instance.addEmbed(await embeds.getSafe("opendiscord:error").build(source,{guild,channel,user,error,layout}))
+ const {guild,channel,user,error,layout,customTitle} = params
+ instance.addEmbed(await embeds.getSafe("opendiscord:error").build(source,{guild,channel,user,error,layout,customTitle}))
instance.setEphemeral(true)
})
)
@@ -964,6 +965,24 @@ const roleMessages = () => {
instance.setEphemeral(true)
})
)
+
+ //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) => {
+ const {guild,user,role,result} = params
+ instance.addEmbed(await embeds.getSafe("opendiscord:reaction-role-dm").build(source,{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) => {
+ const {guild,user,role,result} = params
+ instance.addEmbed(await embeds.getSafe("opendiscord:reaction-role-logs").build(source,{guild,user,role,result}))
+ })
+ )
}
const clearMessages = () => {
@@ -1054,4 +1073,42 @@ const autoMessages = () => {
instance.addEmbed(await embeds.getSafe("opendiscord:autodelete-disable").build(source,{guild,channel,user,ticket,reason}))
})
)
+}
+
+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) => {
+ const {guild,channel,user,ticket,topic} = params
+ instance.addEmbed(await embeds.getSafe("opendiscord:topic-set").build(source,{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) => {
+ const {guild,channel,user,ticket,priority,reason} = params
+ instance.addEmbed(await embeds.getSafe("opendiscord:priority-set").build(source,{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) => {
+ const {guild,channel,user,ticket,priority} = params
+ instance.addEmbed(await embeds.getSafe("opendiscord:priority-get").build(source,{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) => {
+ 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}))
+ })
+ )
}
\ No newline at end of file
diff --git a/src/commands/autocomplete.ts b/src/commands/autocomplete.ts
new file mode 100644
index 0000000..e7cac2a
--- /dev/null
+++ b/src/commands/autocomplete.ts
@@ -0,0 +1,32 @@
+///////////////////////////////////////
+//AUTOCOMPLETE COMMAND UTILS
+///////////////////////////////////////
+import {opendiscord, api, utilities} from "../index"
+import * as discord from "discord.js"
+
+export const registerAutocompleteResponders = async () => {
+ //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) => {
+ //create panel choices
+ const panelChoices : {name:string, value:string}[] = []
+ opendiscord.configs.get("opendiscord:panels").data.forEach((panel) => {
+ panelChoices.push({name:panel.name, value:panel.id})
+ })
+
+ await instance.filteredAutocomplete(panelChoices)
+ }))
+
+ //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) => {
+ //create ticket choices
+ const ticketChoices : {name:string, value:string}[] = []
+ opendiscord.configs.get("opendiscord:options").data.forEach((option) => {
+ if (option.type != "ticket") return
+ ticketChoices.push({name:option.name, value:option.id})
+ })
+
+ instance.filteredAutocomplete(ticketChoices)
+ }))
+}
\ No newline at end of file
diff --git a/src/commands/close.ts b/src/commands/close.ts
index ebb4616..8aac86a 100644
--- a/src/commands/close.ts
+++ b/src/commands/close.ts
@@ -5,6 +5,7 @@ import {opendiscord, api, utilities} from "../index"
import * as discord from "discord.js"
const generalConfig = opendiscord.configs.get("opendiscord:general")
+const lang = opendiscord.languages
export const registerCommandResponders = async () => {
//CLOSE COMMAND RESPONDER
@@ -64,6 +65,18 @@ export const registerCommandResponders = async () => {
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 (!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 reason = instance.options.getString("reason",false)
diff --git a/src/commands/delete.ts b/src/commands/delete.ts
index 11d315f..24ea2ec 100644
--- a/src/commands/delete.ts
+++ b/src/commands/delete.ts
@@ -5,6 +5,7 @@ import {opendiscord, api, utilities} from "../index"
import * as discord from "discord.js"
const generalConfig = opendiscord.configs.get("opendiscord:general")
+const lang = opendiscord.languages
export const registerCommandResponders = async () => {
//DELETE COMMAND RESPONDER
@@ -59,10 +60,30 @@ export const registerCommandResponders = async () => {
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 (!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 reason = instance.options.getString("reason",false)
const withoutTranscript = instance.options.getBoolean("notranscript",false) ?? false
+ //don't allow deleteWithoutTranscript to non-global-admins when enabled
+ if (withoutTranscript && 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()
+ }
+ }
+
//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}))
diff --git a/src/commands/priority.ts b/src/commands/priority.ts
new file mode 100644
index 0000000..cbf6744
--- /dev/null
+++ b/src/commands/priority.ts
@@ -0,0 +1,96 @@
+///////////////////////////////////////
+//PRIORITY COMMAND
+///////////////////////////////////////
+import {opendiscord, api, utilities} from "../index"
+import * as discord from "discord.js"
+
+const generalConfig = opendiscord.configs.get("opendiscord:general")
+
+export const registerCommandResponders = async () => {
+ //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:permissions",1,async (instance,params,source,cancel) => {
+ const permissionMode = generalConfig.data.system.permissions.priority
+
+ 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:priority",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 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 scope = instance.options.getSubCommand()
+ if (!scope || (scope != "set" && scope != "get")) return
+
+ if (scope == "set"){
+ const priorityName = instance.options.getString("priority",true)
+ const reason = instance.options.getString("reason",false)
+
+ 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 provide a valid priority level.",customTitle:"Unknown Priority Level"}))
+ return cancel()
+ }
+
+ //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}))
+
+ }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}))
+ }
+ }),
+ new api.ODWorker("opendiscord:logs",-1,(instance,params,source,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}
+ ])
+ })
+ ])
+}
\ No newline at end of file
diff --git a/src/commands/ticket.ts b/src/commands/ticket.ts
index 40e2cc6..45fcddb 100644
--- a/src/commands/ticket.ts
+++ b/src/commands/ticket.ts
@@ -5,6 +5,7 @@ import {opendiscord, api, utilities} from "../index"
import * as discord from "discord.js"
const generalConfig = opendiscord.configs.get("opendiscord:general")
+const lang = opendiscord.languages
export const registerCommandResponders = async () => {
//TICKET COMMAND RESPONDER
@@ -70,6 +71,7 @@ export const registerCommandResponders = async () => {
else if (res.reason == "global-user-limit") instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-no-permissions-limits").build(source,{guild:instance.guild,channel:instance.channel,user:instance.user,limit:"global-user"}))
else if (res.reason == "option-limit") instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-no-permissions-limits").build(source,{guild:instance.guild,channel:instance.channel,user:instance.user,limit:"option"}))
else if (res.reason == "option-user-limit") instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-no-permissions-limits").build(source,{guild:instance.guild,channel:instance.channel,user:instance.user,limit:"option-user"}))
+ else if (res.reason == "custom") instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error").build(source,{guild:instance.guild,channel:instance.channel,user:instance.user,layout:"simple",error:res.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(source,{guild:instance.guild,channel:instance.channel,user:instance.user,error:"Unknown invalid_permission reason => calculation failed #1",layout:"advanced"}))
return cancel()
}
@@ -132,6 +134,7 @@ export const registerButtonResponders = async () => {
else if (res.reason == "global-user-limit") instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-no-permissions-limits").build(source,{guild:instance.guild,channel:instance.channel,user:instance.user,limit:"global-user"}))
else if (res.reason == "option-limit") instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-no-permissions-limits").build(source,{guild:instance.guild,channel:instance.channel,user:instance.user,limit:"option"}))
else if (res.reason == "option-user-limit") instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-no-permissions-limits").build(source,{guild:instance.guild,channel:instance.channel,user:instance.user,limit:"option-user"}))
+ else if (res.reason == "custom") instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error").build(source,{guild:instance.guild,channel:instance.channel,user:instance.user,layout:"simple",error:res.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(source,{guild:instance.guild,channel:instance.channel,user:instance.user,error:"Unknown invalid_permission reason => calculation failed #1",layout:"advanced"}))
return cancel()
}
@@ -187,6 +190,7 @@ export const registerDropdownResponders = async () => {
else if (res.reason == "global-user-limit") instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-no-permissions-limits").build(source,{guild:instance.guild,channel:instance.channel,user:instance.user,limit:"global-user"}))
else if (res.reason == "option-limit") instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-no-permissions-limits").build(source,{guild:instance.guild,channel:instance.channel,user:instance.user,limit:"option"}))
else if (res.reason == "option-user-limit") instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-no-permissions-limits").build(source,{guild:instance.guild,channel:instance.channel,user:instance.user,limit:"option-user"}))
+ else if (res.reason == "custom") instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error").build(source,{guild:instance.guild,channel:instance.channel,user:instance.user,layout:"simple",error:res.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(source,{guild:instance.guild,channel:instance.channel,user:instance.user,error:"Unknown invalid_permission reason => calculation failed #1",layout:"advanced"}))
return cancel()
}
diff --git a/src/commands/topic.ts b/src/commands/topic.ts
new file mode 100644
index 0000000..f8423c7
--- /dev/null
+++ b/src/commands/topic.ts
@@ -0,0 +1,83 @@
+///////////////////////////////////////
+//TOPIC COMMAND
+///////////////////////////////////////
+import {opendiscord, api, utilities} from "../index"
+import * as discord from "discord.js"
+
+const generalConfig = opendiscord.configs.get("opendiscord:general")
+
+export const registerCommandResponders = async () => {
+ //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:permissions",1,async (instance,params,source,cancel) => {
+ const permissionMode = generalConfig.data.system.permissions.topic
+
+ 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:topic",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 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 scope = instance.options.getSubCommand()
+ if (!scope || (scope != "set")) return
+
+ if (scope == "set"){
+ 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}))
+ }
+ }),
+ new api.ODWorker("opendiscord:logs",-1,(instance,params,source,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}
+ ])
+ })
+ ])
+}
\ No newline at end of file
diff --git a/src/commands/transfer.ts b/src/commands/transfer.ts
new file mode 100644
index 0000000..18c8c8b
--- /dev/null
+++ b/src/commands/transfer.ts
@@ -0,0 +1,81 @@
+///////////////////////////////////////
+//TRANSFER COMMAND
+///////////////////////////////////////
+import {opendiscord, api, utilities} from "../index"
+import * as discord from "discord.js"
+
+const generalConfig = opendiscord.configs.get("opendiscord:general")
+
+export const registerCommandResponders = async () => {
+ //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:permissions",1,async (instance,params,source,cancel) => {
+ const permissionMode = generalConfig.data.system.permissions.transfer
+
+ 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:transfer",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 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 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}))
+ }),
+ new api.ODWorker("opendiscord:logs",-1,(instance,params,source,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}
+ ])
+ })
+ ])
+}
\ No newline at end of file
diff --git a/src/core/api/api.ts b/src/core/api/api.ts
index 10e7906..7286d8f 100644
--- a/src/core/api/api.ts
+++ b/src/core/api/api.ts
@@ -60,4 +60,5 @@ export * from "./openticket/panel"
export * from "./openticket/ticket"
export * from "./openticket/blacklist"
export * from "./openticket/transcript"
-export * from "./openticket/role"
\ No newline at end of file
+export * from "./openticket/role"
+export * from "./openticket/priority"
\ No newline at end of file
diff --git a/src/core/api/defaults/action.ts b/src/core/api/defaults/action.ts
index f3ef684..c86269b 100644
--- a/src/core/api/defaults/action.ts
+++ b/src/core/api/defaults/action.ts
@@ -10,6 +10,7 @@ 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"
/**## ODActionManagerIds_Default `interface`
* This interface is a list of ids available in the `ODActionManager_Default` class.
@@ -19,7 +20,7 @@ export interface ODActionManagerIds_Default {
"opendiscord:create-ticket-permissions":{
source:"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"|null,cooldownUntil?:Date},
+ 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":{
@@ -111,7 +112,25 @@ export interface ODActionManagerIds_Default {
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",
+ 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",
+ 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",
+ 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"
+ },
}
/**## ODActionManager_Default `default_class`
diff --git a/src/core/api/defaults/builder.ts b/src/core/api/defaults/builder.ts
index 6d556c3..96c652b 100644
--- a/src/core/api/defaults/builder.ts
+++ b/src/core/api/defaults/builder.ts
@@ -13,6 +13,7 @@ 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.
@@ -224,7 +225,7 @@ export class ODFile_Default,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"},
@@ -291,6 +295,11 @@ export interface ODEmbedManagerIds_Default {
"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`
@@ -353,7 +362,7 @@ export interface ODMessageManagerIds_Default {
"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"},workers:"opendiscord:error"},
+ "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"},
@@ -367,7 +376,7 @@ export interface ODMessageManagerIds_Default {
"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"|"other",params:{guild:discord.Guild|null,channel:discord.TextBasedChannel,user:discord.User,originalName:string,newName:string},workers:"opendiscord:error-channel-rename"},
+ "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"},
@@ -412,6 +421,9 @@ export interface ODMessageManagerIds_Default {
"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"},
@@ -422,6 +434,11 @@ export interface ODMessageManagerIds_Default {
"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`
diff --git a/src/core/api/defaults/checker.ts b/src/core/api/defaults/checker.ts
index db3bbe1..9cc654c 100644
--- a/src/core/api/defaults/checker.ts
+++ b/src/core/api/defaults/checker.ts
@@ -183,14 +183,8 @@ export class ODCheckerRenderer_Default extends ODCheckerRenderer {
return finalComponents
}
/**Get the length of the longest string in the array. */
- #getLongestLength(text:string[]): number {
- let finalLength = 0
- text.forEach((t) => {
- const l = ansis.strip(t).length
- if (l > finalLength) finalLength = l
- })
-
- return finalLength
+ #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 {
@@ -247,9 +241,18 @@ export type ODCheckerTranslationRegisterMessageIds_Default = (
"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"|
@@ -261,6 +264,7 @@ export type ODCheckerTranslationRegisterMessageIds_Default = (
"opendiscord:number-starts-with"|
"opendiscord:number-ends-with"|
"opendiscord:number-contains"|
+ "opendiscord:number-inverted-contains"|
"opendiscord:number-choices"|
"opendiscord:number-float"|
"opendiscord:number-negative"|
diff --git a/src/core/api/defaults/client.ts b/src/core/api/defaults/client.ts
index d451afa..7b5a540 100644
--- a/src/core/api/defaults/client.ts
+++ b/src/core/api/defaults/client.ts
@@ -2,7 +2,17 @@
//DEFAULT CLIENT MODULE
///////////////////////////////////////
import { ODValidId } from "../modules/base"
-import { ODClientManager, ODSlashCommand, ODTextCommand, ODSlashCommandManager, ODTextCommandManager, ODSlashCommandInteractionCallback, ODTextCommandInteractionCallback } from "../modules/client"
+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.
@@ -13,6 +23,7 @@ import { ODClientManager, ODSlashCommand, ODTextCommand, ODSlashCommandManager,
export class ODClientManager_Default extends ODClientManager {
declare slashCommands: ODSlashCommandManager_Default
declare textCommands: ODTextCommandManager_Default
+ declare contextMenus: ODContextMenuManager_Default
}
/**## ODSlashCommandManagerIds_Default `interface`
@@ -39,6 +50,9 @@ export interface ODSlashCommandManagerIds_Default {
"opendiscord:clear":ODSlashCommand,
"opendiscord:autoclose":ODSlashCommand,
"opendiscord:autodelete":ODSlashCommand,
+ "opendiscord:topic":ODSlashCommand,
+ "opendiscord:priority":ODSlashCommand,
+ "opendiscord:transfer":ODSlashCommand,
}
/**## ODSlashCommandManager_Default `default_class`
@@ -108,7 +122,11 @@ export interface ODTextCommandManagerIds_Default {
"opendiscord:autoclose-disable":ODTextCommand,
"opendiscord:autoclose-enable":ODTextCommand,
"opendiscord:autodelete-disable":ODTextCommand,
- "opendiscord:autodelete-enable":ODTextCommand
+ "opendiscord:autodelete-enable":ODTextCommand,
+ "opendiscord:topic-set":ODTextCommand,
+ "opendiscord:priority-set":ODTextCommand,
+ "opendiscord:priority-get":ODTextCommand,
+ "opendiscord:transfer":ODTextCommand,
}
/**## ODTextCommandManager_Default `default_class`
@@ -142,4 +160,48 @@ export class ODTextCommandManager_Default extends ODTextCommandManager {
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/config.ts b/src/core/api/defaults/config.ts
index afdd836..69a1850 100644
--- a/src/core/api/defaults/config.ts
+++ b/src/core/api/defaults/config.ts
@@ -4,18 +4,38 @@
import { ODValidButtonColor, ODValidId } from "../modules/base"
import * as discord from "discord.js"
import { ODConfigManager, ODConfig, ODJsonConfig } from "../modules/config"
-import { ODClientActivityStatus, ODClientActivityType } from "../modules/client"
+import { ODClientActivityMode, ODClientActivityType } from "../modules/client"
import { ODRoleUpdateMode } from "../openticket/role"
+/** (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.
* 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:questions":ODJsonConfig_DefaultQuestions,
"opendiscord:transcripts":ODJsonConfig_DefaultTranscripts
}
@@ -56,10 +76,12 @@ export interface ODJsonConfig_DefaultStatusType {
enabled:boolean,
/**The type of status (e.g. playing, listening, custom, ...) */
type:Exclude,
+ /**The mode/status of the bot (e.g. online, invisible, idle, do not disturb) */
+ mode:ODClientActivityMode
/**The text for the status. */
text:string,
- /**The status of the bot (e.g. online, invisible, idle, do not disturb) */
- status:ODClientActivityStatus
+ /**Additional text for the status. (visible below 'text') */
+ state:string,
}
/**## ODJsonConfig_DefaultMessageSettingsType `interface`
@@ -111,6 +133,30 @@ export interface ODJsonConfig_DefaultSystemLimits {
userMaximum:number
}
+/**## ODJsonConfig_DefaultSystemChannelTopic `interface`
+ * All global channel topic settings.
+ */
+export interface ODJsonConfig_DefaultSystemChannelTopic {
+ /**Show the option name in the channel topic. */
+ showOptionName:boolean,
+ /**Show the option description in the channel topic. */
+ showOptionDescription:boolean,
+ /**Show the option topic text in the channel topic (configured in the options config). */
+ showOptionTopic:boolean,
+ /**Show the current priority in the channel topic (auto-updated). */
+ showPriority:boolean,
+ /**Show the current close/reopen status in the channel topic (auto-updated). */
+ showClosed:boolean,
+ /**Show the current claim status in the channel topic (auto-updated). */
+ showClaimed:boolean,
+ /**Show the current pin status in the channel topic (auto-updated). */
+ showPinned:boolean,
+ /**Show the creator of the ticket in the channel topic (auto-updated on transfer). */
+ showCreator:boolean,
+ /**Show the first 5 participants of the ticket in the channel topic (auto-updated). */
+ showParticipants:boolean
+}
+
/**## ODJsonConfig_DefaultSystemPermissions `interface`
* Configure permissions for all Open Ticket commands & actions.
*/
@@ -133,7 +179,10 @@ export interface ODJsonConfig_DefaultSystemPermissions {
stats:ODJsonConfig_DefaultCmdPermissionSettingsType,
clear:ODJsonConfig_DefaultCmdPermissionSettingsType,
autoclose:ODJsonConfig_DefaultCmdPermissionSettingsType,
- autodelete:ODJsonConfig_DefaultCmdPermissionSettingsType
+ autodelete:ODJsonConfig_DefaultCmdPermissionSettingsType,
+ transfer:ODJsonConfig_DefaultCmdPermissionSettingsType,
+ topic:ODJsonConfig_DefaultCmdPermissionSettingsType,
+ priority:ODJsonConfig_DefaultCmdPermissionSettingsType,
}
/**## ODJsonConfig_DefaultSystemMessages `interface`
@@ -151,35 +200,60 @@ export interface ODJsonConfig_DefaultSystemMessages {
renaming:ODJsonConfig_DefaultMessageSettingsType,
moving:ODJsonConfig_DefaultMessageSettingsType,
blacklisting:ODJsonConfig_DefaultMessageSettingsType,
- roleAdding:ODJsonConfig_DefaultMessageSettingsType,
- roleRemoving:ODJsonConfig_DefaultMessageSettingsType
+ transferring:ODJsonConfig_DefaultMessageSettingsType,
+ topicChange:ODJsonConfig_DefaultMessageSettingsType,
+ priorityChange:ODJsonConfig_DefaultMessageSettingsType,
+ reactionRole:ODJsonConfig_DefaultMessageSettingsType,
}
/**## ODJsonConfig_DefaultSystem `interface`
* All settings related to the ticket system.
*/
export interface ODJsonConfig_DefaultSystem {
- /**Remove all participants (except admins) from the ticket when it's closed. */
- removeParticipantsOnClose:boolean,
- /**Reply with an ephemeral message when a ticket is created. */
- replyOnTicketCreation:boolean,
- /**Reply with an ephemeral message when reaction roles are changed. */
- replyOnReactionRole:boolean,
- /**Use a translated config checker in the console. */
- useTranslatedConfigChecker:boolean,
/**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. */
sendErrorOnUnknownCommand:boolean,
/**Display the question fields (in a ticket message) in code blocks. */
questionFieldsInCodeBlock:boolean,
+ /**Display embed fields together with question fields (in a ticket message). */
+ displayFieldsWithQuestions:boolean,
+ /**Show global admins roles together with ticket admins in panel embeds. */
+ showGlobalAdminsInPanelRoles:boolean,
/**Disable the (โ
โ) buttons and directly run the action. */
disableVerifyBars:boolean,
/**Display error embeds/messages with red instead of the default bot color. */
useRedErrorEmbeds:boolean,
+ /**Always show the reason field in embeds, even when there is no reason provided. */
+ alwaysShowReason:boolean,
/**The emoji style used in the bot. This will affect all embeds, titles & messages in the bot. */
emojiStyle:"before"|"after"|"double"|"disabled",
-
+ /**The emoji used when pinning tickets. This is '๐' by default. */
+ pinEmoji:string,
+
+ /**Reply with an ephemeral message when a ticket is created. */
+ replyOnTicketCreation:boolean,
+ /**Reply with an ephemeral message when reaction roles are changed. */
+ replyOnReactionRole:boolean,
+ /**Ask for the priority of this ticket on ticket creation. This will happen in a dropdown in the ticket message. */
+ askPriorityOnTicketCreation:boolean,
+ /**Remove all participants (except admins) from the ticket when it's closed. */
+ removeParticipantsOnClose:boolean,
+ /**Disable autoclose for a ticket when it has been closed and re-opened. */
+ disableAutocloseAfterReopen:boolean,
+ /**Only allow autodelete when the ticket is already closed. */
+ autodeleteRequiresClosedTicket:boolean,
+ /**When enabled, only global admins are able to delete a ticket without transcript. */
+ adminOnlyDeleteWithoutTranscript:boolean,
+ /**Only allow ticket closing when at least 1 message has been sent by the creator. (admins are able to bypass) */
+ allowCloseBeforeMessage:boolean,
+ /**Only allow ticket closing when at least 1 message has been sent by a global or ticket admin. (admins are able to bypass) */
+ allowCloseBeforeAdminMessage:boolean,
+ /**Use a translated config checker in the console. */
+ useTranslatedConfigChecker:boolean,
+ /**Pin the (first) ticket message in the channel. This simulates old behaviour like Open Ticket v1, v2 & v3. */
+ pinFirstTicketMessage:boolean,
+
/**Enable/disable the ticket claim & unclaim button in the ticket message. */
enableTicketClaimButtons:boolean,
/**Enable/disable the ticket close & re-open button in the ticket message. */
@@ -199,6 +273,9 @@ export interface ODJsonConfig_DefaultSystem {
/**All settings related to global ticket limits. */
limits:ODJsonConfig_DefaultSystemLimits,
+ /**All global channel topic settings. */
+ channelTopic:ODJsonConfig_DefaultSystemChannelTopic,
+
/**Configure permissions for all Open Ticket commands & actions. */
permissions:ODJsonConfig_DefaultSystemPermissions,
@@ -219,7 +296,7 @@ export interface ODJsonConfig_DefaultGeneralData {
tokenFromENV:boolean,
/**The main (hex) color used in almost every embed in the bot. */
- mainColor:discord.ColorResolvable,
+ mainColor:discord.ColorResolvable|string,
/**The language to use. Can be the id of the language or the id without the prefix when using `opendiscord:...`. */
language:string,
/**The prefix used in all text-commands. */
@@ -295,7 +372,7 @@ export interface ODJsonConfig_DefaultOptionEmbedSettingsType {
/**The description of this embed. */
description:string,
/**A custom color for this embed. (The default bot color is used when empty) */
- customColor:discord.ColorResolvable,
+ customColor:discord.ColorResolvable|string,
/**A URL to an image displayed in the embed. */
image:string,
@@ -332,7 +409,7 @@ export interface ODJsonConfig_DefaultOptionTicketChannelType {
/**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-id"|"random-number"|"random-hex"|"counter-dynamic"|"counter-fixed",
+ 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. */
@@ -346,8 +423,8 @@ export interface ODJsonConfig_DefaultOptionTicketChannelType {
/**The category to move the ticket to when claimed by this user. */
category:string
}[],
- /**The channel description/topic shown at the top of the channel in discord. */
- description:string
+ /**The channel topic shown at the top of the channel in discord. */
+ topic:string
}
/**## ODJsonConfig_DefaultOptionTicketType `interface`
@@ -408,14 +485,14 @@ export interface ODJsonConfig_DefaultOptionTicketType extends ODJsonConfig_Defau
/**Disable autodeleting when the ticket is claimed by someone. */
disableOnClaim:boolean
},
- /**All settings related to the cooldown of this ticket type */
+ /**All settings related to the cooldown of this ticket type. */
cooldown:{
/**Enable cooldown (per user) */
enabled:boolean,
/**The amount of minutes a user needs to wait before being able to create a ticket again. */
cooldownMinutes:number
},
- /**All settings related to the limits of this ticket type */
+ /**All settings related to the limits of this ticket type. */
limits:{
/**Enable option ticket limits. */
enabled:boolean,
@@ -423,6 +500,13 @@ export interface ODJsonConfig_DefaultOptionTicketType extends ODJsonConfig_Defau
globalMaximum:number,
/**The maximum amount of tickets of this type that a user is allowed to create at the same time. */
userMaximum:number
+ },
+ /**All settings related to the slow mode of this ticket type. */
+ slowMode:{
+ /**Enable channel slow mode. */
+ enabled:boolean,
+ /**The amount of seconds users need to wait between sending messages. */
+ slowModeSeconds:number
}
}
@@ -451,6 +535,11 @@ export interface ODJsonConfig_DefaultOptionRoleType extends ODJsonConfig_Default
addOnMemberJoin:boolean
}
+/**## ODJsonConfig_DefaultOptionsData `type`
+ * All contents of the `options.json` config file.
+ */
+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!
@@ -458,11 +547,7 @@ export interface ODJsonConfig_DefaultOptionRoleType extends ODJsonConfig_Default
* This default class is made for the `options.json` config!
*/
export class ODJsonConfig_DefaultOptions extends ODJsonConfig {
- declare data: (
- ODJsonConfig_DefaultOptionTicketType|
- ODJsonConfig_DefaultOptionWebsiteType|
- ODJsonConfig_DefaultOptionRoleType
- )[]
+ declare data: ODJsonConfig_DefaultOptionsData
}
/**## ODJsonConfig_DefaultPanelEmbedSettingsType `interface`
@@ -477,7 +562,7 @@ export interface ODJsonConfig_DefaultPanelEmbedSettingsType {
description:string,
/**A custom color for this embed. (The default bot color is used when empty) */
- customColor:discord.ColorResolvable,
+ customColor:discord.ColorResolvable|string,
/**An optional URL used in the title of the embed. */
url:string,
@@ -545,6 +630,11 @@ export interface ODJsonConfig_DefaultPanelType {
settings:ODJsonConfig_DefaultPanelSettingsType
}
+/**## ODJsonConfig_DefaultPanelsData `type`
+ * All contents of the `panels.json` config file.
+ */
+export type ODJsonConfig_DefaultPanelsData = ODJsonConfig_DefaultPanelType[]
+
/**## 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!
@@ -552,7 +642,7 @@ export interface ODJsonConfig_DefaultPanelType {
* This default class is made for the `panels.json` config!
*/
export class ODJsonConfig_DefaultPanels extends ODJsonConfig {
- declare data: ODJsonConfig_DefaultPanelType[]
+ declare data: ODJsonConfig_DefaultPanelsData
}
/**## ODJSonConfig_DefaultQuestionLengthSettings `interface`
@@ -605,6 +695,11 @@ export interface ODJsonConfig_DefaultParagraphQuestionType {
length:ODJSonConfig_DefaultQuestionLengthSettings
}
+/**## ODJsonConfig_DefaultQuestionsData `type`
+ * All contents of the `questions.json` config file.
+ */
+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!
@@ -612,10 +707,7 @@ export interface ODJsonConfig_DefaultParagraphQuestionType {
* This default class is made for the `questions.json` config!
*/
export class ODJsonConfig_DefaultQuestions extends ODJsonConfig {
- declare data: (
- ODJsonConfig_DefaultShortQuestionType|
- ODJsonConfig_DefaultParagraphQuestionType
- )[]
+ declare data: ODJsonConfig_DefaultQuestionsData
}
/**## ODJsonConfig_DefaultTranscriptsTextLayout `interface`
@@ -689,6 +781,47 @@ export interface ODJsonConfig_DefaultTranscriptsHtmlLayout {
}
}
+/**## ODJsonConfig_DefaultTranscriptsData `interface`
+ * All contents of the `transcripts.json` config file.
+ */
+export interface ODJsonConfig_DefaultTranscriptsData {
+ /**All general settings related to transcripts. */
+ general:{
+ /**Are transcripts enabled? */
+ enabled:boolean,
+
+ /**Enable sending the generated transcript in a channel. */
+ enableChannel:boolean,
+ /**Enable sending the generated transcript to the DM of the ticket creator. */
+ enableCreatorDM:boolean,
+ /**Enable sending the generated transcript to the DM of the participants. */
+ enableParticipantDM:boolean,
+ /**Enable sending the generated transcript to the DM of all admins which were active in the ticket. */
+ enableActiveAdminDM:boolean,
+ /**Enable sending the generated transcript to the DM of all admins which were assigned to the ticket. */
+ enableEveryAdminDM:boolean,
+
+ /**A discord channel id for the `"enableChannel"` setting. */
+ channel:string,
+ /**Want to use text or HTML transcripts? */
+ mode:"html"|"text"
+ },
+ /**All settings related to the embed from the transcripts. (UNIMPLEMENTED!!) */
+ embedSettings:{
+ /**Unimplemented feature */
+ customColor:discord.ColorResolvable|string,
+ /**Unimplemented feature */
+ listAllParticipants:boolean,
+ /**Unimplemented feature */
+ includeTicketStats:boolean
+ },
+ /**The layout of the text transcripts. */
+ textTranscriptStyle:ODJsonConfig_DefaultTranscriptsTextLayout,
+ /**The layout of the HTML transcripts. */
+ htmlTranscriptStyle:ODJsonConfig_DefaultTranscriptsHtmlLayout
+}
+
+
/**## 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!
@@ -696,40 +829,5 @@ export interface ODJsonConfig_DefaultTranscriptsHtmlLayout {
* This default class is made for the `transcripts.json` config!
*/
export class ODJsonConfig_DefaultTranscripts extends ODJsonConfig {
- declare data: {
- /**All general settings related to transcripts. */
- general:{
- /**Are transcripts enabled? */
- enabled:boolean,
-
- /**Enable sending the generated transcript in a channel. */
- enableChannel:boolean,
- /**Enable sending the generated transcript to the DM of the ticket creator. */
- enableCreatorDM:boolean,
- /**Enable sending the generated transcript to the DM of the participants. */
- enableParticipantDM:boolean,
- /**Enable sending the generated transcript to the DM of all admins which were active in the ticket. */
- enableActiveAdminDM:boolean,
- /**Enable sending the generated transcript to the DM of all admins which were assigned to the ticket. */
- enableEveryAdminDM:boolean,
-
- /**A discord channel id for the `"enableChannel"` setting. */
- channel:string,
- /**Want to use text or HTML transcripts? */
- mode:"html"|"text"
- },
- /**All settings related to the embed from the transcripts. (UNIMPLEMENTED!!) */
- embedSettings:{
- /**Unimplemented feature */
- customColor:discord.ColorResolvable,
- /**Unimplemented feature */
- listAllParticipants:boolean,
- /**Unimplemented feature */
- includeTicketStats:boolean
- },
- /**The layout of the text transcripts. */
- textTranscriptStyle:ODJsonConfig_DefaultTranscriptsTextLayout,
- /**The layout of the HTML transcripts. */
- htmlTranscriptStyle:ODJsonConfig_DefaultTranscriptsHtmlLayout
- }
+ declare data: ODJsonConfig_DefaultTranscriptsData
}
\ No newline at end of file
diff --git a/src/core/api/defaults/event.ts b/src/core/api/defaults/event.ts
index 1833156..86496fc 100644
--- a/src/core/api/defaults/event.ts
+++ b/src/core/api/defaults/event.ts
@@ -19,9 +19,9 @@ 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, ODSlashCommandManager_Default, ODTextCommandManager_Default } from "./client"
+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 { ODButtonResponderManager_Default, ODCommandResponderManager_Default, ODDropdownResponderManager_Default, ODModalResponderManager_Default, ODResponderManager_Default } from "./responder"
+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"
@@ -42,6 +42,7 @@ 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.
@@ -122,12 +123,22 @@ export interface ODEventIds_Default {
"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>
@@ -191,7 +202,13 @@ export interface ODEventIds_Default {
"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>
@@ -249,6 +266,10 @@ export interface ODEventIds_Default {
"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>,
diff --git a/src/core/api/defaults/flag.ts b/src/core/api/defaults/flag.ts
index bf2d5eb..8830cc2 100644
--- a/src/core/api/defaults/flag.ts
+++ b/src/core/api/defaults/flag.ts
@@ -23,6 +23,8 @@ export interface ODFlagManagerIds_Default {
"opendiscord:force-slash-update":ODFlag,
"opendiscord:no-compile":ODFlag,
"opendiscord:compile-only":ODFlag,
+ "opendiscord:silent":ODFlag,
+ "opendiscord:cli":ODFlag,
}
/**## ODFlagManager_Default `default_class`
diff --git a/src/core/api/defaults/helpmenu.ts b/src/core/api/defaults/helpmenu.ts
index a8a986d..9864f18 100644
--- a/src/core/api/defaults/helpmenu.ts
+++ b/src/core/api/defaults/helpmenu.ts
@@ -4,6 +4,16 @@
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.
@@ -172,7 +182,8 @@ export interface ODHelpMenuManagerCategoryIds_DefaultTicketUser {
"opendiscord:claim":ODHelpMenuCommandComponent,
"opendiscord:unclaim":ODHelpMenuCommandComponent,
"opendiscord:add":ODHelpMenuCommandComponent,
- "opendiscord:remove":ODHelpMenuCommandComponent
+ "opendiscord:remove":ODHelpMenuCommandComponent,
+ "opendiscord:transfer":ODHelpMenuCommandComponent,
}
/**## ODHelpMenuCategory_DefaultTicketUser `default_class`
@@ -255,7 +266,11 @@ export interface ODHelpMenuManagerCategoryIds_DefaultAdvanced {
"opendiscord:stats-ticket":ODHelpMenuCommandComponent,
"opendiscord:stats-user":ODHelpMenuCommandComponent,
"opendiscord:autoclose-disable":ODHelpMenuCommandComponent,
- "opendiscord:autoclose-enable":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`
diff --git a/src/core/api/defaults/language.ts b/src/core/api/defaults/language.ts
index 8b61260..64270e2 100644
--- a/src/core/api/defaults/language.ts
+++ b/src/core/api/defaults/language.ts
@@ -4,6 +4,60 @@
import { ODValidId } from "../modules/base"
import { ODLanguageManager, ODLanguage } from "../modules/language"
+/** (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.
+ * 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,
+ //ADD NEW LANGUAGES HERE!!!
+}
+
/**## ODLanguageManagerTranslations_Default `type`
* This interface is a list of ids available in the `ODLanguageManager_Default` class.
* It's used to generate typescript declarations for this class.
@@ -22,6 +76,7 @@ export type ODLanguageManagerTranslations_Default = (
"checker.system.dataPath"|
"checker.system.dataDocs"|
"checker.system.dataMessages"|
+
"checker.messages.invalidType"|
"checker.messages.propertyMissing"|
"checker.messages.propertyOptional"|
@@ -29,6 +84,7 @@ export type ODLanguageManagerTranslations_Default = (
"checker.messages.nullInvalid"|
"checker.messages.switchInvalidType"|
"checker.messages.objectSwitchInvalid"|
+
"checker.messages.stringTooShort"|
"checker.messages.stringTooLong"|
"checker.messages.stringLengthInvalid"|
@@ -37,6 +93,15 @@ export type ODLanguageManagerTranslations_Default = (
"checker.messages.stringContains"|
"checker.messages.stringChoices"|
"checker.messages.stringRegex"|
+ "checker.messages.stringInvertedContains"|
+ "checker.messages.stringLowercase"|
+ "checker.messages.stringUppercase"|
+ "checker.messages.stringSpecialCharacters"|
+ "checker.messages.stringNoSpaces"|
+ "checker.messages.stringCapitalWord"|
+ "checker.messages.stringCapitalSentence"|
+ "checker.messages.stringPunctuation"|
+
"checker.messages.numberTooShort"|
"checker.messages.numberTooLong"|
"checker.messages.numberLengthInvalid"|
@@ -53,8 +118,12 @@ export type ODLanguageManagerTranslations_Default = (
"checker.messages.numberNegative"|
"checker.messages.numberPositive"|
"checker.messages.numberZero"|
+ "checker.messages.numberNan"|
+ "checker.messages.numberInvertedContains"|
+
"checker.messages.booleanTrue"|
"checker.messages.booleanFalse"|
+
"checker.messages.arrayEmptyDisabled"|
"checker.messages.arrayEmptyRequired"|
"checker.messages.arrayTooShort"|
@@ -62,6 +131,7 @@ export type ODLanguageManagerTranslations_Default = (
"checker.messages.arrayLengthInvalid"|
"checker.messages.arrayInvalidTypes"|
"checker.messages.arrayDouble"|
+
"checker.messages.discordInvalidId"|
"checker.messages.discordInvalidIdOptions"|
"checker.messages.discordInvalidToken"|
@@ -83,6 +153,8 @@ export type ODLanguageManagerTranslations_Default = (
"checker.messages.unusedOption"|
"checker.messages.unusedQuestion"|
"checker.messages.dropdownOption"|
+ "checker.messages.customInvalidVersion"|
+
"actions.buttons.create"|
"actions.buttons.close"|
"actions.buttons.delete"|
@@ -120,6 +192,7 @@ export type ODLanguageManagerTranslations_Default = (
"actions.titles.blacklistAddDm"|
"actions.titles.blacklistRemoveDm"|
"actions.titles.clear"|
+ "actions.titles.clearTickets"|
"actions.titles.roles"|
"actions.titles.autoclose"|
@@ -129,6 +202,11 @@ export type ODLanguageManagerTranslations_Default = (
"actions.titles.autodeleteEnabled"|
"actions.titles.autodeleteDisabled"|
+ "actions.titles.topicSet"|
+ "actions.titles.prioritySet"|
+ "actions.titles.priorityGet"|
+ "actions.titles.transfer"|
+
"actions.descriptions.create"|
"actions.descriptions.close"|
"actions.descriptions.delete"|
@@ -169,6 +247,11 @@ export type ODLanguageManagerTranslations_Default = (
"actions.descriptions.ticketMessageAutodelete"|
"actions.descriptions.panelReady"|
+ "actions.descriptions.topicSet"|
+ "actions.descriptions.prioritySet"|
+ "actions.descriptions.priorityGet"|
+ "actions.descriptions.transfer"|
+
"actions.modal.closePlaceholder"|
"actions.modal.deletePlaceholder"|
"actions.modal.reopenPlaceholder"|
@@ -207,6 +290,13 @@ export type ODLanguageManagerTranslations_Default = (
"actions.logs.blacklistRemoveDm"|
"actions.logs.clearLog"|
+ "actions.logs.transferLog"|
+ "actions.logs.transferDm"|
+ "actions.logs.prioritySetLog"|
+ "actions.logs.prioritySetDm"|
+ "actions.logs.roleUpdateLog"|
+ "actions.logs.roleUpdateDm"|
+
"transcripts.success.visit"|
"transcripts.success.ready"|
"transcripts.success.textFileDescription"|
@@ -223,6 +313,17 @@ export type ODLanguageManagerTranslations_Default = (
"transcripts.errors.continue"|
"transcripts.errors.backup"|
"transcripts.errors.error"|
+ "transcripts.errors.title"|
+
+ "transcripts.text.messagesTitle"|
+ "transcripts.text.embedTitle"|
+ "transcripts.text.fileTitle"|
+ "transcripts.text.fieldsTitle"|
+ "transcripts.text.reactionsTitle"|
+ "transcripts.text.statsTitle"|
+ "transcripts.text.emptyContent"|
+ "transcripts.text.noTitle"|
+ "transcripts.text.noDesc"|
"errors.titles.internalError"|
"errors.titles.optionMissing"|
@@ -236,6 +337,7 @@ export type ODLanguageManagerTranslations_Default = (
"errors.titles.notInGuild"|
"errors.titles.channelRename"|
"errors.titles.busy"|
+ "errors.titles.permissionError"|
"errors.descriptions.askForInfo"|
"errors.descriptions.askForInfoResolve"|
@@ -258,6 +360,9 @@ export type ODLanguageManagerTranslations_Default = (
"errors.descriptions.channelRename"|
"errors.descriptions.channelRenameSource"|
"errors.descriptions.busy"|
+ "errors.descriptions.closeBeforeMessage"|
+ "errors.descriptions.closeBeforeAdminMessage"|
+ "errors.descriptions.unableToCreateTicket"|
"errors.optionInvalidReasons.stringRegex"|
"errors.optionInvalidReasons.stringMinLength"|
@@ -302,7 +407,6 @@ export type ODLanguageManagerTranslations_Default = (
"params.uppercase.added"|
"params.uppercase.removed"|
"params.uppercase.filter"|
- "params.uppercase.claimedBy"|
"params.uppercase.method"|
"params.uppercase.type"|
"params.uppercase.blacklisted"|
@@ -328,6 +432,27 @@ export type ODLanguageManagerTranslations_Default = (
"params.uppercase.pinned"|
"params.uppercase.creationDate"|
+ "params.uppercase.noone"|
+ "params.uppercase.open"|
+ "params.uppercase.closed"|
+ "params.uppercase.priority"|
+ "params.uppercase.status"|
+ "params.uppercase.participants"|
+ "params.uppercase.yes"|
+ "params.uppercase.no"|
+ "params.uppercase.option"|
+ "params.uppercase.topic"|
+ "params.uppercase.uptime"|
+ "params.uppercase.messages"|
+ "params.uppercase.embeds"|
+ "params.uppercase.files"|
+ "params.uppercase.components"|
+ "params.uppercase.cooldown"|
+ "params.uppercase.maxTickets"|
+ "params.uppercase.admins"|
+ "params.uppercase.roles"|
+ "params.uppercase.size"|
+
"params.lowercase.text"|
"params.lowercase.html"|
"params.lowercase.command"|
@@ -394,6 +519,18 @@ export type ODLanguageManagerTranslations_Default = (
"commands.autodeleteEnable"|
"commands.autodeleteEnableTime"|
+ "commands.topic"|
+ "commands.topicSet"|
+ "commands.topicValue"|
+ "commands.topicList"|
+ "commands.priority"|
+ "commands.prioritySet"|
+ "commands.priorityValue"|
+ "commands.priorityGet"|
+ "commands.priorityList"|
+ "commands.transfer"|
+ "commands.transferUser"|
+
"helpMenu.help"|
"helpMenu.ticket"|
"helpMenu.close"|
@@ -421,11 +558,20 @@ export type ODLanguageManagerTranslations_Default = (
"helpMenu.autodeleteDisable"|
"helpMenu.autodeleteEnable"|
+ "helpMenu.categories.general"|
+ "helpMenu.categories.basicTicket"|
+ "helpMenu.categories.advancedTicket"|
+ "helpMenu.categories.userTicket"|
+ "helpMenu.categories.admin"|
+ "helpMenu.categories.advanced"|
+ "helpMenu.categories.extra"|
+
"stats.scopes.global"|
"stats.scopes.system"|
"stats.scopes.user"|
"stats.scopes.ticket"|
"stats.scopes.participants"|
+ "stats.scopes.messages"|
"stats.properties.ticketsCreated"|
"stats.properties.ticketsClosed"|
@@ -436,54 +582,45 @@ export type ODLanguageManagerTranslations_Default = (
"stats.properties.ticketsPinned"|
"stats.properties.ticketsMoved"|
"stats.properties.usersBlacklisted"|
- "stats.properties.transcriptsCreated"
-)
+ "stats.properties.transcriptsCreated"|
+ "stats.properties.ticketsAutodeleted"|
+ "stats.properties.ticketsTransferred"|
+ "stats.properties.ticketVolume"|
+ "stats.properties.averageTickets"|
+ "stats.properties.currentTickets"|
+ "stats.properties.age"|
+ "stats.properties.responseTime"|
+ "stats.properties.resolutionTime"|
+ "stats.properties.createdOn"|
+ "stats.properties.createdBy"|
+ "stats.properties.closedOn"|
+ "stats.properties.closedBy"|
+ "stats.properties.claimedOn"|
+ "stats.properties.claimedBy"|
+ "stats.properties.pinnedOn"|
+ "stats.properties.pinnedBy"|
+ "stats.properties.deletedOn"|
+ "stats.properties.deletedBy"|
-/**## ODLanguageManagerIds_Default `interface`
- * This interface is a list of ids available in the `ODLanguageManager_Default` class.
- * 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,
-
- //ADD NEW LANGUAGES HERE!!!
-}
+ "stats.roles.developer"|
+ "stats.roles.serverOwner"|
+ "stats.roles.serverAdmin"|
+ "stats.roles.moderator"|
+ "stats.roles.support"|
+ "stats.roles.member"|
+
+ "panel.selectTicket"|
+ "panel.selectRole"|
+ "panel.selectOption"|
+
+ "priorities.urgent"|
+ "priorities.veryHigh"|
+ "priorities.high"|
+ "priorities.normal"|
+ "priorities.low"|
+ "priorities.veryLow"|
+ "priorities.none"
+)
/**## ODLanguageManager_Default `default_class`
* This is a special class that adds type definitions & typescript to the ODLanguageManager class.
diff --git a/src/core/api/defaults/progressbar.ts b/src/core/api/defaults/progressbar.ts
index 2d1b3f1..f353472 100644
--- a/src/core/api/defaults/progressbar.ts
+++ b/src/core/api/defaults/progressbar.ts
@@ -134,6 +134,9 @@ 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`
diff --git a/src/core/api/defaults/responder.ts b/src/core/api/defaults/responder.ts
index 1d80ea9..8d7b317 100644
--- a/src/core/api/defaults/responder.ts
+++ b/src/core/api/defaults/responder.ts
@@ -2,7 +2,7 @@
//DEFAULT RESPONDER MODULE
///////////////////////////////////////
import { ODValidId } from "../modules/base"
-import { ODButtonResponder, ODButtonResponderInstance, ODButtonResponderManager, ODCommandResponder, ODCommandResponderInstance, ODCommandResponderManager, ODDropdownResponder, ODDropdownResponderInstance, ODDropdownResponderManager, ODModalResponder, ODModalResponderInstance, ODModalResponderManager, ODResponderManager } from "../modules/responder"
+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`
@@ -16,6 +16,8 @@ export class ODResponderManager_Default extends ODResponderManager {
declare buttons: ODButtonResponderManager_Default
declare dropdowns: ODDropdownResponderManager_Default
declare modals: ODModalResponderManager_Default
+ declare contextMenus: ODContextMenuResponderManager_Default
+ declare autocomplete: ODAutocompleteResponderManager_Default
}
/**## ODCommandResponderManagerIds_Default `interface`
@@ -42,6 +44,9 @@ export interface ODCommandResponderManagerIds_Default {
"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"},
@@ -252,4 +257,99 @@ export class ODModalResponderManager_Default extends ODModalResponderManager {
*/
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/stat.ts b/src/core/api/defaults/stat.ts
index 35226c6..dd8486e 100644
--- a/src/core/api/defaults/stat.ts
+++ b/src/core/api/defaults/stat.ts
@@ -1,7 +1,6 @@
///////////////////////////////////////
//DEFAULT SESSION MODULE
///////////////////////////////////////
-import { Guild, TextBasedChannel, User } from "discord.js"
import { ODValidId } from "../modules/base"
import { ODStatScope, ODStatGlobalScope, ODStatsManager, ODStat, ODBasicStat, ODDynamicStat, ODValidStatValue, ODStatScopeSetMode } from "../modules/stat"
@@ -14,7 +13,8 @@ export interface ODStatsManagerIds_Default {
"opendiscord:system":ODStatGlobalScope_DefaultSystem,
"opendiscord:user":ODStatScope_DefaultUser,
"opendiscord:ticket":ODStatScope_DefaultTicket,
- "opendiscord:participants":ODStatScope_DefaultParticipants
+ "opendiscord:participants":ODStatScope_DefaultParticipants,
+ "opendiscord:messages":ODStatScope_DefaultMessages,
}
/**## ODStatsManager_Default `default_class`
@@ -60,8 +60,11 @@ export interface ODStatGlobalScopeIds_DefaultGlobal {
"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:transcripts-created":ODBasicStat,
+ "opendiscord:ticket-volume":ODDynamicStat,
+ "opendiscord:average-tickets":ODDynamicStat,
}
/**## ODStatGlobalScope_DefaultGlobal `default_class`
@@ -99,6 +102,13 @@ export class ODStatGlobalScope_DefaultGlobal extends ODStatGlobalScope {
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
@@ -120,6 +130,7 @@ export class ODStatGlobalScope_DefaultGlobal extends ODStatGlobalScope {
*/
export interface ODStatGlobalScopeIds_DefaultSystem {
"opendiscord:startup-date":ODDynamicStat,
+ "opendiscord:system-uptime":ODDynamicStat,
"opendiscord:version":ODDynamicStat
}
@@ -158,6 +169,13 @@ export class ODStatGlobalScope_DefaultSystem extends ODStatGlobalScope {
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
@@ -187,8 +205,10 @@ export interface ODStatScopeIds_DefaultUser {
"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:transcripts-created":ODBasicStat,
+ "opendiscord:current-tickets":ODDynamicStat,
}
/**## ODStatScope_DefaultUser `default_class`
@@ -226,6 +246,13 @@ export class ODStatScope_DefaultUser extends ODStatScope {
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
@@ -251,7 +278,10 @@ export interface ODStatScopeIds_DefaultTicket {
"opendiscord:claimed":ODDynamicStat,
"opendiscord:pinned":ODDynamicStat,
"opendiscord:creation-date":ODDynamicStat,
- "opendiscord:creator":ODDynamicStat
+ "opendiscord:creator":ODDynamicStat,
+ "opendiscord:ticket-age":ODDynamicStat,
+ "opendiscord:response-time":ODDynamicStat,
+ "opendiscord:resolution-time":ODDynamicStat,
}
/**## ODStatScope_DefaultTicket `default_class`
@@ -289,6 +319,13 @@ export class ODStatScope_DefaultTicket extends ODStatScope {
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
@@ -347,6 +384,13 @@ export class ODStatScope_DefaultParticipants extends ODStatScope {
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
@@ -357,6 +401,71 @@ export class ODStatScope_DefaultParticipants extends ODStatScope {
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)
}
diff --git a/src/core/api/main.ts b/src/core/api/main.ts
index 063213e..811d5d6 100644
--- a/src/core/api/main.ts
+++ b/src/core/api/main.ts
@@ -37,6 +37,7 @@ 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.
@@ -127,12 +128,14 @@ export class ODMain {
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.0.7"))
+ this.versions.add(ODVersion.fromString("opendiscord:version","v4.1.0"))
this.versions.add(ODVersion.fromString("opendiscord:api","v1.0.0"))
- this.versions.add(ODVersion.fromString("opendiscord:transcripts","v2.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"))
@@ -173,8 +176,9 @@ export class ODMain {
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.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 :) */
diff --git a/src/core/api/modules/base.ts b/src/core/api/modules/base.ts
index e50016b..d70e03d 100644
--- a/src/core/api/modules/base.ts
+++ b/src/core/api/modules/base.ts
@@ -63,16 +63,6 @@ export class ODId {
get value(){
return this.#value
}
- /**The source of the id (text before `:`). (e.g. `openticket` for all built-in ids)
- *
- * @deprecated Replaced with `getNamespace()` and will be removed in `v4.1.0`.
- */
- source: string
- /**The identifier of the id (text after `:`).
- *
- * @deprecated Replaced with `getIdentifier()` and will be removed in `v4.1.0`.
- */
- identifier: string
/**The change listener for the parent `ODManager` of this `ODId`. */
#change: ((oldId:string,newId:string) => void)|null = null
@@ -92,21 +82,9 @@ export class ODId {
if (result.length > 0) this.#value = result.join("")
else throw new ODSystemError("invalid ID at 'new ODID(id: "+id+")'")
-
- const splitted = this.#value.split(":")
- if (splitted.length > 1){
- this.source = splitted[0]
- splitted.shift()
- this.identifier = splitted.join(":")
- }else{
- this.identifier = splitted.join(":")
- this.source = ""
- }
}else{
//id is ODId
this.#value = id.#value
- this.source = id.source
- this.identifier = id.identifier
}
}
@@ -171,47 +149,6 @@ export class ODManagerChangeHelper {
}
}
-/**## ODManagerRedirectHelper `class`
- * @deprecated ### Will be removed in Open Ticket `v4.1.0`!
- *
- * This is Open Ticket ticket manager redirect helper.
- *
- * It is used to redirect a source to another source when the id isn't found.
- *
- * It will be used in **Open Discord** to allow plugins from all projects to work seamlessly!
- * ## **(โ SYSTEM ONLY!!)**
- */
-export class ODManagerRedirectHelper {
- #data: {fromSource:string,toSource:string}[] = []
-
- /****(โ SYSTEM ONLY!!)** Add a redirect to this manager. Returns `true` when overwritten. */
- add(fromSource:string, toSource:string){
- const index = this.#data.findIndex((data) => data.fromSource === fromSource)
- if (index > -1){
- //already exists
- this.#data[index] = {fromSource,toSource}
- return true
- }else{
- //doesn't exist
- this.#data.push({fromSource,toSource})
- return false
- }
- }
- /****(โ SYSTEM ONLY!!)** Remove a redirect from this manager. Returns `true` when it existed. */
- remove(fromSource:string, toSource:string){
- const index = this.#data.findIndex((data) => data.fromSource === fromSource && data.toSource == toSource)
- if (index > -1){
- //already exists
- this.#data.splice(index,1)
- return true
- }else return false
- }
- /**List all redirects from this manager. */
- list(){
- return [...this.#data]
- }
-}
-
/**## ODManagerData `class`
* This is Open Ticket manager data.
*
@@ -261,11 +198,9 @@ export class ODManager extends ODManagerChangeHe
#changeListeners: ODManagerCallback[] = []
/**An array storing all listeners when data is removed. */
#removeListeners: ODManagerCallback[] = []
- /**Handle all redirects in this `ODManager` */
- redirects: ODManagerRedirectHelper = new ODManagerRedirectHelper()
-
+
constructor(debug?:ODDebugger, debugname?:string){
- super()
+ super()
this.#debug = debug
this.#debugname = debugname
}
@@ -333,15 +268,7 @@ export class ODManager extends ODManagerChangeHe
const newId = new ODId(id)
const data = this.#data.get(newId.value)
if (data) return data
- else{
- //DEPRECATED!!!
- const redirect = this.redirects.list().find((redirect) => redirect.fromSource === newId.getNamespace())
- if (!redirect) return null
- else{
- const redirectId = new ODId(redirect.toSource+":"+newId.getIdentifier())
- return this.get(redirectId)
- }
- }
+ else return null
}
/**Remove data that matches the `ODId`. Returns the removed data. */
remove(id:ODValidId): DataType|null {
@@ -349,15 +276,8 @@ export class ODManager extends ODManagerChangeHe
const data = this.#data.get(newId.value)
if (!data){
- //DEPRECATED!!!
- const redirect = this.redirects.list().find((redirect) => redirect.fromSource === newId.getNamespace())
- if (!redirect){
- if (this.#debug) this.#debug.debug("Removed "+this.#debugname+" from manager",[{key:"id",value:newId.value},{key:"found",value:"false"}])
- return null
- }else{
- const redirectId = new ODId(redirect.toSource+":"+newId.getIdentifier())
- return this.remove(redirectId)
- }
+ 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"}])
@@ -385,15 +305,7 @@ export class ODManager extends ODManagerChangeHe
exists(id:ODValidId): boolean {
const newId = new ODId(id)
if (this.#data.has(newId.value)) return true
- else{
- //DEPRECATED!!!
- const redirect = this.redirects.list().find((redirect) => redirect.fromSource === newId.getNamespace())
- if (!redirect) return false
- else{
- const redirectId = new ODId(redirect.toSource+":"+newId.getIdentifier())
- return this.exists(redirectId)
- }
- }
+ else return false
}
/**Get all data inside this manager*/
getAll(): DataType[] {
@@ -648,8 +560,8 @@ export class ODHTTPGetRequest {
this.throwOnError = throwOnError
const newConfig = config ?? {}
newConfig.method = "GET"
- if (newConfig.headers) Object.assign(newConfig.headers,{"User-Agent":"OpenDiscordBots-OpenTicket/4.0.7"})
- else newConfig.headers = {"User-Agent":"OpenDiscordBots-OpenTicket/4.0.7"}
+ if (newConfig.headers) Object.assign(newConfig.headers,{"User-Agent":"OpenDiscordBots-OpenTicket/4.1.0"})
+ else newConfig.headers = {"User-Agent":"OpenDiscordBots-OpenTicket/4.1.0"}
this.config = newConfig
}
@@ -703,8 +615,8 @@ export class ODHTTPPostRequest {
this.throwOnError = throwOnError
const newConfig = config ?? {}
newConfig.method = "POST"
- if (newConfig.headers) Object.assign(newConfig.headers,{"User-Agent":"OpenDiscordBots-OpenTicket/4.0.7"})
- else newConfig.headers = {"User-Agent":"OpenDiscordBots-OpenTicket/4.0.7"}
+ if (newConfig.headers) Object.assign(newConfig.headers,{"User-Agent":"OpenDiscordBots-OpenTicket/4.1.0"})
+ else newConfig.headers = {"User-Agent":"OpenDiscordBots-OpenTicket/4.1.0"}
this.config = newConfig
}
diff --git a/src/core/api/modules/builder.ts b/src/core/api/modules/builder.ts
index b62ce42..f864f2a 100644
--- a/src/core/api/modules/builder.ts
+++ b/src/core/api/modules/builder.ts
@@ -257,15 +257,7 @@ export class ODQuickButton {
/**The id of this button. */
id: ODId
/**The current data of this button */
- data: Partial = {
- customId:"",
- mode:"button",
- url:null,
- color:null,
- label:null,
- emoji:null,
- disabled:false
- }
+ data: Partial
constructor(id:ODValidId,data:Partial){
this.id = new ODId(id)
@@ -277,12 +269,13 @@ export class ODQuickButton {
try {
//create the discord.js button
const button = new discord.ButtonBuilder()
- if (this.data.mode == "button") button.setCustomId(this.data.customId ?? "od:unknown-button")
+ 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)
@@ -564,21 +557,7 @@ export class ODQuickDropdown {
/**The id of this dropdown. */
id: ODId
/**The current data of this dropdown */
- data: Partial = {
- customId:"",
- type:"string",
- placeholder:null,
- minValues:null,
- maxValues:null,
- disabled:false,
- channelTypes:[],
-
- options:[],
- users:[],
- roles:[],
- channels:[],
- mentionables:[]
- }
+ data: Partial
constructor(id:ODValidId,data:Partial){
this.id = new ODId(id)
@@ -804,12 +783,7 @@ export class ODQuickFile {
/**The id of this file. */
id: ODId
/**The current data of this file */
- data: Partial = {
- file:"",
- name:"file.txt",
- description:null,
- spoiler:false
- }
+ data: Partial
constructor(id:ODValidId,data:Partial){
this.id = new ODId(id)
@@ -862,7 +836,7 @@ export interface ODEmbedData {
/**The title of the embed */
title:string|null,
/**The color of the embed */
- color:discord.ColorResolvable|null,
+ color:discord.ColorResolvable|string|null,
/**The url of the embed */
url:string|null,
/**The description of the embed */
@@ -1020,7 +994,7 @@ export class ODEmbed extends ODBuilderImplementati
//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)
+ 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({
@@ -1060,21 +1034,7 @@ export class ODQuickEmbed {
/**The id of this embed. */
id: ODId
/**The current data of this embed */
- data: Partial = {
- 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
- }
+ data: Partial
constructor(id:ODValidId,data:Partial){
this.id = new ODId(id)
@@ -1087,7 +1047,7 @@ export class ODQuickEmbed {
//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)
+ 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({
@@ -1186,13 +1146,10 @@ export class ODMessageInstance {
content:null,
poll:null,
ephemeral:false,
-
embeds:[],
components:[],
files:[],
-
- additionalOptions:{
- }
+ additionalOptions:{}
}
/**Set the content of this message */
@@ -1354,6 +1311,81 @@ export class ODMessage extends ODBuilderImplementa
}
}
+/**## 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.
*
diff --git a/src/core/api/modules/checker.ts b/src/core/api/modules/checker.ts
index 616e67f..ceb2e84 100644
--- a/src/core/api/modules/checker.ts
+++ b/src/core/api/modules/checker.ts
@@ -82,6 +82,10 @@ export class ODCheckerManager extends ODManager {
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`
@@ -289,6 +293,16 @@ export class ODCheckerFunctionManager extends ODManager {
*/
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.
*
@@ -310,15 +324,29 @@ export class ODChecker extends ODManagerData {
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){
+ 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 = []
@@ -330,12 +358,12 @@ export class ODChecker extends ODManagerData {
messages:this.messages
}
}
- /**Create a string from the location trace (path)*/
+ /**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(`:${t}`)
+ final.push(`:(${this.#ordinalNumber(t+1)})`)
}else{
final.push(`."${t}"`)
}
@@ -391,7 +419,15 @@ 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
+ 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`
@@ -426,7 +462,13 @@ export class ODCheckerStructure {
*/
export interface ODCheckerObjectStructureOptions extends ODCheckerStructureOptions {
/**Add a checker for a property in an object (can also be optional) */
- children?:{key:string, checker:ODCheckerStructure, priority:number, optional:boolean}[]
+ 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`
@@ -454,8 +496,8 @@ export class ODCheckerObjectStructure extends ODCheckerStructure {
//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 < b.priority) return -1
- else if (a.priority > b.priority) return 1
+ if ((a.priority ?? 0) < (b.priority ?? 0)) return -1
+ else if ((a.priority ?? 0) > (b.priority ?? 0)) return 1
else return 0
})
@@ -497,10 +539,28 @@ export interface ODCheckerStringStructureOptions extends ODCheckerStructureOptio
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
+ 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`
@@ -540,13 +600,35 @@ export class ODCheckerStringStructure extends ODCheckerStructure {
}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 return super.check(checker,value,locationTrace)
+ }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)
+ }
}
}
@@ -554,6 +636,8 @@ export class ODCheckerStringStructure extends ODCheckerStructure {
* 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 */
@@ -576,6 +660,8 @@ export interface ODCheckerNumberStructureOptions extends ODCheckerStructureOptio
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? */
@@ -610,6 +696,9 @@ export class ODCheckerNumberStructure extends ODCheckerStructure {
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
@@ -641,19 +730,22 @@ export class ODCheckerNumberStructure extends ODCheckerStructure {
}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" && value < 0){
+ }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" && value > 0){
+ }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" && value === 0){
+ }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)
@@ -718,7 +810,9 @@ export interface ODCheckerArrayStructureOptions extends ODCheckerStructureOption
/**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")[]
+ 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`
@@ -863,15 +957,15 @@ export interface ODCheckerTypeSwitchStructureOptions extends ODCheckerStructureO
/**A checker when the property is a boolean */
boolean?:ODCheckerBooleanStructure,
/**A checker when the property is null */
- null?:ODCheckerStructure,
+ null?:ODCheckerNullStructure,
/**A checker when the property is an array */
- array?:ODCheckerStructure,
+ 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")[]
+ allowedTypes:("string"|"number"|"boolean"|"null"|"array"|"object"|"other")[]
}
/**## ODCheckerTypeSwitchStructure `class`
@@ -925,9 +1019,9 @@ export class ODCheckerTypeSwitchStructure extends ODCheckerStructure {
*/
export interface ODCheckerObjectSwitchStructureOptions extends ODCheckerStructureOptions {
/**An array of object checkers with their name, properties & priority. */
- objects?:{
+ objects:{
/**The properties to match for this checker to be used. */
- properties:{key:string, value:any}[],
+ 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. */
@@ -993,11 +1087,11 @@ export class ODCheckerObjectSwitchStructure extends ODCheckerStructure {
*/
export interface ODCheckerEnabledObjectStructureOptions extends ODCheckerStructureOptions {
/**The name of the property to match the `enabledValue`. */
- property?:string,
- /**The value of the property to be enabled. Defaults to `true` */
- enabledValue?:any,
+ 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
+ checker:ODCheckerObjectStructure
}
/**## ODCheckerEnabledObjectStructure `class`
@@ -1387,38 +1481,29 @@ export class ODCheckerCustomStructure_UniqueIdArray extends ODCheckerArrayStruct
/**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){
+ constructor(id:ODValidId, source:string, scope:string, usedScope?:string, options?:ODCheckerArrayStructureOptions, idOptions?:Omit){
//add premade custom structure checker
const newOptions = options ?? {}
- newOptions.custom = (checker,value,locationTrace,locationId,locationDocs) => {
- const lt = checker.locationTraceDeref(locationTrace)
+ 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()
- if (!Array.isArray(value)) return false
- const uniqueArray: string[] = (checker.storage.get(source,scope) === null) ? [] : checker.storage.get(source,scope)
-
- let localQuit = false
- value.forEach((id,index) => {
- const localLt = checker.locationTraceDeref(lt)
- localLt.push(index)
- if (typeof id != "string") {
- //id isn't a string
- checker.createMessage("opendiscord:id-invalid-type", "error", "This id needs to be a string!", localLt, null, [], this.id, (this.options.docs ?? null))
- localQuit = true
- }else if (uniqueArray.includes(id)) {
- //exists
- if (usedScope){
- const current: string[] = checker.storage.get(source,usedScope) ?? []
- current.push(id)
- checker.storage.set(source,usedScope,current)
- }
- }else{
- //doesn't exist
- checker.createMessage("opendiscord:id-non-existent","error",`The id "${id}" doesn't exist!`,localLt,null,[`"${id}"`],this.id,(this.options.docs ?? null))
- localQuit = true
+ 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 !localQuit
- }
+ 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
diff --git a/src/core/api/modules/client.ts b/src/core/api/modules/client.ts
index 60b0c44..5638a99 100644
--- a/src/core/api/modules/client.ts
+++ b/src/core/api/modules/client.ts
@@ -76,12 +76,18 @@ export class ODClientManager {
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. */
@@ -114,7 +120,7 @@ export class ODClientManager {
this.#debug.debug("Created client with permissions: "+this.permissions.join(", "))
}
/**Get all servers the bot is part of. */
- getGuilds(){
+ 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!")
@@ -135,19 +141,21 @@ export class ODClientManager {
})
}
}
- /**Log-in with a discord auth token. */
- login(): Promise {
+ /**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("ready",async () => {
+ this.client.once("clientReady",async () => {
this.ready = true
- //set slashCommandManager to client applicationCommandManager
- if (!this.client.application) throw new ODSystemError("Couldn't get client application! Unable to register slash commands!")
+ //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)
@@ -158,7 +166,8 @@ export class ODClientManager {
this.#debug.debug("Finished discord.js client.login()")
this.loggedIn = true
}catch(err){
- if (err.message.toLowerCase().includes("used disallowed intents")){
+ 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"))
@@ -326,10 +335,10 @@ export class ODClientManager {
* Possible activity types for the bot.
*/
export type ODClientActivityType = ("playing"|"listening"|"watching"|"custom"|false)
-/**## ODClientPermissions `type`
+/**## ODClientActivityMode `type`
* Possible activity statuses for the bot.
*/
-export type ODClientActivityStatus = ("online"|"invisible"|"idle"|"dnd")
+export type ODClientActivityMode = ("online"|"invisible"|"idle"|"dnd")
/**## ODClientActivityManager `class`
@@ -349,8 +358,10 @@ export class ODClientActivityManager {
type: ODClientActivityType = false
/**The current status text */
text: string = ""
- /**The current status status */
- status: ODClientActivityStatus = "online"
+ /**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
@@ -365,10 +376,11 @@ export class ODClientActivityManager {
}
/**Update the status. When already initiated, it can take up to 10min to see the updated status in discord. */
- setStatus(type:ODClientActivityType, text:string, status:ODClientActivityStatus, forceUpdate?:boolean){
+ setStatus(type:ODClientActivityType, text:string, mode:ODClientActivityMode, state:string, forceUpdate?:boolean){
this.type = type
this.text = text
- this.status = status
+ this.mode = mode
+ this.state = state
if (forceUpdate) this.#updateClientActivity(this.type,this.text)
}
@@ -395,10 +407,10 @@ export class ODClientActivityManager {
this.manager.client.user.setPresence({
activities:[{
type:this.#getStatusTypeEnum(type),
- state:undefined,
+ state:this.state ? this.state : undefined,
name:text,
}],
- status:this.status
+ status:this.mode
})
}
/**Get the enum that links to the correct type */
@@ -515,14 +527,15 @@ export interface ODSlashCommandUniversalCommand {
* The builder for slash commands. Here you can add options to the command.
*/
export interface ODSlashCommandBuilder extends discord.ChatInputApplicationCommandData {
- /**@deprecated `dmPermission` is deprecated. Use `context` instead! (Not using contexts might result in the slash command being re-registered on every startup!) */
- dmPermission?:boolean
/**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 {
@@ -1789,4 +1802,446 @@ export class ODTextCommandManager extends ODManager {
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/config.ts b/src/core/api/modules/config.ts
index 4d34b2d..c59b11a 100644
--- a/src/core/api/modules/config.ts
+++ b/src/core/api/modules/config.ts
@@ -5,6 +5,7 @@ import { ODId, ODManager, ODManagerData, ODPromiseVoid, ODSystemError, ODValidId
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.
@@ -14,8 +15,17 @@ import fs from "fs"
* 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(){
@@ -42,15 +52,45 @@ export class ODConfig extends ODManagerData {
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 {
- //nothing
+ 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 = []
}
}
@@ -65,13 +105,13 @@ export class ODConfig extends ODManagerData {
* const config = new api.ODJsonConfig("plugin-config","test.json","./plugins/testplugin/")
*/
export class ODJsonConfig extends ODConfig {
- /**An array of listeners to run when the config gets reloaded. These are not executed on the initial loading. */
- #reloadListeners: Function[] = []
+ formatter: fjs.custom.BaseFormatter
- constructor(id:ODValidId, file:string, customPath?:string){
+ 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. */
@@ -79,17 +119,20 @@ export class ODJsonConfig extends ODConfig {
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 JSON file. Be aware that this doesn't update classes that used individual parts of the config data! */
+ /**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())
- this.#reloadListeners.forEach((cb) => {
+ super.reload()
+ this.reloadListeners.forEach((cb) => {
try{
cb()
}catch(err){
@@ -101,12 +144,16 @@ export class ODJsonConfig extends ODConfig {
throw new ODSystemError("Unable to reload config \""+nodepath.join("./",this.path)+"\"!")
}
}
- /**Listen for a reload of this JSON file! */
- onReload(cb:Function){
- this.#reloadListeners.push(cb)
- }
- /**Remove all reload listeners. Not recommended! */
- removeAllReloadListeners(){
- this.#reloadListeners = []
+ /**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
index fc634d6..f34399c 100644
--- a/src/core/api/modules/console.ts
+++ b/src/core/api/modules/console.ts
@@ -241,6 +241,8 @@ export class ODConsoleManager {
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
@@ -253,12 +255,12 @@ export class ODConsoleManager {
log(message:string, type?:ODConsoleMessageTypes, params?:ODConsoleMessageParam[]): void
log(message:ODConsoleMessage|ODError|string, type?:ODConsoleMessageTypes, params?:ODConsoleMessageParam[]){
if (message instanceof ODConsoleMessage){
- message.render()
+ if (!this.silent) message.render()
if (this.debugfile) this.debugfile.writeConsoleMessage(message)
this.history.push(message)
}else if (message instanceof ODError){
- message.render()
+ if (!this.silent) message.render()
if (this.debugfile) this.debugfile.writeErrorMessage(message)
this.history.push(message)
@@ -272,7 +274,7 @@ export class ODConsoleManager {
else if (type == "error") newMessage = new ODConsoleErrorMessage(message,params)
else newMessage = new ODConsoleSystemMessage(message,params)
- newMessage.render()
+ if (!this.silent) newMessage.render()
if (this.debugfile) this.debugfile.writeConsoleMessage(newMessage)
this.history.push(newMessage)
}
diff --git a/src/core/api/modules/database.ts b/src/core/api/modules/database.ts
index 1e1dbad..ab6d601 100644
--- a/src/core/api/modules/database.ts
+++ b/src/core/api/modules/database.ts
@@ -7,30 +7,6 @@ import nodepath from "path"
import { ODDebugger } from "./console"
import * as fjs from "formatted-json-stringify"
-/////////////////////////////////////////////////////////
-//TEMPORARY OPENTICKET => OPENDISCORD MIGRATION UTILITIES
-/////////////////////////////////////////////////////////
-
-/** ## โ Temporary function. Will be removed on full OTv4 release! */
-export function TEMP_migrateDatabaseIdPrefix(id:string): string {
- if (!id.startsWith("openticket:")) return id
- return id.replaceAll("openticket:","opendiscord:")
-}
-/** ## โ Temporary function. Will be removed on full OTv4 release! */
-export function TEMP_migrateDatabaseValuePrefix(value:string): string {
- return value.replaceAll('"openticket:','"opendiscord:')
-}
-/** ## โ Temporary function. Will be removed on full OTv4 release! */
-export function TEMP_migrateDatabaseStructurePrefix(structure:ODJsonDatabaseStructure): ODJsonDatabaseStructure {
- return structure.map((data) => {
- return {
- category:TEMP_migrateDatabaseIdPrefix(data.category),
- key:TEMP_migrateDatabaseIdPrefix(data.key),
- value:JSON.parse(TEMP_migrateDatabaseValuePrefix(JSON.stringify(data.value)))
- }
- })
-}
-
/**## ODDatabaseManager `class`
* This is an Open Ticket database manager.
*
@@ -118,10 +94,7 @@ export class ODJsonDatabase extends ODDatabase {
/**Init the database. */
init(): ODPromiseVoid {
- //this.#system.getData()
- //TEMPORARY!!!
- const newData = TEMP_migrateDatabaseStructurePrefix(this.#system.getData())
- this.#system.setData(newData)
+ this.#system.getData()
}
/**Set/overwrite the value of `category` & `key`. Returns `true` when overwritten!
* @example
@@ -226,10 +199,7 @@ export class ODFormattedJsonDatabase extends ODDatabase {
/**Init the database. */
init(): ODPromiseVoid {
- //this.#system.getData()
- //TEMPORARY!!!
- const newData = TEMP_migrateDatabaseStructurePrefix(this.#system.getData())
- this.#system.setData(newData)
+ this.#system.getData()
}
/**Set/overwrite the value of `category` & `key`. Returns `true` when overwritten!
* @example
diff --git a/src/core/api/modules/defaults.ts b/src/core/api/modules/defaults.ts
index aaa0c47..c0b4491 100644
--- a/src/core/api/modules/defaults.ts
+++ b/src/core/api/modules/defaults.ts
@@ -13,6 +13,8 @@ export interface ODDefaults {
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 :) */
@@ -81,6 +83,9 @@ export interface ODDefaults {
/**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). */
@@ -89,6 +94,14 @@ export interface ODDefaults {
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,
@@ -130,6 +143,10 @@ export interface ODDefaults {
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,
@@ -233,6 +250,7 @@ export class ODDefaultsManager {
errorHandling:true,
crashOnError:false,
debugLoading:true,
+ silentLoading:true,
allowDumpCommand:true,
pluginLoading:true,
softPluginLoading:false,
@@ -269,11 +287,17 @@ export class ODDefaultsManager {
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,
@@ -296,6 +320,8 @@ export class ODDefaultsManager {
buttonRespondersLoading:true,
dropdownRespondersLoading:true,
modalRespondersLoading:true,
+ contextMenuRespondersLoading:true,
+ autocompleteRespondersLoading:true,
responderTimeoutMs:2500,
actionsLoading:true,
diff --git a/src/core/api/modules/plugin.ts b/src/core/api/modules/plugin.ts
index ff9d412..9a46320 100644
--- a/src/core/api/modules/plugin.ts
+++ b/src/core/api/modules/plugin.ts
@@ -146,11 +146,8 @@ export class ODPlugin extends ODManagerData {
async execute(debug:ODDebugger,force?:boolean): Promise {
if ((this.enabled && !this.crashed) || force){
try{
- //rewrite the path to make it work on windows & unix based systems
- const workingDir = process.cwd().replace(/C:\\/i,"/")
- const workingPath = nodepath.join("./dist/plugins/",this.getStartFile())
- const pluginPath = nodepath.join(workingDir,workingPath).split(nodepath.sep).join("/")
-
+ //import relative plugin directory path (works on windows & unix based systems)
+ const pluginPath = nodepath.join("../../../../plugins/",this.getStartFile())
await import(pluginPath)
debug.console.log("Plugin \""+this.id.value+"\" loaded successfully!","plugin")
this.executed = true
diff --git a/src/core/api/modules/responder.ts b/src/core/api/modules/responder.ts
index a732d2a..494faa8 100644
--- a/src/core/api/modules/responder.ts
+++ b/src/core/api/modules/responder.ts
@@ -5,7 +5,7 @@ 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, ODSlashCommand, ODTextCommand, ODTextCommandInteractionOption } from "./client"
+import { ODClientManager, ODContextMenu, ODSlashCommand, ODTextCommand, ODTextCommandInteractionOption } from "./client"
import { ODDropdownData, ODMessageBuildResult, ODMessageBuildSentResult, ODModalBuildResult } from "./builder"
/**## ODResponderImplementation `class`
@@ -36,7 +36,7 @@ export class ODResponderImplementation ex
/**## 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
+export type ODResponderTimeoutErrorCallback = (instance:Instance, source:Source) => void|Promise
/**## ODResponderManager `class`
* This is an Open Ticket responder manager.
@@ -61,12 +61,18 @@ export class ODResponderManager {
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)
}
}
@@ -1083,7 +1089,7 @@ export class ODModalResponderInstance {
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()
+ }else throw new ODSystemError("Unable to update modal interaction!")
}catch{
return {success:false,message:null}
}
@@ -1113,4 +1119,298 @@ export class ODModalResponder extends ODResponderI
//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) 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){
+ 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))
+ 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/startscreen.ts b/src/core/api/modules/startscreen.ts
index 7990c19..9aab04b 100644
--- a/src/core/api/modules/startscreen.ts
+++ b/src/core/api/modules/startscreen.ts
@@ -269,13 +269,19 @@ export class ODStartScreenPluginsCategoryComponent extends ODStartScreenCategory
constructor(id:ODValidId, priority:number, plugins:ODPlugin[], unknownCrashedPlugins:ODUnknownCrashedPlugin[]){
super(id,priority,"plugins",() => {
- const renderedPlugins = this.plugins.sort((a,b) => b.priority-a.priority).map((plugin) => {
- if (plugin.enabled && plugin.executed) return ansis.green("โ
["+plugin.name+"] "+plugin.details.shortDescription)
- else if (plugin.enabled && plugin.crashed) return ansis.red("โ ["+plugin.name+"] "+plugin.details.shortDescription)
- else return ansis.gray("๐ค ["+plugin.name+"] "+plugin.details.shortDescription)
- })
+ 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 [...renderedPlugins,...renderedUnknownPlugins].join("\n")
+
+ return [
+ ...renderedActivePlugins,
+ ...renderedDisabledPlugins,
+ ...renderedCrashedPlugins,
+ ...renderedUnknownPlugins
+ ].join("\n")
},false)
this.plugins = plugins
this.unknownCrashedPlugins = unknownCrashedPlugins
diff --git a/src/core/api/modules/stat.ts b/src/core/api/modules/stat.ts
index 63ebcca..dccc1e6 100644
--- a/src/core/api/modules/stat.ts
+++ b/src/core/api/modules/stat.ts
@@ -140,6 +140,23 @@ export class ODStatScope extends ODManager {
//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
@@ -213,6 +230,9 @@ 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)
}
diff --git a/src/core/api/openticket/option.ts b/src/core/api/openticket/option.ts
index 75e741a..f5351a7 100644
--- a/src/core/api/openticket/option.ts
+++ b/src/core/api/openticket/option.ts
@@ -151,12 +151,12 @@ export interface ODTicketOptionIds {
"opendiscord:questions":ODOptionData,
"opendiscord:channel-prefix":ODOptionData,
- "opendiscord:channel-suffix":ODOptionData<"user-name"|"user-id"|"random-number"|"random-hex"|"counter-dynamic"|"counter-fixed">,
+ "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-description":ODOptionData,
+ "opendiscord:channel-topic":ODOptionData,
"opendiscord:dm-message-enabled":ODOptionData,
"opendiscord:dm-message-text":ODOptionData,
@@ -183,6 +183,9 @@ export interface ODTicketOptionIds {
"opendiscord:limits-enabled":ODOptionData,
"opendiscord:limits-maximum-global":ODOptionData,
"opendiscord:limits-maximum-user":ODOptionData
+
+ "opendiscord:slowmode-enabled":ODOptionData,
+ "opendiscord:slowmode-seconds":ODOptionData,
}
/**## ODTicketOption `class`
@@ -350,10 +353,16 @@ export class ODOptionSuffixManager extends ODManager {
}
/**Instantly get the suffix from an `ODTicketOption`. */
- async getSuffixFromOption(option:ODTicketOption,user:discord.User): Promise {
+ async getSuffixFromOption(option:ODTicketOption,user:discord.User,guild:discord.Guild): Promise {
const suffix = this.getAll().find((suffix) => suffix.option.id.value == option.id.value)
if (!suffix) return null
- return await suffix.getSuffix(user)
+ try{
+ const member = await guild.members.fetch(user.id)
+ return await suffix.getSuffix(member)
+ }catch(err){
+ process.emit("uncaughtException",err)
+ return null
+ }
}
}
@@ -374,7 +383,7 @@ export class ODOptionSuffix extends ODManagerData {
}
/**Get the suffix for a new ticket. */
- async getSuffix(user:discord.User): Promise {
+ async getSuffix(member:discord.GuildMember): Promise {
throw new ODSystemError("Tried to use an unimplemented ODOptionSuffix!")
}
}
@@ -387,8 +396,21 @@ export class ODOptionSuffix extends ODManagerData {
* Use `getSuffix()` to get the new suffix!
*/
export class ODOptionUserNameSuffix extends ODOptionSuffix {
- async getSuffix(user:discord.User): Promise {
- return user.username
+ async getSuffix(member:discord.GuildMember): Promise {
+ return member.user.username
+ }
+}
+
+/**## ODOptionUserNicknameSuffix `class`
+ * This is an Open Ticket user-nickname option suffix.
+ *
+ * This class can generate a user-nickname suffix for a discord channel name from a specific option.
+ *
+ * Use `getSuffix()` to get the new suffix!
+ */
+export class ODOptionUserNicknameSuffix extends ODOptionSuffix {
+ async getSuffix(member:discord.GuildMember): Promise {
+ return member.displayName
}
}
@@ -400,8 +422,8 @@ export class ODOptionUserNameSuffix extends ODOptionSuffix {
* Use `getSuffix()` to get the new suffix!
*/
export class ODOptionUserIdSuffix extends ODOptionSuffix {
- async getSuffix(user:discord.User): Promise {
- return user.id
+ async getSuffix(member:discord.GuildMember): Promise {
+ return member.id
}
}
@@ -426,7 +448,7 @@ export class ODOptionCounterDynamicSuffix extends ODOptionSuffix {
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(user:discord.User): Promise {
+ async getSuffix(member:discord.GuildMember): Promise {
const rawCurrentValue = await this.database.get("opendiscord:option-suffix-counter",this.option.id.value)
const currentValue = (typeof rawCurrentValue != "number") ? 0 : rawCurrentValue
const newValue = currentValue+1
@@ -456,7 +478,7 @@ export class ODOptionCounterFixedSuffix extends ODOptionSuffix {
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(user:discord.User): Promise {
+ async getSuffix(member:discord.GuildMember): Promise {
const rawCurrentValue = await this.database.get("opendiscord:option-suffix-counter",this.option.id.value)
const currentValue = (typeof rawCurrentValue != "number") ? 0 : rawCurrentValue
const newValue = (currentValue >= 9999) ? 0 : currentValue+1
@@ -501,7 +523,7 @@ export class ODOptionRandomNumberSuffix extends ODOptionSuffix {
if (history.includes(number)) return this.#generateUniqueValue(history)
else return number
}
- async getSuffix(user:discord.User): Promise {
+ 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)
@@ -539,7 +561,7 @@ export class ODOptionRandomHexSuffix extends ODOptionSuffix {
if (history.includes(hex)) return this.#generateUniqueValue(history)
else return hex
}
- async getSuffix(user:discord.User): Promise {
+ 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)
diff --git a/src/core/api/openticket/priority.ts b/src/core/api/openticket/priority.ts
new file mode 100644
index 0000000..90ea38a
--- /dev/null
+++ b/src/core/api/openticket/priority.ts
@@ -0,0 +1,110 @@
+///////////////////////////////////////
+//OPENTICKET PRIORITY MODULE
+///////////////////////////////////////
+import { ODId, ODManager, ODValidJsonType, ODValidId, ODVersion, ODManagerData } from "../modules/base"
+import { ODDebugger } from "../modules/console"
+import * as discord from "discord.js"
+
+/**## 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.
+ */
+export class ODPriorityManager extends ODManager {
+ /**A reference to the Open Ticket debugger. */
+ #debug: ODDebugger
+
+ 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.
+ * It's used to generate typescript declarations for this class.
+ */
+export interface ODPriorityManagerIds {
+ "opendiscord:urgent":ODPriorityLevel,
+ "opendiscord:very-high":ODPriorityLevel,
+ "opendiscord:high":ODPriorityLevel,
+ "opendiscord:normal":ODPriorityLevel,
+ "opendiscord:low":ODPriorityLevel,
+ "opendiscord:very-low":ODPriorityLevel,
+ "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!
+ *
+ * This default class is made for the global variable `opendiscord.priorities`!
+ */
+export class ODPriorityManager_Default extends ODPriorityManager {
+ get(id:PriorityId): ODPriorityManagerIds[PriorityId]
+ get(id:ODValidId): ODPriorityLevel|null
+
+ get(id:ODValidId): ODPriorityLevel|null {
+ return super.get(id)
+ }
+
+ remove(id:PriorityId): ODPriorityManagerIds[PriorityId]
+ remove(id:ODValidId): ODPriorityLevel|null
+
+ remove(id:ODValidId): ODPriorityLevel|null {
+ return super.remove(id)
+ }
+
+ exists(id:keyof ODPriorityManagerIds): boolean
+ exists(id:ODValidId): boolean
+
+ exists(id:ODValidId): boolean {
+ return super.exists(id)
+ }
+}
+
+/**## ODPriorityLevel `class`
+ * This is an Open Ticket priority level.
+ *
+ * Using this class, you can register or edit a priority level for the ticket priority system.
+ *
+ * Priority levels should be registered in `opendiscord.priorities`.
+ *
+ * #### ๐จ Negative priorities are treated as `disabled/no-priority`!
+ */
+export class ODPriorityLevel extends 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). */
+ rawName:string
+ /**The display name of the level (used in embeds & messages). */
+ displayName:string
+ /**The display emoji of the level (used in embeds & messages). */
+ displayEmoji:string|null
+ /**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){
+ super(id)
+ this.priority = priority
+ this.rawName = rawName
+ this.displayName = displayName
+ this.displayEmoji = displayEmoji
+ this.channelEmoji = channelEmoji
+ }
+ /**Get the display name + emoji for rendering this priority in the UI/embeds. */
+ renderDisplayName(){
+ return (this.displayEmoji ? this.displayEmoji+" " : "")+this.displayName
+ }
+}
\ No newline at end of file
diff --git a/src/core/api/openticket/role.ts b/src/core/api/openticket/role.ts
index ce22481..b61c462 100644
--- a/src/core/api/openticket/role.ts
+++ b/src/core/api/openticket/role.ts
@@ -37,7 +37,7 @@ export interface ODRoleDataJson {
value:ODValidJsonType
}
-/**## ODRoleDataJson `interface`
+/**## ODRoleJson `interface`
* The JSON representatation from a single role.
*/
export interface ODRoleJson {
diff --git a/src/core/api/openticket/ticket.ts b/src/core/api/openticket/ticket.ts
index 7b03243..2d8203a 100644
--- a/src/core/api/openticket/ticket.ts
+++ b/src/core/api/openticket/ticket.ts
@@ -158,6 +158,7 @@ export interface ODTicketIds {
"opendiscord:ticket-message":ODTicketData,
"opendiscord:participants":ODTicketData<{type:"role"|"user",id:string}[]>,
"opendiscord:channel-suffix":ODTicketData,
+ "opendiscord:previous-creators":ODTicketData,
"opendiscord:open":ODTicketData,
"opendiscord:opened-by":ODTicketData,
@@ -165,6 +166,9 @@ export interface ODTicketIds {
"opendiscord:closed":ODTicketData,
"opendiscord:closed-by":ODTicketData,
"opendiscord:closed-on":ODTicketData,
+ "opendiscord:reopened":ODTicketData,
+ "opendiscord:reopened-by":ODTicketData,
+ "opendiscord:reopened-on":ODTicketData,
"opendiscord:claimed":ODTicketData,
"opendiscord:claimed-by":ODTicketData,
"opendiscord:claimed-on":ODTicketData,
@@ -183,6 +187,10 @@ export interface ODTicketIds {
"opendiscord:autodelete-days":ODTicketData,
"opendiscord:answers":ODTicketData<{id:string,name:string,type:"short"|"paragraph",value:string|null}[]>,
+ "opendiscord:priority":ODTicketData,
+ "opendiscord:topic":ODTicketData,
+ "opendiscord:message-sent":ODTicketData,
+ "opendiscord:admin-message-sent":ODTicketData,
}
/**## ODTicket `class`
diff --git a/src/core/api/openticket/transcript.ts b/src/core/api/openticket/transcript.ts
index b5e6e55..9234ec9 100644
--- a/src/core/api/openticket/transcript.ts
+++ b/src/core/api/openticket/transcript.ts
@@ -7,6 +7,7 @@ import { ODTicket, ODTicketManager } from "./ticket"
import { ODMessageBuildResult } from "../modules/builder"
import { ODClientManager } from "../modules/client"
import * as discord from "discord.js"
+import { ODPermissionManager_Default } from "#opendiscord-types"
/**## ODTranscriptManager `class`
* This is an Open Ticket transcript manager.
@@ -21,10 +22,10 @@ export class ODTranscriptManager extends ODManager(id:QuestionId): ODTranscriptCompilerIds[QuestionId]
+ get(id:CompilerId): ODTranscriptCompilerIds[CompilerId]
get(id:ODValidId): ODTranscriptCompiler|null
get(id:ODValidId): ODTranscriptCompiler|null {
return super.get(id)
}
- remove(id:QuestionId): ODTranscriptCompilerIds[QuestionId]
+ remove(id:CompilerId): ODTranscriptCompilerIds[CompilerId]
remove(id:ODValidId): ODTranscriptCompiler|null
remove(id:ODValidId): ODTranscriptCompiler|null {
@@ -166,10 +167,13 @@ export class ODTranscriptCollector {
#tickets: ODTicketManager
/**Alias for the client manager. */
#client: ODClientManager
+ /**Alias for the permissions manager. */
+ #permissions: ODPermissionManager_Default
- constructor(tickets:ODTicketManager,client:ODClientManager){
+ constructor(tickets:ODTicketManager,client:ODClientManager,permissions:ODPermissionManager_Default){
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. */
@@ -386,11 +390,11 @@ export class ODTranscriptCollector {
}
/**Calculate a human-readable file size. Used in transcripts. */
calculateFileSize(bytes:number): {size:number, unit:"B"|"KB"|"MB"|"GB"|"TB"} {
- if (bytes >= 1000000000000) return {size:(Math.round((bytes/1000000000000)*100)/100),unit:"TB"}
- if (bytes >= 1000000000) return {size:(Math.round((bytes/1000000000)*100)/100),unit:"GB"}
- else if (bytes >= 1000000) return {size:(Math.round((bytes/1000000)*100)/100),unit:"MB"}
- else if (bytes >= 1000) return {size:(Math.round((bytes/1000)*100)/100),unit:"KB"}
- else return {size:bytes,unit:"B"}
+ if (bytes < 1024) return {size:Math.round(bytes),unit:"B"}
+ else if (bytes < 1024*1024) return {size:Math.round(bytes/1024),unit:"KB"}
+ else if (bytes < 1024*1024*1024) return {size:Math.round(bytes/(1024*1024)),unit:"MB"}
+ else if (bytes < 1024*1024*1024*1024) return {size:Math.round(bytes/(1024*1024*1024)),unit:"GB"}
+ 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 {
@@ -439,6 +443,25 @@ export class ODTranscriptCollector {
return userData
}
+ /**Analyse the ticket for the amount of messages users & admins have sent in the ticket. */
+ async ticketUserMessagesAnalysis(ticket:ODTicket,guild:discord.Guild,channel:discord.GuildTextBasedChannel){
+ const messages = await this.collectAllMessages(ticket,{bots:true,client:true,users:true})
+ if (!messages) return null
+ const parsedMessages = await this.convertMessagesToTranscriptData(messages)
+ let userMessages = 0
+ 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 (!user) continue
+ const isAdmin = this.#permissions.hasPermissions("support",await this.#permissions.getPermissions(user,channel,guild))
+ if (isAdmin) adminMessages++
+ else userMessages++
+ }
+
+ return {userMessages,adminMessages,totalMessages:userMessages+adminMessages}
+ }
}
/**## ODTranscriptCollectorIncludeSettings `interface`
diff --git a/src/core/cli/cli.ts b/src/core/cli/cli.ts
new file mode 100644
index 0000000..6fcb2ce
--- /dev/null
+++ b/src/core/cli/cli.ts
@@ -0,0 +1,75 @@
+import {opendiscord, api, utilities} from "../../index"
+import {Terminal, terminal} from "terminal-kit"
+import ansis from "ansis"
+
+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 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)
+}
\ No newline at end of file
diff --git a/src/core/cli/editConfig.ts b/src/core/cli/editConfig.ts
new file mode 100644
index 0000000..9eebf29
--- /dev/null
+++ b/src/core/cli/editConfig.ts
@@ -0,0 +1,933 @@
+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
new file mode 100644
index 0000000..ef1bdf4
--- /dev/null
+++ b/src/core/cli/quickSetup.ts
@@ -0,0 +1,1569 @@
+import {opendiscord, api, utilities} from "../../index"
+import {Terminal, terminal} from "terminal-kit"
+import ansis from "ansis"
+import * as discord from "discord.js"
+import crypto from "crypto"
+import {renderHeader, terminate} from "./cli"
+
+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
+}
+
+interface ODQuickSetupVariables {
+ client?:api.ODClientManager,
+ guild?:discord.Guild,
+ globalAdmins?:string[],
+ mainColor?:discord.ColorResolvable,
+ language?:string,
+ slashCommands?:boolean,
+ textCommands?:boolean,
+ status?:api.ODJsonConfig_DefaultStatusType,
+ logChannel?:string|null,
+ ticketCategory?:string|null,
+ ticketOptions:({
+ name:string,
+ description:string,
+ buttonType:"label-emoji"|"emoji"|"label",
+ buttonColor:api.ODValidButtonColor,
+ buttonEmoji:string|null,
+ channelPrefix:string,
+ channelSuffix:api.ODJsonConfig_DefaultOptionTicketChannelType["suffix"]
+ }|null)[],
+ optionIdStorage:string[],
+ autocloseHours?:number|null,
+ cooldownMinutes?:number|null,
+ globalUserLimit?:number|null,
+ removeParticipantsOnClose?:boolean,
+ ticketMessageLayout?:"embed"|"text"|null,
+ emojiStyle?:api.ODJsonConfig_DefaultSystem["emojiStyle"],
+ panelName?:string,
+ panelDescription?:string,
+ panelDropdown?:boolean,
+ panelLayout?:"embed"|"text",
+ panelDescribeOptions?:"simple"|"normal"|"detailed"|null,
+ panelMaxTicketsWarning?:boolean,
+}
+const stepCount = (count:number) => "(Step "+count+"/24) "
+
+const quickSetupStorage: ODQuickSetupVariables = {ticketOptions:[],optionIdStorage:[]}
+const autoCompleteMenuOpts: Terminal.SingleLineMenuOptions = {
+ style:terminal.white,
+ selectedStyle:terminal.bgBlue.white
+}
+const presetColors = new Map([
+ ["dark red","#992d22"],
+ ["red","#ff0000"],
+ ["light red","#f06c6c"],
+ ["dark orange","#ed510e"],
+ ["orange","#ed6f0e"],
+ ["light orange","#f0b06c"],
+ ["openticket","#f8ba00"],
+ ["dark yellow","#deb100"],
+ ["yellow","#ffff00"],
+ ["light yellow","#ffff8c"],
+ ["banana","#ffe896"],
+ ["lime","#a8e312"],
+ ["dark green","#009600"],
+ ["green","#00ff00"],
+ ["light green","#76f266"],
+ ["dark cyan","#00abab"],
+ ["cyan","#00ffff"],
+ ["light cyan","#63ffff"],
+ ["aquamarine","#7fffd4"],
+ ["dark skyblue","#006bc9"],
+ ["skyblue","#0095ff"],
+ ["light skyblue","#40bfff"],
+ ["dark blue","#00006e"],
+ ["blue","#0000ff"],
+ ["light blue","#5353fc"],
+ ["blurple","#5865F2"],
+ ["dark purple","#3f009e"],
+ ["purple","#8000ff"],
+ ["light purple","#9257eb"],
+ ["dark pink","#b82ab0"],
+ ["pink","#ff6bf8"],
+ ["light pink","#ff9cfa"],
+ ["magenta","#ff00ff"],
+ ["black","#000000"],
+ ["brown","#806050"],
+ ["dark gray","#4f4f4f"],
+ ["gray","#808080"],
+ ["light gray","#b3b3b3"],
+ ["white","#ffffff"],
+ ["invisible","#393A41"]
+])
+
+export async function renderQuickSetup(backFn:() => api.ODPromiseVoid){
+ if (quickSetupRequiresReset()) await renderQuickSetupWarning(backFn)
+ else await renderQuickSetupWelcome(backFn)
+}
+
+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.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
+
+ return false
+}
+
+async function renderQuickSetupWarning(backFn:() => api.ODPromiseVoid) {
+ renderHeader("โฑ๏ธ 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")
+
+ const answer = await terminal.singleColumnMenu([
+ ansis.green("โ
No, take me back."),
+ ansis.red("๐จ Yes, continue and reset the config.")
+ ],{
+ leftPadding:"> ",
+ style:terminal.cyan,
+ selectedStyle:terminal.bgDefaultColor.bold,
+ submittedStyle:terminal.bgBlue,
+ extraLines:2,
+ cancelable:true
+ }).promise
+
+ if (answer.canceled || answer.selectedIndex == 0) return await backFn()
+ if (answer.selectedIndex == 1) await renderQuickSetupWelcome(async () => {await renderQuickSetupWarning(backFn)})
+}
+
+async function renderQuickSetupWelcome(backFn:() => api.ODPromiseVoid){
+ renderHeader("โฑ๏ธ Open Ticket Quick Setup: Introduction")
+
+ terminal.bold.underline.blue("Open Ticket: Quick Setup\n")
+ terminal.gray([
+ "Hi there! Thank you for downloading and installing Open Ticket.",
+ "You have chosen to configure the bot using the 'Quick Setup CLI'.",
+ "",
+ "This tool will help you with configuring Open Ticket using a step-by-step method.",
+ ansis.gray("You can "+ansis.red.bold("navigate using the arrow-keys")+" and "+ansis.red.bold("go back using ESC")+"."),
+ "",
+ ansis.magenta("The configuration should normally only take around 6 minutes."),
+ ansis.magenta("Once you've completed the form, the bot is ready for usage!")
+ ].join("\n")+"\n\n")
+
+ const answer = await terminal.singleColumnMenu([
+ ansis.green("Press 'Enter' to start!")
+ ],{
+ 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 renderQuickSetupDevPortal(async () => {await renderQuickSetupWelcome(backFn)})
+}
+
+async function renderQuickSetupDevPortal(backFn:() => api.ODPromiseVoid){
+ renderHeader("โฑ๏ธ 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")
+
+ const answer = await terminal.singleColumnMenu([
+ "โ
Yes I have, and it has been invited to the server.",
+ "โ No not yet, I don't know how to create one.",
+ "๐ถ I've never seen a Discord bot before.",
+ ],{
+ 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 renderQuickSetupBotToken(async () => {await renderQuickSetupDevPortal(backFn)})
+ else if (answer.selectedIndex == 1) await renderQuickSetupDevPortalGuide(0,async () => {await renderQuickSetupDevPortal(backFn)})
+ else if (answer.selectedIndex == 2) await renderQuickSetupDevPortalGuide(1,async () => {await renderQuickSetupDevPortal(backFn)})
+}
+
+async function renderQuickSetupDevPortalGuide(variation:0|1,backFn:() => api.ODPromiseVoid){
+ renderHeader("โฑ๏ธ 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")
+ terminal.gray("Please visit the following URL for a step-by-step guide on how to create a Discord bot.\nIf it still doesn't work, join our Discord server and we will help you further!\n"+ansis.magenta("=> https://otdocs.dj-dj.be/docs/guides/get-started#bot\n\n"))
+ }else{
+ terminal.bold.blue(stepCount(1.2)+"You've mentioned that you've never seen Discord bot before.\n\n")
+ terminal.gray("How did you even download Open Ticket ๐คช? But all jokes aside, we have a step-by-step guide on how to create a Discord bot.\nIf it still doesn't work, join our Discord server and we will help you further!\n"+ansis.magenta("=> https://otdocs.dj-dj.be/docs/guides/get-started#bot\n\n"))
+ }
+
+ const answer = await terminal.singleColumnMenu([
+ ansis.green("โ
Alright, I've made the bot. Take me back please!")
+ ],{
+ leftPadding:"> ",
+ style:terminal.cyan,
+ selectedStyle:terminal.bgDefaultColor.bold,
+ submittedStyle:terminal.bgBlue,
+ extraLines:2,
+ cancelable:true
+ }).promise
+
+ return await backFn()
+}
+
+async function quickSetupLogin(token:string): Promise {
+ const client = new api.ODClientManager(opendiscord.debug)
+ client.token = token
+ client.intents.push("Guilds","GuildMessages","DirectMessages","GuildEmojisAndStickers","GuildMembers","MessageContent","GuildWebhooks","GuildInvites")
+ client.privileges.push("MessageContent","GuildMembers")
+ client.partials.push("Channel","Message")
+ client.permissions.push("AddReactions","AttachFiles","CreatePrivateThreads","CreatePublicThreads","EmbedLinks","ManageChannels","ManageGuild","ManageMessages","ChangeNickname","ManageRoles","ManageThreads","ManageWebhooks","MentionEveryone","ReadMessageHistory","SendMessages","SendMessagesInThreads","UseApplicationCommands","UseExternalEmojis","ViewAuditLog","ViewChannel")
+ client.initClient()
+
+ //client login
+ return new Promise(async (resolve) => {
+ try{
+ client.readyListener = async () => {
+ client.activity.setStatus("custom","Configuring Open Ticket...","idle","",true)
+ resolve(client)
+ }
+ const success = await client.login(true)
+ if (!success) resolve(null)
+ }catch(err){
+ resolve(null)
+ }
+ })
+}
+
+async function renderQuickSetupBotToken(backFn:() => api.ODPromiseVoid){
+ renderHeader("โฑ๏ธ 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> ")
+
+ const answer = await terminal.inputField({
+ style:terminal.white,
+ hintStyle:terminal.gray,
+ cancelable:true
+ }).promise
+
+ if (typeof answer != "string") return await backFn()
+ else{
+ const result = await quickSetupLogin(answer)
+ if (!result){
+ //login failure
+ terminal.red.bold("\n\nโ Something went wrong with logging into the bot.\n")
+ terminal.gray("Please try again and check your token for any mistakes.\nAlso make sure the permissions and priviliged gateaway intents are configured correctly in the developer panel.")
+ await utilities.timer(3000)
+ //retry
+ await renderQuickSetupBotToken(backFn)
+ }else{
+ //login success
+ terminal.green.bold("\n\nโ
Succesfully logged into the bot.\n")
+ terminal.gray("Your bot should be online with the status 'Configuring Open Ticket...'.")
+ await utilities.timer(3000)
+ //continue
+ quickSetupStorage.client = result
+ await renderQuickSetupServer(async () => {await renderQuickSetupBotToken(backFn)})
+ }
+ }
+}
+
+async function renderQuickSetupServer(backFn:() => api.ODPromiseVoid){
+ const {client} = quickSetupStorage
+ if (!client) return
+
+ renderHeader("โฑ๏ธ 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")
+
+ const guilds = await client.getGuilds()
+ const nameList = guilds.map((g) => g.name)
+ const longestName = utilities.getLongestLength(nameList)
+ const guildList = guilds.map((g) => g.name.padEnd(longestName+5," ")+ansis.gray(" ("+g.id+")"))
+
+ const answer = await terminal.singleColumnMenu([ansis.green("๐ "),...guildList],{
+ leftPadding:"> ",
+ style:terminal.cyan,
+ selectedStyle:terminal.bgDefaultColor.bold,
+ submittedStyle:terminal.bgBlue,
+ extraLines:2,
+ cancelable:true
+ }).promise
+
+ if (answer.canceled) return backFn()
+ if (answer.selectedIndex == 0) return await renderQuickSetupServer(backFn)
+ const server = guilds[answer.selectedIndex-1]
+ quickSetupStorage.guild = server
+ await renderQuickSetupAdminRoles([],async () => {await renderQuickSetupServer(backFn)})
+}
+
+async function renderQuickSetupAdminRoles(selectedAdmins:string[],backFn:() => api.ODPromiseVoid,cachedRoles?:discord.Role[]){
+ const {client,guild} = quickSetupStorage
+ if (!client || !guild) return
+
+ renderHeader("โฑ๏ธ 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")
+
+ const roles = cachedRoles ?? (await guild.roles.fetch()).toJSON().sort((a,b) => b.position-a.position)
+ const nameList = roles.map((r) => r.name)
+ const longestName = utilities.getLongestLength(nameList)
+ const roleList = roles.map((r) => selectedAdmins.includes(r.id) ? ansis.green("(โ
) "+r.name.padEnd(longestName+5," ")+ansis.gray(" ("+r.id+")")) : r.name.padEnd(longestName+5," ")+ansis.gray(" ("+r.id+")"))
+
+ const answer = await terminal.singleColumnMenu([ansis.green("๐ "),ansis.green("๐ "),...roleList],{
+ leftPadding:"> ",
+ style:terminal.cyan,
+ selectedStyle:terminal.bgDefaultColor.bold,
+ submittedStyle:terminal.bgBlue,
+ extraLines:2,
+ cancelable:true
+ }).promise
+
+ if (answer.canceled) return backFn()
+ if (answer.selectedIndex == 0) return await renderQuickSetupAdminRoles(selectedAdmins,backFn)
+ if (answer.selectedIndex == 1){
+ quickSetupStorage.globalAdmins = selectedAdmins
+ return await renderQuickSetupColorPicker(async () => {await renderQuickSetupAdminRoles(selectedAdmins,backFn,cachedRoles)})
+ }
+ const adminRole = roles[answer.selectedIndex-2]
+ const index = selectedAdmins.findIndex((r) => r == adminRole.id)
+ if (index > -1) selectedAdmins.splice(index,1)
+ else selectedAdmins.push(adminRole.id)
+ return await renderQuickSetupAdminRoles(selectedAdmins,backFn,roles)
+}
+
+async function renderQuickSetupColorPicker(backFn:() => api.ODPromiseVoid){
+ const {client,guild,globalAdmins} = quickSetupStorage
+ if (!client || !guild || !globalAdmins) return
+
+ renderHeader("โฑ๏ธ 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> ")
+
+ const answer = await terminal.inputField({
+ style:terminal.white,
+ hintStyle:terminal.gray,
+ cancelable:true,
+ autoComplete:Array.from(presetColors.keys()),
+ autoCompleteHint:true,
+ autoCompleteMenu:autoCompleteMenuOpts as Terminal.Autocompletion
+ }).promise
+
+ if (typeof answer != "string") return await backFn()
+ else{
+ if (!Array.from(presetColors.keys()).includes(answer) && !/^#[0-9a-f]{6}$/.test(answer)){
+ terminal.red.bold("\n\nโ Please insert a valid hex-color or a color from the list. (TIP: use tab for autocomplete)\n")
+ await utilities.timer(2000)
+ return await renderQuickSetupColorPicker(backFn)
+ }
+
+ let color: discord.ColorResolvable
+ if (Array.from(presetColors.keys()).includes(answer)){
+ color = presetColors.get(answer) as `#${string}`
+ }else{
+ color = answer as `#${string}`
+ }
+ quickSetupStorage.mainColor = color
+ return await renderQuickSetupLanguage(async () => {await renderQuickSetupColorPicker(backFn)})
+ }
+}
+
+async function renderQuickSetupLanguage(backFn:() => api.ODPromiseVoid){
+ renderHeader("โฑ๏ธ 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> ")
+
+ const answer = await terminal.inputField({
+ style:terminal.white,
+ hintStyle:terminal.gray,
+ cancelable:true,
+ autoComplete:opendiscord.defaults.getDefault("languageList"),
+ autoCompleteHint:true,
+ autoCompleteMenu:autoCompleteMenuOpts as Terminal.Autocompletion
+ }).promise
+
+ if (typeof answer != "string") return await backFn()
+ else{
+ if (!opendiscord.defaults.getDefault("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)
+ }
+
+ quickSetupStorage.language = answer.toLowerCase()
+ return await renderQuickSetupCommandTypes(async () => {await renderQuickSetupLanguage(backFn)})
+ }
+}
+
+async function renderQuickSetupCommandTypes(backFn:() => api.ODPromiseVoid){
+ renderHeader("โฑ๏ธ 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")
+
+ const answer = await terminal.singleColumnMenu([
+ "Use Slash Commands",
+ "Use Text Commands",
+ "Use Both Slash & Text Commands",
+ ],{
+ 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){
+ quickSetupStorage.slashCommands = true
+ quickSetupStorage.textCommands = false
+ }else if (answer.selectedIndex == 1){
+ quickSetupStorage.slashCommands = false
+ quickSetupStorage.textCommands = true
+ }else if (answer.selectedIndex == 2){
+ quickSetupStorage.slashCommands = true
+ quickSetupStorage.textCommands = true
+ }
+ return await renderQuickSetupStatusType(async () => {await renderQuickSetupCommandTypes(backFn)})
+}
+
+async function renderQuickSetupStatusType(backFn:() => api.ODPromiseVoid){
+ renderHeader("โฑ๏ธ 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")
+
+ const answer = await terminal.singleColumnMenu([
+ "Disabled",
+ "Custom",
+ "Listening To ...",
+ "Watching ...",
+ "Playing ..."
+ ],{
+ 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) quickSetupStorage.status = {enabled:false,mode:"online",type:"custom",text:"",state:""}
+ else if (answer.selectedIndex == 1) quickSetupStorage.status = {enabled:true,mode:"online",type:"custom",text:"",state:""}
+ else if (answer.selectedIndex == 2) quickSetupStorage.status = {enabled:true,mode:"online",type:"listening",text:"",state:""}
+ else if (answer.selectedIndex == 3) quickSetupStorage.status = {enabled:true,mode:"online",type:"watching",text:"",state:""}
+ else if (answer.selectedIndex == 4) quickSetupStorage.status = {enabled:true,mode:"online",type:"playing",text:"",state:""}
+
+ if (answer.selectedIndex == 0) return await renderQuickSetupLogs(async () => {await renderQuickSetupStatusType(backFn)})
+ else return await renderQuickSetupStatusText(async () => {await renderQuickSetupStatusType(backFn)})
+}
+
+async function renderQuickSetupStatusText(backFn:() => api.ODPromiseVoid){
+ const {status} = quickSetupStorage
+ if (!status) return
+
+ renderHeader("โฑ๏ธ 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> ")
+ terminal.gray(status.type == "listening" ? "Listening To " : (status.type == "playing" ? "Playing " : (status.type == "watching" ? "Watching " : "")))
+
+ const answer = await terminal.inputField({
+ style:terminal.white,
+ hintStyle:terminal.gray,
+ cancelable:true
+ }).promise
+
+ if (typeof answer != "string") return await backFn()
+ else{
+ status.text = answer
+ return await renderQuickSetupLogs(async () => {await renderQuickSetupStatusText(backFn)})
+ }
+}
+
+async function renderQuickSetupLogs(backFn:() => api.ODPromiseVoid){
+ const {client,guild} = quickSetupStorage
+ if (!client || !guild) return
+
+ renderHeader("โฑ๏ธ 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")
+
+ const rawChannels = (await guild.channels.fetch()).toJSON().filter((c) => c !== null && c.isTextBased())
+ const channels = rawChannels.sort((a,b) => (a.position + 50*((a.parent?.position ?? -1)+1)) - (b.position + 50*((b.parent?.position ?? -1)+1)))
+ const nameList = channels.map((r) => (r.parent ? r.parent.name+" > " : "")+r.name)
+ const longestName = utilities.getLongestLength(nameList)
+ const channelList = channels.map((r) => ((r.parent ? r.parent.name+" > " : "")+r.name).padEnd(longestName+5," ")+ansis.gray(" ("+r.id+")"))
+
+ const answer = await terminal.singleColumnMenu([ansis.green("๐ "),ansis.red("โ "),...channelList],{
+ leftPadding:"> ",
+ style:terminal.cyan,
+ selectedStyle:terminal.bgDefaultColor.bold,
+ submittedStyle:terminal.bgBlue,
+ extraLines:2,
+ cancelable:true
+ }).promise
+
+ if (answer.canceled) return backFn()
+ else if (answer.selectedIndex == 0) return await renderQuickSetupLogs(backFn)
+ else if (answer.selectedIndex == 1){
+ quickSetupStorage.logChannel = null
+ return await renderQuickSetupTicketCategory(async () => {await renderQuickSetupLogs(backFn)})
+ }else{
+ const logChannel = channels[answer.selectedIndex-2]
+ quickSetupStorage.logChannel = logChannel.id
+ return await renderQuickSetupTicketCategory(async () => {await renderQuickSetupLogs(backFn)})
+ }
+}
+
+
+async function renderQuickSetupTicketCategory(backFn:() => api.ODPromiseVoid){
+ const {client,guild} = quickSetupStorage
+ if (!client || !guild) return
+
+ renderHeader("โฑ๏ธ 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")
+
+ const rawCategories = (await guild.channels.fetch()).toJSON().filter((c) => c !== null && c.type == discord.ChannelType.GuildCategory)
+ const categories = rawCategories.sort((a,b) => a.position-b.position)
+ const nameList = categories.map((r) => r.name)
+ const longestName = utilities.getLongestLength(nameList)
+ const categoryList = categories.map((r) => r.name.padEnd(longestName+5," ")+ansis.gray(" ("+r.id+")"))
+
+ const answer = await terminal.singleColumnMenu([ansis.green("๐ "),ansis.red("โ "),...categoryList],{
+ leftPadding:"> ",
+ style:terminal.cyan,
+ selectedStyle:terminal.bgDefaultColor.bold,
+ submittedStyle:terminal.bgBlue,
+ extraLines:2,
+ cancelable:true
+ }).promise
+
+ if (answer.canceled) return backFn()
+ else if (answer.selectedIndex == 0) return await renderQuickSetupTicketCategory(backFn)
+ else if (answer.selectedIndex == 1){
+ quickSetupStorage.ticketCategory = null
+ return await renderQuickSetupTicketCount(async () => {await renderQuickSetupTicketCategory(backFn)})
+ }else{
+ const ticketCategory = categories[answer.selectedIndex-2]
+ quickSetupStorage.ticketCategory = ticketCategory.id
+ return await renderQuickSetupTicketCount(async () => {await renderQuickSetupTicketCategory(backFn)})
+ }
+}
+
+async function renderQuickSetupTicketCount(backFn:() => api.ODPromiseVoid){
+ renderHeader("โฑ๏ธ 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")
+
+ const answer = await terminal.singleColumnMenu([
+ "1๏ธโฃ 1 Ticket Option",
+ "2๏ธโฃ 2 Ticket Options",
+ "3๏ธโฃ 3 Ticket Options",
+ "4๏ธโฃ 4 Ticket Options",
+ "5๏ธโฃ 5 Ticket Options",
+ ],{
+ leftPadding:"> ",
+ style:terminal.cyan,
+ selectedStyle:terminal.bgDefaultColor.bold,
+ submittedStyle:terminal.bgBlue,
+ extraLines:2,
+ cancelable:true
+ }).promise
+
+ if (answer.canceled) return await backFn()
+ else{
+ const ticketCount = answer.selectedIndex+1
+ quickSetupStorage.ticketOptions = []
+ for (let i = 0; i < ticketCount; i++){
+ quickSetupStorage.ticketOptions.push(null)
+ }
+ return await renderQuickSetupCreateTicketName(0,ticketCount,async () => {await renderQuickSetupTicketCount(backFn)})
+ }
+}
+
+async function renderQuickSetupCreateTicketName(ticketIndex:number,requiredTickets:number,backFn:() => api.ODPromiseVoid){
+ renderHeader("โฑ๏ธ 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> ")
+
+ const answer = await terminal.inputField({
+ style:terminal.white,
+ hintStyle:terminal.gray,
+ cancelable:true,
+ }).promise
+
+ if (typeof answer != "string"){
+ //delete option ticket from list when going back to menu or previous ticket settings
+ quickSetupStorage.ticketOptions[ticketIndex] = null
+ return await backFn()
+ }else if (answer.length == 0){
+ terminal.red.bold("\n\nโ Please insert a valid ticket option name.\n")
+ await utilities.timer(2000)
+ return await renderQuickSetupCreateTicketName(ticketIndex,requiredTickets,backFn)
+ }else{
+ quickSetupStorage.ticketOptions[ticketIndex] = {
+ name:answer,
+ description:"",
+ buttonType:"label",
+ buttonEmoji:null,
+ buttonColor:"gray",
+ channelPrefix:"ticket-",
+ channelSuffix:"user-name"
+ }
+ return await renderQuickSetupCreateTicketDescription(ticketIndex,requiredTickets,async () => {await renderQuickSetupCreateTicketName(ticketIndex,requiredTickets,backFn)})
+ }
+}
+
+async function renderQuickSetupCreateTicketDescription(ticketIndex:number,requiredTickets:number,backFn:() => api.ODPromiseVoid){
+ renderHeader("โฑ๏ธ 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> ")
+
+ const answer = await terminal.inputField({
+ style:terminal.white,
+ hintStyle:terminal.gray,
+ cancelable:true,
+ }).promise
+
+ if (typeof answer != "string") return await backFn()
+ else{
+ const ticketOption = quickSetupStorage.ticketOptions[ticketIndex]
+ if (ticketOption) ticketOption.description = answer.replaceAll("\\n","\n")
+ return await renderQuickSetupCreateTicketButtonType(ticketIndex,requiredTickets,async () => {await renderQuickSetupCreateTicketDescription(ticketIndex,requiredTickets,backFn)})
+ }
+}
+
+async function renderQuickSetupCreateTicketButtonType(ticketIndex:number,requiredTickets:number,backFn:() => api.ODPromiseVoid){
+ renderHeader("โฑ๏ธ 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")
+
+ const answer = await terminal.singleColumnMenu([
+ "Emoji + Label/Name",
+ "Label/Name Only",
+ "Emoji Only"
+ ],{
+ leftPadding:"> ",
+ style:terminal.cyan,
+ selectedStyle:terminal.bgDefaultColor.bold,
+ submittedStyle:terminal.bgBlue,
+ extraLines:2,
+ cancelable:true
+ }).promise
+
+ if (answer.canceled) return await backFn()
+ else{
+ const ticketOption = quickSetupStorage.ticketOptions[ticketIndex]
+ if (ticketOption) ticketOption.buttonType = (answer.selectedIndex == 0) ? "label-emoji" : ((answer.selectedIndex == 1) ? "label" : "emoji")
+ if (answer.selectedIndex == 0 || answer.selectedIndex == 2){
+ return await renderQuickSetupCreateTicketButtonEmoji(ticketIndex,requiredTickets,async () => {await renderQuickSetupCreateTicketButtonType(ticketIndex,requiredTickets,backFn)})
+ }else{
+ return await renderQuickSetupCreateTicketButtonColor(ticketIndex,requiredTickets,async () => {await renderQuickSetupCreateTicketButtonType(ticketIndex,requiredTickets,backFn)})
+ }
+ }
+}
+
+async function renderQuickSetupCreateTicketButtonEmoji(ticketIndex:number,requiredTickets:number,backFn:() => api.ODPromiseVoid){
+ renderHeader("โฑ๏ธ 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> ")
+
+ const answer = await terminal.inputField({
+ style:terminal.white,
+ hintStyle:terminal.gray,
+ cancelable:true,
+ }).promise
+
+ if (typeof answer != "string") return await backFn()
+ else{
+ //check emoji using a local config checker instance
+ const isEmojiValid = (new api.ODCheckerCustomStructure_EmojiString("opendiscord:emoji-checker",1,1,true)).check(opendiscord.checkers.createTemporaryCheckerEnvironment(),answer,["emoji"])
+ if (!isEmojiValid){
+ terminal.red.bold("\n\nโ Please insert a valid emoji.\n")
+ await utilities.timer(2000)
+ return await renderQuickSetupCreateTicketButtonEmoji(ticketIndex,requiredTickets,backFn)
+ }
+
+ const ticketOption = quickSetupStorage.ticketOptions[ticketIndex]
+ if (ticketOption) ticketOption.buttonEmoji = answer
+ return await renderQuickSetupCreateTicketButtonColor(ticketIndex,requiredTickets,async () => {await renderQuickSetupCreateTicketButtonEmoji(ticketIndex,requiredTickets,backFn)})
+ }
+}
+
+async function renderQuickSetupCreateTicketButtonColor(ticketIndex:number,requiredTickets:number,backFn:() => api.ODPromiseVoid){
+ renderHeader("โฑ๏ธ 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")
+
+ const answer = await terminal.singleColumnMenu([
+ "Gray (Default)",
+ "Blue",
+ "Red",
+ "Green"
+ ],{
+ leftPadding:"> ",
+ style:terminal.cyan,
+ selectedStyle:terminal.bgDefaultColor.bold,
+ submittedStyle:terminal.bgBlue,
+ extraLines:2,
+ cancelable:true
+ }).promise
+
+ if (answer.canceled) return await backFn()
+ else{
+ const ticketOption = quickSetupStorage.ticketOptions[ticketIndex]
+ if (ticketOption) ticketOption.buttonColor = (answer.selectedIndex == 0) ? "gray" : ((answer.selectedIndex == 1) ? "blue" : ((answer.selectedIndex == 2) ? "red" : "green"))
+ return await renderQuickSetupCreateTicketChannelPrefix(ticketIndex,requiredTickets,async () => {await renderQuickSetupCreateTicketButtonType(ticketIndex,requiredTickets,backFn)})
+ }
+}
+
+async function renderQuickSetupCreateTicketChannelPrefix(ticketIndex:number,requiredTickets:number,backFn:() => api.ODPromiseVoid){
+ renderHeader("โฑ๏ธ 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> ")
+
+ const answer = await terminal.inputField({
+ style:terminal.white,
+ hintStyle:terminal.gray,
+ cancelable:true,
+ }).promise
+
+ if (typeof answer != "string") return await backFn()
+ else if (answer.length == 0){
+ terminal.red.bold("\n\nโ Please insert a valid ticket option channel prefix.\n")
+ await utilities.timer(2000)
+ return await renderQuickSetupCreateTicketChannelPrefix(ticketIndex,requiredTickets,backFn)
+ }else{
+ const ticketOption = quickSetupStorage.ticketOptions[ticketIndex]
+ if (ticketOption) ticketOption.channelPrefix = answer
+ return await renderQuickSetupCreateTicketChannelSuffix(ticketIndex,requiredTickets,async () => {await renderQuickSetupCreateTicketChannelPrefix(ticketIndex,requiredTickets,backFn)})
+ }
+}
+
+async function renderQuickSetupCreateTicketChannelSuffix(ticketIndex:number,requiredTickets:number,backFn:() => api.ODPromiseVoid){
+ renderHeader("โฑ๏ธ 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")
+
+ const answer = await terminal.singleColumnMenu([
+ "Username "+ansis.gray("(e.g. #ticket-DJj123dj, #question-wumpus)"),
+ "User Id "+ansis.gray("(e.g. #ticket-123456789, #question-01020304)"),
+ "Random Number "+ansis.gray("(e.g. #ticket-1234, #question-1411)"),
+ "Random Hex "+ansis.gray("(e.g. #ticket-f8ba, #question-01f3)"),
+ "Dynamic Counter "+ansis.gray("(e.g. #ticket-1, #question-23)"),
+ "Fixed Counter "+ansis.gray("(e.g. #ticket-0001, #question-0023)")
+ ],{
+ leftPadding:"> ",
+ style:terminal.cyan,
+ selectedStyle:terminal.bgDefaultColor.bold,
+ submittedStyle:terminal.bgBlue,
+ extraLines:2,
+ cancelable:true
+ }).promise
+
+ if (answer.canceled) return await backFn()
+ else{
+ const ticketOption = quickSetupStorage.ticketOptions[ticketIndex]
+ if (ticketOption){
+ if (answer.selectedIndex == 0) ticketOption.channelSuffix = "user-name"
+ else if (answer.selectedIndex == 1) ticketOption.channelSuffix = "user-id"
+ else if (answer.selectedIndex == 2) ticketOption.channelSuffix = "random-number"
+ else if (answer.selectedIndex == 3) ticketOption.channelSuffix = "random-hex"
+ else if (answer.selectedIndex == 4) ticketOption.channelSuffix = "counter-dynamic"
+ else if (answer.selectedIndex == 5) ticketOption.channelSuffix = "counter-fixed"
+ }
+
+ //create next ticket
+ if (ticketIndex+1 < requiredTickets) return await renderQuickSetupCreateTicketName(ticketIndex+1,requiredTickets,async () => {await renderQuickSetupCreateTicketChannelSuffix(ticketIndex,requiredTickets,backFn)})
+ else return await renderQuickSetupAutoclose(async () => {await renderQuickSetupCreateTicketChannelSuffix(ticketIndex,requiredTickets,backFn)})
+ }
+}
+
+async function renderQuickSetupAutoclose(backFn:() => api.ODPromiseVoid){
+ renderHeader("โฑ๏ธ 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")
+
+ const answer = await terminal.singleColumnMenu([
+ ansis.red("โ "),
+ "1 Hour Inactivity",
+ "2 Hours Inactivity",
+ "4 Hours Inactivity",
+ "8 Hours Inactivity",
+ "12 Hours Inactivity",
+ "1 Day Inactivity",
+ "2 Days Inactivity",
+ "3 Days Inactivity",
+ ],{
+ 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) quickSetupStorage.autocloseHours = null
+ else quickSetupStorage.autocloseHours = [1,2,4,8,12,24,48,72][answer.selectedIndex-1]
+ return await renderQuickSetupCooldown(async () => {await renderQuickSetupAutoclose(backFn)})
+ }
+}
+
+async function renderQuickSetupCooldown(backFn:() => api.ODPromiseVoid){
+ renderHeader("โฑ๏ธ 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")
+
+ const answer = await terminal.singleColumnMenu([
+ ansis.red("โ "),
+ "1 Minute Cooldown",
+ "2 Minutes Cooldown",
+ "5 Minutes Cooldown",
+ "10 Minutes Cooldown",
+ "15 Minutes Cooldown",
+ "30 Minutes Cooldown",
+ "1 Hour Cooldown",
+ "2 Hours Cooldown",
+ "3 Hours Cooldown",
+ ],{
+ 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) quickSetupStorage.cooldownMinutes = null
+ else quickSetupStorage.cooldownMinutes = [1,2,5,10,15,30,60,120,180][answer.selectedIndex-1]
+ return await renderQuickSetupLimits(async () => {await renderQuickSetupCooldown(backFn)})
+ }
+}
+
+async function renderQuickSetupLimits(backFn:() => api.ODPromiseVoid){
+ renderHeader("โฑ๏ธ 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")
+
+ const answer = await terminal.singleColumnMenu([
+ ansis.red("โ "),
+ "Max 1 Ticket/Person",
+ "Max 2 Tickets/Person",
+ "Max 3 Tickets/Person",
+ "Max 5 Tickets/Person",
+ "Max 10 Tickets/Person",
+ "Max 20 Tickets/Person"
+ ],{
+ 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) quickSetupStorage.globalUserLimit = null
+ else quickSetupStorage.globalUserLimit = [1,2,3,5,10,20][answer.selectedIndex-1]
+ return await renderQuickSetupCloseParticipants(async () => {await renderQuickSetupLimits(backFn)})
+ }
+}
+
+async function renderQuickSetupCloseParticipants(backFn:() => api.ODPromiseVoid){
+ renderHeader("โฑ๏ธ 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")
+
+ const answer = await terminal.singleColumnMenu([
+ "โ No, don't remove ticket participants on close",
+ "โ
Yes, remove ticket participants on close",
+ ],{
+ leftPadding:"> ",
+ style:terminal.cyan,
+ selectedStyle:terminal.bgDefaultColor.bold,
+ submittedStyle:terminal.bgBlue,
+ extraLines:2,
+ cancelable:true
+ }).promise
+
+ if (answer.canceled) return await backFn()
+ else{
+ quickSetupStorage.removeParticipantsOnClose = (answer.selectedIndex == 1)
+ return await renderQuickSetupTicketMessageLayout(async () => {await renderQuickSetupCloseParticipants(backFn)})
+ }
+}
+
+async function renderQuickSetupTicketMessageLayout(backFn:() => api.ODPromiseVoid){
+ renderHeader("โฑ๏ธ 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")
+
+ const answer = await terminal.singleColumnMenu([
+ "๐ Embed Message "+ansis.gray("(Default)"),
+ "๐ฌ Raw Text Message",
+ ansis.red("โ No Message (Not Recommended)")
+ ],{
+ leftPadding:"> ",
+ style:terminal.cyan,
+ selectedStyle:terminal.bgDefaultColor.bold,
+ submittedStyle:terminal.bgBlue,
+ extraLines:2,
+ cancelable:true
+ }).promise
+
+ if (answer.canceled) return await backFn()
+ else{
+ quickSetupStorage.ticketMessageLayout = (answer.selectedIndex == 0) ? "embed" : (answer.selectedIndex == 1) ? "text" : null
+ return await renderQuickSetupEmojiStyle(async () => {await renderQuickSetupTicketMessageLayout(backFn)})
+ }
+}
+
+async function renderQuickSetupEmojiStyle(backFn:() => api.ODPromiseVoid){
+ renderHeader("โฑ๏ธ 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")
+
+ const answer = await terminal.singleColumnMenu([
+ "โ
Before โ (Default) "+ansis.gray("(e.g. ๐ซ Ticket Created)"),
+ "โ After โ
"+ansis.gray("(e.g. Ticket Created ๐ซ)"),
+ "โ
Double โ
"+ansis.gray("(e.g. ๐ซ Ticket Created ๐ซ)"),
+ "โ Disabled โ "+ansis.gray("(e.g. Ticket Created)"),
+ ],{
+ leftPadding:"> ",
+ style:terminal.cyan,
+ selectedStyle:terminal.bgDefaultColor.bold,
+ submittedStyle:terminal.bgBlue,
+ extraLines:2,
+ cancelable:true
+ }).promise
+
+ if (answer.canceled) return await backFn()
+ else{
+ quickSetupStorage.emojiStyle = (answer.selectedIndex == 0) ? "before" : (answer.selectedIndex == 1) ? "after" : (answer.selectedIndex == 2) ? "double" : "disabled"
+ return await renderQuickSetupPanelName(async () => {await renderQuickSetupEmojiStyle(backFn)})
+ }
+}
+
+async function renderQuickSetupPanelName(backFn:() => api.ODPromiseVoid){
+ renderHeader("โฑ๏ธ 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> ")
+
+ const answer = await terminal.inputField({
+ style:terminal.white,
+ hintStyle:terminal.gray,
+ cancelable:true
+ }).promise
+
+ if (typeof answer != "string") return await backFn()
+ else if (answer.length == 0){
+ terminal.red.bold("\n\nโ Please insert a valid panel name.\n")
+ await utilities.timer(2000)
+ return await renderQuickSetupPanelName(backFn)
+ }else{
+ quickSetupStorage.panelName = answer
+ return await renderQuickSetupPanelDescription(async () => {await renderQuickSetupPanelName(backFn)})
+ }
+}
+
+async function renderQuickSetupPanelDescription(backFn:() => api.ODPromiseVoid){
+ renderHeader("โฑ๏ธ 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> ")
+
+ const answer = await terminal.inputField({
+ style:terminal.white,
+ hintStyle:terminal.gray,
+ cancelable:true
+ }).promise
+
+ if (typeof answer != "string") return await backFn()
+ else{
+ quickSetupStorage.panelDescription = answer
+ return await renderQuickSetupPanelDropdown(async () => {await renderQuickSetupPanelDescription(backFn)})
+ }
+}
+
+async function renderQuickSetupPanelDropdown(backFn:() => api.ODPromiseVoid){
+ renderHeader("โฑ๏ธ 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")
+
+ const answer = await terminal.singleColumnMenu([
+ "Use Buttons "+ansis.gray("(Recommended with 2 or less ticket options)"),
+ "Use Dropdown "+ansis.gray("(Recommended with 3 or more ticket options)"),
+ ],{
+ leftPadding:"> ",
+ style:terminal.cyan,
+ selectedStyle:terminal.bgDefaultColor.bold,
+ submittedStyle:terminal.bgBlue,
+ extraLines:2,
+ cancelable:true
+ }).promise
+
+ if (answer.canceled) return await backFn()
+ else{
+ quickSetupStorage.panelDropdown = (answer.selectedIndex == 1)
+ return await renderQuickSetupPanelLayout(async () => {await renderQuickSetupPanelDropdown(backFn)})
+ }
+}
+
+async function renderQuickSetupPanelLayout(backFn:() => api.ODPromiseVoid){
+ renderHeader("โฑ๏ธ 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")
+
+ const answer = await terminal.singleColumnMenu([
+ "๐ Embed Message "+ansis.gray("(Default)"),
+ "๐ฌ Raw Text Message",
+ ],{
+ leftPadding:"> ",
+ style:terminal.cyan,
+ selectedStyle:terminal.bgDefaultColor.bold,
+ submittedStyle:terminal.bgBlue,
+ extraLines:2,
+ cancelable:true
+ }).promise
+
+ if (answer.canceled) return await backFn()
+ else{
+ quickSetupStorage.panelLayout = (answer.selectedIndex == 0) ? "embed" : "text"
+ return await renderQuickSetupPanelDescribeOptions(async () => {await renderQuickSetupPanelLayout(backFn)})
+ }
+}
+
+async function renderQuickSetupPanelDescribeOptions(backFn:() => api.ODPromiseVoid){
+ renderHeader("โฑ๏ธ 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")
+
+ const answer = await terminal.singleColumnMenu([
+ ansis.red("โ "),
+ "๐ข Use Simple Option Descriptions",
+ "๐ Use Normal Option Descriptions "+ansis.gray("(Default)"),
+ "๐ด Use Detailed Option Descriptions",
+ ],{
+ leftPadding:"> ",
+ style:terminal.cyan,
+ selectedStyle:terminal.bgDefaultColor.bold,
+ submittedStyle:terminal.bgBlue,
+ extraLines:2,
+ cancelable:true
+ }).promise
+
+ if (answer.canceled) return await backFn()
+ else{
+ quickSetupStorage.panelDescribeOptions = (answer.selectedIndex == 0) ? null : (answer.selectedIndex == 1) ? "simple" : (answer.selectedIndex == 2) ? "normal" : "detailed"
+ return await renderQuickSetupPanelMaxTicketsWarning(async () => {await renderQuickSetupPanelDescribeOptions(backFn)})
+ }
+}
+
+async function renderQuickSetupPanelMaxTicketsWarning(backFn:() => api.ODPromiseVoid){
+ renderHeader("โฑ๏ธ 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")
+
+ const answer = await terminal.singleColumnMenu([
+ "โ No, don't show the max tickets warning in the panel",
+ "โ
Yes, show the max tickets warning in the panel",
+ ],{
+ leftPadding:"> ",
+ style:terminal.cyan,
+ selectedStyle:terminal.bgDefaultColor.bold,
+ submittedStyle:terminal.bgBlue,
+ extraLines:2,
+ cancelable:true
+ }).promise
+
+ if (answer.canceled) return await backFn()
+ else{
+ quickSetupStorage.panelMaxTicketsWarning = (answer.selectedIndex == 1)
+ return await renderQuickSetupReady(async () => {await renderQuickSetupPanelMaxTicketsWarning(backFn)})
+ }
+}
+
+async function renderQuickSetupReady(backFn:() => api.ODPromiseVoid){
+ renderHeader("๐ 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")
+
+ const commands = ((quickSetupStorage.slashCommands && quickSetupStorage.textCommands) ? "Slash & Text" : (quickSetupStorage.slashCommands) ? "Slash Only" : "Text Only")
+ const statusText = (quickSetupStorage.status?.type == "listening" ? "Listening To " : (quickSetupStorage.status?.type == "playing" ? "Playing " : (quickSetupStorage.status?.type == "watching" ? "Watching " : ""))) + quickSetupStorage.status?.text
+ const status = (quickSetupStorage.status?.enabled) ? statusText : "Disabled"
+
+
+ terminal([
+ ansis.bold.hex("#f8ba00")("Client: ")+(quickSetupStorage.client?.client.user.displayName ?? "?"),
+ ansis.bold.hex("#f8ba00")("Status: ")+status,
+ ansis.bold.hex("#f8ba00")("Server: ")+(quickSetupStorage.guild?.name ?? "?"),
+ ansis.bold.hex("#f8ba00")("Admins: ")+(quickSetupStorage.globalAdmins?.length ?? "?")+" Admins",
+ ansis.bold.hex("#f8ba00")("Color: ")+(quickSetupStorage.mainColor ?? "?"),
+ ansis.bold.hex("#f8ba00")("Language: ")+(quickSetupStorage.language ?? "?"),
+ ansis.bold.hex("#f8ba00")("Commands: ")+commands,
+ ansis.bold.hex("#f8ba00")("Options: ")+quickSetupStorage.ticketOptions.length+" Tickets",
+ ansis.bold.hex("#f8ba00")("Autoclose: ")+(quickSetupStorage.autocloseHours ? quickSetupStorage.autocloseHours+" Hours" : "Disabled"),
+ ansis.bold.hex("#f8ba00")("Cooldown: ")+(quickSetupStorage.cooldownMinutes ? quickSetupStorage.cooldownMinutes+" Minutes" : "Disabled"),
+ ansis.bold.hex("#f8ba00")("Limits: ")+(quickSetupStorage.globalUserLimit ? quickSetupStorage.globalUserLimit+" Tickets/Person" : "Disabled"),
+ ansis.bold.gray("+ 13 More Settings ..."),
+ ].join("\n")+"\n\n")
+
+ const answer = await terminal.singleColumnMenu([
+ ansis.green("Press 'Enter' to save configuration!")
+ ],{
+ leftPadding:"> ",
+ style:terminal.cyan,
+ selectedStyle:terminal.bgDefaultColor.bold,
+ submittedStyle:terminal.bgBlue,
+ extraLines:2,
+ cancelable:true
+ }).promise
+
+ //save configuration
+ if (answer.canceled) return await backFn()
+ else if (answer.selectedIndex == 0){
+ await saveQuickSetupConfig()
+ return await renderQuickSetupFinished()
+ }
+}
+
+async function renderQuickSetupFinished(){
+ renderHeader("โ
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")
+
+ terminal(ansis.gray([
+ "Start the bot using the following command:",
+ ansis.blue("------------------------------"),
+ ansis.magenta.bold("> npm start"),
+ ansis.blue("------------------------------"),
+ "",
+ "Edit the existing config in the CLI or directly in the (./config/) directory:",
+ ansis.blue("------------------------------"),
+ ansis.magenta.bold("> npm run setup"),
+ ansis.blue("------------------------------"),
+ "",
+ ansis.yellow.bold("โญ Don't forget to star our Github repository when you enjoy using the bot! โญ"),
+ ansis.gray("Join our discord server "+ansis.hex("#5865F2").bold.underline("https://discord.dj-dj.be")+" when you need help with troubleshooting."),
+ ].join("\n"))+"\n\n")
+
+ await terminal.singleColumnMenu([
+ ansis.green("Press 'Enter' to exit Quick Setup CLI!")
+ ],{
+ leftPadding:"> ",
+ style:terminal.cyan,
+ selectedStyle:terminal.bgDefaultColor.bold,
+ submittedStyle:terminal.bgBlue,
+ extraLines:2,
+ cancelable:true
+ }).promise
+
+ //stop CLI
+ return await terminate()
+}
+
+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-v4.0.6"
+ },
+
+ token:quickSetupStorage.client?.token ?? "",
+ tokenFromENV:false,
+
+ mainColor:quickSetupStorage.mainColor ?? "#f8ba00",
+ language:quickSetupStorage.language ?? "english",
+ prefix:"!ticket ",
+ serverId:quickSetupStorage.guild?.id ?? "",
+ globalAdmins:quickSetupStorage.globalAdmins ?? [],
+
+ slashCommands:quickSetupStorage.slashCommands ?? false,
+ textCommands:quickSetupStorage.textCommands ?? false,
+
+ status:quickSetupStorage.status ?? {enabled:false,mode:"online",type:"custom",text:"",state:""},
+
+ system:{
+ preferSlashOverText:quickSetupStorage.slashCommands ?? false,
+ sendErrorOnUnknownCommand:true,
+ questionFieldsInCodeBlock:true,
+ displayFieldsWithQuestions:false,
+ showGlobalAdminsInPanelRoles:false,
+ disableVerifyBars:false,
+ useRedErrorEmbeds:true,
+ alwaysShowReason:false,
+ emojiStyle:quickSetupStorage.emojiStyle ?? "before",
+ pinEmoji:"๐",
+
+ replyOnTicketCreation:false,
+ replyOnReactionRole:true,
+ askPriorityOnTicketCreation:false,
+ removeParticipantsOnClose:quickSetupStorage.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:(typeof quickSetupStorage.logChannel == "string"),
+ channel:quickSetupStorage.logChannel ?? ""
+ },
+
+ limits:{
+ enabled:(typeof quickSetupStorage.globalUserLimit == "number"),
+ globalMaximum:100,
+ userMaximum:quickSetupStorage.globalUserLimit ?? 3
+ },
+
+ channelTopic:{
+ showOptionName:true,
+ showOptionDescription:false,
+ showOptionTopic:true,
+ showPriority:false,
+ showClosed:true,
+ showClaimed:false,
+ showPinned:false,
+ 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",
+ },
+
+ 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}
+ }
+ }
+ }
+ generalConfig.data = generalConfigData
+ await generalConfig.save()
+
+ //QUESTIONS CONFIG => no configuration needed (coming soonTM)
+ const questionsConfig = opendiscord.configs.get("opendiscord:questions")
+ const questionsConfigData: api.ODJsonConfig_DefaultQuestionsData = [
+ {
+ 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
+ }
+ }
+ ]
+ questionsConfig.data = questionsConfigData
+ await questionsConfig.save()
+
+ //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)
+ quickSetupStorage.optionIdStorage.push(id)
+
+ return {
+ id:id,
+ name:ticket.name,
+ description:ticket.description,
+ type:"ticket",
+
+ button:{
+ emoji:(ticket.buttonType == "label") ? "" : (ticket.buttonEmoji ?? ""),
+ label:(ticket.buttonType == "emoji") ? "" : ticket.name,
+ color:ticket.buttonColor ?? "gray"
+ },
+
+ ticketAdmins:[],
+ readonlyAdmins:[],
+ allowCreationByBlacklistedUsers:false,
+ questions:[],
+
+ channel:{
+ prefix:ticket.channelPrefix,
+ suffix:ticket.channelSuffix,
+ category:quickSetupStorage.ticketCategory ?? "",
+ closedCategory:"",
+ backupCategory:"",
+ claimedCategory:[],
+ topic:ticket.description
+ },
+
+ dmMessage:{
+ enabled:false,
+ text:"",
+ embed:{
+ enabled:false,
+ title:"",
+ description:"",
+ customColor:"",
+
+ image:"",
+ thumbnail:"",
+ fields:[],
+ timestamp:false
+ }
+ },
+ ticketMessage:{
+ enabled:true,
+ text:"",
+ embed:{
+ enabled:true,
+ title:ticket.name,
+ description:ticket.description,
+ customColor:"",
+
+ image:"",
+ thumbnail:"",
+ fields:[],
+ timestamp:true
+ },
+ ping:{
+ "@here":true,
+ "@everyone":false,
+ custom:[]
+ }
+ },
+ autoclose:{
+ enableInactiveHours:(typeof quickSetupStorage.autocloseHours == "number"),
+ inactiveHours:quickSetupStorage.autocloseHours ?? 24,
+ enableUserLeave:true,
+ disableOnClaim:false
+ },
+ autodelete:{
+ enableInactiveDays:false,
+ inactiveDays:7,
+ enableUserLeave:false,
+ disableOnClaim:false
+ },
+ cooldown:{
+ enabled:(typeof quickSetupStorage.cooldownMinutes == "number"),
+ cooldownMinutes:quickSetupStorage.cooldownMinutes ?? 10
+ },
+ limits:{
+ enabled:false,
+ globalMaximum:20,
+ userMaximum:3
+ },
+ slowMode:{
+ enabled:false,
+ slowModeSeconds:20
+ }
+ }
+ })
+ optionsConfig.data = optionsConfigData
+ await optionsConfig.save()
+
+ //PANELS CONFIG
+ const panelsConfig = opendiscord.configs.get("opendiscord:panels")
+ const panelsConfigData: api.ODJsonConfig_DefaultPanelsData = [
+ {
+ id:generateUniqueIdFromName(quickSetupStorage.panelName ?? "ticket-panel"),
+ name:quickSetupStorage.panelName ?? "Ticket Panel",
+ dropdown:quickSetupStorage.panelDropdown ?? false,
+ options:quickSetupStorage.optionIdStorage,
+
+ text:(quickSetupStorage.panelLayout == "text") ? (quickSetupStorage.panelDescription ?? "") : "",
+ embed:{
+ enabled:true,
+ title:quickSetupStorage.panelName ?? "Ticket Panel",
+ description:(quickSetupStorage.panelLayout == "embed") ? (quickSetupStorage.panelDescription ?? "") : "",
+
+ customColor:"",
+ url:"",
+
+ image:"",
+ thumbnail:"",
+
+ footer:quickSetupStorage.guild?.name ?? "",
+ fields:[],
+ timestamp:false
+ },
+ settings:{
+ dropdownPlaceholder:"Open a ticket",
+
+ enableMaxTicketsWarningInText:(quickSetupStorage.panelLayout == "text" && (quickSetupStorage.panelMaxTicketsWarning ?? false)),
+ enableMaxTicketsWarningInEmbed:(quickSetupStorage.panelLayout == "embed" && (quickSetupStorage.panelMaxTicketsWarning ?? false)),
+
+ describeOptionsLayout:quickSetupStorage.panelDescribeOptions ?? "normal",
+ describeOptionsCustomTitle:"",
+ describeOptionsInText:(quickSetupStorage.panelLayout == "text" && typeof quickSetupStorage.panelDescribeOptions == "string"),
+ describeOptionsInEmbedFields:(quickSetupStorage.panelLayout == "embed" && typeof quickSetupStorage.panelDescribeOptions == "string"),
+ describeOptionsInEmbedDescription:false
+ }
+ }
+ ]
+ panelsConfig.data = panelsConfigData
+ await panelsConfig.save()
+
+ //TRANSCRIPTS CONFIG => no configuration needed (coming soonTM)
+ const transcriptsConfig = opendiscord.configs.get("opendiscord:transcripts")
+ const transcriptsConfigData: api.ODJsonConfig_DefaultTranscriptsData = {
+ general:{
+ enabled:(typeof quickSetupStorage.logChannel == "string"),
+
+ enableChannel:true,
+ enableCreatorDM:true,
+ enableParticipantDM:false,
+ enableActiveAdminDM:false,
+ enableEveryAdminDM:false,
+
+ channel:quickSetupStorage.logChannel ?? "",
+ mode:"html"
+ },
+ embedSettings:{
+ customColor:"",
+ listAllParticipants:false,
+ includeTicketStats:false
+ },
+ textTranscriptStyle:{
+ layout:"normal",
+ includeStats:true,
+ includeIds:false,
+ includeEmbeds:true,
+ includeFiles:true,
+ includeBotMessages:true,
+
+ fileMode:"channel-name",
+ customFileName:"transcript"
+ },
+ htmlTranscriptStyle:{
+ background:{
+ enableCustomBackground:false,
+ backgroundColor:"",
+ backgroundImage:""
+ },
+ 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"
+ }
+ }
+ }
+ transcriptsConfig.data = transcriptsConfigData
+ await transcriptsConfig.save()
+}
+
+/** Steps Todo
+ * ALREADY FINISHED:
+ * - Ticket configuration => per-ticket configuration
+ * - (option ID autogenerated from name BE AWARE OF TICKETS WITH SAME NAME!! + remove unicode, spaces & special chars from id)
+ * - (embed autofilled with name+desc+color, thumbnail will automatically be set to the server icon)
+ * - (ping will be @here)
+ */
\ No newline at end of file
diff --git a/src/core/startup/init.ts b/src/core/startup/init.ts
index 0ae357e..8287218 100644
--- a/src/core/startup/init.ts
+++ b/src/core/startup/init.ts
@@ -31,12 +31,13 @@ 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 ---------------------------")
@@ -103,6 +104,10 @@ export interface ODUtilities {
* 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.
*/
@@ -112,7 +117,15 @@ export interface ODUtilities {
*
* 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
+ 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`
@@ -138,7 +151,8 @@ export class ODVersionMigration {
try{
await this.#func()
return true
- }catch{
+ }catch(err){
+ process.emit("uncaughtException",err)
return false
}
}
@@ -147,7 +161,8 @@ export class ODVersionMigration {
try{
await this.#afterInitFunc()
return true
- }catch{
+ }catch(err){
+ process.emit("uncaughtException",err)
return false
}
}
@@ -208,6 +223,9 @@ export const utilities: ODUtilities = {
})
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
@@ -244,5 +262,18 @@ export const utilities: ODUtilities = {
"LOREMIPSUM", //TODO
]
},
- ODVersionMigration
+ 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 496de92..cd0467b 100644
--- a/src/core/startup/manageMigration.ts
+++ b/src/core/startup/manageMigration.ts
@@ -1,4 +1,24 @@
import {opendiscord, api, utilities} from "../../index"
+import fs from "fs"
+
+/**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")
+ if (!rawVersion) return false
+ const version = api.ODVersion.fromString("opendiscord:last-version",rawVersion)
+ if (opendiscord.versions.get("opendiscord:version").compare(version) == "higher"){
+ return version
+ }else return false
+}
+
+/**Save all versions in `opendiscord.versions` to the global database. */
+async function saveAllVersionsToDatabase(){
+ const globalDatabase = opendiscord.databases.get("opendiscord:global")
+
+ await opendiscord.versions.loopAll(async (version,id) => {
+ await globalDatabase.set("opendiscord:last-version",id.value,version.toString())
+ })
+}
export const loadVersionMigrationSystem = async () => {
//ENTER MIGRATION CONTEXT
@@ -28,7 +48,12 @@ export const loadVersionMigrationSystem = async () => {
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)
+ 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()
@@ -36,7 +61,8 @@ export const loadVersionMigrationSystem = async () => {
return lastVersion
}
-const preloadMigrationContext = async () => {
+/**Initialize the migration context by loading the built-in flags, configs & databases. */
+async function preloadMigrationContext(){
opendiscord.debug.debug("-- MIGRATION CONTEXT START --")
await (await import("../../data/framework/flagLoader.js")).loadAllFlags()
await opendiscord.flags.init()
@@ -47,7 +73,8 @@ const preloadMigrationContext = async () => {
opendiscord.debug.visible = true
}
-const unloadMigrationContext = async () => {
+/**Unload the migration context to start the bot normally. */
+async function unloadMigrationContext(){
opendiscord.debug.visible = false
await opendiscord.databases.loopAll((database,id) => {opendiscord.databases.remove(id)})
await opendiscord.configs.loopAll((config,id) => {opendiscord.configs.remove(id)})
@@ -55,16 +82,24 @@ const unloadMigrationContext = async () => {
opendiscord.debug.debug("-- MIGRATION CONTEXT END --")
}
-const isMigrationRequired = async (): Promise