Added support for private messages in MUC #siskinim-197

This commit is contained in:
Andrzej Wójcik 2020-02-19 16:42:27 +01:00
parent 747dfef04a
commit c5797d237f
No known key found for this signature in database
GPG key ID: 2BE28BB9C1B5FF02
15 changed files with 179 additions and 38 deletions

View file

@ -25,7 +25,7 @@ import TigaseSwift
public class DBSchemaManager {
static let CURRENT_VERSION = 9;
static let CURRENT_VERSION = 10;
fileprivate let dbConnection: DBConnection;

9
Shared/db-schema-10.sql Normal file
View file

@ -0,0 +1,9 @@
BEGIN;
ALTER TABLE chat_history ADD COLUMN recipient_nickname TEXT;
COMMIT;
PRAGMA user_version = 10;

View file

@ -10,6 +10,7 @@
FE00157D2017617B00490340 /* StreamFeaturesCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = FE00157C2017617B00490340 /* StreamFeaturesCache.swift */; };
FE00157F2019090300490340 /* ExperimentalSettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = FE00157E2019090300490340 /* ExperimentalSettingsViewController.swift */; };
FE01ADA91E224CF400FA7E65 /* SiskinPushNotificationsModule.swift in Sources */ = {isa = PBXBuildFile; fileRef = FE01ADA81E224CF400FA7E65 /* SiskinPushNotificationsModule.swift */; };
FE10BCF323FD4EF000E214F3 /* db-schema-10.sql in Resources */ = {isa = PBXBuildFile; fileRef = FE10BCF223FD4EF000E214F3 /* db-schema-10.sql */; };
FE137A4821F6464D006B7F7C /* UIColor_mix.swift in Sources */ = {isa = PBXBuildFile; fileRef = FE137A4721F6464D006B7F7C /* UIColor_mix.swift */; };
FE137A4A21F72AEA006B7F7C /* Appearance.swift in Sources */ = {isa = PBXBuildFile; fileRef = FE137A4921F72AEA006B7F7C /* Appearance.swift */; };
FE137A4C21F75660006B7F7C /* ChatBottomView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FE137A4B21F75660006B7F7C /* ChatBottomView.swift */; };
@ -259,6 +260,7 @@
FE00157C2017617B00490340 /* StreamFeaturesCache.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StreamFeaturesCache.swift; sourceTree = "<group>"; };
FE00157E2019090300490340 /* ExperimentalSettingsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExperimentalSettingsViewController.swift; sourceTree = "<group>"; };
FE01ADA81E224CF400FA7E65 /* SiskinPushNotificationsModule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SiskinPushNotificationsModule.swift; sourceTree = "<group>"; };
FE10BCF223FD4EF000E214F3 /* db-schema-10.sql */ = {isa = PBXFileReference; lastKnownFileType = text; path = "db-schema-10.sql"; sourceTree = "<group>"; };
FE137A4721F6464D006B7F7C /* UIColor_mix.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIColor_mix.swift; sourceTree = "<group>"; };
FE137A4921F72AEA006B7F7C /* Appearance.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Appearance.swift; sourceTree = "<group>"; };
FE137A4B21F75660006B7F7C /* ChatBottomView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatBottomView.swift; sourceTree = "<group>"; };
@ -694,6 +696,7 @@
FE759FC82370B2A4001E78D9 /* Info.plist */,
FE759FF823742AC1001E78D9 /* NotificationCategory.swift */,
FECEF29723B7B838007EC323 /* db-schema-9.sql */,
FE10BCF223FD4EF000E214F3 /* db-schema-10.sql */,
);
path = Shared;
sourceTree = "<group>";
@ -1019,6 +1022,7 @@
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
FE10BCF323FD4EF000E214F3 /* db-schema-10.sql in Resources */,
FE759FF22371F21C001E78D9 /* db-schema-6.sql in Resources */,
FE759FEF2371F21C001E78D9 /* db-schema-3.sql in Resources */,
FE759FED2371F213001E78D9 /* db-schema-2.sql in Resources */,

View file

@ -12,7 +12,7 @@
<key>Shared.xcscheme_^#shared#^_</key>
<dict>
<key>orderHint</key>
<integer>6</integer>
<integer>7</integer>
</dict>
<key>SiskinIM - Share.xcscheme</key>
<dict>

View file

