private void OnNewRoosterMessage(IMessageSender sender, NewValidMessageEventArgs args) { if (args.MESSAGE is RosterResultMessage msg && sender is XMPPClient xmppClient) { string type = msg.TYPE; if (!string.Equals(type, IQMessage.RESULT) && !string.Equals(type, IQMessage.SET)) { // No roster result or set => return return; } SemaLock semaLock = DataCache.INSTANCE.NewChatSemaLock(); IEnumerable <ChatModel> chats = DataCache.INSTANCE.GetChats(client.dbAccount.bareJid, semaLock); // TYPE == SET is being send by the server when the roster changes from outside: if (string.Equals(type, IQMessage.RESULT)) { foreach (ChatModel chat in chats) { chat.inRoster = false; } } List <ChatModel> addedChats = new List <ChatModel>(); List <ChatModel> updatedChats = new List <ChatModel>(); foreach (RosterItem item in msg.ITEMS) { ChatModel chat = chats.Where(c => string.Equals(c.bareJid, item.JID)).FirstOrDefault(); if (!(chat is null)) { chat.inRoster = !string.Equals(item.SUBSCRIPTION, "remove"); updatedChats.Add(chat); }
//--------------------------------------------------------Events:---------------------------------------------------------------------\\ #region --Events-- private void OnOmemoSessionBuildError(XMPPClient xmppClient, OmemoSessionBuildErrorEventArgs args) { Task.Run(() => { ChatModel chat; using (SemaLock semaLock = DataCache.INSTANCE.NewChatSemaLock()) { chat = DataCache.INSTANCE.GetChat(xmppClient.getXMPPAccount().getBareJid(), args.CHAT_JID, semaLock); } if (!(chat is null)) { // Add an error chat message: ChatMessageModel msg = new ChatMessageModel() { chatId = chat.id, date = DateTime.Now, fromBareJid = args.CHAT_JID, isCC = false, isDummyMessage = false, isEncrypted = false, isFavorite = false, isImage = false, message = "Failed to encrypt and send " + args.MESSAGES.Count + " OMEMO message(s) with:\n" + args.ERROR, stableId = AbstractMessage.getRandomId(), state = MessageState.UNREAD, type = MessageMessage.TYPE_ERROR }; DataCache.INSTANCE.AddChatMessage(msg, chat); // Set chat messages to encrypted failed: SetOmemoChatMessagesSendFailed(args.MESSAGES, chat); } }); }
/// <summary> /// Returns a new locked <see cref="SemaLock"/>. /// </summary> public SemaLock NewSemaLock() { SemaLock l = new SemaLock(LOCK_SEMA); l.Wait(); return(l); }
public void StoreDeviceListSubscription(string bareJid, Tuple <OmemoDeviceListSubscriptionState, DateTime> lastUpdate) { if (string.Equals(bareJid, dbAccount.bareJid)) { OmemoDeviceListSubscriptionModel subscription = dbAccount.omemoInfo.deviceListSubscription; subscription.lastUpdateReceived = lastUpdate.Item2; subscription.state = lastUpdate.Item1; using (MainDbContext ctx = new MainDbContext()) { ctx.Update(subscription); } } else { ChatModel chat; using (SemaLock semaLock = DataCache.INSTANCE.NewChatSemaLock()) { chat = DataCache.INSTANCE.GetChat(dbAccount.bareJid, bareJid, semaLock); } if (chat is null) { throw new InvalidOperationException("Failed to store device list subscription. Chat '" + bareJid + "' does not exist."); } OmemoDeviceListSubscriptionModel subscription = chat.omemoInfo.deviceListSubscription; subscription.lastUpdateReceived = lastUpdate.Item2; subscription.state = lastUpdate.Item1; using (MainDbContext ctx = new MainDbContext()) { ctx.Update(subscription); } } }
public async Task EnterRoomAsync() { using (SemaLock semaLock = INFO.NewSemaLock()) { // Update MUC info: INFO.state = MucState.ENTERING; using (MainDbContext ctx = new MainDbContext()) { // Clear MUC members: INFO.occupants.ForEach(o => o.Remove(ctx, true)); INFO.occupants.Clear(); INFO.OnOccupantsChanged(); ctx.Update(INFO); } } // Create message: JoinRoomRequestMessage msg = new JoinRoomRequestMessage(CLIENT.getXMPPAccount().getFullJid(), INFO.chat.bareJid, INFO.nickname, INFO.password); // Subscribe to events for receiving answers: CLIENT.NewMUCMemberPresenceMessage -= OnMucMemberPresenceMessage; CLIENT.NewMUCMemberPresenceMessage += OnMucMemberPresenceMessage; CLIENT.NewMUCPresenceErrorMessage -= OnMucPresenceErrorMessage; CLIENT.NewMUCPresenceErrorMessage += OnMucPresenceErrorMessage; // Send message: await CLIENT.SendAsync(msg); Logger.Info($"Entering MUC room '{INFO.chat.bareJid}' as '{INFO.nickname }'..."); }
private async void OnNewPresence(XMPPClient xmppClient, NewPresenceMessageEventArgs args) { string from = Utils.getBareJidFromFullJid(args.PRESENCE_MESSAGE.getFrom()); // If received a presence message from your own account, ignore it: if (string.Equals(from, client.dbAccount.bareJid)) { return; } ChatModel chat; using (SemaLock semaLock = DataCache.INSTANCE.NewChatSemaLock()) { chat = DataCache.INSTANCE.GetChat(xmppClient.getXMPPAccount().getBareJid(), from, semaLock); } if (chat is null) { Logger.Warn("Received a presence message for an unknown chat from: " + from + ", to: " + client.dbAccount.bareJid); return; } // Answer presence probes: // TODO: answer those only for chats we are subscribed to. if (string.Equals(args.PRESENCE_MESSAGE.TYPE, "probe")) { await AnswerPresenceProbeAsync(from, client.dbAccount.bareJid, chat, args.PRESENCE_MESSAGE); return; } if (chat.chatType == ChatType.CHAT) { if (string.Equals(args.PRESENCE_MESSAGE.TYPE, "subscribe")) { chat.subscription = args.PRESENCE_MESSAGE.TYPE; } if (!string.Equals(args.PRESENCE_MESSAGE.TYPE, "unsubscribe") && !string.Equals(args.PRESENCE_MESSAGE.TYPE, "subscribe")) { switch (chat.subscription) { case "from": case "none": case "pending": case null: chat.presence = Presence.Unavailable; break; default: chat.status = args.PRESENCE_MESSAGE.STATUS; chat.presence = args.PRESENCE_MESSAGE.PRESENCE; break; } } chat.Update(); } }
private async Task OnMucErrorMessageAsync(XMPPClient client, MUCErrorMessage errorMessage) { string room = Utils.getBareJidFromFullJid(errorMessage.getFrom()); if (room is null) { return; } ChatModel chat; using (SemaLock semaLock = DataCache.INSTANCE.NewChatSemaLock()) { chat = DataCache.INSTANCE.GetChat(client.getXMPPAccount().getBareJid(), room, semaLock); } if (chat is null || chat.muc is null) { return; } Logger.Error("Received an error message for MUC: " + chat.bareJid + "\n" + errorMessage.ERROR_MESSAGE); StopMucJoinHelper(chat); if (chat.muc.state != MucState.DISCONNECTED) { await SendMucLeaveMessageAsync(client, chat.muc); } using (SemaLock semaLock = chat.muc.NewSemaLock()) { switch (errorMessage.ERROR_CODE) { // No access - user got baned: case 403: chat.muc.state = MucState.BANED; AddChatInfoMessage(chat, room, "Unable to join room!\nYou are baned from this room."); return; default: chat.muc.state = MucState.ERROR; break; } chat.muc.Update(); } // Add an error chat message: ChatMessageModel msg = new ChatMessageModel() { stableId = errorMessage.ID ?? AbstractMessage.getRandomId(), chatId = chat.id, date = DateTime.Now, fromBareJid = Utils.getBareJidFromFullJid(errorMessage.getFrom()), isImage = false, message = "Code: " + errorMessage.ERROR_CODE + "\nType: " + errorMessage.ERROR_TYPE + "\nMessage:\n" + errorMessage.ERROR_MESSAGE, state = MessageState.UNREAD, type = MessageMessage.TYPE_ERROR }; DataCache.INSTANCE.AddChatMessage(msg, chat); }
protected override async void OnBackgroundActivated(BackgroundActivatedEventArgs args) { BackgroundTaskDeferral deferral = args.TaskInstance.GetDeferral(); switch (args.TaskInstance.Task.Name) { case BackgroundTaskHelper.TOAST_BACKGROUND_TASK_NAME: if (args.TaskInstance.TriggerDetails is ToastNotificationActionTriggerDetail details) { InitLogger(); string arguments = details.Argument; ValueSet userInput = details.UserInput; Logger.Debug("App activated in background through toast with: " + arguments); AbstractToastActivation abstractToastActivation = ToastActivationArgumentParser.ParseArguments(arguments); if (abstractToastActivation is null) { Logger.Warn("Unable to evaluate toast activation string - unknown format"); } else if (abstractToastActivation is MarkChatAsReadToastActivation markChatAsRead) { ToastHelper.RemoveToastGroup(markChatAsRead.CHAT_ID.ToString()); DataCache.INSTANCE.MarkAllChatMessagesAsRead(markChatAsRead.CHAT_ID); } else if (abstractToastActivation is MarkMessageAsReadToastActivation markMessageAsRead) { DataCache.INSTANCE.MarkChatMessageAsRead(markMessageAsRead.CHAT_ID, markMessageAsRead.CHAT_MESSAGE_ID); } else if (abstractToastActivation is SendReplyToastActivation sendReply) { ChatModel chat; using (SemaLock semaLock = DataCache.INSTANCE.NewChatSemaLock()) { chat = DataCache.INSTANCE.GetChat(sendReply.CHAT_ID, semaLock); } if (chat is not null && userInput[ToastHelper.TEXT_BOX_ID] is string text) { string trimedText = text.Trim(UiUtils.TRIM_CHARS); await SendChatMessageAsync(chat, trimedText); } DataCache.INSTANCE.MarkChatMessageAsRead(sendReply.CHAT_ID, sendReply.CHAT_MESSAGE_ID); } ToastHelper.UpdateBadgeNumber(); } break; default: break; } deferral.Complete(); }
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); } } }
public void StoreDevices(List <Tuple <OmemoProtocolAddress, string> > devices, string bareJid) { IEnumerable <OmemoDeviceModel> newDevices = devices.Select(d => new OmemoDeviceModel(d.Item1) { deviceLabel = d.Item2 }); List <OmemoDeviceModel> oldDevices; AbstractModel model; if (string.Equals(bareJid, dbAccount.bareJid)) { oldDevices = dbAccount.omemoInfo.devices; model = dbAccount.omemoInfo; } else { OmemoChatInformationModel omemoChatInfo; using (SemaLock semaLock = DataCache.INSTANCE.NewChatSemaLock()) { omemoChatInfo = DataCache.INSTANCE.GetChat(dbAccount.bareJid, bareJid, semaLock)?.omemoInfo; } model = omemoChatInfo; oldDevices = omemoChatInfo.devices; if (model is null) { throw new InvalidOperationException("Failed to store devices. Chat '" + bareJid + "' does not exist."); } } using (MainDbContext ctx = new MainDbContext()) { // Remove old: for (int i = 0; i < oldDevices.Count; i++) { OmemoDeviceModel device = oldDevices[i]; if (newDevices.Where(d => d.deviceId == device.deviceId).FirstOrDefault() is null) { ctx.Devices.Remove(device); oldDevices.RemoveAt(i); } } // Add/Update existing: foreach (OmemoDeviceModel device in newDevices) { OmemoDeviceModel dev = oldDevices.Where(d => d.deviceId == device.deviceId).FirstOrDefault(); if (!(dev is null)) { dev.deviceLabel = device.deviceLabel; ctx.Devices.Update(dev); }
/// <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(); } }); }
public void StoreSession(OmemoProtocolAddress address, OmemoSessionModel session) { OmemoDeviceModel device; if (string.Equals(address.BARE_JID, dbAccount.bareJid)) { device = dbAccount.omemoInfo.devices.Where(d => address.DEVICE_ID == d.deviceId).FirstOrDefault(); } else { ChatModel chat; using (SemaLock semaLock = DataCache.INSTANCE.NewChatSemaLock()) { chat = DataCache.INSTANCE.GetChat(dbAccount.bareJid, address.BARE_JID, semaLock); } if (chat is null) { throw new InvalidOperationException("Failed to store session. Chat '" + address.BARE_JID + "' does not exist."); } device = chat.omemoInfo.devices.Where(d => d.deviceId == address.DEVICE_ID).FirstOrDefault(); } if (device is null) { throw new InvalidOperationException("Failed to store session. Device '" + address.ToString() + "' does not exist."); } if (device.session is null) { device.session = session; } bool newSession = device.session.id != session.id; OmemoSessionModel oldSession = null; if (newSession) { oldSession = device.session; device.session = session; } device.Update(); // Remove the old session: if (newSession) { using (MainDbContext ctx = new MainDbContext()) { ctx.Remove(oldSession); } } }
public OmemoSessionModel LoadSession(OmemoProtocolAddress address) { if (string.Equals(address.BARE_JID, dbAccount.bareJid)) { return(dbAccount.omemoInfo.devices.Where(d => d.deviceId == address.DEVICE_ID).FirstOrDefault()?.session); } else { using (SemaLock semaLock = DataCache.INSTANCE.NewChatSemaLock()) { return(DataCache.INSTANCE.GetChat(dbAccount.bareJid, address.BARE_JID, semaLock)?.omemoInfo?.devices?.Where(d => d.deviceId == address.DEVICE_ID).FirstOrDefault()?.session); } } }
public OmemoFingerprint LoadFingerprint(OmemoProtocolAddress address) { OmemoFingerprintModel fingerprint; if (string.Equals(address.BARE_JID, dbAccount.bareJid)) { fingerprint = dbAccount.omemoInfo.devices.Where(d => d.deviceId == address.DEVICE_ID).FirstOrDefault()?.fingerprint; } else { using (SemaLock semaLock = DataCache.INSTANCE.NewChatSemaLock()) { fingerprint = DataCache.INSTANCE.GetChat(dbAccount.bareJid, address.BARE_JID, semaLock)?.omemoInfo?.devices?.Where(d => d.deviceId == address.DEVICE_ID).FirstOrDefault()?.fingerprint; } } return(fingerprint?.ToOmemoFingerprint(address)); }
/// <summary> /// Called once a client enters the 'Disconnected' or 'Error' state. /// </summary> private void OnDisconnectedOrError() { IEnumerable <ChatModel> chats; using (SemaLock semaLock = DataCache.INSTANCE.NewChatSemaLock()) { chats = DataCache.INSTANCE.GetChats(client.dbAccount.bareJid, semaLock); } foreach (ChatModel chat in chats) { chat.presence = Presence.Unavailable; } using (MainDbContext ctx = new MainDbContext()) { ctx.UpdateRange(chats); } MucHandler.INSTANCE.OnClientDisconnected(client.xmppClient); }
public void StoreFingerprint(OmemoFingerprint fingerprint) { OmemoDeviceModel device; if (string.Equals(fingerprint.ADDRESS.BARE_JID, dbAccount.bareJid)) { device = dbAccount.omemoInfo.devices.Where(d => fingerprint.ADDRESS.DEVICE_ID == d.deviceId).FirstOrDefault(); } else { ChatModel chat; using (SemaLock semaLock = DataCache.INSTANCE.NewChatSemaLock()) { chat = DataCache.INSTANCE.GetChat(dbAccount.bareJid, fingerprint.ADDRESS.BARE_JID, semaLock); } if (chat is null) { throw new InvalidOperationException("Failed to store fingerprint. Chat '" + fingerprint.ADDRESS.BARE_JID + "' does not exist."); } device = chat.omemoInfo.devices.Where(d => d.deviceId == fingerprint.ADDRESS.DEVICE_ID).FirstOrDefault(); } if (device is null) { throw new InvalidOperationException("Failed to store fingerprint. Device '" + fingerprint.ADDRESS.ToString() + "' does not exist."); } if (device.fingerprint is null) { device.fingerprint = new OmemoFingerprintModel(fingerprint); using (MainDbContext ctx = new MainDbContext()) { ctx.Add(device.fingerprint); ctx.Update(device); } } else { device.fingerprint.lastSeen = fingerprint.lastSeen; device.fingerprint.trusted = fingerprint.trusted; device.fingerprint.Update(); } }
public List <Tuple <OmemoProtocolAddress, string> > LoadDevices(string bareJid) { List <OmemoDeviceModel> devices; if (string.Equals(bareJid, dbAccount.bareJid)) { devices = dbAccount.omemoInfo.devices; } else { using (SemaLock semaLock = DataCache.INSTANCE.NewChatSemaLock()) { devices = DataCache.INSTANCE.GetChat(dbAccount.bareJid, bareJid, semaLock)?.omemoInfo?.devices; } } if (devices is null) { return(new List <Tuple <OmemoProtocolAddress, string> >()); } return(devices.Select(d => new Tuple <OmemoProtocolAddress, string>(new OmemoProtocolAddress(bareJid, d.deviceId), d.deviceLabel)).ToList()); }
//--------------------------------------------------------Set-, Get- Methods:---------------------------------------------------------\\ #region --Set-, Get- Methods-- #endregion //--------------------------------------------------------Misc Methods:---------------------------------------------------------------\\ #region --Misc Methods (Public)-- public Tuple <OmemoDeviceListSubscriptionState, DateTime> LoadDeviceListSubscription(string bareJid) { OmemoDeviceListSubscriptionModel subscription; if (string.Equals(bareJid, dbAccount.bareJid)) { subscription = dbAccount.omemoInfo.deviceListSubscription; } else { using (SemaLock semaLock = DataCache.INSTANCE.NewChatSemaLock()) { subscription = DataCache.INSTANCE.GetChat(dbAccount.bareJid, bareJid, semaLock)?.omemoInfo?.deviceListSubscription; } } if (subscription is null) { return(new Tuple <OmemoDeviceListSubscriptionState, DateTime>(OmemoDeviceListSubscriptionState.NONE, DateTime.MinValue)); } return(new Tuple <OmemoDeviceListSubscriptionState, DateTime>(subscription.state, subscription.lastUpdateReceived)); }
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 }); }
public void OnMUCRoomSubjectMessage(MUCRoomSubjectMessage mucRoomSubject) { string to = Utils.getBareJidFromFullJid(mucRoomSubject.getTo()); string from = Utils.getBareJidFromFullJid(mucRoomSubject.getFrom()); ChatModel chat; using (SemaLock semaLock = DataCache.INSTANCE.NewChatSemaLock()) { chat = DataCache.INSTANCE.GetChat(to, from, semaLock); } if (chat is null || chat.muc is null || (chat.muc.state == MucState.DISCONNECTED && string.Equals(chat.muc.subject, mucRoomSubject.SUBJECT))) { return; } using (SemaLock semaLock = chat.muc.NewSemaLock()) { chat.muc.subject = mucRoomSubject.SUBJECT; using (MainDbContext ctx = new MainDbContext()) { ctx.Update(chat.muc); } } }
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(); } }
public void StoreDevices(List <OmemoProtocolAddress> devices, string bareJid) { IEnumerable <OmemoDeviceModel> newDevices = devices.Select(d => new OmemoDeviceModel(d)); if (string.Equals(bareJid, dbAccount.bareJid)) { using (MainDbContext ctx = new MainDbContext()) { ctx.RemoveRange(dbAccount.omemoInfo.devices); dbAccount.omemoInfo.devices.Clear(); ctx.Update(dbAccount.omemoInfo); dbAccount.omemoInfo.devices.AddRange(newDevices); ctx.Update(dbAccount.omemoInfo); } } else { OmemoChatInformationModel omemoChatInfo; using (SemaLock semaLock = DataCache.INSTANCE.NewChatSemaLock()) { omemoChatInfo = DataCache.INSTANCE.GetChat(dbAccount.bareJid, bareJid, semaLock)?.omemoInfo; } if (omemoChatInfo is null) { throw new InvalidOperationException("Failed to store devices. Chat '" + bareJid + "' does not exist."); } using (MainDbContext ctx = new MainDbContext()) { ctx.RemoveRange(omemoChatInfo.devices); omemoChatInfo.devices.Clear(); ctx.Update(omemoChatInfo); omemoChatInfo.devices.AddRange(newDevices); ctx.Update(omemoChatInfo); } } }
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); } }); } }
//--------------------------------------------------------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(); }); }