@@ -0,0 +1,9 @@ | |||
namespace Discord | |||
{ | |||
public interface IRelationship | |||
{ | |||
RelationshipType Type { get; } | |||
IUser User { get; } | |||
} | |||
} |
@@ -18,7 +18,13 @@ namespace Discord | |||
bool IsWebhook { get; } | |||
/// <summary> Gets the username for this user. </summary> | |||
string Username { get; } | |||
/// <summary> Adds this user as a friend, this will remove a block </summary> | |||
Task AddFriendAsync(RequestOptions options = null); | |||
/// <summary> Blocks this user, and removes the user as a friend </summary> | |||
Task BlockUserAsync(RequestOptions options = null); | |||
/// <summary> Removes the relationship of this user </summary> | |||
Task RemoveRelationshipAsync(RequestOptions options = null); | |||
/// <summary> Returns a private message channel to this user, creating one if it does not already exist. </summary> | |||
Task<IDMChannel> GetOrCreateDMChannelAsync(RequestOptions options = null); | |||
} | |||
@@ -0,0 +1,11 @@ | |||
namespace Discord | |||
{ | |||
public enum RelationshipType | |||
{ | |||
None, | |||
Friend, | |||
Blocked, | |||
IncomingPending, | |||
OutgoingPending | |||
} | |||
} |
@@ -26,6 +26,8 @@ namespace Discord | |||
Task<IGuild> GetGuildAsync(ulong id, CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); | |||
Task<IReadOnlyCollection<IGuild>> GetGuildsAsync(CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); | |||
Task<IGuild> CreateGuildAsync(string name, IVoiceRegion region, Stream jpegIcon = null, RequestOptions options = null); | |||
Task<IReadOnlyCollection<IRelationship>> GetRelationshipsAsync(RequestOptions options = null); | |||
Task<IInvite> GetInviteAsync(string inviteId, RequestOptions options = null); | |||
@@ -192,5 +192,7 @@ namespace Discord | |||
throw new ArgumentOutOfRangeException(name, "Messages must be younger than two weeks old."); | |||
} | |||
} | |||
public static void LoggedInAs(TokenType token, TokenType existing) { if (token != existing) throw new NotSupportedException($"Must be logged in as a {token} to use this endpoint"); } | |||
} | |||
} |
@@ -1,11 +0,0 @@ | |||
#pragma warning disable CS1591 | |||
namespace Discord.API | |||
{ | |||
internal enum RelationshipType | |||
{ | |||
Friend = 1, | |||
Blocked = 2, | |||
IncomingPending = 3, | |||
OutgoingPending = 4 | |||
} | |||
} |
@@ -122,11 +122,14 @@ namespace Discord.Rest | |||
} | |||
/// <inheritdoc /> | |||
public void Dispose() => Dispose(true); | |||
//IDiscordClient | |||
ConnectionState IDiscordClient.ConnectionState => ConnectionState.Disconnected; | |||
ISelfUser IDiscordClient.CurrentUser => CurrentUser; | |||
Task<IReadOnlyCollection<IRelationship>> IDiscordClient.GetRelationshipsAsync(RequestOptions options) | |||
=> Task.FromResult<IReadOnlyCollection<IRelationship>>(null); | |||
Task<IApplication> IDiscordClient.GetApplicationInfoAsync(RequestOptions options) { throw new NotSupportedException(); } | |||
Task<IChannel> IDiscordClient.GetChannelAsync(ulong id, CacheMode mode, RequestOptions options) | |||
@@ -152,5 +152,20 @@ namespace Discord.Rest | |||
var models = await client.ApiClient.GetVoiceRegionsAsync(options).ConfigureAwait(false); | |||
return models.Select(x => RestVoiceRegion.Create(client, x)).FirstOrDefault(x => x.Id == id); | |||
} | |||
public static async Task<IReadOnlyCollection<RestRelationship>> GetRelationshipsAsync(BaseDiscordClient client) | |||
{ | |||
var models = await client.ApiClient.GetRelationshipsAsync().ConfigureAwait(false); | |||
return models.Select(r => RestRelationship.Create(client, r)).ToReadOnlyCollection(models); | |||
} | |||
public static async Task AddFriendAsync(BaseDiscordClient client, ulong user, RequestOptions options) | |||
=> await client.ApiClient.AddFriendAsync(user, options).ConfigureAwait(false); | |||
public static async Task BlockUserAsync(BaseDiscordClient client, ulong user, RequestOptions options) | |||
=> await client.ApiClient.BlockUserAsync(user, options).ConfigureAwait(false); | |||
public static async Task RemoveRelationshipAsync(BaseDiscordClient client, ulong user, RequestOptions options) | |||
=> await client.ApiClient.RemoveRelationshipAsync(user, options).ConfigureAwait(false); | |||
} | |||
} |
@@ -241,7 +241,7 @@ namespace Discord.API | |||
internal Task<TResponse> SendMultipartAsync<TResponse>(string method, Expression<Func<string>> endpointExpr, IReadOnlyDictionary<string, object> multipartArgs, BucketIds ids, | |||
ClientBucketType clientBucket = ClientBucketType.Unbucketed, RequestOptions options = null, [CallerMemberName] string funcName = null) | |||
=> SendMultipartAsync<TResponse>(method, GetEndpoint(endpointExpr), multipartArgs, GetBucketId(ids, endpointExpr, AuthTokenType, funcName), clientBucket, options); | |||
public async Task<TResponse> SendMultipartAsync<TResponse>(string method, string endpoint, IReadOnlyDictionary<string, object> multipartArgs, | |||
public async Task<TResponse> SendMultipartAsync<TResponse>(string method, string endpoint, IReadOnlyDictionary<string, object> multipartArgs, | |||
string bucketId = null, ClientBucketType clientBucket = ClientBucketType.Unbucketed, RequestOptions options = null) | |||
{ | |||
options = options ?? new RequestOptions(); | |||
@@ -730,7 +730,7 @@ namespace Discord.API | |||
Preconditions.NotNullOrWhitespace(args.Name, nameof(args.Name)); | |||
Preconditions.NotNullOrWhitespace(args.RegionId, nameof(args.RegionId)); | |||
options = RequestOptions.CreateOrClone(options); | |||
return await SendJsonAsync<Guild>("POST", () => "guilds", args, new BucketIds(), options: options).ConfigureAwait(false); | |||
} | |||
public async Task<Guild> DeleteGuildAsync(ulong guildId, RequestOptions options = null) | |||
@@ -940,14 +940,14 @@ namespace Discord.API | |||
{ | |||
Preconditions.NotNullOrEmpty(inviteId, nameof(inviteId)); | |||
options = RequestOptions.CreateOrClone(options); | |||
return await SendAsync<Invite>("DELETE", () => $"invites/{inviteId}", new BucketIds(), options: options).ConfigureAwait(false); | |||
} | |||
public async Task AcceptInviteAsync(string inviteId, RequestOptions options = null) | |||
{ | |||
Preconditions.NotNullOrEmpty(inviteId, nameof(inviteId)); | |||
options = RequestOptions.CreateOrClone(options); | |||
await SendAsync("POST", () => $"invites/{inviteId}", new BucketIds(), options: options).ConfigureAwait(false); | |||
} | |||
@@ -1073,6 +1073,43 @@ namespace Discord.API | |||
} | |||
catch (HttpException ex) when (ex.HttpCode == HttpStatusCode.NotFound) { return null; } | |||
} | |||
//Relationships | |||
public async Task<IReadOnlyCollection<Relationship>> GetRelationshipsAsync(RequestOptions options = null) | |||
{ | |||
Preconditions.LoggedInAs(TokenType.User, AuthTokenType); | |||
options = RequestOptions.CreateOrClone(options); | |||
return await SendAsync<IReadOnlyCollection<Relationship>>("GET", () => "users/@me/relationships", new BucketIds(), options: options).ConfigureAwait(false); | |||
} | |||
public async Task AddFriendAsync(ulong userId, RequestOptions options = null) | |||
{ | |||
Preconditions.NotEqual(userId, 0, nameof(userId)); | |||
Preconditions.LoggedInAs(TokenType.User, AuthTokenType); | |||
options = RequestOptions.CreateOrClone(options); | |||
await SendJsonAsync("PUT", () => $"users/@me/relationships/{userId}", new object(), new BucketIds(), options: options).ConfigureAwait(false); | |||
} | |||
public async Task BlockUserAsync(ulong userId, RequestOptions options = null) | |||
{ | |||
Preconditions.NotEqual(userId, 0, nameof(userId)); | |||
Preconditions.LoggedInAs(TokenType.User, AuthTokenType); | |||
options = RequestOptions.CreateOrClone(options); | |||
await SendJsonAsync("PUT", () => $"users/@me/relationships/{userId}", new { type = 2 }, new BucketIds(), options: options).ConfigureAwait(false); | |||
} | |||
public async Task RemoveRelationshipAsync(ulong userId, RequestOptions options = null) | |||
{ | |||
Preconditions.NotEqual(userId, 0, nameof(userId)); | |||
Preconditions.LoggedInAs(TokenType.User, AuthTokenType); | |||
options = RequestOptions.CreateOrClone(options); | |||
await SendAsync("DELETE", () => $"users/@me/relationships/{userId}", new BucketIds(), options: options).ConfigureAwait(false); | |||
} | |||
//Current User/DMs | |||
public async Task<User> GetMyUserAsync(RequestOptions options = null) | |||
@@ -1246,7 +1283,7 @@ namespace Discord.API | |||
int argId = int.Parse(format.Substring(leftIndex + 1, rightIndex - leftIndex - 1)); | |||
string fieldName = GetFieldName(methodArgs[argId + 1]); | |||
int? mappedId; | |||
mappedId = BucketIds.GetIndex(fieldName); | |||
if(!mappedId.HasValue && rightIndex != endIndex && format.Length > rightIndex + 1 && format[rightIndex + 1] == '/') //Ignore the next slash | |||
rightIndex++; | |||
@@ -92,6 +92,9 @@ namespace Discord.Rest | |||
public Task<RestVoiceRegion> GetVoiceRegionAsync(string id, RequestOptions options = null) | |||
=> ClientHelper.GetVoiceRegionAsync(this, id, options); | |||
public Task<IReadOnlyCollection<RestRelationship>> GetRelationshipsAsync() | |||
=> ClientHelper.GetRelationshipsAsync(this); | |||
//IDiscordClient | |||
async Task<IApplication> IDiscordClient.GetApplicationInfoAsync(RequestOptions options) | |||
=> await GetApplicationInfoAsync(options).ConfigureAwait(false); | |||
@@ -0,0 +1,26 @@ | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Text; | |||
using Model = Discord.API.Relationship; | |||
namespace Discord.Rest | |||
{ | |||
public class RestRelationship : IRelationship | |||
{ | |||
public RelationshipType Type { get; internal set; } | |||
public IUser User { get; internal set; } | |||
internal RestRelationship(RelationshipType type, IUser user) | |||
{ | |||
Type = type; | |||
User = user; | |||
} | |||
internal static RestRelationship Create(BaseDiscordClient discord, Model model) | |||
{ | |||
RestUser user = RestUser.Create(discord, model.User); | |||
return new RestRelationship(model.Type, user); | |||
} | |||
} | |||
} |
@@ -49,5 +49,14 @@ namespace Discord.Rest | |||
var model = await UserHelper.ModifyAsync(this, Discord, func, options).ConfigureAwait(false); | |||
Update(model); | |||
} | |||
Task IUser.AddFriendAsync(RequestOptions options) => | |||
throw new InvalidOperationException("You can't friend yourself!"); | |||
Task IUser.BlockUserAsync(RequestOptions options) => | |||
throw new InvalidOperationException("You can't block yourself!"); | |||
Task IUser.RemoveRelationshipAsync(RequestOptions options) => | |||
throw new InvalidOperationException("You don't have any relations with yourself!"); | |||
} | |||
} |
@@ -63,6 +63,13 @@ namespace Discord.Rest | |||
public override string ToString() => $"{Username}#{Discriminator}"; | |||
private string DebuggerDisplay => $"{Username}#{Discriminator} ({Id}{(IsBot ? ", Bot" : "")})"; | |||
public Task AddFriendAsync(RequestOptions options = null) | |||
=> ClientHelper.AddFriendAsync(Discord, Id, options); | |||
public Task BlockUserAsync(RequestOptions options = null) | |||
=> ClientHelper.BlockUserAsync(Discord, Id, options); | |||
public Task RemoveRelationshipAsync(RequestOptions options = null) | |||
=> ClientHelper.RemoveRelationshipAsync(Discord, Id, options); | |||
//IUser | |||
async Task<IDMChannel> IUser.GetOrCreateDMChannelAsync(RequestOptions options) | |||
=> await GetOrCreateDMChannelAsync(options); | |||
@@ -58,7 +58,13 @@ namespace Discord.Rpc | |||
public override string ToString() => $"{Username}#{Discriminator}"; | |||
private string DebuggerDisplay => $"{Username}#{Discriminator} ({Id}{(IsBot ? ", Bot" : "")})"; | |||
//IUser | |||
Task IUser.AddFriendAsync(RequestOptions options) | |||
=> throw new NotSupportedException(); | |||
Task IUser.BlockUserAsync(RequestOptions options) | |||
=> throw new NotSupportedException(); | |||
Task IUser.RemoveRelationshipAsync(RequestOptions options) | |||
=> throw new NotSupportedException(); | |||
async Task<IDMChannel> IUser.GetOrCreateDMChannelAsync(RequestOptions options) | |||
=> await GetOrCreateDMChannelAsync(options); | |||
} | |||
@@ -15,6 +15,7 @@ namespace Discord.WebSocket | |||
private readonly ConcurrentDictionary<ulong, SocketDMChannel> _dmChannels; | |||
private readonly ConcurrentDictionary<ulong, SocketGuild> _guilds; | |||
private readonly ConcurrentDictionary<ulong, SocketGlobalUser> _users; | |||
private readonly ConcurrentDictionary<ulong, SocketRelationship> _relationships; | |||
private readonly ConcurrentHashSet<ulong> _groupChannels; | |||
internal IReadOnlyCollection<SocketChannel> Channels => _channels.ToReadOnlyCollection(); | |||
@@ -22,6 +23,7 @@ namespace Discord.WebSocket | |||
internal IReadOnlyCollection<SocketGroupChannel> GroupChannels => _groupChannels.Select(x => GetChannel(x) as SocketGroupChannel).ToReadOnlyCollection(_groupChannels); | |||
internal IReadOnlyCollection<SocketGuild> Guilds => _guilds.ToReadOnlyCollection(); | |||
internal IReadOnlyCollection<SocketGlobalUser> Users => _users.ToReadOnlyCollection(); | |||
internal IReadOnlyCollection<SocketRelationship> Relationships => _relationships.ToReadOnlyCollection(); | |||
internal IReadOnlyCollection<ISocketPrivateChannel> PrivateChannels => | |||
_dmChannels.Select(x => x.Value as ISocketPrivateChannel).Concat( | |||
@@ -36,6 +38,7 @@ namespace Discord.WebSocket | |||
_dmChannels = new ConcurrentDictionary<ulong, SocketDMChannel>(ConcurrentHashSet.DefaultConcurrencyLevel, (int)(dmChannelCount * CollectionMultiplier)); | |||
_guilds = new ConcurrentDictionary<ulong, SocketGuild>(ConcurrentHashSet.DefaultConcurrencyLevel, (int)(guildCount * CollectionMultiplier)); | |||
_users = new ConcurrentDictionary<ulong, SocketGlobalUser>(ConcurrentHashSet.DefaultConcurrencyLevel, (int)(estimatedUsersCount * CollectionMultiplier)); | |||
_relationships = new ConcurrentDictionary<ulong, SocketRelationship>(ConcurrentHashSet.DefaultConcurrencyLevel, 35); | |||
_groupChannels = new ConcurrentHashSet<ulong>(ConcurrentHashSet.DefaultConcurrencyLevel, (int)(10 * CollectionMultiplier)); | |||
} | |||
@@ -116,5 +119,22 @@ namespace Discord.WebSocket | |||
return user; | |||
return null; | |||
} | |||
internal SocketRelationship GetRelationship(ulong id) | |||
{ | |||
if (_relationships.TryGetValue(id, out SocketRelationship value)) | |||
return value; | |||
return null; | |||
} | |||
internal void AddRelationship(SocketRelationship relationship) | |||
{ | |||
_relationships[relationship.User.Id] = relationship; | |||
} | |||
internal SocketRelationship RemoveRelationship(ulong id) | |||
{ | |||
if (_relationships.TryRemove(id, out SocketRelationship value)) | |||
return value; | |||
return null; | |||
} | |||
} | |||
} |
@@ -215,5 +215,19 @@ namespace Discord.WebSocket | |||
remove { _recipientRemovedEvent.Remove(value); } | |||
} | |||
private readonly AsyncEvent<Func<SocketGroupUser, Task>> _recipientRemovedEvent = new AsyncEvent<Func<SocketGroupUser, Task>>(); | |||
// relationships | |||
public event Func<SocketRelationship, SocketRelationship, Task> RelationshipAdded | |||
{ | |||
add { _relationshipAddedEvent.Add(value); } | |||
remove { _relationshipAddedEvent.Remove(value); } | |||
} | |||
private readonly AsyncEvent<Func<SocketRelationship, SocketRelationship, Task>> _relationshipAddedEvent = new AsyncEvent<Func<SocketRelationship, SocketRelationship, Task>>(); | |||
public event Func<SocketRelationship, Task> RelationshipRemoved | |||
{ | |||
add { _relationshipRemovedEvent.Add(value); } | |||
remove { _relationshipRemovedEvent.Remove(value); } | |||
} | |||
private readonly AsyncEvent<Func<SocketRelationship, Task>> _relationshipRemovedEvent = new AsyncEvent<Func<SocketRelationship, Task>>(); | |||
} | |||
} |
@@ -68,6 +68,7 @@ namespace Discord.WebSocket | |||
public IReadOnlyCollection<SocketGroupChannel> GroupChannels | |||
=> State.PrivateChannels.Select(x => x as SocketGroupChannel).Where(x => x != null).ToImmutableArray(); | |||
public IReadOnlyCollection<RestVoiceRegion> VoiceRegions => _voiceRegions.ToReadOnlyCollection(); | |||
public IReadOnlyCollection<SocketRelationship> Relationships => State.Relationships; | |||
/// <summary> Creates a new REST/WebSocket discord client. </summary> | |||
public DiscordSocketClient() : this(new DiscordSocketConfig()) { } | |||
@@ -256,8 +257,11 @@ namespace Discord.WebSocket | |||
=> ClientHelper.GetConnectionsAsync(this, new RequestOptions()); | |||
/// <inheritdoc /> | |||
public Task<RestInvite> GetInviteAsync(string inviteId) | |||
=> ClientHelper.GetInviteAsync(this, inviteId, new RequestOptions()); | |||
public Task<RestInvite> GetInviteAsync(string inviteId, RequestOptions options = null) | |||
=> ClientHelper.GetInviteAsync(this, inviteId, options); | |||
public Task<IReadOnlyCollection<SocketRelationship>> GetRelationshipsAsync() | |||
=> Task.FromResult(State.Relationships); | |||
/// <inheritdoc /> | |||
public SocketUser GetUser(ulong id) | |||
@@ -472,6 +476,8 @@ namespace Discord.WebSocket | |||
} | |||
for (int i = 0; i < data.PrivateChannels.Length; i++) | |||
AddPrivateChannel(data.PrivateChannels[i], state); | |||
for (int i = 0; i < data.Relationships.Length; i++) | |||
AddRelationship(data.Relationships[i], state); | |||
_sessionId = data.SessionId; | |||
_unavailableGuildCount = unavailableGuilds; | |||
@@ -1495,7 +1501,30 @@ namespace Discord.WebSocket | |||
return; | |||
} | |||
} | |||
break; | |||
return; | |||
//Relationships | |||
case "RELATIONSHIP_ADD": | |||
{ | |||
await _gatewayLogger.DebugAsync("Received Dispatch (RELATIONSHIP_ADD)").ConfigureAwait(false); | |||
var addedModel = (payload as JToken).ToObject<Relationship>(_serializer); | |||
var before = State.GetRelationship(addedModel.Id) ?? SocketRelationship.Create(this, State, new Relationship { Id = addedModel.Id, Type = RelationshipType.None, User = addedModel.User }); | |||
var after = AddRelationship(addedModel, State); | |||
await _relationshipAddedEvent.InvokeAsync(before, after); | |||
return; | |||
} | |||
case "RELATIONSHIP_REMOVE": | |||
{ | |||
await _gatewayLogger.DebugAsync("Received Dispatch (RELATIONSHIP_REMOVE)").ConfigureAwait(false); | |||
var removedModel = (payload as JToken).ToObject<Relationship>(_serializer); | |||
var removed = RemoveRelationship(removedModel.Id); | |||
await _relationshipRemovedEvent.InvokeAsync(removed); | |||
return; | |||
} | |||
//Ignored (User only) | |||
case "CHANNEL_PINS_ACK": | |||
@@ -1798,6 +1827,21 @@ namespace Discord.WebSocket | |||
await _gatewayLogger.DebugAsync($"Unsynced Guild ({details}).").ConfigureAwait(false); | |||
} | |||
internal SocketRelationship GetRelationship(ulong id) | |||
{ | |||
return State.GetRelationship(id); | |||
} | |||
internal SocketRelationship AddRelationship(Relationship model, ClientState state) | |||
{ | |||
var relationship = SocketRelationship.Create(this, state, model); | |||
state.AddRelationship(SocketRelationship.Create(this, state, model)); | |||
return relationship; | |||
} | |||
internal SocketRelationship RemoveRelationship(ulong id) | |||
{ | |||
return State.RemoveRelationship(id); | |||
} | |||
internal int GetAudioId() => _nextAudioId++; | |||
//IDiscordClient | |||
@@ -1840,5 +1884,8 @@ namespace Discord.WebSocket | |||
=> await StartAsync().ConfigureAwait(false); | |||
async Task IDiscordClient.StopAsync() | |||
=> await StopAsync().ConfigureAwait(false); | |||
async Task<IReadOnlyCollection<IRelationship>> IDiscordClient.GetRelationshipsAsync(RequestOptions options) | |||
=> await GetRelationshipsAsync(); | |||
} | |||
} |
@@ -0,0 +1,27 @@ | |||
using System.Diagnostics; | |||
using Model = Discord.API.Relationship; | |||
namespace Discord.WebSocket | |||
{ | |||
[DebuggerDisplay("{Type} - {User}")] | |||
public class SocketRelationship : IRelationship | |||
{ | |||
public RelationshipType Type { get; internal set; } | |||
public IUser User { get; internal set; } | |||
internal SocketRelationship() | |||
{ | |||
} | |||
internal static SocketRelationship Create(DiscordSocketClient discord, ClientState state, Model model) | |||
{ | |||
SocketGlobalUser user = SocketGlobalUser.Create(discord, state, model.User); | |||
return new SocketRelationship | |||
{ | |||
Type = model.Type, | |||
User = user | |||
}; | |||
} | |||
} | |||
} |
@@ -57,6 +57,15 @@ namespace Discord.WebSocket | |||
public Task ModifyAsync(Action<SelfUserProperties> func, RequestOptions options = null) | |||
=> UserHelper.ModifyAsync(this, Discord, func, options); | |||
Task IUser.AddFriendAsync(RequestOptions options) => | |||
throw new InvalidOperationException("You can't friend yourself!"); | |||
Task IUser.BlockUserAsync(RequestOptions options) => | |||
throw new InvalidOperationException("You can't block yourself!"); | |||
Task IUser.RemoveRelationshipAsync(RequestOptions options) => | |||
throw new InvalidOperationException("You don't have any relations with yourself!"); | |||
internal new SocketSelfUser Clone() => MemberwiseClone() as SocketSelfUser; | |||
} | |||
} |
@@ -20,6 +20,7 @@ namespace Discord.WebSocket | |||
public string Mention => MentionUtils.MentionUser(Id); | |||
public Game? Game => Presence.Game; | |||
public UserStatus Status => Presence.Status; | |||
public RelationshipType Relationship => Discord.GetRelationship(Id)?.Type ?? RelationshipType.None; | |||
internal SocketUser(DiscordSocketClient discord, ulong id) | |||
: base(discord, id) | |||
@@ -64,5 +65,12 @@ namespace Discord.WebSocket | |||
public override string ToString() => $"{Username}#{Discriminator}"; | |||
private string DebuggerDisplay => $"{Username}#{Discriminator} ({Id}{(IsBot ? ", Bot" : "")})"; | |||
internal SocketUser Clone() => MemberwiseClone() as SocketUser; | |||
public async Task AddFriendAsync(RequestOptions options = null) | |||
=> await ClientHelper.AddFriendAsync(Discord, Id, options); | |||
public async Task BlockUserAsync(RequestOptions options = null) | |||
=> await ClientHelper.BlockUserAsync(Discord, Id, options); | |||
public async Task RemoveRelationshipAsync(RequestOptions options = null) | |||
=> await ClientHelper.RemoveRelationshipAsync(Discord, Id, options); | |||
} | |||
} |