@ -27,10 +27,10 @@ public class ChatAttachment: ChatEntry {
let url: String;
var appendix: ChatAttachmentAppendix;
init(id: Int, timestamp: Date, account: BareJID, jid: BareJID, state: MessageState, url: String, authorNickname: String?, authorJid: BareJID?, encryption: MessageEncryption, encryptionFingerprint: String?, appendix: ChatAttachmentAppendix, error: String?) {
init(id: Int, timestamp: Date, account: BareJID, jid: BareJID, state: MessageState, url: String, authorNickname: String?, authorJid: BareJID?, recipientNickname: String?, encryption: MessageEncryption, encryptionFingerprint: String?, appendix: ChatAttachmentAppendix, error: String?) {
self.url = url;
self.appendix = appendix;
super.init(id: id, timestamp: timestamp, account: account, jid: jid, state: state, authorNickname: authorNickname, authorJid: authorJid, encryption: encryption, encryptionFingerprint: encryptionFingerprint, error: error);
super.init(id: id, timestamp: timestamp, account: account, jid: jid, state: state, authorNickname: authorNickname, authorJid: authorJid, recipientNickname: recipientNickname, encryption: encryption, encryptionFingerprint: encryptionFingerprint, error: error);
}
override public func copyText(withTimestamp: Bool, withSender: Bool) -> String? {

View file

@ -40,13 +40,14 @@ public class ChatEntry: ChatViewItemProtocol {
// for MUC only but any chat may be a MUC chat...
public let authorNickname: String?;
public let authorJid: BareJID?;
public let recipientNickname: String?;
public let error: String?;
public let encryption: MessageEncryption;
public let encryptionFingerprint: String?;
init(id: Int, timestamp: Date, account: BareJID, jid: BareJID, state: MessageState, authorNickname: String?, authorJid: BareJID?, encryption: MessageEncryption, encryptionFingerprint: String?, error: String?) {
init(id: Int, timestamp: Date, account: BareJID, jid: BareJID, state: MessageState, authorNickname: String?, authorJid: BareJID?, recipientNickname: String?, encryption: MessageEncryption, encryptionFingerprint: String?, error: String?) {
self.id = id;
self.timestamp = timestamp;
self.account = account;
@ -54,6 +55,7 @@ public class ChatEntry: ChatViewItemProtocol {
self.state = state;
self.authorNickname = authorNickname;
self.authorJid = authorJid;
self.recipientNickname = recipientNickname;
self.encryption = encryption;
self.encryptionFingerprint = encryptionFingerprint;
self.error = error;
@ -63,7 +65,7 @@ public class ChatEntry: ChatViewItemProtocol {
guard let item = chatItem as? ChatEntry else {
return false;
}
return self.account == item.account && self.jid == item.jid && self.state.direction == item.state.direction && self.authorNickname == item.authorNickname && self.authorJid == item.authorJid && abs(self.timestamp.timeIntervalSince(item.timestamp)) < allowedTimeDiff() && self.encryption == item.encryption && self.encryptionFingerprint == item.encryptionFingerprint;
return self.account == item.account && self.jid == item.jid && self.state.direction == item.state.direction && self.authorNickname == item.authorNickname && self.authorJid == item.authorJid && self.recipientNickname == item.recipientNickname && abs(self.timestamp.timeIntervalSince(item.timestamp)) < allowedTimeDiff() && self.encryption == item.encryption && self.encryptionFingerprint == item.encryptionFingerprint;
}
public func copyText(withTimestamp: Bool, withSender: Bool) -> String? {

View file

@ -26,9 +26,9 @@ class ChatLinkPreview: ChatEntry {
let url: String;
init(id: Int, timestamp: Date, account: BareJID, jid: BareJID, state: MessageState, url: String, authorNickname: String?, authorJid: BareJID?, encryption: MessageEncryption, encryptionFingerprint: String?, error: String?) {
init(id: Int, timestamp: Date, account: BareJID, jid: BareJID, state: MessageState, url: String, authorNickname: String?, authorJid: BareJID?, recipientNickname: String?, encryption: MessageEncryption, encryptionFingerprint: String?, error: String?) {
self.url = url;
super.init(id: id, timestamp: timestamp, account: account, jid: jid, state: state, authorNickname: authorNickname, authorJid: authorJid, encryption: encryption, encryptionFingerprint: encryptionFingerprint, error: error);
super.init(id: id, timestamp: timestamp, account: account, jid: jid, state: state, authorNickname: authorNickname, authorJid: authorJid, recipientNickname: recipientNickname, encryption: encryption, encryptionFingerprint: encryptionFingerprint, error: error);
}
override func copyText(withTimestamp: Bool, withSender: Bool) -> String? {

View file

@ -26,9 +26,9 @@ class ChatMessage: ChatEntry {
let message: String;
init(id: Int, timestamp: Date, account: BareJID, jid: BareJID, state: MessageState, message: String, authorNickname: String?, authorJid: BareJID?, encryption: MessageEncryption, encryptionFingerprint: String?, error: String?) {
init(id: Int, timestamp: Date, account: BareJID, jid: BareJID, state: MessageState, message: String, authorNickname: String?, authorJid: BareJID?, recipientNickname: String?, encryption: MessageEncryption, encryptionFingerprint: String?, error: String?) {
self.message = message;
super.init(id: id, timestamp: timestamp, account: account, jid: jid, state: state, authorNickname: authorNickname, authorJid: authorJid, encryption: encryption, encryptionFingerprint: encryptionFingerprint, error: error);
super.init(id: id, timestamp: timestamp, account: account, jid: jid, state: state, authorNickname: authorNickname, authorJid: authorJid, recipientNickname: recipientNickname, encryption: encryption, encryptionFingerprint: encryptionFingerprint, error: error);
}
override func copyText(withTimestamp: Bool, withSender: Bool) -> String? {

View file

@ -22,7 +22,7 @@
import UIKit
import TigaseSwift
class MucChatOccupantsTableViewController: CustomTableViewController {
class MucChatOccupantsTableViewController: CustomTableViewController {//}, UIContextMenuInteractionDelegate {
var xmppService:XmppService!;
@ -82,7 +82,7 @@ class MucChatOccupantsTableViewController: CustomTableViewController {
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "MucChatOccupantsTableViewCell", for: indexPath as IndexPath) as! MucChatOccupantsTableViewCell;
let occupant = participants[indexPath.row];
cell.nicknameLabel.text = occupant.nickname;
if let jid = occupant.jid {
@ -112,6 +112,51 @@ class MucChatOccupantsTableViewController: CustomTableViewController {
}
self.navigationController?.popViewController(animated: true);
}
@available(iOS 13.0, *)
override func tableView(_ tableView: UITableView, contextMenuConfigurationForRowAt indexPath: IndexPath, point: CGPoint) -> UIContextMenuConfiguration? {
guard room.state == .joined else {
return nil;
}
let participant = self.participants[indexPath.row];
return UIContextMenuConfiguration(identifier: nil, previewProvider: nil, actionProvider: { suggestedActions in
var actions: [UIAction] = [];
actions.append(UIAction(title: "Private message", handler: { action in
let alert = UIAlertController(title: "Send message", message: "Enter message to send to: \(participant.nickname)", preferredStyle: .alert);
alert.addTextField(configurationHandler: nil);
alert.addAction(UIAlertAction(title: "Send", style: .default, handler: { action in
guard let text = alert.textFields?.first?.text else {
return;
}
MucEventHandler.instance.sendPrivateMessage(room: self.room, recipientNickname: participant.nickname, body: text);
}));
alert.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil));
self.present(alert, animated: true, completion: nil);
}));
if let jid = participant.jid, self.room.presences[self.room.nickname]?.affiliation == MucAffiliation.admin {
actions.append(UIAction(title: "Ban user", handler: { action in
guard let mucModule: MucModule = XmppService.instance.getClient(for: self.room.account)?.modulesManager.getModule(MucModule.ID) else {
return;
}
let alert = UIAlertController(title: "Banning user", message: "Do you want to ban user \(participant.nickname)?", preferredStyle: .alert);
alert.addAction(UIAlertAction(title: "Yes", style: .destructive, handler: { action in
mucModule.setRoomAffiliations(to: self.room, changedAffiliations: [MucModule.RoomAffiliation(jid: jid, affiliation: .outcast)], completionHandler: { error in
guard let err = error else {
return;
}
let alert = UIAlertController(title: "Banning user \(participant.nickname) failed", message: "Server returned an error: \(err.rawValue)", preferredStyle: .alert);
alert.addAction(UIAlertAction(title: "OK", style: .cancel, handler: nil));
self.present(alert, animated: true, completion: nil);
})
}))
alert.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil));
self.present(alert, animated: true, completion: nil);
}));
}
return UIMenu(title: "", children: actions);
});
}
/*
// Override to support editing the table view.

View file

@ -130,7 +130,17 @@ class MucChatViewController: BaseChatViewControllerWithDataSourceAndContextMenuA
cell.avatarView?.set(name: item.authorNickname, avatar: nil, orDefault: AvatarManager.instance.defaultAvatar);
}
}
cell.nicknameView?.text = item.authorNickname;
let sender = item.authorNickname ?? "From \(item.jid.stringValue)";
if let author = item.authorNickname, let recipient = item.recipientNickname {
let val = NSMutableAttributedString(string: item.state.direction == .incoming ? "From \(author) " : "To \(recipient) ");
let font = UIFont.italicSystemFont(ofSize: cell.nicknameView!.font!.pointSize - 2);
val.append(NSAttributedString(string: " (private message)", attributes: [.font: font, .foregroundColor: Appearance.current.secondaryLabelColor]));
cell.nicknameView?.attributedText = val;
} else {
cell.nicknameView?.text = sender;
}
cell.set(message: item);
cell.backgroundColor = Appearance.current.systemBackground;
return cell;
@ -147,7 +157,17 @@ class MucChatViewController: BaseChatViewControllerWithDataSourceAndContextMenuA
cell.avatarView?.set(name: item.authorNickname, avatar: nil, orDefault: AvatarManager.instance.defaultAvatar);
}
}
cell.nicknameView?.text = item.authorNickname;
let sender = item.authorNickname ?? "From \(item.jid.stringValue)";
if let author = item.authorNickname, let recipient = item.recipientNickname {
let val = NSMutableAttributedString(string: item.state.direction == .incoming ? "From \(author) " : "To \(recipient) ");
let font = UIFont.italicSystemFont(ofSize: cell.nicknameView!.font!.pointSize - 2);
val.append(NSAttributedString(string: " (private message)", attributes: [.font: font, .foregroundColor: Appearance.current.secondaryLabelColor]));
cell.nicknameView?.attributedText = val;
} else {
cell.nicknameView?.text = sender;
}
cell.set(attachment: item);
cell.setNeedsUpdateConstraints();
cell.updateConstraintsIfNeeded();

View file

@ -32,7 +32,7 @@ open class DBChatHistoryStore: Logger {
public static let MESSAGE_REMOVED = Notification.Name("messageRemoved");
fileprivate static let CHAT_GET_ID_WITH_ACCOUNT_PARTICIPANT_AND_STANZA_ID = "SELECT id FROM chat_history WHERE account = :account AND jid = :jid AND stanza_id = :stanzaId";
fileprivate static let CHAT_MSG_APPEND = "INSERT INTO chat_history (account, jid, author_jid, author_nickname, timestamp, item_type, data, stanza_id, state, encryption, fingerprint, appendix) VALUES (:account, :jid, :author_jid, :author_nickname, :timestamp, :item_type, :data, :stanza_id, :state, :encryption, :fingerprint, :appendix)";
fileprivate static let CHAT_MSG_APPEND = "INSERT INTO chat_history (account, jid, author_jid, author_nickname, recipient_nickname, timestamp, item_type, data, stanza_id, state, encryption, fingerprint, appendix) VALUES (:account, :jid, :author_jid, :author_nickname, :recipient_nickname, :timestamp, :item_type, :data, :stanza_id, :state, :encryption, :fingerprint, :appendix)";
fileprivate static let CHAT_MSGS_COUNT = "SELECT count(id) FROM chat_history WHERE account = :account AND jid = :jid";
fileprivate static let CHAT_MSGS_DELETE = "DELETE FROM chat_history WHERE account = :account AND jid = :jid";
fileprivate static let CHAT_MSGS_MARK_AS_READ = "UPDATE chat_history SET state = case state when \(MessageState.incoming_error_unread.rawValue) then \(MessageState.incoming_error.rawValue) when \(MessageState.outgoing_error_unread.rawValue) then \(MessageState.outgoing_error.rawValue) else \(MessageState.incoming.rawValue) end WHERE account = :account AND jid = :jid AND state in (\(MessageState.incoming_unread.rawValue), \(MessageState.incoming_error_unread.rawValue), \(MessageState.outgoing_error_unread.rawValue))";
@ -54,7 +54,7 @@ open class DBChatHistoryStore: Logger {
fileprivate lazy var getMessagePositionStmtInverted: DBStatement! = try? self.dbConnection.prepareStatement("SELECT count(id) FROM chat_history WHERE account = :account AND jid = :jid AND id <> :msgId AND (:showLinkPreviews OR item_type IN (\(ItemType.message.rawValue), \(ItemType.attachment.rawValue))) AND timestamp > (SELECT timestamp FROM chat_history WHERE id = :msgId)");
fileprivate lazy var markMessageAsErrorStmt: DBStatement! = try? self.dbConnection.prepareStatement("UPDATE chat_history SET state = :state, error = :error WHERE id = :id");
fileprivate lazy var getMessageErrorDetails: DBStatement! = try? self.dbConnection.prepareStatement("SELECT error FROM chat_history WHERE id = ?");
fileprivate lazy var getChatMessageWithIdStmt: DBStatement! = try! self.dbConnection.prepareStatement("SELECT id, account, jid, author_nickname, author_jid, timestamp, item_type, data, state, preview, encryption, fingerprint, error, appendix FROM chat_history WHERE id = :id");
fileprivate lazy var getChatMessageWithIdStmt: DBStatement! = try! self.dbConnection.prepareStatement("SELECT id, account, jid, author_nickname, author_jid, recipient_nickname, timestamp, item_type, data, state, preview, encryption, fingerprint, error, appendix FROM chat_history WHERE id = :id");
fileprivate let getUnsentMessagesForAccountStmt: DBStatement;
fileprivate let getChatMessagesStmt: DBStatement;
fileprivate let getChatAttachmentsStmt: DBStatement;
@ -71,14 +71,14 @@ open class DBChatHistoryStore: Logger {
self.dispatcher = QueueDispatcher(label: "chat_history_store");
self.dbConnection = dbConnection;
self.getUnsentMessagesForAccountStmt = try! self.dbConnection.prepareStatement("SELECT ch.account as account, ch.jid as jid, ch.data as data, ch.stanza_id as stanza_id, ch.encryption as encryption FROM chat_history ch WHERE ch.account = :account AND ch.state = \(MessageState.outgoing_unsent.rawValue) ORDER BY timestamp ASC");
self.getChatMessagesStmt = try! dbConnection.prepareStatement("SELECT id, author_nickname, author_jid, timestamp, item_type, data, state, preview, encryption, fingerprint, error, appendix FROM chat_history WHERE account = :account AND jid = :jid AND (:showLinkPreviews OR item_type IN (\(ItemType.message.rawValue), \(ItemType.attachment.rawValue))) ORDER BY timestamp DESC LIMIT :limit OFFSET :offset");
self.getChatAttachmentsStmt = try! dbConnection.prepareStatement("SELECT id, author_nickname, author_jid, timestamp, item_type, data, state, preview, encryption, fingerprint, error, appendix FROM chat_history WHERE account = :account AND jid = :jid AND item_type = \(ItemType.attachment.rawValue) ORDER BY timestamp DESC");
self.getChatMessagesStmt = try! dbConnection.prepareStatement("SELECT id, author_nickname, author_jid, recipient_nickname, timestamp, item_type, data, state, preview, encryption, fingerprint, error, appendix FROM chat_history WHERE account = :account AND jid = :jid AND (:showLinkPreviews OR item_type IN (\(ItemType.message.rawValue), \(ItemType.attachment.rawValue))) ORDER BY timestamp DESC LIMIT :limit OFFSET :offset");
self.getChatAttachmentsStmt = try! dbConnection.prepareStatement("SELECT id, author_nickname, author_jid, recipient_nickname, timestamp, item_type, data, state, preview, encryption, fingerprint, error, appendix FROM chat_history WHERE account = :account AND jid = :jid AND item_type = \(ItemType.attachment.rawValue) ORDER BY timestamp DESC");
self.updateItemStmt = try! dbConnection.prepareStatement("UPDATE chat_history SET appendix = :appendix WHERE id = :id")
super.init();
NotificationCenter.default.addObserver(self, selector: #selector(DBChatHistoryStore.accountRemoved), name: NSNotification.Name(rawValue: "accountRemoved"), object: nil);
}
public func appendItem(for account: BareJID, with jid: BareJID, state inState: MessageState, authorNickname: String? = nil, authorJid: BareJID? = nil, type: ItemType = .message, timestamp inTimestamp: Date, stanzaId id: String?, data: String, chatState: ChatState? = nil, errorCondition: ErrorCondition? = nil, errorMessage: String? = nil, encryption: MessageEncryption, encryptionFingerprint: String?, chatAttachmentAppendix: ChatAttachmentAppendix? = nil, skipItemAlreadyExists: Bool = false, completionHandler: ((Int)->Void)?) {
public func appendItem(for account: BareJID, with jid: BareJID, state inState: MessageState, authorNickname: String? = nil, authorJid: BareJID? = nil, recipientNickname: String? = nil, type: ItemType = .message, timestamp inTimestamp: Date, stanzaId id: String?, data: String, chatState: ChatState? = nil, errorCondition: ErrorCondition? = nil, errorMessage: String? = nil, encryption: MessageEncryption, encryptionFingerprint: String?, chatAttachmentAppendix: ChatAttachmentAppendix? = nil, skipItemAlreadyExists: Bool = false, completionHandler: ((Int)->Void)?) {
dispatcher.async {
let timestamp = Date(timeIntervalSince1970: Double(Int64(inTimestamp.timeIntervalSince1970 * 1000)) / 1000);
@ -99,7 +99,8 @@ open class DBChatHistoryStore: Logger {
}
}
let params:[String:Any?] = ["account" : account, "jid" : jid, "timestamp": timestamp, "data": data, "item_type": type.rawValue, "state": state.rawValue, "stanza_id": id, "author_jid" : authorJid, "author_nickname": authorNickname, "encryption": encryption.rawValue, "fingerprint": encryptionFingerprint, "appendix": appendix]
let params:[String:Any?] = ["account" : account, "jid" : jid, "timestamp": timestamp, "data": data, "item_type": type.rawValue, "state": state.rawValue, "stanza_id": id, "author_jid" : authorJid, "author_nickname": authorNickname,
"recipient_nickname": recipientNickname, "encryption": encryption.rawValue, "fingerprint": encryptionFingerprint, "appendix": appendix]
guard let msgId = try! self.appendMessageStmt.insert(params) else {
return;
}
@ -108,12 +109,12 @@ open class DBChatHistoryStore: Logger {
var item: ChatViewItemProtocol?;
switch type {
case .message:
item = ChatMessage(id: msgId, timestamp: timestamp, account: account, jid: jid, state: state, message: data, authorNickname: authorNickname, authorJid: authorJid, encryption: encryption, encryptionFingerprint: encryptionFingerprint, error: errorMessage);
item = ChatMessage(id: msgId, timestamp: timestamp, account: account, jid: jid, state: state, message: data, authorNickname: authorNickname, authorJid: authorJid, recipientNickname: recipientNickname, encryption: encryption, encryptionFingerprint: encryptionFingerprint, error: errorMessage);
case .attachment:
item = ChatAttachment(id: msgId, timestamp: timestamp, account: account, jid: jid, state: state, url: data, authorNickname: authorNickname, authorJid: authorJid, encryption: encryption, encryptionFingerprint: encryptionFingerprint, appendix: chatAttachmentAppendix ?? ChatAttachmentAppendix(), error: errorMessage);
item = ChatAttachment(id: msgId, timestamp: timestamp, account: account, jid: jid, state: state, url: data, authorNickname: authorNickname, authorJid: authorJid, recipientNickname: recipientNickname, encryption: encryption, encryptionFingerprint: encryptionFingerprint, appendix: chatAttachmentAppendix ?? ChatAttachmentAppendix(), error: errorMessage);
case .linkPreview:
if #available(iOS 13.0, *), Settings.linkPreviews.bool() {
item = ChatLinkPreview(id: msgId, timestamp: timestamp, account: account, jid: jid, state: state, url: data, authorNickname: authorNickname, authorJid: authorJid, encryption: encryption, encryptionFingerprint: encryptionFingerprint, error: errorMessage);
item = ChatLinkPreview(id: msgId, timestamp: timestamp, account: account, jid: jid, state: state, url: data, authorNickname: authorNickname, authorJid: authorJid, recipientNickname: recipientNickname, encryption: encryption, encryptionFingerprint: encryptionFingerprint, error: errorMessage);
}
}
if item != nil {
@ -412,6 +413,7 @@ open class DBChatHistoryStore: Logger {
let authorNickname: String? = cursor["author_nickname"];
let authorJid: BareJID? = cursor["author_jid"];
let recipientNickname: String? = cursor["recipient_nickname"];
let encryption: MessageEncryption = MessageEncryption(rawValue: cursor["encryption"] ?? 0) ?? .none;
let encryptionFingerprint: String? = cursor["fingerprint"];
let error: String? = cursor["error"];
@ -420,16 +422,16 @@ open class DBChatHistoryStore: Logger {
case .message:
let message: String = cursor["data"]!;
return ChatMessage(id: id, timestamp: timestamp, account: account, jid: jid, state: MessageState(rawValue: stateInt)!, message: message, authorNickname: authorNickname, authorJid: authorJid, encryption: encryption, encryptionFingerprint: encryptionFingerprint, error: error);
return ChatMessage(id: id, timestamp: timestamp, account: account, jid: jid, state: MessageState(rawValue: stateInt)!, message: message, authorNickname: authorNickname, authorJid: authorJid, recipientNickname: recipientNickname, encryption: encryption, encryptionFingerprint: encryptionFingerprint, error: error);
case .attachment:
let url: String = cursor["data"]!;
let appendix = parseAttachmentAppendix(string: cursor["appendix"]);
return ChatAttachment(id: id, timestamp: timestamp, account: account, jid: jid, state: MessageState(rawValue: stateInt)!, url: url, authorNickname: authorNickname, authorJid: authorJid, encryption: encryption, encryptionFingerprint: encryptionFingerprint, appendix: appendix, error: error);
return ChatAttachment(id: id, timestamp: timestamp, account: account, jid: jid, state: MessageState(rawValue: stateInt)!, url: url, authorNickname: authorNickname, authorJid: authorJid, recipientNickname: recipientNickname, encryption: encryption, encryptionFingerprint: encryptionFingerprint, appendix: appendix, error: error);
case .linkPreview:
let url: String = cursor["data"]!;
return ChatLinkPreview(id: id, timestamp: timestamp, account: account, jid: jid, state: MessageState(rawValue: stateInt)!, url: url, authorNickname: authorNickname, authorJid: authorJid, encryption: encryption, encryptionFingerprint: encryptionFingerprint, error: error)
return ChatLinkPreview(id: id, timestamp: timestamp, account: account, jid: jid, state: MessageState(rawValue: stateInt)!, url: url, authorNickname: authorNickname, authorJid: authorJid, recipientNickname: recipientNickname, encryption: encryption, encryptionFingerprint: encryptionFingerprint, error: error)
}
}

View file

@ -751,6 +751,12 @@ class DBRoom: Room, DBChatProtocol {
}
return true;
}
override func createPrivateMessage(_ body: String?, recipientNickname: String) -> Message {
let stanza = super.createPrivateMessage(body, recipientNickname: recipientNickname);
stanza.id = UUID().uuidString;
return stanza;
}
}
public enum ChatEncryption: String {

View file

@ -100,19 +100,31 @@ class MessageEventHandler: XmppServiceEventHandler {
}
}
DBChatHistoryStore.instance.appendItem(for: account, with: from.bareJid, state: state, type: type, timestamp: timestamp, stanzaId: e.message.id, data: body!, chatState: e.message.chatState, errorCondition: e.message.errorCondition, errorMessage: e.message.errorText, encryption: encryption, encryptionFingerprint: fingerprint, completionHandler: nil);
var authorNickname: String? = nil;
var recipientNickname: String? = nil;
if let room = DBChatStore.instance.getChat(for: account, with: from.bareJid) as? DBRoom {
if state.direction == .incoming {
authorNickname = from.resource;
recipientNickname = room.nickname;
} else {
authorNickname = room.nickname;
recipientNickname = e.message.to?.resource;
}
}
DBChatHistoryStore.instance.appendItem(for: account, with: from.bareJid, state: state, authorNickname: authorNickname, recipientNickname: recipientNickname, type: type, timestamp: timestamp, stanzaId: e.message.id, data: body!, chatState: e.message.chatState, errorCondition: e.message.errorCondition, errorMessage: e.message.errorText, encryption: encryption, encryptionFingerprint: fingerprint, completionHandler: nil);
if type == .message && !state.isError, #available(iOS 13.0, *) {
let detector = try! NSDataDetector(types: NSTextCheckingResult.CheckingType.link.rawValue | NSTextCheckingResult.CheckingType.address.rawValue);
let matches = detector.matches(in: body!, range: NSMakeRange(0, body!.utf16.count));
matches.forEach { match in
if let url = match.url, let scheme = url.scheme, ["https", "http"].contains(scheme) {
DBChatHistoryStore.instance.appendItem(for: account, with: from.bareJid, state: state, type: .linkPreview, timestamp: timestamp, stanzaId: nil, data: url.absoluteString, chatState: e.message.chatState, errorCondition: e.message.errorCondition, errorMessage: e.message.errorText, encryption: encryption, encryptionFingerprint: fingerprint, completionHandler: nil);
DBChatHistoryStore.instance.appendItem(for: account, with: from.bareJid, state: state, authorNickname: authorNickname, recipientNickname: recipientNickname, type: .linkPreview, timestamp: timestamp, stanzaId: nil, data: url.absoluteString, chatState: e.message.chatState, errorCondition: e.message.errorCondition, errorMessage: e.message.errorText, encryption: encryption, encryptionFingerprint: fingerprint, completionHandler: nil);
}
if let address = match.components {
let query = address.values.joined(separator: ",").addingPercentEncoding(withAllowedCharacters: .urlHostAllowed);
let mapUrl = URL(string: "http://maps.apple.com/?q=\(query!)")!;
DBChatHistoryStore.instance.appendItem(for: account, with: from.bareJid, state: state, type: .linkPreview, timestamp: timestamp, stanzaId: nil, data: mapUrl.absoluteString, chatState: e.message.chatState, errorCondition: e.message.errorCondition, errorMessage: e.message.errorText, encryption: encryption, encryptionFingerprint: fingerprint, completionHandler: nil);
DBChatHistoryStore.instance.appendItem(for: account, with: from.bareJid, state: state, authorNickname: authorNickname, recipientNickname: recipientNickname, type: .linkPreview, timestamp: timestamp, stanzaId: nil, data: mapUrl.absoluteString, chatState: e.message.chatState, errorCondition: e.message.errorCondition, errorMessage: e.message.errorText, encryption: encryption, encryptionFingerprint: fingerprint, completionHandler: nil);
}
}
}
@ -170,7 +182,14 @@ class MessageEventHandler: XmppServiceEventHandler {
}
}
DBChatHistoryStore.instance.appendItem(for: account, with: jid, state: state, type: type, timestamp: timestamp, stanzaId: e.message.id, data: body!, errorCondition: e.message.errorCondition, errorMessage: e.message.errorText, encryption: encryption, encryptionFingerprint: fingerprint, completionHandler: { (msgId) in
var authorNickname: String? = nil;
var recipientNickname: String? = nil;
if let room = DBChatStore.instance.getChat(for: account, with: jid) as? DBRoom {
// carbons should not copy PM messages
return;
}
DBChatHistoryStore.instance.appendItem(for: account, with: jid, state: state, authorNickname: authorNickname, recipientNickname: recipientNickname, type: type, timestamp: timestamp, stanzaId: e.message.id, data: body!, errorCondition: e.message.errorCondition, errorMessage: e.message.errorText, encryption: encryption, encryptionFingerprint: fingerprint, completionHandler: { (msgId) in
if state.direction == .outgoing {
DBChatHistoryStore.instance.markAsRead(for: account, with: jid, before: timestamp);
}
@ -181,12 +200,12 @@ class MessageEventHandler: XmppServiceEventHandler {
let matches = detector.matches(in: body!, range: NSMakeRange(0, body!.utf16.count));
matches.forEach { match in
if let url = match.url, let scheme = url.scheme, ["https", "http"].contains(scheme) {
DBChatHistoryStore.instance.appendItem(for: account, with: jid, state: state, type: .linkPreview, timestamp: timestamp, stanzaId: nil, data: url.absoluteString, chatState: e.message.chatState, errorCondition: e.message.errorCondition, errorMessage: e.message.errorText, encryption: encryption, encryptionFingerprint: fingerprint, completionHandler: nil);
DBChatHistoryStore.instance.appendItem(for: account, with: jid, state: state, authorNickname: authorNickname, recipientNickname: recipientNickname, type: .linkPreview, timestamp: timestamp, stanzaId: nil, data: url.absoluteString, chatState: e.message.chatState, errorCondition: e.message.errorCondition, errorMessage: e.message.errorText, encryption: encryption, encryptionFingerprint: fingerprint, completionHandler: nil);
}
if let address = match.components {
let query = address.values.joined(separator: ",").addingPercentEncoding(withAllowedCharacters: .urlHostAllowed);
let mapUrl = URL(string: "http://maps.apple.com/?q=\(query!)")!;
DBChatHistoryStore.instance.appendItem(for: account, with: jid, state: state, type: .linkPreview, timestamp: timestamp, stanzaId: nil, data: mapUrl.absoluteString, errorCondition: e.message.errorCondition, errorMessage: e.message.errorText, encryption: encryption, encryptionFingerprint: fingerprint, completionHandler: nil);
DBChatHistoryStore.instance.appendItem(for: account, with: jid, state: state, authorNickname: authorNickname, recipientNickname: recipientNickname, type: .linkPreview, timestamp: timestamp, stanzaId: nil, data: mapUrl.absoluteString, errorCondition: e.message.errorCondition, errorMessage: e.message.errorText, encryption: encryption, encryptionFingerprint: fingerprint, completionHandler: nil);
}
}
}
@ -208,20 +227,46 @@ class MessageEventHandler: XmppServiceEventHandler {
let jid = account == from.bareJid ? to.bareJid : from.bareJid;
let timestamp = e.timestamp!;
let state: MessageState = calculateState(direction: account == from.bareJid ? .outgoing : .incoming, error: ((e.message.type ?? .chat) == .error), unread: false);
DBChatHistoryStore.instance.appendItem(for: account, with: jid, state: state, type: type, timestamp: timestamp, stanzaId: e.message.id, data: body!, errorCondition: e.message.errorCondition, errorMessage: e.message.errorText, encryption: encryption, encryptionFingerprint: fingerprint, completionHandler: nil);
var state: MessageState = calculateState(direction: account == from.bareJid ? .outgoing : .incoming, error: ((e.message.type ?? .chat) == .error), unread: false);
var authorNickname: String? = nil;
var recipientNickname: String? = nil;
if let room = DBChatStore.instance.getChat(for: account, with: from.bareJid) as? DBRoom {
if room.nickname == from.resource {
if state.isError {
state = .incoming_error;
} else {
state = .incoming;
}
} else {
if state.isError {
state = .outgoing_error;
} else {
state = .outgoing;
}
}
if state.direction == .incoming {
authorNickname = from.resource;
recipientNickname = room.nickname;
} else {
authorNickname = room.nickname;
recipientNickname = e.message.to?.resource;
}
}
DBChatHistoryStore.instance.appendItem(for: account, with: jid, state: state, authorNickname: authorNickname, recipientNickname: recipientNickname, type: type, timestamp: timestamp, stanzaId: e.message.id, data: body!, errorCondition: e.message.errorCondition, errorMessage: e.message.errorText, encryption: encryption, encryptionFingerprint: fingerprint, completionHandler: nil);
if type == .message && !state.isError, #available(iOS 13.0, *) {
let detector = try! NSDataDetector(types: NSTextCheckingResult.CheckingType.link.rawValue | NSTextCheckingResult.CheckingType.address.rawValue);
let matches = detector.matches(in: body!, range: NSMakeRange(0, body!.utf16.count));
matches.forEach { match in
if let url = match.url, let scheme = url.scheme, ["https", "http"].contains(scheme) {
DBChatHistoryStore.instance.appendItem(for: account, with: from.bareJid, state: state, type: .linkPreview, timestamp: timestamp, stanzaId: nil, data: url.absoluteString, errorCondition: e.message.errorCondition, errorMessage: e.message.errorText, encryption: encryption, encryptionFingerprint: fingerprint, completionHandler: nil);
DBChatHistoryStore.instance.appendItem(for: account, with: from.bareJid, state: state, authorNickname: authorNickname, recipientNickname: recipientNickname, type: .linkPreview, timestamp: timestamp, stanzaId: nil, data: url.absoluteString, errorCondition: e.message.errorCondition, errorMessage: e.message.errorText, encryption: encryption, encryptionFingerprint: fingerprint, completionHandler: nil);
}
if let address = match.components {
let query = address.values.joined(separator: ",").addingPercentEncoding(withAllowedCharacters: .urlHostAllowed);
let mapUrl = URL(string: "http://maps.apple.com/?q=\(query!)")!;
DBChatHistoryStore.instance.appendItem(for: account, with: from.bareJid, state: state, type: .linkPreview, timestamp: timestamp, stanzaId: nil, data: mapUrl.absoluteString, errorCondition: e.message.errorCondition, errorMessage: e.message.errorText, encryption: encryption, encryptionFingerprint: fingerprint, completionHandler: nil);
DBChatHistoryStore.instance.appendItem(for: account, with: from.bareJid, state: state, authorNickname: authorNickname, recipientNickname: recipientNickname, type: .linkPreview, timestamp: timestamp, stanzaId: nil, data: mapUrl.absoluteString, errorCondition: e.message.errorCondition, errorMessage: e.message.errorText, encryption: encryption, encryptionFingerprint: fingerprint, completionHandler: nil);
}
}
}

View file

@ -28,6 +28,8 @@ 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 ];
@ -217,6 +219,12 @@ class MucEventHandler: XmppServiceEventHandler {
}
}
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, recipientNickname: recipientNickname, type: .message, timestamp: Date(), stanzaId: message.id, data: body, encryption: .none, encryptionFingerprint: nil, chatAttachmentAppendix: nil, 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;

View file

@ -48,9 +48,9 @@ open class XmppService: Logger, EventHandler {
fileprivate var fetchStart = NSDate();
#if targetEnvironment(simulator)
fileprivate let eventHandlers: [XmppServiceEventHandler] = [NewFeaturesDetector(), MessageEventHandler(), MucEventHandler(), PresenceRosterEventHandler(), AvatarEventHandler(), DiscoEventHandler(), PushEventHandler.instance, BlockedEventHandler.instance];
fileprivate let eventHandlers: [XmppServiceEventHandler] = [NewFeaturesDetector(), MessageEventHandler(), MucEventHandler.instance, PresenceRosterEventHandler(), AvatarEventHandler(), DiscoEventHandler(), PushEventHandler.instance, BlockedEventHandler.instance];
#else
fileprivate let eventHandlers: [XmppServiceEventHandler] = [NewFeaturesDetector(), MessageEventHandler(), MucEventHandler(), PresenceRosterEventHandler(), AvatarEventHandler(), DiscoEventHandler(), PushEventHandler.instance, JingleManager.instance, BlockedEventHandler.instance];
fileprivate let eventHandlers: [XmppServiceEventHandler] = [NewFeaturesDetector(), MessageEventHandler(), MucEventHandler.instance, PresenceRosterEventHandler(), AvatarEventHandler(), DiscoEventHandler(), PushEventHandler.instance, JingleManager.instance, BlockedEventHandler.instance];
#endif
public let dbCapsCache: DBCapabilitiesCache;