Merge commit '7621e2f8dec938cf48181c8b10afc9b01f444e68' into beta

This commit is contained in:
Ilya Laktyushin
2025-12-06 02:17:48 +04:00
commit 8344b97e03
28070 changed files with 7995182 additions and 0 deletions
@@ -0,0 +1,267 @@
import Foundation
import SwiftSignalKit
import Postbox
final class AutomaticCacheEvictionContext {
private final class Impl {
private struct CombinedSettings: Equatable {
var categoryStorageTimeout: [CacheStorageSettings.PeerStorageCategory: Int32]
var exceptions: [AccountSpecificCacheStorageSettings.Value]
}
let queue: Queue
let processingQueue: Queue
let accountManager: AccountManager<TelegramAccountManagerTypes>
let postbox: Postbox
var settingsDisposable: Disposable?
var processDisposable: Disposable?
init(queue: Queue, accountManager: AccountManager<TelegramAccountManagerTypes>, postbox: Postbox) {
self.queue = queue
self.processingQueue = Queue(name: "AutomaticCacheEviction-Processing", qos: .background)
self.accountManager = accountManager
self.postbox = postbox
self.start()
}
deinit {
self.settingsDisposable?.dispose()
self.processDisposable?.dispose()
}
func start() {
self.settingsDisposable?.dispose()
self.processDisposable?.dispose()
let cacheSettings = self.accountManager.sharedData(keys: [SharedDataKeys.cacheStorageSettings])
|> map { sharedData -> CacheStorageSettings in
let cacheSettings: CacheStorageSettings
if let value = sharedData.entries[SharedDataKeys.cacheStorageSettings]?.get(CacheStorageSettings.self) {
cacheSettings = value
} else {
cacheSettings = CacheStorageSettings.defaultSettings
}
return cacheSettings
}
let viewKey: PostboxViewKey = .preferences(keys: Set([PreferencesKeys.accountSpecificCacheStorageSettings]))
let accountSpecificSettings = self.postbox.combinedView(keys: [viewKey])
|> map { views -> AccountSpecificCacheStorageSettings in
let cacheSettings: AccountSpecificCacheStorageSettings
if let view = views.views[viewKey] as? PreferencesView, let value = view.values[PreferencesKeys.accountSpecificCacheStorageSettings]?.get(AccountSpecificCacheStorageSettings.self) {
cacheSettings = value
} else {
cacheSettings = AccountSpecificCacheStorageSettings.defaultSettings
}
return cacheSettings
}
self.settingsDisposable = (combineLatest(queue: self.queue,
cacheSettings,
accountSpecificSettings
)
|> map { cacheSettings, accountSpecificSettings -> CombinedSettings in
return CombinedSettings(
categoryStorageTimeout: cacheSettings.categoryStorageTimeout,
exceptions: accountSpecificSettings.peerStorageTimeoutExceptions
)
}
|> distinctUntilChanged
|> deliverOn(self.queue)).start(next: { [weak self] combinedSettings in
self?.restart(settings: combinedSettings)
})
}
private func restart(settings: CombinedSettings) {
self.processDisposable?.dispose()
let processingQueue = self.processingQueue
let postbox = self.postbox
let mediaBox = self.postbox.mediaBox
let _ = processingQueue
let _ = mediaBox
self.processDisposable = (self.postbox.mediaBox.storageBox.allPeerIds()
|> mapToSignal { peerIds -> Signal<Never, NoError> in
return postbox.transaction { transaction -> [PeerId: CacheStorageSettings.PeerStorageCategory] in
var channelCategoryMapping: [PeerId: CacheStorageSettings.PeerStorageCategory] = [:]
for peerId in peerIds {
if peerId.namespace == Namespaces.Peer.CloudChannel {
var category: CacheStorageSettings.PeerStorageCategory = .channels
if let peer = transaction.getPeer(peerId) as? TelegramChannel, case .group = peer.info {
category = .groups
}
channelCategoryMapping[peerId] = category
}
}
return channelCategoryMapping
}
|> mapToSignal { channelCategoryMapping -> Signal<Never, NoError> in
var signals: Signal<Never, NoError> = .complete()
let listSignal = Signal<PeerId, NoError> { subscriber in
for peerId in peerIds {
subscriber.putNext(peerId)
}
subscriber.putCompletion()
return EmptyDisposable
}
signals = listSignal |> mapToQueue { peerId -> Signal<Never, NoError> in
let timeout: Int32
if let value = settings.exceptions.first(where: { $0.key == peerId }) {
timeout = value.value
} else {
switch peerId.namespace {
case Namespaces.Peer.CloudUser, Namespaces.Peer.SecretChat:
timeout = settings.categoryStorageTimeout[.privateChats] ?? Int32.max
case Namespaces.Peer.CloudGroup:
timeout = settings.categoryStorageTimeout[.groups] ?? Int32.max
default:
if let category = channelCategoryMapping[peerId], case .groups = category {
timeout = settings.categoryStorageTimeout[.groups] ?? Int32.max
} else {
timeout = settings.categoryStorageTimeout[.channels] ?? Int32.max
}
}
}
if timeout == Int32.max {
return .complete()
}
let minPeerTimestamp = Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970) - timeout
//let minPeerTimestamp = Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970)
let allSignal = mediaBox.storageBox.all(peerId: peerId, excludeType: MediaResourceUserContentType.story.rawValue)
|> mapToSignal { peerResourceIds -> Signal<Never, NoError> in
return Signal { subscriber in
var isCancelled = false
processingQueue.justDispatch {
var removeIds: [MediaResourceId] = []
var removeRawIds: [Data] = []
var localCounter = 0
for resourceId in peerResourceIds {
localCounter += 1
if localCounter % 100 == 0 {
if isCancelled {
subscriber.putCompletion()
return
}
}
removeRawIds.append(resourceId)
let id = MediaResourceId(String(data: resourceId, encoding: .utf8)!)
let resourceTimestamp = mediaBox.resourceUsageWithInfo(id: id)
if resourceTimestamp != 0 && resourceTimestamp < minPeerTimestamp {
removeIds.append(id)
}
}
if !removeIds.isEmpty {
Logger.shared.log("AutomaticCacheEviction", "peer \(peerId): cleaning \(removeIds.count) resources")
let _ = mediaBox.removeCachedResourcesWithResult(removeIds).start(next: { actualIds in
var actualRawIds: [Data] = []
for id in actualIds {
if let data = id.stringRepresentation.data(using: .utf8) {
actualRawIds.append(data)
}
}
mediaBox.storageBox.remove(ids: actualRawIds)
subscriber.putCompletion()
})
} else {
subscriber.putCompletion()
}
}
return ActionDisposable {
isCancelled = true
}
}
}
let storySignal = mediaBox.storageBox.all(peerId: peerId, onlyType: MediaResourceUserContentType.story.rawValue)
|> mapToSignal { peerResourceIds -> Signal<Never, NoError> in
return Signal { subscriber in
var isCancelled = false
processingQueue.justDispatch {
var removeIds: [MediaResourceId] = []
var removeRawIds: [Data] = []
var localCounter = 0
for resourceId in peerResourceIds {
localCounter += 1
if localCounter % 100 == 0 {
if isCancelled {
subscriber.putCompletion()
return
}
}
removeRawIds.append(resourceId)
let id = MediaResourceId(String(data: resourceId, encoding: .utf8)!)
let resourceTimestamp = mediaBox.resourceUsageWithInfo(id: id)
if resourceTimestamp != 0 && resourceTimestamp < minPeerTimestamp {
removeIds.append(id)
}
}
if !removeIds.isEmpty {
Logger.shared.log("AutomaticCacheEviction", "peer \(peerId): cleaning \(removeIds.count) resources")
let _ = mediaBox.removeCachedResourcesWithResult(removeIds).start(next: { actualIds in
var actualRawIds: [Data] = []
for id in actualIds {
if let data = id.stringRepresentation.data(using: .utf8) {
actualRawIds.append(data)
}
}
mediaBox.storageBox.remove(ids: actualRawIds)
subscriber.putCompletion()
})
} else {
subscriber.putCompletion()
}
}
return ActionDisposable {
isCancelled = true
}
}
}
return allSignal |> then(storySignal)
}
return signals
}
}).start()
}
}
private let queue: Queue
private let impl: QueueLocalObject<Impl>
init(postbox: Postbox, accountManager: AccountManager<TelegramAccountManagerTypes>) {
let queue = Queue(name: "AutomaticCacheEviction")
self.queue = queue
self.impl = QueueLocalObject(queue: queue, generate: {
return Impl(queue: queue, accountManager: accountManager, postbox: postbox)
})
}
}
@@ -0,0 +1,21 @@
import Foundation
import Postbox
// Incuding at least one Objective-C class in a swift file ensures that it doesn't get stripped by the linker
private final class LinkHelperClass: NSObject {
}
public func canSendMessagesToPeer(_ peer: Peer, ignoreDefault: Bool = false) -> Bool {
if let peer = peer as? TelegramUser, peer.addressName == "replies" {
return false
} else if peer is TelegramUser || peer is TelegramGroup {
return !peer.isDeleted
} else if let peer = peer as? TelegramSecretChat {
return peer.embeddedState == .active
} else if let peer = peer as? TelegramChannel {
return peer.hasPermission(.sendSomething, ignoreDefault: ignoreDefault)
} else {
return false
}
}
@@ -0,0 +1,14 @@
import Foundation
import Postbox
public final class EngineEncoder {
public static func encode(_ value: Encodable) throws -> Data {
return try AdaptedPostboxEncoder().encode(value)
}
}
public final class EngineDecoder {
public static func decode<T>(_ type: T.Type, from data: Data) throws -> T where T : Decodable {
return try AdaptedPostboxDecoder().decode(type, from: data)
}
}
@@ -0,0 +1,16 @@
import Foundation
import Postbox
public func decryptedResourceData(data: MediaResourceData, resource: MediaResource, params: Any) -> Data? {
guard data.complete else {
return nil
}
guard let data = try? Data(contentsOf: URL(fileURLWithPath: data.path), options: [.mappedRead]) else {
return nil
}
if let resource = resource as? EncryptedMediaResource {
return resource.decrypt(data: data, params: params)
} else {
return data
}
}
@@ -0,0 +1,76 @@
import Foundation
import FlatBuffers
import FlatSerialization
import Postbox
#if DEBUG
public var flatBuffers_checkedGet: Bool = true
#else
public var flatBuffers_checkedGet: Bool = false
#endif
@inline(__always)
public func FlatBuffers_getRoot<T: FlatBufferObject & Verifiable>(
byteBuffer: inout ByteBuffer,
fileId: String? = nil,
options: VerifierOptions = .init()
) -> T {
if flatBuffers_checkedGet {
return try! getCheckedRoot(byteBuffer: &byteBuffer, fileId: fileId, options: options)
} else {
return getRoot(byteBuffer: &byteBuffer)
}
}
public enum FlatBuffersError: Error {
case value_missingRequiredField(file: String, line: Int)
case invalidUnionType
public static func missingRequiredField(file: String = #file, line: Int = #line) -> FlatBuffersError {
if flatBuffers_checkedGet {
preconditionFailure()
}
return .value_missingRequiredField(file: file, line: line)
}
}
public extension PeerId {
init(_ id: TelegramCore_PeerId) {
self.init(namespace: PeerId.Namespace._internalFromInt32Value(id.namespace), id: PeerId.Id._internalFromInt64Value(id.id))
}
func asFlatBuffersObject() -> TelegramCore_PeerId {
return TelegramCore_PeerId(namespace: self.namespace._internalGetInt32Value(), id: self.id._internalGetInt64Value())
}
}
public extension MediaId {
init(_ id: TelegramCore_MediaId) {
self.init(namespace: id.namespace, id: id.id)
}
func asFlatBuffersObject() -> TelegramCore_MediaId {
return TelegramCore_MediaId(namespace: self.namespace, id: self.id)
}
}
public extension PixelDimensions {
init(_ dimensions: TelegramCore_PixelDimensions) {
self.init(width: dimensions.width, height: dimensions.height)
}
func asFlatBuffersObject() -> TelegramCore_PixelDimensions {
return TelegramCore_PixelDimensions(width: self.width, height: self.height)
}
}
public extension ItemCollectionId {
init(_ id: TelegramCore_ItemCollectionId) {
self.init(namespace: id.namespace, id: id.id)
}
func asFlatBuffersObject() -> TelegramCore_ItemCollectionId {
return TelegramCore_ItemCollectionId(namespace: self.namespace, id: self.id)
}
}
@@ -0,0 +1,120 @@
import Postbox
import TelegramApi
import MtProtoKit
import Foundation
public func smallestVideoRepresentation(_ representations: [TelegramMediaImage.VideoRepresentation]) -> TelegramMediaImage.VideoRepresentation? {
if representations.count == 0 {
return nil
} else {
var dimensions = representations[0].dimensions
var index = 0
for i in 1 ..< representations.count {
let representationDimensions = representations[i].dimensions
if representationDimensions.width < dimensions.width && representationDimensions.height < dimensions.height {
dimensions = representationDimensions
index = i
}
}
return representations[index]
}
}
public func smallestImageRepresentation(_ representations: [TelegramMediaImageRepresentation]) -> TelegramMediaImageRepresentation? {
if representations.count == 0 {
return nil
} else {
var dimensions = representations[0].dimensions
var index = 0
for i in 1 ..< representations.count {
let representationDimensions = representations[i].dimensions
if representationDimensions.width < dimensions.width && representationDimensions.height < dimensions.height {
dimensions = representationDimensions
index = i
}
}
return representations[index]
}
}
public func largestImageRepresentation(_ representations: [TelegramMediaImageRepresentation]) -> TelegramMediaImageRepresentation? {
if representations.count == 0 {
return nil
} else {
var dimensions = representations[0].dimensions
var index = 0
for i in 1 ..< representations.count {
let representationDimensions = representations[i].dimensions
if representationDimensions.width > dimensions.width && representationDimensions.height > dimensions.height {
dimensions = representationDimensions
index = i
}
}
return representations[index]
}
}
public func imageRepresentationLargerThan(_ representations: [TelegramMediaImageRepresentation], size: PixelDimensions) -> TelegramMediaImageRepresentation? {
if representations.count == 0 {
return nil
} else {
var index: Int?
for i in 0 ..< representations.count {
let representationDimensions = representations[i].dimensions
if let rindex = index {
let dimensions = representations[rindex].dimensions
if representationDimensions.width > size.width && representationDimensions.height > size.height && representationDimensions.width < dimensions.width && representationDimensions.height < dimensions.height {
index = i
}
} else {
if representationDimensions.width > size.width && representationDimensions.height > size.height {
index = i
}
}
}
if let index = index {
return representations[index]
} else {
return largestImageRepresentation(representations)
}
}
}
public func progressiveImageRepresentation(_ representations: [TelegramMediaImageRepresentation]) -> TelegramMediaImageRepresentation? {
for representation in representations {
if representation.progressiveSizes.count > 1 {
return representation
}
}
return nil
}
public func parseMediaData(data: Data) -> Media? {
let buffer = BufferReader(Buffer(data: data))
var parseBuffer: Buffer?
guard let signature = buffer.readInt32() else {
return nil
}
if signature == 0x3072cfa1 {
parseBuffer = parseBytes(buffer).flatMap({ $0.makeData() }).flatMap(MTGzip.decompress).flatMap(Buffer.init(data:))
} else {
parseBuffer = Buffer(data: data)
}
if let parseBuffer = parseBuffer, let object = Api.parse(parseBuffer) {
if let photo = object as? Api.Photo {
return telegramMediaImageFromApiPhoto(photo)
} else if let document = object as? Api.Document {
return telegramMediaFileFromApiDocument(document, altDocuments: [])
}
}
return nil
}
@@ -0,0 +1,404 @@
import Foundation
import Postbox
import TelegramApi
extension JSON {
private init?(_ object: Any) {
if let object = object as? JSONValue {
self = object.jsonValue
} else if let dict = object as? [String: Any] {
var values: [String: JSON] = [:]
for (key, value) in dict {
if let v = JSON(value) {
values[key] = v
} else {
return nil
}
}
self = .dictionary(values)
} else if let array = object as? [Any] {
var values: [JSON] = []
for value in array {
if let v = JSON(value) {
values.append(v)
} else {
return nil
}
}
self = .array(values)
} else if let value = object as? String {
self = .string(value)
} else if let value = object as? Int {
self = .number(Double(value))
} else {
return nil
}
}
public init?(data: Data) {
if let object = try? JSONSerialization.jsonObject(with: data, options: []) {
self.init(object)
} else {
return nil
}
}
public init?(string: String) {
if let data = string.data(using: .utf8) {
self.init(data: data)
} else {
return nil
}
}
public init?(dictionary: [String: Any]) {
var values: [String: JSON] = [:]
for (key, value) in dictionary {
if let v = JSON(value) {
values[key] = v
} else {
return nil
}
}
self = .dictionary(values)
}
}
extension JSON: Collection {
public var startIndex: Index {
switch self {
case let .array(value):
return .array(value.startIndex)
case let .dictionary(value):
return .dictionary(value.startIndex)
default:
return .null
}
}
public var endIndex: Index {
switch self {
case let .array(value):
return .array(value.endIndex)
case let .dictionary(value):
return .dictionary(value.endIndex)
default:
return .null
}
}
public func index(after i: Index) -> Index {
switch (i, self) {
case let (.array(index), .array(value)):
return .array(value.index(after: index))
case let (.dictionary(index), .dictionary(value)):
return .dictionary(value.index(after: index))
default:
return .null
}
}
public subscript (position: Index) -> (String, JSON) {
switch (position, self) {
case let (.array(index), .array(value)):
return (String(index), value[index])
case let (.dictionary(index), .dictionary(value)):
let (key, value) = value[index]
return (key, value)
default:
return ("", .null)
}
}
}
public enum JSONKey {
case index(Int)
case key(String)
}
public protocol JSONSubscriptType {
var jsonKey: JSONKey { get }
}
extension Int: JSONSubscriptType {
public var jsonKey: JSONKey {
return .index(self)
}
}
extension String: JSONSubscriptType {
public var jsonKey: JSONKey {
return .key(self)
}
}
extension JSON {
fileprivate var value: JSONElement {
get {
switch self {
case .null:
return NSNull()
case let .number(value):
return value
case let .string(value):
return value
case let .bool(value):
return value
case let .array(values):
var array: [JSONElement] = []
for value in values {
array.append(value.value)
}
return array
case let .dictionary(values):
var dictionary: [String: JSONElement] = [:]
for (key, value) in values {
dictionary[key] = value.value
}
return dictionary
}
}
}
}
extension JSON {
public subscript(key: JSONSubscriptType) -> JSONElement? {
get {
switch (key.jsonKey, self) {
case let (.index(index), .array(value)):
if value.indices.contains(index) {
return value[index].value
} else {
return nil
}
case let (.key(key), .dictionary(value)):
if let value = value[key] {
return value.value
} else {
return nil
}
default:
return nil
}
}
}
}
extension JSON {
public var string: String? {
guard let jsonData = try? JSONSerialization.data(withJSONObject: self.value) else {
return nil
}
guard let jsonDataString = String(data: jsonData, encoding: .utf8) else {
return nil
}
return jsonDataString
}
}
extension JSON: ExpressibleByDictionaryLiteral {
public init(dictionaryLiteral elements: (String, Any)...) {
self = .dictionary(elements.reduce([String: JSON]()) { (dictionary, element) in
var dictionary = dictionary
if let value = JSON(element.1) {
dictionary[element.0] = value
}
return dictionary
})
}
}
extension JSON: ExpressibleByArrayLiteral {
public init(arrayLiteral elements: Any...) {
self = .array(elements.compactMap { JSON($0) })
}
}
public protocol JSONElement {}
private protocol JSONValue {
var jsonValue: JSON { get }
}
extension NSNull: JSONElement, JSONValue {
var jsonValue: JSON {
return .null
}
}
extension Int: JSONElement, JSONValue {
var jsonValue: JSON {
return .number(Double(self))
}
}
extension Int8: JSONElement, JSONValue {
var jsonValue: JSON {
return .number(Double(self))
}
}
extension Int16: JSONElement, JSONValue {
var jsonValue: JSON {
return .number(Double(self))
}
}
extension Int32: JSONElement, JSONValue {
var jsonValue: JSON {
return .number(Double(self))
}
}
extension Int64: JSONElement, JSONValue {
var jsonValue: JSON {
return .number(Double(self))
}
}
extension UInt: JSONElement, JSONValue {
var jsonValue: JSON {
return .number(Double(self))
}
}
extension UInt8: JSONElement, JSONValue {
var jsonValue: JSON {
return .number(Double(self))
}
}
extension UInt16: JSONElement, JSONValue {
var jsonValue: JSON {
return .number(Double(self))
}
}
extension UInt32: JSONElement, JSONValue {
var jsonValue: JSON {
return .number(Double(self))
}
}
extension UInt64: JSONElement, JSONValue {
var jsonValue: JSON {
return .number(Double(self))
}
}
extension Double: JSONElement, JSONValue {
var jsonValue: JSON {
return .number(self)
}
}
extension String: JSONElement, JSONValue {
var jsonValue: JSON {
return .string(self)
}
}
extension Bool: JSONElement, JSONValue {
var jsonValue: JSON {
return .bool(self)
}
}
extension Array: JSONElement where Element == JSONElement {
}
extension Array: JSONValue where Element == JSONValue {
var jsonValue: JSON {
return .array(self.map { $0.jsonValue })
}
}
extension Dictionary: JSONElement where Key == String, Value == JSONElement {
}
extension Dictionary: JSONValue where Key == String, Value == JSONValue {
var jsonValue: JSON {
return .dictionary(self.mapValues { $0.jsonValue })
}
}
private extension Bool {
init(apiBool: Api.Bool) {
switch apiBool {
case .boolTrue:
self.init(true)
case .boolFalse:
self.init(false)
}
}
var apiBool: Api.Bool {
if self {
return .boolTrue
} else {
return .boolFalse
}
}
}
public extension JSON {
private init?(apiJson: Api.JSONValue, root: Bool) {
switch (apiJson, root) {
case (.jsonNull, false):
self = .null
case let (.jsonNumber(value), false):
self = .number(value)
case let (.jsonString(value), false):
self = .string(value)
case let (.jsonBool(value), false):
self = .bool(Bool(apiBool: value))
case let (.jsonArray(value), _):
self = .array(value.compactMap { JSON(apiJson: $0, root: false) })
case let (.jsonObject(value), _):
self = .dictionary(value.reduce([String: JSON]()) { dictionary, value in
var dictionary = dictionary
switch value {
case let .jsonObjectValue(key, value):
if let value = JSON(apiJson: value, root: false) {
dictionary[key] = value
}
}
return dictionary
})
default:
return nil
}
}
init?(apiJson: Api.JSONValue) {
self.init(apiJson: apiJson, root: true)
}
}
private func apiJson(_ json: JSON, root: Bool) -> Api.JSONValue? {
switch (json, root) {
case (.null, false):
return .jsonNull
case let (.number(value), false):
return .jsonNumber(value: value)
case let (.string(value), false):
return .jsonString(value: value)
case let (.bool(value), false):
return .jsonBool(value: value.apiBool)
case let (.array(value), _):
return .jsonArray(value: value.compactMap { apiJson($0, root: false) })
case let (.dictionary(value), _):
return .jsonObject(value: value.reduce([Api.JSONObjectValue]()) { objectValues, keyAndValue in
var objectValues = objectValues
if let value = apiJson(keyAndValue.value, root: false) {
objectValues.append(.jsonObjectValue(key: keyAndValue.key, value: value))
}
return objectValues
})
default:
return nil
}
}
func apiJson(_ json: JSON) -> Api.JSONValue? {
return apiJson(json, root: true)
}
@@ -0,0 +1,503 @@
import Foundation
import SwiftSignalKit
import Postbox
import TelegramApi
import NetworkLogging
import ManagedFile
private let queue = DispatchQueue(label: "org.telegram.Telegram.trace", qos: .utility)
public func trace2(_ what: @autoclosure() -> String) {
let string = what()
var rawTime = time_t()
time(&rawTime)
var timeinfo = tm()
localtime_r(&rawTime, &timeinfo)
var curTime = timeval()
gettimeofday(&curTime, nil)
let milliseconds = curTime.tv_usec / 1000
//queue.async {
let result = String(format: "%d-%d-%d %02d:%02d:%03d %@", arguments: [Int(timeinfo.tm_year) + 1900, Int(timeinfo.tm_mon + 1), Int(timeinfo.tm_mday), Int(timeinfo.tm_hour), Int(timeinfo.tm_min), Int(milliseconds), string])
print(result)
//}
}
public func trace1(_ domain: String, what: @autoclosure() -> String) {
let string = what()
var rawTime = time_t()
time(&rawTime)
var timeinfo = tm()
localtime_r(&rawTime, &timeinfo)
var curTime = timeval()
gettimeofday(&curTime, nil)
let seconds = curTime.tv_sec
let milliseconds = curTime.tv_usec / 1000
queue.async {
let result = String(format: "[%@] %d-%d-%d %02d:%02d:%02d.%03d %@", arguments: [domain, Int(timeinfo.tm_year) + 1900, Int(timeinfo.tm_mon + 1), Int(timeinfo.tm_mday), Int(timeinfo.tm_hour), Int(timeinfo.tm_min), Int(seconds), Int(milliseconds), string])
print(result)
}
}
public func registerLoggingFunctions() {
setBridgingTraceFunction({ domain, what in
if let what = what {
if let domain = domain {
Logger.shared.log(domain, what as String)
} else {
Logger.shared.log("", what as String)
}
}
})
setBridgingShortTraceFunction({ domain, what in
if let what = what {
if let domain = domain {
Logger.shared.shortLog(domain, what as String)
} else {
Logger.shared.shortLog("", what as String)
}
}
})
setTelegramApiLogger({ what in
Logger.shared.log("Api", what as String)
Logger.shared.shortLog("Api", what as String)
})
}
private var sharedLogger: Logger?
private let binaryEventMarker: UInt64 = 0xcadebabef00dcafe
public final class Logger {
private let queue = Queue(name: "org.telegram.Telegram.log", qos: .utility)
private let maxLength: Int = 2 * 1024 * 1024
private let maxShortLength: Int = 1 * 1024 * 1024
private let maxFiles: Int = 20
private let rootPath: String
private let basePath: String
private var file: (ManagedFile, Int)?
private var shortFile: (ManagedFile, Int)?
public var logToFile: Bool = true {
didSet {
let oldEnabled = self.logToConsole || oldValue
let newEnabled = self.logToConsole || self.logToFile
if oldEnabled != newEnabled {
NetworkSetLoggingEnabled(newEnabled)
}
}
}
public var logToConsole: Bool = true {
didSet {
let oldEnabled = self.logToFile || oldValue
let newEnabled = self.logToFile || self.logToConsole
if oldEnabled != newEnabled {
NetworkSetLoggingEnabled(newEnabled)
}
}
}
public var redactSensitiveData: Bool = true
public static func setSharedLogger(_ logger: Logger) {
sharedLogger = logger
setPostboxLogger({ s in
Logger.shared.log("Postbox", s)
Logger.shared.shortLog("Postbox", s)
}, sync: {
Logger.shared.sync()
})
}
public static var shared: Logger {
if let sharedLogger = sharedLogger {
return sharedLogger
} else {
assertionFailure()
let tempLogger = Logger(rootPath: "", basePath: "")
tempLogger.logToFile = false
tempLogger.logToConsole = false
return tempLogger
}
}
public init(rootPath: String, basePath: String) {
self.rootPath = rootPath
self.basePath = basePath
}
public func sync() {
self.queue.sync {
if let (currentFile, _) = self.file {
let _ = currentFile.sync()
}
}
}
public func collectLogs(prefix: String? = nil) -> Signal<[(String, String)], NoError> {
return Signal { subscriber in
self.queue.async {
let logsPath: String
if let prefix = prefix {
logsPath = self.rootPath + prefix
} else {
logsPath = self.basePath
}
var result: [(Date, String, String)] = []
if let files = try? FileManager.default.contentsOfDirectory(at: URL(fileURLWithPath: logsPath), includingPropertiesForKeys: [URLResourceKey.creationDateKey], options: []) {
for url in files {
if url.lastPathComponent.hasPrefix("log-") {
if let creationDate = (try? url.resourceValues(forKeys: Set([.creationDateKey])))?.creationDate {
result.append((creationDate, url.lastPathComponent, url.path))
}
}
}
}
result.sort(by: { $0.0 < $1.0 })
subscriber.putNext(result.map { ($0.1, $0.2) })
subscriber.putCompletion()
}
return EmptyDisposable
}
}
public func collectLogs(basePath: String) -> Signal<[(String, String)], NoError> {
return Signal { subscriber in
self.queue.async {
let logsPath: String = basePath
var result: [(Date, String, String)] = []
if let files = try? FileManager.default.contentsOfDirectory(at: URL(fileURLWithPath: logsPath), includingPropertiesForKeys: [URLResourceKey.creationDateKey], options: []) {
for url in files {
if url.lastPathComponent.hasPrefix("log-") {
if let creationDate = (try? url.resourceValues(forKeys: Set([.creationDateKey])))?.creationDate {
result.append((creationDate, url.lastPathComponent, url.path))
}
}
}
}
result.sort(by: { $0.0 < $1.0 })
subscriber.putNext(result.map { ($0.1, $0.2) })
subscriber.putCompletion()
}
return EmptyDisposable
}
}
public func collectShortLogFiles() -> Signal<[(String, String)], NoError> {
return Signal { subscriber in
self.queue.async {
var result: [(Date, String, String)] = []
if let files = try? FileManager.default.contentsOfDirectory(at: URL(fileURLWithPath: self.basePath), includingPropertiesForKeys: [URLResourceKey.creationDateKey], options: []) {
for url in files {
if url.lastPathComponent.hasPrefix("critlog-") {
if let creationDate = (try? url.resourceValues(forKeys: Set([.creationDateKey])))?.creationDate {
result.append((creationDate, url.lastPathComponent, url.path))
}
}
}
}
result.sort(by: { $0.0 < $1.0 })
subscriber.putNext(result.map { ($0.1, $0.2) })
subscriber.putCompletion()
}
return EmptyDisposable
}
}
public func collectShortLog() -> Signal<[(Double, String)], NoError> {
return Signal { subscriber in
self.queue.async {
var result: [(Date, String, String)] = []
if let files = try? FileManager.default.contentsOfDirectory(at: URL(fileURLWithPath: self.basePath), includingPropertiesForKeys: [URLResourceKey.creationDateKey], options: []) {
for url in files {
if url.lastPathComponent.hasPrefix("critlog-") {
if let creationDate = (try? url.resourceValues(forKeys: Set([.creationDateKey])))?.creationDate {
result.append((creationDate, url.lastPathComponent, url.path))
}
}
}
}
result.sort(by: { $0.0 < $1.0 })
var events: [(Double, String)] = []
for (_, _, filePath) in result.reversed() {
var fileEvents: [(Double, String)] = []
if let data = try? Data(contentsOf: URL(fileURLWithPath: filePath), options: .mappedRead) {
let dataLength = data.count
data.withUnsafeBytes { rawBytes -> Void in
let bytes = rawBytes.baseAddress!.assumingMemoryBound(to: Int8.self)
var offset = 0
while offset < dataLength {
let remainingLength = dataLength - offset
if remainingLength < 8 + 4 + 8 {
break
}
var maybeMarker: UInt64 = 0
memcpy(&maybeMarker, bytes.advanced(by: offset), 8)
if maybeMarker == binaryEventMarker {
var length: Int32 = 0
memcpy(&length, bytes.advanced(by: offset + 8), 4)
if length < 0 || length > dataLength - offset {
offset += 1
} else {
var timestamp: Double = 0.0
memcpy(&timestamp, bytes.advanced(by: offset + 8 + 4), 8)
let eventStringData = Data(bytes: bytes.advanced(by: offset + 8 + 4 + 8), count: Int(length - 8))
if let string = String(data: eventStringData, encoding: .utf8) {
fileEvents.append((timestamp, string))
}
offset += 8 + 4 + Int(length)
}
} else {
offset += 1
}
}
}
events.append(contentsOf: fileEvents.reversed())
if events.count > 1000 {
break
}
}
}
if events.count > 1000 {
events.removeLast(events.count - 1000)
}
subscriber.putNext(events)
subscriber.putCompletion()
}
return EmptyDisposable
}
}
public func log(_ tag: String, _ what: @autoclosure () -> String) {
if !self.logToFile && !self.logToConsole {
return
}
let string = what()
var rawTime = time_t()
time(&rawTime)
var timeinfo = tm()
localtime_r(&rawTime, &timeinfo)
var curTime = timeval()
gettimeofday(&curTime, nil)
let milliseconds = curTime.tv_usec / 1000
var consoleContent: String?
if self.logToConsole {
let content = String(format: "[%@] %d-%d-%d %02d:%02d:%02d.%03d %@", arguments: [tag, Int(timeinfo.tm_year) + 1900, Int(timeinfo.tm_mon + 1), Int(timeinfo.tm_mday), Int(timeinfo.tm_hour), Int(timeinfo.tm_min), Int(timeinfo.tm_sec), Int(milliseconds), string])
consoleContent = content
print(content)
}
if self.logToFile {
self.queue.async {
let content: String
if let consoleContent = consoleContent {
content = consoleContent
} else {
content = String(format: "[%@] %d-%d-%d %02d:%02d:%02d.%03d %@", arguments: [tag, Int(timeinfo.tm_year) + 1900, Int(timeinfo.tm_mon + 1), Int(timeinfo.tm_mday), Int(timeinfo.tm_hour), Int(timeinfo.tm_min), Int(timeinfo.tm_sec), Int(milliseconds), string])
}
var currentFile: ManagedFile?
var openNew = false
if let (file, length) = self.file {
if length >= self.maxLength {
self.file = nil
openNew = true
} else {
currentFile = file
}
} else {
openNew = true
}
if openNew {
let _ = try? FileManager.default.createDirectory(atPath: self.basePath, withIntermediateDirectories: true, attributes: nil)
var createNew = false
if let files = try? FileManager.default.contentsOfDirectory(at: URL(fileURLWithPath: self.basePath), includingPropertiesForKeys: [URLResourceKey.creationDateKey], options: []) {
var minCreationDate: (Date, URL)?
var maxCreationDate: (Date, URL)?
var count = 0
for url in files {
if url.lastPathComponent.hasPrefix("log-") {
if let values = try? url.resourceValues(forKeys: Set([URLResourceKey.creationDateKey])), let creationDate = values.creationDate {
count += 1
if minCreationDate == nil || minCreationDate!.0 > creationDate {
minCreationDate = (creationDate, url)
}
if maxCreationDate == nil || maxCreationDate!.0 < creationDate {
maxCreationDate = (creationDate, url)
}
}
}
}
if let (_, url) = minCreationDate, count >= self.maxFiles {
let _ = try? FileManager.default.removeItem(at: url)
}
if let (_, url) = maxCreationDate {
var value = stat()
if stat(url.path, &value) == 0 && Int(value.st_size) < self.maxLength {
if let file = ManagedFile(queue: self.queue, path: url.path, mode: .append) {
self.file = (file, Int(value.st_size))
currentFile = file
}
} else {
createNew = true
}
} else {
createNew = true
}
}
if createNew {
let fileName = String(format: "log-%d-%d-%d_%02d-%02d-%02d.%03d.txt", arguments: [Int(timeinfo.tm_year) + 1900, Int(timeinfo.tm_mon + 1), Int(timeinfo.tm_mday), Int(timeinfo.tm_hour), Int(timeinfo.tm_min), Int(timeinfo.tm_sec), Int(milliseconds)])
let path = self.basePath + "/" + fileName
if let file = ManagedFile(queue: self.queue, path: path, mode: .append) {
self.file = (file, 0)
currentFile = file
}
}
}
if let currentFile = currentFile {
if let data = content.data(using: .utf8) {
data.withUnsafeBytes { rawBytes -> Void in
let bytes = rawBytes.baseAddress!.assumingMemoryBound(to: UInt8.self)
let _ = currentFile.write(bytes, count: data.count)
}
var newline: UInt8 = 0x0a
let _ = currentFile.write(&newline, count: 1)
if let file = self.file {
self.file = (file.0, file.1 + data.count + 1)
} else {
assertionFailure()
}
}
}
}
}
}
public func shortLog(_ tag: String, _ what: @autoclosure () -> String) {
let string = what()
var rawTime = time_t()
time(&rawTime)
var timeinfo = tm()
localtime_r(&rawTime, &timeinfo)
var curTime = timeval()
gettimeofday(&curTime, nil)
let milliseconds = curTime.tv_usec / 1000
let timestamp: Double = CFAbsoluteTimeGetCurrent() + kCFAbsoluteTimeIntervalSince1970
self.queue.async {
let content = WriteBuffer()
var binaryEventMarkerValue: UInt64 = binaryEventMarker
content.write(&binaryEventMarkerValue, offset: 0, length: 8)
let stringData = string.data(using: .utf8) ?? Data()
var lengthValue: Int32 = 8 + Int32(stringData.count)
content.write(&lengthValue, offset: 0, length: 4)
var timestampValue: Double = timestamp
content.write(&timestampValue, offset: 0, length: 8)
content.write(stringData)
let contentData = content.makeData()
var currentFile: ManagedFile?
var openNew = false
if let (file, length) = self.shortFile {
if length >= self.maxShortLength {
self.shortFile = nil
openNew = true
} else {
currentFile = file
}
} else {
openNew = true
}
if openNew {
let _ = try? FileManager.default.createDirectory(atPath: self.basePath, withIntermediateDirectories: true, attributes: nil)
var createNew = false
if let files = try? FileManager.default.contentsOfDirectory(at: URL(fileURLWithPath: self.basePath), includingPropertiesForKeys: [URLResourceKey.creationDateKey], options: []) {
var minCreationDate: (Date, URL)?
var maxCreationDate: (Date, URL)?
var count = 0
for url in files {
if url.lastPathComponent.hasPrefix("critlog-") {
if let values = try? url.resourceValues(forKeys: Set([URLResourceKey.creationDateKey])), let creationDate = values.creationDate {
count += 1
if minCreationDate == nil || minCreationDate!.0 > creationDate {
minCreationDate = (creationDate, url)
}
if maxCreationDate == nil || maxCreationDate!.0 < creationDate {
maxCreationDate = (creationDate, url)
}
}
}
}
if let (_, url) = minCreationDate, count >= self.maxFiles {
let _ = try? FileManager.default.removeItem(at: url)
}
if let (_, url) = maxCreationDate {
var value = stat()
if stat(url.path, &value) == 0 && Int(value.st_size) < self.maxShortLength {
if let file = ManagedFile(queue: self.queue, path: url.path, mode: .append) {
self.shortFile = (file, Int(value.st_size))
currentFile = file
}
} else {
createNew = true
}
} else {
createNew = true
}
}
if createNew {
let fileName = String(format: "critlog-%d-%d-%d_%02d-%02d-%02d.%03d.txt", arguments: [Int(timeinfo.tm_year) + 1900, Int(timeinfo.tm_mon + 1), Int(timeinfo.tm_mday), Int(timeinfo.tm_hour), Int(timeinfo.tm_min), Int(timeinfo.tm_sec), Int(milliseconds)])
let path = self.basePath + "/" + fileName
if let file = ManagedFile(queue: self.queue, path: path, mode: .append) {
self.shortFile = (file, 0)
currentFile = file
}
}
}
if let currentFile = currentFile {
let contentDataCount = contentData.count
contentData.withUnsafeBytes { rawBytes -> Void in
let bytes = rawBytes.baseAddress!.assumingMemoryBound(to: UInt8.self)
let _ = currentFile.write(bytes, count: contentDataCount)
}
if let shortFile = self.shortFile {
self.shortFile = (shortFile.0, shortFile.1 + contentDataCount)
} else {
assertionFailure()
}
}
}
}
}
@@ -0,0 +1,13 @@
import Foundation
import Postbox
import CryptoUtils
// Incuding at least one Objective-C class in a swift file ensures that it doesn't get stripped by the linker
private final class LinkHelperClass: NSObject {
}
public extension MemoryBuffer {
func md5Digest() -> Data {
return CryptoMD5(self.memory, Int32(self.length))
}
}
@@ -0,0 +1,35 @@
import Postbox
public enum MediaResourceStatsCategory {
case generic
case image
case video
case audio
case file
case call
case stickers
case voiceMessages
}
public final class TelegramMediaResourceFetchTag: MediaResourceFetchTag {
public let statsCategory: MediaResourceStatsCategory
public init(statsCategory: MediaResourceStatsCategory, userContentType: MediaResourceUserContentType?) {
switch userContentType {
case .file:
self.statsCategory = .file
case .image:
self.statsCategory = .image
case .video:
self.statsCategory = .video
case .audio:
self.statsCategory = .audio
case .sticker:
self.statsCategory = .stickers
case .audioVideoMessage:
self.statsCategory = .voiceMessages
default:
self.statsCategory = statsCategory
}
}
}
@@ -0,0 +1,24 @@
import Foundation
import Postbox
import TelegramApi
public extension MemoryBuffer {
convenience init(_ buffer: Buffer) {
let memory = malloc(Int(buffer.size))!
memcpy(memory, buffer.data, Int(buffer.size))
self.init(memory: memory, capacity: Int(buffer.size), length: Int(buffer.size), freeWhenDone: true)
}
}
extension Buffer {
convenience init(bufferNoCopy: MemoryBuffer) {
self.init(memory: bufferNoCopy.memory, size: bufferNoCopy.length, capacity: bufferNoCopy.length, freeWhenDone: false)
}
convenience init(buffer: MemoryBuffer) {
let memory = malloc(buffer.length)!
memcpy(memory, buffer.memory, buffer.length)
self.init(memory: memory, size: buffer.length, capacity: buffer.length, freeWhenDone: true)
}
}
@@ -0,0 +1,675 @@
import Foundation
import Postbox
import TelegramApi
public extension MessageFlags {
var isSending: Bool {
return (self.contains(.Unsent) || self.contains(.Sending)) && !self.contains(.Failed)
}
}
public extension Message {
var visibleButtonKeyboardMarkup: ReplyMarkupMessageAttribute? {
for attribute in self.attributes {
if let attribute = attribute as? ReplyMarkupMessageAttribute {
if !attribute.flags.contains(.inline) && !attribute.rows.isEmpty {
if attribute.flags.contains(.personal) {
if !personal {
return nil
}
}
return attribute
}
}
}
return nil
}
var visibleReplyMarkupPlaceholder: String? {
for attribute in self.attributes {
if let attribute = attribute as? ReplyMarkupMessageAttribute {
if !attribute.flags.contains(.inline) {
if attribute.flags.contains(.personal) {
if !personal {
return nil
}
}
return attribute.placeholder
}
}
}
return nil
}
var muted: Bool {
for attribute in self.attributes {
if let attribute = attribute as? NotificationInfoMessageAttribute {
return attribute.flags.contains(.muted)
}
}
return false
}
var personal: Bool {
for attribute in self.attributes {
if let attribute = attribute as? NotificationInfoMessageAttribute {
return attribute.flags.contains(.personal)
}
}
return false
}
var requestsSetupReply: Bool {
for attribute in self.attributes {
if let attribute = attribute as? ReplyMarkupMessageAttribute {
if !attribute.flags.contains(.inline) {
if attribute.flags.contains(.personal) {
if !personal {
return false
}
}
return attribute.flags.contains(.setupReply)
}
}
}
return false
}
var isScam: Bool {
if let author = self.author, author.isScam {
return true
}
if let forwardAuthor = self.forwardInfo?.author, forwardAuthor.isScam {
return true
}
for attribute in self.attributes {
if let attribute = attribute as? InlineBotMessageAttribute, let peerId = attribute.peerId, let bot = self.peers[peerId] as? TelegramUser, bot.isScam {
return true
}
}
return false
}
var isFake: Bool {
if let author = self.author, author.isFake {
return true
}
if let forwardAuthor = self.forwardInfo?.author, forwardAuthor.isFake {
return true
}
for attribute in self.attributes {
if let attribute = attribute as? InlineBotMessageAttribute, let peerId = attribute.peerId, let bot = self.peers[peerId] as? TelegramUser, bot.isFake {
return true
}
}
return false
}
var sourceReference: SourceReferenceMessageAttribute? {
for attribute in self.attributes {
if let attribute = attribute as? SourceReferenceMessageAttribute {
return attribute
}
}
return nil
}
var sourceAuthorInfo: SourceAuthorInfoMessageAttribute? {
for attribute in self.attributes {
if let attribute = attribute as? SourceAuthorInfoMessageAttribute {
return attribute
}
}
return nil
}
var effectiveAuthor: Peer? {
if let sourceAuthorInfo = self.sourceAuthorInfo {
if let sourceAuthorId = sourceAuthorInfo.originalAuthor, let peer = self.peers[sourceAuthorId] {
return peer
}
}
if let forwardInfo = self.forwardInfo, let sourceReference = self.sourceReference, forwardInfo.author?.id == sourceReference.messageId.peerId {
if let peer = self.peers[sourceReference.messageId.peerId] {
return peer
}
} else if let forwardInfo = self.forwardInfo, forwardInfo.flags.contains(.isImported), let author = forwardInfo.author {
return author
}
return self.author
}
}
func messagesIdsGroupedByPeerId(_ ids: Set<MessageId>) -> [PeerId: [MessageId]] {
var dict: [PeerId: [MessageId]] = [:]
for id in ids {
let peerId = id.peerId
if dict[peerId] == nil {
dict[peerId] = [id]
} else {
dict[peerId]!.append(id)
}
}
return dict
}
func messagesIdsGroupedByPeerId(_ ids: [MessageId]) -> [PeerId: [MessageId]] {
var dict: [PeerId: [MessageId]] = [:]
for id in ids {
let peerId = id.peerId
if dict[peerId] == nil {
dict[peerId] = [id]
} else {
dict[peerId]!.append(id)
}
}
return dict
}
func messagesIdsGroupedByPeerId(_ ids: ReferencedReplyMessageIds) -> [PeerId: ReferencedReplyMessageIds] {
var dict: [PeerId: ReferencedReplyMessageIds] = [:]
for (targetId, sourceId) in ids.targetIdsBySourceId {
let peerId = sourceId.peerId
dict[peerId, default: ReferencedReplyMessageIds()].add(sourceId: sourceId, targetId: targetId)
}
return dict
}
func messagesIdsGroupedByPeerId(_ ids: Set<MessageAndThreadId>) -> [PeerAndThreadId: [MessageId]] {
var dict: [PeerAndThreadId: [MessageId]] = [:]
for id in ids {
let peerAndThreadId = PeerAndThreadId(peerId: id.messageId.peerId, threadId: id.threadId)
if dict[peerAndThreadId] == nil {
dict[peerAndThreadId] = [id.messageId]
} else {
dict[peerAndThreadId]!.append(id.messageId)
}
}
return dict
}
func messagesIdsGroupedByPeerId(_ ids: [MessageAndThreadId]) -> [PeerAndThreadId: [MessageId]] {
var dict: [PeerAndThreadId: [MessageId]] = [:]
for id in ids {
let peerAndThreadId = PeerAndThreadId(peerId: id.messageId.peerId, threadId: id.threadId)
if dict[peerAndThreadId] == nil {
dict[peerAndThreadId] = [id.messageId]
} else {
dict[peerAndThreadId]!.append(id.messageId)
}
}
return dict
}
func locallyRenderedMessage(message: StoreMessage, peers: [PeerId: Peer], associatedThreadInfo: Message.AssociatedThreadInfo? = nil, associatedMessages: SimpleDictionary<MessageId, Message> = SimpleDictionary()) -> Message? {
guard case let .Id(id) = message.id else {
return nil
}
var messagePeers = SimpleDictionary<PeerId, Peer>()
var author: Peer?
if let authorId = message.authorId {
author = peers[authorId]
if let author = author {
messagePeers[author.id] = author
}
}
if let peer = peers[id.peerId] {
messagePeers[peer.id] = peer
if let group = peer as? TelegramGroup, let migrationReference = group.migrationReference {
if let channelPeer = peers[migrationReference.peerId] {
messagePeers[channelPeer.id] = channelPeer
}
}
if let channel = peer as? TelegramChannel, channel.isMonoForum, let linkedMonoforumId = channel.linkedMonoforumId {
if let channelPeer = peers[linkedMonoforumId] {
messagePeers[channelPeer.id] = channelPeer
}
if let threadId = message.threadId {
if let threadPeer = peers[PeerId(threadId)] {
messagePeers[threadPeer.id] = threadPeer
}
}
}
}
for media in message.media {
for peerId in media.peerIds {
if let peer = peers[peerId] {
messagePeers[peer.id] = peer
}
}
}
var forwardInfo: MessageForwardInfo?
if let info = message.forwardInfo {
forwardInfo = MessageForwardInfo(author: info.authorId.flatMap({ peers[$0] }), source: info.sourceId.flatMap({ peers[$0] }), sourceMessageId: info.sourceMessageId, date: info.date, authorSignature: info.authorSignature, psaType: info.psaType, flags: info.flags)
if let author = forwardInfo?.author {
messagePeers[author.id] = author
}
if let source = forwardInfo?.source {
messagePeers[source.id] = source
}
}
var hasher = Hasher()
hasher.combine(id.id)
hasher.combine(id.peerId)
let hashValue = Int64(hasher.finalize())
let first = UInt32((hashValue >> 32) & 0xffffffff)
let second = UInt32(hashValue & 0xffffffff)
let stableId = first &+ second
return Message(stableId: stableId, stableVersion: 0, id: id, globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: message.threadId, timestamp: message.timestamp, flags: MessageFlags(message.flags), tags: message.tags, globalTags: message.globalTags, localTags: message.localTags, customTags: [], forwardInfo: forwardInfo, author: author, text: message.text, attributes: message.attributes, media: message.media, peers: messagePeers, associatedMessages: associatedMessages, associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: associatedThreadInfo, associatedStories: [:])
}
func locallyRenderedMessage(message: StoreMessage, peers: AccumulatedPeers, associatedThreadInfo: Message.AssociatedThreadInfo? = nil) -> Message? {
guard case let .Id(id) = message.id else {
return nil
}
var messagePeers = SimpleDictionary<PeerId, Peer>()
var author: Peer?
if let authorId = message.authorId {
author = peers.get(authorId)
if let author = author {
messagePeers[author.id] = author
}
}
if let peer = peers.get(id.peerId) {
messagePeers[peer.id] = peer
if let group = peer as? TelegramGroup, let migrationReference = group.migrationReference {
if let channelPeer = peers.get(migrationReference.peerId) {
messagePeers[channelPeer.id] = channelPeer
}
}
}
for media in message.media {
for peerId in media.peerIds {
if let peer = peers.get(peerId) {
messagePeers[peer.id] = peer
}
}
}
var forwardInfo: MessageForwardInfo?
if let info = message.forwardInfo {
forwardInfo = MessageForwardInfo(author: info.authorId.flatMap({ peers.get($0) }), source: info.sourceId.flatMap({ peers.get($0) }), sourceMessageId: info.sourceMessageId, date: info.date, authorSignature: info.authorSignature, psaType: info.psaType, flags: info.flags)
if let author = forwardInfo?.author {
messagePeers[author.id] = author
}
if let source = forwardInfo?.source {
messagePeers[source.id] = source
}
}
var hasher = Hasher()
hasher.combine(id.id)
hasher.combine(id.peerId)
let hashValue = Int64(hasher.finalize())
let first = UInt32((hashValue >> 32) & 0xffffffff)
let second = UInt32(hashValue & 0xffffffff)
let stableId = first &+ second
return Message(stableId: stableId, stableVersion: 0, id: id, globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: message.threadId, timestamp: message.timestamp, flags: MessageFlags(message.flags), tags: message.tags, globalTags: message.globalTags, localTags: message.localTags, customTags: [], forwardInfo: forwardInfo, author: author, text: message.text, attributes: message.attributes, media: message.media, peers: messagePeers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: associatedThreadInfo, associatedStories: [:])
}
public extension Message {
func effectivelyIncoming(_ accountPeerId: PeerId) -> Bool {
if self.id.peerId == accountPeerId {
if let sourceAuthorInfo = self.sourceAuthorInfo {
if sourceAuthorInfo.originalOutgoing {
return false
} else if let originalAuthor = sourceAuthorInfo.originalAuthor, originalAuthor == accountPeerId {
return false
}
} else if let forwardInfo = self.forwardInfo {
if let author = forwardInfo.author, author.id == accountPeerId {
return false
}
}
if self.forwardInfo != nil {
return true
} else {
return false
}
} else if self.author?.id == accountPeerId {
if let channel = self.peers[self.id.peerId] as? TelegramChannel, case .broadcast = channel.info {
return true
}
return false
} else if self.flags.contains(.Incoming) {
return true
} else if let channel = self.peers[self.id.peerId] as? TelegramChannel, case .broadcast = channel.info {
return true
} else {
return false
}
}
func effectivelyFailed(timestamp: Int32) -> Bool {
if self.flags.contains(.Failed) {
return true
} else if self.id.namespace == Namespaces.Message.ScheduledCloud && self.timestamp != scheduleWhenOnlineTimestamp {
return timestamp > self.timestamp + 60
} else {
return false
}
}
func isCopyProtected() -> Bool {
if self.flags.contains(.CopyProtected) {
return true
} else if let group = self.peers[self.id.peerId] as? TelegramGroup, group.flags.contains(.copyProtectionEnabled) {
return true
} else if let channel = self.peers[self.id.peerId] as? TelegramChannel, channel.flags.contains(.copyProtectionEnabled) {
return true
} else {
return false
}
}
func isSensitiveContent(platform: String) -> Bool {
if let rule = self.restrictedContentAttribute?.rules.first(where: { $0.reason == "sensitive" }) {
if rule.platform == "all" || rule.platform == platform {
return true
}
}
if let peer = self.peers[self.id.peerId], peer.hasSensitiveContent(platform: platform) {
return true
}
return false
}
}
public extension Message {
var secretMediaDuration: Double? {
var found = false
for attribute in self.attributes {
if let _ = attribute as? AutoremoveTimeoutMessageAttribute {
found = true
break
} else if let _ = attribute as? AutoclearTimeoutMessageAttribute {
found = true
break
}
}
if !found {
return nil
}
for media in self.media {
switch media {
case _ as TelegramMediaImage:
return nil
case let file as TelegramMediaFile:
return file.duration
default:
break
}
}
return nil
}
}
public extension Message {
var isSentOrAcknowledged: Bool {
if self.flags.contains(.Failed) {
return false
} else if self.flags.isSending {
for attribute in self.attributes {
if let attribute = attribute as? OutgoingMessageInfoAttribute {
if attribute.acknowledged {
return true
}
}
}
return false
} else {
return true
}
}
}
public extension Message {
var adAttribute: AdMessageAttribute? {
for attribute in self.attributes {
if let attribute = attribute as? AdMessageAttribute {
return attribute
}
}
return nil
}
var factCheckAttribute: FactCheckMessageAttribute? {
for attribute in self.attributes {
if let attribute = attribute as? FactCheckMessageAttribute {
return attribute
}
}
return nil
}
var inlineBotAttribute: InlineBusinessBotMessageAttribute? {
for attribute in self.attributes {
if let attribute = attribute as? InlineBusinessBotMessageAttribute {
return attribute
}
}
return nil
}
var derivedDataAttribute: DerivedDataMessageAttribute? {
for attribute in self.attributes {
if let attribute = attribute as? DerivedDataMessageAttribute {
return attribute
}
}
return nil
}
var forwardVideoTimestampAttribute: ForwardVideoTimestampAttribute? {
for attribute in self.attributes {
if let attribute = attribute as? ForwardVideoTimestampAttribute {
return attribute
}
}
return nil
}
}
public extension Message {
var reactionsAttribute: ReactionsMessageAttribute? {
for attribute in self.attributes {
if let attribute = attribute as? ReactionsMessageAttribute {
return attribute
}
}
return nil
}
func effectiveReactionsAttribute(isTags: Bool) -> ReactionsMessageAttribute? {
if !self.hasReactions {
return nil
}
if let result = mergedMessageReactions(attributes: self.attributes, isTags: isTags) {
return result
} else {
return nil
}
}
func effectiveReactions(isTags: Bool) -> [MessageReaction]? {
if !self.hasReactions {
return nil
}
if let result = mergedMessageReactions(attributes: self.attributes, isTags: isTags) {
return result.reactions
} else {
return nil
}
}
var hasReactions: Bool {
for attribute in self.attributes {
if let attribute = attribute as? ReactionsMessageAttribute {
if !attribute.reactions.isEmpty {
return true
}
}
}
for attribute in self.attributes {
if let attribute = attribute as? PendingReactionsMessageAttribute {
if !attribute.reactions.isEmpty {
return true
}
}
}
return false
}
var textEntitiesAttribute: TextEntitiesMessageAttribute? {
for attribute in self.attributes {
if let attribute = attribute as? TextEntitiesMessageAttribute {
return attribute
}
}
return nil
}
var restrictedContentAttribute: RestrictedContentMessageAttribute? {
for attribute in self.attributes {
if let attribute = attribute as? RestrictedContentMessageAttribute {
return attribute
}
}
return nil
}
var paidContent: TelegramMediaPaidContent? {
return self.media.first(where: { $0 is TelegramMediaPaidContent }) as? TelegramMediaPaidContent
}
var authorSignatureAttribute: AuthorSignatureMessageAttribute? {
for attribute in self.attributes {
if let attribute = attribute as? AuthorSignatureMessageAttribute {
return attribute
}
}
return nil
}
var paidStarsAttribute: PaidStarsMessageAttribute? {
for attribute in self.attributes {
if let attribute = attribute as? PaidStarsMessageAttribute {
return attribute
}
}
return nil
}
}
public extension Message {
var webpagePreviewAttribute: WebpagePreviewMessageAttribute? {
for attribute in self.attributes {
if let attribute = attribute as? WebpagePreviewMessageAttribute {
return attribute
}
}
return nil
}
var invertMedia: Bool {
for attribute in self.attributes {
if let _ = attribute as? InvertMediaMessageAttribute {
return true
}
}
return false
}
var invertMediaAttribute: InvertMediaMessageAttribute? {
for attribute in self.attributes {
if let attribute = attribute as? InvertMediaMessageAttribute {
return attribute
}
}
return nil
}
}
public extension Message {
var pendingProcessingAttribute: PendingProcessingMessageAttribute? {
for attribute in self.attributes {
if let attribute = attribute as? PendingProcessingMessageAttribute {
return attribute
}
}
return nil
}
}
public extension Message {
func areReactionsTags(accountPeerId: PeerId) -> Bool {
if self.id.peerId == accountPeerId {
if let reactionsAttribute = self.reactionsAttribute, !reactionsAttribute.reactions.isEmpty {
return reactionsAttribute.isTags
} else {
return true
}
}
return false
}
}
public func _internal_parseMediaAttachment(data: Data) -> Media? {
guard let object = Api.parse(Buffer(buffer: MemoryBuffer(data: data))) else {
return nil
}
if let photo = object as? Api.Photo {
return telegramMediaImageFromApiPhoto(photo)
} else if let file = object as? Api.Document {
return telegramMediaFileFromApiDocument(file, altDocuments: [])
} else {
return nil
}
}
public extension Message {
func messageEffect(availableMessageEffects: AvailableMessageEffects?) -> AvailableMessageEffects.MessageEffect? {
guard let availableMessageEffects else {
return nil
}
for attribute in self.attributes {
if let attribute = attribute as? EffectMessageAttribute {
for effect in availableMessageEffects.messageEffects {
if effect.id == attribute.id {
return effect
}
}
break
}
}
return nil
}
}
@@ -0,0 +1,614 @@
import Foundation
import Postbox
public let anonymousSavedMessagesId: Int64 = 2666000
public extension Peer {
var debugDisplayTitle: String {
switch self {
case let user as TelegramUser:
return user.nameOrPhone
case let group as TelegramGroup:
return group.title
case let channel as TelegramChannel:
return channel.title
default:
return ""
}
}
func restrictionText(platform: String, contentSettings: ContentSettings) -> String? {
var restrictionInfo: PeerAccessRestrictionInfo?
switch self {
case let user as TelegramUser:
restrictionInfo = user.restrictionInfo
case let channel as TelegramChannel:
restrictionInfo = channel.restrictionInfo
default:
break
}
if let restrictionInfo = restrictionInfo {
for rule in restrictionInfo.rules {
if rule.reason == "sensitive" {
continue
}
if rule.platform == "all" || rule.platform == platform || contentSettings.addContentRestrictionReasons.contains(rule.platform) {
if !contentSettings.ignoreContentRestrictionReasons.contains(rule.reason) {
return rule.text
}
}
}
return nil
} else {
return nil
}
}
var addressName: String? {
switch self {
case let user as TelegramUser:
return user.usernames.first(where: { $0.isActive }).map { $0.username } ?? user.username
case _ as TelegramGroup:
return nil
case let channel as TelegramChannel:
return channel.usernames.first(where: { $0.isActive }).map { $0.username } ?? channel.username
default:
return nil
}
}
var usernames: [TelegramPeerUsername] {
switch self {
case let user as TelegramUser:
return user.usernames
case _ as TelegramGroup:
return []
case let channel as TelegramChannel:
return channel.usernames
default:
return []
}
}
var editableUsername: String? {
switch self {
case let user as TelegramUser:
return user.usernames.first(where: { $0.flags.contains(.isEditable) }).map { $0.username } ?? user.username
case _ as TelegramGroup:
return nil
case let channel as TelegramChannel:
return channel.usernames.first(where: { $0.flags.contains(.isEditable) }).map { $0.username } ?? channel.username
default:
return nil
}
}
var displayLetters: [String] {
switch self {
case let user as TelegramUser:
if let firstName = user.firstName, let lastName = user.lastName, !firstName.isEmpty && !lastName.isEmpty {
return [
String(firstName[..<firstName.index(after: firstName.startIndex)].uppercased()),
String(lastName[..<lastName.index(after: lastName.startIndex)].uppercased()),
]
} else if let firstName = user.firstName, !firstName.isEmpty {
return [
String(firstName[..<firstName.index(after: firstName.startIndex)].uppercased())
]
} else if let lastName = user.lastName, !lastName.isEmpty {
return [
String(lastName[..<lastName.index(after: lastName.startIndex)].uppercased()),
]
}
return []
case let group as TelegramGroup:
if group.title.startIndex != group.title.endIndex {
return [
String(group.title[..<group.title.index(after: group.title.startIndex)].uppercased()),
]
} else {
return []
}
case let channel as TelegramChannel:
if channel.title.startIndex != channel.title.endIndex {
return [
String(channel.title[..<channel.title.index(after: channel.title.startIndex)].uppercased()),
]
} else {
return []
}
default:
return []
}
}
var profileImageRepresentations: [TelegramMediaImageRepresentation] {
if let user = self as? TelegramUser {
return user.photo
} else if let group = self as? TelegramGroup {
return group.photo
} else if let channel = self as? TelegramChannel {
return channel.photo
}
return []
}
var smallProfileImage: TelegramMediaImageRepresentation? {
return smallestImageRepresentation(self.profileImageRepresentations)
}
var largeProfileImage: TelegramMediaImageRepresentation? {
return largestImageRepresentation(self.profileImageRepresentations)
}
var isDeleted: Bool {
switch self {
case let user as TelegramUser:
return user.firstName == nil && user.lastName == nil
default:
return false
}
}
var isGenericUser: Bool {
switch self {
case let user as TelegramUser:
if user.isDeleted {
return false
}
if user.botInfo != nil {
return false
}
if user.id.isRepliesOrVerificationCodes {
return false
}
if user.id.isTelegramNotifications {
return false
}
return true
default:
return false
}
}
var isScam: Bool {
switch self {
case let user as TelegramUser:
return user.flags.contains(.isScam)
case let channel as TelegramChannel:
return channel.flags.contains(.isScam)
default:
return false
}
}
var isFake: Bool {
switch self {
case let user as TelegramUser:
return user.flags.contains(.isFake)
case let channel as TelegramChannel:
return channel.flags.contains(.isFake)
default:
return false
}
}
var isVerified: Bool {
switch self {
case let user as TelegramUser:
return user.flags.contains(.isVerified)
case let channel as TelegramChannel:
return channel.flags.contains(.isVerified)
default:
return false
}
}
var isPremium: Bool {
switch self {
case let user as TelegramUser:
return user.flags.contains(.isPremium)
default:
return false
}
}
var isSubscription: Bool {
switch self {
case let channel as TelegramChannel:
return channel.subscriptionUntilDate != nil
default:
return false
}
}
var isCloseFriend: Bool {
switch self {
case let user as TelegramUser:
return user.flags.contains(.isCloseFriend)
default:
return false
}
}
var isCopyProtectionEnabled: Bool {
switch self {
case let group as TelegramGroup:
return group.flags.contains(.copyProtectionEnabled)
case let channel as TelegramChannel:
return channel.flags.contains(.copyProtectionEnabled)
default:
return false
}
}
func hasSensitiveContent(platform: String) -> Bool {
var restrictionInfo: PeerAccessRestrictionInfo?
switch self {
case let user as TelegramUser:
restrictionInfo = user.restrictionInfo
case let channel as TelegramChannel:
restrictionInfo = channel.restrictionInfo
default:
break
}
if let restrictionInfo, let rule = restrictionInfo.rules.first(where: { $0.reason == "sensitive" }) {
if rule.platform == "all" || rule.platform == platform {
return true
}
}
return false
}
var isForum: Bool {
if let channel = self as? TelegramChannel {
return channel.flags.contains(.isForum)
} else if let user = self as? TelegramUser, let botInfo = user.botInfo {
return botInfo.flags.contains(.hasForum)
} else {
return false
}
}
var isMonoForum: Bool {
if let channel = self as? TelegramChannel {
return channel.flags.contains(.isMonoforum)
} else {
return false
}
}
var displayForumAsTabs: Bool {
if let channel = self as? TelegramChannel, isForum {
return channel.flags.contains(.displayForumAsTabs)
} else if self is TelegramUser {
return true
} else {
return false
}
}
var isForumOrMonoForum: Bool {
if let channel = self as? TelegramChannel {
return channel.flags.contains(.isForum) || channel.flags.contains(.isMonoforum)
} else if let user = self as? TelegramUser, let botInfo = user.botInfo, botInfo.flags.contains(.hasForum) {
return true
} else {
return false
}
}
var nameColor: PeerColor? {
switch self {
case let user as TelegramUser:
if let nameColor = user.nameColor {
return nameColor
} else {
return .preset(PeerNameColor(rawValue: Int32(self.id.id._internalGetInt64Value() % 7)))
}
case let channel as TelegramChannel:
if let nameColor = channel.nameColor {
return .preset(nameColor)
} else {
return .preset(PeerNameColor(rawValue: Int32(self.id.id._internalGetInt64Value() % 7)))
}
default:
return nil
}
}
var verificationIconFileId: Int64? {
switch self {
case let user as TelegramUser:
return user.verificationIconFileId
case let channel as TelegramChannel:
return channel.verificationIconFileId
default:
return nil
}
}
var profileColor: PeerNameColor? {
switch self {
case let user as TelegramUser:
return user.profileColor
case let channel as TelegramChannel:
return channel.profileColor
default:
return nil
}
}
var effectiveProfileColor: PeerNameColor? {
switch self.emojiStatus?.content {
case let .starGift(_, _, _, _, _, _, outerColor, _, _):
return PeerNameColor.other(outerColor)
default:
break
}
switch self {
case let user as TelegramUser:
return user.profileColor
case let channel as TelegramChannel:
return channel.profileColor
default:
return nil
}
}
var hasCustomNameColor: Bool {
let defaultNameColor = PeerNameColor(rawValue: Int32(self.id.id._internalGetInt64Value() % 7))
if self.nameColor != .preset(defaultNameColor) {
return true
}
return false
}
var emojiStatus: PeerEmojiStatus? {
switch self {
case let user as TelegramUser:
return user.emojiStatus
case let channel as TelegramChannel:
return channel.emojiStatus
default:
return nil
}
}
var backgroundEmojiId: Int64? {
switch self {
case let user as TelegramUser:
return user.backgroundEmojiId
case let channel as TelegramChannel:
return channel.backgroundEmojiId
default:
return nil
}
}
var profileBackgroundEmojiId: Int64? {
if let emojiStatus {
switch emojiStatus.content {
case let .starGift(_, _, _, _, patternFileId, _, _, _, _):
return patternFileId
default:
break
}
}
switch self {
case let user as TelegramUser:
return user.profileBackgroundEmojiId
case let channel as TelegramChannel:
return channel.profileBackgroundEmojiId
default:
return nil
}
}
}
public extension TelegramPeerUsername {
var isActive: Bool {
return self.flags.contains(.isActive)
}
}
public extension PeerId {
var isGroupOrChannel: Bool {
switch self.namespace {
case Namespaces.Peer.CloudGroup, Namespaces.Peer.CloudChannel:
return true
default:
return false
}
}
}
public func peerDebugDisplayTitles(_ peerIds: [PeerId], _ dict: SimpleDictionary<PeerId, Peer>) -> String {
var peers: [Peer] = []
for id in peerIds {
if let peer = dict[id] {
peers.append(peer)
}
}
return peerDebugDisplayTitles(peers)
}
public func peerDebugDisplayTitles(_ peers: [Peer]) -> String {
if peers.count == 0 {
return ""
} else {
var string = ""
var first = true
for peer in peers {
if first {
first = false
} else {
string.append(", ")
}
string.append(peer.debugDisplayTitle)
}
return string
}
}
public func messageMainPeer(_ message: EngineMessage) -> EnginePeer? {
if let peer = message.peers[message.id.peerId] {
if let peer = peer as? TelegramSecretChat {
return message.peers[peer.regularPeerId].flatMap(EnginePeer.init)
} else {
return EnginePeer(peer)
}
} else {
return nil
}
}
public func peerViewMainPeer(_ view: PeerView) -> Peer? {
if let peer = view.peers[view.peerId] {
if let peer = peer as? TelegramSecretChat {
return view.peers[peer.regularPeerId]
} else {
return peer
}
} else {
return nil
}
}
public func peerViewMonoforumMainPeer(_ view: PeerView) -> Peer? {
if let peer = peerViewMainPeer(view) {
if let channel = peer as? TelegramChannel, channel.flags.contains(.isMonoforum), let linkedMonoforumId = channel.linkedMonoforumId {
return view.peers[linkedMonoforumId]
} else {
return nil
}
} else {
return nil
}
}
public extension RenderedPeer {
convenience init(message: Message) {
var peers = SimpleDictionary<PeerId, Peer>()
let peerId = message.id.peerId
if let peer = message.peers[peerId] {
peers[peer.id] = peer
if let peer = peer as? TelegramSecretChat {
if let regularPeer = message.peers[peer.regularPeerId] {
peers[regularPeer.id] = regularPeer
}
} else if let peer = peer as? TelegramChannel, peer.isMonoForum, let linkedMonoforumId = peer.linkedMonoforumId {
if let regularPeer = message.peers[linkedMonoforumId] {
peers[regularPeer.id] = regularPeer
}
}
}
self.init(peerId: message.id.peerId, peers: peers, associatedMedia: [:])
}
var chatMainPeer: Peer? {
if let peer = self.peers[self.peerId] {
if let peer = peer as? TelegramSecretChat {
return self.peers[peer.regularPeerId]
} else {
return peer
}
} else {
return nil
}
}
var chatOrMonoforumMainPeer: Peer? {
if let channel = self.peer as? TelegramChannel {
if channel.flags.contains(.isMonoforum), let linkedMonoforumId = channel.linkedMonoforumId {
return self.peers[linkedMonoforumId]
} else {
return self.chatMainPeer
}
} else {
return self.chatMainPeer
}
}
}
public func isServicePeer(_ peer: Peer) -> Bool {
if let peer = peer as? TelegramUser {
if peer.id.isReplies {
return true
}
if peer.id.isVerificationCodes {
return true
}
return (peer.id.namespace == Namespaces.Peer.CloudUser && (peer.id.id._internalGetInt64Value() == 777000 || peer.id.id._internalGetInt64Value() == 333000))
}
return false
}
public extension PeerId {
var isTelegramNotifications: Bool {
if self.namespace == Namespaces.Peer.CloudUser {
if self.id._internalGetInt64Value() == 777000 {
return true
}
}
return false
}
var isReplies: Bool {
if self.namespace == Namespaces.Peer.CloudUser {
if self.id._internalGetInt64Value() == 708513 || self.id._internalGetInt64Value() == 1271266957 {
return true
}
}
return false
}
var isVerificationCodes: Bool {
if self.namespace == Namespaces.Peer.CloudUser {
if self.id._internalGetInt64Value() == 489000 {
return true
}
}
return false
}
var isRepliesOrVerificationCodes: Bool {
return self.isReplies || self.isVerificationCodes
}
func isRepliesOrSavedMessages(accountPeerId: PeerId) -> Bool {
if accountPeerId == self {
return true
} else if self.isReplies {
return true
} else if self.isVerificationCodes {
return true
} else {
return false
}
}
var isImport: Bool {
if self.namespace == Namespaces.Peer.CloudUser {
if self.id._internalGetInt64Value() == 225079 {
return true
}
}
return false
}
var isAnonymousSavedMessages: Bool {
if self.namespace == Namespaces.Peer.CloudUser {
if self.id._internalGetInt64Value() == anonymousSavedMessagesId {
return true
}
}
return false
}
var isSecretChat: Bool {
return self.namespace == Namespaces.Peer.SecretChat
}
}
@@ -0,0 +1,50 @@
import Foundation
import Postbox
import SwiftSignalKit
public func _internal_storedMessageFromSearchPeer(postbox: Postbox, peer: Peer) -> Signal<Peer, NoError> {
return postbox.transaction { transaction -> Peer in
if transaction.getPeer(peer.id) == nil {
updatePeersCustom(transaction: transaction, peers: [peer], update: { _, updatedPeer in
return updatedPeer
})
}
if let group = transaction.getPeer(peer.id) as? TelegramGroup, let migrationReference = group.migrationReference {
if let migrationPeer = transaction.getPeer(migrationReference.peerId) {
return migrationPeer
} else {
return peer
}
}
return peer
}
}
func _internal_storedMessageFromSearchPeers(account: Account, peers: [Peer]) -> Signal<Never, NoError> {
return account.postbox.transaction { transaction -> Void in
for peer in peers {
if transaction.getPeer(peer.id) == nil {
updatePeersCustom(transaction: transaction, peers: [peer], update: { _, updatedPeer in
return updatedPeer
})
}
}
}
|> ignoreValues
}
func _internal_storeMessageFromSearch(transaction: Transaction, message: Message) {
if transaction.getMessage(message.id) == nil {
for (_, peer) in message.peers {
if transaction.getPeer(peer.id) == nil {
updatePeersCustom(transaction: transaction, peers: [peer], update: { _, updatedPeer in
return updatedPeer
})
}
}
let storeMessage = StoreMessage(id: .Id(message.id), customStableId: nil, globallyUniqueId: message.globallyUniqueId, groupingKey: message.groupingKey, threadId: message.threadId, timestamp: message.timestamp, flags: StoreMessageFlags(message.flags), tags: message.tags, globalTags: message.globalTags, localTags: message.localTags, forwardInfo: message.forwardInfo.flatMap(StoreMessageForwardInfo.init), authorId: message.author?.id, text: message.text, attributes: message.attributes, media: message.media)
let _ = transaction.addMessages([storeMessage], location: .Random)
}
}
@@ -0,0 +1 @@
import Foundation
@@ -0,0 +1,92 @@
import Foundation
import FlatBuffers
import FlatSerialization
import Postbox
public func TelegramMedia_parse(flatBuffersObject: TelegramCore_Media) throws -> Media {
//TODO:release support other media types
switch flatBuffersObject.valueType {
case .mediaTelegrammediafile:
guard let value = flatBuffersObject.value(type: TelegramCore_Media_TelegramMediaFile.self) else {
throw FlatBuffersError.missingRequiredField()
}
return try TelegramMediaFile(flatBuffersObject: value.file)
case .mediaTelegrammediaimage:
guard let value = flatBuffersObject.value(type: TelegramCore_Media_TelegramMediaImage.self) else {
throw FlatBuffersError.missingRequiredField()
}
return try TelegramMediaImage(flatBuffersObject: value.image)
case .none_:
throw FlatBuffersError.missingRequiredField()
}
}
public func TelegramMedia_serialize(media: Media, flatBuffersBuilder builder: inout FlatBufferBuilder) -> Offset? {
//TODO:release support other media types
switch media {
case let file as TelegramMediaFile:
let fileOffset = file.encodeToFlatBuffers(builder: &builder)
let start = TelegramCore_Media_TelegramMediaFile.startMedia_TelegramMediaFile(&builder)
TelegramCore_Media_TelegramMediaFile.add(file: fileOffset, &builder)
let offset = TelegramCore_Media_TelegramMediaFile.endMedia_TelegramMediaFile(&builder, start: start)
return TelegramCore_Media.createMedia(&builder, valueType: .mediaTelegrammediafile, valueOffset: offset)
case let image as TelegramMediaImage:
let imageOffset = image.encodeToFlatBuffers(builder: &builder)
let start = TelegramCore_Media_TelegramMediaImage.startMedia_TelegramMediaImage(&builder)
TelegramCore_Media_TelegramMediaImage.add(image: imageOffset, &builder)
let offset = TelegramCore_Media_TelegramMediaImage.endMedia_TelegramMediaImage(&builder, start: start)
return TelegramCore_Media.createMedia(&builder, valueType: .mediaTelegrammediaimage, valueOffset: offset)
default:
assert(false)
return nil
}
}
public enum TelegramMedia {
public struct Accessor {
let _wrappedMedia: Media?
let _wrapped: TelegramCore_Media?
public init(_ wrapped: TelegramCore_Media) {
self._wrapped = wrapped
self._wrappedMedia = nil
}
public init(_ wrapped: Media) {
self._wrapped = nil
self._wrappedMedia = wrapped
}
public func _parse() -> Media {
if let _wrappedMedia = self._wrappedMedia {
return _wrappedMedia
} else {
return try! TelegramMedia_parse(flatBuffersObject: self._wrapped!)
}
}
}
}
public extension TelegramMedia.Accessor {
var id: MediaId? {
//TODO:release support other media types
if let _wrappedMedia = self._wrappedMedia {
return _wrappedMedia.id
}
switch self._wrapped!.valueType {
case .mediaTelegrammediafile:
guard let value = self._wrapped!.value(type: TelegramCore_Media_TelegramMediaFile.self) else {
return nil
}
return MediaId(value.file.fileId)
case .mediaTelegrammediaimage:
guard let value = self._wrapped!.value(type: TelegramCore_Media_TelegramMediaImage.self) else {
return nil
}
return MediaId(value.image.imageId)
case .none_:
return nil
}
}
}
@@ -0,0 +1,122 @@
import Foundation
import Postbox
import TelegramApi
func updateMessageMedia(transaction: Transaction, id: MediaId, media: Media?) {
let updatedMessageIndices = transaction.updateMedia(id, update: media)
for index in updatedMessageIndices {
transaction.updateMessage(index.id, update: { currentMessage in
var textEntities: [MessageTextEntity]?
for attribute in currentMessage.attributes {
if let attribute = attribute as? TextEntitiesMessageAttribute {
textEntities = attribute.entities
break
}
}
let (tags, _) = tagsForStoreMessage(incoming: currentMessage.flags.contains(.Incoming), attributes: currentMessage.attributes, media: currentMessage.media, textEntities: textEntities, isPinned: currentMessage.tags.contains(.pinned))
if tags == currentMessage.tags {
return .skip
}
var storeForwardInfo: StoreMessageForwardInfo?
if let forwardInfo = currentMessage.forwardInfo {
storeForwardInfo = StoreMessageForwardInfo(authorId: forwardInfo.author?.id, sourceId: forwardInfo.source?.id, sourceMessageId: forwardInfo.sourceMessageId, date: forwardInfo.date, authorSignature: forwardInfo.authorSignature, psaType: forwardInfo.psaType, flags: forwardInfo.flags)
}
return .update(StoreMessage(id: currentMessage.id, customStableId: nil, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, threadId: currentMessage.threadId, timestamp: currentMessage.timestamp, flags: StoreMessageFlags(currentMessage.flags), tags: tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: currentMessage.attributes, media: currentMessage.media))
})
}
}
struct ReplyThreadUserMessage {
var id: PeerId
var messageId: MessageId
var isOutgoing: Bool
}
func updateMessageThreadStats(transaction: Transaction, threadKey: MessageThreadKey, removedCount: Int, addedMessagePeers: [ReplyThreadUserMessage]) {
updateMessageThreadStatsInternal(transaction: transaction, threadKey: threadKey, removedCount: removedCount, addedMessagePeers: addedMessagePeers, allowChannel: false)
}
private func updateMessageThreadStatsInternal(transaction: Transaction, threadKey: MessageThreadKey, removedCount: Int, addedMessagePeers: [ReplyThreadUserMessage], allowChannel: Bool) {
guard let channel = transaction.getPeer(threadKey.peerId) as? TelegramChannel else {
return
}
var isGroup = true
if case .broadcast = channel.info {
isGroup = false
if !allowChannel {
return
}
}
var channelThreadMessageId: MessageId?
func mergeLatestUsers(current: [PeerId], added: [PeerId], isGroup: Bool, isEmpty: Bool) -> [PeerId] {
if isEmpty {
return []
}
if isGroup {
return current
}
var current = current
for i in 0 ..< min(3, added.count) {
let peerId = added[added.count - 1 - i]
if let index = current.firstIndex(of: peerId) {
current.remove(at: index)
current.insert(peerId, at: 0)
} else {
if current.count >= 3 {
current.removeLast()
}
current.insert(peerId, at: 0)
}
}
return current
}
transaction.updateMessage(MessageId(peerId: threadKey.peerId, namespace: Namespaces.Message.Cloud, id: Int32(clamping: threadKey.threadId)), update: { currentMessage in
var attributes = currentMessage.attributes
loop: for j in 0 ..< attributes.count {
if let attribute = attributes[j] as? ReplyThreadMessageAttribute {
var countDifference = -removedCount
for addedMessage in addedMessagePeers {
if let maxMessageId = attribute.maxMessageId {
if addedMessage.messageId.id > maxMessageId {
countDifference += 1
}
} else {
countDifference += 1
}
}
let count = max(0, attribute.count + Int32(countDifference))
var maxMessageId = attribute.maxMessageId
var maxReadMessageId = attribute.maxReadMessageId
if let maxAddedId = addedMessagePeers.map({ $0.messageId.id }).max() {
if let currentMaxMessageId = maxMessageId {
maxMessageId = max(currentMaxMessageId, maxAddedId)
} else {
maxMessageId = maxAddedId
}
}
if let maxAddedReadId = addedMessagePeers.filter({ $0.isOutgoing }).map({ $0.messageId.id }).max() {
if let currentMaxMessageId = maxReadMessageId {
maxReadMessageId = max(currentMaxMessageId, maxAddedReadId)
} else {
maxReadMessageId = maxAddedReadId
}
}
attributes[j] = ReplyThreadMessageAttribute(count: count, latestUsers: mergeLatestUsers(current: attribute.latestUsers, added: addedMessagePeers.map({ $0.id }), isGroup: isGroup, isEmpty: count == 0), commentsPeerId: attribute.commentsPeerId, maxMessageId: maxMessageId, maxReadMessageId: maxReadMessageId)
} else if let attribute = attributes[j] as? SourceReferenceMessageAttribute {
channelThreadMessageId = attribute.messageId
}
}
return .update(StoreMessage(id: currentMessage.id, customStableId: nil, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, threadId: currentMessage.threadId, timestamp: currentMessage.timestamp, flags: StoreMessageFlags(currentMessage.flags), tags: currentMessage.tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: currentMessage.forwardInfo.flatMap(StoreMessageForwardInfo.init), authorId: currentMessage.author?.id, text: currentMessage.text, attributes: attributes, media: currentMessage.media))
})
if let channelThreadMessageId = channelThreadMessageId {
updateMessageThreadStatsInternal(transaction: transaction, threadKey: MessageThreadKey(peerId: channelThreadMessageId.peerId, threadId: Int64(channelThreadMessageId.id)), removedCount: removedCount, addedMessagePeers: addedMessagePeers, allowChannel: true)
}
}