snikket-ios/Snikket/service/MucEventHandler.swift
2021-09-08 16:13:12 +05:00

229 lines
12 KiB
Swift

//
// MucEventHandler.swift
//
// Siskin IM
// Copyright (C) 2019 "Tigase, Inc." <office@tigase.com>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. Look for COPYING file in the top folder.
// If not, see https://www.gnu.org/licenses/.
//
import Foundation
import TigaseSwift
import UserNotifications
class MucEventHandler: XmppServiceEventHandler {
static let ROOM_STATUS_CHANGED = Notification.Name("roomStatusChanged");
static let ROOM_NAME_CHANGED = Notification.Name("roomNameChanged");
static let ROOM_OCCUPANTS_CHANGED = Notification.Name("roomOccupantsChanged");
static let instance = MucEventHandler();
let events: [Event] = [ SessionEstablishmentModule.SessionEstablishmentSuccessEvent.TYPE, MucModule.YouJoinedEvent.TYPE, MucModule.RoomClosedEvent.TYPE, MucModule.MessageReceivedEvent.TYPE, MucModule.OccupantChangedNickEvent.TYPE, MucModule.OccupantChangedPresenceEvent.TYPE, MucModule.OccupantLeavedEvent.TYPE, MucModule.OccupantComesEvent.TYPE, MucModule.PresenceErrorEvent.TYPE, MucModule.InvitationReceivedEvent.TYPE, MucModule.InvitationDeclinedEvent.TYPE, PEPBookmarksModule.BookmarksChangedEvent.TYPE ];
func handle(event: Event) {
switch event {
case let e as SessionEstablishmentModule.SessionEstablishmentSuccessEvent:
guard !XmppService.instance.isFetch else {
return;
}
if let client = XmppService.instance.getClient(for: e.sessionObject.userBareJid!), let mucModule: MucModule = client.modulesManager.getModule(MucModule.ID) {
mucModule.roomsManager.getRooms().forEach { (r) in
let room = r as! DBRoom;
// first we need to check if room supports MAM
if let discoModule: DiscoveryModule = client.modulesManager.getModule(DiscoveryModule.ID), let mamModule: MessageArchiveManagementModule = client.modulesManager.getModule(MessageArchiveManagementModule.ID) {
discoModule.getInfo(for: room.jid, completionHandler: { result in
var mamVersions: [MessageArchiveManagementModule.Version] = [];
switch result {
case .success(_, _, let features):
room.supportedFeatures = features;
mamVersions = features.map({ MessageArchiveManagementModule.Version(rawValue: $0) }).filter({ $0 != nil}).map({ $0! });
default:
room.supportedFeatures = [];
break;
}
let account = e.sessionObject.userBareJid!;
if let timestamp = room.lastMessageDate, !mamVersions.isEmpty {
room.onRoomJoined = { room in
MessageEventHandler.syncMessages(for: account, version: mamVersions.contains(.MAM2) ? .MAM2 : .MAM1, componentJID: room.jid, since: timestamp);
}
_ = room.rejoin(skipHistoryFetch: true);
NotificationCenter.default.post(name: MucEventHandler.ROOM_STATUS_CHANGED, object: room);
} else if let lastMessageRecieved = DBLastMessageSyncStore.instance.getLastMessage(account: account, jid: room.roomJid), let afterId = lastMessageRecieved.receivedId {
room.onRoomJoined = { room in
MessageEventHandler.syncMessagesAfterID(for: account, version: mamVersions.contains(.MAM2) ? .MAM2 : .MAM1, componentJID: room.jid, afterId: afterId)
}
room.lastMessageDate = Date()
_ = room.rejoin(skipHistoryFetch: true);
NotificationCenter.default.post(name: MucEventHandler.ROOM_STATUS_CHANGED, object: room);
} else {
_ = room.rejoin();
NotificationCenter.default.post(name: MucEventHandler.ROOM_STATUS_CHANGED, object: room);
}
});
} else {
_ = room.rejoin();
NotificationCenter.default.post(name: MucEventHandler.ROOM_STATUS_CHANGED, object: room);
}
}
}
case let e as MucModule.YouJoinedEvent:
guard let room = e.room as? DBRoom else {
return;
}
NotificationCenter.default.post(name: MucEventHandler.ROOM_STATUS_CHANGED, object: room);
NotificationCenter.default.post(name: MucEventHandler.ROOM_OCCUPANTS_CHANGED, object: room.presences[room.nickname]);
updateRoomName(room: room);
case let e as MucModule.RoomClosedEvent:
guard let room = e.room as? DBRoom else {
return;
}
NotificationCenter.default.post(name: MucEventHandler.ROOM_STATUS_CHANGED, object: room);
case let e as MucModule.MessageReceivedEvent:
guard let room = e.room as? DBRoom else {
return;
}
if e.message.findChild(name: "subject") != nil {
room.subject = e.message.subject;
NotificationCenter.default.post(name: MucEventHandler.ROOM_STATUS_CHANGED, object: room);
}
if let xUser = XMucUserElement.extract(from: e.message) {
if xUser.statuses.contains(104) {
self.updateRoomName(room: room);
XmppService.instance.refreshVCard(account: room.account, for: room.roomJid, onSuccess: nil, onError: nil);
}
}
DBChatHistoryStore.instance.append(for: room.account, message: e.message, source: .stream)
DBLastMessageSyncStore.instance.updateOrInsertLastMessage(account: room.account, jid: room.roomJid, receivedId: e.message.id, readId: nil)
case let e as MucModule.AbstractOccupantEvent:
if let room = e.room as? DBRoom, room.isOMEMOCapable, let jid = e.occupant.jid {
if (e.occupant.affiliation == .none || e.occupant.affiliation == .outcast) {
// remove
DispatchQueue.main.async {
if let idx = room.members?.firstIndex(of: jid) {
room.members?.remove(at: idx);
}
}
} else {
// ensure exists
DispatchQueue.main.async {
if let members = room.members, !members.contains(jid) {
room.members?.append(jid);
}
}
}
}
NotificationCenter.default.post(name: MucEventHandler.ROOM_OCCUPANTS_CHANGED, object: e);
case let e as MucModule.PresenceErrorEvent:
guard let error = MucModule.RoomError.from(presence: e.presence), e.nickname == nil || e.nickname! == e.room.nickname else {
return;
}
print("received error from room:", e.room as Any, ", error:", error)
let content = UNMutableNotificationContent();
content.title = "Room \(e.room.roomJid.stringValue)";
content.body = "Could not join room. Reason:\n\(error.reason)";
content.sound = .default;
if error != .banned && error != .registrationRequired {
content.userInfo = ["account": e.sessionObject.userBareJid!.stringValue, "roomJid": e.room.roomJid.stringValue, "nickname": e.room.nickname, "id": "room-join-error"];
}
let request = UNNotificationRequest(identifier: UUID().uuidString, content: content, trigger: nil);
UNUserNotificationCenter.current().add(request) { (error) in
print("could not show notification:", error as Any);
}
guard let mucModule: MucModule = XmppService.instance.getClient(for: e.sessionObject.userBareJid!)?.modulesManager.getModule(MucModule.ID) else {
return;
}
mucModule.leave(room: e.room);
case let e as MucModule.InvitationReceivedEvent:
NotificationCenter.default.post(name: XmppService.MUC_ROOM_INVITATION, object: e);
break;
case let e as PEPBookmarksModule.BookmarksChangedEvent:
guard let client = XmppService.instance.getClient(for: e.sessionObject.userBareJid!), let mucModule: MucModule = client.modulesManager.getModule(MucModule.ID), Settings.enableBookmarksSync.bool() else {
return;
}
e.bookmarks?.items.filter { bookmark in bookmark is Bookmarks.Conference }.map { bookmark in bookmark as! Bookmarks.Conference }.filter { bookmark in
return !mucModule.roomsManager.contains(roomJid: bookmark.jid.bareJid);
}.forEach({ (bookmark) in
guard bookmark.autojoin else {
return;
}
var nick: String?
if let account = e.sessionObject.userBareJid, let displayName = AccountSettings.displayName(account).getString() {
nick = displayName
}
guard let nick = bookmark.nick != nil ? bookmark.nick : nick
else { return }
_ = mucModule.join(roomName: bookmark.jid.localPart!, mucServer: bookmark.jid.domain, nickname: nick, password: bookmark.password);
});
default:
break;
}
}
open func sendPrivateMessage(room: DBRoom, recipientNickname: String, body: String) {
let message = room.createPrivateMessage(body, recipientNickname: recipientNickname);
DBChatHistoryStore.instance.appendItem(for: room.account, with: room.roomJid, state: .outgoing, authorNickname: room.nickname, authorJid: nil, recipientNickname: recipientNickname, participantId: nil, type: .message, timestamp: Date(), stanzaId: message.id, serverMsgId: nil, remoteMsgId: nil, data: body, encryption: .none, encryptionFingerprint: nil, appendix: nil, linkPreviewAction: .auto, completionHandler: nil);
room.context.writer?.write(message);
}
fileprivate func updateRoomName(room: DBRoom) {
guard let client = XmppService.instance.getClient(for: room.account), let discoModule: DiscoveryModule = client.modulesManager.getModule(DiscoveryModule.ID) else {
return;
}
discoModule.getInfo(for: room.jid, onInfoReceived: { (node, identities, features) in
let newName = identities.first(where: { (identity) -> Bool in
return identity.category == "conference";
})?.name?.trimmingCharacters(in: .whitespacesAndNewlines);
room.supportedFeatures = features;
DBChatStore.instance.updateChatName(for: room.account, with: room.roomJid, name: (newName?.isEmpty ?? true) ? nil : newName);
}, onError: nil);
}
}
extension MucModule.RoomError {
var reason: String {
switch self {
case .banned:
return "User is banned";
case .invalidPassword:
return "Invalid password";
case .maxUsersExceeded:
return "Maximum number of users exceeded";
case .nicknameConflict:
return "Nickname already in use";
case .nicknameLockedDown:
return "Nickname is locked down";
case .registrationRequired:
return "Membership is required to access the room";
case .roomLocked:
return "Room is locked";
}
}
}