using Discord.Rest; using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics; using System.IO; using System.Linq; using System.Threading.Tasks; using Model = Discord.API.Channel; using ThreadMember = Discord.API.ThreadMember; using System.Collections.Concurrent; namespace Discord.WebSocket { /// /// Represents a thread channel inside of a guild. /// [DebuggerDisplay(@"{DebuggerDisplay,nq}")] public class SocketThreadChannel : SocketTextChannel, IThreadChannel { /// public ThreadType Type { get; private set; } /// /// Gets the owner of the current thread. /// public SocketThreadUser Owner { get { lock (_ownerLock) { var user = GetUser(_ownerId); if (user == null) { var guildMember = Guild.GetUser(_ownerId); if (guildMember == null) return null; user = SocketThreadUser.Create(Guild, this, guildMember); _members[user.Id] = user; return user; } else return user; } } } /// /// Gets the current users within this thread. /// public SocketThreadUser CurrentUser => Users.FirstOrDefault(x => x.Id == Discord.CurrentUser.Id); /// public bool HasJoined { get; private set; } /// /// if this thread is private, otherwise /// public bool IsPrivateThread => Type == ThreadType.PrivateThread; /// /// Gets the parent channel this thread resides in. /// public SocketGuildChannel ParentChannel { get; private set; } /// public int MessageCount { get; private set; } /// public int MemberCount { get; private set; } /// public bool IsArchived { get; private set; } /// public DateTimeOffset ArchiveTimestamp { get; private set; } /// public ThreadArchiveDuration AutoArchiveDuration { get; private set; } /// public bool IsLocked { get; private set; } /// public bool? IsInvitable { get; private set; } /// public override DateTimeOffset CreatedAt { get; } /// /// Gets a collection of cached users within this thread. /// public new IReadOnlyCollection Users => _members.Values.ToImmutableArray(); private readonly ConcurrentDictionary _members; private string DebuggerDisplay => $"{Name} ({Id}, Thread)"; private bool _usersDownloaded; private readonly object _downloadLock = new object(); private readonly object _ownerLock = new object(); private ulong _ownerId; internal SocketThreadChannel(DiscordSocketClient discord, SocketGuild guild, ulong id, SocketGuildChannel parent, DateTimeOffset? createdAt) : base(discord, id, guild) { ParentChannel = parent; _members = new ConcurrentDictionary(); CreatedAt = createdAt ?? new DateTimeOffset(2022, 1, 9, 0, 0, 0, TimeSpan.Zero); } internal new static SocketThreadChannel Create(SocketGuild guild, ClientState state, Model model) { var parent = guild.GetChannel(model.CategoryId.Value); var entity = new SocketThreadChannel(guild.Discord, guild, model.Id, parent, model.ThreadMetadata.GetValueOrDefault()?.CreatedAt.GetValueOrDefault(null)); entity.Update(state, model); return entity; } internal override void Update(ClientState state, Model model) { base.Update(state, model); Type = (ThreadType)model.Type; MessageCount = model.MessageCount.GetValueOrDefault(-1); MemberCount = model.MemberCount.GetValueOrDefault(-1); if (model.ThreadMetadata.IsSpecified) { IsInvitable = model.ThreadMetadata.Value.Invitable.ToNullable(); IsArchived = model.ThreadMetadata.Value.Archived; ArchiveTimestamp = model.ThreadMetadata.Value.ArchiveTimestamp; AutoArchiveDuration = model.ThreadMetadata.Value.AutoArchiveDuration; IsLocked = model.ThreadMetadata.Value.Locked.GetValueOrDefault(false); } if (model.OwnerId.IsSpecified) { _ownerId = model.OwnerId.Value; } HasJoined = model.ThreadMember.IsSpecified; } internal IReadOnlyCollection RemoveUsers(ulong[] users) { List threadUsers = new(); foreach (var userId in users) { if (_members.TryRemove(userId, out var user)) threadUsers.Add(user); } return threadUsers.ToImmutableArray(); } internal SocketThreadUser AddOrUpdateThreadMember(ThreadMember model, SocketGuildUser guildMember) { if (_members.TryGetValue(model.UserId.Value, out SocketThreadUser member)) member.Update(model); else { member = SocketThreadUser.Create(Guild, this, model, guildMember); member.GlobalUser.AddRef(); _members[member.Id] = member; } return member; } /// public new SocketThreadUser GetUser(ulong id) { var user = Users.FirstOrDefault(x => x.Id == id); return user; } /// /// Gets all users inside this thread. /// /// /// If all users are not downloaded then this method will call and return the result. /// /// The options to be used when sending the request. /// A task representing the download operation. public async Task> GetUsersAsync(RequestOptions options = null) { // download all users if we havent if (!_usersDownloaded) { await DownloadUsersAsync(options); _usersDownloaded = true; } return Users; } /// /// Downloads all users that have access to this thread. /// /// The options to be used when sending the request. /// A task representing the asynchronous download operation. public async Task DownloadUsersAsync(RequestOptions options = null) { var users = await Discord.ApiClient.ListThreadMembersAsync(Id, options); lock (_downloadLock) { foreach (var threadMember in users) { var guildUser = Guild.GetUser(threadMember.UserId.Value); AddOrUpdateThreadMember(threadMember, guildUser); } } } internal new SocketThreadChannel Clone() => MemberwiseClone() as SocketThreadChannel; /// public Task JoinAsync(RequestOptions options = null) => Discord.ApiClient.JoinThreadAsync(Id, options); /// public Task LeaveAsync(RequestOptions options = null) => Discord.ApiClient.LeaveThreadAsync(Id, options); /// /// Adds a user to this thread. /// /// The to add. /// The options to be used when sending the request. /// /// A task that represents the asynchronous operation of adding a member to a thread. /// public Task AddUserAsync(IGuildUser user, RequestOptions options = null) => Discord.ApiClient.AddThreadMemberAsync(Id, user.Id, options); /// /// Removes a user from this thread. /// /// The to remove from this thread. /// The options to be used when sending the request. /// /// A task that represents the asynchronous operation of removing a user from this thread. /// public Task RemoveUserAsync(IGuildUser user, RequestOptions options = null) => Discord.ApiClient.RemoveThreadMemberAsync(Id, user.Id, options); /// /// /// This method is not supported in threads. /// public override Task AddPermissionOverwriteAsync(IRole role, OverwritePermissions permissions, RequestOptions options = null) => throw new NotSupportedException("This method is not supported in threads."); /// /// /// This method is not supported in threads. /// public override Task AddPermissionOverwriteAsync(IUser user, OverwritePermissions permissions, RequestOptions options = null) => throw new NotSupportedException("This method is not supported in threads."); /// /// /// This method is not supported in threads. /// public override Task CreateInviteAsync(int? maxAge = 86400, int? maxUses = null, bool isTemporary = false, bool isUnique = false, RequestOptions options = null) => throw new NotSupportedException("This method is not supported in threads."); /// /// /// This method is not supported in threads. /// public override Task CreateInviteToApplicationAsync(ulong applicationId, int? maxAge, int? maxUses = null, bool isTemporary = false, bool isUnique = false, RequestOptions options = null) => throw new NotSupportedException("This method is not supported in threads."); /// /// /// This method is not supported in threads. /// public override Task CreateInviteToStreamAsync(IUser user, int? maxAge, int? maxUses = null, bool isTemporary = false, bool isUnique = false, RequestOptions options = null) => throw new NotSupportedException("This method is not supported in threads."); /// /// /// This method is not supported in threads. /// public override Task CreateWebhookAsync(string name, Stream avatar = null, RequestOptions options = null) => throw new NotSupportedException("This method is not supported in threads."); /// /// /// This method is not supported in threads. /// public override Task> GetInvitesAsync(RequestOptions options = null) => throw new NotSupportedException("This method is not supported in threads."); /// /// /// This method is not supported in threads. /// public override OverwritePermissions? GetPermissionOverwrite(IRole role) => ParentChannel.GetPermissionOverwrite(role); /// /// /// This method is not supported in threads. /// public override OverwritePermissions? GetPermissionOverwrite(IUser user) => ParentChannel.GetPermissionOverwrite(user); /// /// /// This method is not supported in threads. /// public override Task GetWebhookAsync(ulong id, RequestOptions options = null) => throw new NotSupportedException("This method is not supported in threads."); /// /// /// This method is not supported in threads. /// public override Task> GetWebhooksAsync(RequestOptions options = null) => throw new NotSupportedException("This method is not supported in threads."); /// /// /// This method is not supported in threads. /// public override Task ModifyAsync(Action func, RequestOptions options = null) => ThreadHelper.ModifyAsync(this, Discord, func, options); /// /// /// This method is not supported in threads. /// public override Task RemovePermissionOverwriteAsync(IRole role, RequestOptions options = null) => throw new NotSupportedException("This method is not supported in threads."); /// /// /// This method is not supported in threads. /// public override Task RemovePermissionOverwriteAsync(IUser user, RequestOptions options = null) => throw new NotSupportedException("This method is not supported in threads."); /// /// /// This method is not supported in threads. /// public override IReadOnlyCollection PermissionOverwrites => throw new NotSupportedException("This method is not supported in threads."); /// /// /// This method is not supported in threads. /// public override Task SyncPermissionsAsync(RequestOptions options = null) => throw new NotSupportedException("This method is not supported in threads."); string IChannel.Name => Name; } }