Add check for message content intent

pull/1131/head
Tyrrrz 8 months ago
parent 89f2084759
commit a8895663fe

@ -0,0 +1,28 @@
using System.Text.Json;
using DiscordChatExporter.Core.Utils.Extensions;
using JsonExtensions.Reading;
namespace DiscordChatExporter.Core.Discord.Data;
// https://discord.com/developers/docs/resources/application#application-object
public partial record Application(Snowflake Id, string Name, ApplicationFlags Flags)
{
public bool IsMessageContentIntentEnabled =>
Flags.HasFlag(ApplicationFlags.GatewayMessageContent)
|| Flags.HasFlag(ApplicationFlags.GatewayMessageContentLimited);
}
public partial record Application
{
public static Application Parse(JsonElement json)
{
var id = json.GetProperty("id").GetNonWhiteSpaceString().Pipe(Snowflake.Parse);
var name = json.GetProperty("name").GetNonWhiteSpaceString();
var flags =
json.GetPropertyOrNull("flags")?.GetInt32OrNull()?.Pipe(x => (ApplicationFlags)x)
?? ApplicationFlags.None;
return new Application(id, name, flags);
}
}

@ -0,0 +1,20 @@
using System;
namespace DiscordChatExporter.Core.Discord.Data;
// https://discord.com/developers/docs/resources/application#application-object-application-flags
[Flags]
public enum ApplicationFlags
{
None = 0,
ApplicationAutoModerationRuleCreateBadge = 64,
GatewayPresence = 4096,
GatewayPresenceLimited = 8192,
GatewayGuildMembers = 16384,
GatewayGuildMembersLimited = 32768,
VerificationPendingGuildLimit = 65536,
Embedded = 131072,
GatewayMessageContent = 262144,
GatewayMessageContentLimited = 524288,
ApplicationCommandBadge = 8388608
}

