diff --git a/Oxide.Ext.Discord/BotClient.cs b/Oxide.Ext.Discord/BotClient.cs
index 584058d3e..3c4a8edcc 100644
--- a/Oxide.Ext.Discord/BotClient.cs
+++ b/Oxide.Ext.Discord/BotClient.cs
@@ -52,12 +52,7 @@ public class BotClient
/// If the connection is initialized and not disconnected
///
public bool Initialized { get; private set; }
-
- ///
- /// If the bot has successfully connected to the websocket at least once
- ///
- public bool ConnectedSuccessfully { get; internal set; }
-
+
///
/// Application reference for this bot
///
diff --git a/Oxide.Ext.Discord/Builders/ApplicationCommands/ApplicationCommandBuilder.cs b/Oxide.Ext.Discord/Builders/ApplicationCommands/ApplicationCommandBuilder.cs
index e35ebc72e..9ee1a4385 100644
--- a/Oxide.Ext.Discord/Builders/ApplicationCommands/ApplicationCommandBuilder.cs
+++ b/Oxide.Ext.Discord/Builders/ApplicationCommands/ApplicationCommandBuilder.cs
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using Oxide.Ext.Discord.Entities.Interactions.ApplicationCommands;
+using Oxide.Ext.Discord.Exceptions;
namespace Oxide.Ext.Discord.Builders.ApplicationCommands
{
@@ -65,12 +66,12 @@ public SubCommandGroupBuilder AddSubCommandGroup(string name, string description
if (_chosenType.HasValue && _chosenType.Value != CommandOptionType.SubCommandGroup && _chosenType.Value != CommandOptionType.SubCommand)
{
- throw new Exception("Cannot mix sub command / sub command groups with command options");
+ throw new InvalidApplicationCommandException("Cannot mix sub command / sub command groups with command options");
}
if (Command.Type == ApplicationCommandType.Message || Command.Type == ApplicationCommandType.User)
{
- throw new Exception("Message and User commands cannot have sub command groups");
+ throw new InvalidApplicationCommandException("Message and User commands cannot have sub command groups");
}
_chosenType = CommandOptionType.SubCommandGroup;
@@ -94,12 +95,12 @@ public SubCommandBuilder AddSubCommand(string name, string description)
if (_chosenType.HasValue && _chosenType.Value != CommandOptionType.SubCommandGroup && _chosenType.Value != CommandOptionType.SubCommand)
{
- throw new Exception("Cannot mix sub command / sub command groups with command options");
+ throw new InvalidApplicationCommandException("Cannot mix sub command / sub command groups with command options");
}
if (Command.Type == ApplicationCommandType.Message || Command.Type == ApplicationCommandType.User)
{
- throw new Exception("Message and User commands cannot have sub commands");
+ throw new InvalidApplicationCommandException("Message and User commands cannot have sub commands");
}
_chosenType = CommandOptionType.SubCommand;
@@ -118,7 +119,7 @@ public CommandOptionBuilder AddOption(CommandOptionType type, string name, strin
{
if (_chosenType.HasValue && (_chosenType.Value == CommandOptionType.SubCommandGroup || _chosenType.Value == CommandOptionType.SubCommand))
{
- throw new Exception("Cannot mix sub command / sub command groups with command options");
+ throw new InvalidApplicationCommandException("Cannot mix sub command / sub command groups with command options");
}
return new CommandOptionBuilder(_options, type, name, description, this);
diff --git a/Oxide.Ext.Discord/Builders/ApplicationCommands/CommandOptionBuilder.cs b/Oxide.Ext.Discord/Builders/ApplicationCommands/CommandOptionBuilder.cs
index 47e090dcb..13854896c 100644
--- a/Oxide.Ext.Discord/Builders/ApplicationCommands/CommandOptionBuilder.cs
+++ b/Oxide.Ext.Discord/Builders/ApplicationCommands/CommandOptionBuilder.cs
@@ -2,6 +2,7 @@
using System.Collections.Generic;
using Oxide.Ext.Discord.Entities.Channels;
using Oxide.Ext.Discord.Entities.Interactions.ApplicationCommands;
+using Oxide.Ext.Discord.Exceptions;
namespace Oxide.Ext.Discord.Builders.ApplicationCommands
{
@@ -22,7 +23,7 @@ internal CommandOptionBuilder(List parent, CommandOptionType type
if (type == CommandOptionType.SubCommand || type == CommandOptionType.SubCommandGroup)
{
- throw new Exception($"{type} is not allowed to be used here. Valid types are any non command type.");
+ throw new InvalidApplicationCommandException($"{type} is not allowed to be used here. Valid types are any non command type.");
}
_option = new CommandOption
@@ -66,7 +67,7 @@ public CommandOptionBuilder SetMinValue(int minValue)
{
if (_option.Type != CommandOptionType.Integer && _option.Type != CommandOptionType.Number)
{
- throw new Exception("Can only set min value for Integer or Number Type");
+ throw new InvalidApplicationCommandException("Can only set min value for Integer or Number Type");
}
_option.MinValue = minValue;
@@ -82,7 +83,7 @@ public CommandOptionBuilder SetMinValue(double minValue)
{
if (_option.Type != CommandOptionType.Number)
{
- throw new Exception("Can only set min value for Number Type");
+ throw new InvalidApplicationCommandException("Can only set min value for Number Type");
}
_option.MinValue = minValue;
@@ -98,7 +99,7 @@ public CommandOptionBuilder SetMaxValue(int maxValue)
{
if (_option.Type != CommandOptionType.Integer && _option.Type != CommandOptionType.Number)
{
- throw new Exception("Can only set max value for Integer or Number Type");
+ throw new InvalidApplicationCommandException("Can only set max value for Integer or Number Type");
}
_option.MaxValue = maxValue;
@@ -114,7 +115,7 @@ public CommandOptionBuilder SetMaxValue(double maxValue)
{
if (_option.Type != CommandOptionType.Integer && _option.Type != CommandOptionType.Number)
{
- throw new Exception("Can only set max value for Number Type");
+ throw new InvalidApplicationCommandException("Can only set max value for Number Type");
}
_option.MaxValue = maxValue;
@@ -131,7 +132,7 @@ public CommandOptionBuilder SetChannelTypes(List types)
{
if (_option.Type != CommandOptionType.Channel)
{
- throw new Exception("Can only set ChannelTypes for CommandOptionType.Channel");
+ throw new InvalidApplicationCommandException("Can only set ChannelTypes for CommandOptionType.Channel");
}
_option.ChannelTypes = types;
@@ -154,7 +155,7 @@ public CommandOptionBuilder AddChoice(string name, string value)
if (_option.Type != CommandOptionType.String)
{
- throw new Exception($"Cannot add a string choice to non string type: {_option.Type}");
+ throw new InvalidApplicationCommandException($"Cannot add a string choice to non string type: {_option.Type}");
}
return AddChoice(name, (object)value);
@@ -175,7 +176,7 @@ public CommandOptionBuilder AddChoice(string name, int value)
if (_option.Type != CommandOptionType.Integer)
{
- throw new Exception($"Cannot add a integer choice to non integer type: {_option.Type}");
+ throw new InvalidApplicationCommandException($"Cannot add a integer choice to non integer type: {_option.Type}");
}
return AddChoice(name, (object)value);
@@ -195,7 +196,7 @@ public CommandOptionBuilder AddChoice(string name, double value)
if (_option.Type != CommandOptionType.Number)
{
- throw new Exception($"Cannot add a number choice to non number type: {_option.Type}");
+ throw new InvalidApplicationCommandException($"Cannot add a number choice to non number type: {_option.Type}");
}
return AddChoice(name, (object)value);
diff --git a/Oxide.Ext.Discord/Builders/DiscordEmbedBuilder.cs b/Oxide.Ext.Discord/Builders/DiscordEmbedBuilder.cs
index 858890681..f14d66229 100644
--- a/Oxide.Ext.Discord/Builders/DiscordEmbedBuilder.cs
+++ b/Oxide.Ext.Discord/Builders/DiscordEmbedBuilder.cs
@@ -3,6 +3,7 @@
using System.Globalization;
using Oxide.Ext.Discord.Entities.Messages.Embeds;
using Oxide.Ext.Discord.Entities.Permissions;
+using Oxide.Ext.Discord.Exceptions;
namespace Oxide.Ext.Discord.Builders
{
@@ -22,7 +23,7 @@ public DiscordEmbedBuilder AddTitle(string title)
{
if (title != null && title.Length > 256)
{
- throw new Exception("Title cannot be more than 256 characters");
+ throw new InvalidEmbedException("Title cannot be more than 256 characters");
}
_embed.Title = title;
@@ -39,7 +40,7 @@ public DiscordEmbedBuilder AddDescription(string description)
if (description == null) throw new ArgumentNullException(nameof(description));
if (description.Length > 4096)
{
- throw new Exception("Description cannot be more than 4096 characters");
+ throw new InvalidEmbedException("Description cannot be more than 4096 characters");
}
_embed.Description = description;
@@ -70,7 +71,7 @@ public DiscordEmbedBuilder AddAuthor(string name, string iconUrl = null, string
if (name == null) throw new ArgumentNullException(nameof(name));
if (name.Length > 256)
{
- throw new Exception("Author name cannot be more than 256 characters");
+ throw new InvalidEmbedException("Author name cannot be more than 256 characters");
}
_embed.Author = new EmbedAuthor(name, iconUrl, url, proxyIconUrl);
@@ -89,7 +90,7 @@ public DiscordEmbedBuilder AddFooter(string text, string iconUrl = null, string
if (text == null) throw new ArgumentNullException(nameof(text));
if (text.Length > 2048)
{
- throw new Exception("Author name cannot be more than 2048 characters");
+ throw new InvalidEmbedException("Footer text cannot be more than 2048 characters");
}
_embed.Footer = new EmbedFooter(text, iconUrl, proxyIconUrl);
@@ -222,7 +223,7 @@ public DiscordEmbedBuilder AddBlankField(bool inline)
if ( _embed.Fields.Count >= 25)
{
- throw new Exception("Embeds cannot have more than 25 fields");
+ throw new InvalidEmbedException("Embeds cannot have more than 25 fields");
}
_embed.Fields.Add(new EmbedField("\u200b", "\u200b", inline));
@@ -246,27 +247,27 @@ public DiscordEmbedBuilder AddField(string name, string value, bool inline)
if (_embed.Fields.Count >= 25)
{
- throw new Exception("Embeds cannot have more than 25 fields");
+ throw new InvalidEmbedException("Embeds cannot have more than 25 fields");
}
if (string.IsNullOrEmpty(name))
{
- throw new Exception("Embed Fields cannot have a null or empty name");
+ throw new InvalidEmbedException("Embed Fields cannot have a null or empty name");
}
if (string.IsNullOrEmpty(value))
{
- throw new Exception("Embed Fields cannot have a null or empty value");
+ throw new InvalidEmbedException("Embed Fields cannot have a null or empty value");
}
if (name.Length > 256)
{
- throw new Exception("Field name cannot be more than 256 characters");
+ throw new InvalidEmbedException("Field name cannot be more than 256 characters");
}
if (value.Length > 1024)
{
- throw new Exception("Field value cannot be more than 1024 characters");
+ throw new InvalidEmbedException("Field value cannot be more than 1024 characters");
}
_embed.Fields.Add(new EmbedField(name, value, inline));
diff --git a/Oxide.Ext.Discord/Builders/MessageComponents/MessageComponentBuilder.cs b/Oxide.Ext.Discord/Builders/MessageComponents/MessageComponentBuilder.cs
index 3b61c8cc7..78e39552c 100644
--- a/Oxide.Ext.Discord/Builders/MessageComponents/MessageComponentBuilder.cs
+++ b/Oxide.Ext.Discord/Builders/MessageComponents/MessageComponentBuilder.cs
@@ -2,6 +2,7 @@
using System.Collections.Generic;
using Oxide.Ext.Discord.Entities.Emojis;
using Oxide.Ext.Discord.Entities.Interactions.MessageComponents;
+using Oxide.Ext.Discord.Exceptions;
namespace Oxide.Ext.Discord.Builders.MessageComponents
{
///
@@ -37,7 +38,7 @@ public MessageComponentBuilder AddActionButton(ButtonStyle style, string label,
{
if (style == ButtonStyle.Link)
{
- throw new Exception($"Cannot add link button as action button. Please use {nameof(AddLinkButton)} instead");
+ throw new InvalidMessageComponentException($"Cannot add link button as action button. Please use {nameof(AddLinkButton)} instead");
}
UpdateActionRow();
@@ -123,11 +124,13 @@ public SelectMenuComponentBuilder AddSelectMenu(string customId, string placehol
/// Unique ID for the select menu
/// Label for the input text
/// Style of the Input Text
+ /// Default value for the Input Text
+ /// Is the Input Text Required to be filled out
/// Text to display if no value is selected yet
- /// The min number of options you must select
- /// The max number of options you can select
+ /// The min number of options you must select
+ /// The max number of options you can select
///
- public MessageComponentBuilder AddInputText(string customId, string label, InputTextStyles style, string placeholder = null, int minValues = 1, int maxValues = 1)
+ public MessageComponentBuilder AddInputText(string customId, string label, InputTextStyles style, string value = null, bool? required = null, string placeholder = null, int? minLength = null, int? maxLength = null)
{
if (string.IsNullOrEmpty(customId))
throw new ArgumentException("Value cannot be null or empty.", nameof(customId));
@@ -138,9 +141,11 @@ public MessageComponentBuilder AddInputText(string customId, string label, Input
CustomId = customId,
Label = label,
Style = style,
+ Value = value,
+ Required = required,
Placeholder = placeholder,
- MinLength = minValues,
- MaxLength = maxValues,
+ MinLength = minLength,
+ MaxLength = maxLength,
};
_current.Components.Add(menu);
return this;
@@ -162,7 +167,7 @@ private void UpdateActionRow() where T : BaseComponent
//Max of 5 action rows allowed
if (_components.Count >= 5)
{
- throw new Exception("Cannot have more than 5 action rows");
+ throw new InvalidMessageComponentException("Cannot have more than 5 action rows");
}
//All other components are only 1 per action row so add a new row.
diff --git a/Oxide.Ext.Discord/Builders/MessageComponents/SelectMenuComponentBuilder.cs b/Oxide.Ext.Discord/Builders/MessageComponents/SelectMenuComponentBuilder.cs
index 05c94f9ab..08dcd6748 100644
--- a/Oxide.Ext.Discord/Builders/MessageComponents/SelectMenuComponentBuilder.cs
+++ b/Oxide.Ext.Discord/Builders/MessageComponents/SelectMenuComponentBuilder.cs
@@ -1,6 +1,8 @@
using System;
using Oxide.Ext.Discord.Entities.Emojis;
using Oxide.Ext.Discord.Entities.Interactions.MessageComponents;
+using Oxide.Ext.Discord.Exceptions;
+
namespace Oxide.Ext.Discord.Builders.MessageComponents
{
///
@@ -35,7 +37,7 @@ public SelectMenuComponentBuilder AddOption(string label, string value, string d
if (_menu.Options.Count >= 25)
{
- throw new Exception("Select Menu Options cannot have more than 25 options");
+ throw new InvalidMessageComponentException("Select Menu Options cannot have more than 25 options");
}
_menu.Options.Add(new SelectMenuOption
diff --git a/Oxide.Ext.Discord/DiscordExtension.cs b/Oxide.Ext.Discord/DiscordExtension.cs
index 81626b624..bdced65ad 100644
--- a/Oxide.Ext.Discord/DiscordExtension.cs
+++ b/Oxide.Ext.Discord/DiscordExtension.cs
@@ -34,7 +34,7 @@ public class DiscordExtension : Extension
///
/// Version number of the extension
///
- private static readonly VersionNumber ExtensionVersion = new VersionNumber(2, 1, 0);
+ private static readonly VersionNumber ExtensionVersion = new VersionNumber(2, 1, 1);
///
/// Global logger for areas that aren't part of a client connection
diff --git a/Oxide.Ext.Discord/Entities/Applications/DiscordApplication.cs b/Oxide.Ext.Discord/Entities/Applications/DiscordApplication.cs
index aaeb86013..5343d9529 100644
--- a/Oxide.Ext.Discord/Entities/Applications/DiscordApplication.cs
+++ b/Oxide.Ext.Discord/Entities/Applications/DiscordApplication.cs
@@ -2,13 +2,10 @@
using System.Collections.Generic;
using Newtonsoft.Json;
using Oxide.Ext.Discord.Entities.Api;
-using Oxide.Ext.Discord.Entities.Guilds;
-using Oxide.Ext.Discord.Entities.Interactions;
using Oxide.Ext.Discord.Entities.Interactions.ApplicationCommands;
-using Oxide.Ext.Discord.Entities.Messages;
using Oxide.Ext.Discord.Entities.Teams;
using Oxide.Ext.Discord.Entities.Users;
-using Oxide.Ext.Discord.Entities.Webhooks;
+using Oxide.Ext.Discord.Exceptions;
using Oxide.Ext.Discord.Helpers.Cdn;
namespace Oxide.Ext.Discord.Entities.Applications
@@ -166,6 +163,7 @@ public void GetGlobalCommands(DiscordClient client, ActionCallback when an error occurs with error information
public void GetGlobalCommand(DiscordClient client, Snowflake commandId, Action callback = null, Action error = null)
{
+ if (!commandId.IsValid()) throw new InvalidSnowflakeException(nameof(commandId));
client.Bot.Rest.DoRequest($"/applications/{Id}/commands/{commandId}", RequestMethod.GET, null, callback, error);
}
@@ -207,6 +205,7 @@ public void BulkOverwriteGlobalCommands(DiscordClient client, ListCallback when an error occurs with error information
public void GetGuildCommands(DiscordClient client, Snowflake guildId, Action> callback = null, Action error = null)
{
+ if (!guildId.IsValid()) throw new InvalidSnowflakeException(nameof(guildId));
client.Bot.Rest.DoRequest($"/applications/{Id}/guilds/{guildId}/commands", RequestMethod.GET, null, callback, error);
}
@@ -221,6 +220,8 @@ public void GetGuildCommands(DiscordClient client, Snowflake guildId, ActionCallback when an error occurs with error information
public void GetGuildCommand(DiscordClient client, Snowflake guildId, Snowflake commandId, Action callback = null, Action error = null)
{
+ if (!guildId.IsValid()) throw new InvalidSnowflakeException(nameof(guildId));
+ if (!commandId.IsValid()) throw new InvalidSnowflakeException(nameof(commandId));
client.Bot.Rest.DoRequest($"/applications/{Id}/guilds/{guildId}/commands/{commandId}", RequestMethod.GET, null, callback, error);
}
@@ -236,6 +237,7 @@ public void GetGuildCommand(DiscordClient client, Snowflake guildId, Snowflake c
/// Callback when an error occurs with error information
public void CreateGuildCommand(DiscordClient client, Snowflake guildId, CommandCreate create, Action callback = null, Action error = null)
{
+ if (!guildId.IsValid()) throw new InvalidSnowflakeException(nameof(guildId));
client.Bot.Rest.DoRequest($"/applications/{Id}/guilds/{guildId}/commands", RequestMethod.POST, create, callback, error);
}
@@ -248,6 +250,7 @@ public void CreateGuildCommand(DiscordClient client, Snowflake guildId, CommandC
/// Callback when an error occurs with error information
public void GetGuildCommandPermissions(DiscordClient client, Snowflake guildId, Action> callback = null, Action error = null)
{
+ if (!guildId.IsValid()) throw new InvalidSnowflakeException(nameof(guildId));
client.Bot.Rest.DoRequest($"/applications/{Id}/guilds/{guildId}/commands/permissions", RequestMethod.GET, null, callback, error);
}
@@ -262,6 +265,7 @@ public void GetGuildCommandPermissions(DiscordClient client, Snowflake guildId,
/// Callback when an error occurs with error information
public void BatchEditCommandPermissions(DiscordClient client, Snowflake guildId, List permissions, Action callback = null, Action error = null)
{
+ if (!guildId.IsValid()) throw new InvalidSnowflakeException(nameof(guildId));
client.Bot.Rest.DoRequest($"/applications/{Id}/guilds/{guildId}/commands/permissions", RequestMethod.PUT, permissions, callback, error);
}
}
diff --git a/Oxide.Ext.Discord/Entities/Channels/DiscordChannel.cs b/Oxide.Ext.Discord/Entities/Channels/DiscordChannel.cs
index 31f685426..37a360d1d 100644
--- a/Oxide.Ext.Discord/Entities/Channels/DiscordChannel.cs
+++ b/Oxide.Ext.Discord/Entities/Channels/DiscordChannel.cs
@@ -9,6 +9,7 @@
using Oxide.Ext.Discord.Entities.Messages.Embeds;
using Oxide.Ext.Discord.Entities.Permissions;
using Oxide.Ext.Discord.Entities.Users;
+using Oxide.Ext.Discord.Exceptions;
using Oxide.Ext.Discord.Helpers;
using Oxide.Ext.Discord.Helpers.Cdn;
using Oxide.Ext.Discord.Helpers.Converters;
@@ -106,7 +107,7 @@ public class DiscordChannel : ISnowflakeEntity
public Hash Recipients { get; set; }
///
- /// icon hash
+ /// icon hash of the group DM
///
[JsonProperty("icon")]
public string Icon { get; set; }
@@ -201,7 +202,7 @@ public Hash ThreadMembers
if (Type != ChannelType.GuildPublicThread && Type != ChannelType.GuildPrivateThread)
{
- throw new Exception("Trying to access ThreadMembers on channel that is not a thread");
+ throw new InvalidChannelException("Trying to access ThreadMembers on channel that is not a thread");
}
return _threadMembers = new Hash();
@@ -230,6 +231,7 @@ public Hash ThreadMembers
/// Callback when an error occurs with error information
public static void CreateGuildChannel(DiscordClient client, Snowflake guildId, ChannelCreate channel, Action callback = null, Action error = null)
{
+ if (!guildId.IsValid()) throw new InvalidSnowflakeException(nameof(guildId));
client.Bot.Rest.DoRequest($"/guilds/{guildId}/channels", RequestMethod.POST, channel, callback, error);
}
@@ -244,6 +246,7 @@ public static void CreateGuildChannel(DiscordClient client, Snowflake guildId, C
/// Callback when an error occurs with error information
public static void GetChannel(DiscordClient client, Snowflake channelId, Action callback = null, Action error = null)
{
+ if (!channelId.IsValid()) throw new InvalidSnowflakeException(nameof(channelId));
client.Bot.Rest.DoRequest($"/channels/{channelId}", RequestMethod.GET, null, callback, error);
}
@@ -327,6 +330,7 @@ public void GetChannelMessages(DiscordClient client, ChannelMessagesRequest requ
/// Callback when an error occurs with error information
public void GetChannelMessage(DiscordClient client, Snowflake messageId, Action callback = null, Action error = null)
{
+ if (!messageId.IsValid()) throw new InvalidSnowflakeException(nameof(messageId));
client.Bot.Rest.DoRequest($"/channels/{Id}/messages/{messageId}", RequestMethod.GET, null, callback, error);
}
@@ -343,6 +347,7 @@ public void GetChannelMessage(DiscordClient client, Snowflake messageId, Action<
public void CreateMessage(DiscordClient client, MessageCreate message, Action callback = null, Action error = null)
{
message.Validate();
+ message.ValidateChannelMessage();
client.Bot.Rest.DoRequest($"/channels/{Id}/messages", RequestMethod.POST, message, callback, error);
}
@@ -465,6 +470,7 @@ public void EditChannelPermissions(DiscordClient client, Overwrite overwrite, Ac
/// Callback when an error occurs with error information
public void EditChannelPermissions(DiscordClient client, Snowflake overwriteId, PermissionFlags? allow, PermissionFlags? deny, PermissionType type, Action callback = null, Action error = null)
{
+ if (!overwriteId.IsValid()) throw new InvalidSnowflakeException(nameof(overwriteId));
Overwrite overwrite = new Overwrite
{
Id = overwriteId,
@@ -490,7 +496,7 @@ public void GetChannelInvites(DiscordClient client, Action>
{
if (Type == ChannelType.Dm || Type == ChannelType.GroupDm)
{
- throw new Exception("You can only get channel invites for guild channels.");
+ throw new InvalidChannelException("You can only get channel invites for guild channels.");
}
client.Bot.Rest.DoRequest($"/channels/{Id}/invites", RequestMethod.GET, null, callback, error);
@@ -535,6 +541,7 @@ public void CreateChannelInvite(DiscordClient client, ChannelInvite invite, Acti
/// Callback when an error occurs with error information
public void DeleteChannelPermission(DiscordClient client, Snowflake overwriteId, Action callback = null, Action error = null)
{
+ if (!overwriteId.IsValid()) throw new InvalidSnowflakeException(nameof(overwriteId));
client.Bot.Rest.DoRequest($"/channels/{Id}/permissions/{overwriteId}", RequestMethod.DELETE, null, callback, error);
}
@@ -549,6 +556,7 @@ public void DeleteChannelPermission(DiscordClient client, Snowflake overwriteId,
/// Callback when an error occurs with error information
public void FollowNewsChannel(DiscordClient client, Snowflake webhookChannelId, Action callback = null, Action error = null)
{
+ if (!webhookChannelId.IsValid()) throw new InvalidSnowflakeException(nameof(webhookChannelId));
client.Bot.Rest.DoRequest($"/channels/{Id}/followers?webhook_channel_id={webhookChannelId}", RequestMethod.POST, null, callback, error);
}
@@ -600,6 +608,7 @@ public void GetPinnedMessages(DiscordClient client, Action>
/// Callback when an error occurs with error information
public void GroupDmAddRecipient(DiscordClient client, Snowflake userId, string accessToken, string nick, Action callback = null, Action error = null)
{
+ if (!userId.IsValid()) throw new InvalidSnowflakeException(nameof(userId));
Dictionary data = new Dictionary()
{
["access_token"] = accessToken,
@@ -619,6 +628,7 @@ public void GroupDmAddRecipient(DiscordClient client, Snowflake userId, string a
/// Callback when an error occurs with error information
public void GroupDmRemoveRecipient(DiscordClient client, Snowflake userId, Action callback = null, Action error = null)
{
+ if (!userId.IsValid()) throw new InvalidSnowflakeException(nameof(userId));
client.Bot.Rest.DoRequest($"/channels/{Id}/recipients/{userId}", RequestMethod.DELETE, null, callback, error);
}
@@ -633,6 +643,7 @@ public void GroupDmRemoveRecipient(DiscordClient client, Snowflake userId, Actio
/// Callback when an error occurs with error information
public void StartThreadWithMessage(DiscordClient client, Snowflake messageId, ThreadCreate create, Action callback = null, Action error = null)
{
+ if (!messageId.IsValid()) throw new InvalidSnowflakeException(nameof(messageId));
client.Bot.Rest.DoRequest($"/channels/{Id}/messages/{messageId}/threads", RequestMethod.POST, create, callback, error);
}
@@ -672,6 +683,7 @@ public void JoinThread(DiscordClient client, Action callback = null, ActionCallback when an error occurs with error information
public void AddThreadMember(DiscordClient client, Snowflake userId, Action callback = null, Action error = null)
{
+ if (!userId.IsValid()) throw new InvalidSnowflakeException(nameof(userId));
client.Bot.Rest.DoRequest($"/channels/{Id}/thread-members/{userId}", RequestMethod.PUT, null, callback, error);
}
@@ -698,6 +710,7 @@ public void LeaveThread(DiscordClient client, Action callback = null, ActionCallback when an error occurs with error information
public void RemoveThreadMember(DiscordClient client, Snowflake userId, Action callback = null, Action error = null)
{
+ if (!userId.IsValid()) throw new InvalidSnowflakeException(nameof(userId));
client.Bot.Rest.DoRequest($"/channels/{Id}/thread-members/{userId}", RequestMethod.DELETE, null, callback, error);
}
@@ -712,6 +725,7 @@ public void RemoveThreadMember(DiscordClient client, Snowflake userId, Action ca
/// Callback when an error occurs with error information
public void GetThreadMember(DiscordClient client, Snowflake userId, Action callback = null, Action error = null)
{
+ if (!userId.IsValid()) throw new InvalidSnowflakeException(nameof(userId));
client.Bot.Rest.DoRequest($"/channels/{Id}/thread-members/{userId}", RequestMethod.GET, null, callback, error);
}
diff --git a/Oxide.Ext.Discord/Entities/Channels/Stages/PrivacyLevel.cs b/Oxide.Ext.Discord/Entities/Channels/Stages/PrivacyLevel.cs
index c69093583..475e0f8ca 100644
--- a/Oxide.Ext.Discord/Entities/Channels/Stages/PrivacyLevel.cs
+++ b/Oxide.Ext.Discord/Entities/Channels/Stages/PrivacyLevel.cs
@@ -1,3 +1,4 @@
+using System;
using System.ComponentModel;
namespace Oxide.Ext.Discord.Entities.Channels.Stages
@@ -8,8 +9,9 @@ namespace Oxide.Ext.Discord.Entities.Channels.Stages
public enum PrivacyLevel
{
///
- /// The Stage instance is visible publicly, such as on Stage discovery.
+ /// The Stage instance is visible publicly. (deprecated)
///
+ [Obsolete("Deprecated by Discord")]
[Description("PUBLIC")]
Public = 1,
diff --git a/Oxide.Ext.Discord/Entities/Channels/Stages/StageInstance.cs b/Oxide.Ext.Discord/Entities/Channels/Stages/StageInstance.cs
index 3c6861b41..7a4b2b755 100644
--- a/Oxide.Ext.Discord/Entities/Channels/Stages/StageInstance.cs
+++ b/Oxide.Ext.Discord/Entities/Channels/Stages/StageInstance.cs
@@ -2,6 +2,7 @@
using System.Collections.Generic;
using Newtonsoft.Json;
using Oxide.Ext.Discord.Entities.Api;
+using Oxide.Ext.Discord.Exceptions;
using Oxide.Ext.Discord.Interfaces;
namespace Oxide.Ext.Discord.Entities.Channels.Stages
@@ -37,8 +38,9 @@ public class StageInstance : ISnowflakeEntity
public PrivacyLevel PrivacyLevel { get; set; }
///
- /// Whether or not Stage discovery is disabled
+ /// Whether or not Stage discovery is disabled (deprecated)
///
+ [Obsolete("Deprecated by discord")]
[JsonProperty("discoverable_disabled")]
public bool DiscoverableDisabled { get; set; }
@@ -61,6 +63,7 @@ public class StageInstance : ISnowflakeEntity
/// Callback when an error occurs with error information
public static void CreateStageInstance(DiscordClient client, Snowflake channelId, string topic, PrivacyLevel privacyLevel = PrivacyLevel.GuildOnly, Action callback = null, Action error = null)
{
+ if (!channelId.IsValid()) throw new InvalidSnowflakeException(nameof(channelId));
Dictionary data = new Dictionary
{
["channel_id"] = channelId,
@@ -81,6 +84,7 @@ public static void CreateStageInstance(DiscordClient client, Snowflake channelId
/// Callback when an error occurs with error information
public static void GetStageInstance(DiscordClient client, Snowflake channelId, Action callback = null, Action error = null)
{
+ if (!channelId.IsValid()) throw new InvalidSnowflakeException(nameof(channelId));
client.Bot.Rest.DoRequest($"/stage-instances/{channelId}", RequestMethod.GET, null, callback, error);
}
diff --git a/Oxide.Ext.Discord/Entities/Channels/Threads/ThreadMetadata.cs b/Oxide.Ext.Discord/Entities/Channels/Threads/ThreadMetadata.cs
index ea1a06371..efb60615f 100644
--- a/Oxide.Ext.Discord/Entities/Channels/Threads/ThreadMetadata.cs
+++ b/Oxide.Ext.Discord/Entities/Channels/Threads/ThreadMetadata.cs
@@ -40,5 +40,11 @@ public class ThreadMetadata
///
[JsonProperty("invitable")]
public bool? Invitable { get; set; }
+
+ ///
+ /// Timestamp when the thread was created; only populated for threads created after 2022-01-09
+ ///
+ [JsonProperty("create_timestamp")]
+ public DateTime? CreateTimestamp { get; set; }
}
}
\ No newline at end of file
diff --git a/Oxide.Ext.Discord/Entities/Guilds/DiscordGuild.cs b/Oxide.Ext.Discord/Entities/Guilds/DiscordGuild.cs
index 74f5078c2..a9ad534f7 100644
--- a/Oxide.Ext.Discord/Entities/Guilds/DiscordGuild.cs
+++ b/Oxide.Ext.Discord/Entities/Guilds/DiscordGuild.cs
@@ -15,6 +15,7 @@
using Oxide.Ext.Discord.Entities.Stickers;
using Oxide.Ext.Discord.Entities.Users;
using Oxide.Ext.Discord.Entities.Voice;
+using Oxide.Ext.Discord.Exceptions;
using Oxide.Ext.Discord.Helpers.Cdn;
using Oxide.Ext.Discord.Helpers.Converters;
using Oxide.Ext.Discord.Interfaces;
@@ -565,6 +566,7 @@ public static void CreateGuild(DiscordClient client, GuildCreate create, Action<
/// Callback when an error occurs with error information
public static void GetGuild(DiscordClient client, Snowflake guildId, Action callback = null, Action error = null)
{
+ if (!guildId.IsValid()) throw new InvalidSnowflakeException(nameof(guildId));
client.Bot.Rest.DoRequest($"/guilds/{guildId}", RequestMethod.GET, null, callback, error);
}
@@ -578,6 +580,7 @@ public static void GetGuild(DiscordClient client, Snowflake guildId, ActionCallback when an error occurs with error information
public static void GetGuildPreview(DiscordClient client, Snowflake guildId, Action callback = null, Action error = null)
{
+ if (!guildId.IsValid()) throw new InvalidSnowflakeException(nameof(guildId));
client.Bot.Rest.DoRequest($"/guilds/{guildId}/preview", RequestMethod.GET, null, callback, error);
}
@@ -671,6 +674,7 @@ public void ListActiveThreads(DiscordClient client, Action> cal
/// Callback when an error occurs with error information
public void GetGuildMember(DiscordClient client, Snowflake userId, Action callback = null, Action error = null)
{
+ if (!userId.IsValid()) throw new InvalidSnowflakeException(nameof(userId));
client.Bot.Rest.DoRequest($"/guilds/{Id}/members/{userId}", RequestMethod.GET, null, callback, error);
}
@@ -741,6 +745,7 @@ public void AddGuildMember(DiscordClient client, GuildMember member, string acce
/// Callback when an error occurs with error information
public void AddGuildMember(DiscordClient client, Snowflake userId, GuildMemberAdd member, Action callback = null, Action error = null)
{
+ if (!userId.IsValid()) throw new InvalidSnowflakeException(nameof(userId));
client.Bot.Rest.DoRequest($"/guilds/{Id}/members/{userId}", RequestMethod.PUT, member, callback, error);
}
@@ -755,6 +760,7 @@ public void AddGuildMember(DiscordClient client, Snowflake userId, GuildMemberAd
/// Callback when an error occurs with error information
public void ModifyGuildMember(DiscordClient client, Snowflake userId, GuildMemberUpdate update, Action callback = null, Action error = null)
{
+ if (!userId.IsValid()) throw new InvalidSnowflakeException(nameof(userId));
client.Bot.Rest.DoRequest($"/guilds/{Id}/members/{userId}", RequestMethod.PATCH, update, callback, error);
}
@@ -769,6 +775,7 @@ public void ModifyGuildMember(DiscordClient client, Snowflake userId, GuildMembe
/// Callback when an error occurs with error information
public void ModifyUsersNick(DiscordClient client, Snowflake userId, string nick, Action callback = null, Action error = null)
{
+ if (!userId.IsValid()) throw new InvalidSnowflakeException(nameof(userId));
GuildMemberUpdate update = new GuildMemberUpdate
{
Nick = nick
@@ -838,6 +845,8 @@ public void ModifyCurrentUsersNick(DiscordClient client, string nick, ActionCallback when an error occurs with error information
public void AddGuildMemberRole(DiscordClient client, Snowflake userId, Snowflake roleId, Action callback = null, Action error = null)
{
+ if (!userId.IsValid()) throw new InvalidSnowflakeException(nameof(userId));
+ if (!roleId.IsValid()) throw new InvalidSnowflakeException(nameof(roleId));
client.Bot.Rest.DoRequest($"/guilds/{Id}/members/{userId}/roles/{roleId}", RequestMethod.PUT, null, callback, error);
}
@@ -865,6 +874,8 @@ public void AddGuildMemberRole(DiscordClient client, Snowflake userId, Snowflake
/// Callback when an error occurs with error information
public void RemoveGuildMemberRole(DiscordClient client, Snowflake userId, Snowflake roleId, Action callback = null, Action error = null)
{
+ if (!userId.IsValid()) throw new InvalidSnowflakeException(nameof(userId));
+ if (!roleId.IsValid()) throw new InvalidSnowflakeException(nameof(roleId));
client.Bot.Rest.DoRequest($"/guilds/{Id}/members/{userId}/roles/{roleId}", RequestMethod.DELETE, null, callback, error);
}
@@ -890,6 +901,7 @@ public void RemoveGuildMemberRole(DiscordClient client, Snowflake userId, Snowfl
/// Callback when an error occurs with error information
public void RemoveGuildMember(DiscordClient client, Snowflake userId, Action callback = null, Action error = null)
{
+ if (!userId.IsValid()) throw new InvalidSnowflakeException(nameof(userId));
client.Bot.Rest.DoRequest($"/guilds/{Id}/members/{userId}", RequestMethod.DELETE, null, callback, error);
}
@@ -916,6 +928,7 @@ public void GetGuildBans(DiscordClient client, Action> callback =
/// Callback when an error occurs with error information
public void GetGuildBan(DiscordClient client, Snowflake userId, Action callback = null, Action error = null)
{
+ if (!userId.IsValid()) throw new InvalidSnowflakeException(nameof(userId));
client.Bot.Rest.DoRequest($"/guilds/{Id}/bans/{userId}", RequestMethod.GET, null, callback, error);
}
@@ -943,6 +956,7 @@ public void GetGuildBan(DiscordClient client, Snowflake userId, Action
/// Callback when an error occurs with error information
public void CreateGuildBan(DiscordClient client, Snowflake userId, GuildBanCreate ban, Action callback = null, Action error = null)
{
+ if (!userId.IsValid()) throw new InvalidSnowflakeException(nameof(userId));
client.Bot.Rest.DoRequest($"/guilds/{Id}/bans/{userId}", RequestMethod.PUT, ban, callback, error);
}
@@ -957,6 +971,7 @@ public void CreateGuildBan(DiscordClient client, Snowflake userId, GuildBanCreat
/// Callback when an error occurs with error information
public void RemoveGuildBan(DiscordClient client, Snowflake userId, Action callback = null, Action error = null)
{
+ if (!userId.IsValid()) throw new InvalidSnowflakeException(nameof(userId));
client.Bot.Rest.DoRequest($"/guilds/{Id}/bans/{userId}", RequestMethod.DELETE, null, callback, error);
}
@@ -1027,6 +1042,7 @@ public void ModifyGuildRolePositions(DiscordClient client, ListCallback when an error occurs with error information
public void ModifyGuildRole(DiscordClient client, Snowflake roleId, DiscordRole role, Action callback = null, Action error = null)
{
+ if (!roleId.IsValid()) throw new InvalidSnowflakeException(nameof(roleId));
client.Bot.Rest.DoRequest($"/guilds/{Id}/roles/{roleId}", RequestMethod.PATCH, role, callback, error);
}
@@ -1052,6 +1068,7 @@ public void ModifyGuildRole(DiscordClient client, Snowflake roleId, DiscordRole
/// Callback when an error occurs with error information
public void DeleteGuildRole(DiscordClient client, Snowflake roleId, Action callback = null, Action error = null)
{
+ if (!roleId.IsValid()) throw new InvalidSnowflakeException(nameof(roleId));
client.Bot.Rest.DoRequest($"/guilds/{Id}/roles/{roleId}", RequestMethod.DELETE, null, callback, error);
}
@@ -1154,6 +1171,7 @@ public void GetGuildIntegrations(DiscordClient client, Action>
/// Callback when an error occurs with error information
public void DeleteGuildIntegration(DiscordClient client, Snowflake integrationId, Action callback = null, Action error = null)
{
+ if (!integrationId.IsValid()) throw new InvalidSnowflakeException(nameof(integrationId));
client.Bot.Rest.DoRequest($"/guilds/{Id}/integrations/{integrationId}", RequestMethod.DELETE, null, callback, error);
}
@@ -1198,6 +1216,7 @@ public void GetGuildWidget(DiscordClient client, Action callback =
///
/// Returns the Welcome Screen object for the guild.
+ /// Requires the `MANAGE_GUILD` permission.
///
/// Client to use
/// Callback with welcome screen for the guild
@@ -1316,6 +1335,7 @@ public void DeleteGuildEmoji(DiscordClient client, string emojiId, Action callba
/// Callback when an error occurs with error information
public void ModifyCurrentUserVoiceState(DiscordClient client, Snowflake channelId, bool? suppress = null, DateTime? requestToSpeak = null, Action callback = null, Action error = null)
{
+ if (!channelId.IsValid()) throw new InvalidSnowflakeException(nameof(channelId));
Dictionary data = new Dictionary
{
["channel_id"] = channelId.ToString()
@@ -1346,6 +1366,8 @@ public void ModifyCurrentUserVoiceState(DiscordClient client, Snowflake channelI
/// Callback when an error occurs with error information
public void ModifyUserVoiceState(DiscordClient client, Snowflake userId, Snowflake channelId, bool? suppress = null, Action callback = null, Action error = null)
{
+ if (!userId.IsValid()) throw new InvalidSnowflakeException(nameof(userId));
+ if (!channelId.IsValid()) throw new InvalidSnowflakeException(nameof(channelId));
Dictionary data = new Dictionary
{
["channel_id"] = channelId.ToString()
@@ -1383,6 +1405,7 @@ public void ListGuildStickers(DiscordClient client, Action>
/// Callback when an error occurs with error information
public void GetGuildSticker(DiscordClient client, Snowflake stickerId, Action callback = null, Action error = null)
{
+ if (!stickerId.IsValid()) throw new InvalidSnowflakeException(nameof(stickerId));
client.Bot.Rest.DoRequest($"/guilds/{Id}/stickers/{stickerId}", RequestMethod.GET, null, callback, error);
}
@@ -1427,6 +1450,7 @@ public void ModifyGuildSticker(DiscordClient client, DiscordSticker sticker, Act
/// See Delete Guild Sticker
public void DeleteGuildSticker(DiscordClient client, Snowflake stickerId, Action callback = null, Action error = null)
{
+ if (!stickerId.IsValid()) throw new InvalidSnowflakeException(nameof(stickerId));
client.Bot.Rest.DoRequest($"/guilds/{Id}/stickers/{stickerId}", RequestMethod.DELETE, null, callback, error);
}
#endregion
diff --git a/Oxide.Ext.Discord/Entities/Guilds/GuildPreview.cs b/Oxide.Ext.Discord/Entities/Guilds/GuildPreview.cs
index bba2842c6..22469492f 100644
--- a/Oxide.Ext.Discord/Entities/Guilds/GuildPreview.cs
+++ b/Oxide.Ext.Discord/Entities/Guilds/GuildPreview.cs
@@ -1,6 +1,7 @@
using System.Collections.Generic;
using Newtonsoft.Json;
using Oxide.Ext.Discord.Entities.Emojis;
+using Oxide.Ext.Discord.Entities.Stickers;
namespace Oxide.Ext.Discord.Entities.Guilds
{
@@ -71,5 +72,11 @@ public class GuildPreview
///
[JsonProperty("description")]
public string Description { get; set; }
+
+ ///
+ /// Custom guild stickers
+ ///
+ [JsonProperty("stickers")]
+ public List Stickers { get; set; }
}
}
\ No newline at end of file
diff --git a/Oxide.Ext.Discord/Entities/Guilds/GuildWidget.cs b/Oxide.Ext.Discord/Entities/Guilds/GuildWidget.cs
index 82bd25a66..5eee62dee 100644
--- a/Oxide.Ext.Discord/Entities/Guilds/GuildWidget.cs
+++ b/Oxide.Ext.Discord/Entities/Guilds/GuildWidget.cs
@@ -12,37 +12,37 @@ namespace Oxide.Ext.Discord.Entities.Guilds
public class GuildWidget
{
///
- /// ID of the guild
+ /// Guild id
///
[JsonProperty("id")]
public Snowflake Id { get; set; }
///
- /// Name of the guild
+ /// Guild name (2-100 characters)
///
[JsonProperty("name")]
public string Name { get; set; }
///
- /// Instant invite line for the guild
+ /// Instant invite for the guilds specified widget invite channel
///
[JsonProperty("instant_invite")]
public string InstantInvite { get; set; }
///
- /// List of guild channels
+ /// Voice and stage channels which are accessible by @everyone
///
[JsonProperty("channels")]
public List Channels { get; set; }
///
- /// List of guild members
+ /// Special widget user objects that includes users presence (Limit 100)
///
[JsonProperty("members")]
public List Members { get; set; }
///
- /// The count of the presences
+ /// Number of online members in this guild
///
[JsonProperty("presence_count")]
public int PresenceCount { get; set; }
diff --git a/Oxide.Ext.Discord/Entities/Guilds/ScheduledEvents/GuildScheduledEvent.cs b/Oxide.Ext.Discord/Entities/Guilds/ScheduledEvents/GuildScheduledEvent.cs
index c7f2804a4..521630fe5 100644
--- a/Oxide.Ext.Discord/Entities/Guilds/ScheduledEvents/GuildScheduledEvent.cs
+++ b/Oxide.Ext.Discord/Entities/Guilds/ScheduledEvents/GuildScheduledEvent.cs
@@ -3,6 +3,7 @@
using Newtonsoft.Json;
using Oxide.Ext.Discord.Entities.Api;
using Oxide.Ext.Discord.Entities.Users;
+using Oxide.Ext.Discord.Exceptions;
using Oxide.Ext.Discord.Interfaces;
namespace Oxide.Ext.Discord.Entities.Guilds.ScheduledEvents
{
@@ -113,6 +114,7 @@ public class GuildScheduledEvent : ISnowflakeEntity
/// Callback when an error occurs with error information
public static void ListForGuild(DiscordClient client, Snowflake guildId, ScheduledEventLookup lookup = null, Action> callback = null, Action error = null)
{
+ if (!guildId.IsValid()) throw new InvalidSnowflakeException(nameof(guildId));
client.Bot.Rest.DoRequest($"/guilds/{guildId}/scheduled-events{lookup?.ToQueryString()}", RequestMethod.GET, null, callback, error);
}
@@ -128,6 +130,7 @@ public static void ListForGuild(DiscordClient client, Snowflake guildId, Schedul
/// Callback when an error occurs with error information
public static void Create(DiscordClient client, Snowflake guildId, ScheduledEventCreate create, Action callback = null, Action error = null)
{
+ if (!guildId.IsValid()) throw new InvalidSnowflakeException(nameof(guildId));
client.Bot.Rest.DoRequest($"/guilds/{guildId}/scheduled-events", RequestMethod.POST, create, callback, error);
}
@@ -144,6 +147,8 @@ public static void Create(DiscordClient client, Snowflake guildId, ScheduledEven
/// Callback when an error occurs with error information
public static void Get(DiscordClient client, Snowflake guildId, Snowflake eventId, ScheduledEventLookup lookup = null, Action callback = null, Action error = null)
{
+ if (!guildId.IsValid()) throw new InvalidSnowflakeException(nameof(guildId));
+ if (!eventId.IsValid()) throw new InvalidSnowflakeException(nameof(eventId));
client.Bot.Rest.DoRequest($"/guilds/{guildId}/scheduled-events/{eventId}{lookup?.ToQueryString()}", RequestMethod.GET, null, callback, error);
}
@@ -160,6 +165,8 @@ public static void Get(DiscordClient client, Snowflake guildId, Snowflake eventI
/// Callback when an error occurs with error information
public void Modify(DiscordClient client, Snowflake guildId, Snowflake eventId, ScheduledEventUpdate update, Action callback = null, Action error = null)
{
+ if (!guildId.IsValid()) throw new InvalidSnowflakeException(nameof(guildId));
+ if (!eventId.IsValid()) throw new InvalidSnowflakeException(nameof(eventId));
client.Bot.Rest.DoRequest($"/guilds/{guildId}/scheduled-events/{eventId}", RequestMethod.PATCH, update, callback, error);
}
@@ -174,6 +181,8 @@ public void Modify(DiscordClient client, Snowflake guildId, Snowflake eventId, S
/// Callback when an error occurs with error information
public void Delete(DiscordClient client, Snowflake guildId, Snowflake eventId, Action callback = null, Action error = null)
{
+ if (!guildId.IsValid()) throw new InvalidSnowflakeException(nameof(guildId));
+ if (!eventId.IsValid()) throw new InvalidSnowflakeException(nameof(eventId));
client.Bot.Rest.DoRequest($"/guilds/{guildId}/scheduled-events/{eventId}", RequestMethod.DELETE, null, callback, error);
}
@@ -191,6 +200,9 @@ public void Delete(DiscordClient client, Snowflake guildId, Snowflake eventId, A
/// Callback when an error occurs with error information
public static void GetUsers(DiscordClient client, Snowflake guildId, Snowflake eventId, ScheduledEventUsersLookup lookup = null, Action> callback = null, Action error = null)
{
+ if (!guildId.IsValid()) throw new InvalidSnowflakeException(nameof(guildId));
+ if (!eventId.IsValid()) throw new InvalidSnowflakeException(nameof(eventId));
+
if (lookup?.Limit != null && lookup.Limit.Value > 100)
{
throw new Exception($"{nameof(GuildScheduledEvent)}.{nameof(GetUsers)} Validation Error: {nameof(ScheduledEventUsersLookup)}.{nameof(ScheduledEventUsersLookup.Limit)} cannot be greater than 100");
diff --git a/Oxide.Ext.Discord/Entities/Interactions/ApplicationCommands/CommandFollowupCreate.cs b/Oxide.Ext.Discord/Entities/Interactions/ApplicationCommands/CommandFollowupCreate.cs
index ea7785b58..948753756 100644
--- a/Oxide.Ext.Discord/Entities/Interactions/ApplicationCommands/CommandFollowupCreate.cs
+++ b/Oxide.Ext.Discord/Entities/Interactions/ApplicationCommands/CommandFollowupCreate.cs
@@ -1,20 +1,16 @@
+using System;
using Newtonsoft.Json;
-using Oxide.Ext.Discord.Entities.Messages;
using Oxide.Ext.Discord.Entities.Webhooks;
namespace Oxide.Ext.Discord.Entities.Interactions.ApplicationCommands
{
///
- /// Represents a Command Followup within discord.
+ /// Represents a Command Followup within discord.
///
[JsonObject(MemberSerialization = MemberSerialization.OptIn)]
+ [Obsolete("Replaced with WebhookCreateMessage. This will be removed in the May 2022 update.")]
public class CommandFollowupCreate : WebhookCreateMessage
{
- ///
- /// Callback data flags
- /// Set to 64 to make your response ephemeral
- ///
- [JsonProperty("flags")]
- public MessageFlags? Flags { get; set; }
+
}
}
\ No newline at end of file
diff --git a/Oxide.Ext.Discord/Entities/Interactions/ApplicationCommands/CommandOption.cs b/Oxide.Ext.Discord/Entities/Interactions/ApplicationCommands/CommandOption.cs
index d87ecc55e..b3af75e08 100644
--- a/Oxide.Ext.Discord/Entities/Interactions/ApplicationCommands/CommandOption.cs
+++ b/Oxide.Ext.Discord/Entities/Interactions/ApplicationCommands/CommandOption.cs
@@ -36,7 +36,7 @@ public class CommandOption
public bool? Required { get; set; }
///
- /// Enable autocomplete interactions for this option
+ /// If autocomplete interactions are enabled for this `STRING`, `INTEGER`, or `NUMBER` type option
///
[JsonProperty("autocomplete")]
public bool? Autocomplete { get; set; }
diff --git a/Oxide.Ext.Discord/Entities/Interactions/ApplicationCommands/DiscordApplicationCommand.cs b/Oxide.Ext.Discord/Entities/Interactions/ApplicationCommands/DiscordApplicationCommand.cs
index aab760c57..006a282e3 100644
--- a/Oxide.Ext.Discord/Entities/Interactions/ApplicationCommands/DiscordApplicationCommand.cs
+++ b/Oxide.Ext.Discord/Entities/Interactions/ApplicationCommands/DiscordApplicationCommand.cs
@@ -2,6 +2,7 @@
using System;
using System.Collections.Generic;
using Oxide.Ext.Discord.Entities.Api;
+using Oxide.Ext.Discord.Exceptions;
namespace Oxide.Ext.Discord.Entities.Interactions.ApplicationCommands
{
@@ -115,6 +116,7 @@ public void Delete(DiscordClient client, Action callback = null, ActionCallback when an error occurs with error information
public void GetPermissions(DiscordClient client, Snowflake guildId, Action callback = null, Action error = null)
{
+ if (!guildId.IsValid()) throw new InvalidSnowflakeException(nameof(guildId));
client.Bot.Rest.DoRequest($"/applications/{ApplicationId}/guilds/{guildId}/commands/{Id}/permissions", RequestMethod.GET, null, callback, error);
}
@@ -130,6 +132,7 @@ public void GetPermissions(DiscordClient client, Snowflake guildId, ActionCallback when an error occurs with error information
public void EditPermissions(DiscordClient client, Snowflake guildId, List permissions, Action callback = null, Action error = null)
{
+ if (!guildId.IsValid()) throw new InvalidSnowflakeException(nameof(guildId));
Dictionary data = new Dictionary
{
["permissions"] = permissions
diff --git a/Oxide.Ext.Discord/Entities/Interactions/DiscordInteraction.cs b/Oxide.Ext.Discord/Entities/Interactions/DiscordInteraction.cs
index 58febf87f..04aefdf08 100644
--- a/Oxide.Ext.Discord/Entities/Interactions/DiscordInteraction.cs
+++ b/Oxide.Ext.Discord/Entities/Interactions/DiscordInteraction.cs
@@ -6,6 +6,7 @@
using Oxide.Ext.Discord.Entities.Messages;
using Oxide.Ext.Discord.Entities.Users;
using Oxide.Ext.Discord.Entities.Webhooks;
+using Oxide.Ext.Discord.Exceptions;
namespace Oxide.Ext.Discord.Entities.Interactions
{
@@ -84,6 +85,20 @@ public class DiscordInteraction
///
[JsonProperty("message")]
public DiscordMessage Message { get; set; }
+
+ ///
+ /// The selected language of the invoking user
+ /// Discord Locale Values
+ ///
+ [JsonProperty("locale")]
+ public string Locale { get; set; }
+
+ ///
+ /// The guild's preferred locale, if invoked in a guild
+ /// Discord Locale Values
+ ///
+ [JsonProperty("guild_locale")]
+ public string GuildLocale { get; set; }
private InteractionDataParsed _parsed;
@@ -102,6 +117,8 @@ public class DiscordInteraction
/// Callback when an error occurs with error information
public void CreateInteractionResponse(DiscordClient client, InteractionResponse response, Action callback = null, Action error = null)
{
+ response.Data?.Validate();
+
client.Bot.Rest.DoRequest($"/interactions/{Id}/{Token}/callback", RequestMethod.POST, response, callback, error);
}
@@ -138,9 +155,10 @@ public void DeleteOriginalInteractionResponse(DiscordClient client, Action callb
/// Message to follow up with
/// Callback with the message
/// Callback when an error occurs with error information
- public void CreateFollowUpMessage(DiscordClient client, CommandFollowupCreate message, Action callback = null, Action error = null)
+ public void CreateFollowUpMessage(DiscordClient client, WebhookCreateMessage message, Action callback = null, Action error = null)
{
message.Validate();
+ message.ValidateInteractionMessage();
client.Bot.Rest.DoRequest($"/webhooks/{ApplicationId}/{Token}", RequestMethod.POST, message, callback, error);
}
@@ -155,6 +173,7 @@ public void CreateFollowUpMessage(DiscordClient client, CommandFollowupCreate me
/// Callback when an error occurs with error information
public void EditFollowUpMessage(DiscordClient client, Snowflake messageId, CommandFollowupUpdate edit, Action callback = null, Action error = null)
{
+ if (!messageId.IsValid()) throw new InvalidSnowflakeException(nameof(messageId));
client.Bot.Rest.DoRequest($"/webhooks/{ApplicationId}/{Token}/messages/{messageId}", RequestMethod.PATCH, edit, callback, error);
}
@@ -168,6 +187,7 @@ public void EditFollowUpMessage(DiscordClient client, Snowflake messageId, Comma
/// Callback when an error occurs with error information
public void DeleteFollowUpMessage(DiscordClient client, Snowflake messageId, Action callback = null, Action error = null)
{
+ if (!messageId.IsValid()) throw new InvalidSnowflakeException(nameof(messageId));
client.Bot.Rest.DoRequest($"/webhooks/{ApplicationId}/{Token}/messages/{messageId}", RequestMethod.DELETE, null, callback, error);
}
}
diff --git a/Oxide.Ext.Discord/Entities/Interactions/InteractionCallbackData.cs b/Oxide.Ext.Discord/Entities/Interactions/InteractionCallbackData.cs
index ec889e8a5..4680dfb41 100644
--- a/Oxide.Ext.Discord/Entities/Interactions/InteractionCallbackData.cs
+++ b/Oxide.Ext.Discord/Entities/Interactions/InteractionCallbackData.cs
@@ -2,7 +2,9 @@
using Newtonsoft.Json;
using Oxide.Ext.Discord.Entities.Interactions.MessageComponents;
using Oxide.Ext.Discord.Entities.Messages;
+using Oxide.Ext.Discord.Entities.Messages.AllowedMentions;
using Oxide.Ext.Discord.Entities.Messages.Embeds;
+using Oxide.Ext.Discord.Exceptions;
namespace Oxide.Ext.Discord.Entities.Interactions
{
@@ -35,7 +37,7 @@ public class InteractionCallbackData
/// Allowed mentions
///
[JsonProperty("allowed_mentions")]
- public bool AllowedMentions { get; set; }
+ public AllowedMention AllowedMentions { get; set; }
///
/// A developer-defined identifier for the interactable form
@@ -67,5 +69,18 @@ public class InteractionCallbackData
///
[JsonProperty("attachments")]
public List Attachments { get; set; }
+
+ internal void Validate()
+ {
+ if (!Flags.HasValue)
+ {
+ return;
+ }
+
+ if ((Flags.Value & ~MessageFlags.SuppressEmbeds) != 0)
+ {
+ throw new InvalidInteractionResponseException("Invalid Message Flags Used for Interaction Message. Only supported flags are MessageFlags.SuppressEmbeds");
+ }
+ }
}
}
\ No newline at end of file
diff --git a/Oxide.Ext.Discord/Entities/Interactions/InteractionDataParsed.cs b/Oxide.Ext.Discord/Entities/Interactions/InteractionDataParsed.cs
index db4a50197..5ea57cc32 100644
--- a/Oxide.Ext.Discord/Entities/Interactions/InteractionDataParsed.cs
+++ b/Oxide.Ext.Discord/Entities/Interactions/InteractionDataParsed.cs
@@ -6,6 +6,7 @@
using Oxide.Ext.Discord.Entities.Interactions.MessageComponents;
using Oxide.Ext.Discord.Entities.Permissions;
using Oxide.Ext.Discord.Entities.Users;
+using Oxide.Ext.Discord.Helpers;
using Oxide.Plugins;
namespace Oxide.Ext.Discord.Entities.Interactions
@@ -33,26 +34,26 @@ public class InteractionDataParsed
///
/// Parsed command for the interaction if an application command
///
- public string Command;
-
+ public readonly string Command;
+
///
/// Command group for the interaction if Command Type if an application command
/// Null if command group is not used for the command.
/// Defaults to empty string if command does not have a command group
///
- public string CommandGroup = string.Empty;
+ public string CommandGroup { get; private set; } = string.Empty;
///
/// Sub Command for the interaction if Command Typ if an application command
/// Null if sub command group is not used for the command.
/// Defaults to empty string if command does not have sub command
///
- public string SubCommand = string.Empty;
+ public string SubCommand { get; private set; } = string.Empty;
///
/// Interaction Data Supplied Args if Command Type if an application command
///
- public InteractionDataArgs Args;
+ public InteractionDataArgs Args { get; private set; }
///
/// Returns true if this command was used in a guild; false otherwise.
@@ -69,6 +70,16 @@ public class InteractionDataParsed
///
public readonly List SelectMenuValues;
+ ///
+ /// Discord User's locale converted to oxide lang locale
+ ///
+ public readonly string UserOxideLocale;
+
+ ///
+ /// Discord Guild's locale converted to oxide lang locale
+ ///
+ public readonly string GuildOxideLocale;
+
///
/// Constructor for the data parser.
///
@@ -94,6 +105,9 @@ public InteractionDataParsed(DiscordInteraction interaction)
{
return;
}
+
+ UserOxideLocale = LocaleConverter.GetOxideLocale(interaction.Locale);
+ GuildOxideLocale = LocaleConverter.GetOxideLocale(interaction.GuildLocale);
//Parse the arguments for the application command
ParseCommand(Data.Options);
diff --git a/Oxide.Ext.Discord/Entities/Interactions/MessageComponents/InputTextComponent.cs b/Oxide.Ext.Discord/Entities/Interactions/MessageComponents/InputTextComponent.cs
index b809e930d..eefe90cfc 100644
--- a/Oxide.Ext.Discord/Entities/Interactions/MessageComponents/InputTextComponent.cs
+++ b/Oxide.Ext.Discord/Entities/Interactions/MessageComponents/InputTextComponent.cs
@@ -16,20 +16,20 @@ public class InputTextComponent : BaseInteractableComponent
///
/// Text that appears on top of the input text field, max 80 characters
///
- [JsonProperty("placeholder")]
+ [JsonProperty("label")]
public string Label { get; set; }
///
/// The minimum length of the text input
///
[JsonProperty("min_length")]
- public int MinLength { get; set; }
+ public int? MinLength { get; set; }
///
/// The maximum length of the text input
///
[JsonProperty("max_length")]
- public int MaxLength { get; set; }
+ public int? MaxLength { get; set; }
///
/// The placeholder for the text input field
@@ -37,6 +37,18 @@ public class InputTextComponent : BaseInteractableComponent
[JsonProperty("placeholder")]
public string Placeholder { get; set; }
+ ///
+ /// The pre-filled value for text input
+ ///
+ [JsonProperty("value")]
+ public string Value { get; set; }
+
+ ///
+ /// Is the Input Text Required to be filled out
+ ///
+ [JsonProperty("required")]
+ public bool? Required { get; set; }
+
///
/// Input Text Constructor
///
diff --git a/Oxide.Ext.Discord/Entities/Interactions/MessageComponents/MessageComponentType.cs b/Oxide.Ext.Discord/Entities/Interactions/MessageComponents/MessageComponentType.cs
index d8ef2645f..00303c139 100644
--- a/Oxide.Ext.Discord/Entities/Interactions/MessageComponents/MessageComponentType.cs
+++ b/Oxide.Ext.Discord/Entities/Interactions/MessageComponents/MessageComponentType.cs
@@ -23,6 +23,6 @@ public enum MessageComponentType
///
/// A text box for inserting written responses
///
- InputText = 3
+ InputText = 4
}
}
\ No newline at end of file
diff --git a/Oxide.Ext.Discord/Entities/Messages/DiscordMessage.cs b/Oxide.Ext.Discord/Entities/Messages/DiscordMessage.cs
index 65d21fc70..028761413 100644
--- a/Oxide.Ext.Discord/Entities/Messages/DiscordMessage.cs
+++ b/Oxide.Ext.Discord/Entities/Messages/DiscordMessage.cs
@@ -11,6 +11,7 @@
using Oxide.Ext.Discord.Entities.Messages.Embeds;
using Oxide.Ext.Discord.Entities.Stickers;
using Oxide.Ext.Discord.Entities.Users;
+using Oxide.Ext.Discord.Exceptions;
using Oxide.Ext.Discord.Helpers;
using Oxide.Ext.Discord.Helpers.Converters;
using Oxide.Ext.Discord.Interfaces;
@@ -241,7 +242,9 @@ public class DiscordMessage : IFileAttachments
/// Callback when an error occurs with error information
public static void CreateMessage(DiscordClient client, Snowflake channelId, MessageCreate message, Action callback = null, Action error = null)
{
+ if (!channelId.IsValid()) throw new InvalidSnowflakeException(nameof(channelId));
message.Validate();
+ message.ValidateChannelMessage();
client.Bot.Rest.DoRequest($"/channels/{channelId}/messages", RequestMethod.POST, message, callback, error);
}
@@ -258,6 +261,7 @@ public static void CreateMessage(DiscordClient client, Snowflake channelId, Mess
/// Callback when an error occurs with error information
public static void CreateMessage(DiscordClient client, Snowflake channelId, string message, Action callback = null, Action error = null)
{
+ if (!channelId.IsValid()) throw new InvalidSnowflakeException(nameof(channelId));
MessageCreate createMessage = new MessageCreate
{
Content = message
@@ -279,6 +283,7 @@ public static void CreateMessage(DiscordClient client, Snowflake channelId, stri
/// Callback when an error occurs with error information
public static void CreateMessage(DiscordClient client, Snowflake channelId, DiscordEmbed embed, Action callback = null, Action error = null)
{
+ if (!channelId.IsValid()) throw new InvalidSnowflakeException(nameof(channelId));
MessageCreate createMessage = new MessageCreate
{
Embeds = new List {embed}
@@ -300,6 +305,7 @@ public static void CreateMessage(DiscordClient client, Snowflake channelId, Disc
/// Callback when an error occurs with error information
public static void CreateMessage(DiscordClient client, Snowflake channelId, List embeds, Action callback = null, Action error = null)
{
+ if (!channelId.IsValid()) throw new InvalidSnowflakeException(nameof(channelId));
MessageCreate createMessage = new MessageCreate
{
Embeds = embeds
@@ -320,6 +326,8 @@ public static void CreateMessage(DiscordClient client, Snowflake channelId, List
/// Callback when an error occurs with error information
public static void GetChannelMessage(DiscordClient client, Snowflake channelId, Snowflake messageId, Action callback = null, Action error = null)
{
+ if (!channelId.IsValid()) throw new InvalidSnowflakeException(nameof(channelId));
+ if (!messageId.IsValid()) throw new InvalidSnowflakeException(nameof(messageId));
client.Bot.Rest.DoRequest($"/channels/{channelId}/messages/{messageId}", RequestMethod.GET, null, callback, error);
}
@@ -339,6 +347,7 @@ public void Reply(DiscordClient client, MessageCreate message, Action embeds, ActionCallback when an error occurs with error information
public void CrossPostMessage(DiscordClient client, Snowflake messageId, Action callback = null, Action error = null)
{
+ if (!messageId.IsValid()) throw new InvalidSnowflakeException(nameof(messageId));
client.Bot.Rest.DoRequest($"/channels/{Id}/messages/{messageId}/crosspost", RequestMethod.POST, null, callback, error);
}
@@ -448,12 +458,7 @@ public void CreateReaction(DiscordClient client, DiscordEmoji emoji, Action call
/// Callback when an error occurs with error information
public void CreateReaction(DiscordClient client, string emoji, Action callback = null, Action error = null)
{
- string emojiError = Validation.ValidateEmoji(emoji);
- if (!string.IsNullOrEmpty(emojiError))
- {
- client.Logger.Error($"{nameof(DiscordMessage)}.{nameof(CreateReaction)} Failed emoji validation for emoji '{emoji}' with error:\n{emojiError}");
- return;
- }
+ Validation.ValidateEmoji(emoji);
client.Bot.Rest.DoRequest($"/channels/{ChannelId}/messages/{Id}/reactions/{emoji}/@me", RequestMethod.PUT, null, callback, error);
}
@@ -483,12 +488,7 @@ public void DeleteOwnReaction(DiscordClient client, DiscordEmoji emoji, Action c
/// Callback when an error occurs with error information
public void DeleteOwnReaction(DiscordClient client, string emoji, Action callback = null, Action error = null)
{
- string emojiError = Validation.ValidateEmoji(emoji);
- if (!string.IsNullOrEmpty(emojiError))
- {
- client.Logger.Error($"{nameof(DiscordMessage)}.{nameof(DeleteOwnReaction)} Failed emoji validation for emoji '{emoji}' with error:\n{emojiError}");
- return;
- }
+ Validation.ValidateEmoji(emoji);
client.Bot.Rest.DoRequest($"/channels/{ChannelId}/messages/{Id}/reactions/{emoji}/@me", RequestMethod.DELETE, null, callback, error);
}
@@ -506,6 +506,7 @@ public void DeleteOwnReaction(DiscordClient client, string emoji, Action callbac
/// Callback when an error occurs with error information
public void DeleteUserReaction(DiscordClient client, DiscordEmoji emoji, Snowflake userId, Action callback = null, Action error = null)
{
+ if (!userId.IsValid()) throw new InvalidSnowflakeException(nameof(userId));
DeleteUserReaction(client, emoji.ToDataString(), userId, callback, error);
}
@@ -522,12 +523,9 @@ public void DeleteUserReaction(DiscordClient client, DiscordEmoji emoji, Snowfla
/// Callback when an error occurs with error information
public void DeleteUserReaction(DiscordClient client, string emoji, Snowflake userId, Action callback = null, Action error = null)
{
- string emojiError = Validation.ValidateEmoji(emoji);
- if (!string.IsNullOrEmpty(emojiError))
- {
- client.Logger.Error($"{nameof(DiscordMessage)}.{nameof(DeleteUserReaction)} Failed emoji validation for emoji '{emoji}' with error:\n{emojiError}");
- return;
- }
+ if (!userId.IsValid()) throw new InvalidSnowflakeException(nameof(userId));
+
+ Validation.ValidateEmoji(emoji);
client.Bot.Rest.DoRequest($"/channels/{ChannelId}/messages/{Id}/reactions/{emoji}/{userId}", RequestMethod.DELETE, null, callback, error);
}
@@ -557,12 +555,7 @@ public void GetReactions(DiscordClient client, DiscordEmoji emoji, ActionCallback when an error occurs with error information
public void GetReactions(DiscordClient client, string emoji, Action> callback = null, Action error = null)
{
- string emojiError = Validation.ValidateEmoji(emoji);
- if (!string.IsNullOrEmpty(emojiError))
- {
- client.Logger.Error($"{nameof(DiscordMessage)}.{nameof(GetReactions)} Failed emoji validation for emoji '{emoji}' with error:\n{emojiError}");
- return;
- }
+ Validation.ValidateEmoji(emoji);
client.Bot.Rest.DoRequest($"/channels/{ChannelId}/messages/{Id}/reactions/{emoji}", RequestMethod.GET, null, callback, error);
}
@@ -606,12 +599,7 @@ public void DeleteAllReactionsForEmoji(DiscordClient client, DiscordEmoji emoji,
/// Callback when an error occurs with error information
public void DeleteAllReactionsForEmoji(DiscordClient client, string emoji, Action callback = null, Action error = null)
{
- string emojiError = Validation.ValidateEmoji(emoji);
- if (!string.IsNullOrEmpty(emojiError))
- {
- client.Logger.Error($"{nameof(DiscordMessage)}.{nameof(DeleteAllReactionsForEmoji)} Failed emoji validation for emoji '{emoji}' with error:\n{emojiError}");
- return;
- }
+ Validation.ValidateEmoji(emoji);
client.Bot.Rest.DoRequest($"/channels/{ChannelId}/messages/{Id}/reactions/{emoji}", RequestMethod.DELETE, null, callback, error);
}
diff --git a/Oxide.Ext.Discord/Entities/Messages/MessageCreate.cs b/Oxide.Ext.Discord/Entities/Messages/MessageCreate.cs
index f54a21425..2d1629f50 100644
--- a/Oxide.Ext.Discord/Entities/Messages/MessageCreate.cs
+++ b/Oxide.Ext.Discord/Entities/Messages/MessageCreate.cs
@@ -1,9 +1,10 @@
-using System;
using System.Collections.Generic;
using Newtonsoft.Json;
using Oxide.Ext.Discord.Entities.Interactions.MessageComponents;
using Oxide.Ext.Discord.Entities.Messages.AllowedMentions;
using Oxide.Ext.Discord.Entities.Messages.Embeds;
+using Oxide.Ext.Discord.Exceptions;
+using Oxide.Ext.Discord.Helpers;
using Oxide.Ext.Discord.Interfaces;
namespace Oxide.Ext.Discord.Entities.Messages
@@ -69,6 +70,12 @@ public class MessageCreate : IFileAttachments
[JsonProperty("attachments")]
public List Attachments { get; set; }
+ ///
+ /// Attachments for the message
+ ///
+ [JsonProperty("flags ")]
+ public MessageFlags? Flags { get; set; }
+
///
/// Attachments for a discord message
///
@@ -83,6 +90,8 @@ public class MessageCreate : IFileAttachments
/// Description for the attachment
public void AddAttachment(string filename, byte[] data, string contentType, string description = null)
{
+ Validation.ValidateFilename(filename);
+
if (FileAttachments == null)
{
FileAttachments = new List();
@@ -101,12 +110,38 @@ internal void Validate()
{
if (string.IsNullOrEmpty(Content) && (Embeds == null || Embeds.Count == 0) && (FileAttachments == null || FileAttachments.Count == 0))
{
- throw new Exception("Invalid Message Create. Discord Messages require Either Content, An Embed, Or a File");
+ throw new InvalidMessageException("Discord Messages require Either Content, An Embed, Or a File");
}
if (!string.IsNullOrEmpty(Content) && Content.Length > 2000)
{
- throw new Exception("Invalid Message Create. Content cannot be more than 2000 characters");
+ throw new InvalidMessageException("Content cannot be more than 2000 characters");
+ }
+ }
+
+ internal void ValidateChannelMessage()
+ {
+ if (!Flags.HasValue)
+ {
+ return;
+ }
+
+ if ((Flags.Value & ~MessageFlags.SuppressEmbeds) != 0)
+ {
+ throw new InvalidMessageException("Invalid Message Flags Used for Channel Message. Only supported flags are MessageFlags.SuppressEmbeds");
+ }
+ }
+
+ internal void ValidateInteractionMessage()
+ {
+ if (!Flags.HasValue)
+ {
+ return;
+ }
+
+ if ((Flags.Value & ~(MessageFlags.SuppressEmbeds | MessageFlags.Ephemeral)) != 0)
+ {
+ throw new InvalidMessageException("Invalid Message Flags Used for Interaction Message. Only supported flags are MessageFlags.SuppressEmbeds, and MessageFlags.Ephemeral");
}
}
}
diff --git a/Oxide.Ext.Discord/Entities/Permissions/DiscordColor.cs b/Oxide.Ext.Discord/Entities/Permissions/DiscordColor.cs
index 2b1cf5294..6022c1200 100644
--- a/Oxide.Ext.Discord/Entities/Permissions/DiscordColor.cs
+++ b/Oxide.Ext.Discord/Entities/Permissions/DiscordColor.cs
@@ -1,6 +1,7 @@
using System;
using System.Globalization;
using Newtonsoft.Json;
+using Oxide.Ext.Discord.Exceptions;
namespace Oxide.Ext.Discord.Entities.Permissions
{
@@ -128,7 +129,7 @@ public DiscordColor(uint color)
{
if (color > 0xFFFFFF)
{
- throw new Exception($"Color '{color}' is outside the valid color range");
+ throw new InvalidDiscordColorException($"Color '{color}' is greater than the max color of 0xFFFFFF");
}
Color = color;
diff --git a/Oxide.Ext.Discord/Entities/Snowflake.cs b/Oxide.Ext.Discord/Entities/Snowflake.cs
index c8d9e2edd..d399f5968 100644
--- a/Oxide.Ext.Discord/Entities/Snowflake.cs
+++ b/Oxide.Ext.Discord/Entities/Snowflake.cs
@@ -240,8 +240,8 @@ public int CompareTo(ulong other)
///
/// Converts snowflake to a string
///
- /// Snowflake to be converted to ulong
- /// Snowflake ID as ulong
+ /// Snowflake to be converted to string
+ /// Snowflake ID as string
public static implicit operator string(Snowflake snowflake) => snowflake.Id.ToString();
///
diff --git a/Oxide.Ext.Discord/Entities/Stickers/DiscordSticker.cs b/Oxide.Ext.Discord/Entities/Stickers/DiscordSticker.cs
index 3283651d5..e81e66f7c 100644
--- a/Oxide.Ext.Discord/Entities/Stickers/DiscordSticker.cs
+++ b/Oxide.Ext.Discord/Entities/Stickers/DiscordSticker.cs
@@ -2,6 +2,7 @@
using Newtonsoft.Json;
using Oxide.Ext.Discord.Entities.Api;
using Oxide.Ext.Discord.Entities.Users;
+using Oxide.Ext.Discord.Exceptions;
using Oxide.Ext.Discord.Helpers.Cdn;
using Oxide.Ext.Discord.Interfaces;
@@ -97,6 +98,7 @@ public class DiscordSticker : ISnowflakeEntity
/// Callback when an error occurs with error information
public static void GetSticker(DiscordClient client, Snowflake stickerId, Action callback, Action error = null)
{
+ if (!stickerId.IsValid()) throw new InvalidSnowflakeException(nameof(stickerId));
client.Bot.Rest.DoRequest($"/stickers/{stickerId}", RequestMethod.GET, null, callback, error);
}
diff --git a/Oxide.Ext.Discord/Entities/Users/DiscordUser.cs b/Oxide.Ext.Discord/Entities/Users/DiscordUser.cs
index a81040678..140c2a13c 100644
--- a/Oxide.Ext.Discord/Entities/Users/DiscordUser.cs
+++ b/Oxide.Ext.Discord/Entities/Users/DiscordUser.cs
@@ -9,6 +9,7 @@
using Oxide.Ext.Discord.Entities.Messages.Embeds;
using Oxide.Ext.Discord.Entities.Permissions;
using Oxide.Ext.Discord.Entities.Users.Connections;
+using Oxide.Ext.Discord.Exceptions;
using Oxide.Ext.Discord.Helpers;
using Oxide.Ext.Discord.Helpers.Cdn;
using Oxide.Ext.Discord.Interfaces;
@@ -172,6 +173,7 @@ public static void GetCurrentUser(DiscordClient client, Action call
/// Callback when an error occurs with error information
public static void GetUser(DiscordClient client, Snowflake userId, Action callback = null, Action error = null)
{
+ if (!userId.IsValid()) throw new InvalidSnowflakeException(nameof(userId));
client.Bot.Rest.DoRequest($"/users/{userId}", RequestMethod.GET, null, callback, error);
}
@@ -273,6 +275,7 @@ public void GetCurrentUserGuilds(DiscordClient client, UserGuildsRequest request
/// Callback when an error occurs with error information
public void LeaveGuild(DiscordClient client, Snowflake guildId, Action callback = null, Action error = null)
{
+ if (!guildId.IsValid()) throw new InvalidSnowflakeException(nameof(guildId));
client.Bot.Rest.DoRequest($"/users/@me/guilds/{guildId}", RequestMethod.DELETE, null, callback, error);
}
@@ -295,6 +298,7 @@ public void LeaveGuild(DiscordClient client, Snowflake guildId, Action callback
/// Callback when an error occurs with error information
public static void CreateDirectMessageChannel(DiscordClient client, Snowflake userId, Action callback, Action error = null)
{
+ if (!userId.IsValid()) throw new InvalidSnowflakeException(nameof(userId));
if (userId == client.Bot.BotUser.Id)
{
client.Logger.Error("Tried to create a direct message to the bot which is not allowed.");
@@ -374,7 +378,8 @@ public void GetUserConnections(DiscordClient client, Action> ca
/// Callback when an error occurs with error information
public void GroupDmAddRecipient(DiscordClient client, Snowflake channelId, string accessToken, string nick, Action callback = null, Action error = null)
{
- Dictionary data = new Dictionary()
+ if (!channelId.IsValid()) throw new InvalidSnowflakeException(nameof(channelId));
+ Dictionary data = new Dictionary
{
["access_token"] = accessToken,
["nick"] = nick
@@ -401,6 +406,7 @@ public void GroupDmAddRecipient(DiscordClient client, Snowflake channelId, strin
/// Callback when an error occurs with error information
public void GroupDmRemoveRecipient(DiscordClient client, Snowflake channelId, Action callback = null, Action error = null)
{
+ if (!channelId.IsValid()) throw new InvalidSnowflakeException(nameof(channelId));
client.Bot.Rest.DoRequest($"/channels/{channelId}/recipients/{Id}", RequestMethod.DELETE, null, callback, error);
}
#endregion
diff --git a/Oxide.Ext.Discord/Entities/Webhooks/DiscordWebhook.cs b/Oxide.Ext.Discord/Entities/Webhooks/DiscordWebhook.cs
index f782c6434..b0b6765fd 100644
--- a/Oxide.Ext.Discord/Entities/Webhooks/DiscordWebhook.cs
+++ b/Oxide.Ext.Discord/Entities/Webhooks/DiscordWebhook.cs
@@ -5,6 +5,7 @@
using Oxide.Ext.Discord.Entities.Guilds;
using Oxide.Ext.Discord.Entities.Messages;
using Oxide.Ext.Discord.Entities.Users;
+using Oxide.Ext.Discord.Exceptions;
namespace Oxide.Ext.Discord.Entities.Webhooks
{
///
@@ -95,6 +96,7 @@ public class DiscordWebhook
/// Callback when an error occurs with error information
public static void CreateWebhook(DiscordClient client, Snowflake channelId, string name, string avatar = null, Action callback = null, Action error = null)
{
+ if (!channelId.IsValid()) throw new InvalidSnowflakeException(nameof(channelId));
Dictionary data = new Dictionary
{
["name"] = name,
@@ -114,6 +116,7 @@ public static void CreateWebhook(DiscordClient client, Snowflake channelId, stri
/// Callback when an error occurs with error information
public static void GetChannelWebhooks(DiscordClient client, Snowflake channelId, Action> callback = null, Action error = null)
{
+ if (!channelId.IsValid()) throw new InvalidSnowflakeException(nameof(channelId));
client.Bot.Rest.DoRequest($"/channels/{channelId}/webhooks", RequestMethod.GET, null, callback, error);
}
@@ -127,6 +130,7 @@ public static void GetChannelWebhooks(DiscordClient client, Snowflake channelId,
/// Callback when an error occurs with error information
public static void GetGuildWebhooks(DiscordClient client, Snowflake guildId, Action> callback = null, Action error = null)
{
+ if (!guildId.IsValid()) throw new InvalidSnowflakeException(nameof(guildId));
client.Bot.Rest.DoRequest($"/guilds/{guildId}/webhooks", RequestMethod.GET, null, callback, error);
}
@@ -140,6 +144,7 @@ public static void GetGuildWebhooks(DiscordClient client, Snowflake guildId, Act
/// Callback when an error occurs with error information
public static void GetWebhook(DiscordClient client, Snowflake webhookId, Action callback = null, Action error = null)
{
+ if (!webhookId.IsValid()) throw new InvalidSnowflakeException(nameof(webhookId));
client.Bot.Rest.DoRequest($"/webhooks/{webhookId}", RequestMethod.GET, null, callback, error);
}
@@ -156,6 +161,7 @@ public static void GetWebhook(DiscordClient client, Snowflake webhookId, Action<
/// Callback when an error occurs with error information
public static void GetWebhookWithToken(DiscordClient client, Snowflake webhookId, string webhookToken, Action callback = null, Action error = null)
{
+ if (!webhookId.IsValid()) throw new InvalidSnowflakeException(nameof(webhookId));
client.Bot.Rest.DoRequest($"/webhooks/{webhookId}/{webhookToken}", RequestMethod.GET, null, callback, error);
}
@@ -191,6 +197,8 @@ public static void GetWebhookWithUrl(DiscordClient client, string webhookUrl, Ac
/// Callback when an error occurs with error information
public void ModifyWebhook(DiscordClient client, string name = null, string avatar = null, Snowflake? channelId = null, Action callback = null, Action error = null)
{
+ if (channelId.HasValue && !channelId.Value.IsValid()) throw new InvalidSnowflakeException(nameof(channelId));
+
Dictionary data = new Dictionary
{
["name"] = name ,
@@ -265,6 +273,7 @@ public void ExecuteWebhook(DiscordClient client, WebhookCreateMessage message, W
}
message.Validate();
+ message.ValidateWebhookMessage();
client.Bot.Rest.DoRequest($"/webhooks/{Id}/{Token}{executeParams.GetWebhookFormat()}{executeParams.ToQueryString()}", RequestMethod.POST, message, callback, error);
}
@@ -287,6 +296,7 @@ public void ExecuteWebhook(DiscordClient client, WebhookCreateMessage message, W
executeParams.Wait = true;
message.Validate();
+ message.ValidateWebhookMessage();
client.Bot.Rest.DoRequest($"/webhooks/{Id}/{Token}{executeParams.GetWebhookFormat()}{executeParams.ToQueryString()}", RequestMethod.POST, message, callback, error);
}
@@ -302,6 +312,7 @@ public void ExecuteWebhook(DiscordClient client, WebhookCreateMessage message, W
/// Callback when an error occurs with error information
public void GetWebhookMessage(DiscordClient client, Snowflake messageId, WebhookMessageParams messageParams = null, Action callback = null, Action error = null)
{
+ if (!messageId.IsValid()) throw new InvalidSnowflakeException(nameof(messageId));
if (messageParams == null)
{
messageParams = new WebhookMessageParams();
@@ -339,6 +350,7 @@ public void EditWebhookMessage(DiscordClient client, Snowflake messageId, Discor
/// Callback when an error occurs with error information
public void DeleteWebhookMessage(DiscordClient client, Snowflake messageId, Action callback = null, Action error = null)
{
+ if (!messageId.IsValid()) throw new InvalidSnowflakeException(nameof(messageId));
client.Bot.Rest.DoRequest($"/webhooks/{Id}/{Token}/messages/{messageId}", RequestMethod.DELETE, null, callback, error);
}
}
diff --git a/Oxide.Ext.Discord/Entities/Webhooks/WebhookCreateMessage.cs b/Oxide.Ext.Discord/Entities/Webhooks/WebhookCreateMessage.cs
index 9843a423c..ab9d120c3 100644
--- a/Oxide.Ext.Discord/Entities/Webhooks/WebhookCreateMessage.cs
+++ b/Oxide.Ext.Discord/Entities/Webhooks/WebhookCreateMessage.cs
@@ -1,5 +1,6 @@
using Newtonsoft.Json;
using Oxide.Ext.Discord.Entities.Messages;
+using Oxide.Ext.Discord.Exceptions;
namespace Oxide.Ext.Discord.Entities.Webhooks
{
@@ -20,5 +21,18 @@ public class WebhookCreateMessage : MessageCreate
///
[JsonProperty("avatar_url")]
public string AvatarUrl { get; set; }
+
+ internal void ValidateWebhookMessage()
+ {
+ if (!Flags.HasValue)
+ {
+ return;
+ }
+
+ if ((Flags.Value & ~MessageFlags.SuppressEmbeds) != 0)
+ {
+ throw new InvalidMessageException("Invalid Message Flags Used for Webhook Message. Only supported flags are MessageFlags.SuppressEmbeds");
+ }
+ }
}
}
diff --git a/Oxide.Ext.Discord/Exceptions/BaseDiscordException.cs b/Oxide.Ext.Discord/Exceptions/BaseDiscordException.cs
new file mode 100644
index 000000000..c9796103e
--- /dev/null
+++ b/Oxide.Ext.Discord/Exceptions/BaseDiscordException.cs
@@ -0,0 +1,18 @@
+using System;
+namespace Oxide.Ext.Discord.Exceptions
+{
+ ///
+ /// Represents a base discord extension
+ ///
+ public class BaseDiscordException : Exception
+ {
+ ///
+ /// Constructor
+ ///
+ /// Exception message
+ public BaseDiscordException(string message) : base(message)
+ {
+
+ }
+ }
+}
\ No newline at end of file
diff --git a/Oxide.Ext.Discord/Exceptions/InvalidApplicationCommandException.cs b/Oxide.Ext.Discord/Exceptions/InvalidApplicationCommandException.cs
new file mode 100644
index 000000000..0c54c2035
--- /dev/null
+++ b/Oxide.Ext.Discord/Exceptions/InvalidApplicationCommandException.cs
@@ -0,0 +1,17 @@
+namespace Oxide.Ext.Discord.Exceptions
+{
+ ///
+ /// Represents an invalid application command
+ ///
+ public class InvalidApplicationCommandException : BaseDiscordException
+ {
+ ///
+ /// Constructor
+ ///
+ /// Exception message
+ public InvalidApplicationCommandException(string message) : base(message)
+ {
+
+ }
+ }
+}
\ No newline at end of file
diff --git a/Oxide.Ext.Discord/Exceptions/InvalidChannelException.cs b/Oxide.Ext.Discord/Exceptions/InvalidChannelException.cs
new file mode 100644
index 000000000..f120cc73e
--- /dev/null
+++ b/Oxide.Ext.Discord/Exceptions/InvalidChannelException.cs
@@ -0,0 +1,17 @@
+namespace Oxide.Ext.Discord.Exceptions
+{
+ ///
+ /// Represents using an invalid channel
+ ///
+ public class InvalidChannelException : BaseDiscordException
+ {
+ ///
+ /// Constructor
+ ///
+ /// Exception message
+ public InvalidChannelException(string message): base(message)
+ {
+
+ }
+ }
+}
\ No newline at end of file
diff --git a/Oxide.Ext.Discord/Exceptions/InvalidDiscordColorException.cs b/Oxide.Ext.Discord/Exceptions/InvalidDiscordColorException.cs
new file mode 100644
index 000000000..bb39c5262
--- /dev/null
+++ b/Oxide.Ext.Discord/Exceptions/InvalidDiscordColorException.cs
@@ -0,0 +1,17 @@
+namespace Oxide.Ext.Discord.Exceptions
+{
+ ///
+ /// Represents an invalid discord color
+ ///
+ public class InvalidDiscordColorException : BaseDiscordException
+ {
+ ///
+ /// Constructor
+ ///
+ /// Exception message
+ public InvalidDiscordColorException(string message) : base(message)
+ {
+
+ }
+ }
+}
\ No newline at end of file
diff --git a/Oxide.Ext.Discord/Exceptions/InvalidEmbedException.cs b/Oxide.Ext.Discord/Exceptions/InvalidEmbedException.cs
new file mode 100644
index 000000000..cbf61e9f8
--- /dev/null
+++ b/Oxide.Ext.Discord/Exceptions/InvalidEmbedException.cs
@@ -0,0 +1,17 @@
+namespace Oxide.Ext.Discord.Exceptions
+{
+ ///
+ /// Represents an invalid embed
+ ///
+ public class InvalidEmbedException : BaseDiscordException
+ {
+ ///
+ /// Constructor
+ ///
+ /// Exception message
+ public InvalidEmbedException(string message) : base(message)
+ {
+
+ }
+ }
+}
\ No newline at end of file
diff --git a/Oxide.Ext.Discord/Exceptions/InvalidEmojiException.cs b/Oxide.Ext.Discord/Exceptions/InvalidEmojiException.cs
new file mode 100644
index 000000000..def6f6664
--- /dev/null
+++ b/Oxide.Ext.Discord/Exceptions/InvalidEmojiException.cs
@@ -0,0 +1,18 @@
+namespace Oxide.Ext.Discord.Exceptions
+{
+ ///
+ /// Error thrown when an emoji string fails validation
+ ///
+ public class InvalidEmojiException : BaseDiscordException
+ {
+ ///
+ /// Constructor
+ ///
+ /// Value for the emoji
+ /// Validation error message
+ internal InvalidEmojiException(string emojiValue, string validationError) : base($"'{emojiValue}' failed emoji validation with error: {validationError}")
+ {
+
+ }
+ }
+}
\ No newline at end of file
diff --git a/Oxide.Ext.Discord/Exceptions/InvalidFileNameException.cs b/Oxide.Ext.Discord/Exceptions/InvalidFileNameException.cs
new file mode 100644
index 000000000..3e1b8a1d3
--- /dev/null
+++ b/Oxide.Ext.Discord/Exceptions/InvalidFileNameException.cs
@@ -0,0 +1,16 @@
+namespace Oxide.Ext.Discord.Exceptions
+{
+ ///
+ /// Exception throw when an attachment filename contains invalid characters
+ ///
+ public class InvalidFileNameException : BaseDiscordException
+ {
+ ///
+ /// Constructor
+ ///
+ /// invalid file name
+ public InvalidFileNameException(string fileName) : base($"'{fileName}' is not a valid filename for discord. " +
+ "Valid filename characters are alphanumeric with underscores, dashes, or dots")
+ { }
+ }
+}
\ No newline at end of file
diff --git a/Oxide.Ext.Discord/Exceptions/InvalidInteractionResponseException.cs b/Oxide.Ext.Discord/Exceptions/InvalidInteractionResponseException.cs
new file mode 100644
index 000000000..07e2c7643
--- /dev/null
+++ b/Oxide.Ext.Discord/Exceptions/InvalidInteractionResponseException.cs
@@ -0,0 +1,14 @@
+namespace Oxide.Ext.Discord.Exceptions
+{
+ ///
+ /// Error thrown when an interaction response is invalid
+ ///
+ public class InvalidInteractionResponseException : BaseDiscordException
+ {
+ ///
+ /// Constructor
+ ///
+ /// Exception message
+ public InvalidInteractionResponseException(string message) : base(message) { }
+ }
+}
\ No newline at end of file
diff --git a/Oxide.Ext.Discord/Exceptions/InvalidMessageComponentException.cs b/Oxide.Ext.Discord/Exceptions/InvalidMessageComponentException.cs
new file mode 100644
index 000000000..cdc267bc8
--- /dev/null
+++ b/Oxide.Ext.Discord/Exceptions/InvalidMessageComponentException.cs
@@ -0,0 +1,17 @@
+namespace Oxide.Ext.Discord.Exceptions
+{
+ ///
+ /// Represents an invalid message component
+ ///
+ public class InvalidMessageComponentException : BaseDiscordException
+ {
+ ///
+ /// Constructor
+ ///
+ /// Exception message
+ public InvalidMessageComponentException(string message) : base(message)
+ {
+
+ }
+ }
+}
\ No newline at end of file
diff --git a/Oxide.Ext.Discord/Exceptions/InvalidMessageException.cs b/Oxide.Ext.Discord/Exceptions/InvalidMessageException.cs
new file mode 100644
index 000000000..230ec8da6
--- /dev/null
+++ b/Oxide.Ext.Discord/Exceptions/InvalidMessageException.cs
@@ -0,0 +1,17 @@
+namespace Oxide.Ext.Discord.Exceptions
+{
+ ///
+ /// Represents an invalid message
+ ///
+ public class InvalidMessageException : BaseDiscordException
+ {
+ ///
+ /// Constructor
+ ///
+ /// Exception message
+ public InvalidMessageException(string message) : base(message)
+ {
+
+ }
+ }
+}
\ No newline at end of file
diff --git a/Oxide.Ext.Discord/Exceptions/InvalidSnowflakeException.cs b/Oxide.Ext.Discord/Exceptions/InvalidSnowflakeException.cs
new file mode 100644
index 000000000..ab71fc2de
--- /dev/null
+++ b/Oxide.Ext.Discord/Exceptions/InvalidSnowflakeException.cs
@@ -0,0 +1,17 @@
+namespace Oxide.Ext.Discord.Exceptions
+{
+ ///
+ /// Exception thrown when an invalid Snowflake ID is used in an API call
+ ///
+ public class InvalidSnowflakeException : BaseDiscordException
+ {
+ ///
+ /// Constructor
+ ///
+ /// Name of the parameter that is invalid
+ internal InvalidSnowflakeException(string paramName) : base($"Invalid Snowflake ID. Parameter Name: {paramName}")
+ {
+
+ }
+ }
+}
\ No newline at end of file
diff --git a/Oxide.Ext.Discord/Helpers/LocaleConverter.cs b/Oxide.Ext.Discord/Helpers/LocaleConverter.cs
new file mode 100644
index 000000000..1a4daa8af
--- /dev/null
+++ b/Oxide.Ext.Discord/Helpers/LocaleConverter.cs
@@ -0,0 +1,74 @@
+using Oxide.Plugins;
+
+namespace Oxide.Ext.Discord.Helpers
+{
+ ///
+ /// Converts discord locale codes into oxide locale codes
+ ///
+ public static class LocaleConverter
+ {
+ private static readonly Hash DiscordToOxide = new Hash();
+ private static readonly Hash OxideToDiscord = new Hash();
+
+ static LocaleConverter()
+ {
+ AddLocale("en","en-US");
+ AddLocale("bg-","bg");
+ AddLocale("zh","zh-CN");
+ AddLocale("hr","hr");
+ AddLocale("cs","cs");
+ AddLocale("da","da");
+ AddLocale("nl","nl");
+ AddLocale("fi","fi");
+ AddLocale("fr","fr");
+ AddLocale("de","de");
+ AddLocale("el","el");
+ AddLocale("hi","hi");
+ AddLocale("hu","hu");
+ AddLocale("it","it");
+ AddLocale("ja","ja");
+ AddLocale("ko","ko");
+ AddLocale("lt","lt");
+ AddLocale("no","no");
+ AddLocale("pl","pl");
+ AddLocale("pt","pt-BR");
+ AddLocale("ro","ro");
+ AddLocale("ru","ru");
+ AddLocale("es","es-ES");
+ AddLocale("sv","sv-SE");
+ AddLocale("th","th");
+ AddLocale("tr","tr");
+ AddLocale("uk","uk");
+ AddLocale("vi","vi");
+
+ DiscordToOxide["en-GB"] = "en";
+ DiscordToOxide["zh-TW"] = "zh";
+ }
+
+ private static void AddLocale(string oxide, string discord)
+ {
+ DiscordToOxide[discord] = oxide;
+ OxideToDiscord[oxide] = discord;
+ }
+
+ ///
+ /// Returns the oxide locale for a given discord locale
+ ///
+ /// Discord locale to get oxide locale for
+ /// Oxide locale if it exists; null otherwise
+ public static string GetOxideLocale(string discordLocale)
+ {
+ return !string.IsNullOrEmpty(discordLocale) ? DiscordToOxide[discordLocale] : string.Empty;
+ }
+
+ ///
+ /// Returns the discord locale for a given oxide locale
+ ///
+ /// oxide locale to get discord locale for
+ /// Discord locale if it exists; null otherwise
+ public static string GetDiscordLocale(string oxideLocale)
+ {
+ return !string.IsNullOrEmpty(oxideLocale) ? OxideToDiscord[oxideLocale] : string.Empty;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Oxide.Ext.Discord/Helpers/Validation.cs b/Oxide.Ext.Discord/Helpers/Validation.cs
index 33c6b8c8c..7d101618d 100644
--- a/Oxide.Ext.Discord/Helpers/Validation.cs
+++ b/Oxide.Ext.Discord/Helpers/Validation.cs
@@ -1,4 +1,5 @@
using System.Text.RegularExpressions;
+using Oxide.Ext.Discord.Exceptions;
namespace Oxide.Ext.Discord.Helpers
{
@@ -8,33 +9,44 @@ namespace Oxide.Ext.Discord.Helpers
public static class Validation
{
private static readonly Regex EmojiValidation = new Regex("^.+:[0-9]+$", RegexOptions.Compiled);
+ private static readonly Regex FilenameValidation = new Regex("^[a-zA-Z0-9_.-]*$", RegexOptions.Compiled);
///
/// Validates that the emoji string entered is valid.
///
///
///
- public static string ValidateEmoji(string emoji)
+ public static void ValidateEmoji(string emoji)
{
if (string.IsNullOrEmpty(emoji))
{
- return "Emoji string cannot be null or empty.";
+ throw new InvalidEmojiException(emoji, "Emoji string cannot be null or empty.");
}
if (emoji.Length == 2 && !char.IsSurrogatePair(emoji[0], emoji[1]))
{
- return "Emoji of length 2 must be a surrogate pair";
+ throw new InvalidEmojiException(emoji, "Emoji of length 2 must be a surrogate pair");
}
if (emoji.Length > 2 && !EmojiValidation.IsMatch(emoji))
{
- return "Emoji string is not in the correct format.\n" +
- "If using a normal emoji please use the unicode character for that emoji.\n" +
- "If using a custom emoji the format must be emojiName:emojiId\n" +
- "If using a custom animated emoji the format must be a:emojiName:emojiId";
+ throw new InvalidEmojiException(emoji, "Emoji string is not in the correct format.\n" +
+ "If using a normal emoji please use the unicode character for that emoji.\n" +
+ "If using a custom emoji the format must be emojiName:emojiId\n" +
+ "If using a custom animated emoji the format must be a:emojiName:emojiId");
}
+ }
- return null;
+ ///
+ /// Checks if the filename is a valid discord filename.
+ ///
+ /// Filename to validate
+ public static void ValidateFilename(string filename)
+ {
+ if (!FilenameValidation.IsMatch(filename))
+ {
+ throw new InvalidFileNameException(filename);
+ }
}
}
}
\ No newline at end of file
diff --git a/Oxide.Ext.Discord/Oxide.Ext.Discord.csproj b/Oxide.Ext.Discord/Oxide.Ext.Discord.csproj
index b3545aaf4..655382554 100644
--- a/Oxide.Ext.Discord/Oxide.Ext.Discord.csproj
+++ b/Oxide.Ext.Discord/Oxide.Ext.Discord.csproj
@@ -9,9 +9,9 @@
https://github.com/Kirollos/Oxide.Ext.Discord
net46;net45;net40;net35
7.0
- 2.1.0.0
- 2.1.0
- 2.1.0
+ 2.1.1.0
+ 2.1.1
+ 2.1.1
diff --git a/Oxide.Ext.Discord/Oxide.Ext.Discord.xml b/Oxide.Ext.Discord/Oxide.Ext.Discord.xml
index f7f231865..9c917c229 100644
--- a/Oxide.Ext.Discord/Oxide.Ext.Discord.xml
+++ b/Oxide.Ext.Discord/Oxide.Ext.Discord.xml
@@ -90,11 +90,6 @@
If the connection is initialized and not disconnected
-
-
- If the bot has successfully connected to the websocket at least once
-
-
Application reference for this bot
@@ -674,16 +669,18 @@
If the select menu should be disabled
-
+
Adds a select menu to a new action row
Unique ID for the select menu
Label for the input text
Style of the Input Text
+ Default value for the Input Text
+ Is the Input Text Required to be filled out
Text to display if no value is selected yet
- The min number of options you must select
- The max number of options you can select
+ The min number of options you must select
+ The max number of options you can select
@@ -3866,7 +3863,7 @@
- icon hash
+ icon hash of the group DM
@@ -4518,7 +4515,7 @@
- The Stage instance is visible publicly, such as on Stage discovery.
+ The Stage instance is visible publicly. (deprecated)
@@ -4553,7 +4550,7 @@
- Whether or not Stage discovery is disabled
+ Whether or not Stage discovery is disabled (deprecated)
@@ -4770,6 +4767,11 @@
Only available on private threads
+
+
+ Timestamp when the thread was created; only populated for threads created after 2022-01-09
+
+
Represents a Video Quality Mode
@@ -6944,6 +6946,7 @@
Returns the Welcome Screen object for the guild.
+ Requires the `MANAGE_GUILD` permission.
Client to use
Callback with welcome screen for the guild
@@ -7638,6 +7641,11 @@
The description of a Community guild
+
+
+ Custom guild stickers
+
+
Represents Guild Prune Begin
@@ -7773,32 +7781,32 @@
- ID of the guild
+ Guild id
- Name of the guild
+ Guild name (2-100 characters)
- Instant invite line for the guild
+ Instant invite for the guilds specified widget invite channel
- List of guild channels
+ Voice and stage channels which are accessible by @everyone
- List of guild members
+ Special widget user objects that includes users presence (Limit 100)
- The count of the presences
+ Number of online members in this guild
@@ -8468,13 +8476,7 @@
- Represents a Command Followup within discord.
-
-
-
-
- Callback data flags
- Set to 64 to make your response ephemeral
+ Represents a Command Followup within discord.
@@ -8516,7 +8518,7 @@
- Enable autocomplete interactions for this option
+ If autocomplete interactions are enabled for this `STRING`, `INTEGER`, or `NUMBER` type option
@@ -8865,6 +8867,18 @@
For components, the message they were attached to
+
+
+ The selected language of the invoking user
+ Discord Locale Values
+
+
+
+
+ The guild's preferred locale, if invoked in a guild
+ Discord Locale Values
+
+
Returns the interaction parsed args to make it easier to process that interaction.
@@ -8899,7 +8913,7 @@
Callback once the action is completed
Callback when an error occurs with error information
-
+
Create a followup message for an Interaction
See Create Followup Message
@@ -9089,21 +9103,21 @@
Parsed command for the interaction if an application command
-
+
Command group for the interaction if Command Type if an application command
Null if command group is not used for the command.
Defaults to empty string if command does not have a command group
-
+
Sub Command for the interaction if Command Typ if an application command
Null if sub command group is not used for the command.
Defaults to empty string if command does not have sub command
-
+
Interaction Data Supplied Args if Command Type if an application command
@@ -9123,6 +9137,16 @@
If a triggered this interaction. The values selected from the select menu.
+
+
+ Discord User's locale converted to oxide lang locale
+
+
+
+
+ Discord Guild's locale converted to oxide lang locale
+
+
Constructor for the data parser.
@@ -9463,6 +9487,16 @@
The placeholder for the text input field
+
+
+ The pre-filled value for text input
+
+
+
+
+ Is the Input Text Required to be filled out
+
+
Input Text Constructor
@@ -10765,6 +10799,11 @@
Attachments for the message
+
+
+ Attachments for the message
+
+
Attachments for a discord message
@@ -11764,8 +11803,8 @@
Converts snowflake to a string
- Snowflake to be converted to ulong
- Snowflake ID as ulong
+ Snowflake to be converted to string
+ Snowflake ID as string
@@ -13134,6 +13173,128 @@
Channel Follower Webhooks are internal webhooks used with Channel Following to post new messages into channels
+
+
+ Represents a base discord extension
+
+
+
+
+ Constructor
+
+ Exception message
+
+
+
+ Represents an invalid application command
+
+
+
+
+ Constructor
+
+ Exception message
+
+
+
+ Represents using an invalid channel
+
+
+
+
+ Constructor
+
+ Exception message
+
+
+
+ Represents an invalid discord color
+
+
+
+
+ Constructor
+
+ Exception message
+
+
+
+ Represents an invalid embed
+
+
+
+
+ Constructor
+
+ Exception message
+
+
+
+ Error thrown when an emoji string fails validation
+
+
+
+
+ Constructor
+
+ Value for the emoji
+ Validation error message
+
+
+
+ Exception throw when an attachment filename contains invalid characters
+
+
+
+
+ Constructor
+
+ invalid file name
+
+
+
+ Error thrown when an interaction response is invalid
+
+
+
+
+ Constructor
+
+ Exception message
+
+
+
+ Represents an invalid message component
+
+
+
+
+ Constructor
+
+ Exception message
+
+
+
+ Represents an invalid message
+
+
+
+
+ Constructor
+
+ Exception message
+
+
+
+ Exception thrown when an invalid Snowflake ID is used in an API call
+
+
+
+
+ Constructor
+
+ Name of the parameter that is invalid
+
Adds extension methods to Discord User to allow sending server chat commands to the player
@@ -13775,6 +13936,25 @@
Message to make block quote
Multiline block quote formatted message
+
+
+ Converts discord locale codes into oxide locale codes
+
+
+
+
+ Returns the oxide locale for a given discord locale
+
+ Discord locale to get oxide locale for
+ Oxide locale if it exists; null otherwise
+
+
+
+ Returns the discord locale for a given oxide locale
+
+ oxide locale to get discord locale for
+ Discord locale if it exists; null otherwise
+
Helper methods relating to time
@@ -13875,6 +14055,12 @@
+
+
+ Checks if the filename is a valid discord filename.
+
+ Filename to validate
+
Represents and interface for entities that can upload files
@@ -15488,6 +15674,11 @@
The current sequence number for the websocket
+
+
+ If the bot has successfully connected to the websocket at least once
+
+
Creates a new socket listener
diff --git a/Oxide.Ext.Discord/Rest/Request.cs b/Oxide.Ext.Discord/Rest/Request.cs
index 9378ab408..0a6d2a725 100644
--- a/Oxide.Ext.Discord/Rest/Request.cs
+++ b/Oxide.Ext.Discord/Rest/Request.cs
@@ -184,7 +184,7 @@ public void Fire()
bool isRateLimit = statusCode == 429;
if (isRateLimit)
{
- _logger.Warning($"Discord rate limit reached. (Rate limit info: remaining: [{req.Method}] Route:{req.RequestUri} Remaining: {Bucket.RateLimitRemaining.ToString()} Limit: {Bucket.RateLimit.ToString()}, Reset In: {Bucket.RateLimitReset.ToString()}, Current Time: {Time.TimeSinceEpoch().ToString()}");
+ _logger.Warning($"Discord rate limit reached. (Rate limit info: remaining: [{req.Method}] Route: {req.RequestUri} Content-Type: {req.ContentType} Remaining: {Bucket.RateLimitRemaining.ToString()} Limit: {Bucket.RateLimit.ToString()}, Reset In: {Bucket.RateLimitReset.ToString()}, Current Time: {Time.TimeSinceEpoch().ToString()}");
Close(false);
return;
}
@@ -193,7 +193,7 @@ public void Fire()
_lastError.DiscordError = apiError;
if (apiError != null && apiError.Code != 0)
{
- _logger.Error($"Discord API has returned error Discord Code: {apiError.Code.ToString()} Discord Error: {apiError.Message} Request: [{req.Method}] {req.RequestUri} (Response Code: {httpResponse.StatusCode.ToString()})" +
+ _logger.Error($"Discord API has returned error Discord Code: {apiError.Code.ToString()} Discord Error: {apiError.Message} Request: [{req.Method}] {req.RequestUri} (Response Code: {httpResponse.StatusCode.ToString()}) Content-Type: {req.ContentType}" +
$"\nDiscord Errors: {apiError.Errors}" +
$"\nRequest Body:\n{(Contents != null ? Encoding.UTF8.GetString(Contents) : "Contents is null")}");
}
diff --git a/Oxide.Ext.Discord/WebSockets/Handlers/HeartbeatHandler.cs b/Oxide.Ext.Discord/WebSockets/Handlers/HeartbeatHandler.cs
index c87b2a8e8..d572cdecf 100644
--- a/Oxide.Ext.Discord/WebSockets/Handlers/HeartbeatHandler.cs
+++ b/Oxide.Ext.Discord/WebSockets/Handlers/HeartbeatHandler.cs
@@ -84,7 +84,7 @@ private void HeartbeatElapsed(object sender, ElapsedEventArgs e)
_initial = false;
}
- if (!_client.ConnectedSuccessfully)
+ if (!_listener.SocketHasConnected)
{
_logger.Debug($"{nameof(HeartbeatHandler)}.{nameof(HeartbeatElapsed)} Websocket has not yet connected successfully. Skipping Heartbeat.");
return;
diff --git a/Oxide.Ext.Discord/WebSockets/Handlers/SocketCommandHandler.cs b/Oxide.Ext.Discord/WebSockets/Handlers/SocketCommandHandler.cs
index 39e49a4cc..9471bdead 100644
--- a/Oxide.Ext.Discord/WebSockets/Handlers/SocketCommandHandler.cs
+++ b/Oxide.Ext.Discord/WebSockets/Handlers/SocketCommandHandler.cs
@@ -18,6 +18,8 @@ public class SocketCommandHandler
private readonly List _pendingCommands = new List();
private readonly WebsocketRateLimit _rateLimit = new WebsocketRateLimit();
private readonly Timer _rateLimitTimer;
+ private readonly object _syncRoot = new object();
+ private bool _socketCanSendCommands;
///
/// Constructor
@@ -46,69 +48,116 @@ public void Enqueue(CommandPayload command)
{
_logger.Debug($"{nameof(SocketCommandHandler)}.{nameof(Enqueue)} Queuing command {command.OpCode.ToString()}");
}
-
- if (_webSocket.IsConnected())
+
+ //If websocket has connect and we need to identify or resume send those payloads right away
+ if (_webSocket.IsConnected() && (command.OpCode == GatewayCommandCode.Identify || command.OpCode == GatewayCommandCode.Resume))
{
- _pendingCommands.Add(command);
- SendCommands();
+ _webSocket.Send(command);
return;
}
-
- if (command.OpCode == GatewayCommandCode.PresenceUpdate)
- {
- _pendingCommands.RemoveAll(p => p.OpCode == GatewayCommandCode.PresenceUpdate);
- }
- else if (command.OpCode == GatewayCommandCode.VoiceStateUpdate)
+
+ //If the websocket isn't fully connect enqueue the command until it is ready
+ if (!_socketCanSendCommands)
{
- _pendingCommands.RemoveAll(p => p.OpCode == GatewayCommandCode.VoiceStateUpdate);
- }
+ if (command.OpCode == GatewayCommandCode.PresenceUpdate)
+ {
+ RemoveByType(GatewayCommandCode.PresenceUpdate);
+ }
+ else if (command.OpCode == GatewayCommandCode.VoiceStateUpdate)
+ {
+ RemoveByType(GatewayCommandCode.VoiceStateUpdate);
+ }
- _pendingCommands.Add(command);
+ AddCommand(command);
+ return;
+ }
+
+ AddCommand(command);
+ SendCommands();
}
internal void OnSocketConnected()
{
_logger.Debug($"{nameof(SocketCommandHandler)}.{nameof(OnSocketConnected)} Socket Connected. Sending queued commands.");
+ _socketCanSendCommands = true;
SendCommands();
}
+ internal void OnSocketDisconnected()
+ {
+ _logger.Debug($"{nameof(SocketCommandHandler)}.{nameof(OnSocketConnected)} Socket Disconnected. Queuing Commands.");
+ _socketCanSendCommands = false;
+ }
+
private void RateLimitElapsed(object sender, ElapsedEventArgs e)
{
_logger.Debug($"{nameof(SocketCommandHandler)}.{nameof(RateLimitElapsed)} Rate Limit has elapsed. Send Queued Commands");
_rateLimitTimer.Stop();
+ if (!_socketCanSendCommands)
+ {
+ _rateLimitTimer.Interval = 1000;
+ _logger.Debug($"{nameof(SocketCommandHandler)}.{nameof(RateLimitElapsed)} Can't send commands right now. Trying again in 1 second");
+ _rateLimitTimer.Start();
+ return;
+ }
+
SendCommands();
}
private void SendCommands()
{
- while (_pendingCommands.Count != 0)
+ lock (_syncRoot)
{
- CommandPayload payload = _pendingCommands[0];
- if (_rateLimit.HasReachedRateLimit)
+ while (_pendingCommands.Count != 0)
{
- if (!_rateLimitTimer.Enabled)
+ CommandPayload payload = _pendingCommands[0];
+ if (_rateLimit.HasReachedRateLimit)
+ {
+ if (!_rateLimitTimer.Enabled)
+ {
+ _rateLimitTimer.Interval = _rateLimit.NextReset;
+ _rateLimitTimer.Stop();
+ _rateLimitTimer.Start();
+ _logger.Warning($"{nameof(SocketCommandHandler)}.{nameof(SendCommands)} Rate Limit Hit! Retrying in {_rateLimit.NextReset.ToString()} seconds\nOpcode: {payload.OpCode}\nPayload: {JsonConvert.SerializeObject(payload.Payload, DiscordExtension.ExtensionSerializeSettings)}");
+ }
+
+ return;
+ }
+
+ if (_logger.IsLogging(DiscordLogLevel.Debug))
{
- _rateLimitTimer.Interval = _rateLimit.NextReset;
- _rateLimitTimer.Stop();
- _rateLimitTimer.Start();
- _logger.Warning($"{nameof(SocketCommandHandler)}.{nameof(SendCommands)} Rate Limit Hit! Retrying in {_rateLimit.NextReset.ToString()} seconds\nOpcode: {payload.OpCode}\nPayload: {JsonConvert.SerializeObject(payload.Payload, DiscordExtension.ExtensionSerializeSettings)}");
+ _logger.Debug($"{nameof(SocketCommandHandler)}.{nameof(SendCommands)} Sending Command {payload.OpCode.ToString()}");
}
- return;
- }
+ if (!_webSocket.Send(payload))
+ {
+ return;
+ }
- if (_logger.IsLogging(DiscordLogLevel.Debug))
- {
- _logger.Debug($"{nameof(SocketCommandHandler)}.{nameof(SendCommands)} Sending Command {payload.OpCode.ToString()}");
+ _pendingCommands.RemoveAt(0);
+ _rateLimit.FiredRequest();
}
+ }
+ }
- if (!_webSocket.Send(payload))
+ private void AddCommand(CommandPayload command)
+ {
+ lock (_syncRoot)
+ {
+ if (command.OpCode == GatewayCommandCode.Identify || command.OpCode == GatewayCommandCode.Resume)
{
+ _pendingCommands.Insert(0, command);
return;
}
-
- _pendingCommands.RemoveAt(0);
- _rateLimit.FiredRequest();
+ _pendingCommands.Add(command);
+ }
+ }
+
+ private void RemoveByType(GatewayCommandCode code)
+ {
+ lock (_syncRoot)
+ {
+ _pendingCommands.RemoveAll(c => c.OpCode == code);
}
}
}
diff --git a/Oxide.Ext.Discord/WebSockets/Socket.cs b/Oxide.Ext.Discord/WebSockets/Socket.cs
index 56db5ca0f..25f8f6fdc 100644
--- a/Oxide.Ext.Discord/WebSockets/Socket.cs
+++ b/Oxide.Ext.Discord/WebSockets/Socket.cs
@@ -101,6 +101,7 @@ public void Disconnect(bool attemptReconnect, bool shouldResume, bool requested
{
RequestedReconnect = attemptReconnect;
ShouldAttemptResume = shouldResume;
+ _commands.OnSocketDisconnected();
if (_reconnectTimer != null)
{
diff --git a/Oxide.Ext.Discord/WebSockets/SocketListener.cs b/Oxide.Ext.Discord/WebSockets/SocketListener.cs
index 520d71c0d..54ac8ff7c 100644
--- a/Oxide.Ext.Discord/WebSockets/SocketListener.cs
+++ b/Oxide.Ext.Discord/WebSockets/SocketListener.cs
@@ -41,6 +41,11 @@ public class SocketListener
///
private int _sequence;
+ ///
+ /// If the bot has successfully connected to the websocket at least once
+ ///
+ public bool SocketHasConnected { get; internal set; }
+
private readonly BotClient _client;
private readonly Socket _webSocket;
private readonly ILogger _logger;
@@ -81,8 +86,8 @@ public void Shutdown()
public void SocketOpened(object sender, EventArgs e)
{
_logger.Info("Discord socket opened!");
- _client.CallHook(DiscordExtHooks.OnDiscordWebsocketOpened);
_webSocket.SocketState = SocketState.Connected;
+ _client.CallHook(DiscordExtHooks.OnDiscordWebsocketOpened);
}
///
@@ -107,6 +112,7 @@ public void SocketClosed(object sender, CloseEventArgs e)
_client.CallHook(DiscordExtHooks.OnDiscordWebsocketClosed, e.Reason, e.Code, e.WasClean);
_webSocket.SocketState = SocketState.Disconnected;
_webSocket.DisposeSocket();
+ _commands.OnSocketDisconnected();
if (!_client.Initialized)
{
@@ -575,7 +581,7 @@ private void HandleDispatchReady(EventPayload payload)
}
_client.ReadyData = ready;
- _client.ConnectedSuccessfully = true;
+ SocketHasConnected = true;
_commands.OnSocketConnected();
}