123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271 |
- //
- // SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
- // SPDX-License-Identifier: GPL-3.0-or-later
- //
- import Foundation
- import SwiftyAttributes
- @objc extension NCChatMessage {
- public var isSystemMessage: Bool {
- return self.systemMessage != nil && !self.systemMessage.isEmpty
- }
- public var isEmojiMessage: Bool {
- return self.message != nil && self.message.containsOnlyEmoji && self.message.emojiCount <= 3
- }
- public var isUpdateMessage: Bool {
- return self.systemMessage == "message_deleted" ||
- self.systemMessage == "reaction" ||
- self.systemMessage == "reaction_revoked" ||
- self.systemMessage == "reaction_deleted" ||
- self.systemMessage == "poll_voted" ||
- self.systemMessage == "message_edited"
- }
- public var isDeletedMessage: Bool {
- return self.messageType == kMessageTypeCommentDeleted
- }
- public var isVoiceMessage: Bool {
- return self.messageType == kMessageTypeVoiceMessage
- }
- public var isCommandMessage: Bool {
- return self.messageType == kMessageTypeCommand
- }
- public func isMessage(from userId: String) -> Bool {
- return self.actorType == "users" && self.actorId == userId
- }
- public func isDeletable(for account: TalkAccount, in room: NCRoom) -> Bool {
- guard !self.isDeleting else { return false }
- let sixHoursAgoTimestamp = Int(Date().timeIntervalSince1970 - (6 * 3600))
- // Check server capability for normal messages
- var commentDeletion = NCDatabaseManager.sharedInstance().serverHasTalkCapability(kCapabilityDeleteMessages, forAccountId: account.accountId)
- commentDeletion = commentDeletion && self.messageType == kMessageTypeComment
- commentDeletion = commentDeletion && self.file() == nil
- commentDeletion = commentDeletion && !self.isObjectShare
- // Check server capability for files or shared objects
- var objectDeletion = NCDatabaseManager.sharedInstance().serverHasTalkCapability(kCapabilityRichObjectDelete)
- objectDeletion = objectDeletion && (self.file() != nil || self.isVoiceMessage || self.isObjectShare)
- // Check if user is allowed to delete a message
- let sameUser = self.isMessage(from: account.userId)
- let moderatorUser = (room.type != .oneToOne && room.type != .formerOneToOne) && (room.participantType == .owner || room.participantType == .moderator)
- let serverCanDeleteMessage = commentDeletion || objectDeletion
- let userCanDeleteMessage = sameUser || moderatorUser
- let noTimeLimitForMessageDeletion = NCDatabaseManager.sharedInstance().serverHasTalkCapability(kCapabilityDeleteMessagesUnlimited, forAccountId: account.accountId)
- let deletionAllowedByTime = noTimeLimitForMessageDeletion || (self.timestamp >= sixHoursAgoTimestamp)
- return serverCanDeleteMessage && userCanDeleteMessage && deletionAllowedByTime
- }
- public func isEditable(for account: TalkAccount, in room: NCRoom) -> Bool {
- guard !self.isDeleting else { return false }
- let twentyFourHoursAgoTimestamp = Int(Date().timeIntervalSince1970 - (24 * 3600))
- var serverCanEditMessage = NCDatabaseManager.sharedInstance().serverHasTalkCapability(kCapabilityEditMessages, forAccountId: account.accountId)
- serverCanEditMessage = serverCanEditMessage && self.messageType == kMessageTypeComment && !self.isObjectShare
- let sameUser = self.isMessage(from: account.userId)
- let moderatorUser = (room.type != .oneToOne && room.type != .formerOneToOne) && (room.participantType == .owner || room.participantType == .moderator)
- let userCanDeleteMessage = sameUser || moderatorUser
- let noTimeLimitForMessageEdit = (room.type == .noteToSelf) && NCDatabaseManager.sharedInstance().serverHasTalkCapability(kCapabilityEditMessagesNoteToSelf, forAccountId: account.accountId)
- let editAllowedByTime = noTimeLimitForMessageEdit || (self.timestamp >= twentyFourHoursAgoTimestamp)
- return serverCanEditMessage && userCanDeleteMessage && editAllowedByTime
- }
- public var isObjectShare: Bool {
- return self.message != nil && self.message == "{object}" && self.messageParameters["object"] != nil
- }
- public var richObjectFromObjectShare: [AnyHashable: Any] {
- guard self.isObjectShare,
- let objectDict = self.messageParameters["object"] as? [AnyHashable: Any],
- let jsonData = try? JSONSerialization.data(withJSONObject: objectDict),
- let jsonString = String(data: jsonData, encoding: .utf8),
- let parameter = NCMessageParameter(dictionary: objectDict)
- else { return [:] }
- return [
- "objectType": parameter.type!,
- "objectId": parameter.parameterId!,
- "metaData": jsonString
- ]
- }
- public var poll: NCMessageParameter? {
- guard let objectParameter, objectParameter.type == "talk-poll"
- else { return nil }
- return objectParameter
- }
- public var objectParameter: NCMessageParameter? {
- guard self.isObjectShare,
- let objectDict = self.messageParameters["object"] as? [AnyHashable: Any],
- let objectParameter = NCMessageParameter(dictionary: objectDict)
- else { return nil }
- return objectParameter
- }
- public var messageParameters: [AnyHashable: Any] {
- guard let data = self.messageParametersJSONString?.data(using: .utf8),
- let dict = try? JSONSerialization.jsonObject(with: data) as? [AnyHashable: Any]
- else { return [:] }
- return dict
- }
- // TODO: Should probably be an optional?
- public var systemMessageFormat: NSMutableAttributedString {
- guard let message = self.parsedMessage() else { return NSMutableAttributedString(string: "") }
- return message.withTextColor(.tertiaryLabel)
- }
- // TODO: Should probably be an optional?
- public var sendingMessage: String {
- guard var resultMessage = self.message else { return "" }
- resultMessage = resultMessage.trimmingCharacters(in: .whitespacesAndNewlines)
- for case let (key as String, value as [AnyHashable: Any]) in self.messageParameters {
- if let parameter = NCMessageParameter(dictionary: value) {
- resultMessage = resultMessage.replacingOccurrences(of: "{\(key)}", with: parameter.mentionId)
- }
- }
- return resultMessage
- }
- public var parent: NCChatMessage? {
- guard !self.isDeletedMessage, self.parentId != nil else { return nil }
- var unmanagedChatMessage: NCChatMessage?
- if let managedChatMessage = NCChatMessage.objects(where: "internalId = %@", self.parentId).firstObject() {
- unmanagedChatMessage = NCChatMessage(value: managedChatMessage)
- }
- return unmanagedChatMessage
- }
- public var parentMessageId: Int {
- return self.parent?.messageId ?? -1
- }
- public func isReactionBeingModified(_ reaction: String) -> Bool {
- return self.temporaryReactions().first(where: { ($0 as? NCChatReaction)?.reaction == reaction }) != nil
- }
- public func removeReactionFromTemporaryReactions(_ reaction: String) {
- if let removeReaction = self.temporaryReactions().first(where: { ($0 as? NCChatReaction)?.reaction == reaction }) {
- self.temporaryReactions().remove(removeReaction)
- }
- }
- public func addTemporaryReaction(_ reaction: String) {
- let temporaryReaction = NCChatReaction()
- temporaryReaction.reaction = reaction
- temporaryReaction.state = .adding
- self.temporaryReactions().add(temporaryReaction)
- }
- public func removeReactionTemporarily(_ reaction: String) {
- let temporaryReaction = NCChatReaction()
- temporaryReaction.reaction = reaction
- temporaryReaction.state = .removing
- self.temporaryReactions().add(temporaryReaction)
- }
- internal var isReferenceApiSupported: Bool {
- // Check capabilities directly, otherwise NCSettingsController introduces new dependencies in NotificationServiceExtension
- let activeAccount = NCDatabaseManager.sharedInstance().activeAccount()
- if let serverCapabilities = NCDatabaseManager.sharedInstance().serverCapabilities(forAccountId: activeAccount.accountId) {
- return serverCapabilities.referenceApiSupported
- }
- return false
- }
- public func isSameMessage(_ message: NCChatMessage) -> Bool {
- if self.isTemporary {
- return self.referenceId == message.referenceId
- }
- return self.messageId == message.messageId
- }
- public var collapsedMessageParameters: [AnyHashable: Any] {
- guard let data = self.collapsedMessageParametersJSONString.data(using: .utf8),
- let dict = try? JSONSerialization.jsonObject(with: data) as? [AnyHashable: Any]
- else { return [:] }
- return dict
- }
- public func setCollapsedMessageParameters(_ messageParameters: [AnyHashable: Any]) {
- guard let jsonData = try? JSONSerialization.data(withJSONObject: messageParameters),
- let jsonString = String(data: jsonData, encoding: .utf8)
- else { return }
- self.collapsedMessageParametersJSONString = jsonString
- }
- public var actor: TalkActor {
- return TalkActor(actorId: self.actorId, actorType: self.actorType, actorDisplayName: self.actorDisplayName)
- }
- public var messageIconName: String? {
- if let file = self.file() {
- if NCUtils.isImage(fileType: file.mimetype) {
- return "photo"
- } else if NCUtils.isVideo(fileType: file.mimetype) {
- return "movieclapper"
- } else if NCUtils.isVCard(fileType: file.mimetype) {
- return "person.text.rectangle"
- } else if self.isVoiceMessage {
- return "mic"
- } else if NCUtils.isAudio(fileType: file.mimetype) {
- return "music.note"
- } else {
- return "doc"
- }
- } else if poll != nil {
- return "chart.bar"
- } else if deckCard() != nil {
- return "rectangle.stack"
- } else if geoLocation() != nil {
- return "location"
- }
- return nil
- }
- public var isAnimatableGif: Bool {
- guard let accountId, let file = self.file() else { return false }
- let capabilities = NCDatabaseManager.sharedInstance().serverCapabilities(forAccountId: accountId)
- guard NCUtils.isGif(fileType: file.mimetype), let maxGifSize = capabilities?.maxGifSize, maxGifSize > 0 else { return false }
- return file.size <= maxGifSize
- }
- }
|