Search code examples
.netvb.nettelegraminstant-messagingwtelegramclient

Retrieve the forum topics of a channel/group in Telegram using WTelegramClient API


I'm using WTelegramClient C# API under VB.NET in .NET 4.8. I would like to implement an helper function that will retrieve the forum topics of the specified channel (group).

I think this could be done via the Channels_GetForumTopics function that takes a InputChannelBase object as argument, and my biggest problem I think it is not understanding how to obtain the required access hash value of a specific ChatBase object to create an instance of the InputChannel type (which inherits from InputChannelBase).

...Or how to directly convert a ChatBase object to InputChannel to pass it to the Channels_GetForumTopics function.

The FAQ of WTelegramClient API does not help me to understand it.


This what I've achieved for now:

Private Async Sub Test()

    Dim clientConfig As New TelegramClientConfig With {
        .ApiId = "###",
        .ApiHash = "###",
        .FirstName = "###",
        .LastName = "###",
        .Password = "###",
        .PhoneNumber = "###",
        .VerificationCode = "###"
    }

    Dim client As WTelegram.Client = WTelegramHelper.CreateClient(clientConfig)

    Dim myUser As TL.User = Await client.LoginUserIfNeeded()
    Dim myUserId As Long = WTelegramHelper.GetIdPropertyValue(myUser)
    Console.WriteLine($"We are logged-in as user: {myUser.MainUsername}, id: {myUserId}")

    Dim sourceGroup As TL.ChatBase = 
        (Await WTelegramHelper.GetChannelsByTitleAsync(client, title:="Aliens Rar 👽")
        ).Single()
    Dim sourceGroupId As Long = sourceGroup.ID

    Dim sourceGroupTopics As ReadOnlyCollection(Of TL.ForumTopicBase) = 
        Await WTelegramHelper.GetGroupTopicsAsync(sourceGroup)

End Sub

And for the helper function that should retrieve the forum topics of a specific channel (group):

Public Shared Async Function GetChannelTopicsAsync(client As WTelegram.Client, channel As TL.ChatBase) _
As Task(Of ReadOnlyCollection(Of TL.ForumTopic))

    Dim access_hash As Long = ????
    Dim inputChannel As New InputChannel(channel.ID, access_hash)

    Dim msgs_ForumTopics As Messages_ForumTopics = 
        Await client.Channels_GetForumTopics(inputChannel)

    Dim topicsBase As TL.ForumTopicBase() = msgs_ForumTopics.topics

    Dim topics As List(Of TL.ForumTopic) =
        Array.ConvertAll(topicsBase, Function(topicBase) DirectCast(topicBase, ForumTopic)).ToList()

    ' Suggestion to implement custom ordering here.
    topics.Sort(Comparer(Of TL.ForumTopic).Default)

    Return topics.AsReadOnly()

End Function

Note: I'm not sure if I should rely on TL.ForumTopicBase class, or TL.ForumTopic class, my subsequent intention is to enumerate the messages (from all users) that belongs to a specific topic from the retrieved ones... so I would like to get a collection of the proper class that fits my needs.


Additional (own) helper code used:

Public Class TelegramClientConfig
    Public Property ApiId As String
    Public Property ApiHash As String
    Public Property PhoneNumber As String
    Public Property VerificationCode As String
    Public Property FirstName As String
    Public Property LastName As String
    Public Property Password As String
