Browse Source

[Feature] Application Role Connections Metadata support (#2530)

* models & internal methods

* moar stuff

* working `Get` & `ModifyRoleConnectionMetadata`

* comments & dots

* get & modify user's role connection

* Update src/Discord.Net.Core/Entities/ApplicationRoleConnection/RoleConnection.cs

* Update src/Discord.Net.Core/Entities/ApplicationRoleConnection/RoleConnection.cs

Co-authored-by: Casmir <68127614+csmir@users.noreply.github.com>
pull/2573/head
Misha133 GitHub 2 years ago
parent
commit
37d868f934
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 585 additions and 1 deletions
  1. +43
    -0
      src/Discord.Net.Core/Entities/ApplicationRoleConnection/RoleConnection.cs
  2. +65
    -0
      src/Discord.Net.Core/Entities/ApplicationRoleConnection/RoleConnectionMetadata.cs
  3. +138
    -0
      src/Discord.Net.Core/Entities/ApplicationRoleConnection/RoleConnectionMetadataProperties.cs
  4. +49
    -0
      src/Discord.Net.Core/Entities/ApplicationRoleConnection/RoleConnectionMetadataType.cs
  5. +141
    -0
      src/Discord.Net.Core/Entities/ApplicationRoleConnection/RoleConnectionProperties.cs
  6. +16
    -0
      src/Discord.Net.Rest/API/Common/RoleConnection.cs
  7. +25
    -0
      src/Discord.Net.Rest/API/Common/RoleConnectionMetadata.cs
  8. +72
    -0
      src/Discord.Net.Rest/ClientHelper.cs
  9. +19
    -0
      src/Discord.Net.Rest/DiscordRestApiClient.cs
  10. +17
    -1
      src/Discord.Net.Rest/DiscordRestClient.cs

+ 43
- 0
src/Discord.Net.Core/Entities/ApplicationRoleConnection/RoleConnection.cs View File

@@ -0,0 +1,43 @@
using System;
using System.Collections.Generic;

namespace Discord;

/// <summary>
/// Represents the connection object that the user has attached.
/// </summary>
public class RoleConnection
{
/// <summary>
/// Gets the vanity name of the platform a bot has connected to.
/// </summary>
public string PlatformName { get; }

/// <summary>
/// Gets the username on the platform a bot has connected to.
/// </summary>
public string PlatformUsername { get; }

/// <summary>
/// Gets the object mapping <see cref="RoleConnectionMetadata"/> keys to their string-ified values.
/// </summary>
public IReadOnlyDictionary<string, string> Metadata { get; }

internal RoleConnection(string platformName, string platformUsername, IReadOnlyDictionary<string, string> metadata)
{
PlatformName = platformName;
PlatformUsername = platformUsername;
Metadata = metadata;
}

/// <summary>
/// Initializes a new <see cref="RoleConnectionProperties"/> with the data from this object.
/// </summary>
public RoleConnectionProperties ToRoleConnectionProperties()
=> new()
{
PlatformName = PlatformName,
PlatformUsername = PlatformUsername,
Metadata = Metadata.ToDictionary()
};
}

+ 65
- 0
src/Discord.Net.Core/Entities/ApplicationRoleConnection/RoleConnectionMetadata.cs View File

@@ -0,0 +1,65 @@
using System.Collections.Generic;
using System.Collections.Immutable;

namespace Discord;

/// <summary>
/// Represents the role connection metadata object.
/// </summary>
public class RoleConnectionMetadata
{
/// <summary>
/// Gets the of metadata value.
/// </summary>
public RoleConnectionMetadataType Type { get; }

/// <summary>
/// Gets the dictionary key for the metadata field.
/// </summary>
public string Key { get; }

/// <summary>
/// Gets the name of the metadata field.
/// </summary>
public string Name { get; }

/// <summary>
/// Gets the description of the metadata field.
/// </summary>
public string Description { get; }

/// <summary>
/// Gets translations of the name. <see langword="null"/> if not set.
/// </summary>
public IReadOnlyDictionary<string, string> NameLocalizations { get; }

/// <summary>
/// Gets translations of the description. <see langword="null"/> if not set.
/// </summary>
public IReadOnlyDictionary<string, string> DescriptionLocalizations { get; }

internal RoleConnectionMetadata(RoleConnectionMetadataType type, string key, string name, string description,
IDictionary<string, string> nameLocalizations = null, IDictionary<string, string> descriptionLocalizations = null)
{
Type = type;
Key = key;
Name = name;
Description = description;
NameLocalizations = nameLocalizations?.ToImmutableDictionary();
DescriptionLocalizations = descriptionLocalizations?.ToImmutableDictionary();
}

/// <summary>
/// Initializes a new <see cref="RoleConnectionMetadataProperties"/> with the data from this object.
/// </summary>
public RoleConnectionMetadataProperties ToRoleConnectionMetadataProperties()
=> new()
{
Name = Name,
Description = Description,
Type = Type,
Key = Key,
NameLocalizations = NameLocalizations,
DescriptionLocalizations = DescriptionLocalizations
};
}

+ 138
- 0
src/Discord.Net.Core/Entities/ApplicationRoleConnection/RoleConnectionMetadataProperties.cs View File

@@ -0,0 +1,138 @@
using System.Collections.Generic;
using System;
using System.Collections.Immutable;

namespace Discord;

/// <summary>
/// Properties object used to create or modify <see cref="RoleConnectionMetadata"/> object.
/// </summary>
public class RoleConnectionMetadataProperties
{
private const int MaxKeyLength = 50;
private const int MaxNameLength = 100;
private const int MaxDescriptionLength = 200;

private string _key;
private string _name;
private string _description;

private IReadOnlyDictionary<string, string> _nameLocalizations;
private IReadOnlyDictionary<string, string> _descriptionLocalizations;

/// <summary>
/// Gets or sets the of metadata value.
/// </summary>
public RoleConnectionMetadataType Type { get; set; }

/// <summary>
/// Gets or sets the dictionary key for the metadata field.
/// </summary>
public string Key
{
get => _key;
set
{
Preconditions.AtMost(value.Length, MaxKeyLength, nameof(Key), $"Key length must be less than or equal to {MaxKeyLength}");
_key = value;
}
}

/// <summary>
/// Gets or sets the name of the metadata field.
/// </summary>
public string Name
{
get => _name;
set
{
Preconditions.AtMost(value.Length, MaxNameLength, nameof(Name), $"Name length must be less than or equal to {MaxNameLength}");
_name = value;
}
}

/// <summary>
/// Gets or sets the description of the metadata field.
/// </summary>
public string Description
{
get => _description;
set
{
Preconditions.AtMost(value.Length, MaxDescriptionLength, nameof(Description), $"Description length must be less than or equal to {MaxDescriptionLength}");
_description = value;
}
}

/// <summary>
/// Gets or sets translations of the name. <see langword="null"/> if not set.
/// </summary>
public IReadOnlyDictionary<string, string> NameLocalizations
{
get => _nameLocalizations;
set
{
if (value is not null)
foreach (var localization in value)
if (localization.Value.Length > MaxNameLength)
throw new ArgumentException($"Name localization length must be less than or equal to {MaxNameLength}. Locale '{localization}'");
_nameLocalizations = value;
}
}

/// <summary>
/// Gets or sets translations of the description. <see langword="null"/> if not set.
/// </summary>
public IReadOnlyDictionary<string, string> DescriptionLocalizations
{
get => _descriptionLocalizations;
set
{
if (value is not null)
foreach (var localization in value)
if (localization.Value.Length > MaxDescriptionLength)
throw new ArgumentException($"Description localization length must be less than or equal to {MaxDescriptionLength}. Locale '{localization}'");
_descriptionLocalizations = value;
}
}

/// <summary>
/// Initializes a new instance of <see cref="RoleConnectionMetadataProperties"/>.
/// </summary>
/// <param name="type">The type of the metadata value.</param>
/// <param name="key">The dictionary key for the metadata field. Max 50 characters.</param>
/// <param name="name">The name of the metadata visible in user profile. Max 100 characters.</param>
/// <param name="description">The description of the metadata visible in user profile. Max 200 characters.</param>
/// <param name="nameLocalizations">Translations for the name.</param>
/// <param name="descriptionLocalizations">Translations for the description.</param>
public RoleConnectionMetadataProperties(RoleConnectionMetadataType type, string key, string name, string description,
IDictionary<string, string> nameLocalizations = null, IDictionary<string, string> descriptionLocalizations = null)
{
Type = type;
Key = key;
Name = name;
Description = description;
NameLocalizations = nameLocalizations?.ToImmutableDictionary();
DescriptionLocalizations = descriptionLocalizations?.ToImmutableDictionary();
}

/// <summary>
/// Initializes a new instance of <see cref="RoleConnectionMetadataProperties"/>.
/// </summary>
public RoleConnectionMetadataProperties() { }

/// <summary>
/// Initializes a new <see cref="RoleConnectionMetadataProperties"/> with the data from provided <see cref="RoleConnectionMetadata"/>.
/// </summary>
public static RoleConnectionMetadataProperties FromRoleConnectionMetadata(RoleConnectionMetadata metadata)
=> new()
{
Name = metadata.Name,
Description = metadata.Description,
Type = metadata.Type,
Key = metadata.Key,
NameLocalizations = metadata.NameLocalizations,
DescriptionLocalizations = metadata.DescriptionLocalizations
};
}


+ 49
- 0
src/Discord.Net.Core/Entities/ApplicationRoleConnection/RoleConnectionMetadataType.cs View File

@@ -0,0 +1,49 @@
using System;

namespace Discord;

/// <summary>
/// Represents the type of Application Role Connection Metadata.
/// </summary>
public enum RoleConnectionMetadataType
{
/// <summary>
/// The metadata's integer value is less than or equal to the guild's configured value.
/// </summary>
IntegerLessOrEqual = 1,

/// <summary>
/// The metadata's integer value is greater than or equal to the guild's configured value.
/// </summary>
IntegerGreaterOrEqual = 2,

/// <summary>
/// The metadata's integer value is equal to the guild's configured value.
/// </summary>
IntegerEqual = 3,

/// <summary>
/// The metadata's integer value is not equal to the guild's configured value.
/// </summary>
IntegerNotEqual = 4,

/// <summary>
/// The metadata's ISO8601 string value is less or equal to the guild's configured value.
/// </summary>
DateTimeLessOrEqual = 5,

/// <summary>
/// The metadata's ISO8601 string value is greater to the guild's configured value.
/// </summary>
DateTimeGreaterOrEqual = 6,

/// <summary>
/// The metadata's integer value is equal to the guild's configured value.
/// </summary>
BoolEqual = 7,

/// <summary>
/// The metadata's integer value is equal to the guild's configured value.
/// </summary>
BoolNotEqual = 8,
}

+ 141
- 0
src/Discord.Net.Core/Entities/ApplicationRoleConnection/RoleConnectionProperties.cs View File

@@ -0,0 +1,141 @@
using System;
using System.Collections.Generic;

namespace Discord;

/// <summary>
/// Represents the properties used to modify user's <see cref="RoleConnection"/>.
/// </summary>
public class RoleConnectionProperties
{
private const int MaxPlatformNameLength = 50;
private const int MaxPlatformUsernameLength = 100;
private const int MaxMetadataRecords = 100;

private string _platformName;
private string _platformUsername;
private Dictionary<string, string> _metadata;

/// <summary>
/// Gets or sets the vanity name of the platform a bot has connected. Max 50 characters.
/// </summary>
public string PlatformName
{
get => _platformName;
set
{
if (value is not null)
Preconditions.AtMost(value.Length, MaxPlatformNameLength, nameof(PlatformName), $"Platform name length must be less or equal to {MaxPlatformNameLength}");
_platformName = value;
}
}

/// <summary>
/// Gets or sets the username on the platform a bot has connected. Max 100 characters.
/// </summary>
public string PlatformUsername
{
get => _platformUsername;
set
{
if(value is not null)
Preconditions.AtMost(value.Length, MaxPlatformUsernameLength, nameof(PlatformUsername), $"Platform username length must be less or equal to {MaxPlatformUsernameLength}");
_platformUsername = value;
}
}

/// <summary>
/// Gets or sets object mapping <see cref="RoleConnectionMetadata"/> keys to their string-ified values.
/// </summary>
public Dictionary<string, string> Metadata
{
get => _metadata;
set
{
if (value is not null)
Preconditions.AtMost(value.Count, MaxPlatformUsernameLength, nameof(Metadata), $"Metadata records count must be less or equal to {MaxMetadataRecords}");
_metadata = value;
}
}

/// <summary>
/// Adds a metadata record with the provided key and value.
/// </summary>
/// <returns>The current <see cref="RoleConnectionProperties"/>.</returns>
public RoleConnectionProperties WithDate(string key, DateTimeOffset value)
=> AddMetadataRecord(key, value.ToString("O"));

/// <summary>
/// Adds a metadata record with the provided key and value.
/// </summary>
/// <returns>The current <see cref="RoleConnectionProperties"/>.</returns>
public RoleConnectionProperties WithBool(string key, bool value)
=> AddMetadataRecord(key, value ? "1" : "0");

/// <summary>
/// Adds a metadata record with the provided key and value.
/// </summary>
/// <returns>The current <see cref="RoleConnectionProperties"/>.</returns>
public RoleConnectionProperties WithNumber(string key, int value)
=> AddMetadataRecord(key, value.ToString());

/// <summary>
/// Adds a metadata record with the provided key and value.
/// </summary>
/// <returns>The current <see cref="RoleConnectionProperties"/>.</returns>
public RoleConnectionProperties WithNumber(string key, uint value)
=> AddMetadataRecord(key, value.ToString());

/// <summary>
/// Adds a metadata record with the provided key and value.
/// </summary>
/// <returns>The current <see cref="RoleConnectionProperties"/>.</returns>
public RoleConnectionProperties WithNumber(string key, long value)
=> AddMetadataRecord(key, value.ToString());

/// <summary>
/// Adds a metadata record with the provided key and value.
/// </summary>
/// <returns>The current <see cref="RoleConnectionProperties"/>.</returns>
public RoleConnectionProperties WithNumber(string key, ulong value)
=> AddMetadataRecord(key, value.ToString());

internal RoleConnectionProperties AddMetadataRecord(string key, string value)
{
Metadata ??= new Dictionary<string, string>();
if(!Metadata.ContainsKey(key))
Preconditions.AtMost(Metadata.Count + 1, MaxPlatformUsernameLength, nameof(Metadata), $"Metadata records count must be less or equal to {MaxMetadataRecords}");

_metadata[key] = value;
return this;
}

/// <summary>
/// Initializes a new instance of <see cref="RoleConnectionProperties"/>.
/// </summary>
/// <param name="platformName">The name of the platform a bot has connected.</param>s
/// <param name="platformUsername">Gets the username on the platform a bot has connected.</param>
/// <param name="metadata">Object mapping <see cref="RoleConnectionMetadata"/> keys to their values.</param>
public RoleConnectionProperties(string platformName, string platformUsername, IDictionary<string, string> metadata = null)
{
PlatformName = platformName;
PlatformUsername = platformUsername;
Metadata = metadata.ToDictionary();
}

/// <summary>
/// Initializes a new instance of <see cref="RoleConnectionProperties"/>.
/// </summary>
public RoleConnectionProperties() {}

/// <summary>
/// Initializes a new <see cref="RoleConnectionProperties"/> with the data from provided <see cref="RoleConnection"/>.
/// </summary>
public static RoleConnectionProperties FromRoleConnection(RoleConnection roleConnection)
=> new()
{
PlatformName = roleConnection.PlatformName,
PlatformUsername = roleConnection.PlatformUsername,
Metadata = roleConnection.Metadata.ToDictionary()
};
}

+ 16
- 0
src/Discord.Net.Rest/API/Common/RoleConnection.cs View File

@@ -0,0 +1,16 @@
using Newtonsoft.Json;
using System.Collections.Generic;

namespace Discord.API;

public class RoleConnection
{
[JsonProperty("platform_name")]
public Optional<string> PlatformName { get; set; }

[JsonProperty("platform_username")]
public Optional<string> PlatformUsername { get; set; }

[JsonProperty("metadata")]
public Optional<Dictionary<string, string>> Metadata { get; set; }
}

+ 25
- 0
src/Discord.Net.Rest/API/Common/RoleConnectionMetadata.cs View File

@@ -0,0 +1,25 @@
using Newtonsoft.Json;
using System.Collections.Generic;

namespace Discord.API;

public class RoleConnectionMetadata
{
[JsonProperty("type")]
public RoleConnectionMetadataType Type { get; set; }

[JsonProperty("key")]
public string Key { get; set; }

[JsonProperty("name")]
public string Name { get; set; }

[JsonProperty("description")]
public string Description { get; set; }

[JsonProperty("name_localizations")]
public Optional<Dictionary<string, string>> NameLocalizations { get; set; }

[JsonProperty("description_localizations")]
public Optional<Dictionary<string, string>> DescriptionLocalizations { get; set; }
}

+ 72
- 0
src/Discord.Net.Rest/ClientHelper.cs View File

@@ -264,5 +264,77 @@ namespace Discord.Rest
public static Task RemoveRoleAsync(BaseDiscordClient client, ulong guildId, ulong userId, ulong roleId, RequestOptions options = null)
=> client.ApiClient.RemoveRoleAsync(guildId, userId, roleId, options);
#endregion

#region Role Connection Metadata

public static async Task<IReadOnlyCollection<RoleConnectionMetadata>> GetRoleConnectionMetadataRecordsAsync(BaseDiscordClient client, RequestOptions options = null)
=> (await client.ApiClient.GetApplicationRoleConnectionMetadataRecordsAsync(options))
.Select(model
=> new RoleConnectionMetadata(
model.Type,
model.Key,
model.Name,
model.Description,
model.NameLocalizations.IsSpecified
? model.NameLocalizations.Value?.ToImmutableDictionary()
: null,
model.DescriptionLocalizations.IsSpecified
? model.DescriptionLocalizations.Value?.ToImmutableDictionary()
: null))
.ToImmutableArray();

public static async Task<IReadOnlyCollection<RoleConnectionMetadata>> ModifyRoleConnectionMetadataRecordsAsync(ICollection<RoleConnectionMetadataProperties> metadata, BaseDiscordClient client, RequestOptions options = null)
=> (await client.ApiClient.UpdateApplicationRoleConnectionMetadataRecordsAsync(metadata
.Select(x => new API.RoleConnectionMetadata
{
Name = x.Name,
Description = x.Description,
Key = x.Key,
Type = x.Type,
NameLocalizations = x.NameLocalizations?.ToDictionary(),
DescriptionLocalizations = x.DescriptionLocalizations?.ToDictionary()
}).ToArray()))
.Select(model
=> new RoleConnectionMetadata(
model.Type,
model.Key,
model.Name,
model.Description,
model.NameLocalizations.IsSpecified
? model.NameLocalizations.Value?.ToImmutableDictionary()
: null,
model.DescriptionLocalizations.IsSpecified
? model.DescriptionLocalizations.Value?.ToImmutableDictionary()
: null))
.ToImmutableArray();

public static async Task<RoleConnection> GetUserRoleConnectionAsync(ulong applicationId, BaseDiscordClient client, RequestOptions options = null)
{
var roleConnection = await client.ApiClient.GetUserApplicationRoleConnectionAsync(applicationId, options);

return new RoleConnection(roleConnection.PlatformName.GetValueOrDefault(null),
roleConnection.PlatformUsername.GetValueOrDefault(null),
roleConnection.Metadata.GetValueOrDefault());
}

public static async Task<RoleConnection> ModifyUserRoleConnectionAsync(ulong applicationId, RoleConnectionProperties roleConnection, BaseDiscordClient client, RequestOptions options = null)
{
var updatedConnection = await client.ApiClient.ModifyUserApplicationRoleConnectionAsync(applicationId,
new API.RoleConnection
{
PlatformName = roleConnection.PlatformName,
PlatformUsername = roleConnection.PlatformUsername,
Metadata = roleConnection.Metadata
}, options);

return new RoleConnection(
updatedConnection.PlatformName.GetValueOrDefault(null),
updatedConnection.PlatformUsername.GetValueOrDefault(null),
updatedConnection.Metadata.GetValueOrDefault()?.ToImmutableDictionary()
);
}


#endregion
}
}

