You can not select more than 25 topics Topics must start with a chinese character,a letter or number, can include dashes ('-') and can be up to 35 characters long.

SocketGuild.cs 52 kB

8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
7 years ago
7 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
Add support for channel categories (#907) commit a85c5814a74e473e95fe172f0379cbc7f9f951d8 Author: Christopher F <computerizedtaco@gmail.com> Date: Sat Jan 6 22:25:48 2018 -0500 Code cleanup commit 4b243fd3dd99152b4ebc7ee01d704bd8e57eeee1 Author: Christopher F <computerizedtaco@gmail.com> Date: Sat Jan 6 22:08:28 2018 -0500 Add support for channel categories (#907) commit 41ed9106f2b05530acbf06b245c9aa618011d815 Author: mrspits4ever <spits.lucas@gmail.com> Date: Thu Dec 14 20:02:57 2017 +0100 removed mentioning support for RestCategoryChannel, added channels property to SocketCategoryChannel commit 71142c310847886dff80c49e9357dd0786d67a1b Merge: 4589d731 678a7238 Author: mrspits4ever <spits.lucas@gmail.com> Date: Wed Dec 13 21:17:53 2017 +0100 Merge branch 'dev' of https://github.com/RogueException/Discord.Net into feature/channel-categories commit 4589d73187871c98485ed25c6d223706927af7ec Author: mrspits4ever <spits.lucas@gmail.com> Date: Wed Dec 13 21:17:46 2017 +0100 adressed requested changes commit d59b038efa048b2279602e2015ddd2c185e58d63 Author: pegasy <pegasy@users.noreply.github.com> Date: Mon Sep 25 18:53:23 2017 +0200 Renamed classes / properties / methods to use CategoryChannel instead of ChannelCategory to be consistant with how text / voice channels are named. commit 5c4777dc8cc443108f2e7e4afae98824c9a32b1f Author: pegasy <pegasy@users.noreply.github.com> Date: Sun Sep 24 19:08:25 2017 +0200 removed Guild from class name for ChannelCategory Renamed all properties to use Category instead of Parent Throw exception on GetUsers / GetInvites etc for categories commit e18bd8c799d2327270021c05866cb2e97ad4671b Author: pegasy <pegasy@users.noreply.github.com> Date: Sun Sep 24 15:49:51 2017 +0200 Add support for channel categories (as its own channel type)
8 years ago
7 years ago
7 years ago
7 years ago
7 years ago
Add support for channel categories (#907) commit a85c5814a74e473e95fe172f0379cbc7f9f951d8 Author: Christopher F <computerizedtaco@gmail.com> Date: Sat Jan 6 22:25:48 2018 -0500 Code cleanup commit 4b243fd3dd99152b4ebc7ee01d704bd8e57eeee1 Author: Christopher F <computerizedtaco@gmail.com> Date: Sat Jan 6 22:08:28 2018 -0500 Add support for channel categories (#907) commit 41ed9106f2b05530acbf06b245c9aa618011d815 Author: mrspits4ever <spits.lucas@gmail.com> Date: Thu Dec 14 20:02:57 2017 +0100 removed mentioning support for RestCategoryChannel, added channels property to SocketCategoryChannel commit 71142c310847886dff80c49e9357dd0786d67a1b Merge: 4589d731 678a7238 Author: mrspits4ever <spits.lucas@gmail.com> Date: Wed Dec 13 21:17:53 2017 +0100 Merge branch 'dev' of https://github.com/RogueException/Discord.Net into feature/channel-categories commit 4589d73187871c98485ed25c6d223706927af7ec Author: mrspits4ever <spits.lucas@gmail.com> Date: Wed Dec 13 21:17:46 2017 +0100 adressed requested changes commit d59b038efa048b2279602e2015ddd2c185e58d63 Author: pegasy <pegasy@users.noreply.github.com> Date: Mon Sep 25 18:53:23 2017 +0200 Renamed classes / properties / methods to use CategoryChannel instead of ChannelCategory to be consistant with how text / voice channels are named. commit 5c4777dc8cc443108f2e7e4afae98824c9a32b1f Author: pegasy <pegasy@users.noreply.github.com> Date: Sun Sep 24 19:08:25 2017 +0200 removed Guild from class name for ChannelCategory Renamed all properties to use Category instead of Parent Throw exception on GetUsers / GetInvites etc for categories commit e18bd8c799d2327270021c05866cb2e97ad4671b Author: pegasy <pegasy@users.noreply.github.com> Date: Sun Sep 24 15:49:51 2017 +0200 Add support for channel categories (as its own channel type)
8 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
8 years ago
8 years ago
7 years ago
8 years ago
8 years ago
8 years ago
8 years ago
7 years ago
7 years ago
7 years ago
7 years ago
8 years ago
8 years ago
8 years ago
8 years ago
7 years ago
7 years ago
7 years ago
Add support for channel categories (#907) commit a85c5814a74e473e95fe172f0379cbc7f9f951d8 Author: Christopher F <computerizedtaco@gmail.com> Date: Sat Jan 6 22:25:48 2018 -0500 Code cleanup commit 4b243fd3dd99152b4ebc7ee01d704bd8e57eeee1 Author: Christopher F <computerizedtaco@gmail.com> Date: Sat Jan 6 22:08:28 2018 -0500 Add support for channel categories (#907) commit 41ed9106f2b05530acbf06b245c9aa618011d815 Author: mrspits4ever <spits.lucas@gmail.com> Date: Thu Dec 14 20:02:57 2017 +0100 removed mentioning support for RestCategoryChannel, added channels property to SocketCategoryChannel commit 71142c310847886dff80c49e9357dd0786d67a1b Merge: 4589d731 678a7238 Author: mrspits4ever <spits.lucas@gmail.com> Date: Wed Dec 13 21:17:53 2017 +0100 Merge branch 'dev' of https://github.com/RogueException/Discord.Net into feature/channel-categories commit 4589d73187871c98485ed25c6d223706927af7ec Author: mrspits4ever <spits.lucas@gmail.com> Date: Wed Dec 13 21:17:46 2017 +0100 adressed requested changes commit d59b038efa048b2279602e2015ddd2c185e58d63 Author: pegasy <pegasy@users.noreply.github.com> Date: Mon Sep 25 18:53:23 2017 +0200 Renamed classes / properties / methods to use CategoryChannel instead of ChannelCategory to be consistant with how text / voice channels are named. commit 5c4777dc8cc443108f2e7e4afae98824c9a32b1f Author: pegasy <pegasy@users.noreply.github.com> Date: Sun Sep 24 19:08:25 2017 +0200 removed Guild from class name for ChannelCategory Renamed all properties to use Category instead of Parent Throw exception on GetUsers / GetInvites etc for categories commit e18bd8c799d2327270021c05866cb2e97ad4671b Author: pegasy <pegasy@users.noreply.github.com> Date: Sun Sep 24 15:49:51 2017 +0200 Add support for channel categories (as its own channel type)
8 years ago
7 years ago
Add support for channel categories (#907) commit a85c5814a74e473e95fe172f0379cbc7f9f951d8 Author: Christopher F <computerizedtaco@gmail.com> Date: Sat Jan 6 22:25:48 2018 -0500 Code cleanup commit 4b243fd3dd99152b4ebc7ee01d704bd8e57eeee1 Author: Christopher F <computerizedtaco@gmail.com> Date: Sat Jan 6 22:08:28 2018 -0500 Add support for channel categories (#907) commit 41ed9106f2b05530acbf06b245c9aa618011d815 Author: mrspits4ever <spits.lucas@gmail.com> Date: Thu Dec 14 20:02:57 2017 +0100 removed mentioning support for RestCategoryChannel, added channels property to SocketCategoryChannel commit 71142c310847886dff80c49e9357dd0786d67a1b Merge: 4589d731 678a7238 Author: mrspits4ever <spits.lucas@gmail.com> Date: Wed Dec 13 21:17:53 2017 +0100 Merge branch 'dev' of https://github.com/RogueException/Discord.Net into feature/channel-categories commit 4589d73187871c98485ed25c6d223706927af7ec Author: mrspits4ever <spits.lucas@gmail.com> Date: Wed Dec 13 21:17:46 2017 +0100 adressed requested changes commit d59b038efa048b2279602e2015ddd2c185e58d63 Author: pegasy <pegasy@users.noreply.github.com> Date: Mon Sep 25 18:53:23 2017 +0200 Renamed classes / properties / methods to use CategoryChannel instead of ChannelCategory to be consistant with how text / voice channels are named. commit 5c4777dc8cc443108f2e7e4afae98824c9a32b1f Author: pegasy <pegasy@users.noreply.github.com> Date: Sun Sep 24 19:08:25 2017 +0200 removed Guild from class name for ChannelCategory Renamed all properties to use Category instead of Parent Throw exception on GetUsers / GetInvites etc for categories commit e18bd8c799d2327270021c05866cb2e97ad4671b Author: pegasy <pegasy@users.noreply.github.com> Date: Sun Sep 24 15:49:51 2017 +0200 Add support for channel categories (as its own channel type)
8 years ago
7 years ago
7 years ago
7 years ago
8 years ago
8 years ago
8 years ago
8 years ago
7 years ago
7 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
Add support for channel categories (#907) commit a85c5814a74e473e95fe172f0379cbc7f9f951d8 Author: Christopher F <computerizedtaco@gmail.com> Date: Sat Jan 6 22:25:48 2018 -0500 Code cleanup commit 4b243fd3dd99152b4ebc7ee01d704bd8e57eeee1 Author: Christopher F <computerizedtaco@gmail.com> Date: Sat Jan 6 22:08:28 2018 -0500 Add support for channel categories (#907) commit 41ed9106f2b05530acbf06b245c9aa618011d815 Author: mrspits4ever <spits.lucas@gmail.com> Date: Thu Dec 14 20:02:57 2017 +0100 removed mentioning support for RestCategoryChannel, added channels property to SocketCategoryChannel commit 71142c310847886dff80c49e9357dd0786d67a1b Merge: 4589d731 678a7238 Author: mrspits4ever <spits.lucas@gmail.com> Date: Wed Dec 13 21:17:53 2017 +0100 Merge branch 'dev' of https://github.com/RogueException/Discord.Net into feature/channel-categories commit 4589d73187871c98485ed25c6d223706927af7ec Author: mrspits4ever <spits.lucas@gmail.com> Date: Wed Dec 13 21:17:46 2017 +0100 adressed requested changes commit d59b038efa048b2279602e2015ddd2c185e58d63 Author: pegasy <pegasy@users.noreply.github.com> Date: Mon Sep 25 18:53:23 2017 +0200 Renamed classes / properties / methods to use CategoryChannel instead of ChannelCategory to be consistant with how text / voice channels are named. commit 5c4777dc8cc443108f2e7e4afae98824c9a32b1f Author: pegasy <pegasy@users.noreply.github.com> Date: Sun Sep 24 19:08:25 2017 +0200 removed Guild from class name for ChannelCategory Renamed all properties to use Category instead of Parent Throw exception on GetUsers / GetInvites etc for categories commit e18bd8c799d2327270021c05866cb2e97ad4671b Author: pegasy <pegasy@users.noreply.github.com> Date: Sun Sep 24 15:49:51 2017 +0200 Add support for channel categories (as its own channel type)
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
Add support for channel categories (#907) commit a85c5814a74e473e95fe172f0379cbc7f9f951d8 Author: Christopher F <computerizedtaco@gmail.com> Date: Sat Jan 6 22:25:48 2018 -0500 Code cleanup commit 4b243fd3dd99152b4ebc7ee01d704bd8e57eeee1 Author: Christopher F <computerizedtaco@gmail.com> Date: Sat Jan 6 22:08:28 2018 -0500 Add support for channel categories (#907) commit 41ed9106f2b05530acbf06b245c9aa618011d815 Author: mrspits4ever <spits.lucas@gmail.com> Date: Thu Dec 14 20:02:57 2017 +0100 removed mentioning support for RestCategoryChannel, added channels property to SocketCategoryChannel commit 71142c310847886dff80c49e9357dd0786d67a1b Merge: 4589d731 678a7238 Author: mrspits4ever <spits.lucas@gmail.com> Date: Wed Dec 13 21:17:53 2017 +0100 Merge branch 'dev' of https://github.com/RogueException/Discord.Net into feature/channel-categories commit 4589d73187871c98485ed25c6d223706927af7ec Author: mrspits4ever <spits.lucas@gmail.com> Date: Wed Dec 13 21:17:46 2017 +0100 adressed requested changes commit d59b038efa048b2279602e2015ddd2c185e58d63 Author: pegasy <pegasy@users.noreply.github.com> Date: Mon Sep 25 18:53:23 2017 +0200 Renamed classes / properties / methods to use CategoryChannel instead of ChannelCategory to be consistant with how text / voice channels are named. commit 5c4777dc8cc443108f2e7e4afae98824c9a32b1f Author: pegasy <pegasy@users.noreply.github.com> Date: Sun Sep 24 19:08:25 2017 +0200 removed Guild from class name for ChannelCategory Renamed all properties to use Category instead of Parent Throw exception on GetUsers / GetInvites etc for categories commit e18bd8c799d2327270021c05866cb2e97ad4671b Author: pegasy <pegasy@users.noreply.github.com> Date: Sun Sep 24 15:49:51 2017 +0200 Add support for channel categories (as its own channel type)
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
7 years ago
8 years ago
7 years ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089
  1. using Discord.Audio;
  2. using Discord.Rest;
  3. using System;
  4. using System.Collections.Concurrent;
  5. using System.Collections.Generic;
  6. using System.Collections.Immutable;
  7. using System.Diagnostics;
  8. using System.Linq;
  9. using System.Threading;
  10. using System.Threading.Tasks;
  11. using ChannelModel = Discord.API.Channel;
  12. using EmojiUpdateModel = Discord.API.Gateway.GuildEmojiUpdateEvent;
  13. using ExtendedModel = Discord.API.Gateway.ExtendedGuild;
  14. using GuildSyncModel = Discord.API.Gateway.GuildSyncEvent;
  15. using MemberModel = Discord.API.GuildMember;
  16. using Model = Discord.API.Guild;
  17. using PresenceModel = Discord.API.Presence;
  18. using RoleModel = Discord.API.Role;
  19. using UserModel = Discord.API.User;
  20. using VoiceStateModel = Discord.API.VoiceState;
  21. namespace Discord.WebSocket
  22. {
  23. /// <summary>
  24. /// Represents a WebSocket-based guild object.
  25. /// </summary>
  26. [DebuggerDisplay(@"{DebuggerDisplay,nq}")]
  27. public class SocketGuild : SocketEntity<ulong>, IGuild
  28. {
  29. private readonly SemaphoreSlim _audioLock;
  30. private TaskCompletionSource<bool> _syncPromise, _downloaderPromise;
  31. private TaskCompletionSource<AudioClient> _audioConnectPromise;
  32. private ConcurrentHashSet<ulong> _channels;
  33. private ConcurrentDictionary<ulong, SocketGuildUser> _members;
  34. private ConcurrentDictionary<ulong, SocketRole> _roles;
  35. private ConcurrentDictionary<ulong, SocketVoiceState> _voiceStates;
  36. private ImmutableArray<GuildEmote> _emotes;
  37. private ImmutableArray<string> _features;
  38. private AudioClient _audioClient;
  39. /// <inheritdoc />
  40. public string Name { get; private set; }
  41. /// <inheritdoc />
  42. public int AFKTimeout { get; private set; }
  43. /// <inheritdoc />
  44. public bool IsEmbeddable { get; private set; }
  45. /// <inheritdoc />
  46. public VerificationLevel VerificationLevel { get; private set; }
  47. /// <inheritdoc />
  48. public MfaLevel MfaLevel { get; private set; }
  49. /// <inheritdoc />
  50. public DefaultMessageNotifications DefaultMessageNotifications { get; private set; }
  51. /// <summary>
  52. /// Gets the number of members.
  53. /// </summary>
  54. /// <remarks>
  55. /// This property retrieves the number of members returned by Discord.
  56. /// <note type="tip">
  57. /// <para>
  58. /// Due to how this property is returned by Discord instead of relying on the WebSocket cache, the
  59. /// number here is the most accurate in terms of counting the number of users within this guild.
  60. /// </para>
  61. /// <para>
  62. /// Use this instead of enumerating the count of the
  63. /// <see cref="Discord.WebSocket.SocketGuild.Users" /> collection, as you may see discrepancy
  64. /// between that and this property.
  65. /// </para>
  66. /// </note>
  67. /// </remarks>
  68. public int MemberCount { get; internal set; }
  69. /// <summary> Gets the number of members downloaded to the local guild cache. </summary>
  70. public int DownloadedMemberCount { get; private set; }
  71. internal bool IsAvailable { get; private set; }
  72. /// <summary> Indicates whether the client is connected to this guild. </summary>
  73. public bool IsConnected { get; internal set; }
  74. internal ulong? AFKChannelId { get; private set; }
  75. internal ulong? EmbedChannelId { get; private set; }
  76. internal ulong? SystemChannelId { get; private set; }
  77. /// <inheritdoc />
  78. public ulong OwnerId { get; private set; }
  79. /// <summary> Gets the user that owns this guild. </summary>
  80. public SocketGuildUser Owner => GetUser(OwnerId);
  81. /// <inheritdoc />
  82. public string VoiceRegionId { get; private set; }
  83. /// <inheritdoc />
  84. public string IconId { get; private set; }
  85. /// <inheritdoc />
  86. public string SplashId { get; private set; }
  87. /// <inheritdoc />
  88. public DateTimeOffset CreatedAt => SnowflakeUtils.FromSnowflake(Id);
  89. /// <inheritdoc />
  90. public string IconUrl => CDN.GetGuildIconUrl(Id, IconId);
  91. /// <inheritdoc />
  92. public string SplashUrl => CDN.GetGuildSplashUrl(Id, SplashId);
  93. /// <summary> Indicates whether the client has all the members downloaded to the local guild cache. </summary>
  94. public bool HasAllMembers => MemberCount == DownloadedMemberCount;// _downloaderPromise.Task.IsCompleted;
  95. /// <summary> Indicates whether the guild cache is synced to this guild. </summary>
  96. public bool IsSynced => _syncPromise.Task.IsCompleted;
  97. public Task SyncPromise => _syncPromise.Task;
  98. public Task DownloaderPromise => _downloaderPromise.Task;
  99. /// <summary>
  100. /// Gets the <see cref="IAudioClient" /> associated with this guild.
  101. /// </summary>
  102. public IAudioClient AudioClient => _audioClient;
  103. /// <summary>
  104. /// Gets the default channel in this guild.
  105. /// </summary>
  106. /// <remarks>
  107. /// This property retrieves the first viewable text channel for this guild.
  108. /// <note type="warning">
  109. /// This channel does not guarantee the user can send message to it, as it only looks for the first viewable
  110. /// text channel.
  111. /// </note>
  112. /// </remarks>
  113. /// <returns>
  114. /// A <see cref="SocketTextChannel"/> representing the first viewable channel that the user has access to.
  115. /// </returns>
  116. public SocketTextChannel DefaultChannel => TextChannels
  117. .Where(c => CurrentUser.GetPermissions(c).ViewChannel)
  118. .OrderBy(c => c.Position)
  119. .FirstOrDefault();
  120. /// <summary>
  121. /// Gets the AFK voice channel in this guild.
  122. /// </summary>
  123. /// <returns>
  124. /// A <see cref="SocketVoiceChannel" /> that the AFK users will be moved to after they have idled for too
  125. /// long; <c>null</c> if none is set.
  126. /// </returns>
  127. public SocketVoiceChannel AFKChannel
  128. {
  129. get
  130. {
  131. var id = AFKChannelId;
  132. return id.HasValue ? GetVoiceChannel(id.Value) : null;
  133. }
  134. }
  135. /// <summary>
  136. /// Gets the embed channel (i.e. the channel set in the guild's widget settings) in this guild.
  137. /// </summary>
  138. /// <returns>
  139. /// A channel set within the server's widget settings; <c>null</c> if none is set.
  140. /// </returns>
  141. public SocketGuildChannel EmbedChannel
  142. {
  143. get
  144. {
  145. var id = EmbedChannelId;
  146. return id.HasValue ? GetChannel(id.Value) : null;
  147. }
  148. }
  149. /// <summary>
  150. /// Gets the system channel where randomized welcome messages are sent in this guild.
  151. /// </summary>
  152. /// <returns>
  153. /// A text channel where randomized welcome messages will be sent to; <c>null</c> if none is set.
  154. /// </returns>
  155. public SocketTextChannel SystemChannel
  156. {
  157. get
  158. {
  159. var id = SystemChannelId;
  160. return id.HasValue ? GetTextChannel(id.Value) : null;
  161. }
  162. }
  163. /// <summary>
  164. /// Gets a collection of all text channels in this guild.
  165. /// </summary>
  166. /// <returns>
  167. /// A read-only collection of message channels found within this guild.
  168. /// </returns>
  169. public IReadOnlyCollection<SocketTextChannel> TextChannels
  170. => Channels.Select(x => x as SocketTextChannel).Where(x => x != null).ToImmutableArray();
  171. /// <summary>
  172. /// Gets a collection of all voice channels in this guild.
  173. /// </summary>
  174. /// <returns>
  175. /// A read-only collection of voice channels found within this guild.
  176. /// </returns>
  177. public IReadOnlyCollection<SocketVoiceChannel> VoiceChannels
  178. => Channels.Select(x => x as SocketVoiceChannel).Where(x => x != null).ToImmutableArray();
  179. /// <summary>
  180. /// Gets a collection of all category channels in this guild.
  181. /// </summary>
  182. /// <returns>
  183. /// A read-only collection of category channels found within this guild.
  184. /// </returns>
  185. public IReadOnlyCollection<SocketCategoryChannel> CategoryChannels
  186. => Channels.Select(x => x as SocketCategoryChannel).Where(x => x != null).ToImmutableArray();
  187. /// <summary>
  188. /// Gets the current logged-in user.
  189. /// </summary>
  190. public SocketGuildUser CurrentUser => _members.TryGetValue(Discord.CurrentUser.Id, out SocketGuildUser member) ? member : null;
  191. /// <summary>
  192. /// Gets the built-in role containing all users in this guild.
  193. /// </summary>
  194. /// <returns>
  195. /// A role object that represents an <c>@everyone</c> role in this guild.
  196. /// </returns>
  197. public SocketRole EveryoneRole => GetRole(Id);
  198. /// <summary>
  199. /// Gets a collection of all channels in this guild.
  200. /// </summary>
  201. /// <returns>
  202. /// A read-only collection of generic channels found within this guild.
  203. /// </returns>
  204. public IReadOnlyCollection<SocketGuildChannel> Channels
  205. {
  206. get
  207. {
  208. var channels = _channels;
  209. var state = Discord.State;
  210. return channels.Select(x => state.GetChannel(x) as SocketGuildChannel).Where(x => x != null).ToReadOnlyCollection(channels);
  211. }
  212. }
  213. /// <inheritdoc />
  214. public IReadOnlyCollection<GuildEmote> Emotes => _emotes;
  215. /// <inheritdoc />
  216. public IReadOnlyCollection<string> Features => _features;
  217. /// <summary>
  218. /// Gets a collection of users in this guild.
  219. /// </summary>
  220. /// <remarks>
  221. /// This property retrieves all users found within this guild.
  222. /// <note type="warning">
  223. /// <para>
  224. /// This property may not always return all the members for large guilds (i.e. guilds containing
  225. /// 100+ users). If you are simply looking to get the number of users present in this guild,
  226. /// consider using <see cref="MemberCount"/> instead.
  227. /// </para>
  228. /// <para>
  229. /// Otherwise, you may need to enable <see cref="DiscordSocketConfig.AlwaysDownloadUsers"/> to fetch
  230. /// the full user list upon startup, or use <see cref="DownloadUsersAsync"/> to manually download
  231. /// the users.
  232. /// </para>
  233. /// </note>
  234. /// </remarks>
  235. /// <returns>
  236. /// A collection of guild users found within this guild.
  237. /// </returns>
  238. public IReadOnlyCollection<SocketGuildUser> Users => _members.ToReadOnlyCollection();
  239. /// <summary>
  240. /// Gets a collection of all roles in this guild.
  241. /// </summary>
  242. /// <returns>
  243. /// A read-only collection of roles found within this guild.
  244. /// </returns>
  245. public IReadOnlyCollection<SocketRole> Roles => _roles.ToReadOnlyCollection();
  246. internal SocketGuild(DiscordSocketClient client, ulong id)
  247. : base(client, id)
  248. {
  249. _audioLock = new SemaphoreSlim(1, 1);
  250. _emotes = ImmutableArray.Create<GuildEmote>();
  251. _features = ImmutableArray.Create<string>();
  252. }
  253. internal static SocketGuild Create(DiscordSocketClient discord, ClientState state, ExtendedModel model)
  254. {
  255. var entity = new SocketGuild(discord, model.Id);
  256. entity.Update(state, model);
  257. return entity;
  258. }
  259. internal void Update(ClientState state, ExtendedModel model)
  260. {
  261. IsAvailable = !(model.Unavailable ?? false);
  262. if (!IsAvailable)
  263. {
  264. if (_channels == null)
  265. _channels = new ConcurrentHashSet<ulong>();
  266. if (_members == null)
  267. _members = new ConcurrentDictionary<ulong, SocketGuildUser>();
  268. if (_roles == null)
  269. _roles = new ConcurrentDictionary<ulong, SocketRole>();
  270. /*if (Emojis == null)
  271. _emojis = ImmutableArray.Create<Emoji>();
  272. if (Features == null)
  273. _features = ImmutableArray.Create<string>();*/
  274. _syncPromise = new TaskCompletionSource<bool>();
  275. _downloaderPromise = new TaskCompletionSource<bool>();
  276. return;
  277. }
  278. Update(state, model as Model);
  279. var channels = new ConcurrentHashSet<ulong>(ConcurrentHashSet.DefaultConcurrencyLevel, (int)(model.Channels.Length * 1.05));
  280. {
  281. for (int i = 0; i < model.Channels.Length; i++)
  282. {
  283. var channel = SocketGuildChannel.Create(this, state, model.Channels[i]);
  284. state.AddChannel(channel);
  285. channels.TryAdd(channel.Id);
  286. }
  287. }
  288. _channels = channels;
  289. var members = new ConcurrentDictionary<ulong, SocketGuildUser>(ConcurrentHashSet.DefaultConcurrencyLevel, (int)(model.Members.Length * 1.05));
  290. {
  291. for (int i = 0; i < model.Members.Length; i++)
  292. {
  293. var member = SocketGuildUser.Create(this, state, model.Members[i]);
  294. members.TryAdd(member.Id, member);
  295. }
  296. DownloadedMemberCount = members.Count;
  297. for (int i = 0; i < model.Presences.Length; i++)
  298. {
  299. if (members.TryGetValue(model.Presences[i].User.Id, out SocketGuildUser member))
  300. member.Update(state, model.Presences[i], true);
  301. }
  302. }
  303. _members = members;
  304. MemberCount = model.MemberCount;
  305. var voiceStates = new ConcurrentDictionary<ulong, SocketVoiceState>(ConcurrentHashSet.DefaultConcurrencyLevel, (int)(model.VoiceStates.Length * 1.05));
  306. {
  307. for (int i = 0; i < model.VoiceStates.Length; i++)
  308. {
  309. SocketVoiceChannel channel = null;
  310. if (model.VoiceStates[i].ChannelId.HasValue)
  311. channel = state.GetChannel(model.VoiceStates[i].ChannelId.Value) as SocketVoiceChannel;
  312. var voiceState = SocketVoiceState.Create(channel, model.VoiceStates[i]);
  313. voiceStates.TryAdd(model.VoiceStates[i].UserId, voiceState);
  314. }
  315. }
  316. _voiceStates = voiceStates;
  317. _syncPromise = new TaskCompletionSource<bool>();
  318. _downloaderPromise = new TaskCompletionSource<bool>();
  319. var _ = _syncPromise.TrySetResultAsync(true);
  320. /*if (!model.Large)
  321. _ = _downloaderPromise.TrySetResultAsync(true);*/
  322. }
  323. internal void Update(ClientState state, Model model)
  324. {
  325. AFKChannelId = model.AFKChannelId;
  326. EmbedChannelId = model.EmbedChannelId;
  327. SystemChannelId = model.SystemChannelId;
  328. AFKTimeout = model.AFKTimeout;
  329. IsEmbeddable = model.EmbedEnabled;
  330. IconId = model.Icon;
  331. Name = model.Name;
  332. OwnerId = model.OwnerId;
  333. VoiceRegionId = model.Region;
  334. SplashId = model.Splash;
  335. VerificationLevel = model.VerificationLevel;
  336. MfaLevel = model.MfaLevel;
  337. DefaultMessageNotifications = model.DefaultMessageNotifications;
  338. if (model.Emojis != null)
  339. {
  340. var emojis = ImmutableArray.CreateBuilder<GuildEmote>(model.Emojis.Length);
  341. for (int i = 0; i < model.Emojis.Length; i++)
  342. emojis.Add(model.Emojis[i].ToEntity());
  343. _emotes = emojis.ToImmutable();
  344. }
  345. else
  346. _emotes = ImmutableArray.Create<GuildEmote>();
  347. if (model.Features != null)
  348. _features = model.Features.ToImmutableArray();
  349. else
  350. _features = ImmutableArray.Create<string>();
  351. var roles = new ConcurrentDictionary<ulong, SocketRole>(ConcurrentHashSet.DefaultConcurrencyLevel, (int)(model.Roles.Length * 1.05));
  352. if (model.Roles != null)
  353. {
  354. for (int i = 0; i < model.Roles.Length; i++)
  355. {
  356. var role = SocketRole.Create(this, state, model.Roles[i]);
  357. roles.TryAdd(role.Id, role);
  358. }
  359. }
  360. _roles = roles;
  361. }
  362. internal void Update(ClientState state, GuildSyncModel model)
  363. {
  364. var members = new ConcurrentDictionary<ulong, SocketGuildUser>(ConcurrentHashSet.DefaultConcurrencyLevel, (int)(model.Members.Length * 1.05));
  365. {
  366. for (int i = 0; i < model.Members.Length; i++)
  367. {
  368. var member = SocketGuildUser.Create(this, state, model.Members[i]);
  369. members.TryAdd(member.Id, member);
  370. }
  371. DownloadedMemberCount = members.Count;
  372. for (int i = 0; i < model.Presences.Length; i++)
  373. {
  374. if (members.TryGetValue(model.Presences[i].User.Id, out SocketGuildUser member))
  375. member.Update(state, model.Presences[i], true);
  376. }
  377. }
  378. _members = members;
  379. var _ = _syncPromise.TrySetResultAsync(true);
  380. /*if (!model.Large)
  381. _ = _downloaderPromise.TrySetResultAsync(true);*/
  382. }
  383. internal void Update(ClientState state, EmojiUpdateModel model)
  384. {
  385. var emotes = ImmutableArray.CreateBuilder<GuildEmote>(model.Emojis.Length);
  386. for (int i = 0; i < model.Emojis.Length; i++)
  387. emotes.Add(model.Emojis[i].ToEntity());
  388. _emotes = emotes.ToImmutable();
  389. }
  390. //General
  391. /// <inheritdoc />
  392. public Task DeleteAsync(RequestOptions options = null)
  393. => GuildHelper.DeleteAsync(this, Discord, options);
  394. /// <inheritdoc />
  395. /// <exception cref="ArgumentNullException"><paramref name="func"/> is <c>null</c>.</exception>
  396. public Task ModifyAsync(Action<GuildProperties> func, RequestOptions options = null)
  397. => GuildHelper.ModifyAsync(this, Discord, func, options);
  398. /// <inheritdoc />
  399. /// <exception cref="ArgumentNullException"><paramref name="func"/> is <c>null</c>.</exception>
  400. public Task ModifyEmbedAsync(Action<GuildEmbedProperties> func, RequestOptions options = null)
  401. => GuildHelper.ModifyEmbedAsync(this, Discord, func, options);
  402. /// <inheritdoc />
  403. public Task ReorderChannelsAsync(IEnumerable<ReorderChannelProperties> args, RequestOptions options = null)
  404. => GuildHelper.ReorderChannelsAsync(this, Discord, args, options);
  405. /// <inheritdoc />
  406. public Task ReorderRolesAsync(IEnumerable<ReorderRoleProperties> args, RequestOptions options = null)
  407. => GuildHelper.ReorderRolesAsync(this, Discord, args, options);
  408. /// <inheritdoc />
  409. public Task LeaveAsync(RequestOptions options = null)
  410. => GuildHelper.LeaveAsync(this, Discord, options);
  411. //Bans
  412. /// <summary>
  413. /// Gets a collection of all users banned in this guild.
  414. /// </summary>
  415. /// <param name="options">The options to be used when sending the request.</param>
  416. /// <returns>
  417. /// A task that represents the asynchronous get operation. The task result contains a read-only collection of
  418. /// ban objects that this guild currently possesses, with each object containing the user banned and reason
  419. /// behind the ban.
  420. /// </returns>
  421. public Task<IReadOnlyCollection<RestBan>> GetBansAsync(RequestOptions options = null)
  422. => GuildHelper.GetBansAsync(this, Discord, options);
  423. /// <summary>
  424. /// Gets a ban object for a banned user.
  425. /// </summary>
  426. /// <param name="user">The banned user.</param>
  427. /// <param name="options">The options to be used when sending the request.</param>
  428. /// <returns>
  429. /// A task that represents the asynchronous get operation. The task result contains a ban object, which
  430. /// contains the user information and the reason for the ban; <c>null</c> if the ban entry cannot be found.
  431. /// </returns>
  432. public Task<RestBan> GetBanAsync(IUser user, RequestOptions options = null)
  433. => GuildHelper.GetBanAsync(this, Discord, user.Id, options);
  434. /// <summary>
  435. /// Gets a ban object for a banned user.
  436. /// </summary>
  437. /// <param name="userId">The snowflake identifier for the banned user.</param>
  438. /// <param name="options">The options to be used when sending the request.</param>
  439. /// <returns>
  440. /// A task that represents the asynchronous get operation. The task result contains a ban object, which
  441. /// contains the user information and the reason for the ban; <c>null</c> if the ban entry cannot be found.
  442. /// </returns>
  443. public Task<RestBan> GetBanAsync(ulong userId, RequestOptions options = null)
  444. => GuildHelper.GetBanAsync(this, Discord, userId, options);
  445. /// <inheritdoc />
  446. public Task AddBanAsync(IUser user, int pruneDays = 0, string reason = null, RequestOptions options = null)
  447. => GuildHelper.AddBanAsync(this, Discord, user.Id, pruneDays, reason, options);
  448. /// <inheritdoc />
  449. public Task AddBanAsync(ulong userId, int pruneDays = 0, string reason = null, RequestOptions options = null)
  450. => GuildHelper.AddBanAsync(this, Discord, userId, pruneDays, reason, options);
  451. /// <inheritdoc />
  452. public Task RemoveBanAsync(IUser user, RequestOptions options = null)
  453. => GuildHelper.RemoveBanAsync(this, Discord, user.Id, options);
  454. /// <inheritdoc />
  455. public Task RemoveBanAsync(ulong userId, RequestOptions options = null)
  456. => GuildHelper.RemoveBanAsync(this, Discord, userId, options);
  457. //Channels
  458. /// <summary>
  459. /// Gets a channel in this guild.
  460. /// </summary>
  461. /// <param name="id">The snowflake identifier for the channel.</param>
  462. /// <returns>
  463. /// A generic channel associated with the specified <paramref name="id" />; <c>null</c> if none is found.
  464. /// </returns>
  465. public SocketGuildChannel GetChannel(ulong id)
  466. {
  467. var channel = Discord.State.GetChannel(id) as SocketGuildChannel;
  468. if (channel?.Guild.Id == Id)
  469. return channel;
  470. return null;
  471. }
  472. /// <summary>
  473. /// Gets a text channel in this guild.
  474. /// </summary>
  475. /// <param name="id">The snowflake identifier for the text channel.</param>
  476. /// <returns>
  477. /// A text channel associated with the specified <paramref name="id" />; <c>null</c> if none is found.
  478. /// </returns>
  479. public SocketTextChannel GetTextChannel(ulong id)
  480. => GetChannel(id) as SocketTextChannel;
  481. /// <summary>
  482. /// Gets a voice channel in this guild.
  483. /// </summary>
  484. /// <param name="id">The snowflake identifier for the voice channel.</param>
  485. /// <returns>
  486. /// A voice channel associated with the specified <paramref name="id" />; <c>null</c> if none is found.
  487. /// </returns>
  488. public SocketVoiceChannel GetVoiceChannel(ulong id)
  489. => GetChannel(id) as SocketVoiceChannel;
  490. /// <summary>
  491. /// Creates a new text channel in this guild.
  492. /// </summary>
  493. /// <param name="name">The new name for the text channel.</param>
  494. /// <param name="func">The delegate containing the properties to be applied to the channel upon its creation.</param>
  495. /// <param name="options">The options to be used when sending the request.</param>
  496. /// <exception cref="ArgumentNullException"><paramref name="name"/> is <c>null</c>.</exception>
  497. /// <returns>
  498. /// A task that represents the asynchronous creation operation. The task result contains the newly created
  499. /// text channel.
  500. /// </returns>
  501. public Task<RestTextChannel> CreateTextChannelAsync(string name, Action<TextChannelProperties> func = null, RequestOptions options = null)
  502. => GuildHelper.CreateTextChannelAsync(this, Discord, name, options, func);
  503. /// <summary>
  504. /// Creates a new voice channel in this guild.
  505. /// </summary>
  506. /// <param name="name">The new name for the voice channel.</param>
  507. /// <param name="func">The delegate containing the properties to be applied to the channel upon its creation.</param>
  508. /// <param name="options">The options to be used when sending the request.</param>
  509. /// <exception cref="ArgumentNullException"><paramref name="name"/> is <c>null</c>.</exception>
  510. /// <returns>
  511. /// A task that represents the asynchronous creation operation. The task result contains the newly created
  512. /// voice channel.
  513. /// </returns>
  514. public Task<RestVoiceChannel> CreateVoiceChannelAsync(string name, Action<VoiceChannelProperties> func = null, RequestOptions options = null)
  515. => GuildHelper.CreateVoiceChannelAsync(this, Discord, name, options, func);
  516. /// <summary>
  517. /// Creates a new channel category in this guild.
  518. /// </summary>
  519. /// <param name="name">The new name for the category.</param>
  520. /// <param name="options">The options to be used when sending the request.</param>
  521. /// <exception cref="ArgumentNullException"><paramref name="name"/> is <c>null</c>.</exception>
  522. /// <returns>
  523. /// A task that represents the asynchronous creation operation. The task result contains the newly created
  524. /// category channel.
  525. /// </returns>
  526. public Task<RestCategoryChannel> CreateCategoryChannelAsync(string name, RequestOptions options = null)
  527. => GuildHelper.CreateCategoryChannelAsync(this, Discord, name, options);
  528. internal SocketGuildChannel AddChannel(ClientState state, ChannelModel model)
  529. {
  530. var channel = SocketGuildChannel.Create(this, state, model);
  531. _channels.TryAdd(model.Id);
  532. state.AddChannel(channel);
  533. return channel;
  534. }
  535. internal SocketGuildChannel RemoveChannel(ClientState state, ulong id)
  536. {
  537. if (_channels.TryRemove(id))
  538. return state.RemoveChannel(id) as SocketGuildChannel;
  539. return null;
  540. }
  541. //Integrations
  542. public Task<IReadOnlyCollection<RestGuildIntegration>> GetIntegrationsAsync(RequestOptions options = null)
  543. => GuildHelper.GetIntegrationsAsync(this, Discord, options);
  544. public Task<RestGuildIntegration> CreateIntegrationAsync(ulong id, string type, RequestOptions options = null)
  545. => GuildHelper.CreateIntegrationAsync(this, Discord, id, type, options);
  546. //Invites
  547. /// <summary>
  548. /// Gets a collection of all invites in this guild.
  549. /// </summary>
  550. /// <param name="options">The options to be used when sending the request.</param>
  551. /// <returns>
  552. /// A task that represents the asynchronous get operation. The task result contains a read-only collection of
  553. /// invite metadata, each representing information for an invite found within this guild.
  554. /// </returns>
  555. public Task<IReadOnlyCollection<RestInviteMetadata>> GetInvitesAsync(RequestOptions options = null)
  556. => GuildHelper.GetInvitesAsync(this, Discord, options);
  557. /// <summary>
  558. /// Gets the vanity invite URL of this guild.
  559. /// </summary>
  560. /// <param name="options">The options to be used when sending the request.</param>
  561. /// <returns>
  562. /// A task that represents the asynchronous get operation. The task result contains the partial metadata of
  563. /// the vanity invite found within this guild; <c>null</c> if none is found.
  564. /// </returns>
  565. public Task<RestInviteMetadata> GetVanityInviteAsync(RequestOptions options = null)
  566. => GuildHelper.GetVanityInviteAsync(this, Discord, options);
  567. //Roles
  568. /// <summary>
  569. /// Gets a role in this guild.
  570. /// </summary>
  571. /// <param name="id">The snowflake identifier for the role.</param>
  572. /// <returns>
  573. /// A role that is associated with the specified <paramref name="id"/>; <c>null</c> if none is found.
  574. /// </returns>
  575. public SocketRole GetRole(ulong id)
  576. {
  577. if (_roles.TryGetValue(id, out SocketRole value))
  578. return value;
  579. return null;
  580. }
  581. /// <summary>
  582. /// Creates a new role with the provided name.
  583. /// </summary>
  584. /// <param name="name">The new name for the role.</param>
  585. /// <param name="permissions">The guild permission that the role should possess.</param>
  586. /// <param name="color">The color of the role.</param>
  587. /// <param name="isHoisted">Whether the role is separated from others on the sidebar.</param>
  588. /// <param name="options">The options to be used when sending the request.</param>
  589. /// <exception cref="ArgumentNullException"><paramref name="name"/> is <c>null</c>.</exception>
  590. /// <returns>
  591. /// A task that represents the asynchronous creation operation. The task result contains the newly created
  592. /// role.
  593. /// </returns>
  594. public Task<RestRole> CreateRoleAsync(string name, GuildPermissions? permissions = default(GuildPermissions?), Color? color = default(Color?),
  595. bool isHoisted = false, RequestOptions options = null)
  596. => GuildHelper.CreateRoleAsync(this, Discord, name, permissions, color, isHoisted, options);
  597. internal SocketRole AddRole(RoleModel model)
  598. {
  599. var role = SocketRole.Create(this, Discord.State, model);
  600. _roles[model.Id] = role;
  601. return role;
  602. }
  603. internal SocketRole RemoveRole(ulong id)
  604. {
  605. if (_roles.TryRemove(id, out SocketRole role))
  606. return role;
  607. return null;
  608. }
  609. //Users
  610. /// <summary>
  611. /// Gets a user from this guild.
  612. /// </summary>
  613. /// <remarks>
  614. /// This method retrieves a user found within this guild.
  615. /// <note>
  616. /// This may return <c>null</c> in the WebSocket implementation due to incomplete user collection in
  617. /// large guilds.
  618. /// </note>
  619. /// </remarks>
  620. /// <param name="id">The snowflake identifier of the user.</param>
  621. /// <returns>
  622. /// A guild user associated with the specified <paramref name="id"/>; <c>null</c> if none is found.
  623. /// </returns>
  624. public SocketGuildUser GetUser(ulong id)
  625. {
  626. if (_members.TryGetValue(id, out SocketGuildUser member))
  627. return member;
  628. return null;
  629. }
  630. /// <inheritdoc />
  631. public Task<int> PruneUsersAsync(int days = 30, bool simulate = false, RequestOptions options = null)
  632. => GuildHelper.PruneUsersAsync(this, Discord, days, simulate, options);
  633. internal SocketGuildUser AddOrUpdateUser(UserModel model)
  634. {
  635. if (_members.TryGetValue(model.Id, out SocketGuildUser member))
  636. member.GlobalUser?.Update(Discord.State, model);
  637. else
  638. {
  639. member = SocketGuildUser.Create(this, Discord.State, model);
  640. member.GlobalUser.AddRef();
  641. _members[member.Id] = member;
  642. DownloadedMemberCount++;
  643. }
  644. return member;
  645. }
  646. internal SocketGuildUser AddOrUpdateUser(MemberModel model)
  647. {
  648. if (_members.TryGetValue(model.User.Id, out SocketGuildUser member))
  649. member.Update(Discord.State, model);
  650. else
  651. {
  652. member = SocketGuildUser.Create(this, Discord.State, model);
  653. member.GlobalUser.AddRef();
  654. _members[member.Id] = member;
  655. DownloadedMemberCount++;
  656. }
  657. return member;
  658. }
  659. internal SocketGuildUser AddOrUpdateUser(PresenceModel model)
  660. {
  661. if (_members.TryGetValue(model.User.Id, out SocketGuildUser member))
  662. member.Update(Discord.State, model, false);
  663. else
  664. {
  665. member = SocketGuildUser.Create(this, Discord.State, model);
  666. member.GlobalUser.AddRef();
  667. _members[member.Id] = member;
  668. DownloadedMemberCount++;
  669. }
  670. return member;
  671. }
  672. internal SocketGuildUser RemoveUser(ulong id)
  673. {
  674. if (_members.TryRemove(id, out SocketGuildUser member))
  675. {
  676. DownloadedMemberCount--;
  677. member.GlobalUser.RemoveRef(Discord);
  678. return member;
  679. }
  680. return null;
  681. }
  682. /// <inheritdoc />
  683. public async Task DownloadUsersAsync()
  684. {
  685. await Discord.DownloadUsersAsync(new[] { this }).ConfigureAwait(false);
  686. }
  687. internal void CompleteDownloadUsers()
  688. {
  689. _downloaderPromise.TrySetResultAsync(true);
  690. }
  691. //Audit logs
  692. /// <summary>
  693. /// Gets the specified number of audit log entries for this guild.
  694. /// </summary>
  695. /// <param name="limit">The number of audit log entries to fetch.</param>
  696. /// <param name="options">The options to be used when sending the request.</param>
  697. /// <returns>
  698. /// A task that represents the asynchronous get operation. The task result contains a read-only collection
  699. /// of the requested audit log entries.
  700. /// </returns>
  701. public IAsyncEnumerable<IReadOnlyCollection<RestAuditLogEntry>> GetAuditLogsAsync(int limit, RequestOptions options = null)
  702. => GuildHelper.GetAuditLogsAsync(this, Discord, null, limit, options);
  703. //Webhooks
  704. /// <summary>
  705. /// Returns the webhook with the provided ID.
  706. /// </summary>
  707. /// <param name="id">The ID of the webhook.</param>
  708. /// <param name="options">The options to be used when sending the request.</param>
  709. /// <returns>
  710. /// An awaitable <see cref="Task"/> containing the webhook associated with the ID.
  711. /// </returns>
  712. public Task<RestWebhook> GetWebhookAsync(ulong id, RequestOptions options = null)
  713. => GuildHelper.GetWebhookAsync(this, Discord, id, options);
  714. /// <summary>
  715. /// Gets a collection of webhooks that exist in the guild.
  716. /// </summary>
  717. /// <param name="options">The options to be used when sending the request.</param>
  718. /// <returns>
  719. /// An awaitable <see cref="Task"/> containing a collection of webhooks.
  720. /// </returns>
  721. public Task<IReadOnlyCollection<RestWebhook>> GetWebhooksAsync(RequestOptions options = null)
  722. => GuildHelper.GetWebhooksAsync(this, Discord, options);
  723. //Emotes
  724. /// <inheritdoc />
  725. public Task<GuildEmote> GetEmoteAsync(ulong id, RequestOptions options = null)
  726. => GuildHelper.GetEmoteAsync(this, Discord, id, options);
  727. /// <inheritdoc />
  728. public Task<GuildEmote> CreateEmoteAsync(string name, Image image, Optional<IEnumerable<IRole>> roles = default(Optional<IEnumerable<IRole>>), RequestOptions options = null)
  729. => GuildHelper.CreateEmoteAsync(this, Discord, name, image, roles, options);
  730. /// <inheritdoc />
  731. /// <exception cref="ArgumentNullException"><paramref name="func"/> is <c>null</c>.</exception>
  732. public Task<GuildEmote> ModifyEmoteAsync(GuildEmote emote, Action<EmoteProperties> func, RequestOptions options = null)
  733. => GuildHelper.ModifyEmoteAsync(this, Discord, emote.Id, func, options);
  734. /// <inheritdoc />
  735. public Task DeleteEmoteAsync(GuildEmote emote, RequestOptions options = null)
  736. => GuildHelper.DeleteEmoteAsync(this, Discord, emote.Id, options);
  737. //Voice States
  738. internal async Task<SocketVoiceState> AddOrUpdateVoiceStateAsync(ClientState state, VoiceStateModel model)
  739. {
  740. var voiceChannel = state.GetChannel(model.ChannelId.Value) as SocketVoiceChannel;
  741. var before = GetVoiceState(model.UserId) ?? SocketVoiceState.Default;
  742. var after = SocketVoiceState.Create(voiceChannel, model);
  743. _voiceStates[model.UserId] = after;
  744. if (_audioClient != null && before.VoiceChannel?.Id != after.VoiceChannel?.Id)
  745. {
  746. if (model.UserId == CurrentUser.Id)
  747. {
  748. if (after.VoiceChannel != null && _audioClient.ChannelId != after.VoiceChannel?.Id)
  749. {
  750. _audioClient.ChannelId = after.VoiceChannel.Id;
  751. await RepopulateAudioStreamsAsync().ConfigureAwait(false);
  752. }
  753. }
  754. else
  755. {
  756. await _audioClient.RemoveInputStreamAsync(model.UserId).ConfigureAwait(false); //User changed channels, end their stream
  757. if (CurrentUser.VoiceChannel != null && after.VoiceChannel?.Id == CurrentUser.VoiceChannel?.Id)
  758. await _audioClient.CreateInputStreamAsync(model.UserId).ConfigureAwait(false);
  759. }
  760. }
  761. return after;
  762. }
  763. internal SocketVoiceState? GetVoiceState(ulong id)
  764. {
  765. if (_voiceStates.TryGetValue(id, out SocketVoiceState voiceState))
  766. return voiceState;
  767. return null;
  768. }
  769. internal async Task<SocketVoiceState?> RemoveVoiceStateAsync(ulong id)
  770. {
  771. if (_voiceStates.TryRemove(id, out SocketVoiceState voiceState))
  772. {
  773. if (_audioClient != null)
  774. await _audioClient.RemoveInputStreamAsync(id).ConfigureAwait(false); //User changed channels, end their stream
  775. return voiceState;
  776. }
  777. return null;
  778. }
  779. //Audio
  780. internal AudioInStream GetAudioStream(ulong userId)
  781. {
  782. return _audioClient?.GetInputStream(userId);
  783. }
  784. internal async Task<IAudioClient> ConnectAudioAsync(ulong channelId, bool selfDeaf, bool selfMute, bool external)
  785. {
  786. TaskCompletionSource<AudioClient> promise;
  787. await _audioLock.WaitAsync().ConfigureAwait(false);
  788. try
  789. {
  790. await DisconnectAudioInternalAsync().ConfigureAwait(false);
  791. promise = new TaskCompletionSource<AudioClient>();
  792. _audioConnectPromise = promise;
  793. if (external)
  794. {
  795. var _ = promise.TrySetResultAsync(null);
  796. await Discord.ApiClient.SendVoiceStateUpdateAsync(Id, channelId, selfDeaf, selfMute).ConfigureAwait(false);
  797. return null;
  798. }
  799. if (_audioClient == null)
  800. {
  801. var audioClient = new AudioClient(this, Discord.GetAudioId(), channelId);
  802. audioClient.Disconnected += async ex =>
  803. {
  804. if (!promise.Task.IsCompleted)
  805. {
  806. try
  807. { audioClient.Dispose(); }
  808. catch { }
  809. _audioClient = null;
  810. if (ex != null)
  811. await promise.TrySetExceptionAsync(ex);
  812. else
  813. await promise.TrySetCanceledAsync();
  814. return;
  815. }
  816. };
  817. audioClient.Connected += () =>
  818. {
  819. var _ = promise.TrySetResultAsync(_audioClient);
  820. return Task.Delay(0);
  821. };
  822. _audioClient = audioClient;
  823. }
  824. await Discord.ApiClient.SendVoiceStateUpdateAsync(Id, channelId, selfDeaf, selfMute).ConfigureAwait(false);
  825. }
  826. catch (Exception)
  827. {
  828. await DisconnectAudioInternalAsync().ConfigureAwait(false);
  829. throw;
  830. }
  831. finally
  832. {
  833. _audioLock.Release();
  834. }
  835. try
  836. {
  837. var timeoutTask = Task.Delay(15000);
  838. if (await Task.WhenAny(promise.Task, timeoutTask).ConfigureAwait(false) == timeoutTask)
  839. throw new TimeoutException();
  840. return await promise.Task.ConfigureAwait(false);
  841. }
  842. catch (Exception)
  843. {
  844. await DisconnectAudioAsync().ConfigureAwait(false);
  845. throw;
  846. }
  847. }
  848. internal async Task DisconnectAudioAsync()
  849. {
  850. await _audioLock.WaitAsync().ConfigureAwait(false);
  851. try
  852. {
  853. await DisconnectAudioInternalAsync().ConfigureAwait(false);
  854. }
  855. finally
  856. {
  857. _audioLock.Release();
  858. }
  859. }
  860. private async Task DisconnectAudioInternalAsync()
  861. {
  862. _audioConnectPromise?.TrySetCanceledAsync(); //Cancel any previous audio connection
  863. _audioConnectPromise = null;
  864. if (_audioClient != null)
  865. await _audioClient.StopAsync().ConfigureAwait(false);
  866. await Discord.ApiClient.SendVoiceStateUpdateAsync(Id, null, false, false).ConfigureAwait(false);
  867. _audioClient = null;
  868. }
  869. internal async Task FinishConnectAudio(string url, string token)
  870. {
  871. //TODO: Mem Leak: Disconnected/Connected handlers arent cleaned up
  872. var voiceState = GetVoiceState(Discord.CurrentUser.Id).Value;
  873. await _audioLock.WaitAsync().ConfigureAwait(false);
  874. try
  875. {
  876. if (_audioClient != null)
  877. {
  878. await RepopulateAudioStreamsAsync().ConfigureAwait(false);
  879. await _audioClient.StartAsync(url, Discord.CurrentUser.Id, voiceState.VoiceSessionId, token).ConfigureAwait(false);
  880. }
  881. }
  882. catch (OperationCanceledException)
  883. {
  884. await DisconnectAudioInternalAsync().ConfigureAwait(false);
  885. }
  886. catch (Exception e)
  887. {
  888. await _audioConnectPromise.SetExceptionAsync(e).ConfigureAwait(false);
  889. await DisconnectAudioInternalAsync().ConfigureAwait(false);
  890. }
  891. finally
  892. {
  893. _audioLock.Release();
  894. }
  895. }
  896. internal async Task RepopulateAudioStreamsAsync()
  897. {
  898. await _audioClient.ClearInputStreamsAsync().ConfigureAwait(false); //We changed channels, end all current streams
  899. if (CurrentUser.VoiceChannel != null)
  900. {
  901. foreach (var pair in _voiceStates)
  902. {
  903. if (pair.Value.VoiceChannel?.Id == CurrentUser.VoiceChannel?.Id && pair.Key != CurrentUser.Id)
  904. await _audioClient.CreateInputStreamAsync(pair.Key).ConfigureAwait(false);
  905. }
  906. }
  907. }
  908. /// <summary>
  909. /// Gets the name of the guild.
  910. /// </summary>
  911. /// <returns>
  912. /// A string that resolves to <see cref="Discord.WebSocket.SocketGuild.Name"/>.
  913. /// </returns>
  914. public override string ToString() => Name;
  915. private string DebuggerDisplay => $"{Name} ({Id})";
  916. internal SocketGuild Clone() => MemberwiseClone() as SocketGuild;
  917. //IGuild
  918. /// <inheritdoc />
  919. ulong? IGuild.AFKChannelId => AFKChannelId;
  920. /// <inheritdoc />
  921. IAudioClient IGuild.AudioClient => null;
  922. /// <inheritdoc />
  923. bool IGuild.Available => true;
  924. /// <inheritdoc />
  925. ulong IGuild.DefaultChannelId => DefaultChannel?.Id ?? 0;
  926. /// <inheritdoc />
  927. ulong? IGuild.EmbedChannelId => EmbedChannelId;
  928. /// <inheritdoc />
  929. ulong? IGuild.SystemChannelId => SystemChannelId;
  930. /// <inheritdoc />
  931. IRole IGuild.EveryoneRole => EveryoneRole;
  932. /// <inheritdoc />
  933. IReadOnlyCollection<IRole> IGuild.Roles => Roles;
  934. /// <inheritdoc />
  935. async Task<IReadOnlyCollection<IBan>> IGuild.GetBansAsync(RequestOptions options)
  936. => await GetBansAsync(options).ConfigureAwait(false);
  937. /// <inheritdoc/>
  938. async Task<IBan> IGuild.GetBanAsync(IUser user, RequestOptions options)
  939. => await GetBanAsync(user, options).ConfigureAwait(false);
  940. /// <inheritdoc/>
  941. async Task<IBan> IGuild.GetBanAsync(ulong userId, RequestOptions options)
  942. => await GetBanAsync(userId, options).ConfigureAwait(false);
  943. /// <inheritdoc />
  944. Task<IReadOnlyCollection<IGuildChannel>> IGuild.GetChannelsAsync(CacheMode mode, RequestOptions options)
  945. => Task.FromResult<IReadOnlyCollection<IGuildChannel>>(Channels);
  946. /// <inheritdoc />
  947. Task<IGuildChannel> IGuild.GetChannelAsync(ulong id, CacheMode mode, RequestOptions options)
  948. => Task.FromResult<IGuildChannel>(GetChannel(id));
  949. /// <inheritdoc />
  950. Task<IReadOnlyCollection<ITextChannel>> IGuild.GetTextChannelsAsync(CacheMode mode, RequestOptions options)
  951. => Task.FromResult<IReadOnlyCollection<ITextChannel>>(TextChannels);
  952. /// <inheritdoc />
  953. Task<ITextChannel> IGuild.GetTextChannelAsync(ulong id, CacheMode mode, RequestOptions options)
  954. => Task.FromResult<ITextChannel>(GetTextChannel(id));
  955. /// <inheritdoc />
  956. Task<IReadOnlyCollection<IVoiceChannel>> IGuild.GetVoiceChannelsAsync(CacheMode mode, RequestOptions options)
  957. => Task.FromResult<IReadOnlyCollection<IVoiceChannel>>(VoiceChannels);
  958. /// <inheritdoc />
  959. Task<IReadOnlyCollection<ICategoryChannel>> IGuild.GetCategoriesAsync(CacheMode mode , RequestOptions options)
  960. => Task.FromResult<IReadOnlyCollection<ICategoryChannel>>(CategoryChannels);
  961. /// <inheritdoc />
  962. Task<IVoiceChannel> IGuild.GetVoiceChannelAsync(ulong id, CacheMode mode, RequestOptions options)
  963. => Task.FromResult<IVoiceChannel>(GetVoiceChannel(id));
  964. /// <inheritdoc />
  965. Task<IVoiceChannel> IGuild.GetAFKChannelAsync(CacheMode mode, RequestOptions options)
  966. => Task.FromResult<IVoiceChannel>(AFKChannel);
  967. /// <inheritdoc />
  968. Task<ITextChannel> IGuild.GetDefaultChannelAsync(CacheMode mode, RequestOptions options)
  969. => Task.FromResult<ITextChannel>(DefaultChannel);
  970. /// <inheritdoc />
  971. Task<IGuildChannel> IGuild.GetEmbedChannelAsync(CacheMode mode, RequestOptions options)
  972. => Task.FromResult<IGuildChannel>(EmbedChannel);
  973. /// <inheritdoc />
  974. Task<ITextChannel> IGuild.GetSystemChannelAsync(CacheMode mode, RequestOptions options)
  975. => Task.FromResult<ITextChannel>(SystemChannel);
  976. /// <inheritdoc />
  977. async Task<ITextChannel> IGuild.CreateTextChannelAsync(string name, Action<TextChannelProperties> func, RequestOptions options)
  978. => await CreateTextChannelAsync(name, func, options).ConfigureAwait(false);
  979. /// <inheritdoc />
  980. async Task<IVoiceChannel> IGuild.CreateVoiceChannelAsync(string name, Action<VoiceChannelProperties> func, RequestOptions options)
  981. => await CreateVoiceChannelAsync(name, func, options).ConfigureAwait(false);
  982. /// <inheritdoc />
  983. async Task<ICategoryChannel> IGuild.CreateCategoryAsync(string name, RequestOptions options)
  984. => await CreateCategoryChannelAsync(name, options).ConfigureAwait(false);
  985. /// <inheritdoc />
  986. async Task<IReadOnlyCollection<IGuildIntegration>> IGuild.GetIntegrationsAsync(RequestOptions options)
  987. => await GetIntegrationsAsync(options).ConfigureAwait(false);
  988. /// <inheritdoc />
  989. async Task<IGuildIntegration> IGuild.CreateIntegrationAsync(ulong id, string type, RequestOptions options)
  990. => await CreateIntegrationAsync(id, type, options).ConfigureAwait(false);
  991. /// <inheritdoc />
  992. async Task<IReadOnlyCollection<IInviteMetadata>> IGuild.GetInvitesAsync(RequestOptions options)
  993. => await GetInvitesAsync(options).ConfigureAwait(false);
  994. /// <inheritdoc />
  995. async Task<IInviteMetadata> IGuild.GetVanityInviteAsync(RequestOptions options)
  996. => await GetVanityInviteAsync(options).ConfigureAwait(false);
  997. /// <inheritdoc />
  998. IRole IGuild.GetRole(ulong id)
  999. => GetRole(id);
  1000. /// <inheritdoc />
  1001. async Task<IRole> IGuild.CreateRoleAsync(string name, GuildPermissions? permissions, Color? color, bool isHoisted, RequestOptions options)
  1002. => await CreateRoleAsync(name, permissions, color, isHoisted, options).ConfigureAwait(false);
  1003. /// <inheritdoc />
  1004. Task<IReadOnlyCollection<IGuildUser>> IGuild.GetUsersAsync(CacheMode mode, RequestOptions options)
  1005. => Task.FromResult<IReadOnlyCollection<IGuildUser>>(Users);
  1006. /// <inheritdoc />
  1007. Task<IGuildUser> IGuild.GetUserAsync(ulong id, CacheMode mode, RequestOptions options)
  1008. => Task.FromResult<IGuildUser>(GetUser(id));
  1009. /// <inheritdoc />
  1010. Task<IGuildUser> IGuild.GetCurrentUserAsync(CacheMode mode, RequestOptions options)
  1011. => Task.FromResult<IGuildUser>(CurrentUser);
  1012. /// <inheritdoc />
  1013. Task<IGuildUser> IGuild.GetOwnerAsync(CacheMode mode, RequestOptions options)
  1014. => Task.FromResult<IGuildUser>(Owner);
  1015. /// <inheritdoc />
  1016. async Task<IReadOnlyCollection<IAuditLogEntry>> IGuild.GetAuditLogsAsync(int limit, CacheMode cacheMode, RequestOptions options)
  1017. {
  1018. if (cacheMode == CacheMode.AllowDownload)
  1019. return (await GetAuditLogsAsync(limit, options).FlattenAsync().ConfigureAwait(false)).ToImmutableArray();
  1020. else
  1021. return ImmutableArray.Create<IAuditLogEntry>();
  1022. }
  1023. /// <inheritdoc />
  1024. async Task<IWebhook> IGuild.GetWebhookAsync(ulong id, RequestOptions options)
  1025. => await GetWebhookAsync(id, options).ConfigureAwait(false);
  1026. /// <inheritdoc />
  1027. async Task<IReadOnlyCollection<IWebhook>> IGuild.GetWebhooksAsync(RequestOptions options)
  1028. => await GetWebhooksAsync(options).ConfigureAwait(false);
  1029. }
  1030. }