diff --git a/src/Discord.Net.Core/Entities/Guilds/IGuild.cs b/src/Discord.Net.Core/Entities/Guilds/IGuild.cs
index b39a49776..cdf0118c4 100644
--- a/src/Discord.Net.Core/Entities/Guilds/IGuild.cs
+++ b/src/Discord.Net.Core/Entities/Guilds/IGuild.cs
@@ -683,6 +683,9 @@ namespace Discord
///
/// Downloads all users for this guild if the current list is incomplete.
///
+ ///
+ /// This method downloads all users found within this guild throught the Gateway and caches them.
+ ///
///
/// A task that represents the asynchronous download operation.
///
diff --git a/src/Discord.Net.Core/Entities/Messages/IUserMessage.cs b/src/Discord.Net.Core/Entities/Messages/IUserMessage.cs
index bc52dd01c..e2fb25aae 100644
--- a/src/Discord.Net.Core/Entities/Messages/IUserMessage.cs
+++ b/src/Discord.Net.Core/Entities/Messages/IUserMessage.cs
@@ -57,6 +57,21 @@ namespace Discord
///
Task UnpinAsync(RequestOptions options = null);
+ ///
+ /// Publishes (crossposts) this message.
+ ///
+ /// The options to be used when sending the request.
+ ///
+ /// A task that represents the asynchronous operation for publishing this message.
+ ///
+ ///
+ ///
+ /// This call will throw an if attempted in a non-news channel.
+ ///
+ /// This method will publish (crosspost) the message. Please note, publishing (crossposting), is only available in news channels.
+ ///
+ Task CrosspostAsync(RequestOptions options = null);
+
///
/// Transforms this message's text into a human-readable form by resolving its tags.
///
diff --git a/src/Discord.Net.Rest/DiscordRestApiClient.cs b/src/Discord.Net.Rest/DiscordRestApiClient.cs
index a726ef75d..732cb5f17 100644
--- a/src/Discord.Net.Rest/DiscordRestApiClient.cs
+++ b/src/Discord.Net.Rest/DiscordRestApiClient.cs
@@ -695,6 +695,15 @@ namespace Discord.API
var ids = new BucketIds(channelId: channelId);
await SendAsync("POST", () => $"channels/{channelId}/typing", ids, options: options).ConfigureAwait(false);
}
+ public async Task CrosspostAsync(ulong channelId, ulong messageId, RequestOptions options = null)
+ {
+ Preconditions.NotEqual(channelId, 0, nameof(channelId));
+ Preconditions.NotEqual(messageId, 0, nameof(messageId));
+ options = RequestOptions.CreateOrClone(options);
+
+ var ids = new BucketIds(channelId: channelId);
+ await SendAsync("POST", () => $"channels/{channelId}/messages/{messageId}/crosspost", ids, options: options).ConfigureAwait(false);
+ }
//Channel Permissions
public async Task ModifyChannelPermissionsAsync(ulong channelId, ulong targetId, ModifyChannelPermissionsParams args, RequestOptions options = null)
diff --git a/src/Discord.Net.Rest/Entities/Messages/MessageHelper.cs b/src/Discord.Net.Rest/Entities/Messages/MessageHelper.cs
index b29eca62e..57f8b2509 100644
--- a/src/Discord.Net.Rest/Entities/Messages/MessageHelper.cs
+++ b/src/Discord.Net.Rest/Entities/Messages/MessageHelper.cs
@@ -44,8 +44,10 @@ namespace Discord.Rest
};
return await client.ApiClient.ModifyMessageAsync(msg.Channel.Id, msg.Id, apiArgs, options).ConfigureAwait(false);
}
+
public static Task DeleteAsync(IMessage msg, BaseDiscordClient client, RequestOptions options)
=> DeleteAsync(msg.Channel.Id, msg.Id, client, options);
+
public static async Task DeleteAsync(ulong channelId, ulong msgId, BaseDiscordClient client,
RequestOptions options)
{
@@ -115,6 +117,7 @@ namespace Discord.Rest
{
await client.ApiClient.AddPinAsync(msg.Channel.Id, msg.Id, options).ConfigureAwait(false);
}
+
public static async Task UnpinAsync(IMessage msg, BaseDiscordClient client,
RequestOptions options)
{
@@ -240,6 +243,7 @@ namespace Discord.Rest
return tags.ToImmutable();
}
+
private static int? FindIndex(IReadOnlyList tags, int index)
{
int i = 0;
@@ -253,6 +257,7 @@ namespace Discord.Rest
return null; //Overlaps tag before this
return i;
}
+
public static ImmutableArray FilterTagsByKey(TagType type, ImmutableArray tags)
{
return tags
@@ -260,6 +265,7 @@ namespace Discord.Rest
.Select(x => x.Key)
.ToImmutableArray();
}
+
public static ImmutableArray FilterTagsByValue(TagType type, ImmutableArray tags)
{
return tags
@@ -279,5 +285,14 @@ namespace Discord.Rest
return MessageSource.Bot;
return MessageSource.User;
}
+
+ public static Task CrosspostAsync(IMessage msg, BaseDiscordClient client, RequestOptions options)
+ => CrosspostAsync(msg.Channel.Id, msg.Id, client, options);
+
+ public static async Task CrosspostAsync(ulong channelId, ulong msgId, BaseDiscordClient client,
+ RequestOptions options)
+ {
+ await client.ApiClient.CrosspostAsync(channelId, msgId, options).ConfigureAwait(false);
+ }
}
}
diff --git a/src/Discord.Net.Rest/Entities/Messages/RestMessage.cs b/src/Discord.Net.Rest/Entities/Messages/RestMessage.cs
index f457f4f7a..b4a33c76c 100644
--- a/src/Discord.Net.Rest/Entities/Messages/RestMessage.cs
+++ b/src/Discord.Net.Rest/Entities/Messages/RestMessage.cs
@@ -165,7 +165,7 @@ namespace Discord.Rest
IReadOnlyCollection IMessage.Embeds => Embeds;
///
IReadOnlyCollection IMessage.MentionedUserIds => MentionedUsers.Select(x => x.Id).ToImmutableArray();
-
+
///
public IReadOnlyDictionary Reactions => _reactions.ToDictionary(x => x.Emote, x => new ReactionMetadata { ReactionCount = x.Count, IsMe = x.Me });
diff --git a/src/Discord.Net.Rest/Entities/Messages/RestUserMessage.cs b/src/Discord.Net.Rest/Entities/Messages/RestUserMessage.cs
index 7d652687a..ad2a65615 100644
--- a/src/Discord.Net.Rest/Entities/Messages/RestUserMessage.cs
+++ b/src/Discord.Net.Rest/Entities/Messages/RestUserMessage.cs
@@ -148,6 +148,18 @@ namespace Discord.Rest
TagHandling roleHandling = TagHandling.Name, TagHandling everyoneHandling = TagHandling.Ignore, TagHandling emojiHandling = TagHandling.Name)
=> MentionUtils.Resolve(this, 0, userHandling, channelHandling, roleHandling, everyoneHandling, emojiHandling);
+ ///
+ /// This operation may only be called on a channel.
+ public async Task CrosspostAsync(RequestOptions options = null)
+ {
+ if (!(Channel is RestNewsChannel))
+ {
+ throw new InvalidOperationException("Publishing (crossposting) is only valid in news channels.");
+ }
+
+ await MessageHelper.CrosspostAsync(this, Discord, options);
+ }
+
private string DebuggerDisplay => $"{Author}: {Content} ({Id}{(Attachments.Count > 0 ? $", {Attachments.Count} Attachments" : "")})";
}
}
diff --git a/src/Discord.Net.WebSocket/DiscordSocketClient.cs b/src/Discord.Net.WebSocket/DiscordSocketClient.cs
index be7432bc3..b56498061 100644
--- a/src/Discord.Net.WebSocket/DiscordSocketClient.cs
+++ b/src/Discord.Net.WebSocket/DiscordSocketClient.cs
@@ -1771,17 +1771,7 @@ namespace Discord.WebSocket
return guild;
}
internal SocketGuild RemoveGuild(ulong id)
- {
- var guild = State.RemoveGuild(id);
- if (guild != null)
- {
- foreach (var _ in guild.Channels)
- State.RemoveChannel(id);
- foreach (var user in guild.Users)
- user.GlobalUser.RemoveRef(this);
- }
- return guild;
- }
+ => State.RemoveGuild(id);
/// Unexpected channel type is created.
internal ISocketPrivateChannel AddPrivateChannel(API.Channel model, ClientState state)
diff --git a/src/Discord.Net.WebSocket/Entities/Messages/SocketUserMessage.cs b/src/Discord.Net.WebSocket/Entities/Messages/SocketUserMessage.cs
index b26dfe5fb..e1f0f74dc 100644
--- a/src/Discord.Net.WebSocket/Entities/Messages/SocketUserMessage.cs
+++ b/src/Discord.Net.WebSocket/Entities/Messages/SocketUserMessage.cs
@@ -123,7 +123,7 @@ namespace Discord.WebSocket
model.Content = text;
}
}
-
+
///
/// Only the author of a message may modify the message.
/// Message content is too long, length must be less or equal to .
@@ -147,7 +147,19 @@ namespace Discord.WebSocket
public string Resolve(TagHandling userHandling = TagHandling.Name, TagHandling channelHandling = TagHandling.Name,
TagHandling roleHandling = TagHandling.Name, TagHandling everyoneHandling = TagHandling.Ignore, TagHandling emojiHandling = TagHandling.Name)
=> MentionUtils.Resolve(this, 0, userHandling, channelHandling, roleHandling, everyoneHandling, emojiHandling);
-
+
+ ///
+ /// This operation may only be called on a channel.
+ public async Task CrosspostAsync(RequestOptions options = null)
+ {
+ if (!(Channel is SocketNewsChannel))
+ {
+ throw new InvalidOperationException("Publishing (crossposting) is only valid in news channels.");
+ }
+
+ await MessageHelper.CrosspostAsync(this, Discord, options);
+ }
+
private string DebuggerDisplay => $"{Author}: {Content} ({Id}{(Attachments.Count > 0 ? $", {Attachments.Count} Attachments" : "")})";
internal new SocketUserMessage Clone() => MemberwiseClone() as SocketUserMessage;
}
diff --git a/src/Discord.Net.WebSocket/Entities/Users/SocketUser.cs b/src/Discord.Net.WebSocket/Entities/Users/SocketUser.cs
index 09c4165f4..b830ce79c 100644
--- a/src/Discord.Net.WebSocket/Entities/Users/SocketUser.cs
+++ b/src/Discord.Net.WebSocket/Entities/Users/SocketUser.cs
@@ -44,8 +44,11 @@ namespace Discord.WebSocket
///
/// Gets mutual guilds shared with this user.
///
+ ///
+ /// This property will only include guilds in the same .
+ ///
public IReadOnlyCollection MutualGuilds
- => Discord.Guilds.Where(g => g.Users.Any(u => u.Id == Id)).ToImmutableArray();
+ => Discord.Guilds.Where(g => g.GetUser(Id) != null).ToImmutableArray();
internal SocketUser(DiscordSocketClient discord, ulong id)
: base(discord, id)
diff --git a/src/Discord.Net.Webhook/DiscordWebhookClient.cs b/src/Discord.Net.Webhook/DiscordWebhookClient.cs
index 9c90df565..353345ded 100644
--- a/src/Discord.Net.Webhook/DiscordWebhookClient.cs
+++ b/src/Discord.Net.Webhook/DiscordWebhookClient.cs
@@ -33,7 +33,7 @@ namespace Discord.Webhook
: this(webhookUrl, new DiscordRestConfig()) { }
// regex pattern to match webhook urls
- private static Regex WebhookUrlRegex = new Regex(@"^.*discordapp\.com\/api\/webhooks\/([\d]+)\/([a-z0-9_-]+)$", RegexOptions.Compiled | RegexOptions.IgnoreCase);
+ private static Regex WebhookUrlRegex = new Regex(@"^.*(discord|discordapp)\.com\/api\/webhooks\/([\d]+)\/([a-z0-9_-]+)$", RegexOptions.Compiled | RegexOptions.IgnoreCase);
/// Creates a new Webhook Discord client.
public DiscordWebhookClient(ulong webhookId, string webhookToken, DiscordRestConfig config)
@@ -132,13 +132,13 @@ namespace Discord.Webhook
if (match != null)
{
// ensure that the first group is a ulong, set the _webhookId
- // 0th group is always the entire match, so start at index 1
- if (!(match.Groups[1].Success && ulong.TryParse(match.Groups[1].Value, NumberStyles.None, CultureInfo.InvariantCulture, out webhookId)))
+ // 0th group is always the entire match, and 1 is the domain; so start at index 2
+ if (!(match.Groups[2].Success && ulong.TryParse(match.Groups[2].Value, NumberStyles.None, CultureInfo.InvariantCulture, out webhookId)))
throw ex("The webhook Id could not be parsed.");
- if (!match.Groups[2].Success)
+ if (!match.Groups[3].Success)
throw ex("The webhook token could not be parsed.");
- webhookToken = match.Groups[2].Value;
+ webhookToken = match.Groups[3].Value;
}
else
throw ex();