mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-12-23 22:55:00 +00:00
[WIP] Quick replies
This commit is contained in:
@@ -5,89 +5,12 @@ import TelegramApi
|
||||
import MtProtoKit
|
||||
|
||||
public final class QuickReplyMessageShortcut: Codable, Equatable {
|
||||
private final class CodableMessage: Codable {
|
||||
let message: Message
|
||||
|
||||
init(message: Message) {
|
||||
self.message = message
|
||||
}
|
||||
|
||||
init(from decoder: Decoder) throws {
|
||||
let container = try decoder.container(keyedBy: StringCodingKey.self)
|
||||
|
||||
var media: [Media] = []
|
||||
if let mediaData = try container.decodeIfPresent(Data.self, forKey: "media") {
|
||||
if let value = PostboxDecoder(buffer: MemoryBuffer(data: mediaData)).decodeRootObject() as? Media {
|
||||
media.append(value)
|
||||
}
|
||||
}
|
||||
|
||||
var attributes: [MessageAttribute] = []
|
||||
if let attributesData = try container.decodeIfPresent([Data].self, forKey: "attributes") {
|
||||
for attribute in attributesData {
|
||||
if let value = PostboxDecoder(buffer: MemoryBuffer(data: attribute)).decodeRootObject() as? MessageAttribute {
|
||||
attributes.append(value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.message = Message(
|
||||
stableId: 0,
|
||||
stableVersion: 0,
|
||||
id: MessageId(peerId: PeerId(namespace: PeerId.Namespace._internalFromInt32Value(0), id: PeerId.Id._internalFromInt64Value(0)), namespace: 0, id: 0),
|
||||
globallyUniqueId: nil,
|
||||
groupingKey: nil,
|
||||
groupInfo: nil,
|
||||
threadId: nil,
|
||||
timestamp: 0,
|
||||
flags: [],
|
||||
tags: [],
|
||||
globalTags: [],
|
||||
localTags: [],
|
||||
customTags: [],
|
||||
forwardInfo: nil,
|
||||
author: nil,
|
||||
text: try container.decode(String.self, forKey: "text"),
|
||||
attributes: attributes,
|
||||
media: media,
|
||||
peers: SimpleDictionary(),
|
||||
associatedMessages: SimpleDictionary(),
|
||||
associatedMessageIds: [],
|
||||
associatedMedia: [:],
|
||||
associatedThreadInfo: nil,
|
||||
associatedStories: [:]
|
||||
)
|
||||
}
|
||||
|
||||
func encode(to encoder: Encoder) throws {
|
||||
var container = encoder.container(keyedBy: StringCodingKey.self)
|
||||
|
||||
if let media = self.message.media.first {
|
||||
let mediaEncoder = PostboxEncoder()
|
||||
mediaEncoder.encodeRootObject(media)
|
||||
try container.encode(mediaEncoder.makeData(), forKey: "media")
|
||||
}
|
||||
|
||||
var attributesData: [Data] = []
|
||||
for attribute in self.message.attributes {
|
||||
let attributeEncoder = PostboxEncoder()
|
||||
attributeEncoder.encodeRootObject(attribute)
|
||||
attributesData.append(attributeEncoder.makeData())
|
||||
}
|
||||
try container.encode(attributesData, forKey: "attributes")
|
||||
|
||||
try container.encode(self.message.text, forKey: "text")
|
||||
}
|
||||
}
|
||||
|
||||
public let id: Int32
|
||||
public let shortcut: String
|
||||
public let messages: [EngineMessage]
|
||||
|
||||
public init(id: Int32, shortcut: String, messages: [EngineMessage]) {
|
||||
public init(id: Int32, shortcut: String) {
|
||||
self.id = id
|
||||
self.shortcut = shortcut
|
||||
self.messages = messages
|
||||
}
|
||||
|
||||
public static func ==(lhs: QuickReplyMessageShortcut, rhs: QuickReplyMessageShortcut) -> Bool {
|
||||
@@ -97,9 +20,6 @@ public final class QuickReplyMessageShortcut: Codable, Equatable {
|
||||
if lhs.shortcut != rhs.shortcut {
|
||||
return false
|
||||
}
|
||||
if lhs.messages != rhs.messages {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -108,7 +28,6 @@ public final class QuickReplyMessageShortcut: Codable, Equatable {
|
||||
|
||||
self.id = try container.decode(Int32.self, forKey: "id")
|
||||
self.shortcut = try container.decode(String.self, forKey: "shortcut")
|
||||
self.messages = try container.decode([CodableMessage].self, forKey: "messages").map { EngineMessage($0.message) }
|
||||
}
|
||||
|
||||
public func encode(to encoder: Encoder) throws {
|
||||
@@ -116,42 +35,330 @@ public final class QuickReplyMessageShortcut: Codable, Equatable {
|
||||
|
||||
try container.encode(self.id, forKey: "id")
|
||||
try container.encode(self.shortcut, forKey: "shortcut")
|
||||
try container.encode(self.messages.map { CodableMessage(message: $0._asMessage()) }, forKey: "messages")
|
||||
}
|
||||
}
|
||||
|
||||
public final class QuickReplyMessageShortcutsState: Codable, Equatable {
|
||||
public let shortcuts: [QuickReplyMessageShortcut]
|
||||
struct QuickReplyMessageShortcutsState: Codable, Equatable {
|
||||
var shortcuts: [QuickReplyMessageShortcut]
|
||||
|
||||
public init(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 static func ==(lhs: QuickReplyMessageShortcutsState, rhs: QuickReplyMessageShortcutsState) -> Bool {
|
||||
if lhs.shortcuts != rhs.shortcuts {
|
||||
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_shortcutMessages(account: Account) -> Signal<QuickReplyMessageShortcutsState, NoError> {
|
||||
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
|
||||
|> map { views -> QuickReplyMessageShortcutsState? in
|
||||
guard let view = views.views[viewKey] as? PreferencesView else {
|
||||
return QuickReplyMessageShortcutsState(shortcuts: [])
|
||||
return nil
|
||||
}
|
||||
guard let value = view.values[PreferencesKeys.shortcutMessages()]?.get(QuickReplyMessageShortcutsState.self) else {
|
||||
return QuickReplyMessageShortcutsState(shortcuts: [])
|
||||
return nil
|
||||
}
|
||||
return value
|
||||
}
|
||||
}
|
||||
|
||||
func _internal_updateShortcutMessages(account: Account, state: QuickReplyMessageShortcutsState) -> Signal<Never, NoError> {
|
||||
return account.postbox.transaction { transaction -> Void in
|
||||
func _internal_keepShortcutMessagesUpdated(account: Account) -> Signal<Never, NoError> {
|
||||
let updateSignal = _internal_shortcutMessageList(account: account)
|
||||
|> take(1)
|
||||
|> mapToSignal { list -> Signal<Never, NoError> in
|
||||
var acc: UInt64 = 0
|
||||
for item in list.items {
|
||||
combineInt64Hash(&acc, with: UInt64(item.id))
|
||||
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) -> Signal<ShortcutMessageList, NoError> {
|
||||
return _internal_quickReplyMessageShortcutsState(account: account)
|
||||
|> distinctUntilChanged
|
||||
|> mapToSignal { state -> 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.ScheduledCloud, 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))
|
||||
}
|
||||
}
|
||||
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.insert(QuickReplyMessageShortcut(id: quickReplyId, shortcut: shortcut), at: 0)
|
||||
transaction.setPreferencesEntry(key: PreferencesKeys.shortcutMessages(), value: PreferencesEntry(state))
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user