// SPDX-License-Identifier: AGPL-3.0-or-later // Copyright (C) 2022-2026 Aykut Alparslan KOC using Ferrite.TL; using Ferrite.TL.baseLayer; using Ferrite.TL.baseLayer.dto; using Ferrite.Data.Models; namespace Ferrite.Data.Repositories; public class MessageRepository : IMessageRepository { private readonly IKVStore _store; private readonly IUpdatesStateRepository _updatesState; public MessageRepository(IKVStore store, IUpdatesStateRepository updatesState) { _store = store; _updatesState = updatesState; store.SetSchema(new TableDefinition("ferrite", "messages", new KeyDefinition("pk", new DataColumn { Name = "user_id", Type = DataType.Long }, new DataColumn { Name = "peer_type", Type = DataType.Int }, new DataColumn { Name = "peer_id", Type = DataType.Long }, new DataColumn { Name = "outgoing", Type = DataType.Bool }, new DataColumn { Name = "message_id", Type = DataType.Int }, new DataColumn { Name = "pts", Type = DataType.Int }, new DataColumn { Name = "date", Type = DataType.Long }), new KeyDefinition("by_id", new DataColumn { Name = "user_id", Type = DataType.Long }, new DataColumn { Name = "message_id", Type = DataType.Int }))); } public bool PutMessage(long userId, TLMessage message, int pts) { var metadata = GetMessageMetadata(message); using TLSavedMessage savedMessage = SavedMessage.Builder() .Pts(pts) .OriginalMessage(message.AsSpan()) .Build(); _store.Put(savedMessage.AsSpan().ToArray(), userId, metadata.PeerType, metadata.PeerId, metadata.Outgoing, MessageIds.GetId(message), pts, metadata.Date); _updatesState.PutPts(userId, pts); return true; } private static (int PeerType, long PeerId, bool Outgoing, long Date) GetMessageMetadata(TLMessage message) { return message.Type switch { TLMessage.MessageType.Message => GetMessageMetadata(message.AsMessage()), TLMessage.MessageType.MessageService => GetMessageMetadata(message.AsMessageService()), _ => (0, 0, false, DateTimeOffset.Now.ToUnixTimeSeconds()) }; } private static (int PeerType, long PeerId, bool Outgoing, long Date) GetMessageMetadata(Message message) { var peer = message.Get_PeerIdView(); if (peer.Type is TLPeer.PeerType.PeerChat or TLPeer.PeerType.PeerChannel) { return ((int)peer.Type, GetPeerId(peer), message.OutProperty, message.Date); } if (message.OutProperty) { return ((int)peer.Type, GetPeerId(peer), true, message.Date); } var from = message.Get_FromIdView(); return ((int)from.Type, GetPeerId(from), false, message.Date); } private static (int PeerType, long PeerId, bool Outgoing, long Date) GetMessageMetadata(MessageService message) { var peer = message.Get_PeerIdView(); if (peer.Type is TLPeer.PeerType.PeerChat or TLPeer.PeerType.PeerChannel) { return ((int)peer.Type, GetPeerId(peer), message.OutProperty, message.Date); } if (message.OutProperty) { return ((int)peer.Type, GetPeerId(peer), true, message.Date); } var from = message.Get_FromIdView(); return ((int)from.Type, GetPeerId(from), false, message.Date); } private static long GetPeerId(PeerView peer) => peer.Type switch { TLPeer.PeerType.PeerUser => peer.AsPeerUser().UserId, TLPeer.PeerType.PeerChat => peer.AsPeerChat().ChatId, TLPeer.PeerType.PeerChannel => peer.AsPeerChannel().ChannelId, _ => 0 }; public IReadOnlyCollection GetMessages(long userId, TLInputPeer? peerId = null) { List messages = new(); if (peerId != null) { List parameters = new List(); parameters.Add(userId); parameters.Add((int)peerId.Value.Type); parameters.Add(GetPeerId(peerId.Value)); var results = _store.Iterate(parameters.ToArray()); foreach (var val in results) { messages.Add(new TLSavedMessage(val, 0 ,val.Length)); } } else { var results = _store.Iterate(userId); foreach (var val in results) { messages.Add(new TLSavedMessage(val, 0 ,val.Length)); } } messages = messages.OrderByDescending(m => MessageIds.GetId(m.AsSavedMessage().Get_OriginalMessage())).ToList(); return messages; } public async ValueTask> GetMessagesAsync(long userId, TLInputPeer? peerId = null) { List messages = new(); if (peerId != null) { List parameters = new List(); parameters.Add(userId); parameters.Add((int)peerId.Value.Type); parameters.Add(GetPeerId(peerId.Value)); var results = _store.IterateAsync(parameters.ToArray()); await foreach (var val in results) { messages.Add(new TLSavedMessage(val, 0 ,val.Length)); } } else { var results = _store.IterateAsync(userId); await foreach (var val in results) { try { messages.Add(new TLSavedMessage(val, 0 ,val.Length)); } catch (Exception e) { Console.WriteLine(e); throw; } } } messages = messages.OrderByDescending(m => MessageIds.GetId(m.AsSavedMessage().Get_OriginalMessage())).ToList(); return messages; } public IReadOnlyCollection GetMessages(long userId, int pts, int maxPts, DateTimeOffset date) { List messages = new(); var results = _store.Iterate(userId); foreach (var val in results) { var message = new TLSavedMessage(val, 0, val.Length); var messagePts = message.AsSavedMessage().Pts; if (messagePts >= pts && messagePts <= maxPts && GetMessageDate(message.AsSavedMessage().Get_OriginalMessage()) <= date.ToUnixTimeSeconds()) { messages.Add(message); } } messages = messages.OrderByDescending(m => MessageIds.GetId(m.AsSavedMessage().Get_OriginalMessage())).ToList(); return messages; } public async ValueTask> GetMessagesAsync(long userId, int pts, int maxPts, DateTimeOffset date) { List messages = new(); var results = _store.IterateAsync(userId); await foreach (var val in results) { var message = new TLSavedMessage(val, 0, val.Length); var messagePts = message.AsSavedMessage().Pts; if (messagePts >= pts && messagePts <= maxPts && GetMessageDate(message.AsSavedMessage().Get_OriginalMessage()) <= date.ToUnixTimeSeconds()) { messages.Add(message); } } messages = messages.OrderByDescending(m => MessageIds.GetId(m.AsSavedMessage().Get_OriginalMessage())).ToList(); return messages; } private static int GetMessageDate(TLMessage message) => message.Type switch { TLMessage.MessageType.Message => message.AsMessage().Date, TLMessage.MessageType.MessageService => message.AsMessageService().Date, _ => 0 }; public TLSavedMessage? GetMessage(long userId, int messageId) { var data = _store.GetBySecondaryIndex("by_id", userId, messageId); if (data == null) { return null; } return new TLSavedMessage(data, 0, data.Length); } public async ValueTask GetMessageAsync(long userId, int messageId) { var data = await _store.GetBySecondaryIndexAsync("by_id", userId, messageId); if (data == null) { return null; } return new TLSavedMessage(data, 0, data.Length); } public bool DeleteMessage(long userId, int id) { return _store.DeleteBySecondaryIndex("by_id", userId, id); } public async ValueTask DeleteMessageAsync(long userId, int id) { return await _store.DeleteBySecondaryIndexAsync("by_id", userId, id); } private static long GetPeerId(TLInputPeer p) => p.Type switch { TLInputPeer.InputPeerType.InputPeerChat => p.AsInputPeerChat().ChatId, TLInputPeer.InputPeerType.InputPeerUser => p.AsInputPeerUser().UserId, TLInputPeer.InputPeerType.InputPeerChannel => p.AsInputPeerChannel().ChannelId, TLInputPeer.InputPeerType.InputPeerUserFromMessage => p.AsInputPeerUserFromMessage().UserId, TLInputPeer.InputPeerType.InputPeerChannelFromMessage => p.AsInputPeerChannelFromMessage().ChannelId, _ => 0 }; }