Concrete class prototype
This commit is contained in:
@@ -0,0 +1,77 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Threading.Tasks;
|
||||
using MessageModel = Discord.API.Message;
|
||||
using Model = Discord.API.Channel;
|
||||
|
||||
namespace Discord.WebSocket
|
||||
{
|
||||
internal class DMChannel : IDMChannel, ISocketChannel, ISocketMessageChannel, ISocketPrivateChannel
|
||||
{
|
||||
private readonly MessageManager _messages;
|
||||
|
||||
public new DiscordSocketClient Discord => base.Discord as DiscordSocketClient;
|
||||
public new SocketDMUser Recipient => base.Recipient as SocketDMUser;
|
||||
public IReadOnlyCollection<ISocketUser> Users => ImmutableArray.Create<ISocketUser>(Discord.CurrentUser, Recipient);
|
||||
IReadOnlyCollection<ISocketUser> ISocketPrivateChannel.Recipients => ImmutableArray.Create(Recipient);
|
||||
|
||||
public SocketDMChannel(DiscordSocketClient discord, SocketDMUser recipient, Model model)
|
||||
: base(discord, recipient, model)
|
||||
{
|
||||
if (Discord.MessageCacheSize > 0)
|
||||
_messages = new MessageCache(Discord, this);
|
||||
else
|
||||
_messages = new MessageManager(Discord, this);
|
||||
}
|
||||
|
||||
public override Task<IUser> GetUserAsync(ulong id) => Task.FromResult<IUser>(GetUser(id));
|
||||
public override Task<IReadOnlyCollection<IUser>> GetUsersAsync() => Task.FromResult<IReadOnlyCollection<IUser>>(Users);
|
||||
public ISocketUser GetUser(ulong id)
|
||||
{
|
||||
var currentUser = Discord.CurrentUser;
|
||||
if (id == Recipient.Id)
|
||||
return Recipient;
|
||||
else if (id == currentUser.Id)
|
||||
return currentUser;
|
||||
else
|
||||
return null;
|
||||
}
|
||||
|
||||
public override async Task<IMessage> GetMessageAsync(ulong id)
|
||||
{
|
||||
return await _messages.DownloadAsync(id).ConfigureAwait(false);
|
||||
}
|
||||
public override async Task<IReadOnlyCollection<IMessage>> GetMessagesAsync(int limit)
|
||||
{
|
||||
return await _messages.DownloadAsync(null, Direction.Before, limit).ConfigureAwait(false);
|
||||
}
|
||||
public override async Task<IReadOnlyCollection<IMessage>> GetMessagesAsync(ulong fromMessageId, Direction dir, int limit)
|
||||
{
|
||||
return await _messages.DownloadAsync(fromMessageId, dir, limit).ConfigureAwait(false);
|
||||
}
|
||||
public ISocketMessage CreateMessage(ISocketUser author, MessageModel model)
|
||||
{
|
||||
return _messages.Create(author, model);
|
||||
}
|
||||
public ISocketMessage AddMessage(ISocketUser author, MessageModel model)
|
||||
{
|
||||
var msg = _messages.Create(author, model);
|
||||
_messages.Add(msg);
|
||||
return msg;
|
||||
}
|
||||
public ISocketMessage GetMessage(ulong id)
|
||||
{
|
||||
return _messages.Get(id);
|
||||
}
|
||||
public ISocketMessage RemoveMessage(ulong id)
|
||||
{
|
||||
return _messages.Remove(id);
|
||||
}
|
||||
|
||||
public SocketDMChannel Clone() => MemberwiseClone() as SocketDMChannel;
|
||||
|
||||
IMessage IMessageChannel.GetCachedMessage(ulong id) => GetMessage(id);
|
||||
ISocketUser ISocketMessageChannel.GetUser(ulong id, bool skipCheck) => GetUser(id);
|
||||
ISocketChannel ISocketChannel.Clone() => Clone();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,144 @@
|
||||
using Discord.Rest;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using MessageModel = Discord.API.Message;
|
||||
using Model = Discord.API.Channel;
|
||||
using UserModel = Discord.API.User;
|
||||
using VoiceStateModel = Discord.API.VoiceState;
|
||||
|
||||
namespace Discord.WebSocket
|
||||
{
|
||||
internal class SocketGroupChannel : IGroupChannel, ISocketChannel, ISocketMessageChannel, ISocketPrivateChannel
|
||||
{
|
||||
internal override bool IsAttached => true;
|
||||
|
||||
private readonly MessageManager _messages;
|
||||
private ConcurrentDictionary<ulong, VoiceState> _voiceStates;
|
||||
|
||||
public new DiscordSocketClient Discord => base.Discord as DiscordSocketClient;
|
||||
public IReadOnlyCollection<ISocketUser> Users
|
||||
=> _users.Select(x => x.Value as ISocketUser).Concat(ImmutableArray.Create(Discord.CurrentUser)).ToReadOnlyCollection(() => _users.Count + 1);
|
||||
public new IReadOnlyCollection<ISocketUser> Recipients => _users.Select(x => x.Value as ISocketUser).ToReadOnlyCollection(_users);
|
||||
|
||||
public SocketGroupChannel(DiscordSocketClient discord, Model model)
|
||||
: base(discord, model)
|
||||
{
|
||||
if (Discord.MessageCacheSize > 0)
|
||||
_messages = new MessageCache(Discord, this);
|
||||
else
|
||||
_messages = new MessageManager(Discord, this);
|
||||
_voiceStates = new ConcurrentDictionary<ulong, VoiceState>(1, 5);
|
||||
}
|
||||
public override void Update(Model model)
|
||||
{
|
||||
if (source == UpdateSource.Rest && IsAttached) return;
|
||||
|
||||
base.Update(model, source);
|
||||
}
|
||||
|
||||
internal void UpdateUsers(UserModel[] models, DataStore dataStore)
|
||||
{
|
||||
var users = new ConcurrentDictionary<ulong, GroupUser>(1, models.Length);
|
||||
for (int i = 0; i < models.Length; i++)
|
||||
{
|
||||
var globalUser = Discord.GetOrAddUser(models[i], dataStore);
|
||||
users[models[i].Id] = new SocketGroupUser(this, globalUser);
|
||||
}
|
||||
_users = users;
|
||||
}
|
||||
internal override void UpdateUsers(UserModel[] models)
|
||||
=> UpdateUsers(models, source, Discord.DataStore);
|
||||
|
||||
public SocketGroupUser AddUser(UserModel model, DataStore dataStore)
|
||||
{
|
||||
GroupUser user;
|
||||
if (_users.TryGetValue(model.Id, out user))
|
||||
return user as SocketGroupUser;
|
||||
else
|
||||
{
|
||||
var globalUser = Discord.GetOrAddUser(model, dataStore);
|
||||
var privateUser = new SocketGroupUser(this, globalUser);
|
||||
_users[privateUser.Id] = privateUser;
|
||||
return privateUser;
|
||||
}
|
||||
}
|
||||
public ISocketUser GetUser(ulong id)
|
||||
{
|
||||
GroupUser user;
|
||||
if (_users.TryGetValue(id, out user))
|
||||
return user as SocketGroupUser;
|
||||
if (id == Discord.CurrentUser.Id)
|
||||
return Discord.CurrentUser;
|
||||
return null;
|
||||
}
|
||||
public SocketGroupUser RemoveUser(ulong id)
|
||||
{
|
||||
GroupUser user;
|
||||
if (_users.TryRemove(id, out user))
|
||||
return user as SocketGroupUser;
|
||||
return null;
|
||||
}
|
||||
|
||||
public VoiceState AddOrUpdateVoiceState(VoiceStateModel model, DataStore dataStore, ConcurrentDictionary<ulong, VoiceState> voiceStates = null)
|
||||
{
|
||||
var voiceChannel = dataStore.GetChannel(model.ChannelId.Value) as SocketVoiceChannel;
|
||||
var voiceState = new VoiceState(voiceChannel, model);
|
||||
(voiceStates ?? _voiceStates)[model.UserId] = voiceState;
|
||||
return voiceState;
|
||||
}
|
||||
public VoiceState? GetVoiceState(ulong id)
|
||||
{
|
||||
VoiceState voiceState;
|
||||
if (_voiceStates.TryGetValue(id, out voiceState))
|
||||
return voiceState;
|
||||
return null;
|
||||
}
|
||||
public VoiceState? RemoveVoiceState(ulong id)
|
||||
{
|
||||
VoiceState voiceState;
|
||||
if (_voiceStates.TryRemove(id, out voiceState))
|
||||
return voiceState;
|
||||
return null;
|
||||
}
|
||||
|
||||
public override async Task<IMessage> GetMessageAsync(ulong id)
|
||||
{
|
||||
return await _messages.DownloadAsync(id).ConfigureAwait(false);
|
||||
}
|
||||
public override async Task<IReadOnlyCollection<IMessage>> GetMessagesAsync(int limit)
|
||||
{
|
||||
return await _messages.DownloadAsync(null, Direction.Before, limit).ConfigureAwait(false);
|
||||
}
|
||||
public override async Task<IReadOnlyCollection<IMessage>> GetMessagesAsync(ulong fromMessageId, Direction dir, int limit)
|
||||
{
|
||||
return await _messages.DownloadAsync(fromMessageId, dir, limit).ConfigureAwait(false);
|
||||
}
|
||||
public ISocketMessage CreateMessage(ISocketUser author, MessageModel model)
|
||||
{
|
||||
return _messages.Create(author, model);
|
||||
}
|
||||
public ISocketMessage AddMessage(ISocketUser author, MessageModel model)
|
||||
{
|
||||
var msg = _messages.Create(author, model);
|
||||
_messages.Add(msg);
|
||||
return msg;
|
||||
}
|
||||
public ISocketMessage GetMessage(ulong id)
|
||||
{
|
||||
return _messages.Get(id);
|
||||
}
|
||||
public ISocketMessage RemoveMessage(ulong id)
|
||||
{
|
||||
return _messages.Remove(id);
|
||||
}
|
||||
|
||||
public SocketDMChannel Clone() => MemberwiseClone() as SocketDMChannel;
|
||||
|
||||
IMessage IMessageChannel.GetCachedMessage(ulong id) => GetMessage(id);
|
||||
ISocketUser ISocketMessageChannel.GetUser(ulong id, bool skipCheck) => GetUser(id);
|
||||
ISocketChannel ISocketChannel.Clone() => Clone();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,157 @@
|
||||
using Discord.API.Rest;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Model = Discord.API.Channel;
|
||||
|
||||
namespace Discord.Rest
|
||||
{
|
||||
[DebuggerDisplay(@"{DebuggerDisplay,nq}")]
|
||||
internal abstract class SocketGuildChannel : ISnowflakeEntity, IGuildChannel
|
||||
{
|
||||
private List<Overwrite> _overwrites; //TODO: Is maintaining a list here too expensive? Is this threadsafe?
|
||||
|
||||
public string Name { get; private set; }
|
||||
public int Position { get; private set; }
|
||||
|
||||
public Guild Guild { get; private set; }
|
||||
|
||||
public override DiscordRestClient Discord => Guild.Discord;
|
||||
|
||||
public GuildChannel(Guild guild, Model model)
|
||||
: base(model.Id)
|
||||
{
|
||||
Guild = guild;
|
||||
|
||||
Update(model);
|
||||
}
|
||||
public virtual void Update(Model model)
|
||||
{
|
||||
if (source == UpdateSource.Rest && IsAttached) return;
|
||||
|
||||
Name = model.Name.Value;
|
||||
Position = model.Position.Value;
|
||||
|
||||
var overwrites = model.PermissionOverwrites.Value;
|
||||
var newOverwrites = new List<Overwrite>(overwrites.Length);
|
||||
for (int i = 0; i < overwrites.Length; i++)
|
||||
newOverwrites.Add(new Overwrite(overwrites[i]));
|
||||
_overwrites = newOverwrites;
|
||||
}
|
||||
|
||||
public async Task UpdateAsync()
|
||||
{
|
||||
if (IsAttached) throw new NotSupportedException();
|
||||
|
||||
var model = await Discord.ApiClient.GetChannelAsync(Id).ConfigureAwait(false);
|
||||
Update(model, UpdateSource.Rest);
|
||||
}
|
||||
public async Task ModifyAsync(Action<ModifyGuildChannelParams> func)
|
||||
{
|
||||
if (func == null) throw new NullReferenceException(nameof(func));
|
||||
|
||||
var args = new ModifyGuildChannelParams();
|
||||
func(args);
|
||||
|
||||
if (!args._name.IsSpecified)
|
||||
args._name = Name;
|
||||
|
||||
var model = await Discord.ApiClient.ModifyGuildChannelAsync(Id, args).ConfigureAwait(false);
|
||||
Update(model, UpdateSource.Rest);
|
||||
}
|
||||
public async Task DeleteAsync()
|
||||
{
|
||||
await Discord.ApiClient.DeleteChannelAsync(Id).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public abstract Task<IGuildUser> GetUserAsync(ulong id);
|
||||
public abstract Task<IReadOnlyCollection<IGuildUser>> GetUsersAsync();
|
||||
|
||||
public async Task<IReadOnlyCollection<IInviteMetadata>> GetInvitesAsync()
|
||||
{
|
||||
var models = await Discord.ApiClient.GetChannelInvitesAsync(Id).ConfigureAwait(false);
|
||||
return models.Select(x => new InviteMetadata(Discord, x)).ToImmutableArray();
|
||||
}
|
||||
public async Task<IInviteMetadata> CreateInviteAsync(int? maxAge, int? maxUses, bool isTemporary)
|
||||
{
|
||||
var args = new CreateChannelInviteParams
|
||||
{
|
||||
MaxAge = maxAge ?? 0,
|
||||
MaxUses = maxUses ?? 0,
|
||||
Temporary = isTemporary
|
||||
};
|
||||
var model = await Discord.ApiClient.CreateChannelInviteAsync(Id, args).ConfigureAwait(false);
|
||||
return new InviteMetadata(Discord, model);
|
||||
}
|
||||
|
||||
public OverwritePermissions? GetPermissionOverwrite(IUser user)
|
||||
{
|
||||
for (int i = 0; i < _overwrites.Count; i++)
|
||||
{
|
||||
if (_overwrites[i].TargetId == user.Id)
|
||||
return _overwrites[i].Permissions;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
public OverwritePermissions? GetPermissionOverwrite(IRole role)
|
||||
{
|
||||
for (int i = 0; i < _overwrites.Count; i++)
|
||||
{
|
||||
if (_overwrites[i].TargetId == role.Id)
|
||||
return _overwrites[i].Permissions;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public async Task AddPermissionOverwriteAsync(IUser user, OverwritePermissions perms)
|
||||
{
|
||||
var args = new ModifyChannelPermissionsParams { Allow = perms.AllowValue, Deny = perms.DenyValue, Type = "member" };
|
||||
await Discord.ApiClient.ModifyChannelPermissionsAsync(Id, user.Id, args).ConfigureAwait(false);
|
||||
_overwrites.Add(new Overwrite(new API.Overwrite { Allow = perms.AllowValue, Deny = perms.DenyValue, TargetId = user.Id, TargetType = PermissionTarget.User }));
|
||||
}
|
||||
public async Task AddPermissionOverwriteAsync(IRole role, OverwritePermissions perms)
|
||||
{
|
||||
var args = new ModifyChannelPermissionsParams { Allow = perms.AllowValue, Deny = perms.DenyValue, Type = "role" };
|
||||
await Discord.ApiClient.ModifyChannelPermissionsAsync(Id, role.Id, args).ConfigureAwait(false);
|
||||
_overwrites.Add(new Overwrite(new API.Overwrite { Allow = perms.AllowValue, Deny = perms.DenyValue, TargetId = role.Id, TargetType = PermissionTarget.Role }));
|
||||
}
|
||||
public async Task RemovePermissionOverwriteAsync(IUser user)
|
||||
{
|
||||
await Discord.ApiClient.DeleteChannelPermissionAsync(Id, user.Id).ConfigureAwait(false);
|
||||
|
||||
for (int i = 0; i < _overwrites.Count; i++)
|
||||
{
|
||||
if (_overwrites[i].TargetId == user.Id)
|
||||
{
|
||||
_overwrites.RemoveAt(i);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
public async Task RemovePermissionOverwriteAsync(IRole role)
|
||||
{
|
||||
await Discord.ApiClient.DeleteChannelPermissionAsync(Id, role.Id).ConfigureAwait(false);
|
||||
|
||||
for (int i = 0; i < _overwrites.Count; i++)
|
||||
{
|
||||
if (_overwrites[i].TargetId == role.Id)
|
||||
{
|
||||
_overwrites.RemoveAt(i);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override string ToString() => Name;
|
||||
private string DebuggerDisplay => $"{Name} ({Id})";
|
||||
|
||||
IGuild IGuildChannel.Guild => Guild;
|
||||
IReadOnlyCollection<Overwrite> IGuildChannel.PermissionOverwrites => _overwrites.AsReadOnly();
|
||||
|
||||
async Task<IUser> IChannel.GetUserAsync(ulong id) => await GetUserAsync(id).ConfigureAwait(false);
|
||||
async Task<IReadOnlyCollection<IUser>> IChannel.GetUsersAsync() => await GetUsersAsync().ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
using Discord.Rest;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using MessageModel = Discord.API.Message;
|
||||
using Model = Discord.API.Channel;
|
||||
|
||||
namespace Discord.WebSocket
|
||||
{
|
||||
internal class SocketTextChannel : TextChannel, ISocketGuildChannel, ISocketMessageChannel
|
||||
{
|
||||
internal override bool IsAttached => true;
|
||||
|
||||
private readonly MessageManager _messages;
|
||||
|
||||
public new DiscordSocketClient Discord => base.Discord as DiscordSocketClient;
|
||||
public new SocketGuild Guild => base.Guild as SocketGuild;
|
||||
|
||||
public IReadOnlyCollection<SocketGuildUser> Members
|
||||
=> Guild.Members.Where(x => Permissions.GetValue(Permissions.ResolveChannel(x, this, x.GuildPermissions.RawValue), ChannelPermission.ReadMessages)).ToImmutableArray();
|
||||
|
||||
public SocketTextChannel(SocketGuild guild, Model model)
|
||||
: base(guild, model)
|
||||
{
|
||||
if (Discord.MessageCacheSize > 0)
|
||||
_messages = new MessageCache(Discord, this);
|
||||
else
|
||||
_messages = new MessageManager(Discord, this);
|
||||
}
|
||||
|
||||
public override Task<IGuildUser> GetUserAsync(ulong id) => Task.FromResult<IGuildUser>(GetUser(id));
|
||||
public override Task<IReadOnlyCollection<IGuildUser>> GetUsersAsync() => Task.FromResult<IReadOnlyCollection<IGuildUser>>(Members);
|
||||
public SocketGuildUser GetUser(ulong id, bool skipCheck = false)
|
||||
{
|
||||
var user = Guild.GetUser(id);
|
||||
if (skipCheck) return user;
|
||||
|
||||
if (user != null)
|
||||
{
|
||||
ulong perms = Permissions.ResolveChannel(user, this, user.GuildPermissions.RawValue);
|
||||
if (Permissions.GetValue(perms, ChannelPermission.ReadMessages))
|
||||
return user;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public override async Task<IMessage> GetMessageAsync(ulong id)
|
||||
{
|
||||
return await _messages.DownloadAsync(id).ConfigureAwait(false);
|
||||
}
|
||||
public override async Task<IReadOnlyCollection<IMessage>> GetMessagesAsync(int limit = DiscordConfig.MaxMessagesPerBatch)
|
||||
{
|
||||
return await _messages.DownloadAsync(null, Direction.Before, limit).ConfigureAwait(false);
|
||||
}
|
||||
public override async Task<IReadOnlyCollection<IMessage>> GetMessagesAsync(ulong fromMessageId, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch)
|
||||
{
|
||||
return await _messages.DownloadAsync(fromMessageId, dir, limit).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public ISocketMessage CreateMessage(ISocketUser author, MessageModel model)
|
||||
{
|
||||
return _messages.Create(author, model);
|
||||
}
|
||||
public ISocketMessage AddMessage(ISocketUser author, MessageModel model)
|
||||
{
|
||||
var msg = _messages.Create(author, model);
|
||||
_messages.Add(msg);
|
||||
return msg;
|
||||
}
|
||||
public ISocketMessage GetMessage(ulong id)
|
||||
{
|
||||
return _messages.Get(id);
|
||||
}
|
||||
public ISocketMessage RemoveMessage(ulong id)
|
||||
{
|
||||
return _messages.Remove(id);
|
||||
}
|
||||
|
||||
public SocketTextChannel Clone() => MemberwiseClone() as SocketTextChannel;
|
||||
|
||||
IReadOnlyCollection<ISocketUser> ISocketMessageChannel.Users => Members;
|
||||
|
||||
IMessage IMessageChannel.GetCachedMessage(ulong id) => GetMessage(id);
|
||||
ISocketUser ISocketMessageChannel.GetUser(ulong id, bool skipCheck) => GetUser(id, skipCheck);
|
||||
ISocketChannel ISocketChannel.Clone() => Clone();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
using Discord.Audio;
|
||||
using Discord.Rest;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Model = Discord.API.Channel;
|
||||
|
||||
namespace Discord.WebSocket
|
||||
{
|
||||
internal class SocketVoiceChannel : VoiceChannel, ISocketGuildChannel
|
||||
{
|
||||
internal override bool IsAttached => true;
|
||||
|
||||
public new DiscordSocketClient Discord => base.Discord as DiscordSocketClient;
|
||||
public new SocketGuild Guild => base.Guild as SocketGuild;
|
||||
|
||||
public IReadOnlyCollection<IGuildUser> Members
|
||||
=> Guild.VoiceStates.Where(x => x.Value.VoiceChannel.Id == Id).Select(x => Guild.GetUser(x.Key)).ToImmutableArray();
|
||||
|
||||
public SocketVoiceChannel(SocketGuild guild, Model model)
|
||||
: base(guild, model)
|
||||
{
|
||||
}
|
||||
|
||||
public override Task<IGuildUser> GetUserAsync(ulong id)
|
||||
=> Task.FromResult(GetUser(id));
|
||||
public override Task<IReadOnlyCollection<IGuildUser>> GetUsersAsync()
|
||||
=> Task.FromResult(Members);
|
||||
public IGuildUser GetUser(ulong id)
|
||||
{
|
||||
var user = Guild.GetUser(id);
|
||||
if (user != null && user.VoiceChannel.Id == Id)
|
||||
return user;
|
||||
return null;
|
||||
}
|
||||
|
||||
public override async Task<IAudioClient> ConnectAsync()
|
||||
{
|
||||
var audioMode = Discord.AudioMode;
|
||||
if (audioMode == AudioMode.Disabled)
|
||||
throw new InvalidOperationException($"Audio is not enabled on this client, {nameof(DiscordSocketConfig.AudioMode)} in {nameof(DiscordSocketConfig)} must be set.");
|
||||
|
||||
return await Guild.ConnectAudioAsync(Id,
|
||||
(audioMode & AudioMode.Incoming) == 0,
|
||||
(audioMode & AudioMode.Outgoing) == 0).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public SocketVoiceChannel Clone() => MemberwiseClone() as SocketVoiceChannel;
|
||||
|
||||
ISocketChannel ISocketChannel.Clone() => Clone();
|
||||
}
|
||||
}
|
||||
419
src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs
Normal file
419
src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs
Normal file
@@ -0,0 +1,419 @@
|
||||
using Discord.Audio;
|
||||
using Discord.Rest;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using ChannelModel = Discord.API.Channel;
|
||||
using EmojiUpdateModel = Discord.API.Gateway.GuildEmojiUpdateEvent;
|
||||
using ExtendedModel = Discord.API.Gateway.ExtendedGuild;
|
||||
using GuildSyncModel = Discord.API.Gateway.GuildSyncEvent;
|
||||
using MemberModel = Discord.API.GuildMember;
|
||||
using Model = Discord.API.Guild;
|
||||
using PresenceModel = Discord.API.Presence;
|
||||
using RoleModel = Discord.API.Role;
|
||||
using VoiceStateModel = Discord.API.VoiceState;
|
||||
|
||||
namespace Discord.WebSocket
|
||||
{
|
||||
internal class SocketGuild : Guild, IGuild, IUserGuild
|
||||
{
|
||||
internal override bool IsAttached => true;
|
||||
|
||||
private readonly SemaphoreSlim _audioLock;
|
||||
private TaskCompletionSource<bool> _syncPromise, _downloaderPromise;
|
||||
private TaskCompletionSource<AudioClient> _audioConnectPromise;
|
||||
private ConcurrentHashSet<ulong> _channels;
|
||||
private ConcurrentDictionary<ulong, SocketGuildUser> _members;
|
||||
private ConcurrentDictionary<ulong, VoiceState> _voiceStates;
|
||||
internal bool _available;
|
||||
|
||||
public bool Available => _available && Discord.ConnectionState == ConnectionState.Connected;
|
||||
public int MemberCount { get; set; }
|
||||
public int DownloadedMemberCount { get; private set; }
|
||||
public AudioClient AudioClient { get; private set; }
|
||||
|
||||
public bool HasAllMembers => _downloaderPromise.Task.IsCompleted;
|
||||
public bool IsSynced => _syncPromise.Task.IsCompleted;
|
||||
public Task SyncPromise => _syncPromise.Task;
|
||||
public Task DownloaderPromise => _downloaderPromise.Task;
|
||||
|
||||
public new DiscordSocketClient Discord => base.Discord as DiscordSocketClient;
|
||||
public SocketGuildUser CurrentUser => GetUser(Discord.CurrentUser.Id);
|
||||
public IReadOnlyCollection<ISocketGuildChannel> Channels
|
||||
{
|
||||
get
|
||||
{
|
||||
var channels = _channels;
|
||||
var store = Discord.DataStore;
|
||||
return channels.Select(x => store.GetChannel(x) as ISocketGuildChannel).Where(x => x != null).ToReadOnlyCollection(channels);
|
||||
}
|
||||
}
|
||||
public IReadOnlyCollection<SocketGuildUser> Members => _members.ToReadOnlyCollection();
|
||||
public IEnumerable<KeyValuePair<ulong, VoiceState>> VoiceStates => _voiceStates;
|
||||
|
||||
public SocketGuild(DiscordSocketClient discord, ExtendedModel model, DataStore dataStore) : base(discord, model)
|
||||
{
|
||||
_audioLock = new SemaphoreSlim(1, 1);
|
||||
_syncPromise = new TaskCompletionSource<bool>();
|
||||
_downloaderPromise = new TaskCompletionSource<bool>();
|
||||
Update(model, dataStore);
|
||||
}
|
||||
|
||||
public void Update(ExtendedModel model, DataStore dataStore)
|
||||
{
|
||||
if (source == UpdateSource.Rest && IsAttached) return;
|
||||
|
||||
_available = !(model.Unavailable ?? false);
|
||||
if (!_available)
|
||||
{
|
||||
if (_channels == null)
|
||||
_channels = new ConcurrentHashSet<ulong>();
|
||||
if (_members == null)
|
||||
_members = new ConcurrentDictionary<ulong, SocketGuildUser>();
|
||||
if (_roles == null)
|
||||
_roles = new ConcurrentDictionary<ulong, Role>();
|
||||
if (Emojis == null)
|
||||
Emojis = ImmutableArray.Create<Emoji>();
|
||||
if (Features == null)
|
||||
Features = ImmutableArray.Create<string>();
|
||||
return;
|
||||
}
|
||||
|
||||
base.Update(model as Model, source);
|
||||
|
||||
var channels = new ConcurrentHashSet<ulong>(1, (int)(model.Channels.Length * 1.05));
|
||||
{
|
||||
for (int i = 0; i < model.Channels.Length; i++)
|
||||
AddChannel(model.Channels[i], dataStore, channels);
|
||||
}
|
||||
_channels = channels;
|
||||
|
||||
var members = new ConcurrentDictionary<ulong, SocketGuildUser>(1, (int)(model.Presences.Length * 1.05));
|
||||
{
|
||||
DownloadedMemberCount = 0;
|
||||
for (int i = 0; i < model.Members.Length; i++)
|
||||
AddOrUpdateUser(model.Members[i], dataStore, members);
|
||||
if (Discord.ApiClient.AuthTokenType != TokenType.User)
|
||||
{
|
||||
var _ = _syncPromise.TrySetResultAsync(true);
|
||||
if (!model.Large)
|
||||
_ = _downloaderPromise.TrySetResultAsync(true);
|
||||
}
|
||||
|
||||
for (int i = 0; i < model.Presences.Length; i++)
|
||||
AddOrUpdateUser(model.Presences[i], dataStore, members);
|
||||
}
|
||||
_members = members;
|
||||
MemberCount = model.MemberCount;
|
||||
|
||||
var voiceStates = new ConcurrentDictionary<ulong, VoiceState>(1, (int)(model.VoiceStates.Length * 1.05));
|
||||
{
|
||||
for (int i = 0; i < model.VoiceStates.Length; i++)
|
||||
AddOrUpdateVoiceState(model.VoiceStates[i], dataStore, voiceStates);
|
||||
}
|
||||
_voiceStates = voiceStates;
|
||||
}
|
||||
public void Update(GuildSyncModel model, DataStore dataStore)
|
||||
{
|
||||
if (source == UpdateSource.Rest && IsAttached) return;
|
||||
|
||||
var members = new ConcurrentDictionary<ulong, SocketGuildUser>(1, (int)(model.Presences.Length * 1.05));
|
||||
{
|
||||
DownloadedMemberCount = 0;
|
||||
for (int i = 0; i < model.Members.Length; i++)
|
||||
AddOrUpdateUser(model.Members[i], dataStore, members);
|
||||
var _ = _syncPromise.TrySetResultAsync(true);
|
||||
if (!model.Large)
|
||||
_ = _downloaderPromise.TrySetResultAsync(true);
|
||||
|
||||
for (int i = 0; i < model.Presences.Length; i++)
|
||||
AddOrUpdateUser(model.Presences[i], dataStore, members);
|
||||
}
|
||||
_members = members;
|
||||
}
|
||||
|
||||
public void Update(EmojiUpdateModel model)
|
||||
{
|
||||
if (source == UpdateSource.Rest && IsAttached) return;
|
||||
|
||||
var emojis = ImmutableArray.CreateBuilder<Emoji>(model.Emojis.Length);
|
||||
for (int i = 0; i < model.Emojis.Length; i++)
|
||||
emojis.Add(new Emoji(model.Emojis[i]));
|
||||
Emojis = emojis.ToImmutableArray();
|
||||
}
|
||||
|
||||
public override Task<IGuildChannel> GetChannelAsync(ulong id) => Task.FromResult<IGuildChannel>(GetChannel(id));
|
||||
public override Task<IReadOnlyCollection<IGuildChannel>> GetChannelsAsync() => Task.FromResult<IReadOnlyCollection<IGuildChannel>>(Channels);
|
||||
public void AddChannel(ChannelModel model, DataStore dataStore, ConcurrentHashSet<ulong> channels = null)
|
||||
{
|
||||
var channel = ToChannel(model);
|
||||
(channels ?? _channels).TryAdd(model.Id);
|
||||
dataStore.AddChannel(channel);
|
||||
}
|
||||
public ISocketGuildChannel GetChannel(ulong id)
|
||||
{
|
||||
return Discord.DataStore.GetChannel(id) as ISocketGuildChannel;
|
||||
}
|
||||
public ISocketGuildChannel RemoveChannel(ulong id)
|
||||
{
|
||||
_channels.TryRemove(id);
|
||||
return Discord.DataStore.RemoveChannel(id) as ISocketGuildChannel;
|
||||
}
|
||||
|
||||
public Role AddRole(RoleModel model, ConcurrentDictionary<ulong, Role> roles = null)
|
||||
{
|
||||
var role = new Role(this, model);
|
||||
(roles ?? _roles)[model.Id] = role;
|
||||
return role;
|
||||
}
|
||||
public Role RemoveRole(ulong id)
|
||||
{
|
||||
Role role;
|
||||
if (_roles.TryRemove(id, out role))
|
||||
return role;
|
||||
return null;
|
||||
}
|
||||
|
||||
public override Task<IGuildUser> GetUserAsync(ulong id) => Task.FromResult<IGuildUser>(GetUser(id));
|
||||
public override Task<IGuildUser> GetCurrentUserAsync()
|
||||
=> Task.FromResult<IGuildUser>(CurrentUser);
|
||||
public override Task<IReadOnlyCollection<IGuildUser>> GetUsersAsync()
|
||||
=> Task.FromResult<IReadOnlyCollection<IGuildUser>>(Members);
|
||||
public SocketGuildUser AddOrUpdateUser(MemberModel model, DataStore dataStore, ConcurrentDictionary<ulong, SocketGuildUser> members = null)
|
||||
{
|
||||
members = members ?? _members;
|
||||
|
||||
SocketGuildUser member;
|
||||
if (members.TryGetValue(model.User.Id, out member))
|
||||
member.Update(model, UpdateSource.WebSocket);
|
||||
else
|
||||
{
|
||||
var user = Discord.GetOrAddUser(model.User, dataStore);
|
||||
member = new SocketGuildUser(this, user, model);
|
||||
members[user.Id] = member;
|
||||
DownloadedMemberCount++;
|
||||
}
|
||||
return member;
|
||||
}
|
||||
public SocketGuildUser AddOrUpdateUser(PresenceModel model, DataStore dataStore, ConcurrentDictionary<ulong, SocketGuildUser> members = null)
|
||||
{
|
||||
members = members ?? _members;
|
||||
|
||||
SocketGuildUser member;
|
||||
if (members.TryGetValue(model.User.Id, out member))
|
||||
member.Update(model, UpdateSource.WebSocket);
|
||||
else
|
||||
{
|
||||
var user = Discord.GetOrAddUser(model.User, dataStore);
|
||||
member = new SocketGuildUser(this, user, model);
|
||||
members[user.Id] = member;
|
||||
DownloadedMemberCount++;
|
||||
}
|
||||
return member;
|
||||
}
|
||||
public SocketGuildUser GetUser(ulong id)
|
||||
{
|
||||
SocketGuildUser member;
|
||||
if (_members.TryGetValue(id, out member))
|
||||
return member;
|
||||
return null;
|
||||
}
|
||||
public SocketGuildUser RemoveUser(ulong id)
|
||||
{
|
||||
SocketGuildUser member;
|
||||
if (_members.TryRemove(id, out member))
|
||||
{
|
||||
DownloadedMemberCount--;
|
||||
return member;
|
||||
}
|
||||
member.User.RemoveRef(Discord);
|
||||
return null;
|
||||
}
|
||||
public override async Task DownloadUsersAsync()
|
||||
{
|
||||
await Discord.DownloadUsersAsync(new [] { this });
|
||||
}
|
||||
public void CompleteDownloadMembers()
|
||||
{
|
||||
_downloaderPromise.TrySetResultAsync(true);
|
||||
}
|
||||
|
||||
public VoiceState AddOrUpdateVoiceState(VoiceStateModel model, DataStore dataStore, ConcurrentDictionary<ulong, VoiceState> voiceStates = null)
|
||||
{
|
||||
var voiceChannel = dataStore.GetChannel(model.ChannelId.Value) as SocketVoiceChannel;
|
||||
var voiceState = new VoiceState(voiceChannel, model);
|
||||
(voiceStates ?? _voiceStates)[model.UserId] = voiceState;
|
||||
return voiceState;
|
||||
}
|
||||
public VoiceState? GetVoiceState(ulong id)
|
||||
{
|
||||
VoiceState voiceState;
|
||||
if (_voiceStates.TryGetValue(id, out voiceState))
|
||||
return voiceState;
|
||||
return null;
|
||||
}
|
||||
public VoiceState? RemoveVoiceState(ulong id)
|
||||
{
|
||||
VoiceState voiceState;
|
||||
if (_voiceStates.TryRemove(id, out voiceState))
|
||||
return voiceState;
|
||||
return null;
|
||||
}
|
||||
|
||||
public async Task<IAudioClient> ConnectAudioAsync(ulong channelId, bool selfDeaf, bool selfMute)
|
||||
{
|
||||
try
|
||||
{
|
||||
TaskCompletionSource<AudioClient> promise;
|
||||
|
||||
await _audioLock.WaitAsync().ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
await DisconnectAudioInternalAsync().ConfigureAwait(false);
|
||||
promise = new TaskCompletionSource<AudioClient>();
|
||||
_audioConnectPromise = promise;
|
||||
await Discord.ApiClient.SendVoiceStateUpdateAsync(Id, channelId, selfDeaf, selfMute).ConfigureAwait(false);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_audioLock.Release();
|
||||
}
|
||||
|
||||
var timeoutTask = Task.Delay(15000);
|
||||
if (await Task.WhenAny(promise.Task, timeoutTask) == timeoutTask)
|
||||
throw new TimeoutException();
|
||||
return await promise.Task.ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
await DisconnectAudioInternalAsync().ConfigureAwait(false);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
public async Task DisconnectAudioAsync(AudioClient client = null)
|
||||
{
|
||||
await _audioLock.WaitAsync().ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
await DisconnectAudioInternalAsync(client).ConfigureAwait(false);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_audioLock.Release();
|
||||
}
|
||||
}
|
||||
private async Task DisconnectAudioInternalAsync(AudioClient client = null)
|
||||
{
|
||||
var oldClient = AudioClient;
|
||||
if (oldClient != null)
|
||||
{
|
||||
if (client == null || oldClient == client)
|
||||
{
|
||||
_audioConnectPromise?.TrySetCanceledAsync(); //Cancel any previous audio connection
|
||||
_audioConnectPromise = null;
|
||||
}
|
||||
if (oldClient == client)
|
||||
{
|
||||
AudioClient = null;
|
||||
await oldClient.DisconnectAsync().ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
public async Task FinishConnectAudio(int id, string url, string token)
|
||||
{
|
||||
var voiceState = GetVoiceState(CurrentUser.Id).Value;
|
||||
|
||||
await _audioLock.WaitAsync().ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
if (AudioClient == null)
|
||||
{
|
||||
var audioClient = new AudioClient(this, id);
|
||||
audioClient.Disconnected += async ex =>
|
||||
{
|
||||
await _audioLock.WaitAsync().ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
if (AudioClient == audioClient) //Only reconnect if we're still assigned as this guild's audio client
|
||||
{
|
||||
if (ex != null)
|
||||
{
|
||||
//Reconnect if we still have channel info.
|
||||
//TODO: Is this threadsafe? Could channel data be deleted before we access it?
|
||||
var voiceState2 = GetVoiceState(CurrentUser.Id);
|
||||
if (voiceState2.HasValue)
|
||||
{
|
||||
var voiceChannelId = voiceState2.Value.VoiceChannel?.Id;
|
||||
if (voiceChannelId != null)
|
||||
await Discord.ApiClient.SendVoiceStateUpdateAsync(Id, voiceChannelId, voiceState2.Value.IsSelfDeafened, voiceState2.Value.IsSelfMuted);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
try { AudioClient.Dispose(); } catch { }
|
||||
AudioClient = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
_audioLock.Release();
|
||||
}
|
||||
};
|
||||
AudioClient = audioClient;
|
||||
}
|
||||
await AudioClient.ConnectAsync(url, CurrentUser.Id, voiceState.VoiceSessionId, token).ConfigureAwait(false);
|
||||
await _audioConnectPromise.TrySetResultAsync(AudioClient).ConfigureAwait(false);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
await DisconnectAudioAsync();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
await _audioConnectPromise.SetExceptionAsync(e).ConfigureAwait(false);
|
||||
await DisconnectAudioAsync();
|
||||
}
|
||||
finally
|
||||
{
|
||||
_audioLock.Release();
|
||||
}
|
||||
}
|
||||
public async Task FinishJoinAudioChannel()
|
||||
{
|
||||
await _audioLock.WaitAsync().ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
if (AudioClient != null)
|
||||
await _audioConnectPromise.TrySetResultAsync(AudioClient).ConfigureAwait(false);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_audioLock.Release();
|
||||
}
|
||||
}
|
||||
|
||||
public SocketGuild Clone() => MemberwiseClone() as SocketGuild;
|
||||
|
||||
new internal ISocketGuildChannel ToChannel(ChannelModel model)
|
||||
{
|
||||
switch (model.Type)
|
||||
{
|
||||
case ChannelType.Text:
|
||||
return new SocketTextChannel(this, model);
|
||||
case ChannelType.Voice:
|
||||
return new SocketVoiceChannel(this, model);
|
||||
default:
|
||||
throw new InvalidOperationException($"Unexpected channel type: {model.Type}");
|
||||
}
|
||||
}
|
||||
|
||||
bool IUserGuild.IsOwner => OwnerId == Discord.CurrentUser.Id;
|
||||
GuildPermissions IUserGuild.Permissions => CurrentUser.GuildPermissions;
|
||||
IAudioClient IGuild.AudioClient => AudioClient;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
using Discord.API.Rest;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Threading.Tasks;
|
||||
using Model = Discord.API.Integration;
|
||||
|
||||
namespace Discord.Rest
|
||||
{
|
||||
[DebuggerDisplay(@"{DebuggerDisplay,nq}")]
|
||||
internal class GuildIntegration : IEntity<ulong>, IGuildIntegration
|
||||
{
|
||||
private long _syncedAtTicks;
|
||||
|
||||
public string Name { get; private set; }
|
||||
public string Type { get; private set; }
|
||||
public bool IsEnabled { get; private set; }
|
||||
public bool IsSyncing { get; private set; }
|
||||
public ulong ExpireBehavior { get; private set; }
|
||||
public ulong ExpireGracePeriod { get; private set; }
|
||||
|
||||
public Guild Guild { get; private set; }
|
||||
public Role Role { get; private set; }
|
||||
public User User { get; private set; }
|
||||
public IntegrationAccount Account { get; private set; }
|
||||
|
||||
public override DiscordRestClient Discord => Guild.Discord;
|
||||
public DateTimeOffset SyncedAt => DateTimeUtils.FromTicks(_syncedAtTicks);
|
||||
|
||||
public GuildIntegration(Guild guild, Model model)
|
||||
: base(model.Id)
|
||||
{
|
||||
Guild = guild;
|
||||
Update(model);
|
||||
}
|
||||
|
||||
public void Update(Model model)
|
||||
{
|
||||
Name = model.Name;
|
||||
Type = model.Type;
|
||||
IsEnabled = model.Enabled;
|
||||
IsSyncing = model.Syncing;
|
||||
ExpireBehavior = model.ExpireBehavior;
|
||||
ExpireGracePeriod = model.ExpireGracePeriod;
|
||||
_syncedAtTicks = model.SyncedAt.UtcTicks;
|
||||
|
||||
Role = Guild.GetRole(model.RoleId);
|
||||
User = new User(model.User);
|
||||
}
|
||||
|
||||
public async Task DeleteAsync()
|
||||
{
|
||||
await Discord.ApiClient.DeleteGuildIntegrationAsync(Guild.Id, Id).ConfigureAwait(false);
|
||||
}
|
||||
public async Task ModifyAsync(Action<ModifyGuildIntegrationParams> func)
|
||||
{
|
||||
if (func == null) throw new NullReferenceException(nameof(func));
|
||||
|
||||
var args = new ModifyGuildIntegrationParams();
|
||||
func(args);
|
||||
var model = await Discord.ApiClient.ModifyGuildIntegrationAsync(Guild.Id, Id, args).ConfigureAwait(false);
|
||||
|
||||
Update(model, UpdateSource.Rest);
|
||||
}
|
||||
public async Task SyncAsync()
|
||||
{
|
||||
await Discord.ApiClient.SyncGuildIntegrationAsync(Guild.Id, Id).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public override string ToString() => Name;
|
||||
private string DebuggerDisplay => $"{Name} ({Id}{(IsEnabled ? ", Enabled" : "")})";
|
||||
|
||||
IGuild IGuildIntegration.Guild => Guild;
|
||||
IUser IGuildIntegration.User => User;
|
||||
IRole IGuildIntegration.Role => Role;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
using Model = Discord.API.Message;
|
||||
|
||||
namespace Discord.WebSocket
|
||||
{
|
||||
internal interface ISocketMessage : IMessage
|
||||
{
|
||||
DiscordSocketClient Discord { get; }
|
||||
new ISocketMessageChannel Channel { get; }
|
||||
|
||||
void Update(Model model);
|
||||
ISocketMessage Clone();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
using Discord.Rest;
|
||||
using Model = Discord.API.Message;
|
||||
|
||||
namespace Discord.WebSocket
|
||||
{
|
||||
internal class SocketSystemMessage : SystemMessage, ISocketMessage
|
||||
{
|
||||
internal override bool IsAttached => true;
|
||||
|
||||
public new DiscordSocketClient Discord => base.Discord as DiscordSocketClient;
|
||||
public new ISocketMessageChannel Channel => base.Channel as ISocketMessageChannel;
|
||||
|
||||
public SocketSystemMessage(ISocketMessageChannel channel, IUser author, Model model)
|
||||
: base(channel, author, model)
|
||||
{
|
||||
}
|
||||
|
||||
public ISocketMessage Clone() => MemberwiseClone() as ISocketMessage;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
using Discord.Rest;
|
||||
using Model = Discord.API.Message;
|
||||
|
||||
namespace Discord.WebSocket
|
||||
{
|
||||
internal class SocketUserMessage : UserMessage, ISocketMessage
|
||||
{
|
||||
internal override bool IsAttached => true;
|
||||
|
||||
public new DiscordSocketClient Discord => base.Discord as DiscordSocketClient;
|
||||
public new ISocketMessageChannel Channel => base.Channel as ISocketMessageChannel;
|
||||
|
||||
public SocketUserMessage(ISocketMessageChannel channel, IUser author, Model model)
|
||||
: base(channel, author, model)
|
||||
{
|
||||
}
|
||||
|
||||
public ISocketMessage Clone() => MemberwiseClone() as ISocketMessage;
|
||||
}
|
||||
}
|
||||
9
src/Discord.Net.WebSocket/Entities/Users/ISocketUser.cs
Normal file
9
src/Discord.Net.WebSocket/Entities/Users/ISocketUser.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
namespace Discord.WebSocket
|
||||
{
|
||||
internal interface ISocketUser : IUser, IEntity<ulong>
|
||||
{
|
||||
SocketGlobalUser User { get; }
|
||||
|
||||
ISocketUser Clone();
|
||||
}
|
||||
}
|
||||
17
src/Discord.Net.WebSocket/Entities/Users/Presence.cs
Normal file
17
src/Discord.Net.WebSocket/Entities/Users/Presence.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
namespace Discord.WebSocket
|
||||
{
|
||||
//TODO: C#7 Candidate for record type
|
||||
internal struct Presence : IPresence
|
||||
{
|
||||
public Game Game { get; }
|
||||
public UserStatus Status { get; }
|
||||
|
||||
public Presence(Game game, UserStatus status)
|
||||
{
|
||||
Game = game;
|
||||
Status = status;
|
||||
}
|
||||
|
||||
public Presence Clone() => this;
|
||||
}
|
||||
}
|
||||
46
src/Discord.Net.WebSocket/Entities/Users/SocketDMUser.cs
Normal file
46
src/Discord.Net.WebSocket/Entities/Users/SocketDMUser.cs
Normal file
@@ -0,0 +1,46 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using PresenceModel = Discord.API.Presence;
|
||||
|
||||
namespace Discord.WebSocket
|
||||
{
|
||||
[DebuggerDisplay("{DebuggerDisplay,nq}")]
|
||||
internal class SocketDMUser : ISocketUser
|
||||
{
|
||||
internal bool IsAttached => true;
|
||||
bool IEntity<ulong>.IsAttached => IsAttached;
|
||||
|
||||
public SocketGlobalUser User { get; }
|
||||
|
||||
public DiscordSocketClient Discord => User.Discord;
|
||||
|
||||
public Game Game => Presence.Game;
|
||||
public UserStatus Status => Presence.Status;
|
||||
public Presence Presence => User.Presence; //{ get; private set; }
|
||||
|
||||
public ulong Id => User.Id;
|
||||
public string AvatarUrl => User.AvatarUrl;
|
||||
public DateTimeOffset CreatedAt => User.CreatedAt;
|
||||
public string Discriminator => User.Discriminator;
|
||||
public ushort DiscriminatorValue => User.DiscriminatorValue;
|
||||
public bool IsBot => User.IsBot;
|
||||
public string Mention => MentionUtils.Mention(this);
|
||||
public string Username => User.Username;
|
||||
|
||||
public SocketDMUser(SocketGlobalUser user)
|
||||
{
|
||||
User = user;
|
||||
}
|
||||
|
||||
public void Update(PresenceModel model)
|
||||
{
|
||||
User.Update(model, source);
|
||||
}
|
||||
|
||||
public SocketDMUser Clone() => MemberwiseClone() as SocketDMUser;
|
||||
ISocketUser ISocketUser.Clone() => Clone();
|
||||
|
||||
public override string ToString() => $"{Username}#{Discriminator}";
|
||||
private string DebuggerDisplay => $"{Username}#{Discriminator} ({Id})";
|
||||
}
|
||||
}
|
||||
60
src/Discord.Net.WebSocket/Entities/Users/SocketGlobalUser.cs
Normal file
60
src/Discord.Net.WebSocket/Entities/Users/SocketGlobalUser.cs
Normal file
@@ -0,0 +1,60 @@
|
||||
using Discord.Rest;
|
||||
using System;
|
||||
using Model = Discord.API.User;
|
||||
using PresenceModel = Discord.API.Presence;
|
||||
|
||||
namespace Discord.WebSocket
|
||||
{
|
||||
internal class SocketGlobalUser : User, ISocketUser
|
||||
{
|
||||
internal override bool IsAttached => true;
|
||||
|
||||
private ushort _references;
|
||||
|
||||
public Presence Presence { get; private set; }
|
||||
|
||||
public new DiscordSocketClient Discord { get { throw new NotSupportedException(); } }
|
||||
SocketGlobalUser ISocketUser.User => this;
|
||||
|
||||
public SocketGlobalUser(Model model)
|
||||
: base(model)
|
||||
{
|
||||
}
|
||||
|
||||
public void AddRef()
|
||||
{
|
||||
checked
|
||||
{
|
||||
lock (this)
|
||||
_references++;
|
||||
}
|
||||
}
|
||||
public void RemoveRef(DiscordSocketClient discord)
|
||||
{
|
||||
lock (this)
|
||||
{
|
||||
if (--_references == 0)
|
||||
discord.RemoveUser(Id);
|
||||
}
|
||||
}
|
||||
|
||||
public override void Update(Model model)
|
||||
{
|
||||
lock (this)
|
||||
base.Update(model, source);
|
||||
}
|
||||
public void Update(PresenceModel model)
|
||||
{
|
||||
//Race conditions are okay here. Multiple shards racing already cant guarantee presence in order.
|
||||
|
||||
//lock (this)
|
||||
//{
|
||||
var game = model.Game != null ? new Game(model.Game) : null;
|
||||
Presence = new Presence(game, model.Status);
|
||||
//}
|
||||
}
|
||||
|
||||
public SocketGlobalUser Clone() => MemberwiseClone() as SocketGlobalUser;
|
||||
ISocketUser ISocketUser.Clone() => Clone();
|
||||
}
|
||||
}
|
||||
36
src/Discord.Net.WebSocket/Entities/Users/SocketGroupUser.cs
Normal file
36
src/Discord.Net.WebSocket/Entities/Users/SocketGroupUser.cs
Normal file
@@ -0,0 +1,36 @@
|
||||
using Discord.Rest;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace Discord.WebSocket
|
||||
{
|
||||
[DebuggerDisplay("{DebuggerDisplay,nq}")]
|
||||
internal class SocketGroupUser : GroupUser, ISocketUser
|
||||
{
|
||||
internal override bool IsAttached => true;
|
||||
|
||||
public new DiscordSocketClient Discord => base.Discord as DiscordSocketClient;
|
||||
public new SocketGroupChannel Channel => base.Channel as SocketGroupChannel;
|
||||
public new SocketGlobalUser User => base.User as SocketGlobalUser;
|
||||
public Presence Presence => User.Presence; //{ get; private set; }
|
||||
|
||||
public override Game Game => Presence.Game;
|
||||
public override UserStatus Status => Presence.Status;
|
||||
|
||||
public VoiceState? VoiceState => Channel.GetVoiceState(Id);
|
||||
public bool IsSelfDeafened => VoiceState?.IsSelfDeafened ?? false;
|
||||
public bool IsSelfMuted => VoiceState?.IsSelfMuted ?? false;
|
||||
public bool IsSuppressed => VoiceState?.IsSuppressed ?? false;
|
||||
public SocketVoiceChannel VoiceChannel => VoiceState?.VoiceChannel;
|
||||
|
||||
public SocketGroupUser(SocketGroupChannel channel, SocketGlobalUser user)
|
||||
: base(channel, user)
|
||||
{
|
||||
}
|
||||
|
||||
public SocketGroupUser Clone() => MemberwiseClone() as SocketGroupUser;
|
||||
ISocketUser ISocketUser.Clone() => Clone();
|
||||
|
||||
public override string ToString() => $"{Username}#{Discriminator}";
|
||||
private string DebuggerDisplay => $"{Username}#{Discriminator} ({Id})";
|
||||
}
|
||||
}
|
||||
53
src/Discord.Net.WebSocket/Entities/Users/SocketGuildUser.cs
Normal file
53
src/Discord.Net.WebSocket/Entities/Users/SocketGuildUser.cs
Normal file
@@ -0,0 +1,53 @@
|
||||
using Discord.Rest;
|
||||
using Model = Discord.API.GuildMember;
|
||||
using PresenceModel = Discord.API.Presence;
|
||||
|
||||
namespace Discord.WebSocket
|
||||
{
|
||||
internal class SocketGuildUser : GuildUser, ISocketUser, IVoiceState
|
||||
{
|
||||
internal override bool IsAttached => true;
|
||||
|
||||
public new DiscordSocketClient Discord => base.Discord as DiscordSocketClient;
|
||||
public new SocketGuild Guild => base.Guild as SocketGuild;
|
||||
public new SocketGlobalUser User => base.User as SocketGlobalUser;
|
||||
public Presence Presence => User.Presence; //{ get; private set; }
|
||||
|
||||
public override Game Game => Presence.Game;
|
||||
public override UserStatus Status => Presence.Status;
|
||||
|
||||
public VoiceState? VoiceState => Guild.GetVoiceState(Id);
|
||||
public bool IsSelfDeafened => VoiceState?.IsSelfDeafened ?? false;
|
||||
public bool IsSelfMuted => VoiceState?.IsSelfMuted ?? false;
|
||||
public bool IsSuppressed => VoiceState?.IsSuppressed ?? false;
|
||||
public SocketVoiceChannel VoiceChannel => VoiceState?.VoiceChannel;
|
||||
public bool IsDeafened => VoiceState?.IsDeafened ?? false;
|
||||
public bool IsMuted => VoiceState?.IsMuted ?? false;
|
||||
public string VoiceSessionId => VoiceState?.VoiceSessionId ?? "";
|
||||
|
||||
public SocketGuildUser(SocketGuild guild, SocketGlobalUser user, Model model)
|
||||
: base(guild, user, model)
|
||||
{
|
||||
//Presence = new Presence(null, UserStatus.Offline);
|
||||
}
|
||||
public SocketGuildUser(SocketGuild guild, SocketGlobalUser user, PresenceModel model)
|
||||
: base(guild, user, model)
|
||||
{
|
||||
}
|
||||
|
||||
public override void Update(PresenceModel model)
|
||||
{
|
||||
base.Update(model, source);
|
||||
|
||||
var game = model.Game != null ? new Game(model.Game) : null;
|
||||
//Presence = new Presence(game, model.Status);
|
||||
|
||||
User.Update(model, source);
|
||||
}
|
||||
|
||||
IVoiceChannel IVoiceState.VoiceChannel => VoiceState?.VoiceChannel;
|
||||
|
||||
public SocketGuildUser Clone() => MemberwiseClone() as SocketGuildUser;
|
||||
ISocketUser ISocketUser.Clone() => Clone();
|
||||
}
|
||||
}
|
||||
47
src/Discord.Net.WebSocket/Entities/Users/SocketSelfUser.cs
Normal file
47
src/Discord.Net.WebSocket/Entities/Users/SocketSelfUser.cs
Normal file
@@ -0,0 +1,47 @@
|
||||
using Discord.API.Rest;
|
||||
using Discord.Rest;
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Model = Discord.API.User;
|
||||
|
||||
namespace Discord.WebSocket
|
||||
{
|
||||
internal class SocketSelfUser : SelfUser, ISocketUser, ISelfUser
|
||||
{
|
||||
internal override bool IsAttached => true;
|
||||
|
||||
public new DiscordSocketClient Discord => base.Discord as DiscordSocketClient;
|
||||
SocketGlobalUser ISocketUser.User { get { throw new NotSupportedException(); } }
|
||||
|
||||
public SocketSelfUser(DiscordSocketClient discord, Model model)
|
||||
: base(discord, model)
|
||||
{
|
||||
}
|
||||
|
||||
public async Task ModifyStatusAsync(Action<ModifyPresenceParams> func)
|
||||
{
|
||||
if (func == null) throw new NullReferenceException(nameof(func));
|
||||
|
||||
var args = new ModifyPresenceParams();
|
||||
func(args);
|
||||
|
||||
var game = args._game.GetValueOrDefault(_game);
|
||||
var status = args._status.GetValueOrDefault(_status);
|
||||
|
||||
long idleSince = _idleSince;
|
||||
if (status == UserStatus.Idle && _status != UserStatus.Idle)
|
||||
idleSince = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
|
||||
var apiGame = game != null ? new API.Game { Name = game.Name, StreamType = game.StreamType, StreamUrl = game.StreamUrl } : null;
|
||||
|
||||
await Discord.ApiClient.SendStatusUpdateAsync(status == UserStatus.Idle ? _idleSince : (long?)null, apiGame).ConfigureAwait(false);
|
||||
|
||||
//Save values
|
||||
_idleSince = idleSince;
|
||||
_game = game;
|
||||
_status = status;
|
||||
}
|
||||
|
||||
public SocketSelfUser Clone() => MemberwiseClone() as SocketSelfUser;
|
||||
ISocketUser ISocketUser.Clone() => Clone();
|
||||
}
|
||||
}
|
||||
52
src/Discord.Net.WebSocket/Entities/Users/VoiceState.cs
Normal file
52
src/Discord.Net.WebSocket/Entities/Users/VoiceState.cs
Normal file
@@ -0,0 +1,52 @@
|
||||
using System;
|
||||
using Model = Discord.API.VoiceState;
|
||||
|
||||
namespace Discord.WebSocket
|
||||
{
|
||||
//TODO: C#7 Candidate for record type
|
||||
internal struct VoiceState : IVoiceState
|
||||
{
|
||||
[Flags]
|
||||
private enum Flags : byte
|
||||
{
|
||||
None = 0x00,
|
||||
Suppressed = 0x01,
|
||||
Muted = 0x02,
|
||||
Deafened = 0x04,
|
||||
SelfMuted = 0x08,
|
||||
SelfDeafened = 0x10,
|
||||
}
|
||||
|
||||
private readonly Flags _voiceStates;
|
||||
|
||||
public SocketVoiceChannel VoiceChannel { get; }
|
||||
public string VoiceSessionId { get; }
|
||||
|
||||
public bool IsMuted => (_voiceStates & Flags.Muted) != 0;
|
||||
public bool IsDeafened => (_voiceStates & Flags.Deafened) != 0;
|
||||
public bool IsSuppressed => (_voiceStates & Flags.Suppressed) != 0;
|
||||
public bool IsSelfMuted => (_voiceStates & Flags.SelfMuted) != 0;
|
||||
public bool IsSelfDeafened => (_voiceStates & Flags.SelfDeafened) != 0;
|
||||
|
||||
public VoiceState(SocketVoiceChannel voiceChannel, Model model)
|
||||
: this(voiceChannel, model.SessionId, model.SelfMute, model.SelfDeaf, model.Suppress) { }
|
||||
public VoiceState(SocketVoiceChannel voiceChannel, string sessionId, bool isSelfMuted, bool isSelfDeafened, bool isSuppressed)
|
||||
{
|
||||
VoiceChannel = voiceChannel;
|
||||
VoiceSessionId = sessionId;
|
||||
|
||||
Flags voiceStates = Flags.None;
|
||||
if (isSelfMuted)
|
||||
voiceStates |= Flags.SelfMuted;
|
||||
if (isSelfDeafened)
|
||||
voiceStates |= Flags.SelfDeafened;
|
||||
if (isSuppressed)
|
||||
voiceStates |= Flags.Suppressed;
|
||||
_voiceStates = voiceStates;
|
||||
}
|
||||
|
||||
public VoiceState Clone() => this;
|
||||
|
||||
IVoiceChannel IVoiceState.VoiceChannel => VoiceChannel;
|
||||
}
|
||||
}
|
||||
87
src/Discord.Net.WebSocket/Entities/Utilities/MessageCache.cs
Normal file
87
src/Discord.Net.WebSocket/Entities/Utilities/MessageCache.cs
Normal file
@@ -0,0 +1,87 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Discord.WebSocket
|
||||
{
|
||||
internal class MessageCache : MessageManager
|
||||
{
|
||||
private readonly ConcurrentDictionary<ulong, ISocketMessage> _messages;
|
||||
private readonly ConcurrentQueue<ulong> _orderedMessages;
|
||||
private readonly int _size;
|
||||
|
||||
public override IReadOnlyCollection<ISocketMessage> Messages => _messages.ToReadOnlyCollection();
|
||||
|
||||
public MessageCache(DiscordSocketClient discord, ISocketMessageChannel channel)
|
||||
: base(discord, channel)
|
||||
{
|
||||
_size = discord.MessageCacheSize;
|
||||
_messages = new ConcurrentDictionary<ulong, ISocketMessage>(1, (int)(_size * 1.05));
|
||||
_orderedMessages = new ConcurrentQueue<ulong>();
|
||||
}
|
||||
|
||||
public override void Add(ISocketMessage message)
|
||||
{
|
||||
if (_messages.TryAdd(message.Id, message))
|
||||
{
|
||||
_orderedMessages.Enqueue(message.Id);
|
||||
|
||||
ulong msgId;
|
||||
ISocketMessage msg;
|
||||
while (_orderedMessages.Count > _size && _orderedMessages.TryDequeue(out msgId))
|
||||
_messages.TryRemove(msgId, out msg);
|
||||
}
|
||||
}
|
||||
|
||||
public override ISocketMessage Remove(ulong id)
|
||||
{
|
||||
ISocketMessage msg;
|
||||
_messages.TryRemove(id, out msg);
|
||||
return msg;
|
||||
}
|
||||
|
||||
public override ISocketMessage Get(ulong id)
|
||||
{
|
||||
ISocketMessage result;
|
||||
if (_messages.TryGetValue(id, out result))
|
||||
return result;
|
||||
return null;
|
||||
}
|
||||
public override IImmutableList<ISocketMessage> GetMany(ulong? fromMessageId, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch)
|
||||
{
|
||||
if (limit < 0) throw new ArgumentOutOfRangeException(nameof(limit));
|
||||
if (limit == 0) return ImmutableArray<ISocketMessage>.Empty;
|
||||
|
||||
IEnumerable<ulong> cachedMessageIds;
|
||||
if (fromMessageId == null)
|
||||
cachedMessageIds = _orderedMessages;
|
||||
else if (dir == Direction.Before)
|
||||
cachedMessageIds = _orderedMessages.Where(x => x < fromMessageId.Value);
|
||||
else
|
||||
cachedMessageIds = _orderedMessages.Where(x => x > fromMessageId.Value);
|
||||
|
||||
return cachedMessageIds
|
||||
.Take(limit)
|
||||
.Select(x =>
|
||||
{
|
||||
ISocketMessage msg;
|
||||
if (_messages.TryGetValue(x, out msg))
|
||||
return msg;
|
||||
return null;
|
||||
})
|
||||
.Where(x => x != null)
|
||||
.ToImmutableArray();
|
||||
}
|
||||
|
||||
public override async Task<ISocketMessage> DownloadAsync(ulong id)
|
||||
{
|
||||
var msg = Get(id);
|
||||
if (msg != null)
|
||||
return msg;
|
||||
return await base.DownloadAsync(id).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
using Discord.API.Rest;
|
||||
using Discord.Rest;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Model = Discord.API.Message;
|
||||
|
||||
namespace Discord.WebSocket
|
||||
{
|
||||
internal class MessageManager
|
||||
{
|
||||
private readonly DiscordSocketClient _discord;
|
||||
private readonly ISocketMessageChannel _channel;
|
||||
|
||||
public virtual IReadOnlyCollection<ISocketMessage> Messages
|
||||
=> ImmutableArray.Create<ISocketMessage>();
|
||||
|
||||
public MessageManager(DiscordSocketClient discord, ISocketMessageChannel channel)
|
||||
{
|
||||
_discord = discord;
|
||||
_channel = channel;
|
||||
}
|
||||
|
||||
public virtual void Add(ISocketMessage message) { }
|
||||
public virtual ISocketMessage Remove(ulong id) => null;
|
||||
public virtual ISocketMessage Get(ulong id) => null;
|
||||
|
||||
public virtual IImmutableList<ISocketMessage> GetMany(ulong? fromMessageId, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch)
|
||||
=> ImmutableArray.Create<ISocketMessage>();
|
||||
|
||||
public virtual async Task<ISocketMessage> DownloadAsync(ulong id)
|
||||
{
|
||||
var model = await _discord.ApiClient.GetChannelMessageAsync(_channel.Id, id).ConfigureAwait(false);
|
||||
if (model != null)
|
||||
return Create(new User(model.Author.Value), model);
|
||||
return null;
|
||||
}
|
||||
public async Task<IReadOnlyCollection<ISocketMessage>> DownloadAsync(ulong? fromId, Direction dir, int limit)
|
||||
{
|
||||
//TODO: Test heavily, especially the ordering of messages
|
||||
if (limit < 0) throw new ArgumentOutOfRangeException(nameof(limit));
|
||||
if (limit == 0) return ImmutableArray<ISocketMessage>.Empty;
|
||||
|
||||
var cachedMessages = GetMany(fromId, dir, limit);
|
||||
if (cachedMessages.Count == limit)
|
||||
return cachedMessages;
|
||||
else if (cachedMessages.Count > limit)
|
||||
return cachedMessages.Skip(cachedMessages.Count - limit).ToImmutableArray();
|
||||
else
|
||||
{
|
||||
var args = new GetChannelMessagesParams
|
||||
{
|
||||
Limit = limit - cachedMessages.Count,
|
||||
RelativeDirection = dir
|
||||
};
|
||||
if (cachedMessages.Count == 0)
|
||||
{
|
||||
if (fromId != null)
|
||||
args.RelativeMessageId = fromId.Value;
|
||||
}
|
||||
else
|
||||
args.RelativeMessageId = dir == Direction.Before ? cachedMessages[0].Id : cachedMessages[cachedMessages.Count - 1].Id;
|
||||
var downloadedMessages = await _discord.ApiClient.GetChannelMessagesAsync(_channel.Id, args).ConfigureAwait(false);
|
||||
|
||||
var guild = (_channel as ISocketGuildChannel)?.Guild;
|
||||
return cachedMessages.Concat(downloadedMessages.Select(x =>
|
||||
{
|
||||
IUser user = _channel.GetUser(x.Author.Value.Id, true);
|
||||
if (user == null)
|
||||
{
|
||||
var newUser = new User(x.Author.Value);
|
||||
if (guild != null)
|
||||
user = new GuildUser(guild, newUser);
|
||||
else
|
||||
user = newUser;
|
||||
}
|
||||
return Create(user, x);
|
||||
})).ToImmutableArray();
|
||||
}
|
||||
}
|
||||
|
||||
public ISocketMessage Create(IUser author, Model model)
|
||||
{
|
||||
if (model.Type == MessageType.Default)
|
||||
return new SocketUserMessage(_channel, author, model);
|
||||
else
|
||||
return new SocketSystemMessage(_channel, author, model);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user