@@ -12,7 +12,7 @@ namespace Discord.API | |||||
[JsonProperty("roles")] | [JsonProperty("roles")] | ||||
public ulong[] Roles { get; set; } | public ulong[] Roles { get; set; } | ||||
[JsonProperty("joined_at")] | [JsonProperty("joined_at")] | ||||
public DateTime JoinedAt { get; set; } | |||||
public DateTimeOffset JoinedAt { get; set; } | |||||
[JsonProperty("deaf")] | [JsonProperty("deaf")] | ||||
public bool Deaf { get; set; } | public bool Deaf { get; set; } | ||||
[JsonProperty("mute")] | [JsonProperty("mute")] | ||||
@@ -26,6 +26,6 @@ namespace Discord.API | |||||
[JsonProperty("account")] | [JsonProperty("account")] | ||||
public IntegrationAccount Account { get; set; } | public IntegrationAccount Account { get; set; } | ||||
[JsonProperty("synced_at")] | [JsonProperty("synced_at")] | ||||
public DateTime SyncedAt { get; set; } | |||||
public DateTimeOffset SyncedAt { get; set; } | |||||
} | } | ||||
} | } |
@@ -16,7 +16,7 @@ namespace Discord.API | |||||
[JsonProperty("temporary")] | [JsonProperty("temporary")] | ||||
public bool Temporary { get; set; } | public bool Temporary { get; set; } | ||||
[JsonProperty("created_at")] | [JsonProperty("created_at")] | ||||
public DateTime CreatedAt { get; set; } | |||||
public DateTimeOffset CreatedAt { get; set; } | |||||
[JsonProperty("revoked")] | [JsonProperty("revoked")] | ||||
public bool Revoked { get; set; } | public bool Revoked { get; set; } | ||||
} | } | ||||
@@ -14,9 +14,9 @@ namespace Discord.API | |||||
[JsonProperty("content")] | [JsonProperty("content")] | ||||
public Optional<string> Content { get; set; } | public Optional<string> Content { get; set; } | ||||
[JsonProperty("timestamp")] | [JsonProperty("timestamp")] | ||||
public Optional<DateTime> Timestamp { get; set; } | |||||
public Optional<DateTimeOffset> Timestamp { get; set; } | |||||
[JsonProperty("edited_timestamp")] | [JsonProperty("edited_timestamp")] | ||||
public Optional<DateTime?> EditedTimestamp { get; set; } | |||||
public Optional<DateTimeOffset?> EditedTimestamp { get; set; } | |||||
[JsonProperty("tts")] | [JsonProperty("tts")] | ||||
public Optional<bool> IsTextToSpeech { get; set; } | public Optional<bool> IsTextToSpeech { get; set; } | ||||
[JsonProperty("mention_everyone")] | [JsonProperty("mention_everyone")] | ||||
@@ -19,6 +19,6 @@ namespace Discord.API.Gateway | |||||
[JsonProperty("channels")] | [JsonProperty("channels")] | ||||
public Channel[] Channels { get; set; } | public Channel[] Channels { get; set; } | ||||
[JsonProperty("joined_at")] | [JsonProperty("joined_at")] | ||||
public DateTime JoinedAt { get; set; } | |||||
public DateTimeOffset JoinedAt { get; set; } | |||||
} | } | ||||
} | } |
@@ -10,7 +10,6 @@ using System; | |||||
using System.Collections.Concurrent; | using System.Collections.Concurrent; | ||||
using System.Collections.Generic; | using System.Collections.Generic; | ||||
using System.Collections.Immutable; | using System.Collections.Immutable; | ||||
using System.Diagnostics; | |||||
using System.Linq; | using System.Linq; | ||||
using System.Threading; | using System.Threading; | ||||
using System.Threading.Tasks; | using System.Threading.Tasks; | ||||
@@ -38,6 +37,7 @@ namespace Discord | |||||
public event Func<ISelfUser, ISelfUser, Task> CurrentUserUpdated; | public event Func<ISelfUser, ISelfUser, Task> CurrentUserUpdated; | ||||
public event Func<IChannel, IUser, Task> UserIsTyping; | public event Func<IChannel, IUser, Task> UserIsTyping; | ||||
public event Func<int, Task> LatencyUpdated; | public event Func<int, Task> LatencyUpdated; | ||||
//TODO: Add PresenceUpdated? VoiceStateUpdated? | |||||
private readonly ConcurrentQueue<ulong> _largeGuilds; | private readonly ConcurrentQueue<ulong> _largeGuilds; | ||||
private readonly Logger _gatewayLogger; | private readonly Logger _gatewayLogger; | ||||
@@ -50,6 +50,7 @@ namespace Discord | |||||
private readonly bool _enablePreUpdateEvents; | private readonly bool _enablePreUpdateEvents; | ||||
private readonly int _largeThreshold; | private readonly int _largeThreshold; | ||||
private readonly int _totalShards; | private readonly int _totalShards; | ||||
private ConcurrentHashSet<ulong> _dmChannels; | |||||
private string _sessionId; | private string _sessionId; | ||||
private int _lastSeq; | private int _lastSeq; | ||||
private ImmutableDictionary<string, VoiceRegion> _voiceRegions; | private ImmutableDictionary<string, VoiceRegion> _voiceRegions; | ||||
@@ -71,20 +72,14 @@ namespace Discord | |||||
internal DataStore DataStore { get; private set; } | internal DataStore DataStore { get; private set; } | ||||
internal CachedSelfUser CurrentUser => _currentUser as CachedSelfUser; | internal CachedSelfUser CurrentUser => _currentUser as CachedSelfUser; | ||||
internal IReadOnlyCollection<CachedGuild> Guilds | |||||
{ | |||||
get | |||||
{ | |||||
var guilds = DataStore.Guilds; | |||||
return guilds.ToReadOnlyCollection(guilds); | |||||
} | |||||
} | |||||
internal IReadOnlyCollection<CachedGuild> Guilds => DataStore.Guilds; | |||||
internal IReadOnlyCollection<CachedDMChannel> DMChannels | internal IReadOnlyCollection<CachedDMChannel> DMChannels | ||||
{ | { | ||||
get | get | ||||
{ | { | ||||
var users = DataStore.Users; | |||||
return users.Select(x => x.DMChannel).Where(x => x != null).ToReadOnlyCollection(users); | |||||
var dmChannels = _dmChannels; | |||||
var store = DataStore; | |||||
return dmChannels.Select(x => store.GetChannel(x) as CachedDMChannel).Where(x => x != null).ToReadOnlyCollection(dmChannels); | |||||
} | } | ||||
} | } | ||||
internal IReadOnlyCollection<VoiceRegion> VoiceRegions => _voiceRegions.ToReadOnlyCollection(); | internal IReadOnlyCollection<VoiceRegion> VoiceRegions => _voiceRegions.ToReadOnlyCollection(); | ||||
@@ -136,6 +131,7 @@ namespace Discord | |||||
_voiceRegions = ImmutableDictionary.Create<string, VoiceRegion>(); | _voiceRegions = ImmutableDictionary.Create<string, VoiceRegion>(); | ||||
_largeGuilds = new ConcurrentQueue<ulong>(); | _largeGuilds = new ConcurrentQueue<ulong>(); | ||||
_dmChannels = new ConcurrentHashSet<ulong>(); | |||||
} | } | ||||
protected override async Task OnLoginAsync() | protected override async Task OnLoginAsync() | ||||
@@ -305,11 +301,16 @@ namespace Discord | |||||
{ | { | ||||
return Task.FromResult<IChannel>(DataStore.GetChannel(id)); | return Task.FromResult<IChannel>(DataStore.GetChannel(id)); | ||||
} | } | ||||
internal CachedDMChannel AddDMChannel(API.Channel model, DataStore dataStore) | |||||
public override Task<IReadOnlyCollection<IDMChannel>> GetDMChannelsAsync() | |||||
{ | |||||
return Task.FromResult<IReadOnlyCollection<IDMChannel>>(DMChannels); | |||||
} | |||||
internal CachedDMChannel AddDMChannel(API.Channel model, DataStore dataStore, ConcurrentHashSet<ulong> dmChannels) | |||||
{ | { | ||||
var recipient = GetOrAddUser(model.Recipient.Value, dataStore); | var recipient = GetOrAddUser(model.Recipient.Value, dataStore); | ||||
var channel = recipient.AddDMChannel(model); | var channel = recipient.AddDMChannel(model); | ||||
dataStore.AddChannel(channel); | dataStore.AddChannel(channel); | ||||
dmChannels.TryAdd(model.Id); | |||||
return channel; | return channel; | ||||
} | } | ||||
internal CachedDMChannel RemoveDMChannel(ulong id) | internal CachedDMChannel RemoveDMChannel(ulong id) | ||||
@@ -317,6 +318,7 @@ namespace Discord | |||||
var dmChannel = DataStore.RemoveChannel(id) as CachedDMChannel; | var dmChannel = DataStore.RemoveChannel(id) as CachedDMChannel; | ||||
var recipient = dmChannel.Recipient; | var recipient = dmChannel.Recipient; | ||||
recipient.RemoveDMChannel(id); | recipient.RemoveDMChannel(id); | ||||
_dmChannels.TryRemove(id); | |||||
return dmChannel; | return dmChannel; | ||||
} | } | ||||
@@ -455,6 +457,7 @@ namespace Discord | |||||
var data = (payload as JToken).ToObject<ReadyEvent>(_serializer); | var data = (payload as JToken).ToObject<ReadyEvent>(_serializer); | ||||
var dataStore = _dataStoreProvider(ShardId, _totalShards, data.Guilds.Length, data.PrivateChannels.Length); | var dataStore = _dataStoreProvider(ShardId, _totalShards, data.Guilds.Length, data.PrivateChannels.Length); | ||||
var dmChannels = new ConcurrentHashSet<ulong>(); | |||||
var currentUser = new CachedSelfUser(this, data.User); | var currentUser = new CachedSelfUser(this, data.User); | ||||
//dataStore.GetOrAddUser(data.User.Id, _ => currentUser); | //dataStore.GetOrAddUser(data.User.Id, _ => currentUser); | ||||
@@ -462,10 +465,11 @@ namespace Discord | |||||
for (int i = 0; i < data.Guilds.Length; i++) | for (int i = 0; i < data.Guilds.Length; i++) | ||||
AddGuild(data.Guilds[i], dataStore); | AddGuild(data.Guilds[i], dataStore); | ||||
for (int i = 0; i < data.PrivateChannels.Length; i++) | for (int i = 0; i < data.PrivateChannels.Length; i++) | ||||
AddDMChannel(data.PrivateChannels[i], dataStore); | |||||
AddDMChannel(data.PrivateChannels[i], dataStore, dmChannels); | |||||
_sessionId = data.SessionId; | _sessionId = data.SessionId; | ||||
_currentUser = currentUser; | _currentUser = currentUser; | ||||
_dmChannels = dmChannels; | |||||
DataStore = dataStore; | DataStore = dataStore; | ||||
await Ready.RaiseAsync().ConfigureAwait(false); | await Ready.RaiseAsync().ConfigureAwait(false); | ||||
@@ -577,7 +581,7 @@ namespace Discord | |||||
} | } | ||||
} | } | ||||
else | else | ||||
channel = AddDMChannel(data, DataStore); | |||||
channel = AddDMChannel(data, DataStore, _dmChannels); | |||||
if (channel != null) | if (channel != null) | ||||
await ChannelCreated.RaiseAsync(channel).ConfigureAwait(false); | await ChannelCreated.RaiseAsync(channel).ConfigureAwait(false); | ||||
} | } | ||||
@@ -9,13 +9,14 @@ namespace Discord | |||||
[DebuggerDisplay(@"{DebuggerDisplay,nq}")] | [DebuggerDisplay(@"{DebuggerDisplay,nq}")] | ||||
internal class GuildIntegration : Entity<ulong>, IGuildIntegration | internal class GuildIntegration : Entity<ulong>, IGuildIntegration | ||||
{ | { | ||||
private long _syncedAtTicks; | |||||
public string Name { get; private set; } | public string Name { get; private set; } | ||||
public string Type { get; private set; } | public string Type { get; private set; } | ||||
public bool IsEnabled { get; private set; } | public bool IsEnabled { get; private set; } | ||||
public bool IsSyncing { get; private set; } | public bool IsSyncing { get; private set; } | ||||
public ulong ExpireBehavior { get; private set; } | public ulong ExpireBehavior { get; private set; } | ||||
public ulong ExpireGracePeriod { get; private set; } | public ulong ExpireGracePeriod { get; private set; } | ||||
public DateTime SyncedAt { get; private set; } | |||||
public Guild Guild { get; private set; } | public Guild Guild { get; private set; } | ||||
public Role Role { get; private set; } | public Role Role { get; private set; } | ||||
@@ -23,6 +24,7 @@ namespace Discord | |||||
public IntegrationAccount Account { get; private set; } | public IntegrationAccount Account { get; private set; } | ||||
public override DiscordClient Discord => Guild.Discord; | public override DiscordClient Discord => Guild.Discord; | ||||
public DateTimeOffset SyncedAt => DateTimeUtils.FromTicks(_syncedAtTicks); | |||||
public GuildIntegration(Guild guild, Model model) | public GuildIntegration(Guild guild, Model model) | ||||
: base(model.Id) | : base(model.Id) | ||||
@@ -41,7 +43,7 @@ namespace Discord | |||||
IsSyncing = model.Syncing; | IsSyncing = model.Syncing; | ||||
ExpireBehavior = model.ExpireBehavior; | ExpireBehavior = model.ExpireBehavior; | ||||
ExpireGracePeriod = model.ExpireGracePeriod; | ExpireGracePeriod = model.ExpireGracePeriod; | ||||
SyncedAt = model.SyncedAt; | |||||
_syncedAtTicks = model.SyncedAt.UtcTicks; | |||||
Role = Guild.GetRole(model.RoleId); | Role = Guild.GetRole(model.RoleId); | ||||
User = new User(Discord, model.User); | User = new User(Discord, model.User); | ||||
@@ -2,6 +2,7 @@ | |||||
namespace Discord | namespace Discord | ||||
{ | { | ||||
//TODO: Add docstrings | |||||
public interface IGuildIntegration | public interface IGuildIntegration | ||||
{ | { | ||||
ulong Id { get; } | ulong Id { get; } | ||||
@@ -11,7 +12,7 @@ namespace Discord | |||||
bool IsSyncing { get; } | bool IsSyncing { get; } | ||||
ulong ExpireBehavior { get; } | ulong ExpireBehavior { get; } | ||||
ulong ExpireGracePeriod { get; } | ulong ExpireGracePeriod { get; } | ||||
DateTime SyncedAt { get; } | |||||
DateTimeOffset SyncedAt { get; } | |||||
IntegrationAccount Account { get; } | IntegrationAccount Account { get; } | ||||
IGuild Guild { get; } | IGuild Guild { get; } | ||||
@@ -5,6 +5,6 @@ namespace Discord | |||||
public interface ISnowflakeEntity : IEntity<ulong> | public interface ISnowflakeEntity : IEntity<ulong> | ||||
{ | { | ||||
/// <summary> Gets when this object was created. </summary> | /// <summary> Gets when this object was created. </summary> | ||||
DateTime CreatedAt { get; } | |||||
DateTimeOffset CreatedAt { get; } | |||||
} | } | ||||
} | } |
@@ -17,6 +17,6 @@ namespace Discord | |||||
/// <summary> Gets the amount of times this invite has been used. </summary> | /// <summary> Gets the amount of times this invite has been used. </summary> | ||||
int Uses { get; } | int Uses { get; } | ||||
/// <summary> Gets when this invite was created. </summary> | /// <summary> Gets when this invite was created. </summary> | ||||
DateTime CreatedAt { get; } | |||||
DateTimeOffset CreatedAt { get; } | |||||
} | } | ||||
} | } |
@@ -5,14 +5,17 @@ namespace Discord | |||||
{ | { | ||||
internal class InviteMetadata : Invite, IInviteMetadata | internal class InviteMetadata : Invite, IInviteMetadata | ||||
{ | { | ||||
private long _createdAtTicks; | |||||
public bool IsRevoked { get; private set; } | public bool IsRevoked { get; private set; } | ||||
public bool IsTemporary { get; private set; } | public bool IsTemporary { get; private set; } | ||||
public int? MaxAge { get; private set; } | public int? MaxAge { get; private set; } | ||||
public int? MaxUses { get; private set; } | public int? MaxUses { get; private set; } | ||||
public int Uses { get; private set; } | public int Uses { get; private set; } | ||||
public DateTime CreatedAt { get; private set; } | |||||
public IUser Inviter { get; private set; } | public IUser Inviter { get; private set; } | ||||
public DateTimeOffset CreatedAt => DateTimeUtils.FromTicks(_createdAtTicks); | |||||
public InviteMetadata(DiscordClient client, Model model) | public InviteMetadata(DiscordClient client, Model model) | ||||
: base(client, model) | : base(client, model) | ||||
{ | { | ||||
@@ -28,7 +31,7 @@ namespace Discord | |||||
MaxAge = model.MaxAge != 0 ? model.MaxAge : (int?)null; | MaxAge = model.MaxAge != 0 ? model.MaxAge : (int?)null; | ||||
MaxUses = model.MaxUses; | MaxUses = model.MaxUses; | ||||
Uses = model.Uses; | Uses = model.Uses; | ||||
CreatedAt = model.CreatedAt; | |||||
_createdAtTicks = model.CreatedAt.UtcTicks; | |||||
} | } | ||||
} | } | ||||
} | } |
@@ -8,7 +8,7 @@ namespace Discord | |||||
public interface IMessage : IDeletable, ISnowflakeEntity, IUpdateable | public interface IMessage : IDeletable, ISnowflakeEntity, IUpdateable | ||||
{ | { | ||||
/// <summary> Gets the time of this message's last edit, if any. </summary> | /// <summary> Gets the time of this message's last edit, if any. </summary> | ||||
DateTime? EditedTimestamp { get; } | |||||
DateTimeOffset? EditedTimestamp { get; } | |||||
/// <summary> Returns true if this message was sent as a text-to-speech message. </summary> | /// <summary> Returns true if this message was sent as a text-to-speech message. </summary> | ||||
bool IsTTS { get; } | bool IsTTS { get; } | ||||
/// <summary> Returns the original, unprocessed text for this message. </summary> | /// <summary> Returns the original, unprocessed text for this message. </summary> | ||||
@@ -16,7 +16,7 @@ namespace Discord | |||||
/// <summary> Returns the text for this message after mention processing. </summary> | /// <summary> Returns the text for this message after mention processing. </summary> | ||||
string Text { get; } | string Text { get; } | ||||
/// <summary> Gets the time this message was sent. </summary> | /// <summary> Gets the time this message was sent. </summary> | ||||
DateTime Timestamp { get; } | |||||
DateTimeOffset Timestamp { get; } | |||||
/// <summary> Gets the channel this message was sent to. </summary> | /// <summary> Gets the channel this message was sent to. </summary> | ||||
IMessageChannel Channel { get; } | IMessageChannel Channel { get; } | ||||
@@ -12,12 +12,12 @@ namespace Discord | |||||
internal class Message : SnowflakeEntity, IMessage | internal class Message : SnowflakeEntity, IMessage | ||||
{ | { | ||||
private bool _isMentioningEveryone; | private bool _isMentioningEveryone; | ||||
private long _timestampTicks; | |||||
private long? _editedTimestampTicks; | |||||
public DateTime? EditedTimestamp { get; private set; } | |||||
public bool IsTTS { get; private set; } | public bool IsTTS { get; private set; } | ||||
public string RawText { get; private set; } | public string RawText { get; private set; } | ||||
public string Text { get; private set; } | public string Text { get; private set; } | ||||
public DateTime Timestamp { get; private set; } | |||||
public IMessageChannel Channel { get; } | public IMessageChannel Channel { get; } | ||||
public IUser Author { get; } | public IUser Author { get; } | ||||
@@ -29,6 +29,8 @@ namespace Discord | |||||
public ImmutableArray<User> MentionedUsers { get; private set; } | public ImmutableArray<User> MentionedUsers { get; private set; } | ||||
public override DiscordClient Discord => (Channel as Entity<ulong>).Discord; | public override DiscordClient Discord => (Channel as Entity<ulong>).Discord; | ||||
public DateTimeOffset? EditedTimestamp => DateTimeUtils.FromTicks(_editedTimestampTicks); | |||||
public DateTimeOffset Timestamp => DateTimeUtils.FromTicks(_timestampTicks); | |||||
public Message(IMessageChannel channel, IUser author, Model model) | public Message(IMessageChannel channel, IUser author, Model model) | ||||
: base(model.Id) | : base(model.Id) | ||||
@@ -56,9 +58,9 @@ namespace Discord | |||||
if (model.IsTextToSpeech.IsSpecified) | if (model.IsTextToSpeech.IsSpecified) | ||||
IsTTS = model.IsTextToSpeech.Value; | IsTTS = model.IsTextToSpeech.Value; | ||||
if (model.Timestamp.IsSpecified) | if (model.Timestamp.IsSpecified) | ||||
Timestamp = model.Timestamp.Value; | |||||
_timestampTicks = model.Timestamp.Value.UtcTicks; | |||||
if (model.EditedTimestamp.IsSpecified) | if (model.EditedTimestamp.IsSpecified) | ||||
EditedTimestamp = model.EditedTimestamp.Value; | |||||
_editedTimestampTicks = model.EditedTimestamp.Value?.UtcTicks; | |||||
if (model.IsMentioningEveryone.IsSpecified) | if (model.IsMentioningEveryone.IsSpecified) | ||||
_isMentioningEveryone = model.IsMentioningEveryone.Value; | _isMentioningEveryone = model.IsMentioningEveryone.Value; | ||||
@@ -5,7 +5,7 @@ namespace Discord | |||||
internal abstract class SnowflakeEntity : Entity<ulong>, ISnowflakeEntity | internal abstract class SnowflakeEntity : Entity<ulong>, ISnowflakeEntity | ||||
{ | { | ||||
//TODO: C#7 Candidate for Extension Property. Lets us remove this class. | //TODO: C#7 Candidate for Extension Property. Lets us remove this class. | ||||
public DateTime CreatedAt => DateTimeUtils.FromSnowflake(Id); | |||||
public DateTimeOffset CreatedAt => DateTimeUtils.FromSnowflake(Id); | |||||
public SnowflakeEntity(ulong id) | public SnowflakeEntity(ulong id) | ||||
: base(id) | : base(id) | ||||
@@ -14,9 +14,10 @@ namespace Discord | |||||
[DebuggerDisplay("{DebuggerDisplay,nq}")] | [DebuggerDisplay("{DebuggerDisplay,nq}")] | ||||
internal class GuildUser : IGuildUser, ISnowflakeEntity | internal class GuildUser : IGuildUser, ISnowflakeEntity | ||||
{ | { | ||||
private long? _joinedAtTicks; | |||||
public bool IsDeaf { get; private set; } | public bool IsDeaf { get; private set; } | ||||
public bool IsMute { get; private set; } | public bool IsMute { get; private set; } | ||||
public DateTime? JoinedAt { get; private set; } | |||||
public string Nickname { get; private set; } | public string Nickname { get; private set; } | ||||
public GuildPermissions GuildPermissions { get; private set; } | public GuildPermissions GuildPermissions { get; private set; } | ||||
@@ -26,7 +27,7 @@ namespace Discord | |||||
public ulong Id => User.Id; | public ulong Id => User.Id; | ||||
public string AvatarUrl => User.AvatarUrl; | public string AvatarUrl => User.AvatarUrl; | ||||
public DateTime CreatedAt => User.CreatedAt; | |||||
public DateTimeOffset CreatedAt => User.CreatedAt; | |||||
public string Discriminator => User.Discriminator; | public string Discriminator => User.Discriminator; | ||||
public bool IsAttached => User.IsAttached; | public bool IsAttached => User.IsAttached; | ||||
public bool IsBot => User.IsBot; | public bool IsBot => User.IsBot; | ||||
@@ -36,6 +37,7 @@ namespace Discord | |||||
public virtual Game? Game => User.Game; | public virtual Game? Game => User.Game; | ||||
public DiscordClient Discord => Guild.Discord; | public DiscordClient Discord => Guild.Discord; | ||||
public DateTimeOffset? JoinedAt => DateTimeUtils.FromTicks(_joinedAtTicks); | |||||
public GuildUser(Guild guild, User user) | public GuildUser(Guild guild, User user) | ||||
{ | { | ||||
@@ -62,7 +64,7 @@ namespace Discord | |||||
//if (model.Mute.IsSpecified) | //if (model.Mute.IsSpecified) | ||||
IsMute = model.Mute; | IsMute = model.Mute; | ||||
//if (model.JoinedAt.IsSpecified) | //if (model.JoinedAt.IsSpecified) | ||||
JoinedAt = model.JoinedAt; | |||||
_joinedAtTicks = model.JoinedAt.UtcTicks; | |||||
if (model.Nick.IsSpecified) | if (model.Nick.IsSpecified) | ||||
Nickname = model.Nick.Value; | Nickname = model.Nick.Value; | ||||
@@ -13,7 +13,7 @@ namespace Discord | |||||
/// <summary> Returns true if the guild has muted this user. </summary> | /// <summary> Returns true if the guild has muted this user. </summary> | ||||
bool IsMute { get; } | bool IsMute { get; } | ||||
/// <summary> Gets when this user joined this guild. </summary> | /// <summary> Gets when this user joined this guild. </summary> | ||||
DateTime? JoinedAt { get; } | |||||
DateTimeOffset? JoinedAt { get; } | |||||
/// <summary> Gets the nickname for this user. </summary> | /// <summary> Gets the nickname for this user. </summary> | ||||
string Nickname { get; } | string Nickname { get; } | ||||
/// <summary> Gets the guild-level permissions granted to this user by their roles. </summary> | /// <summary> Gets the guild-level permissions granted to this user by their roles. </summary> | ||||
@@ -9,14 +9,15 @@ namespace Discord | |||||
internal class User : SnowflakeEntity, IUser | internal class User : SnowflakeEntity, IUser | ||||
{ | { | ||||
private string _avatarId; | private string _avatarId; | ||||
public string Discriminator { get; private set; } | |||||
private ushort _discriminator; | |||||
public bool IsBot { get; private set; } | public bool IsBot { get; private set; } | ||||
public string Username { get; private set; } | public string Username { get; private set; } | ||||
public override DiscordClient Discord { get; } | public override DiscordClient Discord { get; } | ||||
public string AvatarUrl => API.CDN.GetUserAvatarUrl(Id, _avatarId); | public string AvatarUrl => API.CDN.GetUserAvatarUrl(Id, _avatarId); | ||||
public string Discriminator => _discriminator.ToString("D4"); | |||||
public string Mention => MentionUtils.Mention(this, false); | public string Mention => MentionUtils.Mention(this, false); | ||||
public string NicknameMention => MentionUtils.Mention(this, true); | public string NicknameMention => MentionUtils.Mention(this, true); | ||||
public virtual Game? Game => null; | public virtual Game? Game => null; | ||||
@@ -33,7 +34,7 @@ namespace Discord | |||||
if (source == UpdateSource.Rest && IsAttached) return; | if (source == UpdateSource.Rest && IsAttached) return; | ||||
_avatarId = model.Avatar; | _avatarId = model.Avatar; | ||||
Discriminator = model.Discriminator; | |||||
_discriminator = ushort.Parse(model.Discriminator); | |||||
IsBot = model.Bot; | IsBot = model.Bot; | ||||
Username = model.Username; | Username = model.Username; | ||||
} | } | ||||
@@ -32,7 +32,15 @@ namespace Discord | |||||
public new DiscordSocketClient Discord => base.Discord as DiscordSocketClient; | public new DiscordSocketClient Discord => base.Discord as DiscordSocketClient; | ||||
public CachedGuildUser CurrentUser => GetUser(Discord.CurrentUser.Id); | public CachedGuildUser CurrentUser => GetUser(Discord.CurrentUser.Id); | ||||
public IReadOnlyCollection<ICachedGuildChannel> Channels => _channels.Select(x => GetChannel(x)).ToReadOnlyCollection(_channels); | |||||
public IReadOnlyCollection<ICachedGuildChannel> Channels | |||||
{ | |||||
get | |||||
{ | |||||
var channels = _channels; | |||||
var store = Discord.DataStore; | |||||
return channels.Select(x => store.GetChannel(x) as ICachedGuildChannel).Where(x => x != null).ToReadOnlyCollection(channels); | |||||
} | |||||
} | |||||
public IReadOnlyCollection<CachedGuildUser> Members => _members.ToReadOnlyCollection(); | public IReadOnlyCollection<CachedGuildUser> Members => _members.ToReadOnlyCollection(); | ||||
public CachedGuild(DiscordSocketClient discord, ExtendedModel model, DataStore dataStore) : base(discord, model) | public CachedGuild(DiscordSocketClient discord, ExtendedModel model, DataStore dataStore) : base(discord, model) | ||||
@@ -11,12 +11,13 @@ namespace Discord.Extensions | |||||
public static IReadOnlyCollection<TValue> ToReadOnlyCollection<TValue, TSource>(this IEnumerable<TValue> query, IReadOnlyCollection<TSource> source) | public static IReadOnlyCollection<TValue> ToReadOnlyCollection<TValue, TSource>(this IEnumerable<TValue> query, IReadOnlyCollection<TSource> source) | ||||
=> new ConcurrentDictionaryWrapper<TValue, TSource>(source, query); | => new ConcurrentDictionaryWrapper<TValue, TSource>(source, query); | ||||
} | } | ||||
internal struct ConcurrentDictionaryWrapper<TValue, TSource> : IReadOnlyCollection<TValue> | internal struct ConcurrentDictionaryWrapper<TValue, TSource> : IReadOnlyCollection<TValue> | ||||
{ | { | ||||
private readonly IReadOnlyCollection<TSource> _source; | private readonly IReadOnlyCollection<TSource> _source; | ||||
private readonly IEnumerable<TValue> _query; | private readonly IEnumerable<TValue> _query; | ||||
//It's okay that this count is affected by race conditions - we're wrapping a concurrent collection and that's to be expected | |||||
public int Count => _source.Count; | public int Count => _source.Count; | ||||
public ConcurrentDictionaryWrapper(IReadOnlyCollection<TSource> source, IEnumerable<TValue> query) | public ConcurrentDictionaryWrapper(IReadOnlyCollection<TSource> source, IEnumerable<TValue> query) | ||||
@@ -4,15 +4,12 @@ namespace Discord | |||||
{ | { | ||||
internal static class DateTimeUtils | internal static class DateTimeUtils | ||||
{ | { | ||||
private const ulong EpochTicks = 621355968000000000UL; | |||||
private const ulong DiscordEpochMillis = 1420070400000UL; | |||||
public static DateTimeOffset FromSnowflake(ulong value) | |||||
=> DateTimeOffset.FromUnixTimeMilliseconds((long)((value >> 22) + 1420070400000UL)); | |||||
public static DateTime FromEpochMilliseconds(ulong value) | |||||
=> new DateTime((long)(value * TimeSpan.TicksPerMillisecond + EpochTicks), DateTimeKind.Utc); | |||||
public static DateTime FromEpochSeconds(ulong value) | |||||
=> new DateTime((long)(value * TimeSpan.TicksPerSecond + EpochTicks), DateTimeKind.Utc); | |||||
public static DateTime FromSnowflake(ulong value) | |||||
=> FromEpochMilliseconds((value >> 22) + DiscordEpochMillis); | |||||
public static DateTimeOffset FromTicks(long ticks) | |||||
=> new DateTimeOffset(ticks, TimeSpan.Zero); | |||||
public static DateTimeOffset? FromTicks(long? ticks) | |||||
=> ticks != null ? new DateTimeOffset(ticks.Value, TimeSpan.Zero) : (DateTimeOffset?)null; | |||||
} | } | ||||
} | } |