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.

DiscordRpcClient.cs 25 KiB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478
  1. using Discord.API.Rpc;
  2. using Discord.Logging;
  3. using Discord.Net.Converters;
  4. using Discord.Rest;
  5. using Newtonsoft.Json;
  6. using Newtonsoft.Json.Linq;
  7. using System;
  8. using System.Collections.Generic;
  9. using System.Collections.Immutable;
  10. using System.Linq;
  11. using System.Threading.Tasks;
  12. using System.Threading;
  13. namespace Discord.Rpc
  14. {
  15. public partial class DiscordRpcClient : BaseDiscordClient, IDiscordClient
  16. {
  17. private readonly JsonSerializer _serializer;
  18. private readonly ConnectionManager _connection;
  19. private readonly Logger _rpcLogger;
  20. private readonly SemaphoreSlim _stateLock, _authorizeLock;
  21. public ConnectionState ConnectionState { get; private set; }
  22. public IReadOnlyCollection<string> Scopes { get; private set; }
  23. public DateTimeOffset TokenExpiresAt { get; private set; }
  24. internal new API.DiscordRpcApiClient ApiClient => base.ApiClient as API.DiscordRpcApiClient;
  25. public new RestSelfUser CurrentUser { get { return base.CurrentUser as RestSelfUser; } private set { base.CurrentUser = value; } }
  26. public RestApplication ApplicationInfo { get; private set; }
  27. /// <summary> Creates a new RPC discord client. </summary>
  28. public DiscordRpcClient(string clientId, string origin)
  29. : this(clientId, origin, new DiscordRpcConfig()) { }
  30. /// <summary> Creates a new RPC discord client. </summary>
  31. public DiscordRpcClient(string clientId, string origin, DiscordRpcConfig config)
  32. : base(config, CreateApiClient(clientId, origin, config))
  33. {
  34. _stateLock = new SemaphoreSlim(1, 1);
  35. _authorizeLock = new SemaphoreSlim(1, 1);
  36. _rpcLogger = LogManager.CreateLogger("RPC");
  37. _connection = new ConnectionManager(_stateLock, _rpcLogger, config.ConnectionTimeout,
  38. OnConnectingAsync, OnDisconnectingAsync, x => ApiClient.Disconnected += x);
  39. _connection.Connected += () => _connectedEvent.InvokeAsync();
  40. _connection.Disconnected += (ex, recon) => _disconnectedEvent.InvokeAsync(ex);
  41. _serializer = new JsonSerializer { ContractResolver = new DiscordContractResolver() };
  42. _serializer.Error += (s, e) =>
  43. {
  44. _rpcLogger.WarningAsync(e.ErrorContext.Error).GetAwaiter().GetResult();
  45. e.ErrorContext.Handled = true;
  46. };
  47. ApiClient.SentRpcMessage += async opCode => await _rpcLogger.DebugAsync($"Sent {opCode}").ConfigureAwait(false);
  48. ApiClient.ReceivedRpcEvent += ProcessMessageAsync;
  49. }
  50. private static API.DiscordRpcApiClient CreateApiClient(string clientId, string origin, DiscordRpcConfig config)
  51. => new API.DiscordRpcApiClient(clientId, DiscordRestConfig.UserAgent, origin, config.RestClientProvider, config.WebSocketProvider);
  52. internal override void Dispose(bool disposing)
  53. {
  54. if (disposing)
  55. {
  56. StopAsync().GetAwaiter().GetResult();
  57. ApiClient.Dispose();
  58. }
  59. }
  60. public Task StartAsync() => _connection.StartAsync();
  61. public Task StopAsync() => _connection.StopAsync();
  62. private async Task OnConnectingAsync()
  63. {
  64. await _rpcLogger.DebugAsync("Connecting ApiClient").ConfigureAwait(false);
  65. await ApiClient.ConnectAsync().ConfigureAwait(false);
  66. await _connection.WaitAsync().ConfigureAwait(false);
  67. }
  68. private async Task OnDisconnectingAsync(Exception ex)
  69. {
  70. await _rpcLogger.DebugAsync("Disconnecting ApiClient").ConfigureAwait(false);
  71. await ApiClient.DisconnectAsync().ConfigureAwait(false);
  72. }
  73. public async Task<string> AuthorizeAsync(string[] scopes, string rpcToken = null, RequestOptions options = null)
  74. {
  75. await _authorizeLock.WaitAsync().ConfigureAwait(false);
  76. try
  77. {
  78. await _connection.StartAsync().ConfigureAwait(false);
  79. await _connection.WaitAsync().ConfigureAwait(false);
  80. var result = await ApiClient.SendAuthorizeAsync(scopes, rpcToken, options).ConfigureAwait(false);
  81. await _connection.StopAsync().ConfigureAwait(false);
  82. return result.Code;
  83. }
  84. finally
  85. {
  86. _authorizeLock.Release();
  87. }
  88. }
  89. public async Task SubscribeGlobal(RpcGlobalEvent evnt, RequestOptions options = null)
  90. {
  91. await ApiClient.SendGlobalSubscribeAsync(GetEventName(evnt), options).ConfigureAwait(false);
  92. }
  93. public async Task UnsubscribeGlobal(RpcGlobalEvent evnt, RequestOptions options = null)
  94. {
  95. await ApiClient.SendGlobalUnsubscribeAsync(GetEventName(evnt), options).ConfigureAwait(false);
  96. }
  97. public async Task SubscribeGuild(ulong guildId, RpcChannelEvent evnt, RequestOptions options = null)
  98. {
  99. await ApiClient.SendGuildSubscribeAsync(GetEventName(evnt), guildId, options).ConfigureAwait(false);
  100. }
  101. public async Task UnsubscribeGuild(ulong guildId, RpcChannelEvent evnt, RequestOptions options = null)
  102. {
  103. await ApiClient.SendGuildUnsubscribeAsync(GetEventName(evnt), guildId, options).ConfigureAwait(false);
  104. }
  105. public async Task SubscribeChannel(ulong channelId, RpcChannelEvent evnt, RequestOptions options = null)
  106. {
  107. await ApiClient.SendChannelSubscribeAsync(GetEventName(evnt), channelId).ConfigureAwait(false);
  108. }
  109. public async Task UnsubscribeChannel(ulong channelId, RpcChannelEvent evnt, RequestOptions options = null)
  110. {
  111. await ApiClient.SendChannelUnsubscribeAsync(GetEventName(evnt), channelId).ConfigureAwait(false);
  112. }
  113. public async Task<RpcGuild> GetRpcGuildAsync(ulong id, RequestOptions options = null)
  114. {
  115. var model = await ApiClient.SendGetGuildAsync(id, options).ConfigureAwait(false);
  116. return RpcGuild.Create(this, model);
  117. }
  118. public async Task<IReadOnlyCollection<RpcGuildSummary>> GetRpcGuildsAsync(RequestOptions options = null)
  119. {
  120. var models = await ApiClient.SendGetGuildsAsync(options).ConfigureAwait(false);
  121. return models.Guilds.Select(x => RpcGuildSummary.Create(x)).ToImmutableArray();
  122. }
  123. public async Task<RpcChannel> GetRpcChannelAsync(ulong id, RequestOptions options = null)
  124. {
  125. var model = await ApiClient.SendGetChannelAsync(id, options).ConfigureAwait(false);
  126. return RpcChannel.Create(this, model);
  127. }
  128. public async Task<IReadOnlyCollection<RpcChannelSummary>> GetRpcChannelsAsync(ulong guildId, RequestOptions options = null)
  129. {
  130. var models = await ApiClient.SendGetChannelsAsync(guildId, options).ConfigureAwait(false);
  131. return models.Channels.Select(x => RpcChannelSummary.Create(x)).ToImmutableArray();
  132. }
  133. public async Task<IMessageChannel> SelectTextChannelAsync(IChannel channel, RequestOptions options = null)
  134. {
  135. var model = await ApiClient.SendSelectTextChannelAsync(channel.Id, options).ConfigureAwait(false);
  136. return RpcChannel.Create(this, model) as IMessageChannel;
  137. }
  138. public async Task<IMessageChannel> SelectTextChannelAsync(RpcChannelSummary channel, RequestOptions options = null)
  139. {
  140. var model = await ApiClient.SendSelectTextChannelAsync(channel.Id, options).ConfigureAwait(false);
  141. return RpcChannel.Create(this, model) as IMessageChannel;
  142. }
  143. public async Task<IMessageChannel> SelectTextChannelAsync(ulong channelId, RequestOptions options = null)
  144. {
  145. var model = await ApiClient.SendSelectTextChannelAsync(channelId, options).ConfigureAwait(false);
  146. return RpcChannel.Create(this, model) as IMessageChannel;
  147. }
  148. public async Task<IRpcAudioChannel> SelectVoiceChannelAsync(IChannel channel, bool force = false, RequestOptions options = null)
  149. {
  150. var model = await ApiClient.SendSelectVoiceChannelAsync(channel.Id, force, options).ConfigureAwait(false);
  151. return RpcChannel.Create(this, model) as IRpcAudioChannel;
  152. }
  153. public async Task<IRpcAudioChannel> SelectVoiceChannelAsync(RpcChannelSummary channel, bool force = false, RequestOptions options = null)
  154. {
  155. var model = await ApiClient.SendSelectVoiceChannelAsync(channel.Id, force, options).ConfigureAwait(false);
  156. return RpcChannel.Create(this, model) as IRpcAudioChannel;
  157. }
  158. public async Task<IRpcAudioChannel> SelectVoiceChannelAsync(ulong channelId, bool force = false, RequestOptions options = null)
  159. {
  160. var model = await ApiClient.SendSelectVoiceChannelAsync(channelId, force, options).ConfigureAwait(false);
  161. return RpcChannel.Create(this, model) as IRpcAudioChannel;
  162. }
  163. public async Task<VoiceSettings> GetVoiceSettingsAsync(RequestOptions options = null)
  164. {
  165. var model = await ApiClient.GetVoiceSettingsAsync(options).ConfigureAwait(false);
  166. return VoiceSettings.Create(model);
  167. }
  168. public async Task SetVoiceSettingsAsync(Action<VoiceProperties> func, RequestOptions options = null)
  169. {
  170. if (func == null) throw new NullReferenceException(nameof(func));
  171. var settings = new VoiceProperties();
  172. settings.Input = new VoiceDeviceProperties();
  173. settings.Output = new VoiceDeviceProperties();
  174. settings.Mode = new VoiceModeProperties();
  175. func(settings);
  176. var model = new API.Rpc.VoiceSettings
  177. {
  178. AutomaticGainControl = settings.AutomaticGainControl,
  179. EchoCancellation = settings.EchoCancellation,
  180. NoiseSuppression = settings.NoiseSuppression,
  181. QualityOfService = settings.QualityOfService,
  182. SilenceWarning = settings.SilenceWarning
  183. };
  184. model.Input = new API.Rpc.VoiceDeviceSettings
  185. {
  186. DeviceId = settings.Input.DeviceId,
  187. Volume = settings.Input.Volume
  188. };
  189. model.Output = new API.Rpc.VoiceDeviceSettings
  190. {
  191. DeviceId = settings.Output.DeviceId,
  192. Volume = settings.Output.Volume
  193. };
  194. model.Mode = new API.Rpc.VoiceMode
  195. {
  196. AutoThreshold = settings.Mode.AutoThreshold,
  197. Delay = settings.Mode.Delay,
  198. Threshold = settings.Mode.Threshold,
  199. Type = settings.Mode.Type
  200. };
  201. if (settings.Input.AvailableDevices.IsSpecified)
  202. model.Input.AvailableDevices = settings.Input.AvailableDevices.Value.Select(x => x.ToModel()).ToArray();
  203. if (settings.Output.AvailableDevices.IsSpecified)
  204. model.Output.AvailableDevices = settings.Output.AvailableDevices.Value.Select(x => x.ToModel()).ToArray();
  205. if (settings.Mode.Shortcut.IsSpecified)
  206. model.Mode.Shortcut = settings.Mode.Shortcut.Value.Select(x => x.ToModel()).ToArray();
  207. await ApiClient.SetVoiceSettingsAsync(model, options).ConfigureAwait(false);
  208. }
  209. public async Task SetUserVoiceSettingsAsync(ulong userId, Action<UserVoiceProperties> func, RequestOptions options = null)
  210. {
  211. if (func == null) throw new NullReferenceException(nameof(func));
  212. var settings = new UserVoiceProperties();
  213. func(settings);
  214. var model = new API.Rpc.UserVoiceSettings
  215. {
  216. Mute = settings.Mute,
  217. UserId = settings.UserId,
  218. Volume = settings.Volume
  219. };
  220. if (settings.Pan.IsSpecified)
  221. model.Pan = settings.Pan.Value.ToModel();
  222. await ApiClient.SetUserVoiceSettingsAsync(userId, model, options).ConfigureAwait(false);
  223. }
  224. private static string GetEventName(RpcGlobalEvent rpcEvent)
  225. {
  226. switch (rpcEvent)
  227. {
  228. case RpcGlobalEvent.ChannelCreated: return "CHANNEL_CREATE";
  229. case RpcGlobalEvent.GuildCreated: return "GUILD_CREATE";
  230. case RpcGlobalEvent.VoiceSettingsUpdated: return "VOICE_SETTINGS_UPDATE";
  231. default:
  232. throw new InvalidOperationException($"Unknown RPC Global Event: {rpcEvent}");
  233. }
  234. }
  235. private static string GetEventName(RpcGuildEvent rpcEvent)
  236. {
  237. switch (rpcEvent)
  238. {
  239. case RpcGuildEvent.GuildStatus: return "GUILD_STATUS";
  240. default:
  241. throw new InvalidOperationException($"Unknown RPC Guild Event: {rpcEvent}");
  242. }
  243. }
  244. private static string GetEventName(RpcChannelEvent rpcEvent)
  245. {
  246. switch (rpcEvent)
  247. {
  248. case RpcChannelEvent.MessageCreate: return "MESSAGE_CREATE";
  249. case RpcChannelEvent.MessageUpdate: return "MESSAGE_UPDATE";
  250. case RpcChannelEvent.MessageDelete: return "MESSAGE_DELETE";
  251. case RpcChannelEvent.SpeakingStart: return "SPEAKING_START";
  252. case RpcChannelEvent.SpeakingStop: return "SPEAKING_STOP";
  253. case RpcChannelEvent.VoiceStateCreate: return "VOICE_STATE_CREATE";
  254. case RpcChannelEvent.VoiceStateUpdate: return "VOICE_STATE_UPDATE";
  255. case RpcChannelEvent.VoiceStateDelete: return "VOICE_STATE_DELETE";
  256. default:
  257. throw new InvalidOperationException($"Unknown RPC Channel Event: {rpcEvent}");
  258. }
  259. }
  260. private async Task ProcessMessageAsync(string cmd, Optional<string> evnt, Optional<object> payload)
  261. {
  262. try
  263. {
  264. switch (cmd)
  265. {
  266. case "DISPATCH":
  267. switch (evnt.Value)
  268. {
  269. //Connection
  270. case "READY":
  271. {
  272. await _rpcLogger.DebugAsync("Received Dispatch (READY)").ConfigureAwait(false);
  273. var data = (payload.Value as JToken).ToObject<ReadyEvent>(_serializer);
  274. RequestOptions options = new RequestOptions
  275. {
  276. //CancellationToken = _cancelToken //TODO: Implement
  277. };
  278. if (ApiClient.LoginState == LoginState.LoggedIn)
  279. {
  280. var _ = Task.Run(async () =>
  281. {
  282. try
  283. {
  284. var response = await ApiClient.SendAuthenticateAsync(options).ConfigureAwait(false);
  285. CurrentUser = RestSelfUser.Create(this, response.User);
  286. ApiClient.CurrentUserId = CurrentUser.Id;
  287. ApplicationInfo = RestApplication.Create(this, response.Application);
  288. Scopes = response.Scopes;
  289. TokenExpiresAt = response.Expires;
  290. var __ = _connection.CompleteAsync();
  291. await _rpcLogger.InfoAsync("Ready").ConfigureAwait(false);
  292. }
  293. catch (Exception ex)
  294. {
  295. await _rpcLogger.ErrorAsync($"Error handling {cmd}{(evnt.IsSpecified ? $" ({evnt})" : "")}", ex).ConfigureAwait(false);
  296. return;
  297. }
  298. });
  299. }
  300. else
  301. {
  302. var _ = _connection.CompleteAsync();
  303. await _rpcLogger.InfoAsync("Ready").ConfigureAwait(false);
  304. }
  305. }
  306. break;
  307. //Channels
  308. case "CHANNEL_CREATE":
  309. {
  310. await _rpcLogger.DebugAsync("Received Dispatch (CHANNEL_CREATE)").ConfigureAwait(false);
  311. var data = (payload.Value as JToken).ToObject<ChannelSummary>(_serializer);
  312. var channel = RpcChannelSummary.Create(data);
  313. await _channelCreatedEvent.InvokeAsync(channel).ConfigureAwait(false);
  314. }
  315. break;
  316. //Guilds
  317. case "GUILD_CREATE":
  318. {
  319. await _rpcLogger.DebugAsync("Received Dispatch (GUILD_CREATE)").ConfigureAwait(false);
  320. var data = (payload.Value as JToken).ToObject<GuildSummary>(_serializer);
  321. var guild = RpcGuildSummary.Create(data);
  322. await _guildCreatedEvent.InvokeAsync(guild).ConfigureAwait(false);
  323. }
  324. break;
  325. case "GUILD_STATUS":
  326. {
  327. await _rpcLogger.DebugAsync("Received Dispatch (GUILD_STATUS)").ConfigureAwait(false);
  328. var data = (payload.Value as JToken).ToObject<GuildStatusEvent>(_serializer);
  329. var guildStatus = RpcGuildStatus.Create(data);
  330. await _guildStatusUpdatedEvent.InvokeAsync(guildStatus).ConfigureAwait(false);
  331. }
  332. break;
  333. //Voice
  334. case "VOICE_STATE_CREATE":
  335. {
  336. await _rpcLogger.DebugAsync("Received Dispatch (VOICE_STATE_CREATE)").ConfigureAwait(false);
  337. var data = (payload.Value as JToken).ToObject<ExtendedVoiceState>(_serializer);
  338. var voiceState = RpcVoiceState.Create(this, data);
  339. await _voiceStateCreatedEvent.InvokeAsync(voiceState).ConfigureAwait(false);
  340. }
  341. break;
  342. case "VOICE_STATE_UPDATE":
  343. {
  344. await _rpcLogger.DebugAsync("Received Dispatch (VOICE_STATE_UPDATE)").ConfigureAwait(false);
  345. var data = (payload.Value as JToken).ToObject<ExtendedVoiceState>(_serializer);
  346. var voiceState = RpcVoiceState.Create(this, data);
  347. await _voiceStateUpdatedEvent.InvokeAsync(voiceState).ConfigureAwait(false);
  348. }
  349. break;
  350. case "VOICE_STATE_DELETE":
  351. {
  352. await _rpcLogger.DebugAsync("Received Dispatch (VOICE_STATE_DELETE)").ConfigureAwait(false);
  353. var data = (payload.Value as JToken).ToObject<ExtendedVoiceState>(_serializer);
  354. var voiceState = RpcVoiceState.Create(this, data);
  355. await _voiceStateDeletedEvent.InvokeAsync(voiceState).ConfigureAwait(false);
  356. }
  357. break;
  358. case "SPEAKING_START":
  359. {
  360. await _rpcLogger.DebugAsync("Received Dispatch (SPEAKING_START)").ConfigureAwait(false);
  361. var data = (payload.Value as JToken).ToObject<SpeakingEvent>(_serializer);
  362. await _speakingStartedEvent.InvokeAsync(data.UserId).ConfigureAwait(false);
  363. }
  364. break;
  365. case "SPEAKING_STOP":
  366. {
  367. await _rpcLogger.DebugAsync("Received Dispatch (SPEAKING_STOP)").ConfigureAwait(false);
  368. var data = (payload.Value as JToken).ToObject<SpeakingEvent>(_serializer);
  369. await _speakingStoppedEvent.InvokeAsync(data.UserId).ConfigureAwait(false);
  370. }
  371. break;
  372. case "VOICE_SETTINGS_UPDATE":
  373. {
  374. await _rpcLogger.DebugAsync("Received Dispatch (VOICE_SETTINGS_UPDATE)").ConfigureAwait(false);
  375. var data = (payload.Value as JToken).ToObject<API.Rpc.VoiceSettings>(_serializer);
  376. var settings = VoiceSettings.Create(data);
  377. await _voiceSettingsUpdated.InvokeAsync(settings).ConfigureAwait(false);
  378. }
  379. break;
  380. //Messages
  381. case "MESSAGE_CREATE":
  382. {
  383. await _rpcLogger.DebugAsync("Received Dispatch (MESSAGE_CREATE)").ConfigureAwait(false);
  384. var data = (payload.Value as JToken).ToObject<MessageEvent>(_serializer);
  385. var msg = RpcMessage.Create(this, data.ChannelId, data.Message);
  386. await _messageReceivedEvent.InvokeAsync(msg).ConfigureAwait(false);
  387. }
  388. break;
  389. case "MESSAGE_UPDATE":
  390. {
  391. await _rpcLogger.DebugAsync("Received Dispatch (MESSAGE_UPDATE)").ConfigureAwait(false);
  392. var data = (payload.Value as JToken).ToObject<MessageEvent>(_serializer);
  393. var msg = RpcMessage.Create(this, data.ChannelId, data.Message);
  394. await _messageUpdatedEvent.InvokeAsync(msg).ConfigureAwait(false);
  395. }
  396. break;
  397. case "MESSAGE_DELETE":
  398. {
  399. await _rpcLogger.DebugAsync("Received Dispatch (MESSAGE_DELETE)").ConfigureAwait(false);
  400. var data = (payload.Value as JToken).ToObject<MessageEvent>(_serializer);
  401. await _messageDeletedEvent.InvokeAsync(data.ChannelId, data.Message.Id).ConfigureAwait(false);
  402. }
  403. break;
  404. //Others
  405. default:
  406. await _rpcLogger.WarningAsync($"Unknown Dispatch ({evnt})").ConfigureAwait(false);
  407. return;
  408. }
  409. break;
  410. /*default: //Other opcodes are used for command responses
  411. await _rpcLogger.WarningAsync($"Unknown OpCode ({cmd})").ConfigureAwait(false);
  412. return;*/
  413. }
  414. }
  415. catch (Exception ex)
  416. {
  417. await _rpcLogger.ErrorAsync($"Error handling {cmd}{(evnt.IsSpecified ? $" ({evnt})" : "")}", ex).ConfigureAwait(false);
  418. return;
  419. }
  420. }
  421. //IDiscordClient
  422. ConnectionState IDiscordClient.ConnectionState => _connection.State;
  423. Task<IApplication> IDiscordClient.GetApplicationInfoAsync(RequestOptions options) => Task.FromResult<IApplication>(ApplicationInfo);
  424. async Task IDiscordClient.StartAsync()
  425. => await StartAsync().ConfigureAwait(false);
  426. async Task IDiscordClient.StopAsync()
  427. => await StopAsync().ConfigureAwait(false);
  428. }
  429. }