+ 19
- 0
src/Discord.Net.Rest/DiscordRestApiClient.cs View File

@@ -4,12 +4,15 @@ using Discord.Net;
using Discord.Net.Converters;
using Discord.Net.Queue;
using Discord.Net.Rest;

using Newtonsoft.Json;

using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.ComponentModel.Design;
using System.Diagnostics;
using System.Diagnostics.Contracts;
using System.Globalization;
using System.IO;
using System.Linq;
@@ -2507,5 +2510,21 @@ namespace Discord.API
}

#endregion

#region Application Role Connections Metadata

public async Task<RoleConnectionMetadata[]> GetApplicationRoleConnectionMetadataRecordsAsync(RequestOptions options = null)
=> await SendAsync<RoleConnectionMetadata[]>("GET", () => $"applications/{CurrentApplicationId}/role-connections/metadata", new BucketIds(), options: options).ConfigureAwait(false);

public async Task<RoleConnectionMetadata[]> UpdateApplicationRoleConnectionMetadataRecordsAsync(RoleConnectionMetadata[] roleConnections, RequestOptions options = null)
=> await SendJsonAsync <RoleConnectionMetadata[]>("PUT", () => $"applications/{CurrentApplicationId}/role-connections/metadata", roleConnections, new BucketIds(), options: options).ConfigureAwait(false);