@ -62,7 +62,7 @@ public partial record Channel
public static Channel Parse(JsonElement json, Channel? parent = null, int? positionHint = null)
{
var id = json.GetProperty("id").GetNonWhiteSpaceString().Pipe(Snowflake.Parse);
var kind = (ChannelKind)json.GetProperty("type").GetInt32();
var kind = json.GetProperty("type").GetInt32().Pipe(t => (ChannelKind)t);
var guildId =
json.GetPropertyOrNull("guild_id")

@ -118,14 +118,15 @@ public partial record Message
public static Message Parse(JsonElement json)
{
var id = json.GetProperty("id").GetNonWhiteSpaceString().Pipe(Snowflake.Parse);
var kind = (MessageKind)json.GetProperty("type").GetInt32();
var kind = json.GetProperty("type").GetInt32().Pipe(t => (MessageKind)t);
var flags =
(MessageFlags?)json.GetPropertyOrNull("flags")?.GetInt32OrNull() ?? MessageFlags.None;
var author = json.GetProperty("author").Pipe(User.Parse);
json.GetPropertyOrNull("flags")?.GetInt32OrNull()?.Pipe(f => (MessageFlags)f)
?? MessageFlags.None;
var author = json.GetProperty("author").Pipe(User.Parse);
var timestamp = json.GetProperty("timestamp").GetDateTimeOffset();
var editedTimestamp = json.GetPropertyOrNull("edited_timestamp")?.GetDateTimeOffsetOrNull();
var callEndedTimestamp = json.GetPropertyOrNull("call")
?.GetPropertyOrNull("ended_timestamp")
?.GetDateTimeOffsetOrNull();

@ -13,7 +13,7 @@ public record Sticker(Snowflake Id, string Name, StickerFormat Format, string So
{
var id = json.GetProperty("id").GetNonWhiteSpaceString().Pipe(Snowflake.Parse);
var name = json.GetProperty("name").GetNonNullString();
var format = (StickerFormat)json.GetProperty("format_type").GetInt32();
var format = json.GetProperty("format_type").GetInt32().Pipe(t => (StickerFormat)t);
var sourceUrl = ImageCdn.GetStickerUrl(
id,

@ -86,10 +86,13 @@ public class DiscordClient
);
}
private async ValueTask<TokenKind> GetTokenKindAsync(
private async ValueTask<TokenKind> ResolveTokenKindAsync(
CancellationToken cancellationToken = default
)
{
if (_resolvedTokenKind is not null)
return _resolvedTokenKind.Value;
// Try authenticating as a user
using var userResponse = await GetResponseAsync(
"users/@me",
@ -98,7 +101,7 @@ public class DiscordClient
);
if (userResponse.StatusCode != HttpStatusCode.Unauthorized)
return TokenKind.User;
return (_resolvedTokenKind = TokenKind.User).Value;
// Try authenticating as a bot
using var botResponse = await GetResponseAsync(
@ -108,7 +111,7 @@ public class DiscordClient
);
if (botResponse.StatusCode != HttpStatusCode.Unauthorized)
return TokenKind.Bot;
return (_resolvedTokenKind = TokenKind.Bot).Value;
throw new DiscordChatExporterException("Authentication token is invalid.", true);
}
@ -116,11 +119,12 @@ public class DiscordClient
private async ValueTask<HttpResponseMessage> GetResponseAsync(
string url,
CancellationToken cancellationToken = default
)
{
var tokenKind = _resolvedTokenKind ??= await GetTokenKindAsync(cancellationToken);
return await GetResponseAsync(url, tokenKind, cancellationToken);
}
) =>
await GetResponseAsync(
url,
await ResolveTokenKindAsync(cancellationToken),
cancellationToken
);
private async ValueTask<JsonElement> GetJsonResponseAsync(
string url,
@ -152,9 +156,9 @@ public class DiscordClient
_
=> throw new DiscordChatExporterException(
$"""
Request to '{url}' failed: {response.StatusCode.ToString().ToSpaceSeparatedWords().ToLowerInvariant()}.
Response content: {await response.Content.ReadAsStringAsync(cancellationToken)}
""",
Request to '{url}' failed: {response.StatusCode.ToString().ToSpaceSeparatedWords().ToLowerInvariant()}.
Response content: {await response.Content.ReadAsStringAsync(cancellationToken)}
""",
true
)
};
@ -174,6 +178,14 @@ public class DiscordClient
: null;
}
public async ValueTask<Application> GetApplicationAsync(
CancellationToken cancellationToken = default
)
{
var response = await GetJsonResponseAsync("applications/@me", cancellationToken);
return Application.Parse(response);
}
public async ValueTask<User?> TryGetUserAsync(
Snowflake userId,
CancellationToken cancellationToken = default
@ -285,7 +297,7 @@ public class DiscordClient
if (guildId == Guild.DirectMessages.Id)
yield break;
var tokenKind = _resolvedTokenKind ??= await GetTokenKindAsync(cancellationToken);
var tokenKind = await ResolveTokenKindAsync(cancellationToken);
var channels = (await GetGuildChannelsAsync(guildId, cancellationToken))
// Categories cannot have threads
@ -559,6 +571,22 @@ public class DiscordClient
[EnumeratorCancellation] CancellationToken cancellationToken = default
)
{
// If authenticating as a bot, ensure that we have the correct permissions to
// retrieve message content.
// https://github.com/Tyrrrz/DiscordChatExporter/issues/1106#issuecomment-1741548959
var tokenKind = await ResolveTokenKindAsync(cancellationToken);
if (tokenKind == TokenKind.Bot)
{
var application = await GetApplicationAsync(cancellationToken);
if (!application.IsMessageContentIntentEnabled)
{
throw new DiscordChatExporterException(
"Bot account does not have the Message Content Intent enabled.",
true
);
}
}
// Get the last message in the specified range, so we can later calculate the
// progress based on the difference between message timestamps.
// This also snapshots the boundaries, which means that messages posted after

Loading…
Cancel
Save