Concrete class prototype

This commit is contained in:
RogueException
2016-09-22 21:15:37 -03:00
parent ab42129eb9
commit 6319933ed0
394 changed files with 3648 additions and 3224 deletions

View File

@@ -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();
}
}

View File

@@ -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();
}
}

View File

@@ -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);
}
}

View File

@@ -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();
}
}

View File

@@ -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();
}
}

View 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;
}
}

View File

@@ -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;
}
}

View File

@@ -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();
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -0,0 +1,9 @@
namespace Discord.WebSocket
{
internal interface ISocketUser : IUser, IEntity<ulong>
{
SocketGlobalUser User { get; }
ISocketUser Clone();
}
}

View 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;
}
}

View 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})";
}
}

View 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();
}
}

View 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})";
}
}

View 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();
}
}

View 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();
}
}

View 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;
}
}

View 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);
}
}
}

View File

@@ -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);
}
}
}