public async Task<RoleConnection> GetUserApplicationRoleConnectionAsync(ulong applicationId, RequestOptions options = null)
=> await SendAsync<RoleConnection>("GET", () => $"users/@me/applications/{applicationId}/role-connection", new BucketIds(), options: options);

public async Task<RoleConnection> ModifyUserApplicationRoleConnectionAsync(ulong applicationId, RoleConnection connection, RequestOptions options = null)
=> await SendJsonAsync<RoleConnection>("PUT", () => $"users/@me/applications/{applicationId}/role-connection", connection, new BucketIds(), options: options);

#endregion
}
}

+ 17
- 1
src/Discord.Net.Rest/DiscordRestClient.cs View File

@@ -231,7 +231,23 @@ namespace Discord.Rest
=> MessageHelper.RemoveAllReactionsAsync(channelId, messageId, this, options);
public Task RemoveAllReactionsForEmoteAsync(ulong channelId, ulong messageId, IEmote emote, RequestOptions options = null)
=> MessageHelper.RemoveAllReactionsForEmoteAsync(channelId, messageId, emote, this, options);
#endregion

public Task<IReadOnlyCollection<RoleConnectionMetadata>> GetRoleConnectionMetadataRecordsAsync(RequestOptions options = null)
=> ClientHelper.GetRoleConnectionMetadataRecordsAsync(this, options);

public Task<IReadOnlyCollection<RoleConnectionMetadata>> ModifyRoleConnectionMetadataRecordsAsync(ICollection<RoleConnectionMetadataProperties> metadata, RequestOptions options = null)
{
Preconditions.AtMost(metadata.Count, 5, nameof(metadata), "An application can have a maximum of 5 metadata records.");
return ClientHelper.ModifyRoleConnectionMetadataRecordsAsync(metadata, this, options);
}

public Task<RoleConnection> GetUserApplicationRoleConnectionAsync(ulong applicationId, RequestOptions options = null)
=> ClientHelper.GetUserRoleConnectionAsync(applicationId, this, options);

public Task<RoleConnection> ModifyUserApplicationRoleConnectionAsync(ulong applicationId, RoleConnectionProperties roleConnection, RequestOptions options = null)
=> ClientHelper.ModifyUserRoleConnectionAsync(applicationId, roleConnection, this, options);

#endregion

#region IDiscordClient
/// <inheritdoc />


Loading…
Cancel
Save