mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
900 lines
35 KiB
Swift
900 lines
35 KiB
Swift
import Foundation
|
|
import Postbox
|
|
import SwiftSignalKit
|
|
import TelegramApi
|
|
import MtProtoKit
|
|
|
|
public final class QuickReplyMessageShortcut: Codable, Equatable {
|
|
public let id: Int32
|
|
public let shortcut: String
|
|
|
|
public init(id: Int32, shortcut: String) {
|
|
self.id = id
|
|
self.shortcut = shortcut
|
|
}
|
|
|
|
public static func ==(lhs: QuickReplyMessageShortcut, rhs: QuickReplyMessageShortcut) -> Bool {
|
|
if lhs.id != rhs.id {
|
|
return false
|
|
}
|
|
if lhs.shortcut != rhs.shortcut {
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
|
|
public init(from decoder: Decoder) throws {
|
|
let container = try decoder.container(keyedBy: StringCodingKey.self)
|
|
|
|
self.id = try container.decode(Int32.self, forKey: "id")
|
|
self.shortcut = try container.decode(String.self, forKey: "shortcut")
|
|
}
|
|
|
|
public func encode(to encoder: Encoder) throws {
|
|
var container = encoder.container(keyedBy: StringCodingKey.self)
|
|
|
|
try container.encode(self.id, forKey: "id")
|
|
try container.encode(self.shortcut, forKey: "shortcut")
|
|
}
|
|
}
|
|
|
|
struct QuickReplyMessageShortcutsState: Codable, Equatable {
|
|
var shortcuts: [QuickReplyMessageShortcut]
|
|
|
|
init(shortcuts: [QuickReplyMessageShortcut]) {
|
|
self.shortcuts = shortcuts
|
|
}
|
|
}
|
|
|
|
public final class ShortcutMessageList: Equatable {
|
|
public final class Item: Equatable {
|
|
public let id: Int32?
|
|
public let shortcut: String
|
|
public let topMessage: EngineMessage
|
|
public let totalCount: Int
|
|
|
|
public init(id: Int32?, shortcut: String, topMessage: EngineMessage, totalCount: Int) {
|
|
self.id = id
|
|
self.shortcut = shortcut
|
|
self.topMessage = topMessage
|
|
self.totalCount = totalCount
|
|
}
|
|
|
|
public static func ==(lhs: Item, rhs: Item) -> Bool {
|
|
if lhs === rhs {
|
|
return true
|
|
}
|
|
if lhs.id != rhs.id {
|
|
return false
|
|
}
|
|
if lhs.shortcut != rhs.shortcut {
|
|
return false
|
|
}
|
|
if lhs.topMessage != rhs.topMessage {
|
|
return false
|
|
}
|
|
if lhs.totalCount != rhs.totalCount {
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
}
|
|
|
|
public let items: [Item]
|
|
public let isLoading: Bool
|
|
|
|
public init(items: [Item], isLoading: Bool) {
|
|
self.items = items
|
|
self.isLoading = isLoading
|
|
}
|
|
|
|
public static func ==(lhs: ShortcutMessageList, rhs: ShortcutMessageList) -> Bool {
|
|
if lhs === rhs {
|
|
return true
|
|
}
|
|
if lhs.items != rhs.items {
|
|
return false
|
|
}
|
|
if lhs.isLoading != rhs.isLoading {
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
}
|
|
|
|
func _internal_quickReplyMessageShortcutsState(account: Account) -> Signal<QuickReplyMessageShortcutsState?, NoError> {
|
|
let viewKey: PostboxViewKey = .preferences(keys: Set([PreferencesKeys.shortcutMessages()]))
|
|
return account.postbox.combinedView(keys: [viewKey])
|
|
|> map { views -> QuickReplyMessageShortcutsState? in
|
|
guard let view = views.views[viewKey] as? PreferencesView else {
|
|
return nil
|
|
}
|
|
guard let value = view.values[PreferencesKeys.shortcutMessages()]?.get(QuickReplyMessageShortcutsState.self) else {
|
|
return nil
|
|
}
|
|
return value
|
|
}
|
|
}
|
|
|
|
func _internal_keepShortcutMessagesUpdated(account: Account) -> Signal<Never, NoError> {
|
|
let updateSignal = _internal_shortcutMessageList(account: account, onlyRemote: true)
|
|
|> take(1)
|
|
|> mapToSignal { list -> Signal<Never, NoError> in
|
|
var acc: UInt64 = 0
|
|
for item in list.items {
|
|
guard let itemId = item.id else {
|
|
continue
|
|
}
|
|
combineInt64Hash(&acc, with: UInt64(itemId))
|
|
combineInt64Hash(&acc, with: md5StringHash(item.shortcut))
|
|
combineInt64Hash(&acc, with: UInt64(item.topMessage.id.id))
|
|
|
|
var editTimestamp: Int32 = 0
|
|
inner: for attribute in item.topMessage.attributes {
|
|
if let attribute = attribute as? EditedMessageAttribute {
|
|
editTimestamp = attribute.date
|
|
break inner
|
|
}
|
|
}
|
|
combineInt64Hash(&acc, with: UInt64(editTimestamp))
|
|
}
|
|
|
|
return account.network.request(Api.functions.messages.getQuickReplies(hash: finalizeInt64Hash(acc)))
|
|
|> map(Optional.init)
|
|
|> `catch` { _ -> Signal<Api.messages.QuickReplies?, NoError> in
|
|
return .single(nil)
|
|
}
|
|
|> mapToSignal { result -> Signal<Never, NoError> in
|
|
guard let result else {
|
|
return .complete()
|
|
}
|
|
|
|
return account.postbox.transaction { transaction in
|
|
var state = transaction.getPreferencesEntry(key: PreferencesKeys.shortcutMessages())?.get(QuickReplyMessageShortcutsState.self) ?? QuickReplyMessageShortcutsState(shortcuts: [])
|
|
switch result {
|
|
case let .quickReplies(quickReplies, messages, chats, users):
|
|
let previousShortcuts = state.shortcuts
|
|
state.shortcuts.removeAll()
|
|
|
|
let parsedPeers = AccumulatedPeers(transaction: transaction, chats: chats, users: users)
|
|
updatePeers(transaction: transaction, accountPeerId: account.peerId, peers: parsedPeers)
|
|
|
|
var storeMessages: [StoreMessage] = []
|
|
|
|
for message in messages {
|
|
if let message = StoreMessage(apiMessage: message, accountPeerId: account.peerId, peerIsForum: false) {
|
|
storeMessages.append(message)
|
|
}
|
|
}
|
|
let _ = transaction.addMessages(storeMessages, location: .Random)
|
|
var topMessageIds: [Int32: Int32] = [:]
|
|
|
|
for quickReply in quickReplies {
|
|
switch quickReply {
|
|
case let .quickReply(shortcutId, shortcut, topMessage, _):
|
|
state.shortcuts.append(QuickReplyMessageShortcut(
|
|
id: shortcutId,
|
|
shortcut: shortcut
|
|
))
|
|
topMessageIds[shortcutId] = topMessage
|
|
}
|
|
}
|
|
|
|
if previousShortcuts != state.shortcuts {
|
|
for shortcut in previousShortcuts {
|
|
if let topMessageId = topMessageIds[shortcut.id] {
|
|
//TODO:remove earlier
|
|
let _ = topMessageId
|
|
} else {
|
|
let existingCloudMessages = transaction.getMessagesWithThreadId(peerId: account.peerId, namespace: Namespaces.Message.QuickReplyCloud, threadId: Int64(shortcut.id), from: MessageIndex.lowerBound(peerId: account.peerId, namespace: Namespaces.Message.QuickReplyCloud), includeFrom: false, to: MessageIndex.upperBound(peerId: account.peerId, namespace: Namespaces.Message.QuickReplyCloud), limit: 1000)
|
|
let existingLocalMessages = transaction.getMessagesWithThreadId(peerId: account.peerId, namespace: Namespaces.Message.QuickReplyLocal, threadId: Int64(shortcut.id), from: MessageIndex.lowerBound(peerId: account.peerId, namespace: Namespaces.Message.QuickReplyLocal), includeFrom: false, to: MessageIndex.upperBound(peerId: account.peerId, namespace: Namespaces.Message.QuickReplyLocal), limit: 1000)
|
|
|
|
transaction.deleteMessages(existingCloudMessages.map(\.id), forEachMedia: nil)
|
|
transaction.deleteMessages(existingLocalMessages.map(\.id), forEachMedia: nil)
|
|
}
|
|
}
|
|
}
|
|
case .quickRepliesNotModified:
|
|
break
|
|
}
|
|
|
|
transaction.setPreferencesEntry(key: PreferencesKeys.shortcutMessages(), value: PreferencesEntry(state))
|
|
}
|
|
|> ignoreValues
|
|
}
|
|
}
|
|
|
|
return updateSignal
|
|
}
|
|
|
|
func _internal_shortcutMessageList(account: Account, onlyRemote: Bool) -> Signal<ShortcutMessageList, NoError> {
|
|
let pendingShortcuts: Signal<[String: EngineMessage], NoError>
|
|
if onlyRemote {
|
|
pendingShortcuts = .single([:])
|
|
} else {
|
|
pendingShortcuts = account.postbox.aroundMessageHistoryViewForLocation(.peer(peerId: account.peerId, threadId: nil), anchor: .upperBound, ignoreMessagesInTimestampRange: nil, count: 100, fixedCombinedReadStates: nil, topTaggedMessageIdNamespaces: Set(), tag: nil, appendMessagesFromTheSameGroup: false, namespaces: .just(Set([Namespaces.Message.QuickReplyLocal])), orderStatistics: [])
|
|
|> map { view , _, _ -> [String: EngineMessage] in
|
|
var topMessages: [String: EngineMessage] = [:]
|
|
for entry in view.entries {
|
|
var shortcut: String?
|
|
inner: for attribute in entry.message.attributes {
|
|
if let attribute = attribute as? OutgoingQuickReplyMessageAttribute {
|
|
shortcut = attribute.shortcut
|
|
break inner
|
|
}
|
|
}
|
|
if let shortcut {
|
|
if let currentTopMessage = topMessages[shortcut] {
|
|
if entry.message.index < currentTopMessage.index {
|
|
topMessages[shortcut] = EngineMessage(entry.message)
|
|
}
|
|
} else {
|
|
topMessages[shortcut] = EngineMessage(entry.message)
|
|
}
|
|
}
|
|
}
|
|
return topMessages
|
|
}
|
|
|> distinctUntilChanged
|
|
}
|
|
|
|
return combineLatest(queue: .mainQueue(),
|
|
_internal_quickReplyMessageShortcutsState(account: account) |> distinctUntilChanged,
|
|
pendingShortcuts
|
|
)
|
|
|> mapToSignal { state, pendingShortcuts -> Signal<ShortcutMessageList, NoError> in
|
|
guard let state else {
|
|
return .single(ShortcutMessageList(items: [], isLoading: true))
|
|
}
|
|
|
|
var keys: [PostboxViewKey] = []
|
|
var historyViewKeys: [Int32: PostboxViewKey] = [:]
|
|
var summaryKeys: [Int32: PostboxViewKey] = [:]
|
|
for shortcut in state.shortcuts {
|
|
let historyViewKey: PostboxViewKey = .historyView(PostboxViewKey.HistoryView(
|
|
peerId: account.peerId,
|
|
threadId: Int64(shortcut.id),
|
|
clipHoles: false,
|
|
trackHoles: false,
|
|
anchor: .lowerBound,
|
|
appendMessagesFromTheSameGroup: false,
|
|
namespaces: .just(Set([Namespaces.Message.QuickReplyCloud])),
|
|
count: 10
|
|
))
|
|
historyViewKeys[shortcut.id] = historyViewKey
|
|
keys.append(historyViewKey)
|
|
|
|
let summaryKey: PostboxViewKey = .historyTagSummaryView(tag: [], peerId: account.peerId, threadId: Int64(shortcut.id), namespace: Namespaces.Message.QuickReplyCloud, customTag: nil)
|
|
summaryKeys[shortcut.id] = summaryKey
|
|
keys.append(summaryKey)
|
|
}
|
|
return account.postbox.combinedView(
|
|
keys: keys
|
|
)
|
|
|> map { views -> ShortcutMessageList in
|
|
var items: [ShortcutMessageList.Item] = []
|
|
|
|
for shortcut in state.shortcuts {
|
|
guard let historyViewKey = historyViewKeys[shortcut.id], let historyView = views.views[historyViewKey] as? MessageHistoryView else {
|
|
continue
|
|
}
|
|
|
|
var totalCount = 1
|
|
if let summaryKey = summaryKeys[shortcut.id], let summaryView = views.views[summaryKey] as? MessageHistoryTagSummaryView {
|
|
if let count = summaryView.count {
|
|
totalCount = max(1, Int(count))
|
|
}
|
|
}
|
|
|
|
if let entry = historyView.entries.first {
|
|
items.append(ShortcutMessageList.Item(id: shortcut.id, shortcut: shortcut.shortcut, topMessage: EngineMessage(entry.message), totalCount: totalCount))
|
|
}
|
|
}
|
|
|
|
for (shortcut, message) in pendingShortcuts.sorted(by: { $0.key < $1.key }) {
|
|
if !items.contains(where: { $0.shortcut == shortcut }) {
|
|
items.append(ShortcutMessageList.Item(
|
|
id: nil,
|
|
shortcut: shortcut,
|
|
topMessage: message,
|
|
totalCount: 1
|
|
))
|
|
}
|
|
}
|
|
|
|
return ShortcutMessageList(items: items, isLoading: false)
|
|
}
|
|
|> distinctUntilChanged
|
|
}
|
|
}
|
|
|
|
func _internal_editMessageShortcut(account: Account, id: Int32, shortcut: String) -> Signal<Never, NoError> {
|
|
let remoteApply = account.network.request(Api.functions.messages.editQuickReplyShortcut(shortcutId: id, shortcut: shortcut))
|
|
|> `catch` { _ -> Signal<Api.Bool, NoError> in
|
|
return .single(.boolFalse)
|
|
}
|
|
|> mapToSignal { _ -> Signal<Never, NoError> in
|
|
return .complete()
|
|
}
|
|
|
|
return account.postbox.transaction { transaction in
|
|
var state = transaction.getPreferencesEntry(key: PreferencesKeys.shortcutMessages())?.get(QuickReplyMessageShortcutsState.self) ?? QuickReplyMessageShortcutsState(shortcuts: [])
|
|
if let index = state.shortcuts.firstIndex(where: { $0.id == id }) {
|
|
state.shortcuts[index] = QuickReplyMessageShortcut(id: id, shortcut: shortcut)
|
|
}
|
|
transaction.setPreferencesEntry(key: PreferencesKeys.shortcutMessages(), value: PreferencesEntry(state))
|
|
}
|
|
|> ignoreValues
|
|
|> then(remoteApply)
|
|
}
|
|
|
|
func _internal_deleteMessageShortcuts(account: Account, ids: [Int32]) -> Signal<Never, NoError> {
|
|
return account.postbox.transaction { transaction in
|
|
var state = transaction.getPreferencesEntry(key: PreferencesKeys.shortcutMessages())?.get(QuickReplyMessageShortcutsState.self) ?? QuickReplyMessageShortcutsState(shortcuts: [])
|
|
|
|
for id in ids {
|
|
if let index = state.shortcuts.firstIndex(where: { $0.id == id }) {
|
|
state.shortcuts.remove(at: index)
|
|
}
|
|
}
|
|
transaction.setPreferencesEntry(key: PreferencesKeys.shortcutMessages(), value: PreferencesEntry(state))
|
|
|
|
for id in ids {
|
|
cloudChatAddClearHistoryOperation(transaction: transaction, peerId: account.peerId, threadId: Int64(id), explicitTopMessageId: nil, minTimestamp: nil, maxTimestamp: nil, type: .quickReplyMessages)
|
|
}
|
|
}
|
|
|> ignoreValues
|
|
}
|
|
|
|
func _internal_reorderMessageShortcuts(account: Account, ids: [Int32], localCompletion: @escaping () -> Void) -> Signal<Never, NoError> {
|
|
let remoteApply = account.network.request(Api.functions.messages.reorderQuickReplies(order: ids))
|
|
|> `catch` { _ -> Signal<Api.Bool, NoError> in
|
|
return .single(.boolFalse)
|
|
}
|
|
|> mapToSignal { _ -> Signal<Never, NoError> in
|
|
return .complete()
|
|
}
|
|
|
|
return account.postbox.transaction { transaction in
|
|
var state = transaction.getPreferencesEntry(key: PreferencesKeys.shortcutMessages())?.get(QuickReplyMessageShortcutsState.self) ?? QuickReplyMessageShortcutsState(shortcuts: [])
|
|
|
|
let previousShortcuts = state.shortcuts
|
|
state.shortcuts.removeAll()
|
|
for id in ids {
|
|
if let index = previousShortcuts.firstIndex(where: { $0.id == id }) {
|
|
state.shortcuts.append(previousShortcuts[index])
|
|
}
|
|
}
|
|
for shortcut in previousShortcuts {
|
|
if !state.shortcuts.contains(where: { $0.id == shortcut.id }) {
|
|
state.shortcuts.append(shortcut)
|
|
}
|
|
}
|
|
|
|
transaction.setPreferencesEntry(key: PreferencesKeys.shortcutMessages(), value: PreferencesEntry(state))
|
|
}
|
|
|> ignoreValues
|
|
|> afterCompleted {
|
|
localCompletion()
|
|
}
|
|
|> then(remoteApply)
|
|
}
|
|
|
|
func _internal_sendMessageShortcut(account: Account, peerId: PeerId, id: Int32) -> Signal<Never, NoError> {
|
|
return account.postbox.transaction { transaction -> Peer? in
|
|
return transaction.getPeer(peerId)
|
|
}
|
|
|> mapToSignal { peer -> Signal<Never, NoError> in
|
|
guard let peer, let inputPeer = apiInputPeer(peer) else {
|
|
return .complete()
|
|
}
|
|
return account.network.request(Api.functions.messages.sendQuickReplyMessages(peer: inputPeer, shortcutId: id))
|
|
|> map(Optional.init)
|
|
|> `catch` { _ -> Signal<Api.Updates?, NoError> in
|
|
return .single(nil)
|
|
}
|
|
|> mapToSignal { result -> Signal<Never, NoError> in
|
|
if let result {
|
|
account.stateManager.addUpdates(result)
|
|
}
|
|
return .complete()
|
|
}
|
|
}
|
|
}
|
|
|
|
func _internal_applySentQuickReplyMessage(transaction: Transaction, shortcut: String, quickReplyId: Int32) {
|
|
var state = transaction.getPreferencesEntry(key: PreferencesKeys.shortcutMessages())?.get(QuickReplyMessageShortcutsState.self) ?? QuickReplyMessageShortcutsState(shortcuts: [])
|
|
|
|
if !state.shortcuts.contains(where: { $0.id == quickReplyId }) {
|
|
state.shortcuts.append(QuickReplyMessageShortcut(id: quickReplyId, shortcut: shortcut))
|
|
transaction.setPreferencesEntry(key: PreferencesKeys.shortcutMessages(), value: PreferencesEntry(state))
|
|
}
|
|
}
|
|
|
|
public final class TelegramBusinessRecipients: Codable, Equatable {
|
|
public struct Categories: OptionSet {
|
|
public var rawValue: Int32
|
|
|
|
public init(rawValue: Int32) {
|
|
self.rawValue = rawValue
|
|
}
|
|
|
|
public static let existingChats = Categories(rawValue: 1 << 0)
|
|
public static let newChats = Categories(rawValue: 1 << 1)
|
|
public static let contacts = Categories(rawValue: 1 << 2)
|
|
public static let nonContacts = Categories(rawValue: 1 << 3)
|
|
}
|
|
|
|
private enum CodingKeys: String, CodingKey {
|
|
case categories
|
|
case additionalPeers
|
|
case exclude
|
|
}
|
|
|
|
public let categories: Categories
|
|
public let additionalPeers: Set<PeerId>
|
|
public let exclude: Bool
|
|
|
|
public init(categories: Categories, additionalPeers: Set<PeerId>, exclude: Bool) {
|
|
self.categories = categories
|
|
self.additionalPeers = additionalPeers
|
|
self.exclude = exclude
|
|
}
|
|
|
|
public init(from decoder: Decoder) throws {
|
|
let container = try decoder.container(keyedBy: CodingKeys.self)
|
|
|
|
self.categories = Categories(rawValue: try container.decode(Int32.self, forKey: .categories))
|
|
self.additionalPeers = Set(try container.decode([PeerId].self, forKey: .additionalPeers))
|
|
self.exclude = try container.decode(Bool.self, forKey: .exclude)
|
|
}
|
|
|
|
public func encode(to encoder: Encoder) throws {
|
|
var container = encoder.container(keyedBy: CodingKeys.self)
|
|
|
|
try container.encode(self.categories.rawValue, forKey: .categories)
|
|
try container.encode(Array(self.additionalPeers).sorted(), forKey: .additionalPeers)
|
|
try container.encode(self.exclude, forKey: .exclude)
|
|
}
|
|
|
|
public static func ==(lhs: TelegramBusinessRecipients, rhs: TelegramBusinessRecipients) -> Bool {
|
|
if lhs === rhs {
|
|
return true
|
|
}
|
|
|
|
if lhs.categories != rhs.categories {
|
|
return false
|
|
}
|
|
if lhs.additionalPeers != rhs.additionalPeers {
|
|
return false
|
|
}
|
|
if lhs.exclude != rhs.exclude {
|
|
return false
|
|
}
|
|
|
|
return true
|
|
}
|
|
}
|
|
|
|
public final class TelegramBusinessGreetingMessage: Codable, Equatable {
|
|
private enum CodingKeys: String, CodingKey {
|
|
case shortcutId
|
|
case recipients
|
|
case inactivityDays
|
|
}
|
|
|
|
public let shortcutId: Int32
|
|
public let recipients: TelegramBusinessRecipients
|
|
public let inactivityDays: Int
|
|
|
|
public init(shortcutId: Int32, recipients: TelegramBusinessRecipients, inactivityDays: Int) {
|
|
self.shortcutId = shortcutId
|
|
self.recipients = recipients
|
|
self.inactivityDays = inactivityDays
|
|
}
|
|
|
|
public init(from decoder: Decoder) throws {
|
|
let container = try decoder.container(keyedBy: CodingKeys.self)
|
|
|
|
self.shortcutId = try container.decode(Int32.self, forKey: .shortcutId)
|
|
self.recipients = try container.decode(TelegramBusinessRecipients.self, forKey: .recipients)
|
|
self.inactivityDays = Int(try container.decode(Int32.self, forKey: .inactivityDays))
|
|
}
|
|
|
|
public func encode(to encoder: Encoder) throws {
|
|
var container = encoder.container(keyedBy: CodingKeys.self)
|
|
|
|
try container.encode(self.shortcutId, forKey: .shortcutId)
|
|
try container.encode(self.recipients, forKey: .recipients)
|
|
try container.encode(Int32(clamping: self.inactivityDays), forKey: .inactivityDays)
|
|
}
|
|
|
|
public static func ==(lhs: TelegramBusinessGreetingMessage, rhs: TelegramBusinessGreetingMessage) -> Bool {
|
|
if lhs === rhs {
|
|
return true
|
|
}
|
|
|
|
if lhs.shortcutId != rhs.shortcutId {
|
|
return false
|
|
}
|
|
if lhs.recipients != rhs.recipients {
|
|
return false
|
|
}
|
|
if lhs.inactivityDays != rhs.inactivityDays {
|
|
return false
|
|
}
|
|
|
|
return true
|
|
}
|
|
}
|
|
|
|
extension TelegramBusinessGreetingMessage {
|
|
convenience init(apiGreetingMessage: Api.BusinessGreetingMessage) {
|
|
switch apiGreetingMessage {
|
|
case let .businessGreetingMessage(shortcutId, recipients, noActivityDays):
|
|
self.init(
|
|
shortcutId: shortcutId,
|
|
recipients: TelegramBusinessRecipients(apiValue: recipients),
|
|
inactivityDays: Int(noActivityDays)
|
|
)
|
|
}
|
|
}
|
|
}
|
|
|
|
public final class TelegramBusinessAwayMessage: Codable, Equatable {
|
|
public enum Schedule: Codable, Equatable {
|
|
private enum CodingKeys: String, CodingKey {
|
|
case discriminator
|
|
case customBeginTimestamp
|
|
case customEndTimestamp
|
|
}
|
|
|
|
case always
|
|
case outsideWorkingHours
|
|
case custom(beginTimestamp: Int32, endTimestamp: Int32)
|
|
|
|
public init(from decoder: Decoder) throws {
|
|
let container = try decoder.container(keyedBy: CodingKeys.self)
|
|
|
|
switch try container.decode(Int32.self, forKey: .discriminator) {
|
|
case 0:
|
|
self = .always
|
|
case 1:
|
|
self = .outsideWorkingHours
|
|
case 2:
|
|
self = .custom(beginTimestamp: try container.decode(Int32.self, forKey: .customBeginTimestamp), endTimestamp: try container.decode(Int32.self, forKey: .customEndTimestamp))
|
|
default:
|
|
self = .always
|
|
}
|
|
}
|
|
|
|
public func encode(to encoder: Encoder) throws {
|
|
var container = encoder.container(keyedBy: CodingKeys.self)
|
|
|
|
switch self {
|
|
case .always:
|
|
try container.encode(0 as Int32, forKey: .discriminator)
|
|
case .outsideWorkingHours:
|
|
try container.encode(1 as Int32, forKey: .discriminator)
|
|
case let .custom(beginTimestamp, endTimestamp):
|
|
try container.encode(2 as Int32, forKey: .discriminator)
|
|
try container.encode(beginTimestamp, forKey: .customBeginTimestamp)
|
|
try container.encode(endTimestamp, forKey: .customEndTimestamp)
|
|
}
|
|
}
|
|
}
|
|
|
|
private enum CodingKeys: String, CodingKey {
|
|
case shortcutId
|
|
case recipients
|
|
case schedule
|
|
case sendWhenOffline
|
|
}
|
|
|
|
public let shortcutId: Int32
|
|
public let recipients: TelegramBusinessRecipients
|
|
public let schedule: Schedule
|
|
public let sendWhenOffline: Bool
|
|
|
|
public init(shortcutId: Int32, recipients: TelegramBusinessRecipients, schedule: Schedule, sendWhenOffline: Bool) {
|
|
self.shortcutId = shortcutId
|
|
self.recipients = recipients
|
|
self.schedule = schedule
|
|
self.sendWhenOffline = sendWhenOffline
|
|
}
|
|
|
|
public init(from decoder: Decoder) throws {
|
|
let container = try decoder.container(keyedBy: CodingKeys.self)
|
|
|
|
self.shortcutId = try container.decode(Int32.self, forKey: .shortcutId)
|
|
self.recipients = try container.decode(TelegramBusinessRecipients.self, forKey: .recipients)
|
|
self.schedule = try container.decode(Schedule.self, forKey: .schedule)
|
|
self.sendWhenOffline = try container.decodeIfPresent(Bool.self, forKey: .sendWhenOffline) ?? false
|
|
}
|
|
|
|
public func encode(to encoder: Encoder) throws {
|
|
var container = encoder.container(keyedBy: CodingKeys.self)
|
|
|
|
try container.encode(self.shortcutId, forKey: .shortcutId)
|
|
try container.encode(self.recipients, forKey: .recipients)
|
|
try container.encode(self.schedule, forKey: .schedule)
|
|
try container.encode(self.sendWhenOffline, forKey: .sendWhenOffline)
|
|
}
|
|
|
|
public static func ==(lhs: TelegramBusinessAwayMessage, rhs: TelegramBusinessAwayMessage) -> Bool {
|
|
if lhs === rhs {
|
|
return true
|
|
}
|
|
|
|
if lhs.shortcutId != rhs.shortcutId {
|
|
return false
|
|
}
|
|
if lhs.recipients != rhs.recipients {
|
|
return false
|
|
}
|
|
if lhs.schedule != rhs.schedule {
|
|
return false
|
|
}
|
|
if lhs.sendWhenOffline != rhs.sendWhenOffline {
|
|
return false
|
|
}
|
|
|
|
return true
|
|
}
|
|
}
|
|
|
|
extension TelegramBusinessAwayMessage {
|
|
convenience init(apiAwayMessage: Api.BusinessAwayMessage) {
|
|
switch apiAwayMessage {
|
|
case let .businessAwayMessage(flags, shortcutId, schedule, recipients):
|
|
let mappedSchedule: Schedule
|
|
switch schedule {
|
|
case .businessAwayMessageScheduleAlways:
|
|
mappedSchedule = .always
|
|
case .businessAwayMessageScheduleOutsideWorkHours:
|
|
mappedSchedule = .outsideWorkingHours
|
|
case let .businessAwayMessageScheduleCustom(startDate, endDate):
|
|
mappedSchedule = .custom(beginTimestamp: startDate, endTimestamp: endDate)
|
|
}
|
|
|
|
let sendWhenOffline = (flags & (1 << 0)) != 0
|
|
|
|
self.init(
|
|
shortcutId: shortcutId,
|
|
recipients: TelegramBusinessRecipients(apiValue: recipients),
|
|
schedule: mappedSchedule,
|
|
sendWhenOffline: sendWhenOffline
|
|
)
|
|
}
|
|
}
|
|
}
|
|
|
|
extension TelegramBusinessRecipients {
|
|
convenience init(apiValue: Api.BusinessRecipients) {
|
|
switch apiValue {
|
|
case let .businessRecipients(flags, users):
|
|
var categories: Categories = []
|
|
if (flags & (1 << 0)) != 0 {
|
|
categories.insert(.existingChats)
|
|
}
|
|
if (flags & (1 << 1)) != 0 {
|
|
categories.insert(.newChats)
|
|
}
|
|
if (flags & (1 << 2)) != 0 {
|
|
categories.insert(.contacts)
|
|
}
|
|
if (flags & (1 << 3)) != 0 {
|
|
categories.insert(.nonContacts)
|
|
}
|
|
|
|
self.init(
|
|
categories: categories,
|
|
additionalPeers: Set((users ?? []).map(PeerId.init)),
|
|
exclude: (flags & (1 << 5)) != 0
|
|
)
|
|
}
|
|
}
|
|
|
|
func apiInputValue(additionalPeers: [Peer]) -> Api.InputBusinessRecipients {
|
|
var users: [Api.InputUser]?
|
|
if !additionalPeers.isEmpty {
|
|
users = additionalPeers.compactMap(apiInputUser)
|
|
}
|
|
|
|
var flags: Int32 = 0
|
|
|
|
if self.categories.contains(.existingChats) {
|
|
flags |= 1 << 0
|
|
}
|
|
if self.categories.contains(.newChats) {
|
|
flags |= 1 << 1
|
|
}
|
|
if self.categories.contains(.contacts) {
|
|
flags |= 1 << 2
|
|
}
|
|
if self.categories.contains(.nonContacts) {
|
|
flags |= 1 << 3
|
|
}
|
|
if self.exclude {
|
|
flags |= 1 << 5
|
|
}
|
|
if users != nil {
|
|
flags |= 1 << 4
|
|
}
|
|
|
|
return .inputBusinessRecipients(flags: flags, users: users)
|
|
}
|
|
}
|
|
|
|
func _internal_updateBusinessGreetingMessage(account: Account, greetingMessage: TelegramBusinessGreetingMessage?) -> Signal<Never, NoError> {
|
|
let remoteApply = account.postbox.transaction { transaction -> [Peer] in
|
|
guard let greetingMessage else {
|
|
return []
|
|
}
|
|
return greetingMessage.recipients.additionalPeers.compactMap(transaction.getPeer)
|
|
}
|
|
|> mapToSignal { additionalPeers in
|
|
var mappedMessage: Api.InputBusinessGreetingMessage?
|
|
if let greetingMessage {
|
|
mappedMessage = .inputBusinessGreetingMessage(
|
|
shortcutId: greetingMessage.shortcutId,
|
|
recipients: greetingMessage.recipients.apiInputValue(additionalPeers: additionalPeers),
|
|
noActivityDays: Int32(clamping: greetingMessage.inactivityDays)
|
|
)
|
|
}
|
|
|
|
var flags: Int32 = 0
|
|
if mappedMessage != nil {
|
|
flags |= 1 << 0
|
|
}
|
|
|
|
return account.network.request(Api.functions.account.updateBusinessGreetingMessage(flags: flags, message: mappedMessage))
|
|
|> `catch` { _ -> Signal<Api.Bool, NoError> in
|
|
return .single(.boolFalse)
|
|
}
|
|
|> mapToSignal { _ -> Signal<Never, NoError> in
|
|
return .complete()
|
|
}
|
|
}
|
|
|
|
return account.postbox.transaction { transaction in
|
|
transaction.updatePeerCachedData(peerIds: Set([account.peerId]), update: { _, current in
|
|
var current = (current as? CachedUserData) ?? CachedUserData()
|
|
current = current.withUpdatedGreetingMessage(greetingMessage)
|
|
return current
|
|
})
|
|
}
|
|
|> ignoreValues
|
|
|> then(remoteApply)
|
|
}
|
|
|
|
func _internal_updateBusinessAwayMessage(account: Account, awayMessage: TelegramBusinessAwayMessage?) -> Signal<Never, NoError> {
|
|
let remoteApply = account.postbox.transaction { transaction -> [Peer] in
|
|
guard let awayMessage else {
|
|
return []
|
|
}
|
|
return awayMessage.recipients.additionalPeers.compactMap(transaction.getPeer)
|
|
}
|
|
|> mapToSignal { additionalPeers in
|
|
var mappedMessage: Api.InputBusinessAwayMessage?
|
|
if let awayMessage {
|
|
let mappedSchedule: Api.BusinessAwayMessageSchedule
|
|
switch awayMessage.schedule {
|
|
case .always:
|
|
mappedSchedule = .businessAwayMessageScheduleAlways
|
|
case .outsideWorkingHours:
|
|
mappedSchedule = .businessAwayMessageScheduleOutsideWorkHours
|
|
case let .custom(beginTimestamp, endTimestamp):
|
|
mappedSchedule = .businessAwayMessageScheduleCustom(startDate: beginTimestamp, endDate: endTimestamp)
|
|
}
|
|
|
|
var flags: Int32 = 0
|
|
if awayMessage.sendWhenOffline {
|
|
flags |= 1 << 0
|
|
}
|
|
|
|
mappedMessage = .inputBusinessAwayMessage(
|
|
flags: flags,
|
|
shortcutId: awayMessage.shortcutId,
|
|
schedule: mappedSchedule,
|
|
recipients: awayMessage.recipients.apiInputValue(additionalPeers: additionalPeers)
|
|
)
|
|
}
|
|
|
|
var flags: Int32 = 0
|
|
if mappedMessage != nil {
|
|
flags |= 1 << 0
|
|
}
|
|
|
|
return account.network.request(Api.functions.account.updateBusinessAwayMessage(flags: flags, message: mappedMessage))
|
|
|> `catch` { _ -> Signal<Api.Bool, NoError> in
|
|
return .single(.boolFalse)
|
|
}
|
|
|> mapToSignal { _ -> Signal<Never, NoError> in
|
|
return .complete()
|
|
}
|
|
}
|
|
|
|
return account.postbox.transaction { transaction in
|
|
transaction.updatePeerCachedData(peerIds: Set([account.peerId]), update: { _, current in
|
|
var current = (current as? CachedUserData) ?? CachedUserData()
|
|
current = current.withUpdatedAwayMessage(awayMessage)
|
|
return current
|
|
})
|
|
}
|
|
|> ignoreValues
|
|
|> then(remoteApply)
|
|
}
|
|
|
|
public final class TelegramAccountConnectedBot: Codable, Equatable {
|
|
public let id: PeerId
|
|
public let recipients: TelegramBusinessRecipients
|
|
public let canReply: Bool
|
|
|
|
public init(id: PeerId, recipients: TelegramBusinessRecipients, canReply: Bool) {
|
|
self.id = id
|
|
self.recipients = recipients
|
|
self.canReply = canReply
|
|
}
|
|
|
|
public static func ==(lhs: TelegramAccountConnectedBot, rhs: TelegramAccountConnectedBot) -> Bool {
|
|
if lhs === rhs {
|
|
return true
|
|
}
|
|
if lhs.id != rhs.id {
|
|
return false
|
|
}
|
|
if lhs.recipients != rhs.recipients {
|
|
return false
|
|
}
|
|
if lhs.canReply != rhs.canReply {
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
}
|
|
|
|
public func _internal_setAccountConnectedBot(account: Account, bot: TelegramAccountConnectedBot?) -> Signal<Never, NoError> {
|
|
let remoteApply = account.postbox.transaction { transaction -> (Peer?, [Peer]) in
|
|
guard let bot else {
|
|
return (nil, [])
|
|
}
|
|
return (transaction.getPeer(bot.id), bot.recipients.additionalPeers.compactMap(transaction.getPeer))
|
|
}
|
|
|> mapToSignal { botUser, additionalPeers in
|
|
var flags: Int32 = 0
|
|
var mappedBot: Api.InputUser = .inputUserEmpty
|
|
var mappedRecipients: Api.InputBusinessRecipients = .inputBusinessRecipients(flags: 0, users: nil)
|
|
|
|
if let bot, let inputBotUser = botUser.flatMap(apiInputUser) {
|
|
mappedBot = inputBotUser
|
|
if bot.canReply {
|
|
flags |= 1 << 0
|
|
}
|
|
mappedRecipients = bot.recipients.apiInputValue(additionalPeers: additionalPeers)
|
|
}
|
|
|
|
return account.network.request(Api.functions.account.updateConnectedBot(flags: flags, bot: mappedBot, recipients: mappedRecipients))
|
|
|> map(Optional.init)
|
|
|> `catch` { _ -> Signal<Api.Updates?, NoError> in
|
|
return .single(nil)
|
|
}
|
|
|> mapToSignal { result -> Signal<Never, NoError> in
|
|
if let result {
|
|
account.stateManager.addUpdates(result)
|
|
}
|
|
return .complete()
|
|
}
|
|
}
|
|
|
|
return account.postbox.transaction { transaction in
|
|
transaction.updatePeerCachedData(peerIds: Set([account.peerId]), update: { _, current in
|
|
var current = (current as? CachedUserData) ?? CachedUserData()
|
|
current = current.withUpdatedConnectedBot(bot)
|
|
return current
|
|
})
|
|
}
|
|
|> ignoreValues
|
|
|> then(remoteApply)
|
|
}
|