Open Ticket v4.2: Config Update (JSONC)

This commit is contained in:
DJj123dj
2026-05-17 22:28:23 +02:00
parent afd7f80b8d
commit dd8d5a12d6
106 changed files with 2199 additions and 1198 deletions
-3
View File
@@ -32,9 +32,6 @@ export interface ODTicketOptionIdMappings extends ODOptionIdConstraint {
"opendiscord:channel-prefix":ODOptionData<string>,
"opendiscord:channel-suffix":ODOptionData<"user-name"|"user-nickname"|"user-id"|"random-number"|"random-hex"|"counter-dynamic"|"counter-fixed">,
"opendiscord:channel-category":ODOptionData<string>,
"opendiscord:channel-category-closed":ODOptionData<string>,
"opendiscord:channel-category-backup":ODOptionData<string>,
"opendiscord:channel-categories-claimed":ODOptionData<{user:string,category:string}[]>,
"opendiscord:channel-topic":ODOptionData<string>,
"opendiscord:dm-message-enabled":ODOptionData<boolean>,
+124 -65
View File
@@ -31,7 +31,7 @@ interface ODQuickSetupVariables {
globalUserLimit?:number|null,
removeParticipantsOnClose?:boolean,
ticketMessageLayout?:"embed"|"text"|null,
emojiStyle?:api.ODGeneralJsonConfig_System["emojiStyle"],
emojiStyle?:api.ODGeneralJsonConfig_TicketSystem["emojiStyle"],
panelName?:string,
panelDescription?:string,
panelDropdown?:boolean,
@@ -234,7 +234,7 @@ async function renderQuickSetupBotToken(backFn:() => api.ODPromiseVoid){
cli.renderHeader(headerOpts,"⏱️ Open Ticket Quick Setup: Bot Token")
terminal.bold.blue(stepCount(2)+"Please insert the token of your discord bot.\n")
terminal.gray("This is used to configure the bot and is then stored securely in the './config/general.json' file.\n\n> ")
terminal.gray("This is used to configure the bot and is then stored securely in the './config/general.jsonc' file.\n\n> ")
const answer = await terminal.inputField({
style:terminal.white,
@@ -1203,11 +1203,7 @@ async function saveQuickSetupConfig(){
//GENERAL CONFIG
const generalConfig = opendiscord.configs.get("opendiscord:general")
const generalConfigData: api.ODGeneralJsonConfig_GeneralData = {
_INFO:{
support:"https://otdocs.dj-dj.be",
discord:"https://discord.dj-dj.be",
version:"open-ticket-"+opendiscord.versions.get("opendiscord:version").toString()
},
_CONFIG_VERSION:"open-ticket-"+opendiscord.versions.get("opendiscord:version").toString(),
token:quickSetupStorage.client?.token ?? "<unknown-token>",
tokenFromENV:false,
@@ -1222,8 +1218,29 @@ async function saveQuickSetupConfig(){
textCommands:quickSetupStorage.textCommands ?? false,
status:quickSetupStorage.status ?? {enabled:false,mode:"online",type:"custom",text:"",state:""},
logs:{
enabled:(typeof quickSetupStorage.logChannel == "string"),
channel:quickSetupStorage.logChannel ?? "",
logMessages:{
creation:{dm:true,logs:true},
closing:{dm:true,logs:true},
deleting:{dm:true,logs:true},
reopening:{dm:false,logs:true},
claiming:{dm:false,logs:true},
pinning:{dm:false,logs:true},
adding:{dm:false,logs:true},
removing:{dm:false,logs:true},
renaming:{dm:false,logs:true},
moving:{dm:true,logs:true},
blacklisting:{dm:true,logs:true},
transferring:{dm:true,logs:true},
topicChange:{dm:false,logs:true},
priorityChange:{dm:false,logs:true},
reactionRole:{dm:false,logs:true}
}
},
system:{
ticketSystem:{
preferSlashOverText:quickSetupStorage.slashCommands ?? false,
sendErrorOnUnknownCommand:true,
questionFieldsInCodeBlock:true,
@@ -1234,10 +1251,11 @@ async function saveQuickSetupConfig(){
alwaysShowReason:false,
emojiStyle:quickSetupStorage.emojiStyle ?? "before",
pinEmoji:"📌",
closeEmoji:"🔒",
replyOnTicketCreation:false,
replyOnTicketCreation:true,
replyOnReactionRole:true,
askPriorityOnTicketCreation:false,
askPriorityOnTicketCreation:true,
removeParticipantsOnClose:quickSetupStorage.removeParticipantsOnClose ?? false,
disableAutocloseAfterReopen:true,
autodeleteRequiresClosedTicket:true,
@@ -1245,7 +1263,7 @@ async function saveQuickSetupConfig(){
allowCloseBeforeMessage:false,
allowCloseBeforeAdminMessage:true,
useTranslatedConfigChecker:true,
pinFirstTicketMessage:false,
pinFirstTicketMessage:true,
enableTicketClaimButtons:true,
enableTicketCloseButtons:true,
@@ -1253,11 +1271,7 @@ async function saveQuickSetupConfig(){
enableTicketDeleteButtons:true,
enableTicketActionWithReason:true,
enableDeleteWithoutTranscript:true,
logs:{
enabled:(typeof quickSetupStorage.logChannel == "string"),
channel:quickSetupStorage.logChannel ?? ""
},
enableCreateTicketForOtherUser:true,
limits:{
enabled:(typeof quickSetupStorage.globalUserLimit == "number"),
@@ -1276,49 +1290,41 @@ async function saveQuickSetupConfig(){
showCreator:false,
showParticipants:false
},
permissions:{
help:"everyone",
panel:"admin",
ticket:"everyone",
close:"admin",
delete:"admin",
reopen:"admin",
claim:"admin",
unclaim:"admin",
pin:"admin",
unpin:"admin",
move:"admin",
rename:"admin",
add:"admin",
remove:"admin",
blacklist:"admin",
stats:"everyone",
clear:"admin",
autoclose:"admin",
autodelete:"admin",
transfer:"admin",
topic:"admin",
priority:"admin",
closedCategory:{
enabled:false,
categoryId:""
},
messages:{
creation:{dm:true,logs:true},
closing:{dm:true,logs:true},
deleting:{dm:true,logs:true},
reopening:{dm:false,logs:true},
claiming:{dm:false,logs:true},
pinning:{dm:false,logs:true},
adding:{dm:false,logs:true},
removing:{dm:false,logs:true},
renaming:{dm:false,logs:true},
moving:{dm:true,logs:true},
blacklisting:{dm:true,logs:true},
transferring:{dm:true,logs:true},
topicChange:{dm:false,logs:true},
priorityChange:{dm:false,logs:true},
reactionRole:{dm:false,logs:true}
}
backupCategory:{
enabled:false,
categoryId:""
},
claimedCategories:[],
},
permissions:{
help:"everyone",
panel:"admin",
ticket:"everyone",
close:"admin",
delete:"admin",
reopen:"admin",
claim:"admin",
unclaim:"admin",
pin:"admin",
unpin:"admin",
move:"admin",
rename:"admin",
add:"admin",
remove:"admin",
blacklist:"admin",
stats:"everyone",
clear:"admin",
autoclose:"admin",
autodelete:"admin",
transfer:"admin",
topic:"admin",
priority:"admin",
transcripts:"admin"
}
}
generalConfig.data = generalConfigData
@@ -1330,10 +1336,11 @@ async function saveQuickSetupConfig(){
{
id:"example-question-1",
name:"Example Question 1",
description:"This is a short text input question.",
type:"short",
required:true,
placeholder:"Insert your short answer here!",
placeholder:"Insert answer...",
length:{
enabled:false,
min:0,
@@ -1343,15 +1350,69 @@ async function saveQuickSetupConfig(){
{
id:"example-question-2",
name:"Example Question 2",
description:"This is a paragraph text input question.",
type:"paragraph",
required:false,
placeholder:"Insert your long answer here!",
placeholder:"Insert answer...",
length:{
enabled:false,
min:0,
max:1000
}
},
{
id:"example-question-3",
name:"Example Question 3",
description:"This is a dropdown question.",
type:"dropdown",
required:false,
placeholder:"Choose your answer...",
choices:[
{title:"Choice A",description:"Apple",emoji:"🍎"},
{title:"Choice B",description:"Banana",emoji:"🍌"},
{title:"Choice C",description:"Orange",emoji:"🍊"},
{title:"Choice D",description:"Kiwi",emoji:"🥝"}
]
},
{
id:"example-question-4",
name:"Example Question 4",
description:"This is a radio select question.",
type:"radio-select",
required:true,
choices:[
{title:"Choice A",description:"Up",selectedByDefault:false},
{title:"Choice B",description:"Down",selectedByDefault:false},
{title:"Choice C",description:"Left",selectedByDefault:false},
{title:"Choice D",description:"Right",selectedByDefault:false}
]
},
{
id:"example-question-5",
name:"Example Question 5",
description:"This is a checkbox select question.",
type:"checkbox-select",
required:true,
limits:{
enabled:false,
min:0,
max:10
},
choices:[
{title:"Choice A",description:"Happiness",selectedByDefault:false},
{title:"Choice B",description:"Anger",selectedByDefault:false},
{title:"Choice C",description:"Sadness",selectedByDefault:false},
{title:"Choice D",description:"Fear",selectedByDefault:false}
]
},
{
id:"example-text-display",
type:"text-display",
textContents:"This is a text display. It isn't a question, but allows you to display a text, explaination or details."
}
]
questionsConfig.data = questionsConfigData
@@ -1384,9 +1445,6 @@ async function saveQuickSetupConfig(){
prefix:ticket.channelPrefix,
suffix:ticket.channelSuffix,
category:quickSetupStorage.ticketCategory ?? "",
closedCategory:"",
backupCategory:"",
claimedCategory:[],
topic:ticket.description
},
@@ -1482,6 +1540,7 @@ async function saveQuickSetupConfig(){
},
settings:{
dropdownPlaceholder:"Open a ticket",
maximumButtonsPerRow:5,
enableMaxTicketsWarningInText:(quickSetupStorage.panelLayout == "text" && (quickSetupStorage.panelMaxTicketsWarning ?? false)),
enableMaxTicketsWarningInEmbed:(quickSetupStorage.panelLayout == "embed" && (quickSetupStorage.panelMaxTicketsWarning ?? false)),
+2 -2
View File
@@ -58,7 +58,7 @@ export class ODOpenTicketMain extends api.ODMain {
priorities: api.ODMappedPriorityManager
constructor(){
const version = api.ODVersion.fromString("opendiscord:version","v4.1.3")
const version = api.ODVersion.fromString("opendiscord:version","v4.2.0")
const debugfile = new api.ODDebugFileManager("./","otdebug.txt",5000,version)
const console = new api.ODConsoleManager(100,debugfile)
const debug = new api.ODDebugger(console)
@@ -108,7 +108,7 @@ export class ODOpenTicketMain extends api.ODMain {
},"openticket")
this.livestatus.useMain(this)
this.versions.add(api.ODVersion.fromString("opendiscord:version","v4.1.3"))
this.versions.add(api.ODVersion.fromString("opendiscord:version","v4.2.0"))
this.versions.add(api.ODVersion.fromString("opendiscord:transcripts","v2.1.0"))
//OPEN TICKET
+196 -95
View File
@@ -10,20 +10,20 @@ import { ODRoleUpdateMode } from "../api/role.js"
* It's used to generate typescript declarations for this class.
*/
export interface ODConfigManagerIdMappings extends api.ODConfigManagerIdConstraint {
"opendiscord:general":ODGeneralJsonConfig,
"opendiscord:questions":ODQuestionsJsonConfig,
"opendiscord:options":ODOptionsJsonConfig,
"opendiscord:panels":ODPanelsJsonConfig,
"opendiscord:transcripts":ODTranscriptsJsonConfig
"opendiscord:general":ODGeneralJsonCommentsConfig,
"opendiscord:questions":ODQuestionsJsonCommentsConfig,
"opendiscord:options":ODOptionsJsonCommentsConfig,
"opendiscord:panels":ODPanelsJsonCommentsConfig,
"opendiscord:transcripts":ODTranscriptsJsonCommentsConfig
}
///////////////////////////////////////
// CONFIG STRUCTURES, VALUES & TYPES
// --> general.json
// --> general.jsonc
///////////////////////////////////////
/**## ODGeneralJsonConfig_Status `interface`
* This interface is an object which has all properties for the status object in the `general.json` config!
* This interface is an object which has all properties for the status object in the `general.jsonc` config!
*/
export interface ODGeneralJsonConfig_Status {
/**Is the status enabled? */
@@ -39,7 +39,7 @@ export interface ODGeneralJsonConfig_Status {
}
/**## ODGeneralJsonConfig_MessageSettings `interface`
* This interface is an object which has all properties for the "system"."messages".... object in the `general.json` config!
* This interface is an object which has all properties for the "system"."messages".... object in the `general.jsonc` config!
*/
export interface ODGeneralJsonConfig_MessageSettings {
/**Enable sending DM logs to the ticket creator for this action. */
@@ -49,22 +49,10 @@ export interface ODGeneralJsonConfig_MessageSettings {
}
/**## ODGeneralJsonConfig_CmdPermissionSettingsType `type`
* This type is a collection of command permission settings for the "system"."permissions".... object in the `general.json` config!
* This type is a collection of command permission settings for the "system"."permissions".... object in the `general.jsonc` config!
*/
export type ODGeneralJsonConfig_CmdPermissionSettingsType = "admin"|"everyone"|"none"|string
/**## ODGeneralJsonConfig_Info `interface`
* This object contains a few URLs and metadata for the config.
*/
export interface ODGeneralJsonConfig_Info {
/**A link to the Open Ticket documentation. */
support:string,
/**A link to the DJdj Development discord server. */
discord:string,
/**The version of Open Ticket this config is compatible with. */
version:string
}
/**## ODGeneralJsonConfig_SystemLogs `interface`
* All settings related to the log channel.
*/
@@ -72,7 +60,9 @@ export interface ODGeneralJsonConfig_SystemLogs {
/**Enable logging. Individual actions should still be added via the `"system"."messages"..."logs"` */
enabled:boolean,
/**The channel to send logs to. */
channel:string
channel:string,
/**Configure dm & log messages for all Open Ticket commands & actions. */
logMessages:ODGeneralJsonConfig_LogMessages
}
/**## ODGeneralJsonConfig_SystemLimits `interface`
@@ -137,12 +127,13 @@ export interface ODGeneralJsonConfig_SystemPermissions {
transfer:ODGeneralJsonConfig_CmdPermissionSettingsType,
topic:ODGeneralJsonConfig_CmdPermissionSettingsType,
priority:ODGeneralJsonConfig_CmdPermissionSettingsType,
transcripts:ODGeneralJsonConfig_CmdPermissionSettingsType,
}
/**## ODGeneralJsonConfig_SystemMessages `interface`
/**## ODGeneralJsonConfig_LogMessages `interface`
* Configure dm & log messages for all Open Ticket commands & actions.
*/
export interface ODGeneralJsonConfig_SystemMessages {
export interface ODGeneralJsonConfig_LogMessages {
creation:ODGeneralJsonConfig_MessageSettings,
closing:ODGeneralJsonConfig_MessageSettings,
deleting:ODGeneralJsonConfig_MessageSettings,
@@ -160,10 +151,10 @@ export interface ODGeneralJsonConfig_SystemMessages {
reactionRole:ODGeneralJsonConfig_MessageSettings,
}
/**## ODGeneralJsonConfig_System `interface`
/**## ODGeneralJsonConfig_TicketSystem `interface`
* All settings related to the ticket system.
*/
export interface ODGeneralJsonConfig_System {
export interface ODGeneralJsonConfig_TicketSystem {
/**Prefer slash-commands over text-commands when displaying them in menu's and messages. */
preferSlashOverText:boolean,
/**Reply with "unknown command" when the prefix is used without a valid command. */
@@ -184,6 +175,8 @@ export interface ODGeneralJsonConfig_System {
emojiStyle:"before"|"after"|"double"|"disabled",
/**The emoji used when pinning tickets. This is '📌' by default. */
pinEmoji:string,
/**The emoji used when closing tickets. This is '🔒' by default. */
closeEmoji:string,
/**Reply with an ephemeral message when a ticket is created. */
replyOnTicketCreation:boolean,
@@ -220,9 +213,8 @@ export interface ODGeneralJsonConfig_System {
enableTicketActionWithReason:boolean,
/**Enable/disable the delete without transcript feature (button & /delete command). */
enableDeleteWithoutTranscript:boolean,
/**All settings related to the log channel. */
logs:ODGeneralJsonConfig_SystemLogs,
/**Enable/disable creating tickets for other users with /ticket <user>. (ADMIN ONLY) */
enableCreateTicketForOtherUser:boolean,
/**All settings related to global ticket limits. */
limits:ODGeneralJsonConfig_SystemLimits,
@@ -230,19 +222,31 @@ export interface ODGeneralJsonConfig_System {
/**All global channel topic settings. */
channelTopic:ODGeneralJsonConfig_SystemChannelTopic,
/**Configure permissions for all Open Ticket commands & actions. */
permissions:ODGeneralJsonConfig_SystemPermissions,
/**Configure dm & log messages for all Open Ticket commands & actions. */
messages:ODGeneralJsonConfig_SystemMessages
/**Move closed tickets to this channel category. */
closedCategory:{
enabled:boolean
categoryId:string
},
/**Create tickets in this channel category when the original category is full (max 50 channels). */
backupCategory:{
enabled:boolean
categoryId:string
},
/**Move claimed tickets to the matching channel category of the user that claimed the ticket. */
claimedCategories:{
/**The user who claimed the ticket. */
user:string,
/**The category to move the ticket to. */
category:string
}[],
}
/**## ODGeneralJsonConfig_GeneralData `interface`
* All contents of the `general.json` config file.
* All contents of the `general.jsonc` config file.
*/
export interface ODGeneralJsonConfig_GeneralData {
/**This object contains a few URLs and metadata for the config. */
_INFO:ODGeneralJsonConfig_Info,
_CONFIG_VERSION:string,
/**The token of the bot. (Empty when using `tokenFromENV`) */
token:string,
@@ -267,18 +271,24 @@ export interface ODGeneralJsonConfig_GeneralData {
/**All settings related to the status of the bot. */
status:ODGeneralJsonConfig_Status,
/**All settings related to the ticket system. */
system:ODGeneralJsonConfig_System
ticketSystem:ODGeneralJsonConfig_TicketSystem,
/**Configure permissions for all Open Ticket commands & actions. */
permissions:ODGeneralJsonConfig_SystemPermissions,
/**All settings related to the log channel. */
logs:ODGeneralJsonConfig_SystemLogs,
}
///////////////////////////////////////
// CONFIG STRUCTURES, VALUES & TYPES
// --> options.json
// --> options.jsonc
///////////////////////////////////////
/**## ODOptionsJsonConfig_BaseOption `interface`
* This interface is an object which has all basic properties for options in the `options.json` config!
* The basic properties for options in the `options.jsonc` config!
*/
export interface ODOptionsJsonConfig_BaseOption {
/**The id of this option. */
@@ -288,7 +298,7 @@ export interface ODOptionsJsonConfig_BaseOption {
/**The description of this option. */
description:string,
/**The type of this option. This type also determines the other option-specific variables. */
type:"ticket"|"website"|"role",
type:"ticket"|"website"|"role"|"sub-panel",
/**All settings related to the button for the 3 option types. */
button:{
/**The emoji of the button. (can also be empty) */
@@ -299,7 +309,7 @@ export interface ODOptionsJsonConfig_BaseOption {
}
/**## ODOptionsJsonConfig_OptionButtonSettings `interface`
* This interface is an object which has all button settings for ticket & reaction role options in the `options.json` config!
* The button settings for ticket, sub-panel & reaction role options in the `options.jsonc` config!
*/
export interface ODOptionsJsonConfig_OptionButtonSettings {
/**The emoji of the button. (can also be empty) */
@@ -311,7 +321,7 @@ export interface ODOptionsJsonConfig_OptionButtonSettings {
}
/**## ODOptionsJsonConfig_TicketOptionEmbedSettings `interface`
* This interface is an object which has all message embed settings for ticket options in the `options.json` config!
* The message embed settings for ticket options in the `options.jsonc` config!
*/
export interface ODOptionsJsonConfig_TicketOptionEmbedSettings {
/**Is this embed enabled? */
@@ -340,7 +350,7 @@ export interface ODOptionsJsonConfig_TicketOptionEmbedSettings {
}
/**## ODOptionsJsonConfig_TicketOptionPingSettings `interface`
* This interface is an object which has all message ping settings for ticket options in the `options.json` config!
* The message ping settings for ticket options in the `options.jsonc` config!
*/
export interface ODOptionsJsonConfig_TicketOptionPingSettings {
/**Ping `@here`. */
@@ -361,23 +371,12 @@ export interface ODOptionsJsonConfig_TicketOptionChannelSettings {
suffix:"user-name"|"user-nickname"|"user-id"|"random-number"|"random-hex"|"counter-dynamic"|"counter-fixed",
/**An optional discord category id to create this ticket in. */
category:string,
/**An optional discord category id to move this ticket to when closed. */
closedCategory:string,
/**An optional discord category id to create this ticket in when the primary one is full (max. 50 tickets). */
backupCategory:string,
/**A list of discord category ids to move this ticket to when claimed by a specific user. */
claimedCategory:{
/**The user which claimed the ticket. */
user:string,
/**The category to move the ticket to when claimed by this user. */
category:string
}[],
/**The channel topic shown at the top of the channel in discord. */
topic:string
}
/**## ODOptionsJsonConfig_TicketOption `interface`
* This interface is an object which has all ticket properties for options in the `options.json` config!
* All properties for ticket options in the `options.jsonc` config!
*/
export interface ODOptionsJsonConfig_TicketOption extends ODOptionsJsonConfig_BaseOption {
type:"ticket",
@@ -388,7 +387,7 @@ export interface ODOptionsJsonConfig_TicketOption extends ODOptionsJsonConfig_Ba
readonlyAdmins:string[],
/**When enabled, blacklisted users can still create this ticket type. (used for appeals, etc) */
allowCreationByBlacklistedUsers:boolean,
/**A list of valid question ids from the `questions.json` config. */
/**A list of valid question ids from the `questions.jsonc` config. */
questions:string[],
/**All settings related to the ticket channel itself. */
channel:ODOptionsJsonConfig_TicketOptionChannelSettings,
@@ -460,7 +459,7 @@ export interface ODOptionsJsonConfig_TicketOption extends ODOptionsJsonConfig_Ba
}
/**## ODOptionsJsonConfig_WebsiteOption `interface`
* This interface is an object which has all website properties for options in the `options.json` config!
* All properties for website options in the `options.jsonc` config!
*/
export interface ODOptionsJsonConfig_WebsiteOption extends ODOptionsJsonConfig_BaseOption {
type:"website",
@@ -469,7 +468,7 @@ export interface ODOptionsJsonConfig_WebsiteOption extends ODOptionsJsonConfig_B
}
/**## ODOptionsJsonConfig_RoleOption `interface`
* This interface is an object which has all reaction role properties for options in the `options.json` config!
* All properties for reaction-role options in the `options.jsonc` config!
*/
export interface ODOptionsJsonConfig_RoleOption extends ODOptionsJsonConfig_BaseOption {
type:"role",
@@ -484,18 +483,28 @@ export interface ODOptionsJsonConfig_RoleOption extends ODOptionsJsonConfig_Base
addOnMemberJoin:boolean
}
/**## ODOptionsJsonConfig_OptionsData `type`
* All contents of the `options.json` config file.
/**## ODOptionsJsonConfig_SubPanelOption `interface`
* All properties for sub-panel options in the `options.jsonc` config!
*/
export type ODOptionsJsonConfig_OptionsData = (ODOptionsJsonConfig_TicketOption|ODOptionsJsonConfig_WebsiteOption|ODOptionsJsonConfig_RoleOption)[]
export interface ODOptionsJsonConfig_SubPanelOption extends ODOptionsJsonConfig_BaseOption {
type:"sub-panel",
button:ODOptionsJsonConfig_OptionButtonSettings,
/**The panel ID of the sub-panel to show when the button is clicked. */
subPanelId:string
}
/**## ODOptionsJsonConfig_OptionsData `type`
* All contents of the `options.jsonc` config file.
*/
export type ODOptionsJsonConfig_OptionsData = (ODOptionsJsonConfig_TicketOption|ODOptionsJsonConfig_WebsiteOption|ODOptionsJsonConfig_RoleOption|ODOptionsJsonConfig_SubPanelOption)[]
///////////////////////////////////////
// CONFIG STRUCTURES, VALUES & TYPES
// --> panels.json
// --> panels.jsonc
///////////////////////////////////////
/**## ODPanelsJsonConfig_PanelEmbedSettings `interface`
* This interface is an object which has all message embed settings for panels in the `panels.json` config!
* This interface is an object which has all message embed settings for panels in the `panels.jsonc` config!
*/
export interface ODPanelsJsonConfig_PanelEmbedSettings {
/**Is this embed enabled? */
@@ -535,6 +544,8 @@ export interface ODPanelsJsonConfig_PanelEmbedSettings {
export interface ODPanelsJsonConfig_PanelSettings {
/**The placeholder used in the dropdown when enabled. */
dropdownPlaceholder:string,
/**The maximum amount of option buttons before starting a new row. */
maximumButtonsPerRow:number
/**Enable a max tickets warning in the text contents. */
enableMaxTicketsWarningInText:boolean,
@@ -554,7 +565,7 @@ export interface ODPanelsJsonConfig_PanelSettings {
}
/**## ODPanelsJsonConfig_Panel `interface`
* This interface is an object which has all properties for panels in the `panels.json` config!
* This interface is an object which has all properties for panels in the `panels.jsonc` config!
*/
export interface ODPanelsJsonConfig_Panel {
/**The id of this panel. */
@@ -563,7 +574,7 @@ export interface ODPanelsJsonConfig_Panel {
name:string,
/**When enabled, the panel uses a dropdown instead of buttons. */
dropdown:boolean,
/**A list of valid options ids from the `options.json` config. */
/**A list of valid options ids from the `options.jsonc` config. */
options:string[],
/**The raw text contents of this panel. (empty for embed only) */
@@ -575,19 +586,19 @@ export interface ODPanelsJsonConfig_Panel {
}
/**## ODPanelsJsonConfig_PanelsData `type`
* All contents of the `panels.json` config file.
* All contents of the `panels.jsonc` config file.
*/
export type ODPanelsJsonConfig_PanelsData = ODPanelsJsonConfig_Panel[]
///////////////////////////////////////
// CONFIG STRUCTURES, VALUES & TYPES
// --> questions.json
// --> questions.jsonc
///////////////////////////////////////
/**## ODQuestionsJsonConfig_QuestionLengthSettings `interface`
/**## ODQuestionsJsonConfig_TextLengthLimits `interface`
* This interface is a collection of settings related to length validation in a question.
*/
export interface ODQuestionsJsonConfig_QuestionLengthSettings {
export interface ODQuestionsJsonConfig_TextLengthLimits {
/**Enable text length verification. */
enabled:boolean,
/**The minimum text input length. */
@@ -596,48 +607,138 @@ export interface ODQuestionsJsonConfig_QuestionLengthSettings {
max:number
}
/**## ODQuestionsJsonConfig_CheckboxLimits `interface`
* The required amount of checkboxes validation in a question.
*/
export interface ODQuestionsJsonConfig_CheckboxLimits {
/**Enable checkbox limits. */
enabled:boolean,
/**The minimum amount of selected checkboxes. */
min:number,
/**The maximum amount of selected checkboxes. */
max:number
}
/**## ODQuestionsJsonConfig_DropdownChoice `interface`
* A dropdown choice used in `ODQuestionsJsonConfig_DropdownQuestion`
*/
export interface ODQuestionsJsonConfig_DropdownChoice {
/**The title of the choice. */
title:string,
/**The optional description of the choice. (Leave empty for none) */
description:string,
/**The optional emoji of the choice. (Leave empty for none) */
emoji:string
}
/**## ODQuestionsJsonConfig_RadioCheckboxChoice `interface`
* A radio/checkbox choice used in `ODQuestionsJsonConfig_RadioSelectQuestion` & `ODQuestionsJsonConfig_CheckboxSelectQuestion`
*/
export interface ODQuestionsJsonConfig_RadioCheckboxChoice {
/**The title of the choice. */
title:string,
/**The optional description of the choice. (Leave empty for none) */
description:string,
/**Is this choice selected by default? */
selectedByDefault:boolean
}
/**## ODQuestionsJsonConfig_BaseQuestion `interface`
* This interface is an object which has all universal properties for questions in the `questions.json` config!
* This interface is an object which has all universal properties for questions in the `questions.jsonc` config!
*/
export interface ODQuestionsJsonConfig_BaseQuestion {
/**The id of this question. */
id:string,
/**The name of this question. */
name:string,
/**The description of this question. (Leave empty for none) */
description:string,
/**The type of this question. */
type:"short"|"paragraph",
type:"short"|"paragraph"|"text-display"|"dropdown"|"radio-select"|"checkbox-select",
/**Is this question required? */
required:boolean,
}
/**## ODQuestionsJsonConfig_TextDisplayQuestion `interface`
* All properties for a text-display in the `questions.jsonc` config!
*/
export interface ODQuestionsJsonConfig_TextDisplayQuestion {
/**The id of this text-display. */
id:string,
/**The type of this question. */
type:"text-display",
/**The text contents to show in the modal. */
textContents:string
}
/**## ODQuestionsJsonConfig_ShortQuestion `interface`
* This interface is an object which has all properties for short questions in the `questions.json` config!
* All properties for short questions in the `questions.jsonc` config!
*/
export interface ODQuestionsJsonConfig_ShortQuestion extends ODQuestionsJsonConfig_BaseQuestion {
type:"short",
/**A placeholder for the question. */
placeholder:string,
/**A collection of settings related to length validation in a question. */
length:ODQuestionsJsonConfig_QuestionLengthSettings
length:ODQuestionsJsonConfig_TextLengthLimits
}
/**## ODQuestionsJsonConfig_ParagraphQuestion `interface`
* This interface is an object which has all properties for paragraph questions in the `questions.json` config!
* All properties for paragraph questions in the `questions.jsonc` config!
*/
export interface ODQuestionsJsonConfig_ParagraphQuestion extends ODQuestionsJsonConfig_BaseQuestion {
type:"paragraph",
/**A placeholder for the question. */
placeholder:string,
/**A collection of settings related to length validation in a question. */
length:ODQuestionsJsonConfig_QuestionLengthSettings
length:ODQuestionsJsonConfig_TextLengthLimits
}
/**## ODQuestionsJsonConfig_DropdownQuestion `interface`
* All properties for dropdown questions in the `questions.jsonc` config!
*/
export interface ODQuestionsJsonConfig_DropdownQuestion extends ODQuestionsJsonConfig_BaseQuestion {
type:"dropdown",
/**A placeholder for the dropdown. */
placeholder:string,
/**A list of maximum 25 dropdown choices. */
choices:ODQuestionsJsonConfig_DropdownChoice[]
}
/**## ODQuestionsJsonConfig_RadioSelectQuestion `interface`
* All properties for radio select questions in the `questions.jsonc` config!
*/
export interface ODQuestionsJsonConfig_RadioSelectQuestion extends ODQuestionsJsonConfig_BaseQuestion {
type:"radio-select",
/**A list of minimum 2, maximum 10 radio choices. */
choices:ODQuestionsJsonConfig_RadioCheckboxChoice[]
}
/**## ODQuestionsJsonConfig_CheckboxSelectQuestion `interface`
* All properties for checkbox select questions in the `questions.jsonc` config!
*/
export interface ODQuestionsJsonConfig_CheckboxSelectQuestion extends ODQuestionsJsonConfig_BaseQuestion {
type:"checkbox-select",
/**Verify the checked amount of checkboxes with a minimum & maximum. */
limits:ODQuestionsJsonConfig_CheckboxLimits
/**A list of minimum 1, maximum 10 checkbox choices. */
choices:ODQuestionsJsonConfig_RadioCheckboxChoice[]
}
/**## ODQuestionsJsonConfig_QuestionsData `type`
* All contents of the `questions.json` config file.
* All contents of the `questions.jsonc` config file.
*/
export type ODQuestionsJsonConfig_QuestionsData = (ODQuestionsJsonConfig_ShortQuestion|ODQuestionsJsonConfig_ParagraphQuestion)[]
export type ODQuestionsJsonConfig_QuestionsData = (
ODQuestionsJsonConfig_ShortQuestion|
ODQuestionsJsonConfig_ParagraphQuestion|
ODQuestionsJsonConfig_TextDisplayQuestion|
ODQuestionsJsonConfig_DropdownQuestion|
ODQuestionsJsonConfig_RadioSelectQuestion|
ODQuestionsJsonConfig_CheckboxSelectQuestion
)[]
///////////////////////////////////////
// CONFIG STRUCTURES, VALUES & TYPES
// --> transcripts.json
// --> transcripts.jsonc
///////////////////////////////////////
/**## ODTranscriptsJsonConfig_TranscriptsTextLayout `interface`
@@ -712,7 +813,7 @@ export interface ODTranscriptsJsonConfig_TranscriptsHtmlLayout {
}
/**## ODTranscriptsJsonConfig_TranscriptsData `interface`
* All contents of the `transcripts.json` config file.
* All contents of the `transcripts.jsonc` config file.
*/
export interface ODTranscriptsJsonConfig_TranscriptsData {
/**All general settings related to transcripts. */
@@ -760,27 +861,27 @@ export interface ODTranscriptsJsonConfig_TranscriptsData {
*/
export class ODMappedConfigManager extends api.ODConfigManager<ODConfigManagerIdMappings> {}
/**## ODGeneralJsonConfig `class
* A special class with types for the Open Ticket `config/general.json` config file
/**## ODGeneralJsonCommentsConfig `class
* A special class with types for the Open Ticket `config/general.jsonc` config file
*/
export class ODGeneralJsonConfig extends api.ODJsonConfig<ODGeneralJsonConfig_GeneralData> {}
export class ODGeneralJsonCommentsConfig extends api.ODJsonCommentsConfig<ODGeneralJsonConfig_GeneralData> {}
/**## ODQuestionsJsonConfig `class
* A special class with types for the Open Ticket `config/questions.json` config file
/**## ODQuestionsJsonCommentsConfig `class
* A special class with types for the Open Ticket `config/questions.jsonc` config file
*/
export class ODQuestionsJsonConfig extends api.ODJsonConfig<ODQuestionsJsonConfig_QuestionsData> {}
export class ODQuestionsJsonCommentsConfig extends api.ODJsonCommentsConfig<ODQuestionsJsonConfig_QuestionsData> {}
/**## ODOptionsJsonConfig `class
* A special class with types for the Open Ticket `config/options.json` config file
/**## ODOptionsJsonCommentsConfig `class
* A special class with types for the Open Ticket `config/options.jsonc` config file
*/
export class ODOptionsJsonConfig extends api.ODJsonConfig<ODOptionsJsonConfig_OptionsData> {}
export class ODOptionsJsonCommentsConfig extends api.ODJsonCommentsConfig<ODOptionsJsonConfig_OptionsData> {}
/**## ODPanelsJsonConfig `class
* A special class with types for the Open Ticket `config/panels.json` config file
/**## ODPanelsJsonCommentsConfig `class
* A special class with types for the Open Ticket `config/panels.jsonc` config file
*/
export class ODPanelsJsonConfig extends api.ODJsonConfig<ODPanelsJsonConfig_PanelsData> {}
export class ODPanelsJsonCommentsConfig extends api.ODJsonCommentsConfig<ODPanelsJsonConfig_PanelsData> {}
/**## ODTranscriptsJsonConfig `class
* A special class with types for the Open Ticket `config/transcripts.json` config file
/**## ODTranscriptsJsonCommentsConfig `class
* A special class with types for the Open Ticket `config/transcripts.jsonc` config file
*/
export class ODTranscriptsJsonConfig extends api.ODJsonConfig<ODTranscriptsJsonConfig_TranscriptsData> {}
export class ODTranscriptsJsonCommentsConfig extends api.ODJsonCommentsConfig<ODTranscriptsJsonConfig_TranscriptsData> {}
+5 -5
View File
@@ -6,21 +6,21 @@ import * as api from "@open-discord-bots/framework/api"
export interface ODOpenTicketFuseList {
/**Load the default Open Ticket ticket priority levels. */
priorityLoading:boolean,
/**Load the default Open Ticket questions (from `config/questions.json`) */
/**Load the default Open Ticket questions (from `config/questions.jsonc`) */
questionLoading:boolean,
/**Load the default Open Ticket options (from `config/options.json`) */
/**Load the default Open Ticket options (from `config/options.jsonc`) */
optionLoading:boolean,
/**Load the default Open Ticket panels (from `config/panels.json`) */
/**Load the default Open Ticket panels (from `config/panels.jsonc`) */
panelLoading:boolean,
/**Load the default Open Ticket tickets (from `database/tickets.json`) */
ticketLoading:boolean,
/**Load the default Open Ticket reaction roles (from `config/options.json`) */
/**Load the default Open Ticket reaction roles (from `config/options.jsonc`) */
roleLoading:boolean,
/**Load the default Open Ticket blacklist (from `database/users.json`) */
blacklistLoading:boolean,
/**Load the default Open Ticket transcript compilers. */
transcriptCompilerLoading:boolean,
/**Load the default Open Ticket transcript history (from `database/transcripts.json`) */
/**Load the default Open Ticket transcript history (from `database/transcripts.jsonc`) */
transcriptHistoryLoading:boolean,
/**The interval in milliseconds that are between autoclose timeout checkers. */
autocloseCheckInterval:number,
+95 -49
View File
@@ -1,9 +1,24 @@
import {opendiscord, api, utilities} from "../../index.js"
import fs from "fs"
import path from "path"
/**Check if the no-migration flag is active. */
function isMigrationAllowedFromFlag(){
return (!process.argv.includes("--no-migration") && !process.argv.includes("-nm"))
}
/**Read the global.json database raw to detect the last version of the bot. */
function getRawLastVersion(){
const isDevDatabase = process.argv.includes("--dev-database") || process.argv.includes("-dd")
const globalDatabaseLocation = path.join(process.cwd(),(isDevDatabase) ? "./devdatabase/global.json" : "./database/global.json")
const rawData: api.ODJsonDatabaseStructure = JSON.parse(fs.readFileSync(globalDatabaseLocation).toString())
const lastVersion = rawData.find((d) => d.category == "opendiscord:last-version" && d.key == "opendiscord:version")?.value ?? null
return lastVersion as string|null
}
/**Check if migration is required. Returns the last version used in the database. */
async function isMigrationRequired(): Promise<false|api.ODVersion> {
const rawVersion = await opendiscord.databases.get("opendiscord:global").get("opendiscord:last-version","opendiscord:version")
const rawVersion = getRawLastVersion()
if (!rawVersion) return false
const version = api.ODVersion.fromString("opendiscord:last-version",rawVersion)
if (opendiscord.versions.get("opendiscord:version").compare(version) == "higher"){
@@ -20,47 +35,6 @@ async function saveAllVersionsToDatabase(){
})
}
export async function loadVersionMigrationSystem(){
//ENTER MIGRATION CONTEXT
await preloadMigrationContext()
const lastVersion = await isMigrationRequired()
//save last version to database (OR set to current version if no migration is required)
opendiscord.versions.add(lastVersion ? lastVersion : api.ODVersion.fromString("opendiscord:last-version",opendiscord.versions.get("opendiscord:version").toString()))
if (lastVersion && !opendiscord.flags.get("opendiscord:no-migration").value){
//MIGRATION IS REQUIRED
opendiscord.log("Detected old data!","info")
opendiscord.log("Starting closed API context...","debug")
await utilities.timer(600)
opendiscord.log("Migrating data to new version...","debug")
await loadAllVersionMigrations(lastVersion)
opendiscord.log("Stopping closed API context...","debug")
await utilities.timer(400)
opendiscord.log("All data is now up to date!","info")
await utilities.timer(200)
console.log("---------------------------------------------------------------------")
}
saveAllVersionsToDatabase()
//DEFAULT FLAGS
if (opendiscord.flags.exists("opendiscord:no-plugins") && opendiscord.flags.get("opendiscord:no-plugins").value) opendiscord.sharedFuses.setFuse("pluginLoading",false)
if (opendiscord.flags.exists("opendiscord:soft-plugins") && opendiscord.flags.get("opendiscord:soft-plugins").value) opendiscord.sharedFuses.setFuse("softPluginLoading",true)
if (opendiscord.flags.exists("opendiscord:crash") && opendiscord.flags.get("opendiscord:crash").value) opendiscord.sharedFuses.setFuse("crashOnError",true)
if (opendiscord.flags.exists("opendiscord:force-slash-update") && opendiscord.flags.get("opendiscord:force-slash-update").value){
opendiscord.sharedFuses.setFuse("forceSlashCommandRegistration",true)
opendiscord.sharedFuses.setFuse("forceContextMenuRegistration",true)
}
if (opendiscord.flags.exists("opendiscord:silent") && opendiscord.flags.get("opendiscord:silent").value) opendiscord.console.silent = true
//LEAVE MIGRATION CONTEXT
await unloadMigrationContext()
return lastVersion
}
/**Initialize the migration context by loading the built-in flags, configs & databases. */
async function preloadMigrationContext(){
opendiscord.debug.debug("-- MIGRATION CONTEXT START --")
@@ -73,6 +47,52 @@ async function preloadMigrationContext(){
opendiscord.debug.visible = true
}
export async function loadVersionMigrationSystem(){
const lastVersion = await isMigrationRequired()
//save last version in version manager (OR set to current version if no migration is required)
opendiscord.versions.add(lastVersion ? lastVersion : api.ODVersion.fromString("opendiscord:last-version",opendiscord.versions.get("opendiscord:version").toString()))
//MIGRATION IS REQUIRED
if (lastVersion && isMigrationAllowedFromFlag()){
//BEFORE STARTUP MIGRATION
opendiscord.log("Detected old data!","info")
await loadBeforeStartupMigrations(lastVersion)
}
//ENTER MIGRATION CONTEXT (must be separate for flags to work)
await preloadMigrationContext()
if (lastVersion && isMigrationAllowedFromFlag()){
//CONTEXT MIGRATION
opendiscord.log("Starting restricted API context...","debug")
await utilities.timer(600)
opendiscord.log("Migrating data to new version...","debug")
await loadContextMigrations(lastVersion)
opendiscord.log("Stopping restricted API context...","debug")
await utilities.timer(400)
opendiscord.log("All data is now up to date!","info")
await utilities.timer(200)
console.log("---------------------------------------------------------------------")
}
saveAllVersionsToDatabase()
//SET FUSES & PROPERTIES OF SPECIAL FLAGS
if (opendiscord.flags.exists("opendiscord:no-plugins") && opendiscord.flags.get("opendiscord:no-plugins").value) opendiscord.sharedFuses.setFuse("pluginLoading",false)
if (opendiscord.flags.exists("opendiscord:soft-plugins") && opendiscord.flags.get("opendiscord:soft-plugins").value) opendiscord.sharedFuses.setFuse("softPluginLoading",true)
if (opendiscord.flags.exists("opendiscord:crash") && opendiscord.flags.get("opendiscord:crash").value) opendiscord.sharedFuses.setFuse("crashOnError",true)
if (opendiscord.flags.exists("opendiscord:force-slash-update") && opendiscord.flags.get("opendiscord:force-slash-update").value){
opendiscord.sharedFuses.setFuse("forceSlashCommandRegistration",true)
opendiscord.sharedFuses.setFuse("forceContextMenuRegistration",true)
}
if (opendiscord.flags.exists("opendiscord:silent") && opendiscord.flags.get("opendiscord:silent").value) opendiscord.console.silent = true
//LEAVE MIGRATION CONTEXT
await unloadMigrationContext()
return lastVersion
}
/**Unload the migration context to start the bot normally. */
async function unloadMigrationContext(){
opendiscord.debug.visible = false
@@ -98,8 +118,8 @@ function createMigrationBackup(){
else fs.cpSync("./database/","./.backup/database/",{force:true,recursive:true})
}
/**Execute all version migration functions which are handled in the restricted migration context. */
async function loadAllVersionMigrations(lastVersion:api.ODVersion){
/**Execute all version migration functions which are handled before any flags, configs or databases are loaded. */
async function loadBeforeStartupMigrations(lastVersion:api.ODVersion){
const migrations = (await import("./migration.js")).migrations
migrations.sort((a,b) => {
const comparison = a.version.compare(b.version)
@@ -114,10 +134,36 @@ async function loadAllVersionMigrations(lastVersion:api.ODVersion){
for (const migration of migrations){
if (migration.version.compare(lastVersion) == "higher"){
const success = await migration.migrate()
const success = await migration.migrateBeforeStartup()
if (success) opendiscord.log("Migrated data to "+migration.version.toString()+"!","debug",[
{key:"success",value:success ? "true" : "false"},
{key:"afterInit",value:"false"}
{key:"type",value:"before-startup"}
])
else throw new api.ODSystemError("Migration Error: Unable to migrate database & config to the new version of the bot.")
}
}
}
/**Execute all version migration functions which are handled in the restricted migration context. */
async function loadContextMigrations(lastVersion:api.ODVersion){
const migrations = (await import("./migration.js")).migrations
migrations.sort((a,b) => {
const comparison = a.version.compare(b.version)
if (comparison == "equal") return 0
else if (comparison == "higher") return 1
else return -1
})
if (migrations.length > 0){
//create backup of config & database
createMigrationBackup()
}
for (const migration of migrations){
if (migration.version.compare(lastVersion) == "higher"){
const success = await migration.migrateInContext()
if (success) opendiscord.log("Migrated data to "+migration.version.toString()+"!","debug",[
{key:"success",value:success ? "true" : "false"},
{key:"type",value:"restricted-context"}
])
else throw new api.ODSystemError("Migration Error: Unable to migrate database & config to the new version of the bot.")
}
@@ -125,7 +171,7 @@ async function loadAllVersionMigrations(lastVersion:api.ODVersion){
}
/**Execute all version migration functions which are handled in the normal startup sequence. */
export async function loadAllAfterInitVersionMigrations(lastVersion:api.ODVersion){
export async function loadAfterStartupMigrations(lastVersion:api.ODVersion){
const migrations = (await import("./migration.js")).migrations
migrations.sort((a,b) => {
const comparison = a.version.compare(b.version)
@@ -140,10 +186,10 @@ export async function loadAllAfterInitVersionMigrations(lastVersion:api.ODVersio
for (const migration of migrations){
if (migration.version.compare(lastVersion) == "higher"){
const success = await migration.migrateAfterInit()
const success = await migration.migrateAfterStartup()
if (success) opendiscord.log("Migrated data to "+migration.version.toString()+"!","debug",[
{key:"success",value:success ? "true" : "false"},
{key:"afterInit",value:"true"}
{key:"type",value:"after-startup"}
])
else throw new api.ODSystemError("Migration Error: Unable to migrate database & config to the new version of the bot.")
}
+308 -104
View File
@@ -1,147 +1,351 @@
import { opendiscord, api, utilities } from "../../index.js"
import fs from "fs"
import path from "path"
export const migrations = [
//MIGRATE TO v4.0.0
new api.ODVersionMigration(api.ODVersion.fromString("opendiscord:version","v4.0.0"),async () => {},async () => {}),
new api.ODVersionMigration(api.ODVersion.fromString("opendiscord:version","v4.0.0"),{}),
//MIGRATE TO v4.0.1
new api.ODVersionMigration(api.ODVersion.fromString("opendiscord:version","v4.0.1"),async () => {},async () => {
//AFTER INIT MIGRATION
//add opendiscord:panel-message properties for all existing panels.
const globalDatabase = opendiscord.databases.get("opendiscord:global")
for (const panel of (await globalDatabase.getCategory("opendiscord:panel-update") ?? [])){
globalDatabase.set("opendiscord:panel-message",panel.key,panel.value)
new api.ODVersionMigration(api.ODVersion.fromString("opendiscord:version","v4.0.1"),{
afterStartupMigrate:async () => {
//add opendiscord:panel-message properties for all existing panels.
const globalDatabase = opendiscord.databases.get("opendiscord:global")
for (const panel of (await globalDatabase.getCategory("opendiscord:panel-update") ?? [])){
globalDatabase.set("opendiscord:panel-message",panel.key,panel.value)
}
}
}),
//MIGRATE TO v4.0.2
new api.ODVersionMigration(api.ODVersion.fromString("opendiscord:version","v4.0.2"),async () => {},async () => {}),
new api.ODVersionMigration(api.ODVersion.fromString("opendiscord:version","v4.0.2"),{}),
//MIGRATE TO v4.0.3
new api.ODVersionMigration(api.ODVersion.fromString("opendiscord:version","v4.0.3"),async () => {},async () => {}),
new api.ODVersionMigration(api.ODVersion.fromString("opendiscord:version","v4.0.3"),{}),
//MIGRATE TO v4.0.4
new api.ODVersionMigration(api.ODVersion.fromString("opendiscord:version","v4.0.4"),async () => {},async () => {}),
new api.ODVersionMigration(api.ODVersion.fromString("opendiscord:version","v4.0.4"),{}),
//MIGRATE TO v4.0.5
new api.ODVersionMigration(api.ODVersion.fromString("opendiscord:version","v4.0.5"),async () => {},async () => {}),
new api.ODVersionMigration(api.ODVersion.fromString("opendiscord:version","v4.0.5"),{}),
//MIGRATE TO v4.0.6
new api.ODVersionMigration(api.ODVersion.fromString("opendiscord:version","v4.0.6"),async () => {},async () => {}),
new api.ODVersionMigration(api.ODVersion.fromString("opendiscord:version","v4.0.6"),{}),
//MIGRATE TO v4.0.7
new api.ODVersionMigration(api.ODVersion.fromString("opendiscord:version","v4.0.7"),async () => {},async () => {}),
new api.ODVersionMigration(api.ODVersion.fromString("opendiscord:version","v4.0.7"),{}),
//MIGRATE TO v4.1.0
new api.ODVersionMigration(api.ODVersion.fromString("opendiscord:version","v4.1.0"),async () => {},async () => {
//AFTER INIT MIGRATION
new api.ODVersionMigration(api.ODVersion.fromString("opendiscord:version","v4.1.0"),{
afterStartupMigrate:async () => {
//migrate config
const generalConfig = opendiscord.configs.get("opendiscord:general")
const optionConfig = opendiscord.configs.get("opendiscord:options")
//migrate config
const generalConfig = opendiscord.configs.get("opendiscord:general")
const optionConfig = opendiscord.configs.get("opendiscord:options")
if (!generalConfig.data.status.state){
//only migrate config when it hasn't been done manually by the user.
if (!generalConfig.data.status.state){
//only migrate config when it hasn't been done manually by the user.
if (!generalConfig.data["_INFO"]) throw new api.ODSystemError("Couldn't find general.json '_INFO' category.")
generalConfig.data["_INFO"].version = "open-ticket-v4.1.0"
if (!generalConfig.data._INFO) throw new api.ODSystemError("Couldn't find general.json '_INFO' category.")
generalConfig.data._INFO.version = "open-ticket-v4.1.0"
if (!generalConfig.data.status) throw new api.ODSystemError("Couldn't find general.json 'status' category.")
generalConfig.data.status.mode = generalConfig.data.status["status"] ?? "online"
generalConfig.data.status.state = ""
delete generalConfig.data.status["status"]
if (!generalConfig.data.status) throw new api.ODSystemError("Couldn't find general.json 'status' category.")
generalConfig.data.status.mode = generalConfig.data.status["status"] ?? "online"
generalConfig.data.status.state = ""
delete generalConfig.data.status["status"]
if (!generalConfig.data["system"]) throw new api.ODSystemError("Couldn't find general.json 'system' category.")
generalConfig.data["system"].displayFieldsWithQuestions = false
generalConfig.data["system"].showGlobalAdminsInPanelRoles = false
generalConfig.data["system"].alwaysShowReason = false
generalConfig.data["system"].pinEmoji = "📌"
generalConfig.data["system"].askPriorityOnTicketCreation = false
generalConfig.data["system"].disableAutocloseAfterReopen = true
generalConfig.data["system"].autodeleteRequiresClosedTicket = true
generalConfig.data["system"].adminOnlyDeleteWithoutTranscript = true
generalConfig.data["system"].allowCloseBeforeMessage = false
generalConfig.data["system"].allowCloseBeforeAdminMessage = true
generalConfig.data["system"].pinFirstTicketMessage = false
if (!generalConfig.data.system) throw new api.ODSystemError("Couldn't find general.json 'system' category.")
generalConfig.data.system.displayFieldsWithQuestions = false
generalConfig.data.system.showGlobalAdminsInPanelRoles = false
generalConfig.data.system.alwaysShowReason = false
generalConfig.data.system.pinEmoji = "📌"
generalConfig.data.system.askPriorityOnTicketCreation = false
generalConfig.data.system.disableAutocloseAfterReopen = true
generalConfig.data.system.autodeleteRequiresClosedTicket = true
generalConfig.data.system.adminOnlyDeleteWithoutTranscript = true
generalConfig.data.system.allowCloseBeforeMessage = false
generalConfig.data.system.allowCloseBeforeAdminMessage = true
generalConfig.data.system.pinFirstTicketMessage = false
generalConfig.data.system.channelTopic = {
showOptionName:true,
showOptionDescription:false,
showOptionTopic:true,
showPriority:false,
showClosed:true,
showClaimed:false,
showPinned:false,
showCreator:false,
showParticipants:false
}
if (!generalConfig.data.system.permissions) throw new api.ODSystemError("Couldn't find general.json 'system.permissions' category.")
generalConfig.data.system.permissions.transfer = "admin"
generalConfig.data.system.permissions.topic = "admin"
generalConfig.data.system.permissions.priority = "admin"
if (!generalConfig.data.system.messages) throw new api.ODSystemError("Couldn't find general.json 'system.messages' category.")
generalConfig.data.system.messages.transferring = {dm:false,logs:true}
generalConfig.data.system.messages.topicChange = {dm:false,logs:true}
generalConfig.data.system.messages.priorityChange = {dm:false,logs:true}
generalConfig.data.system.messages.reactionRole = generalConfig.data.system.messages["roleAdding"] ?? {dm:false,logs:true}
delete generalConfig.data.system.messages["roleAdding"]
delete generalConfig.data.system.messages["roleRemoving"]
for (const option of optionConfig.data){
if (option.type != "ticket") continue
option.channel.topic = option.channel["description"] ?? ""
delete option.channel["description"]
option.slowMode = {
enabled:false,
slowModeSeconds:20
generalConfig.data["system"].channelTopic = {
showOptionName:true,
showOptionDescription:false,
showOptionTopic:true,
showPriority:false,
showClosed:true,
showClaimed:false,
showPinned:false,
showCreator:false,
showParticipants:false
}
if (!generalConfig.data["system"].permissions) throw new api.ODSystemError("Couldn't find general.json 'system.permissions' category.")
generalConfig.data["system"].permissions.transfer = "admin"
generalConfig.data["system"].permissions.topic = "admin"
generalConfig.data["system"].permissions.priority = "admin"
if (!generalConfig.data["system"].messages) throw new api.ODSystemError("Couldn't find general.json 'system.messages' category.")
generalConfig.data["system"].messages.transferring = {dm:false,logs:true}
generalConfig.data["system"].messages.topicChange = {dm:false,logs:true}
generalConfig.data["system"].messages.priorityChange = {dm:false,logs:true}
generalConfig.data["system"].messages.reactionRole = generalConfig.data["system"].messages["roleAdding"] ?? {dm:false,logs:true}
delete generalConfig.data["system"].messages["roleAdding"]
delete generalConfig.data["system"].messages["roleRemoving"]
for (const option of optionConfig.data){
if (option.type != "ticket") continue
option.channel.topic = option.channel["description"] ?? ""
delete option.channel["description"]
option.slowMode = {
enabled:false,
slowModeSeconds:20
}
}
await generalConfig.save()
await optionConfig.save()
}
await generalConfig.save()
await optionConfig.save()
}
//migrate database
const optionDatabase = opendiscord.databases.get("opendiscord:options")
const ticketDatabase = opendiscord.databases.get("opendiscord:tickets")
//migrate database
const optionDatabase = opendiscord.databases.get("opendiscord:options")
const ticketDatabase = opendiscord.databases.get("opendiscord:tickets")
for (const option of (await optionDatabase.getCategory("opendiscord:used-option") ?? [])){
const optionData = option.value
const topicData = optionData.data.find((d) => d.id == "opendiscord:channel-description")
if (topicData) topicData.id = "opendiscord:channel-topic"
if (!optionData.data.find((d) => d.id == "opendiscord:slowmode-enabled")) optionData.data.push({id:"opendiscord:slowmode-enabled",value:false})
if (!optionData.data.find((d) => d.id == "opendiscord:slowmode-seconds")) optionData.data.push({id:"opendiscord:slowmode-seconds",value:20})
for (const option of (await optionDatabase.getCategory("opendiscord:used-option") ?? [])){
const optionData = option.value
const topicData = optionData.data.find((d) => d.id == "opendiscord:channel-description")
if (topicData) topicData.id = "opendiscord:channel-topic"
if (!optionData.data.find((d) => d.id == "opendiscord:slowmode-enabled")) optionData.data.push({id:"opendiscord:slowmode-enabled",value:false})
if (!optionData.data.find((d) => d.id == "opendiscord:slowmode-seconds")) optionData.data.push({id:"opendiscord:slowmode-seconds",value:20})
optionDatabase.set("opendiscord:used-option",option.key,optionData)
}
optionDatabase.set("opendiscord:used-option",option.key,optionData)
}
for (const ticket of (await ticketDatabase.getCategory("opendiscord:ticket") ?? [])){
const ticketData = ticket.value
if (!ticketData.data.find((d) => d.id == "opendiscord:previous-creators")) ticketData.data.push({id:"opendiscord:previous-creators",value:[]})
if (!ticketData.data.find((d) => d.id == "opendiscord:reopened")) ticketData.data.push({id:"opendiscord:reopened",value:false})
if (!ticketData.data.find((d) => d.id == "opendiscord:reopened-by")) ticketData.data.push({id:"opendiscord:reopened-by",value:null})
if (!ticketData.data.find((d) => d.id == "opendiscord:reopened-on")) ticketData.data.push({id:"opendiscord:reopened-on",value:null})
if (!ticketData.data.find((d) => d.id == "opendiscord:priority")) ticketData.data.push({id:"opendiscord:priority",value:-1})
if (!ticketData.data.find((d) => d.id == "opendiscord:topic")) ticketData.data.push({id:"opendiscord:topic",value:""})
if (!ticketData.data.find((d) => d.id == "opendiscord:message-sent")) ticketData.data.push({id:"opendiscord:message-sent",value:true})
if (!ticketData.data.find((d) => d.id == "opendiscord:admin-message-sent")) ticketData.data.push({id:"opendiscord:admin-message-sent",value:true})
for (const ticket of (await ticketDatabase.getCategory("opendiscord:ticket") ?? [])){
const ticketData = ticket.value
if (!ticketData.data.find((d) => d.id == "opendiscord:previous-creators")) ticketData.data.push({id:"opendiscord:previous-creators",value:[]})
if (!ticketData.data.find((d) => d.id == "opendiscord:reopened")) ticketData.data.push({id:"opendiscord:reopened",value:false})
if (!ticketData.data.find((d) => d.id == "opendiscord:reopened-by")) ticketData.data.push({id:"opendiscord:reopened-by",value:null})
if (!ticketData.data.find((d) => d.id == "opendiscord:reopened-on")) ticketData.data.push({id:"opendiscord:reopened-on",value:null})
if (!ticketData.data.find((d) => d.id == "opendiscord:priority")) ticketData.data.push({id:"opendiscord:priority",value:-1})
if (!ticketData.data.find((d) => d.id == "opendiscord:topic")) ticketData.data.push({id:"opendiscord:topic",value:""})
if (!ticketData.data.find((d) => d.id == "opendiscord:message-sent")) ticketData.data.push({id:"opendiscord:message-sent",value:true})
if (!ticketData.data.find((d) => d.id == "opendiscord:admin-message-sent")) ticketData.data.push({id:"opendiscord:admin-message-sent",value:true})
ticketDatabase.set("opendiscord:ticket",ticket.key,ticketData)
ticketDatabase.set("opendiscord:ticket",ticket.key,ticketData)
}
}
}),
//MIGRATE TO v4.1.1
new api.ODVersionMigration(api.ODVersion.fromString("opendiscord:version","v4.1.1"),async () => {},async () => {}),
new api.ODVersionMigration(api.ODVersion.fromString("opendiscord:version","v4.1.1"),{}),
//MIGRATE TO v4.1.2
new api.ODVersionMigration(api.ODVersion.fromString("opendiscord:version","v4.1.2"),async () => {},async () => {}),
new api.ODVersionMigration(api.ODVersion.fromString("opendiscord:version","v4.1.2"),{}),
//MIGRATE TO v4.1.3
new api.ODVersionMigration(api.ODVersion.fromString("opendiscord:version","v4.1.3"),async () => {},async () => {}),
new api.ODVersionMigration(api.ODVersion.fromString("opendiscord:version","v4.1.3"),{}),
//MIGRATE TO v4.2.0
new api.ODVersionMigration(api.ODVersion.fromString("opendiscord:version","v4.2.0"),{
beforeStartupMigrate:async () => {
const isDevconfig = (process.argv.includes("--dev-config") || process.argv.includes("-dc"))
//transfer config files to .jsonc
const configDir = path.join(process.cwd(),(isDevconfig) ? "./devconfig/" : "./config/")
for (const file of fs.readdirSync(configDir).filter((f) => f.endsWith(".json"))){
try{
fs.copyFileSync(path.join(configDir,file),path.join(configDir,file.replace(".json",".jsonc")))
fs.rmSync(path.join(configDir,file))
}catch(err){
process.emit("uncaughtException",err)
}
}
},
afterStartupMigrate:async () => {
//migrate config
const generalConfig = opendiscord.configs.get("opendiscord:general")
const questionConfig = opendiscord.configs.get("opendiscord:questions")
const optionConfig = opendiscord.configs.get("opendiscord:options")
const panelConfig = opendiscord.configs.get("opendiscord:panels")
const transcriptConfig = opendiscord.configs.get("opendiscord:transcripts")
if (!generalConfig.data.ticketSystem){
//only migrate config when it hasn't been done manually by the user.
if (!generalConfig.data["_INFO"]) throw new api.ODSystemError("Couldn't find general.jsonc '_INFO' category.")
delete generalConfig.data["_INFO"]
generalConfig.data._CONFIG_VERSION = "open-ticket-v4.2.0"
if (!generalConfig.data["system"]) throw new api.ODSystemError("Couldn't find general.jsonc 'system' category.")
generalConfig.data.ticketSystem = generalConfig.data["system"]
generalConfig.data.ticketSystem.closeEmoji = "🔒"
generalConfig.data.ticketSystem.askPriorityOnTicketCreation = true
generalConfig.data.ticketSystem.enableCreateTicketForOtherUser = true
delete generalConfig.data["system"]
generalConfig.data.logs = generalConfig.data.ticketSystem["logs"]
delete generalConfig.data.ticketSystem["logs"]
generalConfig.data.logs.logMessages = generalConfig.data.ticketSystem["messages"]
delete generalConfig.data.ticketSystem["messages"]
generalConfig.data.permissions = generalConfig.data.ticketSystem["permissions"]
delete generalConfig.data.ticketSystem["permissions"]
generalConfig.data.permissions.transcripts = "admin"
//closed category
const closedCategory = {enabled:false,categoryId:"DISCORD_CATEGORY_ID"}
for (const option of optionConfig.data){
if (option.type != "ticket") continue
if (option.channel["closedCategory"] && /^\d+$/.test(option.channel["closedCategory"])){
closedCategory.enabled = true
closedCategory.categoryId = option.channel["closedCategory"]
}
}
generalConfig.data.ticketSystem.closedCategory = closedCategory
//backup category
const backupCategory = {enabled:false,categoryId:"DISCORD_CATEGORY_ID"}
for (const option of optionConfig.data){
if (option.type != "ticket") continue
if (option.channel["backupCategory"] && /^\d+$/.test(option.channel["backupCategory"])){
backupCategory.enabled = true
backupCategory.categoryId = option.channel["backupCategory"]
}
}
generalConfig.data.ticketSystem.backupCategory = backupCategory
//claimed categories
const claimedCategories: {user:string,category:string}[] = []
for (const option of optionConfig.data){
if (option.type != "ticket" || !Array.isArray(option.channel["claimedCategory"])) continue
for (const {user,category} of option.channel["claimedCategory"]){
if (typeof user == "string" && typeof category == "string" && /^\d+$/.test(user) && /^\d+$/.test(category) && !claimedCategories.find((c) => c.user == user)){
claimedCategories.push({user,category})
}
}
}
generalConfig.data.ticketSystem.claimedCategories = claimedCategories
//delete properties from options.jsonc
for (const option of optionConfig.data){
if (option.type != "ticket") continue
delete option.channel["closedCategory"]
delete option.channel["backupCategory"]
delete option.channel["claimedCategory"]
}
//update panels config:
for (const panel of panelConfig.data){
panel.settings.maximumButtonsPerRow = 5
}
//update questions config:
for (const question of questionConfig.data){
if (question.type !== "paragraph" && question.type !== "short") continue
question.description = ""
}
//add new sub-panel option example (for users to try)
optionConfig.data.push({
id:"example-sub-panel",
name:"Example Sub-Panel",
description:"This is an example of how to implement a sub-panel in Open Ticket.",
type:"sub-panel",
button:{
color:"gray",
label:"Sub-Panel Example",
emoji:"📋"
},
subPanelId:panelConfig.data[0]?.id ?? "example-panel"
})
//add new question examples (for users to try)
questionConfig.data.push(
{
id:"example-dropdown-question",
name:"Example Dropdown Question",
description:"This is a dropdown question.",
type:"dropdown",
required:false,
placeholder:"Choose your answer...",
choices:[
{title:"Choice A",description:"Apple",emoji:"🍎"},
{title:"Choice B",description:"Banana",emoji:"🍌"},
{title:"Choice C",description:"Orange",emoji:"🍊"},
{title:"Choice D",description:"Kiwi",emoji:"🥝"}
]
},
{
id:"example-radio-question",
name:"Example Radio Question",
description:"This is a radio select question.",
type:"radio-select",
required:true,
choices:[
{title:"Choice A",description:"Up",selectedByDefault:false},
{title:"Choice B",description:"Down",selectedByDefault:false},
{title:"Choice C",description:"Left",selectedByDefault:false},
{title:"Choice D",description:"Right",selectedByDefault:false}
]
},
{
id:"example-checkbox-question",
name:"Example Checkbox Question",
description:"This is a checkbox select question.",
type:"checkbox-select",
required:true,
limits:{
enabled:false,
min:0,
max:10
},
choices:[
{title:"Choice A",description:"Happiness",selectedByDefault:false},
{title:"Choice B",description:"Anger",selectedByDefault:false},
{title:"Choice C",description:"Sadness",selectedByDefault:false},
{title:"Choice D",description:"Fear",selectedByDefault:false}
]
},
{
id:"example-text-display-question",
type:"text-display",
textContents:"This is a text display. It isn't a question, but allows you to display a text, explaination or details."
}
)
await generalConfig.save()
await questionConfig.save()
await optionConfig.save()
await panelConfig.save()
await transcriptConfig.save()
}
//migrate database
const optionDatabase = opendiscord.databases.get("opendiscord:options")
const ticketDatabase = opendiscord.databases.get("opendiscord:tickets")
for (const option of (await optionDatabase.getCategory("opendiscord:used-option") ?? [])){
const optionData = option.value
optionData.data = optionData.data.filter((data) => (
data.id !== "opendiscord:channel-category-closed" &&
data.id !== "pendiscord:channel-category-backup" &&
data.id !== "opendiscord:channel-categories-claimed"
))
optionDatabase.set("opendiscord:used-option",option.key,optionData)
}
for (const ticket of (await ticketDatabase.getCategory("opendiscord:ticket") ?? [])){
const ticketData = ticket.value
if (!ticketData.data.find((d) => d.id == "opendiscord:channel-renamed")) ticketData.data.push({id:"opendiscord:channel-renamed",value:null})
ticketDatabase.set("opendiscord:ticket",ticket.key,ticketData)
}
}
}),
]