#Art Review Discord API Issues

1 messages · Page 1 of 1 (latest)

fathom night
#

Vedal mentioned in yesterday's dev stream that the issue is that Discord.NET's GetActiveThreadsAsync uses the /guilds/{guild.id}/threads/active endpoint to fetch the posts from the #1095941852068331560 channel.

As he mentioned the request can timeout due to the insane amount of active posts.

Discord's frontend uses the /channels/{channel.id}/threads/search endpoint which is undocumented.
The important thing about this is that it has a limit url param that limits the max number of threads and messages returned.

For example, when filtering for the evil tag this is the request url:

https://discord.com/api/v9/channels/1095941852068331560/threads/search?archived=true&sort_by=last_message_time&sort_order=desc&limit=25&tag=1343789002502705223&tag_setting=match_some&offset=0

I don't know why archived is set to true in the request, but the param can be dropped.

Since it is not wrapped by Discord.NET the response would have to be parsed manually, but luckily the starter messages are available in the first_messages array of the response's root element.

private static async Task<string> GetThreads(string token, ulong channelId, int limit = 20) {
    using HttpClient http = new();
    http.BaseAddress = new Uri("https://discord.com/api/v9/");
    http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bot", token);

    return await http.GetStringAsync($"channels/{channelId}/threads/search?sort_by=last_message_time&sort_order=desc&limit={limit}");
}

While the endpoint is undocumented and is seemingly only used by the Discord client it may change without notice, but maybe it would be worth a try.

#

After that the message ids can be extracted from the json and fetched again by Discord.NET to integrate better into the existing code base or the other needed properties can be extracted in a similar manner.

private static IEnumerable<ulong> GetFirstMessageIds(string json) {
    using JsonDocument document = JsonDocument.Parse(json);
    JsonElement messages = document.RootElement.GetProperty("first_messages");

    foreach (JsonElement message in messages.EnumerateArray())
        yield return ulong.Parse(message.GetProperty("id").GetString()!);
}
fathom night
#

Example:

internal record Attachment {
    public required string Url         { get; init; }
    public required string FileName    { get; init; }
    public required string ContentType { get; init; }
}

internal record ArtPost {
    public required string                  Name        { get; init; }
    public required string                  Content     { get; init; }
    public required IEnumerable<Attachment> Attachments { get; init; }

    public static IEnumerable<ArtPost> ArtPostsFromJson(string json) {
        using JsonDocument document = JsonDocument.Parse(json);

        JsonElement[] threads = document.RootElement.GetProperty("threads").EnumerateArray().ToArray();
        JsonElement[] messages = document.RootElement.GetProperty("first_messages").EnumerateArray().ToArray();

        for (int i = 0; i < threads.Length; i++)
            yield return new ArtPost {
                Name = threads[i].GetProperty("name").GetString()!,
                Content = messages[i].GetProperty("content").GetString()!,
                Attachments = messages[i].GetProperty("attachments").EnumerateArray().Select(attachment => new Attachment {
                    Url = attachment.GetProperty("url").GetString()!,
                    FileName = attachment.GetProperty("filename").GetString()!,
                    ContentType = attachment.GetProperty("content_type").GetString()!
                })
            };
    }
}
placid cape
#

The API is officialized on OpenAPI spec and it has a reference in the dev blog. Just poorly documented