mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
692 lines
35 KiB
Swift
692 lines
35 KiB
Swift
import Foundation
|
|
import Postbox
|
|
import TelegramApi
|
|
|
|
import SyncCore
|
|
|
|
public func tagsForStoreMessage(incoming: Bool, attributes: [MessageAttribute], media: [Media], textEntities: [MessageTextEntity]?, isPinned: Bool) -> (MessageTags, GlobalMessageTags) {
|
|
var isSecret = false
|
|
var isUnconsumedPersonalMention = false
|
|
for attribute in attributes {
|
|
if let timerAttribute = attribute as? AutoclearTimeoutMessageAttribute {
|
|
if timerAttribute.timeout > 0 && timerAttribute.timeout <= 60 {
|
|
isSecret = true
|
|
}
|
|
} else if let timerAttribute = attribute as? AutoremoveTimeoutMessageAttribute {
|
|
if timerAttribute.timeout > 0 && timerAttribute.timeout <= 60 {
|
|
isSecret = true
|
|
}
|
|
} else if let mentionAttribute = attribute as? ConsumablePersonalMentionMessageAttribute {
|
|
if !mentionAttribute.consumed {
|
|
isUnconsumedPersonalMention = true
|
|
}
|
|
}
|
|
}
|
|
|
|
var tags = MessageTags()
|
|
var globalTags = GlobalMessageTags()
|
|
|
|
if isUnconsumedPersonalMention {
|
|
tags.insert(.unseenPersonalMessage)
|
|
}
|
|
|
|
if isPinned {
|
|
tags.insert(.pinned)
|
|
}
|
|
|
|
for attachment in media {
|
|
if let _ = attachment as? TelegramMediaImage {
|
|
if !isSecret {
|
|
tags.insert(.photoOrVideo)
|
|
tags.insert(.photo)
|
|
}
|
|
} else if let file = attachment as? TelegramMediaFile {
|
|
var refinedTag: MessageTags? = .file
|
|
var isAnimated = false
|
|
inner: for attribute in file.attributes {
|
|
switch attribute {
|
|
case let .Video(_, _, flags):
|
|
if flags.contains(.instantRoundVideo) {
|
|
refinedTag = .voiceOrInstantVideo
|
|
} else {
|
|
if !isSecret {
|
|
refinedTag = [.photoOrVideo, .video]
|
|
} else {
|
|
refinedTag = nil
|
|
}
|
|
}
|
|
case let .Audio(isVoice, _, _, _, _):
|
|
if isVoice {
|
|
refinedTag = .voiceOrInstantVideo
|
|
} else {
|
|
refinedTag = .music
|
|
}
|
|
break inner
|
|
case .Sticker:
|
|
refinedTag = nil
|
|
break inner
|
|
case .Animated:
|
|
isAnimated = true
|
|
default:
|
|
break
|
|
}
|
|
}
|
|
if isAnimated {
|
|
refinedTag = .gif
|
|
}
|
|
if file.isAnimatedSticker {
|
|
refinedTag = nil
|
|
}
|
|
if let refinedTag = refinedTag {
|
|
tags.insert(refinedTag)
|
|
}
|
|
} else if let webpage = attachment as? TelegramMediaWebpage, case .Loaded = webpage.content {
|
|
tags.insert(.webPage)
|
|
} else if let action = attachment as? TelegramMediaAction {
|
|
switch action.action {
|
|
case let .phoneCall(_, discardReason, _, _):
|
|
globalTags.insert(.Calls)
|
|
if incoming, let discardReason = discardReason, case .missed = discardReason {
|
|
globalTags.insert(.MissedCalls)
|
|
}
|
|
default:
|
|
break
|
|
}
|
|
} else if let location = attachment as? TelegramMediaMap, location.liveBroadcastingTimeout != nil {
|
|
tags.insert(.liveLocation)
|
|
}
|
|
}
|
|
if let textEntities = textEntities, !textEntities.isEmpty && !tags.contains(.webPage) {
|
|
for entity in textEntities {
|
|
switch entity.type {
|
|
case .Url, .Email:
|
|
if media.isEmpty || !(media.first is TelegramMediaWebpage) {
|
|
tags.insert(.webPage)
|
|
}
|
|
default:
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
if !incoming {
|
|
assert(true)
|
|
}
|
|
return (tags, globalTags)
|
|
}
|
|
|
|
func apiMessagePeerId(_ messsage: Api.Message) -> PeerId? {
|
|
switch messsage {
|
|
case let .message(message):
|
|
let chatPeerId = message.peerId
|
|
return chatPeerId.peerId
|
|
case let .messageEmpty(_, id, peerId):
|
|
if let peerId = peerId {
|
|
return peerId.peerId
|
|
} else {
|
|
return nil
|
|
}
|
|
case let .messageService(flags, _, fromId, chatPeerId, _, _, _, _):
|
|
return chatPeerId.peerId
|
|
}
|
|
}
|
|
|
|
func apiMessagePeerIds(_ message: Api.Message) -> [PeerId] {
|
|
switch message {
|
|
case let .message(flags, _, fromId, chatPeerId, fwdHeader, viaBotId, _, _, _, media, _, entities, _, _, _, _, _, _, _, _):
|
|
let peerId: PeerId = chatPeerId.peerId
|
|
|
|
var result = [peerId]
|
|
|
|
let resolvedFromId = fromId?.peerId ?? chatPeerId.peerId
|
|
|
|
if resolvedFromId != peerId {
|
|
result.append(resolvedFromId)
|
|
}
|
|
|
|
if let fwdHeader = fwdHeader {
|
|
switch fwdHeader {
|
|
case let .messageFwdHeader(messageFwdHeader):
|
|
if let fromId = messageFwdHeader.fromId {
|
|
result.append(fromId.peerId)
|
|
}
|
|
if let savedFromPeer = messageFwdHeader.savedFromPeer {
|
|
result.append(savedFromPeer.peerId)
|
|
}
|
|
}
|
|
}
|
|
|
|
if let viaBotId = viaBotId {
|
|
result.append(PeerId(namespace: Namespaces.Peer.CloudUser, id: viaBotId))
|
|
}
|
|
|
|
if let media = media {
|
|
switch media {
|
|
case let .messageMediaContact(_, _, _, _, userId):
|
|
if userId != 0 {
|
|
result.append(PeerId(namespace: Namespaces.Peer.CloudUser, id: userId))
|
|
}
|
|
default:
|
|
break
|
|
}
|
|
}
|
|
|
|
if let entities = entities {
|
|
for entity in entities {
|
|
switch entity {
|
|
case let .messageEntityMentionName(_, _, userId):
|
|
result.append(PeerId(namespace: Namespaces.Peer.CloudUser, id: userId))
|
|
default:
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
return result
|
|
case .messageEmpty:
|
|
return []
|
|
case let .messageService(flags, _, fromId, chatPeerId, _, _, action, _):
|
|
let peerId: PeerId = chatPeerId.peerId
|
|
var result = [peerId]
|
|
|
|
let resolvedFromId = fromId?.peerId ?? chatPeerId.peerId
|
|
|
|
if resolvedFromId != peerId {
|
|
result.append(resolvedFromId)
|
|
}
|
|
|
|
switch action {
|
|
case .messageActionChannelCreate, .messageActionChatDeletePhoto, .messageActionChatEditPhoto, .messageActionChatEditTitle, .messageActionEmpty, .messageActionPinMessage, .messageActionHistoryClear, .messageActionGameScore, .messageActionPaymentSent, .messageActionPaymentSentMe, .messageActionPhoneCall, .messageActionScreenshotTaken, .messageActionCustomAction, .messageActionBotAllowed, .messageActionSecureValuesSent, .messageActionSecureValuesSentMe, .messageActionContactSignUp, .messageActionGroupCall:
|
|
break
|
|
case let .messageActionChannelMigrateFrom(_, chatId):
|
|
result.append(PeerId(namespace: Namespaces.Peer.CloudGroup, id: chatId))
|
|
case let .messageActionChatAddUser(users):
|
|
for id in users {
|
|
result.append(PeerId(namespace: Namespaces.Peer.CloudUser, id: id))
|
|
}
|
|
case let .messageActionChatCreate(_, users):
|
|
for id in users {
|
|
result.append(PeerId(namespace: Namespaces.Peer.CloudUser, id: id))
|
|
}
|
|
case let .messageActionChatDeleteUser(userId):
|
|
result.append(PeerId(namespace: Namespaces.Peer.CloudUser, id: userId))
|
|
case let .messageActionChatJoinedByLink(inviterId):
|
|
result.append(PeerId(namespace: Namespaces.Peer.CloudUser, id: inviterId))
|
|
case let .messageActionChatMigrateTo(channelId):
|
|
result.append(PeerId(namespace: Namespaces.Peer.CloudChannel, id: channelId))
|
|
case let .messageActionGeoProximityReached(fromId, toId, _):
|
|
result.append(fromId.peerId)
|
|
result.append(toId.peerId)
|
|
case let .messageActionInviteToGroupCall(_, userIds):
|
|
for id in userIds {
|
|
result.append(PeerId(namespace: Namespaces.Peer.CloudUser, id: id))
|
|
}
|
|
}
|
|
|
|
return result
|
|
}
|
|
}
|
|
|
|
func apiMessageAssociatedMessageIds(_ message: Api.Message) -> [MessageId]? {
|
|
switch message {
|
|
case let .message(_, _, _, chatPeerId, _, _, replyTo, _, _, _, _, _, _, _, _, _, _, _, _, _):
|
|
if let replyTo = replyTo {
|
|
let peerId: PeerId = chatPeerId.peerId
|
|
|
|
switch replyTo {
|
|
case let .messageReplyHeader(_, replyToMsgId, replyToPeerId, _):
|
|
return [MessageId(peerId: replyToPeerId?.peerId ?? peerId, namespace: Namespaces.Message.Cloud, id: replyToMsgId)]
|
|
}
|
|
}
|
|
case .messageEmpty:
|
|
break
|
|
case let .messageService(_, _, _, chatPeerId, replyHeader, _, _, _):
|
|
if let replyHeader = replyHeader {
|
|
switch replyHeader {
|
|
case let .messageReplyHeader(_, replyToMsgId, replyToPeerId, _):
|
|
return [MessageId(peerId: replyToPeerId?.peerId ?? chatPeerId.peerId, namespace: Namespaces.Message.Cloud, id: replyToMsgId)]
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func textMediaAndExpirationTimerFromApiMedia(_ media: Api.MessageMedia?, _ peerId:PeerId) -> (Media?, Int32?) {
|
|
if let media = media {
|
|
switch media {
|
|
case let .messageMediaPhoto(_, photo, ttlSeconds):
|
|
if let photo = photo {
|
|
if let mediaImage = telegramMediaImageFromApiPhoto(photo) {
|
|
return (mediaImage, ttlSeconds)
|
|
}
|
|
} else {
|
|
return (TelegramMediaExpiredContent(data: .image), nil)
|
|
}
|
|
case let .messageMediaContact(phoneNumber, firstName, lastName, vcard, userId):
|
|
let contactPeerId: PeerId? = userId == 0 ? nil : PeerId(namespace: Namespaces.Peer.CloudUser, id: userId)
|
|
let mediaContact = TelegramMediaContact(firstName: firstName, lastName: lastName, phoneNumber: phoneNumber, peerId: contactPeerId, vCardData: vcard.isEmpty ? nil : vcard)
|
|
return (mediaContact, nil)
|
|
case let .messageMediaGeo(geo):
|
|
let mediaMap = telegramMediaMapFromApiGeoPoint(geo, title: nil, address: nil, provider: nil, venueId: nil, venueType: nil, liveBroadcastingTimeout: nil, liveProximityNotificationRadius: nil, heading: nil)
|
|
return (mediaMap, nil)
|
|
case let .messageMediaVenue(geo, title, address, provider, venueId, venueType):
|
|
let mediaMap = telegramMediaMapFromApiGeoPoint(geo, title: title, address: address, provider: provider, venueId: venueId, venueType: venueType, liveBroadcastingTimeout: nil, liveProximityNotificationRadius: nil, heading: nil)
|
|
return (mediaMap, nil)
|
|
case let .messageMediaGeoLive(_, geo, heading, period, proximityNotificationRadius):
|
|
let mediaMap = telegramMediaMapFromApiGeoPoint(geo, title: nil, address: nil, provider: nil, venueId: nil, venueType: nil, liveBroadcastingTimeout: period, liveProximityNotificationRadius: proximityNotificationRadius, heading: heading)
|
|
return (mediaMap, nil)
|
|
case let .messageMediaDocument(_, document, ttlSeconds):
|
|
if let document = document {
|
|
if let mediaFile = telegramMediaFileFromApiDocument(document) {
|
|
return (mediaFile, ttlSeconds)
|
|
}
|
|
} else {
|
|
return (TelegramMediaExpiredContent(data: .file), nil)
|
|
}
|
|
case let .messageMediaWebPage(webpage):
|
|
if let mediaWebpage = telegramMediaWebpageFromApiWebpage(webpage, url: nil) {
|
|
return (mediaWebpage, nil)
|
|
}
|
|
case .messageMediaUnsupported:
|
|
return (TelegramMediaUnsupported(), nil)
|
|
case .messageMediaEmpty:
|
|
break
|
|
case let .messageMediaGame(game):
|
|
return (TelegramMediaGame(apiGame: game), nil)
|
|
case let .messageMediaInvoice(flags, title, description, photo, receiptMsgId, currency, totalAmount, startParam):
|
|
var parsedFlags = TelegramMediaInvoiceFlags()
|
|
if (flags & (1 << 3)) != 0 {
|
|
parsedFlags.insert(.isTest)
|
|
}
|
|
if (flags & (1 << 1)) != 0 {
|
|
parsedFlags.insert(.shippingAddressRequested)
|
|
}
|
|
return (TelegramMediaInvoice(title: title, description: description, photo: photo.flatMap(TelegramMediaWebFile.init), receiptMessageId: receiptMsgId.flatMap { MessageId(peerId: peerId, namespace: Namespaces.Message.Cloud, id: $0) }, currency: currency, totalAmount: totalAmount, startParam: startParam, flags: parsedFlags), nil)
|
|
case let .messageMediaPoll(poll, results):
|
|
switch poll {
|
|
case let .poll(id, flags, question, answers, closePeriod, _):
|
|
let publicity: TelegramMediaPollPublicity
|
|
if (flags & (1 << 1)) != 0 {
|
|
publicity = .public
|
|
} else {
|
|
publicity = .anonymous
|
|
}
|
|
let kind: TelegramMediaPollKind
|
|
if (flags & (1 << 3)) != 0 {
|
|
kind = .quiz
|
|
} else {
|
|
kind = .poll(multipleAnswers: (flags & (1 << 2)) != 0)
|
|
}
|
|
return (TelegramMediaPoll(pollId: MediaId(namespace: Namespaces.Media.CloudPoll, id: id), publicity: publicity, kind: kind, text: question, options: answers.map(TelegramMediaPollOption.init(apiOption:)), correctAnswers: nil, results: TelegramMediaPollResults(apiResults: results), isClosed: (flags & (1 << 0)) != 0, deadlineTimeout: closePeriod), nil)
|
|
}
|
|
case let .messageMediaDice(value, emoticon):
|
|
return (TelegramMediaDice(emoji: emoticon, value: value), nil)
|
|
}
|
|
}
|
|
|
|
return (nil, nil)
|
|
}
|
|
|
|
func messageTextEntitiesFromApiEntities(_ entities: [Api.MessageEntity]) -> [MessageTextEntity] {
|
|
var result: [MessageTextEntity] = []
|
|
for entity in entities {
|
|
switch entity {
|
|
case .messageEntityUnknown, .inputMessageEntityMentionName:
|
|
break
|
|
case let .messageEntityMention(offset, length):
|
|
result.append(MessageTextEntity(range: Int(offset) ..< Int(offset + length), type: .Mention))
|
|
case let .messageEntityHashtag(offset, length):
|
|
result.append(MessageTextEntity(range: Int(offset) ..< Int(offset + length), type: .Hashtag))
|
|
case let .messageEntityBotCommand(offset, length):
|
|
result.append(MessageTextEntity(range: Int(offset) ..< Int(offset + length), type: .BotCommand))
|
|
case let .messageEntityUrl(offset, length):
|
|
result.append(MessageTextEntity(range: Int(offset) ..< Int(offset + length), type: .Url))
|
|
case let .messageEntityEmail(offset, length):
|
|
result.append(MessageTextEntity(range: Int(offset) ..< Int(offset + length), type: .Email))
|
|
case let .messageEntityBold(offset, length):
|
|
result.append(MessageTextEntity(range: Int(offset) ..< Int(offset + length), type: .Bold))
|
|
case let .messageEntityItalic(offset, length):
|
|
result.append(MessageTextEntity(range: Int(offset) ..< Int(offset + length), type: .Italic))
|
|
case let .messageEntityCode(offset, length):
|
|
result.append(MessageTextEntity(range: Int(offset) ..< Int(offset + length), type: .Code))
|
|
case let .messageEntityPre(offset, length, _):
|
|
result.append(MessageTextEntity(range: Int(offset) ..< Int(offset + length), type: .Pre))
|
|
case let .messageEntityTextUrl(offset, length, url):
|
|
result.append(MessageTextEntity(range: Int(offset) ..< Int(offset + length), type: .TextUrl(url: url)))
|
|
case let .messageEntityMentionName(offset, length, userId):
|
|
result.append(MessageTextEntity(range: Int(offset) ..< Int(offset + length), type: .TextMention(peerId: PeerId(namespace: Namespaces.Peer.CloudUser, id: userId))))
|
|
case let .messageEntityPhone(offset, length):
|
|
result.append(MessageTextEntity(range: Int(offset) ..< Int(offset + length), type: .PhoneNumber))
|
|
case let .messageEntityCashtag(offset, length):
|
|
result.append(MessageTextEntity(range: Int(offset) ..< Int(offset + length), type: .Hashtag))
|
|
case let .messageEntityUnderline(offset, length):
|
|
result.append(MessageTextEntity(range: Int(offset) ..< Int(offset + length), type: .Underline))
|
|
case let .messageEntityStrike(offset, length):
|
|
result.append(MessageTextEntity(range: Int(offset) ..< Int(offset + length), type: .Strikethrough))
|
|
case let .messageEntityBlockquote(offset, length):
|
|
result.append(MessageTextEntity(range: Int(offset) ..< Int(offset + length), type: .BlockQuote))
|
|
case let .messageEntityBankCard(offset, length):
|
|
result.append(MessageTextEntity(range: Int(offset) ..< Int(offset + length), type: .BankCard))
|
|
}
|
|
}
|
|
return result
|
|
}
|
|
|
|
extension StoreMessage {
|
|
convenience init?(apiMessage: Api.Message, namespace: MessageId.Namespace = Namespaces.Message.Cloud) {
|
|
switch apiMessage {
|
|
case let .message(flags, id, fromId, chatPeerId, fwdFrom, viaBotId, replyTo, date, message, media, replyMarkup, entities, views, forwards, replies, editDate, postAuthor, groupingId, restrictionReason, ttlPeriod):
|
|
let resolvedFromId = fromId?.peerId ?? chatPeerId.peerId
|
|
|
|
let peerId: PeerId
|
|
var authorId: PeerId?
|
|
switch chatPeerId {
|
|
case .peerUser:
|
|
peerId = chatPeerId.peerId
|
|
authorId = resolvedFromId
|
|
case let .peerChat(chatId):
|
|
peerId = PeerId(namespace: Namespaces.Peer.CloudGroup, id: chatId)
|
|
authorId = resolvedFromId
|
|
case let .peerChannel(channelId):
|
|
peerId = PeerId(namespace: Namespaces.Peer.CloudChannel, id: channelId)
|
|
authorId = resolvedFromId
|
|
}
|
|
|
|
var attributes: [MessageAttribute] = []
|
|
|
|
var threadId: Int64?
|
|
if let replyTo = replyTo {
|
|
var threadMessageId: MessageId?
|
|
switch replyTo {
|
|
case let .messageReplyHeader(_, replyToMsgId, replyToPeerId, replyToTopId):
|
|
let replyPeerId = replyToPeerId?.peerId ?? peerId
|
|
if let replyToTopId = replyToTopId {
|
|
let threadIdValue = MessageId(peerId: replyPeerId, namespace: Namespaces.Message.Cloud, id: replyToTopId)
|
|
threadMessageId = threadIdValue
|
|
if replyPeerId == peerId {
|
|
threadId = makeMessageThreadId(threadIdValue)
|
|
}
|
|
} else if peerId.namespace == Namespaces.Peer.CloudChannel {
|
|
let threadIdValue = MessageId(peerId: replyPeerId, namespace: Namespaces.Message.Cloud, id: replyToMsgId)
|
|
threadMessageId = threadIdValue
|
|
threadId = makeMessageThreadId(threadIdValue)
|
|
}
|
|
attributes.append(ReplyMessageAttribute(messageId: MessageId(peerId: replyPeerId, namespace: Namespaces.Message.Cloud, id: replyToMsgId), threadMessageId: threadMessageId))
|
|
}
|
|
}
|
|
|
|
var forwardInfo: StoreMessageForwardInfo?
|
|
if let fwdFrom = fwdFrom {
|
|
switch fwdFrom {
|
|
case let .messageFwdHeader(flags, fromId, fromName, date, channelPost, postAuthor, savedFromPeer, savedFromMsgId, psaType):
|
|
var forwardInfoFlags: MessageForwardInfo.Flags = []
|
|
let isImported = (flags & (1 << 7)) != 0
|
|
if isImported {
|
|
forwardInfoFlags.insert(.isImported)
|
|
}
|
|
|
|
var authorId: PeerId?
|
|
var sourceId: PeerId?
|
|
var sourceMessageId: MessageId?
|
|
|
|
if let fromId = fromId {
|
|
switch fromId {
|
|
case .peerChannel:
|
|
let peerId = fromId.peerId
|
|
sourceId = peerId
|
|
|
|
if let channelPost = channelPost {
|
|
sourceMessageId = MessageId(peerId: peerId, namespace: Namespaces.Message.Cloud, id: channelPost)
|
|
}
|
|
default:
|
|
authorId = fromId.peerId
|
|
}
|
|
}
|
|
|
|
if let savedFromPeer = savedFromPeer, let savedFromMsgId = savedFromMsgId {
|
|
let peerId: PeerId
|
|
switch savedFromPeer {
|
|
case let .peerChannel(channelId):
|
|
peerId = PeerId(namespace: Namespaces.Peer.CloudChannel, id: channelId)
|
|
case let .peerChat(chatId):
|
|
peerId = PeerId(namespace: Namespaces.Peer.CloudGroup, id: chatId)
|
|
case let .peerUser(userId):
|
|
peerId = PeerId(namespace: Namespaces.Peer.CloudUser, id: userId)
|
|
}
|
|
let messageId: MessageId = MessageId(peerId: peerId, namespace: Namespaces.Message.Cloud, id: savedFromMsgId)
|
|
attributes.append(SourceReferenceMessageAttribute(messageId: messageId))
|
|
}
|
|
|
|
if let authorId = authorId {
|
|
forwardInfo = StoreMessageForwardInfo(authorId: authorId, sourceId: sourceId, sourceMessageId: sourceMessageId, date: date, authorSignature: postAuthor, psaType: psaType, flags: forwardInfoFlags)
|
|
} else if let sourceId = sourceId {
|
|
forwardInfo = StoreMessageForwardInfo(authorId: sourceId, sourceId: sourceId, sourceMessageId: sourceMessageId, date: date, authorSignature: postAuthor, psaType: psaType, flags: forwardInfoFlags)
|
|
} else if let postAuthor = postAuthor ?? fromName {
|
|
forwardInfo = StoreMessageForwardInfo(authorId: nil, sourceId: nil, sourceMessageId: sourceMessageId, date: date, authorSignature: postAuthor, psaType: psaType, flags: forwardInfoFlags)
|
|
}
|
|
}
|
|
}
|
|
|
|
let messageText = message
|
|
var medias: [Media] = []
|
|
|
|
var consumableContent: (Bool, Bool)? = nil
|
|
|
|
if let media = media {
|
|
let (mediaValue, expirationTimer) = textMediaAndExpirationTimerFromApiMedia(media, peerId)
|
|
if let mediaValue = mediaValue {
|
|
medias.append(mediaValue)
|
|
|
|
if let expirationTimer = expirationTimer, expirationTimer > 0 {
|
|
attributes.append(AutoclearTimeoutMessageAttribute(timeout: expirationTimer, countdownBeginTime: nil))
|
|
|
|
consumableContent = (true, false)
|
|
}
|
|
}
|
|
}
|
|
|
|
if let ttlPeriod = ttlPeriod {
|
|
attributes.append(AutoremoveTimeoutMessageAttribute(timeout: ttlPeriod, countdownBeginTime: date))
|
|
}
|
|
|
|
if let postAuthor = postAuthor {
|
|
attributes.append(AuthorSignatureMessageAttribute(signature: postAuthor))
|
|
}
|
|
|
|
for case let file as TelegramMediaFile in medias {
|
|
if peerId.namespace == Namespaces.Peer.CloudUser || peerId.namespace == Namespaces.Peer.CloudGroup || peerId.namespace == Namespaces.Peer.CloudChannel {
|
|
if file.isVoice {
|
|
consumableContent = (true, (flags & (1 << 5)) == 0)
|
|
break
|
|
} else if file.isInstantVideo {
|
|
consumableContent = (true, (flags & (1 << 5)) == 0)
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
if let (value, consumed) = consumableContent, value {
|
|
attributes.append(ConsumableContentMessageAttribute(consumed: consumed))
|
|
}
|
|
|
|
if let viaBotId = viaBotId {
|
|
attributes.append(InlineBotMessageAttribute(peerId: PeerId(namespace: Namespaces.Peer.CloudUser, id: viaBotId), title: nil))
|
|
}
|
|
|
|
if namespace != Namespaces.Message.ScheduledCloud {
|
|
if let views = views {
|
|
attributes.append(ViewCountMessageAttribute(count: Int(views)))
|
|
}
|
|
|
|
if let forwards = forwards {
|
|
attributes.append(ForwardCountMessageAttribute(count: Int(forwards)))
|
|
}
|
|
}
|
|
|
|
if let editDate = editDate {
|
|
attributes.append(EditedMessageAttribute(date: editDate, isHidden: (flags & (1 << 21)) != 0))
|
|
}
|
|
|
|
var entitiesAttribute: TextEntitiesMessageAttribute?
|
|
if let entities = entities, !entities.isEmpty {
|
|
let attribute = TextEntitiesMessageAttribute(entities: messageTextEntitiesFromApiEntities(entities))
|
|
entitiesAttribute = attribute
|
|
attributes.append(attribute)
|
|
} else {
|
|
var noEntities = false
|
|
loop: for media in medias {
|
|
switch media {
|
|
case _ as TelegramMediaContact,
|
|
_ as TelegramMediaMap:
|
|
noEntities = true
|
|
break loop
|
|
default:
|
|
break
|
|
}
|
|
}
|
|
if !noEntities {
|
|
let attribute = TextEntitiesMessageAttribute(entities: [])
|
|
entitiesAttribute = attribute
|
|
attributes.append(attribute)
|
|
}
|
|
}
|
|
|
|
if (flags & (1 << 17)) != 0 {
|
|
attributes.append(ContentRequiresValidationMessageAttribute())
|
|
}
|
|
|
|
/*if let reactions = reactions {
|
|
attributes.append(ReactionsMessageAttribute(apiReactions: reactions))
|
|
}*/
|
|
|
|
if let replies = replies {
|
|
let recentRepliersPeerIds: [PeerId]?
|
|
switch replies {
|
|
case let .messageReplies(_, repliesCount, _, recentRepliers, channelId, maxId, readMaxId):
|
|
if let recentRepliers = recentRepliers {
|
|
recentRepliersPeerIds = recentRepliers.map { $0.peerId }
|
|
} else {
|
|
recentRepliersPeerIds = nil
|
|
}
|
|
|
|
let commentsPeerId = channelId.flatMap { PeerId(namespace: Namespaces.Peer.CloudChannel, id: $0) }
|
|
|
|
attributes.append(ReplyThreadMessageAttribute(count: repliesCount, latestUsers: recentRepliersPeerIds ?? [], commentsPeerId: commentsPeerId, maxMessageId: maxId, maxReadMessageId: readMaxId))
|
|
}
|
|
}
|
|
|
|
if let restrictionReason = restrictionReason {
|
|
attributes.append(RestrictedContentMessageAttribute(rules: restrictionReason.map(RestrictionRule.init(apiReason:))))
|
|
}
|
|
|
|
var storeFlags = StoreMessageFlags()
|
|
|
|
if let replyMarkup = replyMarkup {
|
|
let parsedReplyMarkup = ReplyMarkupMessageAttribute(apiMarkup: replyMarkup)
|
|
attributes.append(parsedReplyMarkup)
|
|
if !parsedReplyMarkup.flags.contains(.inline) {
|
|
storeFlags.insert(.TopIndexable)
|
|
}
|
|
}
|
|
|
|
if (flags & (1 << 1)) == 0 {
|
|
storeFlags.insert(.Incoming)
|
|
}
|
|
|
|
if (flags & (1 << 18)) != 0 {
|
|
storeFlags.insert(.WasScheduled)
|
|
storeFlags.insert(.CountedAsIncoming)
|
|
}
|
|
|
|
if (flags & (1 << 4)) != 0 || (flags & (1 << 13)) != 0 {
|
|
var notificationFlags: NotificationInfoMessageAttributeFlags = []
|
|
if (flags & (1 << 4)) != 0 {
|
|
notificationFlags.insert(.personal)
|
|
let notConsumed = (flags & (1 << 5)) != 0
|
|
attributes.append(ConsumablePersonalMentionMessageAttribute(consumed: !notConsumed, pending: false))
|
|
}
|
|
if (flags & (1 << 13)) != 0 {
|
|
notificationFlags.insert(.muted)
|
|
}
|
|
attributes.append(NotificationInfoMessageAttribute(flags: notificationFlags))
|
|
}
|
|
|
|
let isPinned = (flags & (1 << 24)) != 0
|
|
|
|
let (tags, globalTags) = tagsForStoreMessage(incoming: storeFlags.contains(.Incoming), attributes: attributes, media: medias, textEntities: entitiesAttribute?.entities, isPinned: isPinned)
|
|
|
|
storeFlags.insert(.CanBeGroupedIntoFeed)
|
|
|
|
self.init(id: MessageId(peerId: peerId, namespace: namespace, id: id), globallyUniqueId: nil, groupingKey: groupingId, threadId: threadId, timestamp: date, flags: storeFlags, tags: tags, globalTags: globalTags, localTags: [], forwardInfo: forwardInfo, authorId: authorId, text: messageText, attributes: attributes, media: medias)
|
|
case .messageEmpty:
|
|
return nil
|
|
case let .messageService(flags, id, fromId, chatPeerId, replyTo, date, action, ttlPeriod):
|
|
let peerId: PeerId = chatPeerId.peerId
|
|
let authorId: PeerId? = fromId?.peerId ?? chatPeerId.peerId
|
|
|
|
var attributes: [MessageAttribute] = []
|
|
|
|
var threadId: Int64?
|
|
if let replyTo = replyTo {
|
|
var threadMessageId: MessageId?
|
|
switch replyTo {
|
|
case let .messageReplyHeader(_, replyToMsgId, replyToPeerId, replyToTopId):
|
|
let replyPeerId = replyToPeerId?.peerId ?? peerId
|
|
if let replyToTopId = replyToTopId {
|
|
let threadIdValue = MessageId(peerId: replyPeerId, namespace: Namespaces.Message.Cloud, id: replyToTopId)
|
|
threadMessageId = threadIdValue
|
|
if replyPeerId == peerId {
|
|
threadId = makeMessageThreadId(threadIdValue)
|
|
}
|
|
}
|
|
attributes.append(ReplyMessageAttribute(messageId: MessageId(peerId: replyPeerId, namespace: Namespaces.Message.Cloud, id: replyToMsgId), threadMessageId: threadMessageId))
|
|
}
|
|
}
|
|
|
|
if (flags & (1 << 17)) != 0 {
|
|
attributes.append(ContentRequiresValidationMessageAttribute())
|
|
}
|
|
|
|
var storeFlags = StoreMessageFlags()
|
|
if (flags & 2) == 0 {
|
|
let _ = storeFlags.insert(.Incoming)
|
|
}
|
|
|
|
if (flags & (1 << 4)) != 0 || (flags & (1 << 13)) != 0 {
|
|
var notificationFlags: NotificationInfoMessageAttributeFlags = []
|
|
if (flags & (1 << 4)) != 0 {
|
|
notificationFlags.insert(.personal)
|
|
}
|
|
if (flags & (1 << 4)) != 0 {
|
|
notificationFlags.insert(.personal)
|
|
let notConsumed = (flags & (1 << 5)) != 0
|
|
attributes.append(ConsumablePersonalMentionMessageAttribute(consumed: !notConsumed, pending: false))
|
|
}
|
|
if (flags & (1 << 13)) != 0 {
|
|
notificationFlags.insert(.muted)
|
|
}
|
|
attributes.append(NotificationInfoMessageAttribute(flags: notificationFlags))
|
|
}
|
|
|
|
var media: [Media] = []
|
|
if let action = telegramMediaActionFromApiAction(action) {
|
|
media.append(action)
|
|
}
|
|
|
|
if let ttlPeriod = ttlPeriod {
|
|
attributes.append(AutoremoveTimeoutMessageAttribute(timeout: ttlPeriod, countdownBeginTime: date))
|
|
}
|
|
|
|
let (tags, globalTags) = tagsForStoreMessage(incoming: storeFlags.contains(.Incoming), attributes: attributes, media: media, textEntities: nil, isPinned: false)
|
|
|
|
storeFlags.insert(.CanBeGroupedIntoFeed)
|
|
|
|
if (flags & (1 << 18)) != 0 {
|
|
storeFlags.insert(.WasScheduled)
|
|
}
|
|
|
|
self.init(id: MessageId(peerId: peerId, namespace: namespace, id: id), globallyUniqueId: nil, groupingKey: nil, threadId: threadId, timestamp: date, flags: storeFlags, tags: tags, globalTags: globalTags, localTags: [], forwardInfo: nil, authorId: authorId, text: "", attributes: attributes, media: media)
|
|
}
|
|
}
|
|
}
|