/// <summary> /// Adds and starts a new chat. /// </summary> /// <param name="client">Which account/client owns this chat?</param> /// <param name="bareJid">The bare JID of the opponent.</param> /// <param name="addToRoster">Should the chat get added to the users roster?</param> /// <param name="requestSubscription">Request a presence subscription?</param> private async Task AddChatAsync(Client client, string bareJid, bool addToRoster, bool requestSubscription) { if (client is null || string.IsNullOrEmpty(bareJid)) { Logger.Error("Unable to add chat! client ?= " + (client is null) + " bareJid ?=" + (bareJid is null)); return; } await Task.Run(async() => { SemaLock semaLock = DataCache.INSTANCE.NewChatSemaLock(); ChatModel chat = DataCache.INSTANCE.GetChat(client.dbAccount.bareJid, bareJid, semaLock); bool newChat = chat is null; if (newChat) { chat = new ChatModel(bareJid, client.dbAccount); } else { semaLock.Dispose(); } // Set chat active: chat.isChatActive = true; chat.lastActive = DateTime.Now; // Add to roster: if (addToRoster && !chat.inRoster) { await client.xmppClient.GENERAL_COMMAND_HELPER.addToRosterAsync(bareJid); chat.inRoster = true; } // Request presence subscription: if (requestSubscription && (string.Equals(chat.subscription, "none") || string.Equals(chat.subscription, "from")) && string.IsNullOrEmpty(chat.subscription)) { await client.xmppClient.GENERAL_COMMAND_HELPER.requestPresenceSubscriptionAsync(bareJid); chat.subscription = "pending"; } if (newChat) { DataCache.INSTANCE.AddChatUnsafe(chat, client); semaLock.Dispose(); } else { chat.Update(); } }); }
private async Task AddMucAsync(Client client, string roomBareJid, string nickname, string password, bool bookmark, bool autoJoin) { ChatModel chat = new ChatModel(roomBareJid, client.dbAccount) { chatType = ChatType.MUC, inRoster = bookmark, subscription = "none", isChatActive = true }; MucInfoModel muc = new MucInfoModel() { chat = chat, subject = null, autoEnterRoom = autoJoin, name = null, nickname = nickname, password = string.IsNullOrEmpty(password) ? null : password, state = MucState.DISCONNECTED }; chat.muc = muc; SemaLock semaLock = DataCache.INSTANCE.NewChatSemaLock(); semaLock.Wait(); DataCache.INSTANCE.AddChatUnsafe(chat, client); semaLock.Dispose(); if (muc.autoEnterRoom) { await MucHandler.INSTANCE.EnterMucAsync(client.xmppClient, muc); } if (bookmark) { List <ConferenceItem> conferenceItems; using (MainDbContext ctx = new MainDbContext()) { conferenceItems = ctx.GetXEP0048ConferenceItemsForAccount(client.dbAccount.bareJid); } MessageResponseHelperResult <IQMessage> result = await client.xmppClient.PUB_SUB_COMMAND_HELPER.setBookmars_xep_0048Async(conferenceItems); if (result.STATE == MessageResponseHelperResultState.SUCCESS) { if (result.RESULT is IQErrorMessage errMsg) { Logger.Warn("Failed to set bookmarks: " + errMsg.ToString()); } } else { Logger.Warn("Failed to set bookmarks: " + result.STATE); } } }
private async Task AddIoTDevice(string deviceBareJid, Client client) { // Run it in a new Task since we want to access the DB and this is a blocking action. await Task.Run(async() => { // Add to DB: ChatModel chat = new ChatModel(deviceBareJid, client.dbAccount) { chatType = ChatType.IOT_DEVICE, isChatActive = true }; SemaLock semaLock = DataCache.INSTANCE.NewChatSemaLock(); DataCache.INSTANCE.AddChatUnsafe(chat, client); semaLock.Dispose(); // Add to the roster: await client.xmppClient.GENERAL_COMMAND_HELPER.addToRosterAsync(deviceBareJid); // Add as a PEP node: // TODO }); }
private async Task AddMucAsync(Client client, string roomBareJid, string nickname, string password, bool bookmark, bool autoJoin) { ChatModel chat = new ChatModel(roomBareJid, client.dbAccount) { chatType = ChatType.MUC, inRoster = bookmark, subscription = "none", isChatActive = true }; MucInfoModel muc = new MucInfoModel() { chat = chat, subject = null, autoEnterRoom = autoJoin, name = null, nickname = nickname, password = string.IsNullOrEmpty(password) ? null : password, state = MucState.DISCONNECTED }; chat.muc = muc; SemaLock semaLock = DataCache.INSTANCE.NewChatSemaLock(); semaLock.Wait(); DataCache.INSTANCE.AddChatUnsafe(chat, client); semaLock.Dispose(); if (muc.autoEnterRoom) { await MucHandler.INSTANCE.EnterMucAsync(client.xmppClient, muc); } if (bookmark) { await client.PublishBookmarksAsync(); } }
//--------------------------------------------------------Events:---------------------------------------------------------------------\\ #region --Events-- private void OnNewMucMemberPresenceMessage(XMPPClient client, NewMUCMemberPresenceMessageEventArgs args) { Task.Run(async() => { MUCMemberPresenceMessage msg = args.mucMemberPresenceMessage; string roomJid = Utils.getBareJidFromFullJid(msg.getFrom()); if (roomJid is null) { return; } SemaLock semaLock = DataCache.INSTANCE.NewChatSemaLock(); ChatModel chat = DataCache.INSTANCE.GetChat(client.getXMPPAccount().getBareJid(), roomJid, semaLock); semaLock.Dispose(); if (chat is null || chat.muc is null) { return; } // Prevent multiple accesses to the same occupant at the same time: await OCCUPANT_CREATION_SEMA.WaitAsync(); MucOccupantModel occupant = chat.muc.occupants.Where(o => string.Equals(o.nickname, msg.FROM_NICKNAME)).FirstOrDefault(); bool newOccupant = occupant is null; if (newOccupant) { occupant = new MucOccupantModel() { nickname = msg.FROM_NICKNAME }; } occupant.affiliation = msg.AFFILIATION; occupant.role = msg.ROLE; occupant.fullJid = msg.JID; bool isUnavailable = Equals(msg.TYPE, "unavailable"); bool nicknameChanged = false; if (isUnavailable) { if (msg.STATUS_CODES.Contains(MUCPresenceStatusCode.PRESENCE_SELFE_REFERENCE)) { // Nickname got changed by user or room: if (msg.STATUS_CODES.Contains(MUCPresenceStatusCode.MEMBER_NICK_CHANGED) || msg.STATUS_CODES.Contains(MUCPresenceStatusCode.ROOM_NICK_CHANGED)) { nicknameChanged = true; // Update MUC info: chat.muc.nickname = msg.NICKNAME; // Update the user nickname: occupant.nickname = msg.NICKNAME; } // Occupant got kicked: else if (msg.STATUS_CODES.Contains(MUCPresenceStatusCode.MEMBER_GOT_KICKED)) { chat.muc.state = MucState.KICKED; } else if (msg.STATUS_CODES.Contains(MUCPresenceStatusCode.MEMBER_GOT_BANED)) { chat.muc.state = MucState.BANED; } else { chat.muc.state = MucState.DISCONNECTED; } } if (msg.STATUS_CODES.Contains(MUCPresenceStatusCode.MEMBER_GOT_KICKED)) { // Add kicked chat message: AddOccupantKickedMessage(chat, roomJid, occupant.nickname); } if (msg.STATUS_CODES.Contains(MUCPresenceStatusCode.MEMBER_GOT_BANED)) { // Add baned chat message: AddOccupantBanedMessage(chat, roomJid, occupant.nickname); } } using (SemaLock mucSemaLock = chat.muc.NewSemaLock()) { using (SemaLock occupantSemaLock = occupant.NewSemaLock()) { // If the type equals 'unavailable', a user left the room: if (isUnavailable && !nicknameChanged) { if (!newOccupant) { using (MainDbContext ctx = new MainDbContext()) { chat.muc.occupants.Remove(occupant); chat.muc.OnOccupantsChanged(); ctx.Update(chat.muc); } using (MainDbContext ctx = new MainDbContext()) { ctx.Remove(occupant); } } } else { if (newOccupant) { occupant.Add(); chat.muc.occupants.Add(occupant); chat.muc.OnOccupantsChanged(); using (MainDbContext ctx = new MainDbContext()) { ctx.Update(chat.muc); } } else { using (MainDbContext ctx = new MainDbContext()) { ctx.Update(occupant); } } } } } OCCUPANT_CREATION_SEMA.Release(); }); }
public async Task HandleNewChatMessageAsync(MessageMessage msg) { // Handel MUC room subject messages: if (msg is MUCRoomSubjectMessage) { MucHandler.INSTANCE.OnMUCRoomSubjectMessage(msg as MUCRoomSubjectMessage); return; } string from = Utils.getBareJidFromFullJid(msg.getFrom()); string to = Utils.getBareJidFromFullJid(msg.getTo()); string chatBareJid = string.Equals(from, client.dbAccount.bareJid) ? to : from; SemaLock semaLock = DataCache.INSTANCE.NewChatSemaLock(); ChatModel chat = DataCache.INSTANCE.GetChat(client.dbAccount.bareJid, chatBareJid, semaLock); bool newChat = chat is null; bool chatChanged = false; // Spam detection: if (Settings.GetSettingBoolean(SettingsConsts.SPAM_DETECTION_ENABLED)) { if (Settings.GetSettingBoolean(SettingsConsts.SPAM_DETECTION_FOR_ALL_CHAT_MESSAGES) || newChat) { if (SpamHelper.INSTANCE.IsSpam(msg.MESSAGE)) { Logger.Warn("Received spam message from " + chatBareJid); return; } } } // Detect invalid chat messages: if (!string.Equals(msg.TYPE, MessageMessage.TYPE_CHAT) && !string.Equals(msg.TYPE, MessageMessage.TYPE_ERROR) && !string.Equals(msg.TYPE, MessageMessage.TYPE_GROUPCHAT)) { Logger.Warn($"Received an unknown message type ('{msg.TYPE}') form '{chatBareJid}'. Dropping it."); return; } // Add the new chat to the DB since it's expected to be there by for example our OMEMO encryption: if (newChat) { chat = new ChatModel(chatBareJid, client.dbAccount) { lastActive = msg.delay, chatType = string.Equals(msg.TYPE, MessageMessage.TYPE_GROUPCHAT) ? ChatType.MUC : ChatType.CHAT, isChatActive = false // Mark chat as inactive until we can be sure, it's a valid message }; DataCache.INSTANCE.AddChatUnsafe(chat, client); } else { // Mark chat as active: chat.isChatActive = true; chatChanged = true; } semaLock.Dispose(); // Check if device id is valid and if, decrypt the OMEMO messages: if (msg is OmemoEncryptedMessage omemoMessage) { OmemoHelper helper = client.xmppClient.getOmemoHelper(); if (helper is null) { OnOmemoSessionBuildError(client.xmppClient, new OmemoSessionBuildErrorEventArgs(chatBareJid, OmemoSessionBuildError.KEY_ERROR, new List <OmemoEncryptedMessage> { omemoMessage })); Logger.Error("Failed to decrypt OMEMO message - OmemoHelper is null"); return; } else if (!await DecryptOmemoEncryptedMessageAsync(omemoMessage, !newChat && chat.omemoInfo.trustedKeysOnly)) { if (newChat) { // We failed to decrypt, so this chat could be spam. Delete it again... DataCache.INSTANCE.DeleteChat(chat, false, false); Logger.Debug($"Deleting chat '{chat.bareJid}' again, since decrypting the initial OMEMO message failed."); } return; } else if (omemoMessage.IS_PURE_KEY_EXCHANGE_MESSAGE) { return; } } // Valid new chat, so we can change it to active now: chat.isChatActive = true; chatChanged = true; ChatMessageModel message = null; if (!newChat) { message = DataCache.INSTANCE.GetChatMessage(chat.id, msg.ID); } // Filter messages that already exist: // ToDo: Allow MUC messages being edited and detect it if (!(message is null)) { Logger.Debug("Duplicate message received from '" + chatBareJid + "'. Dropping it..."); return; } message = new ChatMessageModel(msg, chat); // Set the image path and file name: if (message.isImage) { await DataCache.PrepareImageModelPathAndNameAsync(message.image); } DataCache.INSTANCE.AddChatMessage(message, chat); // Handle MUC invite messages: if (msg is DirectMUCInvitationMessage inviteMessage) { if (!newChat) { Logger.Info("Ignoring received MUC direct invitation form '" + chatBareJid + "' since we already joined this MUC (" + inviteMessage.ROOM_JID + ")."); return; } // Ensure we add the message to the DB before we add the invite since the invite has the message as a foreign key: using (MainDbContext ctx = new MainDbContext()) { ctx.Add(new MucDirectInvitationModel(inviteMessage, message)); } } bool isMUCMessage = string.Equals(MessageMessage.TYPE_GROUPCHAT, message.type); if (chat.lastActive.CompareTo(msg.delay) < 0) { chatChanged = true; chat.lastActive = msg.delay; } // Send XEP-0184 (Message Delivery Receipts) reply: if (msg.RECIPT_REQUESTED && !Settings.GetSettingBoolean(SettingsConsts.DONT_SEND_CHAT_MESSAGE_RECEIVED_MARKERS)) { await Task.Run(async() => { DeliveryReceiptMessage receiptMessage = new DeliveryReceiptMessage(client.dbAccount.fullJid.FullJid(), from, msg.ID); await client.xmppClient.SendAsync(receiptMessage); }); } if (chatChanged) { chat.Update(); chatChanged = false; } // Show toast: if (!chat.muted) { await Task.Run(() => { try { switch (msg.TYPE) { case MessageMessage.TYPE_GROUPCHAT: case MessageMessage.TYPE_CHAT: if (!message.isCC) { // Create toast: if (message.isImage) { ToastHelper.ShowChatTextImageToast(message, chat); } else { ToastHelper.ShowChatTextToast(message, chat); } // Update badge notification count: ToastHelper.UpdateBadgeNumber(); } break; default: break; } } catch (Exception e) { Logger.Error("Failed to send toast notification!", e); } }); } }