Files
simplex-chat/apps/ios/SimpleXChat/ChatUtils.swift
Evgeny 4811d663e6 rfc: bot messages and buttons, core: command markdown, supported commands in profile preferences, chat sessions preference, peer type field in profile to identify bots (#5360)
* rfc: bot messages and buttons

* update

* update bot rfc

* core: add bot commands to chat preferences and peer type to profile

* update postgresql schema

* update query plans

* chat sessions preference

* markdown for bot commands

* schema

* core: file preference, options to create bot from CLI

* core: different command type

* ios: commands menu

* update types

* update ios

* improve command markdown

* core, ios: update types

* android, desktop: clickable commands in messages in chats with bots

* android, desktop: commands menu

* command menu button, bot icon

* ios: connect flow for bots

* android, desktop: connect flow for bots

* icon

* CLI commands to view and set commands, remove "hidden" property of command, bot api docs

* corrections

* fix inheriting profile preferences to business groups

* note on business address

* ios: export localizations

* fix test

* commands to set file preference on user/contact, tidy up layout and display of command and attachment buttons
2025-08-07 11:13:35 +01:00

115 lines
3.7 KiB
Swift

//
// ChatUtils.swift
// SimpleXChat
//
// Created by Levitating Pineapple on 15/07/2024.
// Copyright © 2024 SimpleX Chat. All rights reserved.
//
import Foundation
public protocol ChatLike {
var chatInfo: ChatInfo { get}
var chatItems: [ChatItem] { get }
var chatStats: ChatStats { get }
}
extension ChatLike {
public func groupFeatureEnabled(_ feature: GroupFeature) -> Bool {
if case let .group(groupInfo, _) = self.chatInfo {
let p = groupInfo.fullGroupPreferences
return switch feature {
case .timedMessages: p.timedMessages.on
case .directMessages: p.directMessages.on(for: groupInfo.membership)
case .fullDelete: p.fullDelete.on
case .reactions: p.reactions.on
case .voice: p.voice.on(for: groupInfo.membership)
case .files: p.files.on(for: groupInfo.membership)
case .simplexLinks: p.simplexLinks.on(for: groupInfo.membership)
case .history: p.history.on
case .reports: p.reports.on
}
} else {
return true
}
}
public func prohibitedByPref(
hasSimplexLink: Bool,
isMediaOrFileAttachment: Bool,
isVoice: Bool
) -> Bool {
// preference checks should match checks in compose view
let simplexLinkProhibited = hasSimplexLink && !groupFeatureEnabled(.simplexLinks)
let fileProhibited = isMediaOrFileAttachment && !groupFeatureEnabled(.files)
let voiceProhibited = isVoice && !chatInfo.featureEnabled(.voice)
return switch chatInfo {
case .direct: voiceProhibited
case .group: simplexLinkProhibited || fileProhibited || voiceProhibited
case .local: false
case .contactRequest: false
case .contactConnection: false
case .invalidJSON: false
}
}
}
public func filterChatsToForwardTo<C: ChatLike>(chats: [C]) -> [C] {
var filteredChats = chats.filter { c in
c.chatInfo.chatType != .local && canForwardToChat(c.chatInfo)
}
if let privateNotes = chats.first(where: { $0.chatInfo.chatType == .local }) {
filteredChats.insert(privateNotes, at: 0)
}
return filteredChats
}
public func foundChat(_ chat: ChatLike, _ searchStr: String) -> Bool {
let cInfo = chat.chatInfo
return switch cInfo {
case let .direct(contact):
viewNameContains(cInfo, searchStr) ||
contact.profile.displayName.localizedLowercase.contains(searchStr) ||
contact.fullName.localizedLowercase.contains(searchStr)
default:
viewNameContains(cInfo, searchStr)
}
func viewNameContains(_ cInfo: ChatInfo, _ s: String) -> Bool {
cInfo.chatViewName.localizedLowercase.contains(s)
}
}
private func canForwardToChat(_ cInfo: ChatInfo) -> Bool {
switch cInfo {
case let .direct(contact): cInfo.sendMsgEnabled && !contact.sendMsgToConnect
case .group: cInfo.sendMsgEnabled
case .local: cInfo.sendMsgEnabled
case .contactRequest: false
case .contactConnection: false
case .invalidJSON: false
}
}
public func chatIconName(_ cInfo: ChatInfo) -> String {
switch cInfo {
case let .direct(contact): contact.chatIconName
case let .group(groupInfo, _): groupInfo.chatIconName
case .local: "folder.circle.fill"
case .contactRequest: "person.crop.circle.fill"
default: "circle.fill"
}
}
public func hasSimplexLink(_ text: String?) -> Bool {
if let text, let parsedMsg = parseSimpleXMarkdown(text) {
parsedMsgHasSimplexLink(parsedMsg)
} else {
false
}
}
public func parsedMsgHasSimplexLink(_ parsedMsg: [FormattedText]) -> Bool {
parsedMsg.contains(where: { ft in ft.format?.isSimplexLink ?? false })
}