mirror of
https://github.com/Tyrrrz/DiscordChatExporter.git
synced 2025-06-08 10:22:25 -04:00
Add check for message content intent
This commit is contained in:
parent
89f2084759
commit
a8895663fe
6 changed files with 96 additions and 19 deletions
28
DiscordChatExporter.Core/Discord/Data/Application.cs
Normal file
28
DiscordChatExporter.Core/Discord/Data/Application.cs
Normal file
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
20
DiscordChatExporter.Core/Discord/Data/ApplicationFlags.cs
Normal file
20
DiscordChatExporter.Core/Discord/Data/ApplicationFlags.cs
Normal file
|
@ -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)
|
public static Channel Parse(JsonElement json, Channel? parent = null, int? positionHint = null)
|
||||||
{
|
{
|
||||||
var id = json.GetProperty("id").GetNonWhiteSpaceString().Pipe(Snowflake.Parse);
|
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 =
|
var guildId =
|
||||||
json.GetPropertyOrNull("guild_id")
|
json.GetPropertyOrNull("guild_id")
|
||||||
|
|
|
@ -118,14 +118,15 @@ public partial record Message
|
||||||
public static Message Parse(JsonElement json)
|
public static Message Parse(JsonElement json)
|
||||||
{
|
{
|
||||||
var id = json.GetProperty("id").GetNonWhiteSpaceString().Pipe(Snowflake.Parse);
|
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);
|
|
||||||
|
|
||||||
|
var flags =
|
||||||
|
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 timestamp = json.GetProperty("timestamp").GetDateTimeOffset();
|
||||||
var editedTimestamp = json.GetPropertyOrNull("edited_timestamp")?.GetDateTimeOffsetOrNull();
|
var editedTimestamp = json.GetPropertyOrNull("edited_timestamp")?.GetDateTimeOffsetOrNull();
|
||||||
|
|
||||||
var callEndedTimestamp = json.GetPropertyOrNull("call")
|
var callEndedTimestamp = json.GetPropertyOrNull("call")
|
||||||
?.GetPropertyOrNull("ended_timestamp")
|
?.GetPropertyOrNull("ended_timestamp")
|
||||||
?.GetDateTimeOffsetOrNull();
|
?.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 id = json.GetProperty("id").GetNonWhiteSpaceString().Pipe(Snowflake.Parse);
|
||||||
var name = json.GetProperty("name").GetNonNullString();
|
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(
|
var sourceUrl = ImageCdn.GetStickerUrl(
|
||||||
id,
|
id,
|
||||||
|
|
|
@ -86,10 +86,13 @@ public class DiscordClient
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async ValueTask<TokenKind> GetTokenKindAsync(
|
private async ValueTask<TokenKind> ResolveTokenKindAsync(
|
||||||
CancellationToken cancellationToken = default
|
CancellationToken cancellationToken = default
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
|
if (_resolvedTokenKind is not null)
|
||||||
|
return _resolvedTokenKind.Value;
|
||||||
|
|
||||||
// Try authenticating as a user
|
// Try authenticating as a user
|
||||||
using var userResponse = await GetResponseAsync(
|
using var userResponse = await GetResponseAsync(
|
||||||
"users/@me",
|
"users/@me",
|
||||||
|
@ -98,7 +101,7 @@ public class DiscordClient
|
||||||
);
|
);
|
||||||
|
|
||||||
if (userResponse.StatusCode != HttpStatusCode.Unauthorized)
|
if (userResponse.StatusCode != HttpStatusCode.Unauthorized)
|
||||||
return TokenKind.User;
|
return (_resolvedTokenKind = TokenKind.User).Value;
|
||||||
|
|
||||||
// Try authenticating as a bot
|
// Try authenticating as a bot
|
||||||
using var botResponse = await GetResponseAsync(
|
using var botResponse = await GetResponseAsync(
|
||||||
|
@ -108,7 +111,7 @@ public class DiscordClient
|
||||||
);
|
);
|
||||||
|
|
||||||
if (botResponse.StatusCode != HttpStatusCode.Unauthorized)
|
if (botResponse.StatusCode != HttpStatusCode.Unauthorized)
|
||||||
return TokenKind.Bot;
|
return (_resolvedTokenKind = TokenKind.Bot).Value;
|
||||||
|
|
||||||
throw new DiscordChatExporterException("Authentication token is invalid.", true);
|
throw new DiscordChatExporterException("Authentication token is invalid.", true);
|
||||||
}
|
}
|
||||||
|
@ -116,11 +119,12 @@ public class DiscordClient
|
||||||
private async ValueTask<HttpResponseMessage> GetResponseAsync(
|
private async ValueTask<HttpResponseMessage> GetResponseAsync(
|
||||||
string url,
|
string url,
|
||||||
CancellationToken cancellationToken = default
|
CancellationToken cancellationToken = default
|
||||||
)
|
) =>
|
||||||
{
|
await GetResponseAsync(
|
||||||
var tokenKind = _resolvedTokenKind ??= await GetTokenKindAsync(cancellationToken);
|
url,
|
||||||
return await GetResponseAsync(url, tokenKind, cancellationToken);
|
await ResolveTokenKindAsync(cancellationToken),
|
||||||
}
|
cancellationToken
|
||||||
|
);
|
||||||
|
|
||||||
private async ValueTask<JsonElement> GetJsonResponseAsync(
|
private async ValueTask<JsonElement> GetJsonResponseAsync(
|
||||||
string url,
|
string url,
|
||||||
|
@ -152,9 +156,9 @@ public class DiscordClient
|
||||||
_
|
_
|
||||||
=> throw new DiscordChatExporterException(
|
=> throw new DiscordChatExporterException(
|
||||||
$"""
|
$"""
|
||||||
Request to '{url}' failed: {response.StatusCode.ToString().ToSpaceSeparatedWords().ToLowerInvariant()}.
|
Request to '{url}' failed: {response.StatusCode.ToString().ToSpaceSeparatedWords().ToLowerInvariant()}.
|
||||||
Response content: {await response.Content.ReadAsStringAsync(cancellationToken)}
|
Response content: {await response.Content.ReadAsStringAsync(cancellationToken)}
|
||||||
""",
|
""",
|
||||||
true
|
true
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
|
@ -174,6 +178,14 @@ public class DiscordClient
|
||||||
: null;
|
: 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(
|
public async ValueTask<User?> TryGetUserAsync(
|
||||||
Snowflake userId,
|
Snowflake userId,
|
||||||
CancellationToken cancellationToken = default
|
CancellationToken cancellationToken = default
|
||||||
|
@ -285,7 +297,7 @@ public class DiscordClient
|
||||||
if (guildId == Guild.DirectMessages.Id)
|
if (guildId == Guild.DirectMessages.Id)
|
||||||
yield break;
|
yield break;
|
||||||
|
|
||||||
var tokenKind = _resolvedTokenKind ??= await GetTokenKindAsync(cancellationToken);
|
var tokenKind = await ResolveTokenKindAsync(cancellationToken);
|
||||||
|
|
||||||
var channels = (await GetGuildChannelsAsync(guildId, cancellationToken))
|
var channels = (await GetGuildChannelsAsync(guildId, cancellationToken))
|
||||||
// Categories cannot have threads
|
// Categories cannot have threads
|
||||||
|
@ -559,6 +571,22 @@ public class DiscordClient
|
||||||
[EnumeratorCancellation] CancellationToken cancellationToken = default
|
[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
|
// Get the last message in the specified range, so we can later calculate the
|
||||||
// progress based on the difference between message timestamps.
|
// progress based on the difference between message timestamps.
|
||||||
// This also snapshots the boundaries, which means that messages posted after
|
// This also snapshots the boundaries, which means that messages posted after
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue