Compare commits
21 commits
Author | SHA1 | Date | |
---|---|---|---|
c9ba8b4952 | |||
5aec1b7e7b | |||
66d67257be | |||
c6579beba2 | |||
dac221246c | |||
12337101b7 | |||
e3984ab968 | |||
0304744d9d | |||
2b73548b7a | |||
a9c28a04b6 | |||
f5d044512a | |||
0ee12cb1ec | |||
586f9c8f07 | |||
c6c7379f20 | |||
8e7a21c81a | |||
ad940c6167 | |||
a88997e8e5 | |||
384f046915 | |||
bb9ef65e42 | |||
c12c17de41 | |||
bfd3196374 |
39 changed files with 334 additions and 610 deletions
|
@ -74,22 +74,6 @@ public class AddCategoryCommand
|
||||||
}, true);
|
}, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
try
|
await LogChannel.Success(command.User.Mention + " added `" + category.Name + "` as `" + title + "` to the category list.");
|
||||||
{
|
|
||||||
DiscordChannel logChannel = await SupportChild.client.GetChannelAsync(Config.logChannel);
|
|
||||||
await logChannel.SendMessageAsync(new DiscordEmbedBuilder
|
|
||||||
{
|
|
||||||
Color = DiscordColor.Green,
|
|
||||||
Description = command.User.Mention + " added `" + category.Name + "` to the category list.",
|
|
||||||
Footer = new DiscordEmbedBuilder.EmbedFooter
|
|
||||||
{
|
|
||||||
Text = "Category: " + title
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
catch (NotFoundException)
|
|
||||||
{
|
|
||||||
Logger.Error("Could not send message in log channel.");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -62,24 +62,7 @@ public class AddCommand
|
||||||
Description = "Added " + member.Mention + " to ticket."
|
Description = "Added " + member.Mention + " to ticket."
|
||||||
});
|
});
|
||||||
|
|
||||||
try
|
await LogChannel.Success(member.Mention + " was added to " + command.Channel.Mention + " by " + command.User.Mention + ".", ticket.id);
|
||||||
{
|
|
||||||
DiscordChannel logChannel = await SupportChild.client.GetChannelAsync(Config.logChannel);
|
|
||||||
await logChannel.SendMessageAsync(new DiscordEmbedBuilder
|
|
||||||
{
|
|
||||||
Color = DiscordColor.Green,
|
|
||||||
Description = member.Mention + " was added to " + command.Channel.Mention +
|
|
||||||
" by " + command.User.Mention + ".",
|
|
||||||
Footer = new DiscordEmbedBuilder.EmbedFooter
|
|
||||||
{
|
|
||||||
Text = "Ticket: " + ticket.id.ToString("00000")
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
catch (NotFoundException)
|
|
||||||
{
|
|
||||||
Logger.Error("Could not send message in log channel.");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
catch (Exception)
|
catch (Exception)
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
|
using System.Globalization;
|
||||||
using DSharpPlus.Entities;
|
using DSharpPlus.Entities;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using DSharpPlus.Commands;
|
using DSharpPlus.Commands;
|
||||||
|
@ -27,7 +28,7 @@ public class AddMessageCommand
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Database.TryGetMessage(identifier.ToLower(), out Database.Message _))
|
if (Database.TryGetMessage(identifier.ToLower(CultureInfo.InvariantCulture), out Database.Message _))
|
||||||
{
|
{
|
||||||
await command.RespondAsync(new DiscordEmbedBuilder
|
await command.RespondAsync(new DiscordEmbedBuilder
|
||||||
{
|
{
|
||||||
|
@ -54,22 +55,6 @@ public class AddMessageCommand
|
||||||
}, true);
|
}, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
try
|
await LogChannel.Success(command.User.Mention + " added or updated `" + identifier + "` in the /say command.\n\nContent:\n\n" + message);
|
||||||
{
|
|
||||||
DiscordChannel logChannel = await SupportChild.client.GetChannelAsync(Config.logChannel);
|
|
||||||
await logChannel.SendMessageAsync(new DiscordEmbedBuilder
|
|
||||||
{
|
|
||||||
Color = DiscordColor.Green,
|
|
||||||
Description = command.User.Mention + " added or updated `" + identifier + "` in the /say command.\n\nContent:\n\n" + message,
|
|
||||||
Footer = new DiscordEmbedBuilder.EmbedFooter
|
|
||||||
{
|
|
||||||
Text = "Identifier: " + identifier
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
catch (NotFoundException)
|
|
||||||
{
|
|
||||||
Logger.Error("Could not send message in log channel.");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -43,8 +43,10 @@ public class AddStaffCommand
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool alreadyStaff = Database.IsStaff(staffMember.Id);
|
||||||
|
|
||||||
await using MySqlConnection c = Database.GetConnection();
|
await using MySqlConnection c = Database.GetConnection();
|
||||||
MySqlCommand cmd = Database.IsStaff(staffMember.Id) ? new MySqlCommand(@"UPDATE staff SET name = @name WHERE user_id = @user_id", c) : new MySqlCommand(@"INSERT INTO staff (user_id, name) VALUES (@user_id, @name);", c);
|
MySqlCommand cmd = alreadyStaff ? new MySqlCommand(@"UPDATE staff SET name = @name WHERE user_id = @user_id", c) : new MySqlCommand(@"INSERT INTO staff (user_id, name) VALUES (@user_id, @name);", c);
|
||||||
|
|
||||||
c.Open();
|
c.Open();
|
||||||
cmd.Parameters.AddWithValue("@user_id", staffMember.Id);
|
cmd.Parameters.AddWithValue("@user_id", staffMember.Id);
|
||||||
|
@ -52,25 +54,23 @@ public class AddStaffCommand
|
||||||
cmd.ExecuteNonQuery();
|
cmd.ExecuteNonQuery();
|
||||||
cmd.Dispose();
|
cmd.Dispose();
|
||||||
|
|
||||||
await command.RespondAsync(new DiscordEmbedBuilder
|
if (alreadyStaff)
|
||||||
{
|
{
|
||||||
Color = DiscordColor.Green,
|
await command.RespondAsync(new DiscordEmbedBuilder
|
||||||
Description = staffMember.Mention + " was added to staff."
|
|
||||||
});
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
// Log it if the log channel exists
|
|
||||||
DiscordChannel logChannel = await SupportChild.client.GetChannelAsync(Config.logChannel);
|
|
||||||
await logChannel.SendMessageAsync(new DiscordEmbedBuilder
|
|
||||||
{
|
{
|
||||||
Color = DiscordColor.Green,
|
Color = DiscordColor.Green,
|
||||||
Description = staffMember.Mention + " was added to staff by " + command.User.Mention + "."
|
Description = staffMember.Mention + " is already a staff member, refreshed username in database."
|
||||||
});
|
}, true);
|
||||||
}
|
}
|
||||||
catch (NotFoundException)
|
else
|
||||||
{
|
{
|
||||||
Logger.Error("Could not send message in log channel.");
|
await command.RespondAsync(new DiscordEmbedBuilder
|
||||||
|
{
|
||||||
|
Color = DiscordColor.Green,
|
||||||
|
Description = staffMember.Mention + " was added to staff."
|
||||||
|
}, true);
|
||||||
|
|
||||||
|
await LogChannel.Success(staffMember.Mention + " was added to staff by " + command.User.Mention + ".");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -18,7 +18,7 @@ public class AdminCommands
|
||||||
[Command("setticket")]
|
[Command("setticket")]
|
||||||
[Description("Turns a channel into a ticket. WARNING: Anyone will be able to delete the channel using /close.")]
|
[Description("Turns a channel into a ticket. WARNING: Anyone will be able to delete the channel using /close.")]
|
||||||
public async Task SetTicket(SlashCommandContext command,
|
public async Task SetTicket(SlashCommandContext command,
|
||||||
[Parameter("user")] [Description("(Optional) The owner of the ticket.")] DiscordUser user = null)
|
[Parameter("user")][Description("(Optional) The owner of the ticket.")] DiscordUser user = null)
|
||||||
{
|
{
|
||||||
// Check if ticket exists in the database
|
// Check if ticket exists in the database
|
||||||
if (Database.IsOpenTicket(command.Channel.Id))
|
if (Database.IsOpenTicket(command.Channel.Id))
|
||||||
|
@ -40,31 +40,14 @@ public class AdminCommands
|
||||||
Description = "Channel has been designated ticket " + id.ToString("00000") + "."
|
Description = "Channel has been designated ticket " + id.ToString("00000") + "."
|
||||||
});
|
});
|
||||||
|
|
||||||
try
|
await LogChannel.Success(command.Channel.Mention + " has been designated ticket " + id.ToString("00000") + " by " + command.Member?.Mention + ".", (uint)id);
|
||||||
{
|
|
||||||
// Log it if the log channel exists
|
|
||||||
DiscordChannel logChannel = await SupportChild.client.GetChannelAsync(Config.logChannel);
|
|
||||||
await logChannel.SendMessageAsync(new DiscordEmbedBuilder
|
|
||||||
{
|
|
||||||
Color = DiscordColor.Green,
|
|
||||||
Description = command.Channel.Mention + " has been designated ticket " + id.ToString("00000") + " by " + command.Member?.Mention + ".",
|
|
||||||
Footer = new DiscordEmbedBuilder.EmbedFooter
|
|
||||||
{
|
|
||||||
Text = "Ticket: " + id.ToString("00000")
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
catch (NotFoundException)
|
|
||||||
{
|
|
||||||
Logger.Error("Could not send message in log channel.");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[RequireGuild]
|
[RequireGuild]
|
||||||
[Command("unsetticket")]
|
[Command("unsetticket")]
|
||||||
[Description("Deletes a ticket from the ticket system without deleting the channel.")]
|
[Description("Deletes a ticket from the ticket system without deleting the channel.")]
|
||||||
public async Task UnsetTicket(SlashCommandContext command,
|
public async Task UnsetTicket(SlashCommandContext command,
|
||||||
[Parameter("ticket-id")] [Description("(Optional) Ticket to unset. Uses the channel you are in by default. Use ticket ID, not channel ID!")] long ticketID = 0)
|
[Parameter("ticket-id")][Description("(Optional) Ticket to unset. Uses the channel you are in by default. Use ticket ID, not channel ID!")] long ticketID = 0)
|
||||||
{
|
{
|
||||||
Database.Ticket ticket;
|
Database.Ticket ticket;
|
||||||
DiscordChannel channel = null;
|
DiscordChannel channel = null;
|
||||||
|
@ -121,24 +104,7 @@ public class AdminCommands
|
||||||
Description = "Channel has been undesignated as a ticket."
|
Description = "Channel has been undesignated as a ticket."
|
||||||
});
|
});
|
||||||
|
|
||||||
try
|
await LogChannel.Success(command.Channel.Mention + " has been undesignated as a ticket by " + command.User.Mention + ".", ticket.id);
|
||||||
{
|
|
||||||
// Log it if the log channel exists
|
|
||||||
DiscordChannel logChannel = await SupportChild.client.GetChannelAsync(Config.logChannel);
|
|
||||||
await logChannel.SendMessageAsync(new DiscordEmbedBuilder
|
|
||||||
{
|
|
||||||
Color = DiscordColor.Green,
|
|
||||||
Description = command.Channel.Mention + " has been undesignated as a ticket by " + command.User.Mention + ".",
|
|
||||||
Footer = new DiscordEmbedBuilder.EmbedFooter
|
|
||||||
{
|
|
||||||
Text = "Ticket: " + ticket.id.ToString("00000")
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
catch (NotFoundException)
|
|
||||||
{
|
|
||||||
Logger.Error("Could not send message in log channel.");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -159,21 +125,9 @@ public class AdminCommands
|
||||||
{
|
{
|
||||||
Color = DiscordColor.Green,
|
Color = DiscordColor.Green,
|
||||||
Description = "Reloading bot application..."
|
Description = "Reloading bot application..."
|
||||||
});
|
}, true);
|
||||||
|
|
||||||
try
|
await LogChannel.Success(command.Channel.Mention + " reloaded the bot.");
|
||||||
{
|
|
||||||
DiscordChannel logChannel = await SupportChild.client.GetChannelAsync(Config.logChannel);
|
|
||||||
await logChannel.SendMessageAsync(new DiscordEmbedBuilder
|
|
||||||
{
|
|
||||||
Color = DiscordColor.Green,
|
|
||||||
Description = command.Channel.Mention + " reloaded the bot.",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
catch (NotFoundException)
|
|
||||||
{
|
|
||||||
Logger.Error("Could not send message in log channel.");
|
|
||||||
}
|
|
||||||
|
|
||||||
Logger.Log("Reloading bot...");
|
Logger.Log("Reloading bot...");
|
||||||
await SupportChild.Reload();
|
await SupportChild.Reload();
|
||||||
|
|
|
@ -89,26 +89,9 @@ public class AssignCommand
|
||||||
Description = "You have been assigned to a support ticket: " + command.Channel.Mention
|
Description = "You have been assigned to a support ticket: " + command.Channel.Mention
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
catch (UnauthorizedException) { }
|
catch (UnauthorizedException) { /* ignore */ }
|
||||||
}
|
}
|
||||||
|
|
||||||
try
|
await LogChannel.Success(member.Mention + " was assigned to " + command.Channel.Mention + " by " + command.User.Mention + ".", ticket.id);
|
||||||
{
|
|
||||||
// Log it if the log channel exists
|
|
||||||
DiscordChannel logChannel = await SupportChild.client.GetChannelAsync(Config.logChannel);
|
|
||||||
await logChannel.SendMessageAsync(new DiscordEmbedBuilder
|
|
||||||
{
|
|
||||||
Color = DiscordColor.Green,
|
|
||||||
Description = member.Mention + " was assigned to " + command.Channel.Mention + " by " + command.User.Mention + ".",
|
|
||||||
Footer = new DiscordEmbedBuilder.EmbedFooter
|
|
||||||
{
|
|
||||||
Text = "Ticket: " + ticket.id.ToString("00000")
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
catch (NotFoundException)
|
|
||||||
{
|
|
||||||
Logger.Error("Could not send message in log channel.");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -35,20 +35,7 @@ public class BlacklistCommand
|
||||||
Description = "Blocked " + user.Mention + " from opening new tickets."
|
Description = "Blocked " + user.Mention + " from opening new tickets."
|
||||||
}, true);
|
}, true);
|
||||||
|
|
||||||
try
|
await LogChannel.Success(user.Mention + " was blocked from opening tickets by " + command.User.Mention + ".");
|
||||||
{
|
|
||||||
// Log it if the log channel exists
|
|
||||||
DiscordChannel logChannel = await SupportChild.client.GetChannelAsync(Config.logChannel);
|
|
||||||
await logChannel.SendMessageAsync(new DiscordEmbedBuilder
|
|
||||||
{
|
|
||||||
Color = DiscordColor.Green,
|
|
||||||
Description = user.Mention + " was blocked from opening tickets by " + command.User.Mention + "."
|
|
||||||
});
|
|
||||||
}
|
|
||||||
catch (NotFoundException)
|
|
||||||
{
|
|
||||||
Logger.Error("Could not send message in log channel.");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
catch (Exception)
|
catch (Exception)
|
||||||
{
|
{
|
||||||
|
|
|
@ -141,28 +141,12 @@ public class CloseCommand
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// Log it if the log channel exists
|
|
||||||
DiscordChannel logChannel = await SupportChild.client.GetChannelAsync(Config.logChannel);
|
|
||||||
|
|
||||||
await using FileStream file = new FileStream(filePath, FileMode.Open, FileAccess.Read);
|
await using FileStream file = new FileStream(filePath, FileMode.Open, FileAccess.Read);
|
||||||
DiscordMessageBuilder message = new DiscordMessageBuilder();
|
await LogChannel.Success("Ticket " + ticket.id.ToString("00000") + " closed by " + interaction.User.Mention + ".\n" + closeReason, ticket.id, new Utilities.File(fileName, file));
|
||||||
message.AddEmbed(new DiscordEmbedBuilder
|
|
||||||
{
|
|
||||||
Color = DiscordColor.Green,
|
|
||||||
Description = "Ticket " + ticket.id.ToString("00000") + " closed by " +
|
|
||||||
interaction.User.Mention + ".\n" + closeReason,
|
|
||||||
Footer = new DiscordEmbedBuilder.EmbedFooter
|
|
||||||
{
|
|
||||||
Text = "Ticket: " + ticket.id.ToString("00000")
|
|
||||||
}
|
|
||||||
});
|
|
||||||
message.AddFiles(new Dictionary<string, Stream> { { fileName, file } });
|
|
||||||
|
|
||||||
await logChannel.SendMessageAsync(message);
|
|
||||||
}
|
}
|
||||||
catch (NotFoundException)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
Logger.Error("Could not send message in log channel.");
|
Logger.Error("Error occurred sending transcript log message. ", e);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Config.closingNotifications)
|
if (Config.closingNotifications)
|
||||||
|
@ -200,13 +184,12 @@ public class CloseCommand
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
message.AddFiles(new Dictionary<string, Stream> { { fileName, file } });
|
message.AddFiles(new Dictionary<string, Stream> { { fileName, file } });
|
||||||
|
|
||||||
await staffMember.SendMessageAsync(message);
|
await staffMember.SendMessageAsync(message);
|
||||||
}
|
}
|
||||||
catch (NotFoundException) { }
|
catch (NotFoundException) { /* ignore */ }
|
||||||
catch (UnauthorizedException) { }
|
catch (UnauthorizedException) { /* ignore */ }
|
||||||
}
|
}
|
||||||
|
|
||||||
Database.ArchiveTicket(ticket);
|
Database.ArchiveTicket(ticket);
|
||||||
|
|
|
@ -51,18 +51,6 @@ public class CreateButtonPanelCommand
|
||||||
Description = "Successfully created message, make sure to run this command again if you add new categories to the bot."
|
Description = "Successfully created message, make sure to run this command again if you add new categories to the bot."
|
||||||
}, true);
|
}, true);
|
||||||
|
|
||||||
try
|
await LogChannel.Success(command.User.Mention + " created a new button panel in " + command.Channel.Mention + ".");
|
||||||
{
|
|
||||||
DiscordChannel logChannel = await SupportChild.client.GetChannelAsync(Config.logChannel);
|
|
||||||
await logChannel.SendMessageAsync(new DiscordEmbedBuilder
|
|
||||||
{
|
|
||||||
Color = DiscordColor.Green,
|
|
||||||
Description = command.User.Mention + " created a new button panel in " + command.Channel.Mention + "."
|
|
||||||
});
|
|
||||||
}
|
|
||||||
catch (NotFoundException)
|
|
||||||
{
|
|
||||||
Logger.Error("Could not send message in log channel.");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -16,7 +16,7 @@ public class CreateSelectionBoxPanelCommand
|
||||||
[Command("createselectionboxpanel")]
|
[Command("createselectionboxpanel")]
|
||||||
[Description("Creates a selection box which users can use to open new tickets in specific categories.")]
|
[Description("Creates a selection box which users can use to open new tickets in specific categories.")]
|
||||||
public async Task OnExecute(SlashCommandContext command,
|
public async Task OnExecute(SlashCommandContext command,
|
||||||
[Parameter("message")][Description("(Optional) The message to show in the selection box.")] string message = null)
|
[Parameter("placeholder")][Description("(Optional) The message to show in the selection box.")] string message = null)
|
||||||
{
|
{
|
||||||
DiscordMessageBuilder builder = new DiscordMessageBuilder()
|
DiscordMessageBuilder builder = new DiscordMessageBuilder()
|
||||||
.WithContent(" ")
|
.WithContent(" ")
|
||||||
|
@ -29,19 +29,7 @@ public class CreateSelectionBoxPanelCommand
|
||||||
Description = "Successfully created message, make sure to run this command again if you add new categories to the bot."
|
Description = "Successfully created message, make sure to run this command again if you add new categories to the bot."
|
||||||
}, true);
|
}, true);
|
||||||
|
|
||||||
try
|
await LogChannel.Success(command.User.Mention + " created a new selector panel in " + command.Channel.Mention + ".");
|
||||||
{
|
|
||||||
DiscordChannel logChannel = await SupportChild.client.GetChannelAsync(Config.logChannel);
|
|
||||||
await logChannel.SendMessageAsync(new DiscordEmbedBuilder
|
|
||||||
{
|
|
||||||
Color = DiscordColor.Green,
|
|
||||||
Description = command.User.Mention + " created a new selector panel in " + command.Channel.Mention + "."
|
|
||||||
});
|
|
||||||
}
|
|
||||||
catch (NotFoundException)
|
|
||||||
{
|
|
||||||
Logger.Error("Could not send message in log channel.");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async Task<List<DiscordSelectComponent>> GetSelectComponents(SlashCommandContext command, string placeholder)
|
public static async Task<List<DiscordSelectComponent>> GetSelectComponents(SlashCommandContext command, string placeholder)
|
||||||
|
|
|
@ -45,23 +45,7 @@ public class InterviewCommands
|
||||||
}, true);
|
}, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
try
|
await LogChannel.Success(command.User.Mention + " restarted interview in " + command.Channel.Mention + ".", ticket.id);
|
||||||
{
|
|
||||||
DiscordChannel logChannel = await SupportChild.client.GetChannelAsync(Config.logChannel);
|
|
||||||
await logChannel.SendMessageAsync(new DiscordEmbedBuilder
|
|
||||||
{
|
|
||||||
Color = DiscordColor.Green,
|
|
||||||
Description = command.User.Mention + " restarted interview in " + command.Channel.Mention + ".",
|
|
||||||
Footer = new DiscordEmbedBuilder.EmbedFooter
|
|
||||||
{
|
|
||||||
Text = "Ticket: " + ticket.id.ToString("00000")
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
catch (NotFoundException)
|
|
||||||
{
|
|
||||||
Logger.Error("Could not send message in log channel.");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Command("stop")]
|
[Command("stop")]
|
||||||
|
@ -107,22 +91,6 @@ public class InterviewCommands
|
||||||
}, true);
|
}, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
try
|
await LogChannel.Success(command.User.Mention + " stopped the interview in " + command.Channel.Mention + ".", ticket.id);
|
||||||
{
|
|
||||||
DiscordChannel logChannel = await SupportChild.client.GetChannelAsync(Config.logChannel);
|
|
||||||
await logChannel.SendMessageAsync(new DiscordEmbedBuilder
|
|
||||||
{
|
|
||||||
Color = DiscordColor.Green,
|
|
||||||
Description = command.User.Mention + " stopped the interview in " + command.Channel.Mention + ".",
|
|
||||||
Footer = new DiscordEmbedBuilder.EmbedFooter
|
|
||||||
{
|
|
||||||
Text = "Ticket: " + ticket.id.ToString("00000")
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
catch (NotFoundException)
|
|
||||||
{
|
|
||||||
Logger.Error("Could not send message in log channel.");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -43,7 +43,7 @@ public class InterviewTemplateCommands
|
||||||
{
|
{
|
||||||
string defaultTemplate =
|
string defaultTemplate =
|
||||||
"{\n" +
|
"{\n" +
|
||||||
" \"category-id\": \"" + category.Id + "\",\n" +
|
" \"category-id\": " + category.Id + ",\n" +
|
||||||
" \"interview\":\n" +
|
" \"interview\":\n" +
|
||||||
" {\n" +
|
" {\n" +
|
||||||
" \"message\": \"\",\n" +
|
" \"message\": \"\",\n" +
|
||||||
|
@ -147,7 +147,7 @@ public class InterviewTemplateCommands
|
||||||
await command.RespondAsync(new DiscordEmbedBuilder
|
await command.RespondAsync(new DiscordEmbedBuilder
|
||||||
{
|
{
|
||||||
Color = DiscordColor.Green,
|
Color = DiscordColor.Green,
|
||||||
Description = "Uploaded interview template."
|
Description = "Uploaded interview template for `" + category.Name + "`."
|
||||||
}, true);
|
}, true);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
@ -168,18 +168,12 @@ public class InterviewTemplateCommands
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
MemoryStream memStream = new(Encoding.UTF8.GetBytes(Database.GetInterviewTemplateJSON(template.categoryID)));
|
MemoryStream memStream = new(Encoding.UTF8.GetBytes(Database.GetInterviewTemplateJSON(template.categoryID)));
|
||||||
|
await LogChannel.Success(command.User.Mention + " uploaded a new interview template for the `" + category.Name + "` category.", 0,
|
||||||
// Log it if the log channel exists
|
new Utilities.File("interview-template-" + template.categoryID + ".json", memStream));
|
||||||
DiscordChannel logChannel = await SupportChild.client.GetChannelAsync(Config.logChannel);
|
|
||||||
await logChannel.SendMessageAsync(new DiscordMessageBuilder().AddEmbed(new DiscordEmbedBuilder
|
|
||||||
{
|
|
||||||
Color = DiscordColor.Green,
|
|
||||||
Description = command.User.Mention + " uploaded a new interview template for the `" + category.Name + "` category."
|
|
||||||
}).AddFile("interview-template-" + template.categoryID + ".json", memStream));
|
|
||||||
}
|
}
|
||||||
catch (NotFoundException)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
Logger.Error("Could not send message in log channel.");
|
Logger.Error("Unable to log interview template upload.", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
|
@ -241,19 +235,7 @@ public class InterviewTemplateCommands
|
||||||
Description = "Deleted interview template."
|
Description = "Deleted interview template."
|
||||||
}, true);
|
}, true);
|
||||||
|
|
||||||
try
|
await LogChannel.Success(command.User.Mention + " deleted the interview template for the `" + category.Name + "` category.", 0,
|
||||||
{
|
new Utilities.File("interview-template-" + category.Id + ".json", memStream));
|
||||||
// Log it if the log channel exists
|
|
||||||
DiscordChannel logChannel = await SupportChild.client.GetChannelAsync(Config.logChannel);
|
|
||||||
await logChannel.SendMessageAsync(new DiscordMessageBuilder().AddEmbed(new DiscordEmbedBuilder
|
|
||||||
{
|
|
||||||
Color = DiscordColor.Green,
|
|
||||||
Description = command.User.Mention + " deleted the interview template for the `" + category.Name + "` category."
|
|
||||||
}).AddFile("interview-template-" + category.Id + ".json", memStream));
|
|
||||||
}
|
|
||||||
catch (NotFoundException)
|
|
||||||
{
|
|
||||||
Logger.Error("Could not send message in log channel.");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -16,7 +16,7 @@ public class ListCommand
|
||||||
[Command("list")]
|
[Command("list")]
|
||||||
[Description("Lists tickets opened by a user.")]
|
[Description("Lists tickets opened by a user.")]
|
||||||
public async Task OnExecute(SlashCommandContext command,
|
public async Task OnExecute(SlashCommandContext command,
|
||||||
[Parameter("user")][Description("(Optional) The user to get tickets by, yourself by default.")] DiscordUser user = null)
|
[Parameter("user")][Description("(Optional) The user whose tickets to get, yourself by default.")] DiscordUser user = null)
|
||||||
{
|
{
|
||||||
DiscordUser listUser = user == null ? command.User : user;
|
DiscordUser listUser = user == null ? command.User : user;
|
||||||
|
|
||||||
|
|
|
@ -105,22 +105,6 @@ public class MoveCommand
|
||||||
Description = "Ticket was moved to `" + categoryChannel.Name + "`."
|
Description = "Ticket was moved to `" + categoryChannel.Name + "`."
|
||||||
});
|
});
|
||||||
|
|
||||||
try
|
await LogChannel.Success(command.User.Mention + " moved " + command.Channel.Mention + " to `" + categoryChannel.Name + "`.", ticket.id);
|
||||||
{
|
|
||||||
DiscordChannel logChannel = await SupportChild.client.GetChannelAsync(Config.logChannel);
|
|
||||||
await logChannel.SendMessageAsync(new DiscordEmbedBuilder
|
|
||||||
{
|
|
||||||
Color = DiscordColor.Green,
|
|
||||||
Description = command.User.Mention + " moved " + command.Channel.Mention + " to `" + categoryChannel.Name + "`.",
|
|
||||||
Footer = new DiscordEmbedBuilder.EmbedFooter
|
|
||||||
{
|
|
||||||
Text = "Ticket: " + ticket.id.ToString("00000")
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
catch (NotFoundException)
|
|
||||||
{
|
|
||||||
Logger.Error("Could not send message in log channel.");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -11,7 +11,6 @@ using DSharpPlus.Exceptions;
|
||||||
using SupportChild.Interviews;
|
using SupportChild.Interviews;
|
||||||
|
|
||||||
namespace SupportChild.Commands;
|
namespace SupportChild.Commands;
|
||||||
|
|
||||||
public class NewCommand
|
public class NewCommand
|
||||||
{
|
{
|
||||||
[RequireGuild]
|
[RequireGuild]
|
||||||
|
@ -134,6 +133,11 @@ public class NewCommand
|
||||||
return (false, "Error: Could not find the category to place the ticket in.");
|
return (false, "Error: Could not find the category to place the ticket in.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (category.Children.Count == 50)
|
||||||
|
{
|
||||||
|
return (false, "This ticket category is full, can not create ticket channel.");
|
||||||
|
}
|
||||||
|
|
||||||
DiscordMember member = null;
|
DiscordMember member = null;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
@ -152,7 +156,10 @@ public class NewCommand
|
||||||
{
|
{
|
||||||
ticketChannel = await category.Guild.CreateChannelAsync("ticket", DiscordChannelType.Text, category);
|
ticketChannel = await category.Guild.CreateChannelAsync("ticket", DiscordChannelType.Text, category);
|
||||||
}
|
}
|
||||||
catch (Exception) { /* ignored */ }
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Logger.Error("Error occured while creating ticket.", e);
|
||||||
|
}
|
||||||
|
|
||||||
if (ticketChannel == null)
|
if (ticketChannel == null)
|
||||||
{
|
{
|
||||||
|
@ -160,13 +167,13 @@ public class NewCommand
|
||||||
"!\nIs the channel limit reached in the server or ticket category?");
|
"!\nIs the channel limit reached in the server or ticket category?");
|
||||||
}
|
}
|
||||||
|
|
||||||
ulong staffID = 0;
|
DiscordMember assignedStaff = null;
|
||||||
if (Config.randomAssignment)
|
if (Config.randomAssignment)
|
||||||
{
|
{
|
||||||
staffID = Database.GetRandomActiveStaff(0)?.userID ?? 0;
|
assignedStaff = await RandomAssignCommand.GetRandomVerifiedStaffMember(ticketChannel, userID, 0, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
long id = Database.NewTicket(member.Id, staffID, ticketChannel.Id);
|
long id = Database.NewTicket(member.Id, assignedStaff?.Id ?? 0, ticketChannel.Id);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await ticketChannel.ModifyAsync(modifiedAttributes => modifiedAttributes.Name = "ticket-" + id.ToString("00000"));
|
await ticketChannel.ModifyAsync(modifiedAttributes => modifiedAttributes.Name = "ticket-" + id.ToString("00000"));
|
||||||
|
@ -174,7 +181,7 @@ public class NewCommand
|
||||||
catch (DiscordException e)
|
catch (DiscordException e)
|
||||||
{
|
{
|
||||||
Logger.Error("Exception occurred trying to modify channel: " + e);
|
Logger.Error("Exception occurred trying to modify channel: " + e);
|
||||||
Logger.Error("JsomMessage: " + e.JsonMessage);
|
Logger.Error("JsonMessage: " + e.JsonMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
|
@ -184,7 +191,7 @@ public class NewCommand
|
||||||
catch (DiscordException e)
|
catch (DiscordException e)
|
||||||
{
|
{
|
||||||
Logger.Error("Exception occurred trying to add channel permissions: " + e);
|
Logger.Error("Exception occurred trying to add channel permissions: " + e);
|
||||||
Logger.Error("JsomMessage: " + e.JsonMessage);
|
Logger.Error("JsonMessage: " + e.JsonMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
DiscordMessage message = await ticketChannel.SendMessageAsync("Hello, " + member.Mention + "!\n" + Config.welcomeMessage);
|
DiscordMessage message = await ticketChannel.SendMessageAsync("Hello, " + member.Mention + "!\n" + Config.welcomeMessage);
|
||||||
|
@ -209,20 +216,19 @@ public class NewCommand
|
||||||
await Interviewer.StartInterview(ticketChannel);
|
await Interviewer.StartInterview(ticketChannel);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (staffID != 0)
|
if (assignedStaff != null)
|
||||||
{
|
{
|
||||||
await ticketChannel.SendMessageAsync(new DiscordEmbedBuilder
|
await ticketChannel.SendMessageAsync(new DiscordEmbedBuilder
|
||||||
{
|
{
|
||||||
Color = DiscordColor.Green,
|
Color = DiscordColor.Green,
|
||||||
Description = "Ticket was randomly assigned to <@" + staffID + ">."
|
Description = "Ticket was randomly assigned to " + assignedStaff.Mention + "."
|
||||||
});
|
});
|
||||||
|
|
||||||
if (Config.assignmentNotifications)
|
if (Config.assignmentNotifications)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
DiscordMember staffMember = await category.Guild.GetMemberAsync(staffID);
|
await assignedStaff.SendMessageAsync(new DiscordEmbedBuilder
|
||||||
await staffMember.SendMessageAsync(new DiscordEmbedBuilder
|
|
||||||
{
|
{
|
||||||
Color = DiscordColor.Green,
|
Color = DiscordColor.Green,
|
||||||
Description = "You have been randomly assigned to a newly opened support ticket: " +
|
Description = "You have been randomly assigned to a newly opened support ticket: " +
|
||||||
|
@ -232,30 +238,12 @@ public class NewCommand
|
||||||
catch (DiscordException e)
|
catch (DiscordException e)
|
||||||
{
|
{
|
||||||
Logger.Error("Exception occurred assign random staff member: " + e);
|
Logger.Error("Exception occurred assign random staff member: " + e);
|
||||||
Logger.Error("JsomMessage: " + e.JsonMessage);
|
Logger.Error("JsonMessage: " + e.JsonMessage);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
try
|
await LogChannel.Success("Ticket " + ticketChannel.Mention + " opened by " + member.Mention + ".", (uint)id);
|
||||||
{
|
|
||||||
// Log it if the log channel exists
|
|
||||||
DiscordChannel logChannel = await SupportChild.client.GetChannelAsync(Config.logChannel);
|
|
||||||
await logChannel.SendMessageAsync(new DiscordEmbedBuilder
|
|
||||||
{
|
|
||||||
Color = DiscordColor.Green,
|
|
||||||
Description = "Ticket " + ticketChannel.Mention + " opened by " + member.Mention + ".",
|
|
||||||
Footer = new DiscordEmbedBuilder.EmbedFooter
|
|
||||||
{
|
|
||||||
Text = "Ticket: " + id.ToString("00000")
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
catch (NotFoundException)
|
|
||||||
{
|
|
||||||
Logger.Error("Could not send message in log channel.");
|
|
||||||
}
|
|
||||||
|
|
||||||
return (true, "Ticket opened, " + member.Mention + "!\n" + ticketChannel.Mention);
|
return (true, "Ticket opened, " + member.Mention + "!\n" + ticketChannel.Mention);
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -31,9 +31,14 @@ public class RandomAssignCommand
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get a random staff member who is verified to have the correct role if applicable
|
// Get a random staff member who is verified to have the correct role if applicable
|
||||||
DiscordMember staffMember = await GetRandomVerifiedStaffMember(command, role, ticket);
|
DiscordMember staffMember = await GetRandomVerifiedStaffMember(command.Channel, ticket.creatorID, ticket.assignedStaffID, role);
|
||||||
if (staffMember == null)
|
if (staffMember == null)
|
||||||
{
|
{
|
||||||
|
await command.RespondAsync(new DiscordEmbedBuilder
|
||||||
|
{
|
||||||
|
Color = DiscordColor.Red,
|
||||||
|
Description = "Error: Could not find an applicable staff member with access to this channel."
|
||||||
|
}, true);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -66,85 +71,56 @@ public class RandomAssignCommand
|
||||||
Description = "You have been randomly assigned to a support ticket: " + command.Channel.Mention
|
Description = "You have been randomly assigned to a support ticket: " + command.Channel.Mention
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
catch (UnauthorizedException) { }
|
catch (UnauthorizedException) { /* ignore */ }
|
||||||
}
|
}
|
||||||
|
|
||||||
try
|
await LogChannel.Success(staffMember.Mention + " was randomly assigned to " + command.Channel.Mention + " by " + command.User.Mention + ".", ticket.id);
|
||||||
{
|
|
||||||
// Log it if the log channel exists
|
|
||||||
DiscordChannel logChannel = await SupportChild.client.GetChannelAsync(Config.logChannel);
|
|
||||||
await logChannel.SendMessageAsync(new DiscordEmbedBuilder
|
|
||||||
{
|
|
||||||
Color = DiscordColor.Green,
|
|
||||||
Description = staffMember.Mention + " was randomly assigned to " + command.Channel.Mention + " by " + command.User.Mention + ".",
|
|
||||||
Footer = new DiscordEmbedBuilder.EmbedFooter
|
|
||||||
{
|
|
||||||
Text = "Ticket: " + ticket.id.ToString("00000")
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
catch (NotFoundException)
|
|
||||||
{
|
|
||||||
Logger.Error("Could not send message in log channel.");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static async Task<DiscordMember> GetRandomVerifiedStaffMember(SlashCommandContext command, DiscordRole targetRole, Database.Ticket ticket)
|
internal static async Task<DiscordMember> GetRandomVerifiedStaffMember(DiscordChannel channel, ulong creatorID, ulong currentStaffID, DiscordRole targetRole)
|
||||||
{
|
{
|
||||||
if (targetRole != null) // A role was provided
|
List<Database.StaffMember> staffMembers;
|
||||||
|
ulong[] ignoredUserIDs = [creatorID, currentStaffID];
|
||||||
|
|
||||||
|
if (targetRole == null)
|
||||||
|
{
|
||||||
|
// No role was specified, any active staff will be picked
|
||||||
|
staffMembers = Database.GetActiveStaff(ignoredUserIDs);
|
||||||
|
}
|
||||||
|
else
|
||||||
{
|
{
|
||||||
// Check if role rassign should override staff's active status
|
// Check if role rassign should override staff's active status
|
||||||
List<Database.StaffMember> staffMembers = Config.randomAssignRoleOverride
|
staffMembers = Config.randomAssignRoleOverride
|
||||||
? Database.GetAllStaff(ticket.assignedStaffID, ticket.creatorID)
|
? Database.GetAllStaff(ignoredUserIDs)
|
||||||
: Database.GetActiveStaff(ticket.assignedStaffID, ticket.creatorID);
|
: Database.GetActiveStaff(ignoredUserIDs);
|
||||||
|
}
|
||||||
|
|
||||||
// Randomize the list before checking for roles in order to reduce number of API calls
|
// Randomize the list before checking for roles in order to reduce number of API calls
|
||||||
staffMembers.Shuffle();
|
staffMembers.Shuffle();
|
||||||
|
|
||||||
// Get the first staff member that has the role
|
// Get the first staff member that has the role
|
||||||
foreach (Database.StaffMember sm in staffMembers)
|
foreach (Database.StaffMember staffMember in staffMembers)
|
||||||
|
{
|
||||||
|
try
|
||||||
{
|
{
|
||||||
try
|
DiscordMember verifiedMember = await channel.Guild.GetMemberAsync(staffMember.userID);
|
||||||
|
|
||||||
|
// If a role is set filter to only members with that role
|
||||||
|
if (targetRole == null || verifiedMember.Roles.Any(role => role.Id == targetRole.Id))
|
||||||
{
|
{
|
||||||
DiscordMember verifiedMember = await command.Guild.GetMemberAsync(sm.userID);
|
// Only assign staff members with access to this channel
|
||||||
if (verifiedMember?.Roles?.Any(role => role.Id == targetRole.Id) ?? false)
|
if (verifiedMember.PermissionsIn(channel).HasFlag(DiscordPermissions.AccessChannels))
|
||||||
{
|
{
|
||||||
return verifiedMember;
|
return verifiedMember;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
}
|
||||||
{
|
catch (Exception e)
|
||||||
command.Client.Logger.Log(LogLevel.Information, e, "Error occured trying to find a staff member in the rassign command.");
|
{
|
||||||
}
|
Logger.Error("Error occured trying to find a staff member for random assignment. User ID: " + staffMember.userID, e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else // No role was specified, any active staff will be picked
|
|
||||||
{
|
|
||||||
Database.StaffMember staffEntry = Database.GetRandomActiveStaff(ticket.assignedStaffID, ticket.creatorID);
|
|
||||||
if (staffEntry == null)
|
|
||||||
{
|
|
||||||
await command.RespondAsync(new DiscordEmbedBuilder
|
|
||||||
{
|
|
||||||
Color = DiscordColor.Red,
|
|
||||||
Description = "Error: There are no other staff members to choose from."
|
|
||||||
}, true);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the staff member from discord
|
|
||||||
try
|
|
||||||
{
|
|
||||||
return await command.Guild.GetMemberAsync(staffEntry.userID);
|
|
||||||
}
|
|
||||||
catch (NotFoundException) { }
|
|
||||||
}
|
|
||||||
|
|
||||||
// Send a more generic error if we get to this point and still haven't found the staff member
|
|
||||||
await command.RespondAsync(new DiscordEmbedBuilder
|
|
||||||
{
|
|
||||||
Color = DiscordColor.Red,
|
|
||||||
Description = "Error: Could not find an applicable staff member."
|
|
||||||
}, true);
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -43,19 +43,6 @@ public class RemoveCategoryCommand
|
||||||
}, true);
|
}, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
try
|
await LogChannel.Success("`" + category.Name + "` was removed by " + command.User.Mention + ".");
|
||||||
{
|
|
||||||
// Log it if the log channel exists
|
|
||||||
DiscordChannel logChannel = await SupportChild.client.GetChannelAsync(Config.logChannel);
|
|
||||||
await logChannel.SendMessageAsync(new DiscordEmbedBuilder
|
|
||||||
{
|
|
||||||
Color = DiscordColor.Green,
|
|
||||||
Description = "`" + category.Name + "` was removed by " + command.User.Mention + "."
|
|
||||||
});
|
|
||||||
}
|
|
||||||
catch (NotFoundException)
|
|
||||||
{
|
|
||||||
Logger.Error("Could not send message in log channel.");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,4 +1,5 @@
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
|
using System.Globalization;
|
||||||
using DSharpPlus.Entities;
|
using DSharpPlus.Entities;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using DSharpPlus.Commands;
|
using DSharpPlus.Commands;
|
||||||
|
@ -16,7 +17,7 @@ public class RemoveMessageCommand
|
||||||
public async Task OnExecute(SlashCommandContext command,
|
public async Task OnExecute(SlashCommandContext command,
|
||||||
[Parameter("identifier")][Description("The identifier word used in the /say command.")] string identifier)
|
[Parameter("identifier")][Description("The identifier word used in the /say command.")] string identifier)
|
||||||
{
|
{
|
||||||
if (!Database.TryGetMessage(identifier.ToLower(), out Database.Message _))
|
if (!Database.TryGetMessage(identifier.ToLower(CultureInfo.InvariantCulture), out Database.Message _))
|
||||||
{
|
{
|
||||||
await command.RespondAsync(new DiscordEmbedBuilder
|
await command.RespondAsync(new DiscordEmbedBuilder
|
||||||
{
|
{
|
||||||
|
@ -43,18 +44,6 @@ public class RemoveMessageCommand
|
||||||
}, true);
|
}, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
try
|
await LogChannel.Success("`" + identifier + "` was removed from the /say command by " + command.User.Mention + ".");
|
||||||
{
|
|
||||||
DiscordChannel logChannel = await SupportChild.client.GetChannelAsync(Config.logChannel);
|
|
||||||
await logChannel.SendMessageAsync(new DiscordEmbedBuilder
|
|
||||||
{
|
|
||||||
Color = DiscordColor.Green,
|
|
||||||
Description = "`" + identifier + "` was removed from the /say command by " + command.User.Mention + "."
|
|
||||||
});
|
|
||||||
}
|
|
||||||
catch (NotFoundException)
|
|
||||||
{
|
|
||||||
Logger.Error("Could not send message in log channel.");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,10 +1,11 @@
|
||||||
using System.ComponentModel;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.ComponentModel;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using DSharpPlus.Commands;
|
using DSharpPlus.Commands;
|
||||||
using DSharpPlus.Commands.ContextChecks;
|
using DSharpPlus.Commands.ContextChecks;
|
||||||
using DSharpPlus.Commands.Processors.SlashCommands;
|
using DSharpPlus.Commands.Processors.SlashCommands;
|
||||||
using DSharpPlus.Entities;
|
using DSharpPlus.Entities;
|
||||||
using DSharpPlus.Exceptions;
|
|
||||||
using MySqlConnector;
|
using MySqlConnector;
|
||||||
|
|
||||||
namespace SupportChild.Commands;
|
namespace SupportChild.Commands;
|
||||||
|
@ -27,6 +28,31 @@ public class RemoveStaffCommand
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await command.DeferResponseAsync(true);
|
||||||
|
|
||||||
|
if (Database.TryGetAssignedTickets(user.Id, out List<Database.Ticket> assignedTickets))
|
||||||
|
{
|
||||||
|
foreach (Database.Ticket assignedTicket in assignedTickets)
|
||||||
|
{
|
||||||
|
Database.UnassignStaff(assignedTicket);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
DiscordChannel ticketChannel = await SupportChild.client.GetChannelAsync(assignedTicket.channelID);
|
||||||
|
await ticketChannel.SendMessageAsync(new DiscordEmbedBuilder
|
||||||
|
{
|
||||||
|
Color = DiscordColor.Green,
|
||||||
|
Description = "Unassigned <@" + assignedTicket.assignedStaffID + "> from ticket."
|
||||||
|
});
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Logger.Error("Error when trying to send message about unassigning staff member from ticket-" + assignedTicket.id.ToString("00000"), e);
|
||||||
|
}
|
||||||
|
|
||||||
|
await LogChannel.Success("<@" + assignedTicket.assignedStaffID + "> was unassigned from <#" + assignedTicket.channelID + "> by " + command.User.Mention + ".", assignedTicket.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
await using MySqlConnection c = Database.GetConnection();
|
await using MySqlConnection c = Database.GetConnection();
|
||||||
c.Open();
|
c.Open();
|
||||||
MySqlCommand deletion = new MySqlCommand(@"DELETE FROM staff WHERE user_id=@user_id", c);
|
MySqlCommand deletion = new MySqlCommand(@"DELETE FROM staff WHERE user_id=@user_id", c);
|
||||||
|
@ -40,19 +66,6 @@ public class RemoveStaffCommand
|
||||||
Description = "User was removed from staff."
|
Description = "User was removed from staff."
|
||||||
}, true);
|
}, true);
|
||||||
|
|
||||||
try
|
await LogChannel.Success(user.Mention + " was removed from staff by " + command.User.Mention + ".");
|
||||||
{
|
|
||||||
// Log it if the log channel exists
|
|
||||||
DiscordChannel logChannel = await SupportChild.client.GetChannelAsync(Config.logChannel);
|
|
||||||
await logChannel.SendMessageAsync(new DiscordEmbedBuilder
|
|
||||||
{
|
|
||||||
Color = DiscordColor.Green,
|
|
||||||
Description = user.Mention + " was removed from staff by " + command.User.Mention + "."
|
|
||||||
});
|
|
||||||
}
|
|
||||||
catch (NotFoundException)
|
|
||||||
{
|
|
||||||
Logger.Error("Could not send message in log channel.");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,6 +1,7 @@
|
||||||
using DSharpPlus.Entities;
|
using DSharpPlus.Entities;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
|
using System.Globalization;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using DSharpPlus.Commands;
|
using DSharpPlus.Commands;
|
||||||
using DSharpPlus.Commands.ContextChecks;
|
using DSharpPlus.Commands.ContextChecks;
|
||||||
|
@ -22,11 +23,12 @@ public class SayCommand
|
||||||
// Print list of all messages if no identifier is provided
|
// Print list of all messages if no identifier is provided
|
||||||
if (identifier == null)
|
if (identifier == null)
|
||||||
{
|
{
|
||||||
SendMessageList(command);
|
await SendMessageList(command);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!Database.TryGetMessage(identifier.ToLower(), out Database.Message message))
|
if (!Database.TryGetMessage(identifier.ToLower(CultureInfo.InvariantCulture),
|
||||||
|
out Database.Message message))
|
||||||
{
|
{
|
||||||
await command.RespondAsync(new DiscordEmbedBuilder
|
await command.RespondAsync(new DiscordEmbedBuilder
|
||||||
{
|
{
|
||||||
|
@ -42,22 +44,10 @@ public class SayCommand
|
||||||
Description = message.message.Replace("\\n", "\n")
|
Description = message.message.Replace("\\n", "\n")
|
||||||
});
|
});
|
||||||
|
|
||||||
try
|
await LogChannel.Success(command.User.Mention + " posted the " + message.identifier + " message in " + command.Channel.Mention + ".");
|
||||||
{
|
|
||||||
DiscordChannel logChannel = await SupportChild.client.GetChannelAsync(Config.logChannel);
|
|
||||||
await logChannel.SendMessageAsync(new DiscordEmbedBuilder
|
|
||||||
{
|
|
||||||
Color = DiscordColor.Green,
|
|
||||||
Description = command.User.Mention + " posted the " + message.identifier + " message in " + command.Channel.Mention + "."
|
|
||||||
});
|
|
||||||
}
|
|
||||||
catch (NotFoundException)
|
|
||||||
{
|
|
||||||
Logger.Error("Could not send message in log channel.");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static async void SendMessageList(SlashCommandContext command)
|
private static async Task SendMessageList(SlashCommandContext command)
|
||||||
{
|
{
|
||||||
List<Database.Message> messages = Database.GetAllMessages();
|
List<Database.Message> messages = Database.GetAllMessages();
|
||||||
if (messages.Count == 0)
|
if (messages.Count == 0)
|
||||||
|
|
|
@ -43,22 +43,6 @@ public class SetSummaryCommand
|
||||||
Description = "Summary set."
|
Description = "Summary set."
|
||||||
}, true);
|
}, true);
|
||||||
|
|
||||||
try
|
await LogChannel.Success(command.User.Mention + " set the summary for " + command.Channel.Mention + " to:\n\n" + summary, ticket.id);
|
||||||
{
|
|
||||||
DiscordChannel logChannel = await SupportChild.client.GetChannelAsync(Config.logChannel);
|
|
||||||
await logChannel.SendMessageAsync(new DiscordEmbedBuilder
|
|
||||||
{
|
|
||||||
Color = DiscordColor.Green,
|
|
||||||
Description = command.User.Mention + " set the summary for " + command.Channel.Mention + " to:\n\n" + summary,
|
|
||||||
Footer = new DiscordEmbedBuilder.EmbedFooter
|
|
||||||
{
|
|
||||||
Text = "Ticket: " + ticket.id.ToString("00000")
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
catch (NotFoundException)
|
|
||||||
{
|
|
||||||
Logger.Error("Could not send message in log channel.");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -25,7 +25,7 @@ public class StatusCommand
|
||||||
.AddField("Open tickets:", openTickets + "", true)
|
.AddField("Open tickets:", openTickets + "", true)
|
||||||
.AddField("Closed tickets:", closedTickets + " ", true)
|
.AddField("Closed tickets:", closedTickets + " ", true)
|
||||||
.AddField("Report bugs:", "[Toastielab Issues](https://toastielab.dev/Emotions-stuff/SupportChild/issues)", true)
|
.AddField("Report bugs:", "[Toastielab Issues](https://toastielab.dev/Emotions-stuff/SupportChild/issues)", true)
|
||||||
.AddField("Commands:", "[Toastielab Repository](https://toastielab.dev/Emotions-stuff/SupportChild)", true);
|
.AddField("Commands:", "[Toastielab Repository](https://toastielab.dev/Emotions-stuff/SupportChild/src/branch/main/docs/Commands.md)", true);
|
||||||
await command.RespondAsync(botInfo);
|
await command.RespondAsync(botInfo);
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -58,30 +58,13 @@ public class ToggleActiveCommand
|
||||||
}, true);
|
}, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
try
|
if (user != null && user.Id != command.User.Id)
|
||||||
{
|
{
|
||||||
DiscordChannel logChannel = await SupportChild.client.GetChannelAsync(Config.logChannel);
|
await LogChannel.Success(staffUser.Mention + " set " + command.Channel.Mention + "'s status to " + (staffMember.active ? "active" : "inactive"));
|
||||||
|
|
||||||
if (user != null && user.Id != command.User.Id)
|
|
||||||
{
|
|
||||||
await logChannel.SendMessageAsync(new DiscordEmbedBuilder
|
|
||||||
{
|
|
||||||
Color = DiscordColor.Green,
|
|
||||||
Description = staffUser.Mention + " set " + command.Channel.Mention + "'s status to " + (staffMember.active ? "active" : "inactive")
|
|
||||||
});
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
await logChannel.SendMessageAsync(new DiscordEmbedBuilder
|
|
||||||
{
|
|
||||||
Color = DiscordColor.Green,
|
|
||||||
Description = staffUser.Mention + " set their own status to " + (staffMember.active ? "active" : "inactive")
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
catch (NotFoundException)
|
else
|
||||||
{
|
{
|
||||||
Logger.Error("Could not send message in log channel.");
|
await LogChannel.Success(staffUser.Mention + " set their own status to " + (staffMember.active ? "active" : "inactive"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -121,27 +121,12 @@ public class TranscriptCommand
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// Log it if the log channel exists
|
|
||||||
DiscordChannel logChannel = await SupportChild.client.GetChannelAsync(Config.logChannel);
|
|
||||||
await using FileStream file = new FileStream(filePath, FileMode.Open, FileAccess.Read);
|
await using FileStream file = new FileStream(filePath, FileMode.Open, FileAccess.Read);
|
||||||
|
await LogChannel.Success("Transcript generated by " + command.User.Mention + ".", ticket.id, new Utilities.File(fileName, file));
|
||||||
DiscordMessageBuilder message = new DiscordMessageBuilder();
|
|
||||||
message.AddEmbed(new DiscordEmbedBuilder
|
|
||||||
{
|
|
||||||
Color = DiscordColor.Green,
|
|
||||||
Description = "Transcript generated by " + command.User.Mention + ".",
|
|
||||||
Footer = new DiscordEmbedBuilder.EmbedFooter
|
|
||||||
{
|
|
||||||
Text = "Ticket: " + ticket.id.ToString("00000")
|
|
||||||
}
|
|
||||||
});
|
|
||||||
message.AddFiles(new Dictionary<string, Stream> { { fileName, file } });
|
|
||||||
|
|
||||||
await logChannel.SendMessageAsync(message);
|
|
||||||
}
|
}
|
||||||
catch (NotFoundException)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
Logger.Error("Could not send message in log channel.");
|
Logger.Error("Failed to log transcript generation.", e);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (await SendDirectMessage(command, fileName, filePath, zipSize, ticket.id))
|
if (await SendDirectMessage(command, fileName, filePath, zipSize, ticket.id))
|
||||||
|
|
|
@ -39,26 +39,9 @@ public class UnassignCommand
|
||||||
await command.RespondAsync(new DiscordEmbedBuilder
|
await command.RespondAsync(new DiscordEmbedBuilder
|
||||||
{
|
{
|
||||||
Color = DiscordColor.Green,
|
Color = DiscordColor.Green,
|
||||||
Description = "Unassigned staff member from ticket."
|
Description = "Unassigned <@" + ticket.assignedStaffID + "> from ticket."
|
||||||
});
|
});
|
||||||
|
|
||||||
try
|
await LogChannel.Success("<@" + ticket.assignedStaffID + "> was unassigned from <#" + ticket.channelID + "> by " + command.User.Mention + ".", ticket.id);
|
||||||
{
|
|
||||||
// Log it if the log channel exists
|
|
||||||
DiscordChannel logChannel = await SupportChild.client.GetChannelAsync(Config.logChannel);
|
|
||||||
await logChannel.SendMessageAsync(new DiscordEmbedBuilder
|
|
||||||
{
|
|
||||||
Color = DiscordColor.Green,
|
|
||||||
Description = "Staff member was unassigned from " + command.Channel.Mention + " by " + command.User.Mention + ".",
|
|
||||||
Footer = new DiscordEmbedBuilder.EmbedFooter
|
|
||||||
{
|
|
||||||
Text = "Ticket: " + ticket.id.ToString("00000")
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
catch (NotFoundException)
|
|
||||||
{
|
|
||||||
Logger.Error("Could not send message in log channel.");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -34,20 +34,7 @@ public class UnblacklistCommand
|
||||||
Description = "Unblocked " + user.Mention + " from opening new tickets."
|
Description = "Unblocked " + user.Mention + " from opening new tickets."
|
||||||
}, true);
|
}, true);
|
||||||
|
|
||||||
try
|
await LogChannel.Success(user.Mention + " was unblocked from opening tickets by " + command.User.Mention + ".");
|
||||||
{
|
|
||||||
// Log it if the log channel exists
|
|
||||||
DiscordChannel logChannel = await SupportChild.client.GetChannelAsync(Config.logChannel);
|
|
||||||
await logChannel.SendMessageAsync(new DiscordEmbedBuilder
|
|
||||||
{
|
|
||||||
Color = DiscordColor.Green,
|
|
||||||
Description = user.Mention + " was unblocked from opening tickets by " + command.User.Mention + "."
|
|
||||||
});
|
|
||||||
}
|
|
||||||
catch (NotFoundException)
|
|
||||||
{
|
|
||||||
Logger.Error("Could not send message in log channel.");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
catch (Exception)
|
catch (Exception)
|
||||||
{
|
{
|
||||||
|
|
|
@ -20,6 +20,7 @@ internal static class Config
|
||||||
internal static bool newCommandUsesSelector = false;
|
internal static bool newCommandUsesSelector = false;
|
||||||
internal static int ticketLimit = 5;
|
internal static int ticketLimit = 5;
|
||||||
internal static bool pinFirstMessage = false;
|
internal static bool pinFirstMessage = false;
|
||||||
|
internal static string transcriptDir = "";
|
||||||
|
|
||||||
internal static bool ticketUpdatedNotifications = false;
|
internal static bool ticketUpdatedNotifications = false;
|
||||||
internal static double ticketUpdatedNotificationDelay = 0.0;
|
internal static double ticketUpdatedNotificationDelay = 0.0;
|
||||||
|
@ -91,6 +92,7 @@ internal static class Config
|
||||||
newCommandUsesSelector = json.SelectToken("bot.new-command-uses-selector")?.Value<bool>() ?? false;
|
newCommandUsesSelector = json.SelectToken("bot.new-command-uses-selector")?.Value<bool>() ?? false;
|
||||||
ticketLimit = json.SelectToken("bot.ticket-limit")?.Value<int>() ?? 5;
|
ticketLimit = json.SelectToken("bot.ticket-limit")?.Value<int>() ?? 5;
|
||||||
pinFirstMessage = json.SelectToken("bot.pin-first-message")?.Value<bool>() ?? false;
|
pinFirstMessage = json.SelectToken("bot.pin-first-message")?.Value<bool>() ?? false;
|
||||||
|
transcriptDir = json.SelectToken("bot.transcript-dir")?.Value<string>() ?? "";
|
||||||
|
|
||||||
ticketUpdatedNotifications = json.SelectToken("notifications.ticket-updated")?.Value<bool>() ?? false;
|
ticketUpdatedNotifications = json.SelectToken("notifications.ticket-updated")?.Value<bool>() ?? false;
|
||||||
ticketUpdatedNotificationDelay = json.SelectToken("notifications.ticket-updated-delay")?.Value<double>() ?? 0.0;
|
ticketUpdatedNotificationDelay = json.SelectToken("notifications.ticket-updated-delay")?.Value<double>() ?? 0.0;
|
||||||
|
|
|
@ -7,8 +7,6 @@ using MySqlConnector;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using Newtonsoft.Json.Serialization;
|
using Newtonsoft.Json.Serialization;
|
||||||
using SupportChild.Interviews;
|
using SupportChild.Interviews;
|
||||||
using SupportChild.Interviews;
|
|
||||||
using SupportChild;
|
|
||||||
|
|
||||||
namespace SupportChild;
|
namespace SupportChild;
|
||||||
|
|
||||||
|
@ -468,12 +466,6 @@ public static class Database
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static StaffMember GetRandomActiveStaff(params ulong[] ignoredUserIDs)
|
|
||||||
{
|
|
||||||
List<StaffMember> staffMembers = GetActiveStaff(ignoredUserIDs);
|
|
||||||
return staffMembers.Any() ? staffMembers[RandomNumberGenerator.GetInt32(staffMembers.Count)] : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static List<StaffMember> GetActiveStaff(params ulong[] ignoredUserIDs)
|
public static List<StaffMember> GetActiveStaff(params ulong[] ignoredUserIDs)
|
||||||
{
|
{
|
||||||
using MySqlConnection c = GetConnection();
|
using MySqlConnection c = GetConnection();
|
||||||
|
|
|
@ -18,6 +18,8 @@ namespace SupportChild;
|
||||||
|
|
||||||
public static class EventHandler
|
public static class EventHandler
|
||||||
{
|
{
|
||||||
|
internal static bool hasLoggedGuilds = false;
|
||||||
|
|
||||||
public static Task OnReady(DiscordClient client, GuildDownloadCompletedEventArgs e)
|
public static Task OnReady(DiscordClient client, GuildDownloadCompletedEventArgs e)
|
||||||
{
|
{
|
||||||
Logger.Log("Connected to Discord.");
|
Logger.Log("Connected to Discord.");
|
||||||
|
@ -35,6 +37,12 @@ public static class EventHandler
|
||||||
|
|
||||||
public static async Task OnGuildAvailable(DiscordClient discordClient, GuildAvailableEventArgs e)
|
public static async Task OnGuildAvailable(DiscordClient discordClient, GuildAvailableEventArgs e)
|
||||||
{
|
{
|
||||||
|
if (hasLoggedGuilds)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
hasLoggedGuilds = true;
|
||||||
|
|
||||||
Logger.Log("Found Discord server: " + e.Guild.Name + " (" + e.Guild.Id + ")");
|
Logger.Log("Found Discord server: " + e.Guild.Name + " (" + e.Guild.Id + ")");
|
||||||
|
|
||||||
if (SupportChild.commandLineArgs.serversToLeave.Contains(e.Guild.Id))
|
if (SupportChild.commandLineArgs.serversToLeave.Contains(e.Guild.Id))
|
||||||
|
@ -104,8 +112,8 @@ public static class EventHandler
|
||||||
Description = "A ticket you are assigned to has been updated: " + e.Channel.Mention
|
Description = "A ticket you are assigned to has been updated: " + e.Channel.Mention
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
catch (NotFoundException) { }
|
catch (NotFoundException) { /* ignored */ }
|
||||||
catch (UnauthorizedException) { }
|
catch (UnauthorizedException) { /* ignored */ }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -166,27 +174,19 @@ public static class EventHandler
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Database.TryGetAssignedTickets(e.Member.Id, out List<Database.Ticket> assignedTickets) && Config.logChannel != 0)
|
if (LogChannel.IsEnabled && Database.TryGetAssignedTickets(e.Member.Id, out List<Database.Ticket> assignedTickets))
|
||||||
{
|
{
|
||||||
DiscordChannel logChannel = await client.GetChannelAsync(Config.logChannel);
|
foreach (Database.Ticket ticket in assignedTickets)
|
||||||
if (logChannel != null)
|
|
||||||
{
|
{
|
||||||
foreach (Database.Ticket ticket in assignedTickets)
|
try
|
||||||
{
|
{
|
||||||
try
|
DiscordChannel channel = await client.GetChannelAsync(ticket.channelID);
|
||||||
|
if (channel?.GuildId == e.Guild.Id)
|
||||||
{
|
{
|
||||||
DiscordChannel channel = await client.GetChannelAsync(ticket.channelID);
|
await LogChannel.Warn("Assigned staff member '" + e.Member.Username + "#" + e.Member.Discriminator + "' has left the server: <#" + channel.Id + ">");
|
||||||
if (channel?.GuildId == e.Guild.Id)
|
|
||||||
{
|
|
||||||
await logChannel.SendMessageAsync(new DiscordEmbedBuilder
|
|
||||||
{
|
|
||||||
Color = DiscordColor.Red,
|
|
||||||
Description = "Assigned staff member '" + e.Member.Username + "#" + e.Member.Discriminator + "' has left the server: <#" + channel.Id + ">"
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
catch (Exception) { /* ignored */ }
|
|
||||||
}
|
}
|
||||||
|
catch (Exception) { /* ignored */ }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -245,45 +245,41 @@ public static class EventHandler
|
||||||
Logger.Warn("Unknown form input received! '" + e.Id + "'");
|
Logger.Warn("Unknown form input received! '" + e.Id + "'");
|
||||||
return;
|
return;
|
||||||
case DiscordComponentType.UserSelect:
|
case DiscordComponentType.UserSelect:
|
||||||
switch (e.Id)
|
if (e.Id.StartsWith("supportchild_interviewuserselector"))
|
||||||
{
|
{
|
||||||
case not null when e.Id.StartsWith("supportchild_interviewuserselector"):
|
await Interviewer.ProcessButtonOrSelectorResponse(e.Interaction);
|
||||||
await Interviewer.ProcessButtonOrSelectorResponse(e.Interaction);
|
return;
|
||||||
return;
|
|
||||||
default:
|
|
||||||
Logger.Warn("Unknown selection box option received! '" + e.Id + "'");
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Logger.Warn("Unknown selection box option received! '" + e.Id + "'");
|
||||||
|
return;
|
||||||
case DiscordComponentType.RoleSelect:
|
case DiscordComponentType.RoleSelect:
|
||||||
switch (e.Id)
|
if (e.Id.StartsWith("supportchild_interviewroleselector"))
|
||||||
{
|
{
|
||||||
case not null when e.Id.StartsWith("supportchild_interviewroleselector"):
|
await Interviewer.ProcessButtonOrSelectorResponse(e.Interaction);
|
||||||
await Interviewer.ProcessButtonOrSelectorResponse(e.Interaction);
|
return;
|
||||||
return;
|
|
||||||
default:
|
|
||||||
Logger.Warn("Unknown selection box option received! '" + e.Id + "'");
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Logger.Warn("Unknown selection box option received! '" + e.Id + "'");
|
||||||
|
return;
|
||||||
case DiscordComponentType.MentionableSelect:
|
case DiscordComponentType.MentionableSelect:
|
||||||
switch (e.Id)
|
if (e.Id.StartsWith("supportchild_interviewmentionableselector"))
|
||||||
{
|
{
|
||||||
case not null when e.Id.StartsWith("supportchild_interviewmentionableselector"):
|
await Interviewer.ProcessButtonOrSelectorResponse(e.Interaction);
|
||||||
await Interviewer.ProcessButtonOrSelectorResponse(e.Interaction);
|
return;
|
||||||
return;
|
|
||||||
default:
|
|
||||||
Logger.Warn("Unknown selection box option received! '" + e.Id + "'");
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Logger.Warn("Unknown selection box option received! '" + e.Id + "'");
|
||||||
|
return;
|
||||||
case DiscordComponentType.ChannelSelect:
|
case DiscordComponentType.ChannelSelect:
|
||||||
switch (e.Id)
|
if (e.Id.StartsWith("supportchild_interviewchannelselector"))
|
||||||
{
|
{
|
||||||
case not null when e.Id.StartsWith("supportchild_interviewchannelselector"):
|
await Interviewer.ProcessButtonOrSelectorResponse(e.Interaction);
|
||||||
await Interviewer.ProcessButtonOrSelectorResponse(e.Interaction);
|
return;
|
||||||
return;
|
|
||||||
default:
|
|
||||||
Logger.Warn("Unknown selection box option received! '" + e.Id + "'");
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Logger.Warn("Unknown selection box option received! '" + e.Id + "'");
|
||||||
|
return;
|
||||||
default:
|
default:
|
||||||
Logger.Warn("Unknown interaction type received! '" + e.Interaction.Data.ComponentType + "'");
|
Logger.Warn("Unknown interaction type received! '" + e.Interaction.Data.ComponentType + "'");
|
||||||
break;
|
break;
|
||||||
|
@ -292,7 +288,7 @@ public static class EventHandler
|
||||||
catch (DiscordException ex)
|
catch (DiscordException ex)
|
||||||
{
|
{
|
||||||
Logger.Error("Interaction Exception occurred: " + ex);
|
Logger.Error("Interaction Exception occurred: " + ex);
|
||||||
Logger.Error("JsomMessage: " + ex.JsonMessage);
|
Logger.Error("JsonMessage: " + ex.JsonMessage);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
|
|
@ -168,7 +168,7 @@
|
||||||
{
|
{
|
||||||
"category-id":
|
"category-id":
|
||||||
{
|
{
|
||||||
"type": "string",
|
"type": "number",
|
||||||
"title": "Category ID",
|
"title": "Category ID",
|
||||||
"description": "The id of the category this template applies to. You can change this and re-upload the template to apply it to a different category."
|
"description": "The id of the category this template applies to. You can change this and re-upload the template to apply it to a different category."
|
||||||
},
|
},
|
||||||
|
|
12
Jenkinsfile
vendored
12
Jenkinsfile
vendored
|
@ -10,12 +10,16 @@ pipeline {
|
||||||
parallel {
|
parallel {
|
||||||
stage('Linux') {
|
stage('Linux') {
|
||||||
steps {
|
steps {
|
||||||
sh 'dotnet publish -r linux-x64 -c Release --self-contained true --no-restore --output Linux-x64/'
|
sh 'dotnet publish -r linux-x64 -c Release -p:PublishTrimmed=true --self-contained true --no-restore --output Linux-x64/'
|
||||||
|
sh 'mv Linux-x64/SupportChild Linux-x64/SupportChild-SC'
|
||||||
|
sh 'dotnet publish -r linux-x64 -c Release --self-contained false --no-restore --output Linux-x64/'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
stage('Windows') {
|
stage('Windows') {
|
||||||
steps {
|
steps {
|
||||||
sh 'dotnet publish -r win-x64 -c Release --self-contained true --no-restore --output Windows-x64/'
|
sh 'dotnet publish -r win-x64 -c Release -p:PublishTrimmed=true --self-contained true --no-restore --output Windows-x64/'
|
||||||
|
sh 'mv Windows-x64/SupportChild.exe Windows-x64/SupportChild-SC.exe'
|
||||||
|
sh 'dotnet publish -r win-x64 -c Release --self-contained false --no-restore --output Windows-x64/'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -25,14 +29,16 @@ pipeline {
|
||||||
stage('Linux') {
|
stage('Linux') {
|
||||||
steps {
|
steps {
|
||||||
archiveArtifacts(artifacts: 'Linux-x64/SupportChild', caseSensitive: true)
|
archiveArtifacts(artifacts: 'Linux-x64/SupportChild', caseSensitive: true)
|
||||||
|
archiveArtifacts(artifacts: 'Linux-x64/SupportChild-SC', caseSensitive: true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
stage('Windows') {
|
stage('Windows') {
|
||||||
steps {
|
steps {
|
||||||
archiveArtifacts(artifacts: 'Windows-x64/SupportChild.exe', caseSensitive: true)
|
archiveArtifacts(artifacts: 'Windows-x64/SupportChild.exe', caseSensitive: true)
|
||||||
|
archiveArtifacts(artifacts: 'Windows-x64/SupportChild-SC.exe', caseSensitive: true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
74
LogChannel.cs
Normal file
74
LogChannel.cs
Normal file
|
@ -0,0 +1,74 @@
|
||||||
|
using System;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using DSharpPlus.Entities;
|
||||||
|
using DSharpPlus.Exceptions;
|
||||||
|
|
||||||
|
namespace SupportChild;
|
||||||
|
|
||||||
|
public static class LogChannel
|
||||||
|
{
|
||||||
|
public static bool IsEnabled => Config.logChannel != 0;
|
||||||
|
|
||||||
|
public static async Task Log(string message, uint ticketID = 0, Utilities.File file = null)
|
||||||
|
{
|
||||||
|
await Log(DiscordColor.Cyan, message, ticketID, file);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async Task Success(string message, uint ticketID = 0, Utilities.File file = null)
|
||||||
|
{
|
||||||
|
await Log(DiscordColor.Green, message, ticketID, file);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async Task Warn(string message, uint ticketID = 0, Utilities.File file = null)
|
||||||
|
{
|
||||||
|
await Log(DiscordColor.Orange, message, ticketID, file);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async Task Error(string message, uint ticketID = 0, Utilities.File file = null)
|
||||||
|
{
|
||||||
|
await Log(DiscordColor.Red, message, ticketID, file);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task Log(DiscordColor color, string message, uint ticketID = 0, Utilities.File file = null)
|
||||||
|
{
|
||||||
|
if (!IsEnabled)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
DiscordEmbedBuilder embedBuilder = new()
|
||||||
|
{
|
||||||
|
Color = color,
|
||||||
|
Description = message
|
||||||
|
};
|
||||||
|
if (ticketID != 0)
|
||||||
|
{
|
||||||
|
embedBuilder.WithFooter("Ticket: " + ticketID.ToString("00000"));
|
||||||
|
}
|
||||||
|
|
||||||
|
DiscordMessageBuilder messageBuilder = new();
|
||||||
|
messageBuilder.AddEmbed(embedBuilder);
|
||||||
|
if (file != null)
|
||||||
|
{
|
||||||
|
messageBuilder.AddFile(file.fileName, file.contents);
|
||||||
|
}
|
||||||
|
|
||||||
|
DiscordChannel logChannel = await SupportChild.client.GetChannelAsync(Config.logChannel);
|
||||||
|
await logChannel.SendMessageAsync(messageBuilder);
|
||||||
|
}
|
||||||
|
catch (NotFoundException)
|
||||||
|
{
|
||||||
|
Logger.Error("Log channel does not exist. Channel ID: " + Config.logChannel);
|
||||||
|
}
|
||||||
|
catch (UnauthorizedException)
|
||||||
|
{
|
||||||
|
Logger.Error("No permissions to send message in log channel. Channel ID: " + Config.logChannel);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Logger.Error("Error occured trying to send message in log channel. Channel ID: " + Config.logChannel, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1 +1,3 @@
|
||||||
|
[![Build Status](https://ci.toastielab.dev/buildStatus/icon?job=Toastie-stuff%2FSupportChild%2Fmain)](https://ci.toastielab.dev/job/Toastie-stuff/job/SupportChild/job/main/)
|
||||||
|
|
||||||
# SupportChild
|
# SupportChild
|
||||||
|
|
|
@ -27,7 +27,6 @@ internal static class SupportChild
|
||||||
"config",
|
"config",
|
||||||
Required = false,
|
Required = false,
|
||||||
HelpText = "Select a config file to use.",
|
HelpText = "Select a config file to use.",
|
||||||
Default = "config.yml",
|
|
||||||
MetaValue = "PATH")]
|
MetaValue = "PATH")]
|
||||||
public string configPath { get; set; }
|
public string configPath { get; set; }
|
||||||
|
|
||||||
|
@ -35,7 +34,6 @@ internal static class SupportChild
|
||||||
"transcripts",
|
"transcripts",
|
||||||
Required = false,
|
Required = false,
|
||||||
HelpText = "Select directory to store transcripts in.",
|
HelpText = "Select directory to store transcripts in.",
|
||||||
Default = "./transcripts",
|
|
||||||
MetaValue = "PATH")]
|
MetaValue = "PATH")]
|
||||||
public string transcriptDir { get; set; }
|
public string transcriptDir { get; set; }
|
||||||
|
|
||||||
|
@ -226,6 +224,7 @@ internal static class SupportChild
|
||||||
client = clientBuilder.Build();
|
client = clientBuilder.Build();
|
||||||
|
|
||||||
Logger.Log("Connecting to Discord...");
|
Logger.Log("Connecting to Discord...");
|
||||||
|
EventHandler.hasLoggedGuilds = false;
|
||||||
await client.ConnectAsync();
|
await client.ConnectAsync();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,7 +10,7 @@
|
||||||
<PublishSingleFile>true</PublishSingleFile>
|
<PublishSingleFile>true</PublishSingleFile>
|
||||||
<TrimMode>partial</TrimMode>
|
<TrimMode>partial</TrimMode>
|
||||||
|
|
||||||
<Authors>EmotionChild</Authors>
|
<Authors>Toastie_t0ast</Authors>
|
||||||
<Description>A Discord support ticket bot built for the Ellie's home server</Description>
|
<Description>A Discord support ticket bot built for the Ellie's home server</Description>
|
||||||
<NeutralLanguage>en</NeutralLanguage>
|
<NeutralLanguage>en</NeutralLanguage>
|
||||||
<RepositoryType>Git</RepositoryType>
|
<RepositoryType>Git</RepositoryType>
|
||||||
|
@ -36,7 +36,7 @@
|
||||||
<PackageReference Include="DSharpPlus.Commands" Version="5.0.0-nightly-02405" />
|
<PackageReference Include="DSharpPlus.Commands" Version="5.0.0-nightly-02405" />
|
||||||
<PackageReference Include="DSharpPlus.Interactivity" Version="5.0.0-nightly-02405" />
|
<PackageReference Include="DSharpPlus.Interactivity" Version="5.0.0-nightly-02405" />
|
||||||
<PackageReference Include="EmbeddedBuildTime" Version="1.0.3" />
|
<PackageReference Include="EmbeddedBuildTime" Version="1.0.3" />
|
||||||
<PackageReference Include="GitInfo" Version="3.5.0">
|
<PackageReference Include="GitInfo" Version="3.3.5">
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
|
|
|
@ -13,23 +13,11 @@ namespace SupportChild;
|
||||||
|
|
||||||
internal static class Transcriber
|
internal static class Transcriber
|
||||||
{
|
{
|
||||||
private static string transcriptDir = "./transcripts"; // TODO: Should be local variable (or come from the config class) when added to config to avoid race conditions when reloading
|
|
||||||
|
|
||||||
internal static async Task ExecuteAsync(ulong channelID, uint ticketID)
|
internal static async Task ExecuteAsync(ulong channelID, uint ticketID)
|
||||||
{
|
{
|
||||||
DiscordClient discordClient = new DiscordClient(Config.token);
|
DiscordClient discordClient = new DiscordClient(Config.token);
|
||||||
ChannelExporter exporter = new ChannelExporter(discordClient);
|
ChannelExporter exporter = new ChannelExporter(discordClient);
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(SupportChild.commandLineArgs.transcriptDir))
|
|
||||||
{
|
|
||||||
transcriptDir = SupportChild.commandLineArgs.transcriptDir;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!Directory.Exists(transcriptDir))
|
|
||||||
{
|
|
||||||
Directory.CreateDirectory(transcriptDir);
|
|
||||||
}
|
|
||||||
|
|
||||||
string htmlPath = GetHtmlPath(ticketID);
|
string htmlPath = GetHtmlPath(ticketID);
|
||||||
string zipPath = GetZipPath(ticketID);
|
string zipPath = GetZipPath(ticketID);
|
||||||
string assetDirPath = GetAssetDirPath(ticketID);
|
string assetDirPath = GetAssetDirPath(ticketID);
|
||||||
|
@ -88,10 +76,10 @@ internal static class Transcriber
|
||||||
{
|
{
|
||||||
using (ZipArchive zip = ZipFile.Open(zipPath, ZipArchiveMode.Create))
|
using (ZipArchive zip = ZipFile.Open(zipPath, ZipArchiveMode.Create))
|
||||||
{
|
{
|
||||||
zip.CreateEntryFromFile(htmlPath, htmlFilename);
|
zip.CreateEntryFromFile(htmlPath, htmlFilename, CompressionLevel.SmallestSize);
|
||||||
foreach (string assetFile in assetFiles)
|
foreach (string assetFile in assetFiles)
|
||||||
{
|
{
|
||||||
zip.CreateEntryFromFile(assetFile, assetDirName + "/" + Path.GetFileName(assetFile));
|
zip.CreateEntryFromFile(assetFile, assetDirName + "/" + Path.GetFileName(assetFile), CompressionLevel.SmallestSize);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -99,19 +87,39 @@ internal static class Transcriber
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static string GetTranscriptDir()
|
||||||
|
{
|
||||||
|
string transcriptDir = "./transcripts";
|
||||||
|
if (!string.IsNullOrEmpty(SupportChild.commandLineArgs.transcriptDir))
|
||||||
|
{
|
||||||
|
transcriptDir = SupportChild.commandLineArgs.transcriptDir;
|
||||||
|
}
|
||||||
|
else if (!string.IsNullOrEmpty(Config.transcriptDir))
|
||||||
|
{
|
||||||
|
transcriptDir = Config.transcriptDir;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Directory.Exists(transcriptDir))
|
||||||
|
{
|
||||||
|
Directory.CreateDirectory(transcriptDir);
|
||||||
|
}
|
||||||
|
|
||||||
|
return transcriptDir;
|
||||||
|
}
|
||||||
|
|
||||||
internal static string GetHtmlPath(uint ticketNumber)
|
internal static string GetHtmlPath(uint ticketNumber)
|
||||||
{
|
{
|
||||||
return transcriptDir + "/" + GetHTMLFilename(ticketNumber);
|
return GetTranscriptDir() + "/" + GetHTMLFilename(ticketNumber);
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static string GetZipPath(uint ticketNumber)
|
internal static string GetZipPath(uint ticketNumber)
|
||||||
{
|
{
|
||||||
return transcriptDir + "/" + GetZipFilename(ticketNumber);
|
return GetTranscriptDir() + "/" + GetZipFilename(ticketNumber);
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static string GetAssetDirPath(uint ticketNumber)
|
internal static string GetAssetDirPath(uint ticketNumber)
|
||||||
{
|
{
|
||||||
return transcriptDir + "/" + GetAssetDirName(ticketNumber);
|
return GetTranscriptDir() + "/" + GetAssetDirName(ticketNumber);
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static string GetAssetDirName(uint ticketNumber)
|
internal static string GetAssetDirName(uint ticketNumber)
|
||||||
|
|
10
Utilities.cs
10
Utilities.cs
|
@ -1,5 +1,6 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Globalization;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
|
@ -14,6 +15,7 @@ public static class Extensions
|
||||||
{
|
{
|
||||||
return needles.Any(haystack.Contains);
|
return needles.Any(haystack.Contains);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static bool ContainsAny(this string haystack, params char[] needles)
|
public static bool ContainsAny(this string haystack, params char[] needles)
|
||||||
{
|
{
|
||||||
return needles.Any(haystack.Contains);
|
return needles.Any(haystack.Contains);
|
||||||
|
@ -24,6 +26,12 @@ public static class Utilities
|
||||||
{
|
{
|
||||||
private static readonly Random rng = new Random();
|
private static readonly Random rng = new Random();
|
||||||
|
|
||||||
|
public class File(string fileName, Stream contents)
|
||||||
|
{
|
||||||
|
public string fileName = fileName;
|
||||||
|
public Stream contents = contents;
|
||||||
|
}
|
||||||
|
|
||||||
public static void Shuffle<T>(this IList<T> list)
|
public static void Shuffle<T>(this IList<T> list)
|
||||||
{
|
{
|
||||||
int n = list.Count;
|
int n = list.Count;
|
||||||
|
@ -95,7 +103,7 @@ public static class Utilities
|
||||||
|
|
||||||
public static DiscordColor StringToColor(string color)
|
public static DiscordColor StringToColor(string color)
|
||||||
{
|
{
|
||||||
switch (color.ToLower())
|
switch (color.ToLower(CultureInfo.InvariantCulture))
|
||||||
{
|
{
|
||||||
case "black":
|
case "black":
|
||||||
return DiscordColor.Black;
|
return DiscordColor.Black;
|
||||||
|
|
|
@ -39,6 +39,9 @@
|
||||||
# Pins the first message in a ticket to allow for quick navigation to the top in large tickets.
|
# Pins the first message in a ticket to allow for quick navigation to the top in large tickets.
|
||||||
pin-first-message: true
|
pin-first-message: true
|
||||||
|
|
||||||
|
# Ticket transcript location, can be overridden using command line arguments.
|
||||||
|
transcript-dir: "./transcripts"
|
||||||
|
|
||||||
notifications:
|
notifications:
|
||||||
# Notifies the assigned staff member when a new message is posted in a ticket if the ticket has been silent for a configurable amount of time.
|
# Notifies the assigned staff member when a new message is posted in a ticket if the ticket has been silent for a configurable amount of time.
|
||||||
# Other staff members and bots do not trigger this.
|
# Other staff members and bots do not trigger this.
|
||||||
|
|
|
@ -196,7 +196,7 @@ except for selection boxes and buttons where each step becomes a button or selec
|
||||||
<td>
|
<td>
|
||||||
`heading`
|
`heading`
|
||||||
</td>
|
</td>
|
||||||
<td>Yes</td>
|
<td>No</td>
|
||||||
<td>String</td>
|
<td>String</td>
|
||||||
<td>
|
<td>
|
||||||
The title of the embed message.
|
The title of the embed message.
|
||||||
|
@ -206,7 +206,7 @@ The title of the embed message.
|
||||||
<td>
|
<td>
|
||||||
`summary-field`
|
`summary-field`
|
||||||
</td>
|
</td>
|
||||||
<td>Yes</td>
|
<td>No</td>
|
||||||
<td>String</td>
|
<td>String</td>
|
||||||
<td>
|
<td>
|
||||||
When an interview ends all previous answers with this property will be put in a summary.
|
When an interview ends all previous answers with this property will be put in a summary.
|
||||||
|
@ -218,7 +218,7 @@ The value of this property is the name which will be displayed next to the answe
|
||||||
<td>
|
<td>
|
||||||
`button-style`
|
`button-style`
|
||||||
</td>
|
</td>
|
||||||
<td>Yes</td>
|
<td>No</td>
|
||||||
<td>String</td>
|
<td>String</td>
|
||||||
<td>
|
<td>
|
||||||
The style of this step's button. Requires that the parent step is a `BUTTONS` step.
|
The style of this step's button. Requires that the parent step is a `BUTTONS` step.
|
||||||
|
@ -237,7 +237,7 @@ Default style is `SECONDARY`.
|
||||||
<td>
|
<td>
|
||||||
`selector-description`
|
`selector-description`
|
||||||
</td>
|
</td>
|
||||||
<td>Yes</td>
|
<td>No</td>
|
||||||
<td>String</td>
|
<td>String</td>
|
||||||
<td>
|
<td>
|
||||||
Description for this option in the parent step's selection box. Requires that the parent step is a `TEXT_SELECTOR`.
|
Description for this option in the parent step's selection box. Requires that the parent step is a `TEXT_SELECTOR`.
|
||||||
|
@ -247,7 +247,7 @@ Description for this option in the parent step's selection box. Requires that th
|
||||||
<td>
|
<td>
|
||||||
`selector-placeholder`
|
`selector-placeholder`
|
||||||
</td>
|
</td>
|
||||||
<td>Yes</td>
|
<td>No</td>
|
||||||
<td>String</td>
|
<td>String</td>
|
||||||
<td>
|
<td>
|
||||||
The placeholder text shown before a value is selected in the selection box. Requires that this step is a `TEXT_SELECTOR`.
|
The placeholder text shown before a value is selected in the selection box. Requires that this step is a `TEXT_SELECTOR`.
|
||||||
|
@ -257,7 +257,7 @@ The placeholder text shown before a value is selected in the selection box. Requ
|
||||||
<td>
|
<td>
|
||||||
`max-length`
|
`max-length`
|
||||||
</td>
|
</td>
|
||||||
<td>Yes</td>
|
<td>No</td>
|
||||||
<td>Number</td>
|
<td>Number</td>
|
||||||
<td>
|
<td>
|
||||||
The maximum length of the user's response message. Requires that this step is a `TEXT_INPUT`.
|
The maximum length of the user's response message. Requires that this step is a `TEXT_INPUT`.
|
||||||
|
@ -267,7 +267,7 @@ The maximum length of the user's response message. Requires that this step is a
|
||||||
<td>
|
<td>
|
||||||
`min-length`
|
`min-length`
|
||||||
</td>
|
</td>
|
||||||
<td>Yes</td>
|
<td>No</td>
|
||||||
<td>Number</td>
|
<td>Number</td>
|
||||||
<td>
|
<td>
|
||||||
The minimum length of the user's response message. Requires that this step is a `TEXT_INPUT`.
|
The minimum length of the user's response message. Requires that this step is a `TEXT_INPUT`.
|
||||||
|
|
Loading…
Reference in a new issue