Skip to content

Commit

Permalink
feature: Add support for spoiler formatting & attachments (#1255)
Browse files Browse the repository at this point in the history
Resolves #1216.

* Add support for spoiler formatting

* Implement support for sending message attachments that are spoilers

* use consistent naming

* update docstring for new isSpoiler param

* move extension method to be under core project, make spoiler prefix a const

* typo fix

* update missing xmldocs

* move SpoilerPrefix const outside of interface

* Add isSpoiler support to webhook client

adds the isSpoiler field to uploading files with a webhook, which will only
insert "SPOILER_" to the start of the filename. This does not include other
fields in the payload, as this is not in the documentation, and was not observed
like in the regular client
  • Loading branch information
Chris-Johnston authored and foxbot committed May 17, 2019
1 parent 76f82d6 commit f3b20b2
Show file tree
Hide file tree
Showing 16 changed files with 104 additions and 71 deletions.
6 changes: 4 additions & 2 deletions src/Discord.Net.Core/Entities/Channels/IMessageChannel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -54,11 +54,12 @@ public interface IMessageChannel : IChannel
/// <param name="isTTS">Whether the message should be read aloud by Discord or not.</param>
/// <param name="embed">The <see cref="Discord.EmbedType.Rich" /> <see cref="Embed" /> to be sent.</param>
/// <param name="options">The options to be used when sending the request.</param>
/// <param name="isSpoiler">Whether the message attachment should be hidden as a spoiler.</param>
/// <returns>
/// A task that represents an asynchronous send operation for delivering the message. The task result
/// contains the sent message.
/// </returns>
Task<IUserMessage> SendFileAsync(string filePath, string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null);
Task<IUserMessage> SendFileAsync(string filePath, string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, bool isSpoiler = false);
/// <summary>
/// Sends a file to this message channel with an optional caption.
/// </summary>
Expand All @@ -82,11 +83,12 @@ public interface IMessageChannel : IChannel
/// <param name="isTTS">Whether the message should be read aloud by Discord or not.</param>
/// <param name="embed">The <see cref="Discord.EmbedType.Rich"/> <see cref="Embed"/> to be sent.</param>
/// <param name="options">The options to be used when sending the request.</param>
/// <param name="isSpoiler">Whether the message attachment should be hidden as a spoiler.</param>
/// <returns>
/// A task that represents an asynchronous send operation for delivering the message. The task result
/// contains the sent message.
/// </returns>
Task<IUserMessage> SendFileAsync(Stream stream, string filename, string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null);
Task<IUserMessage> SendFileAsync(Stream stream, string filename, string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, bool isSpoiler = false);

/// <summary>
/// Gets a message from this message channel.
Expand Down
15 changes: 15 additions & 0 deletions src/Discord.Net.Core/Extensions/AttachmentExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
namespace Discord
{
public static class AttachmentExtensions
{
/// <summary>
/// The prefix applied to files to indicate that it is a spoiler.
/// </summary>
public const string SpoilerPrefix = "SPOILER_";
/// <summary>
/// Gets whether the message's attachments are spoilers or not.
/// </summary>
public static bool IsSpoiler(this IAttachment attachment)
=> attachment.Filename.StartsWith(SpoilerPrefix);
}
}
4 changes: 3 additions & 1 deletion src/Discord.Net.Core/Format.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ namespace Discord
public static class Format
{
// Characters which need escaping
private static readonly string[] SensitiveCharacters = { "\\", "*", "_", "~", "`" };
private static readonly string[] SensitiveCharacters = { "\\", "*", "_", "~", "`", "|" };

/// <summary> Returns a markdown-formatted string with bold formatting. </summary>
public static string Bold(string text) => $"**{text}**";
Expand All @@ -14,6 +14,8 @@ public static class Format
public static string Underline(string text) => $"__{text}__";
/// <summary> Returns a markdown-formatted string with strikethrough formatting. </summary>
public static string Strikethrough(string text) => $"~~{text}~~";
/// <summary> Returns a string with spoiler formatting. </summary>
public static string Spoiler(string text) => $"||{text}||";
/// <summary> Returns a markdown-formatted URL. Only works in <see cref="EmbedBuilder"/> descriptions and fields. </summary>
public static string Url(string text, string url) => $"[{text}]({url})";
/// <summary> Escapes a URL so that a preview is not generated. </summary>
Expand Down
8 changes: 7 additions & 1 deletion src/Discord.Net.Rest/API/Rest/UploadFileParams.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ internal class UploadFileParams
public Optional<string> Nonce { get; set; }
public Optional<bool> IsTTS { get; set; }
public Optional<Embed> Embed { get; set; }
public bool IsSpoiler { get; set; } = false;

public UploadFileParams(Stream file)
{
Expand All @@ -28,7 +29,10 @@ public UploadFileParams(Stream file)
public IReadOnlyDictionary<string, object> ToDictionary()
{
var d = new Dictionary<string, object>();
d["file"] = new MultipartFile(File, Filename.GetValueOrDefault("unknown.dat"));
var filename = Filename.GetValueOrDefault("unknown.dat");
if (IsSpoiler && !filename.StartsWith(AttachmentExtensions.SpoilerPrefix))
filename = filename.Insert(0, AttachmentExtensions.SpoilerPrefix);
d["file"] = new MultipartFile(File, filename);

var payload = new Dictionary<string, object>();
if (Content.IsSpecified)
Expand All @@ -39,6 +43,8 @@ public IReadOnlyDictionary<string, object> ToDictionary()
payload["nonce"] = Nonce.Value;
if (Embed.IsSpecified)
payload["embed"] = Embed.Value;
if (IsSpoiler)
payload["hasSpoiler"] = IsSpoiler.ToString();

var json = new StringBuilder();
using (var text = new StringWriter(json))
Expand Down
8 changes: 7 additions & 1 deletion src/Discord.Net.Rest/API/Rest/UploadWebhookFileParams.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ internal class UploadWebhookFileParams
public Optional<string> AvatarUrl { get; set; }
public Optional<Embed[]> Embeds { get; set; }

public bool IsSpoiler { get; set; } = false;

public UploadWebhookFileParams(Stream file)
{
File = file;
Expand All @@ -30,7 +32,11 @@ public UploadWebhookFileParams(Stream file)
public IReadOnlyDictionary<string, object> ToDictionary()
{
var d = new Dictionary<string, object>();
d["file"] = new MultipartFile(File, Filename.GetValueOrDefault("unknown.dat"));
var filename = Filename.GetValueOrDefault("unknown.dat");
if (IsSpoiler && !filename.StartsWith(AttachmentExtensions.SpoilerPrefix))
filename = filename.Insert(0, AttachmentExtensions.SpoilerPrefix);

d["file"] = new MultipartFile(File, filename);

var payload = new Dictionary<string, object>();
if (Content.IsSpecified)
Expand Down
8 changes: 4 additions & 4 deletions src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -199,18 +199,18 @@ public static async Task<RestUserMessage> SendMessageAsync(IMessageChannel chann
/// <exception cref="IOException">An I/O error occurred while opening the file.</exception>
/// <exception cref="ArgumentOutOfRangeException">Message content is too long, length must be less or equal to <see cref="DiscordConfig.MaxMessageSize"/>.</exception>
public static async Task<RestUserMessage> SendFileAsync(IMessageChannel channel, BaseDiscordClient client,
string filePath, string text, bool isTTS, Embed embed, RequestOptions options)
string filePath, string text, bool isTTS, Embed embed, RequestOptions options, bool isSpoiler)
{
string filename = Path.GetFileName(filePath);
using (var file = File.OpenRead(filePath))
return await SendFileAsync(channel, client, file, filename, text, isTTS, embed, options).ConfigureAwait(false);
return await SendFileAsync(channel, client, file, filename, text, isTTS, embed, options, isSpoiler).ConfigureAwait(false);
}

/// <exception cref="ArgumentOutOfRangeException">Message content is too long, length must be less or equal to <see cref="DiscordConfig.MaxMessageSize"/>.</exception>
public static async Task<RestUserMessage> SendFileAsync(IMessageChannel channel, BaseDiscordClient client,
Stream stream, string filename, string text, bool isTTS, Embed embed, RequestOptions options)
Stream stream, string filename, string text, bool isTTS, Embed embed, RequestOptions options, bool isSpoiler)
{
var args = new UploadFileParams(stream) { Filename = filename, Content = text, IsTTS = isTTS, Embed = embed != null ? embed.ToModel() : Optional<API.Embed>.Unspecified };
var args = new UploadFileParams(stream) { Filename = filename, Content = text, IsTTS = isTTS, Embed = embed != null ? embed.ToModel() : Optional<API.Embed>.Unspecified, IsSpoiler = isSpoiler };
var model = await client.ApiClient.UploadFileAsync(channel.Id, args, options).ConfigureAwait(false);
return RestUserMessage.Create(client, channel, client.CurrentUser, model);
}
Expand Down
6 changes: 3 additions & 3 deletions src/Discord.Net.Rest/Entities/Channels/IRestMessageChannel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,12 +42,12 @@ public interface IRestMessageChannel : IMessageChannel
/// A task that represents an asynchronous send operation for delivering the message. The task result
/// contains the sent message.
/// </returns>
new Task<RestUserMessage> SendFileAsync(string filePath, string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null);
new Task<RestUserMessage> SendFileAsync(string filePath, string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, bool isSpoiler = false);
/// <summary>
/// Sends a file to this message channel with an optional caption.
/// </summary>
/// <remarks>
/// This method follows the same behavior as described in <see cref="IMessageChannel.SendFileAsync(Stream, string, string, bool, Embed, RequestOptions)"/>.
/// This method follows the same behavior as described in <see cref="IMessageChannel.SendFileAsync(Stream, string, string, bool, Embed, RequestOptions, bool)"/>.
/// Please visit its documentation for more details on this method.
/// </remarks>
/// <param name="stream">The <see cref="Stream" /> of the file to be sent.</param>
Expand All @@ -60,7 +60,7 @@ public interface IRestMessageChannel : IMessageChannel
/// A task that represents an asynchronous send operation for delivering the message. The task result
/// contains the sent message.
/// </returns>
new Task<RestUserMessage> SendFileAsync(Stream stream, string filename, string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null);
new Task<RestUserMessage> SendFileAsync(Stream stream, string filename, string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, bool isSpoiler = false);

/// <summary>
/// Gets a message from this message channel.
Expand Down
16 changes: 8 additions & 8 deletions src/Discord.Net.Rest/Entities/Channels/RestDMChannel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -121,12 +121,12 @@ public Task<RestUserMessage> SendMessageAsync(string text = null, bool isTTS = f
/// <exception cref="NotSupportedException"><paramref name="filePath" /> is in an invalid format.</exception>
/// <exception cref="IOException">An I/O error occurred while opening the file.</exception>
/// <exception cref="ArgumentOutOfRangeException">Message content is too long, length must be less or equal to <see cref="DiscordConfig.MaxMessageSize"/>.</exception>
public Task<RestUserMessage> SendFileAsync(string filePath, string text, bool isTTS = false, Embed embed = null, RequestOptions options = null)
=> ChannelHelper.SendFileAsync(this, Discord, filePath, text, isTTS, embed, options);
public Task<RestUserMessage> SendFileAsync(string filePath, string text, bool isTTS = false, Embed embed = null, RequestOptions options = null, bool isSpoiler = false)
=> ChannelHelper.SendFileAsync(this, Discord, filePath, text, isTTS, embed, options, isSpoiler);
/// <inheritdoc />
/// <exception cref="ArgumentOutOfRangeException">Message content is too long, length must be less or equal to <see cref="DiscordConfig.MaxMessageSize"/>.</exception>
public Task<RestUserMessage> SendFileAsync(Stream stream, string filename, string text, bool isTTS = false, Embed embed = null, RequestOptions options = null)
=> ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS, embed, options);
public Task<RestUserMessage> SendFileAsync(Stream stream, string filename, string text, bool isTTS = false, Embed embed = null, RequestOptions options = null, bool isSpoiler = false)
=> ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS, embed, options, isSpoiler);

/// <inheritdoc />
public Task DeleteMessageAsync(ulong messageId, RequestOptions options = null)
Expand Down Expand Up @@ -200,11 +200,11 @@ IAsyncEnumerable<IReadOnlyCollection<IMessage>> IMessageChannel.GetMessagesAsync
async Task<IReadOnlyCollection<IMessage>> IMessageChannel.GetPinnedMessagesAsync(RequestOptions options)
=> await GetPinnedMessagesAsync(options).ConfigureAwait(false);
/// <inheritdoc />
async Task<IUserMessage> IMessageChannel.SendFileAsync(string filePath, string text, bool isTTS, Embed embed, RequestOptions options)
=> await SendFileAsync(filePath, text, isTTS, embed, options).ConfigureAwait(false);
async Task<IUserMessage> IMessageChannel.SendFileAsync(string filePath, string text, bool isTTS, Embed embed, RequestOptions options, bool isSpoiler)
=> await SendFileAsync(filePath, text, isTTS, embed, options, isSpoiler).ConfigureAwait(false);
/// <inheritdoc />
async Task<IUserMessage> IMessageChannel.SendFileAsync(Stream stream, string filename, string text, bool isTTS, Embed embed, RequestOptions options)
=> await SendFileAsync(stream, filename, text, isTTS, embed, options).ConfigureAwait(false);
async Task<IUserMessage> IMessageChannel.SendFileAsync(Stream stream, string filename, string text, bool isTTS, Embed embed, RequestOptions options, bool isSpoiler)
=> await SendFileAsync(stream, filename, text, isTTS, embed, options, isSpoiler).ConfigureAwait(false);
/// <inheritdoc />
async Task<IUserMessage> IMessageChannel.SendMessageAsync(string text, bool isTTS, Embed embed, RequestOptions options)
=> await SendMessageAsync(text, isTTS, embed, options).ConfigureAwait(false);
Expand Down
16 changes: 8 additions & 8 deletions src/Discord.Net.Rest/Entities/Channels/RestGroupChannel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -123,12 +123,12 @@ public Task<RestUserMessage> SendMessageAsync(string text = null, bool isTTS = f
/// <exception cref="NotSupportedException"><paramref name="filePath" /> is in an invalid format.</exception>
/// <exception cref="IOException">An I/O error occurred while opening the file.</exception>
/// <exception cref="ArgumentOutOfRangeException">Message content is too long, length must be less or equal to <see cref="DiscordConfig.MaxMessageSize"/>.</exception>
public Task<RestUserMessage> SendFileAsync(string filePath, string text, bool isTTS = false, Embed embed = null, RequestOptions options = null)
=> ChannelHelper.SendFileAsync(this, Discord, filePath, text, isTTS, embed, options);
public Task<RestUserMessage> SendFileAsync(string filePath, string text, bool isTTS = false, Embed embed = null, RequestOptions options = null, bool isSpoiler = false)
=> ChannelHelper.SendFileAsync(this, Discord, filePath, text, isTTS, embed, options, isSpoiler);
/// <inheritdoc />
/// <exception cref="ArgumentOutOfRangeException">Message content is too long, length must be less or equal to <see cref="DiscordConfig.MaxMessageSize"/>.</exception>
public Task<RestUserMessage> SendFileAsync(Stream stream, string filename, string text, bool isTTS = false, Embed embed = null, RequestOptions options = null)
=> ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS, embed, options);
public Task<RestUserMessage> SendFileAsync(Stream stream, string filename, string text, bool isTTS = false, Embed embed = null, RequestOptions options = null, bool isSpoiler = false)
=> ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS, embed, options, isSpoiler);

/// <inheritdoc />
public Task TriggerTypingAsync(RequestOptions options = null)
Expand Down Expand Up @@ -178,11 +178,11 @@ IAsyncEnumerable<IReadOnlyCollection<IMessage>> IMessageChannel.GetMessagesAsync
async Task<IReadOnlyCollection<IMessage>> IMessageChannel.GetPinnedMessagesAsync(RequestOptions options)
=> await GetPinnedMessagesAsync(options).ConfigureAwait(false);

async Task<IUserMessage> IMessageChannel.SendFileAsync(string filePath, string text, bool isTTS, Embed embed, RequestOptions options)
=> await SendFileAsync(filePath, text, isTTS, embed, options).ConfigureAwait(false);
async Task<IUserMessage> IMessageChannel.SendFileAsync(string filePath, string text, bool isTTS, Embed embed, RequestOptions options, bool isSpoiler)
=> await SendFileAsync(filePath, text, isTTS, embed, options, isSpoiler).ConfigureAwait(false);

async Task<IUserMessage> IMessageChannel.SendFileAsync(Stream stream, string filename, string text, bool isTTS, Embed embed, RequestOptions options)
=> await SendFileAsync(stream, filename, text, isTTS, embed, options).ConfigureAwait(false);
async Task<IUserMessage> IMessageChannel.SendFileAsync(Stream stream, string filename, string text, bool isTTS, Embed embed, RequestOptions options, bool isSpoiler)
=> await SendFileAsync(stream, filename, text, isTTS, embed, options, isSpoiler).ConfigureAwait(false);
async Task<IUserMessage> IMessageChannel.SendMessageAsync(string text, bool isTTS, Embed embed, RequestOptions options)
=> await SendMessageAsync(text, isTTS, embed, options).ConfigureAwait(false);

Expand Down
Loading

0 comments on commit f3b20b2

Please sign in to comment.