Search code examples
c#telegramchattdlib

Unexpected Chats in "UpdateNewChat" Event in TDLib


I am experiencing an issue with the UpdateNewChat event when using the LoadChats method in TDLib. Occasionally, I receive updates for chats that I am no longer a member of or have never been a member of. This includes archived chats, deleted chats, and public chats that I previously left. I was also encountering chats that were actually groups connected to channels (in these channels, however, I am a member).

Steps to Reproduce:

  1. Call the LoadChats method.
  2. Observe the UpdateNewChat, UpdateChatPosition events.

Expected Behavior: Only receive updates for chats that I am currently a member of.

Actual Behavior: Receiving updates for chats that I am not a member of or have left in the past.

Environment:

tdlib.native version: 1.8.29(from nuget)

tdlib version: 1.8.29(from nuget)

Programming language: C# 11

(.NET8)

Question:

What exactly does the UpdateNewChat event send, and how does it work? The documentation mentions that this event should be handled when a chat is loaded or created, but it does not explain where these chats are coming from. I expected to receive only chats that I am currently a member of (even if they are archived). Is this behavior expected, or might there be an issue with how I'm handling this event? Why would this event bring chats that I am no longer a member of?

My Update handler:

internal static class CommonUpdateHandlerMethods
{
    public static async void UpdateChatListHanlder(object sender, TdApi.Update e)
    {
        var client = (TdClient)sender;
        switch (e)
        {
            case TdApi.Update.UpdateNewChat newChat:
                {
                    await UpdateNewChat(client, newChat.Chat.Id);
                    break;
                }
            case TdApi.Update.UpdateChatPosition chatPos:
                {
                    if (chatPos.Position.Order == CommonConstants.TdApi.ShouldRemoveChat)//ShouldRemoveChat = 0
                    {
                        switch (chatPos.Position.List)
                        {
                            case TdApi.ChatList.ChatListArchive:
                                {
                                    CommonData.ArchiveChatList.Remove(chatPos.ChatId, out _);
                                    break;
                                }
                            case TdApi.ChatList.ChatListMain:
                                {
                                    CommonData.MainChatList.Remove(chatPos.ChatId, out _);
                                    break;
                                }
                        }
                    }
                    else
                    {
                        await UpdateNewChat(client, chatPos.ChatId);
                    }
                    break;
                }
        }
    }

    private static async Task UpdateNewChat(TdClient client, long newChatId)
    {
        var chat = await client.GetChatAsync(newChatId);
        if (chat.ChatLists.Any(list => list is TdApi.ChatList.ChatListArchive))
        {
            CommonData.ArchiveChatList.AddOrUpdate(chat.Id, chat, (id, chat) => chat);
        }
        else if (chat.ChatLists.Any(list => list is TdApi.ChatList.ChatListMain))
        {
            CommonData.MainChatList.AddOrUpdate(chat.Id, chat, (id, chat) => chat);
        }
        else
        {
            CommonData.UnkownChatList.AddOrUpdate(chat.Id, chat, (id, chat) => chat);
        }
    }
}

CommonData class:

 internal static class CommonData
 {
   public static ConcurrentDictionary<long, TdApi.Chat> MainChatList { get; set; } = [];
   public static ConcurrentDictionary<long, TdApi.Chat> ArchiveChatList { get; set; } = [];
   public static ConcurrentDictionary<long, TdApi.Chat> UnkownChatList { get; set; } = [];
 }

Helper class:

internal static class Helper
{
    private static async Task InnerLoadChatsAsync(TdClient client, TdApi.ChatList chatList, int delay)
    {
        try
        {
            while (true)
            {
                await client.LoadChatsAsync(chatList, CommonConstants.DefaultLimit);
                Thread.Sleep(delay);
            }
        }
        catch (Exception ex)
        {
            if (ex is TdException tde && tde.Error.Code == CommonConstants.ErrorCodes.NotFound)//NotFound = 404
                return;
            else
                throw;
        }
    }
    internal static async Task LoadChatsAsync(TdClient client, int delay = CommonConstants.DefaultLoadChatDelay)//DefaultLoadChatDelay = 100
    {
        await InnerLoadChatsAsync(client, new TdApi.ChatList.ChatListArchive(), delay);
        await InnerLoadChatsAsync(client, new TdApi.ChatList.ChatListMain(), delay);
    }
}

My tester method:

public static async Task TestLoadChats(TdClient client)
{
    await Helper.LoadChatsAsync(client);
    Console.WriteLine("CHATM[{0}]", CommonData.MainChatList.Count);
    Console.WriteLine("CHATA[{0}]", CommonData.ArchiveChatList.Count);
    using var sw = new StreamWriter("finfo.txt");
    foreach (var chat in CommonData.UnkownChatList)
    {
        sw.WriteLine("{0}\n {1}\n {2}\n--------------------------------------", chat.Key, chat.Value.Title, chat.Value.Type.DataType);
    }
    Console.WriteLine("CHATU[{0}]", CommonData.UnkownChatList.Count);
}

Solution

  • The updateNewChat message is sent every time a new new chat is discovered. This includes also chats referenced by messages sent by your connections.

    TDLib getting started page says that:

    [An Application] must maintain a cache of chats received through this update.

    After creating a cache of all the discovered chats, you can filter the ones of which you are currently a member by detecting the updateChatAddedToList messages. Specifically, you are interested in the chats that belong to chatListMain.

    This is an example of this kind of messages:

    {
      "@type": "updateChatAddedToList",
      "chat_id": 1111111,
      "chat_list": {
        "@type": "chatListMain"
      },
      "@client_id": 1
    }