End Class
Public NotInheritable Class WTelegramHelper

    ''' <summary>
    ''' Prevents a default instance of the <see cref="WTelegramHelper"/> class from being created.
    ''' </summary>
    <DebuggerNonUserCode>
    Private Sub New()
    End Sub

    ''' <summary>
    ''' Creates an instance of the <see cref="WTelegram.Client"/> class using the 
    ''' configuration specified in the provided <see cref="TelegramClientConfig"/> object.
    ''' </summary>
    ''' 
    ''' <param name="clientConfig">
    ''' A <see cref="TelegramClientConfig"/> object containing the 
    ''' client configuration settings.
    ''' </param>
    ''' 
    ''' <returns>
    ''' An instance of the <see cref="WTelegram.Client"/> object configured 
    ''' based on the provided <see cref="TelegramClientConfig"/> object.
    ''' </returns>
    <DebuggerStepThrough>
    Public Shared Function CreateClient(clientConfig As TelegramClientConfig) As WTelegram.Client

        Dim configProvider As Func(Of String, String) =
             Function(key As String) As String
                 Select Case key.ToLower()
                     Case "api_id"
                         Return clientConfig.ApiId
                     Case "api_hash"
                         Return clientConfig.ApiHash
                     Case "phone_number"
                         Return clientConfig.PhoneNumber
                     Case "verification_code"
                         Return clientConfig.VerificationCode
                     Case "first_name"
                         Return clientConfig.FirstName ' If sign-up is required.
                     Case "last_name"
                         Return clientConfig.LastName  ' If sign-up is required.
                     Case "password"
                         Return clientConfig.Password  ' If user has enabled 2FA.
                     Case Else
                         Return Nothing ' Let WTelegramClient decide the default config.
                 End Select
             End Function

        Dim client As New WTelegram.Client(configProvider)
        Return client

    End Function

    ''' <summary>
    ''' Retrieves the value of the "ID" property (uppercase) from an object's type that
    ''' implements the <see cref="IPeerInfo"/> interface (e.g., <see cref="TL.User"/>).
    ''' <para></para>
    ''' This method accommodates the non-VB.NET compliant member naming convention 
    ''' used in the WTelegramClient API, originally written in C#.
    ''' </summary>
    ''' 
    ''' <param name="sourceObject">
    ''' The source object implementing the <see cref="IPeerInfo"/> interface.
    ''' </param>
    ''' 
    ''' <returns>
    ''' The value of the <see cref="IPeerInfo.ID"/> property from the source object.
    ''' </returns>
    <DebuggerStepThrough>
    Public Shared Function GetIdPropertyValue(sourceObject As IPeerInfo) As Long

        Dim propInfo As PropertyInfo =
            GetType(IPeerInfo).
            GetProperty("ID", BindingFlags.Instance Or BindingFlags.Public,
                        Type.DefaultBinder, GetType(Long), Type.EmptyTypes,
                        Array.Empty(Of ParameterModifier))

        Dim value As Long = CLng(propInfo.GetValue(sourceObject))
        Return value

    End Function

    ''' <summary>
    ''' Asynchronously retrieves the <see cref="TL.ChatBase"/> instances 
    ''' that match the specified channel or group title (case-insensitive).
    ''' </summary>
    ''' 
    ''' <param name="client">
    ''' The source <see cref="WTelegram.Client"/> object used to retrieve channels.
    ''' </param>
    ''' 
    ''' <param name="title">
    ''' The title of the channel or group to search for.
    ''' </param>
    ''' 
    ''' <returns>
    ''' An asynchronous operation that yields a <see cref="ReadOnlyCollection(Of TL.ChatBase)"/> 
    ''' containing the matching channels or groups.
    ''' </returns>
    <DebuggerStepThrough>
    Public Shared Async Function GetChannelsByTitleAsync(client As WTelegram.Client,
                                                         title As String) As Task(Of ReadOnlyCollection(Of TL.ChatBase))

        Dim allChannels As Messages_Chats = Await client.Messages_GetAllChats()
        Dim retChannels As New List(Of TL.ChatBase)

        For Each channel As ChatBase In allChannels.chats.Values
            If String.Compare(channel.Title, title, StringComparison.OrdinalIgnoreCase) = 0 Then
                retChannels.Add(channel)
            End If
        Next channel

        ' Suggestion to implement custom ordering here.
        retChannels.Sort(Comparer(Of TL.ChatBase).Default)
        
        Return retChannels.AsReadOnly()

    End Function

End Class

Solution

  • I figured out that ChatBase can be casted to Channel type, so these are the main changes I did to the previous code that I posted:

    Public Shared Async Function GetChannelsByTitleAsync(client As WTelegram.Client,
                                                         title As String) As Task(Of ReadOnlyCollection(Of TL.Channel))
    
        Dim msgs_Chats As Messages_Chats = Await client.Messages_GetAllChats()
        Dim chats As TL.ChatBase() = msgs_Chats.chats.Values.ToArray()
    
        Dim retChannels As New List(Of TL.Channel)
        For Each channel As ChatBase In chats
            If String.Compare(channel.Title, title, StringComparison.OrdinalIgnoreCase) = 0 Then
                retChannels.Add(DirectCast(channel, Channel))
            End If
        Next channel
    
        ' Custom alphabetical ordering.
        retChannels.Sort(Function(channel1 As ChatBase, channel2 As ChatBase)
                             Return channel1.Title.CompareTo(channel2.Title)
                         End Function)
    
        Return retChannels.AsReadOnly()
    
    End Function
    
    Dim sourceGroup As TL.Channel =
        (Await WTelegramHelper.GetChannelsByTitleAsync(client, title:="Aliens Rar 👽")
        ).Single()
    

    At this point I have a compatible Channel object, so the rest is easy to adapt:

    Public Shared Async Function GetChannelTopicsAsync(client As WTelegram.Client, channel As TL.Channel
                                                      ) As Task(Of ReadOnlyCollection(Of TL.ForumTopic))
    
        If Not channel.IsGroup Then
            Return Nothing
        End If
    
        Dim msgs_ForumTopics As Messages_ForumTopics = Await client.Channels_GetForumTopics(channel)
        Dim forumTopics As TL.ForumTopicBase() = msgs_ForumTopics.topics
    
        Dim retTopics As New List(Of TL.ForumTopic)
        For Each topic As TL.ForumTopicBase In forumTopics
            Dim forumTopic As TL.ForumTopic = DirectCast(topic, TL.ForumTopic)
            retTopics.Add(forumTopic)
        Next topic
    
        ' Custom alphabetical ordering.
        retTopics.Sort(Function(topic1, topic2)
                           Return topic1.title.CompareTo(topic2.title)
                       End Function)
    
        Return If(Not retTopics.Any(), Nothing, retTopics.AsReadOnly())
    
    End Function
    
    Dim topics As ReadOnlyCollection(Of TL.ForumTopic) =
        Await GetChannelTopicsAsync(client, sourceGroup)
    
    For Each topic As ForumTopic In topics 
        Console.WriteLine(topic.title)
    Next
    

    UPDATE: I noticed that the Channels_GetForumTopics has an offset parameter. I suppose this should be used if there are many topics to return per request, so the code above will need little modifications in those cases.