// This controller only expects messages from clients; the Discord bridge posts to // DiscordMessageController.cs /// <summary> /// Sends a message from the web chat to Discord and notifies other clients to stop the typing indicator. /// </summary> /// <param name="message">The SendMessage object representing the current operation.</param> /// <returns>A Task representing the state of the request.</returns> public async Task SendMessage(SendMessage message) { // Discord bridge isn't ready, so just reject the message instead of attempting to send it if (!_discordBot.DiscordClient.Ready) { await this.Clients.Caller.SendAsync("BridgeDown"); } // Sanitise the message contents NewMessage newMessage = new NewMessage(RemoveMassPings(message.Content)); // Get the internal user by the connection ID mapping HubUser hubUser = UserHandler.UserMappings[this.Context.ConnectionId]; BridgeMessage bridgeMessage = new BridgeMessage(); // Parse the logged in user's name into a Discord ID. // Maybe add a check here if it fails? Users should only be able to create accounts with names from their Discord IDs // but unclear what happens if db is corrupted etc. ulong.TryParse(this.Context.User.Identity.Name, out ulong userId); bridgeMessage.UserId = userId; bridgeMessage.SignalRMessage = newMessage; // Send the message to the Discord client await _discordBot.DiscordClient.IngestSignalR(bridgeMessage); // Tell other clients to stop the typing indicator await this.Clients.Others.SendAsync("StopTypingForClient", hubUser); }
/// <summary> /// Sends other clients a typing indicator. /// </summary> /// <returns>A Task representing the state of the request.</returns> public async Task ClientTyping() { HubUser hubUser = UserHandler.UserMappings[this.Context.ConnectionId]; TypingMessage typingMessage = new TypingMessage { // Timestamp is in milliseconds to make JavaScript side easier. Timestamp = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds().ToString(), UserId = hubUser.DiscordId }; await this.Clients.Others.SendAsync("ClientTyping", typingMessage); }
/// <summary> /// Sends the client the list of users known to be connected. /// </summary> /// <returns>A Task representing the state of the request.</returns> public async Task GetConnectedUsers() { List <HubUser> hubUsers = new List <HubUser>(); foreach (KeyValuePair <string, HubUser> keyValuePair in UserHandler.UserMappings) { // Get the HubUser object that's mapped to the user's connection ID HubUser user = keyValuePair.Value; if (user.HasDiscordInfo == null) { try { // Try to get a DiscordMember object from Discord. // Only attempts it on the first go (i.e. if it hasn't looked for one before) // This isn't the default DiscordClient GetMemberById, look in Client.cs for more info. // It throws an exception if it doesn't exist. DiscordMember member = _discordBot.DiscordClient.GetMemberById(ulong.Parse(user.AspNetUsername)).Result; // If the user is a member of the guild user.HasDiscordInfo = true; user.DiscordId = member.Id.ToString(); user.DiscordDisplayName = member.DisplayName; // Replace GIF avatars with the first frame user.AvatarUrl = member.AvatarUrl.Replace(".gif", ".png"); } catch { // The user doesn't exist, stop the bot from searching again. user.HasDiscordInfo = false; } } // This might lead to duplicates, but if we've got multiple people // sharing the same Discord account with multiple connection IDs // then I guess it's fine to show dupes. hubUsers.Add(user); } await this.Clients.Caller.SendAsync("ConnectedIds", hubUsers); }
/// <summary> /// The method used to override the default connection handler to add user state tracking. /// </summary> /// <returns>A Task representing the state of the request.</returns> public override Task OnConnectedAsync() { lock (UserHandler.ConnectedIds) { UserHandler.ConnectedIds.Add(Context.ConnectionId); } HubUser user = new HubUser { AspNetUsername = Context.User.Identity.Name, HasDiscordInfo = null }; UserHandler.UserMappings.TryAdd(Context.ConnectionId, user); UserLogger.WriteLog(Context.User.Identity.Name, Context.ConnectionId, UserLogger.ConnectionStatus.Connected); this.Clients.All.SendAsync("ViewerConnected"); return(base.OnConnectedAsync()); }