Update handling in WTelegramClient
Published: 2024-01-09 10:15:35

in the [previous article](https://dev.to/mrali109/introduction-to-c-user-bots-with-wtelegramclient-2c5c) we talked about starting to work with WTC and setting up the basic requirements. in this article we are going through the process of handling updates for beginners. lets get started.


## setup

we have our basic hello world code from previous article here:

```csharp
using var client = new WTelegram.Client(Config);

await client.LoginUserIfNeeded();
return;

static string? Config(string what)
{
    switch (what)
    {
        case "session_pathname":  return Path.Join(Environment.CurrentDirectory, "mysessionFILE.session");
        case "api_id":            return "YOUR_API_ID";
        case "api_hash":          return "YOUR_API_HASH";
        case "phone_number":      return "+12025550156";
        case "verification_code": Console.Write("Code: "); return Console.ReadLine()!;
        case "first_name":        return "John";    // if sign-up is required
        case "last_name":         return "Doe";     // if sign-up is required
        case "password":          return "secret!"; // if user has enabled 2FA
        default:                  return null;
    }
}
```
[Github](https://github.com/MrAliSalehi/HelloWtc/blob/master/Program.cs)

don't forget to replace your phone number, your [api_id & api_hash](https://dev.to/mrali109/introduction-to-c-user-bots-with-wtelegramclient-2c5c#:~:text=2.required%20properties) and also your 2FA password.

## subscribing to updates

the first thing we need to do, is to tell the WTC that we want to listen to the incoming updates, to do that we simply subscribe to an Event called `OnUpdate`:

```csharp
client.OnUpdate += UpdateHandler;
async Task UpdateHandler(UpdatesBase updatesBase) {
    return;
}
```
lets talk about what just happened here, first we have our `client` which at this point "might" be logged-in, we don't know yet, when we are subscribing (`+=`) to an Event which **gives** us an object of type `UpdatesBase` and needs us to return a `Task`, basically a `Func<UpdatesBase, Task>` (we'll talk about that in the future) and then we are returning right after the subscription.
> if your code is not async you can do `return Task.CompletedTask;` and remove the `async`.

## Updates

Telegram Type system by many people is considered Complex, or Confusing, and maybe it is, you just have to work with it for a relatively long time (that literally the definition of complexity but whatever). but I believe it would be much much more complex IF we didn't have `pattern-matching` in C#, BUT WE DO!

lets look at the basic types in WTC.
first we have something called `UpdatesBase`, It derives from `IObject`. an empty interface, this is due to serialization reasons, but you don't need to worry about this, you just need know this:

> every **Telegram** object inside the WTC, drives from `IObject`.

lets look at its fields:

```csharp
Update[] UpdateList;
Dictionary<long, User> Users;
Dictionary<long, ChatBase> Chats;
DateTime Date;
```
we are going to talk about the most important one: `UpdateList`,
as the name says, this is an list of `Update`.

all the updates that we need are going to be inside this array.
if you receive one message, this is going to be an array with one item inside it, if you get 10 messages in a very short period of time, lets say in `200 milli seconds`, this array is going to be filled with `10` items.

but if you are getting 1 message every seconds, your `UpdateHandler` will be called 10 times with 1 message each time.

so it is important to check the whole array of update and not just the `first` or `last` item.
so we'll write:

```csharp
async Task UpdateHandler(UpdatesBase updatesBase)
{
    foreach (Update? update in updatesBase.UpdateList)
    {
        
    }
}
```
take a closer look, the `update` variable is an `Update?`. what is it?

```csharp
public abstract class Update : IObject { }
```
this is very similar to the concept of `IObject`, as you can see its an `abstract` class that has no items inside it, just like the `IObject`, this is the base definition of **every Update** inside the WTC.
remember that I said:
every **object** is an `IObject`.
but also every **update** drives from `Update`, for the same reasons!

so what should we do with it ? this is where the `pattern-matching` comes into play, we can **cast** this object to whatever we think it might be!

> you can hover over this type in your IDE and see that types it **CAN** be, for example: 
 
![types](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2yweqyf7iefdio4oiz9o.png)
_this is what it looks like in my IDE, tons of types! but don't let it scare you, we don't need all of them! in fact we only need a few!_


## reading the updates

alright lets start with something simple, we want to see all the new text messages!

we can do the following:

```csharp
foreach (Update? update in updatesBase.UpdateList)
{
    if (update is UpdateNewMessage newMessage)
    {
    }
}
```
in this code, we are **Safe** casting the `update` to the type `UpdateNewMessage ` which is responsible for the incoming & **outgoing** new messages inside a (basic) group or private chat.
with the `if statement` we indicate that if the `update` is not what we wanted, do not PANIC, don't throw any exceptions, just relax and continue to the next update, if we did something like this:

```csharp
foreach (Update? update in updatesBase.UpdateList)
{
    var newMsg = (UpdateNewMessage)update;
}
```
it would cause some problems, since we are **hard casting** here, compiler would assume that it is 100% the given type, so it wont check for anything first,  but we are not 100% sure that the update is `UpdateNewMessage`, are we? it could be one of those many types that we saw earlier, so it will throw error and potentially break our application.
so its much cleaner and safer to do the pattern matching with the if statement ( or even switch-statements if you have multiple types to handle).

lets see what we have inside the `newMessage`:

```csharp
MessageBase message;
int pts;
int pts_count;
```
_ignore the pts stuff here_

we are seeing a new type: `MessageBase`, you are going to see a lot of `..Base` types while working with Telegram API, (average OOP moment 😂)
but this one is not completely useless to us, it contains some information about the message, the 2 important ones are:
```csharp
Peer Peer;
DateTime Date;
```
the `Peer` is itself another long discussion but for now, lets say its a `Person` for the sake of simplicity, this `Peer` only has one field: `ID` of the person(a unique identifier).
and the `date` is the time when the message was **sent**.

so we can print them:

```csharp
if (update is UpdateNewMessage newMessage)
{
    var fromId = newMessage.message.Peer.ID;
    Console.WriteLine($"msg from {fromId} at {newMessage.message.Date}");
}
```

you will see something like this in the console:
```
msg from 5298595170 at 1/7/2024 6:56:44 AM
```
so we got the data and the `sender id`, but where the heck is the text? 

not that fast, there is another type

![crying kid meme](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/y8tzlwz7qg0da28k1iiq.png)

the `newMessage.message` can be one of these 3 things: 

-  `MessageEmpty`
-  `Message`
-  `MessageService`

for now, we will be using `Message`, so we can write:

```csharp
if (update is UpdateNewMessage newMessage)
{
    var fromId = newMessage.message.Peer.ID;
    Console.WriteLine($"msg from {fromId} at {newMessage.message.Date}");
    if (newMessage.message is Message msg)
    {

    }
}
```
so now the `msg` contains the information we need, but first lets make this code a bit clear and more readable, we can return early when the update is something that we don't need:

```csharp
if (update is not UpdateNewMessage newMessage) 
    continue;

var fromId = newMessage.message.Peer.ID;
Console.Write($"msg from {fromId} at {newMessage.message.Date}");

if (newMessage.message is not Message msg) 
    continue;

Console.WriteLine($" text: {msg.message}");
```

using the `not` operator we indicate that if the `update` is not what we wanted, just continue iterating over the list, and the same thing for the `newMessage.message` and then finally we can print out all the text messages!

```
msg from 1548097770 at 1/7/2024 8:44:50 AM text: HELLO WTC!
```

### The alternative

now that you've learned the update handling in the **hard way**, let me introduce you to the easy way:

there is an extension package called `WTelegramClient.Extensions.Updates`, go ahead and install it:

`> dotnet add package WTelegramClient.Extensions.Updates`

using this package you can simply do this:

```csharp
client.RegisterUpdateType<UpdateNewMessage>(async (message, updateBase) =>
{
    Console.WriteLine($"msg from {message.message.Peer.ID}");
});

//or..

client.RegisterUpdateType<UpdateNewMessage>(OnNewMessage);

async Task OnNewMessage(UpdateNewMessage message, UpdatesBase? updateBase)
{
    Console.WriteLine($"msg from {message.message.Peer.ID}");
}
```
so you can basically filter any `Update` type that you want, there are other filters for `Chat` types and more features, I recommend check it out:

[WTelegramClient.Extensions.Updates](https://github.com/MrAliSalehi/WTelegramClient.Extensions.Updates)


### different types of content

so far we've learned how to print out the text messages, but there are other things to capture (e.x Documents, Photos, Videos..etc).
we'll cover a few of them in the following section.

#### Photos

first we want to put all the new pictures inside a Directory, lets call it `photos`, so we should create it first:

```csharp
//beginning
var photoDirectory = Path.Combine(Environment.CurrentDirectory, "photos");
if (!Directory.Exists(photoDirectory))
    Directory.CreateDirectory(photoDirectory);

// ..login
//..update handler
```
simply check if the directory doesn't exist, create it!

now we can look for the photos in our `UpdateHandler`:

```csharp
if (newMessage.message is not Message msg)
    continue;

if (msg.media is MessageMediaPhoto messageMediaPhoto)
{

}
```
okay so the `msg.media` is a `MessageMedia` which is just another `Base` class, this one can be a lot of things as well. but now we only want the photos so we casted it to `MessageMediaPhoto`.

the `messageMediaPhoto` has a field called `photo` which is yet another `Base` class.
the `photo` field can be 2 things:

- `PhotoEmpty`
- `Photo`

the first one is completely useless, in almost all the cases you want the `Photo`, so instead of nesting `if statements` we can write it like this:

```csharp
if (msg.media is MessageMediaPhoto {photo: Photo thePicture}) { }
```
we are simply checking if the type is `MessageMediaPhoto ` **AND** its `photo` field is of type `Photo` in a single `if-statement`
and finally we are accessing it through the `thePicture` variable.
keep in mind that if the `msg.media` is not the specified type, the `{photo: Photo thePicture}` part is not going to be executed, so you wont be able to use `thePicture`.

this is equivalent to this code:

```csharp
if (msg.media is MessageMediaPhoto messageMediaPhoto)
{
    if (messageMediaPhoto.photo is Photo photo) { }
}
```
we are just checking the 2 conditions at the same level, you can also write it like this:

```csharp
if (msg.media is MessageMediaPhoto messageMediaPhoto
    && messageMediaPhoto.photo is Photo photo) {}
```

`thePicture` has everything you need to know about a picture, lets download it in our desired Directory.

```csharp
if (msg.media is MessageMediaPhoto {photo: Photo thePicture})
{
    var path = Path.Combine(photoDirectory, $"{thePicture.id}.jpg");
    await using var fs = new FileStream(path, FileMode.OpenOrCreate);
    await client.DownloadFileAsync(thePicture, fs);
}
```
not much to explain here, we are creating a path using the photo id (you can name it whatever you want) and we are creating a `FileStream` using that path and using the `DownloadFileAsync` helper method we are downloading it.
you can test this and go to the `../bin/Debug/net/photos` and see the photos you've downloaded!
 
#### Documents

in telegram terms, documents are basically any type of media except photo. so videos, GIFs, audio, voice, stickers ...etc.

so process is very similar to downloading photos, first create the directory:

```csharp
var docDirectory = Path.Combine(Environment.CurrentDirectory, "documents");
if (!Directory.Exists(docDirectory))
    Directory.CreateDirectory(docDirectory);
```
then check for the document type:

```csharp
if (is photo...)
{
//..
}
else if (msg.media is MessageMediaDocument { document: Document doc }) //either Document or DocumentEmpty, same as PhotoBase
{
    var filename = doc.Filename ?? $"{doc.id}.{doc.mime_type.Split('/')[1]}";
    var path = Path.Combine(docDirectory, filename);
    await using var fs = new FileStream(path, FileMode.OpenOrCreate);
    await client.DownloadFileAsync(doc, fs);
}
```
so what we did here is a bit different, for the file name we can check if the `FileName` has a value (`??`) if so, we don't need to check for its `type`, if it doesn't have any name, we split the `mime_type` by `/`

> mime types are represented like this `Video/mp4` or `Photo/jpg` ..etc

and then we get the second index of the `Split` so that we can only get the `type` and with that we create a file name, then make a `FileStream` and download the file. exactly the same as Photo, you can also go ahead and store the different types in separate directories based on this `mime_type` but we are not going to do that here.

## Blocking

you we've made it, we handled our updates, lets run the application;

what happened? why did the problem aborted immediately? well because we don't `block` our thread.
after registering our handler:
```csharp
client.OnUpdate += UpdateHandler;
```
there is basically no logic to tell the program: "wait until we tell you to exit" or something like that.
how can we fix it?

the most simplest solution could be this:
```csharp
client.OnUpdate += UpdateHandler;
while (true)
    await Task.Delay(TimeSpan.FromSeconds(1));
```

yes, put an infinite loop after registering the handler and just sleep for some amount of seconds, doesn't matter you can put `TimeSpan.FromDays(99999)`, just the enough amount to not put any pressure on the CPU.

**but this is not a perfect solution!**

for a long term running or a production grade application I personally would recommend something like a [Background Service](https://learn.microsoft.com/en-us/aspnet/core/fundamentals/host/hosted-services?view=aspnetcore-8.0&tabs=visual-studio) (which we are going to cover in the next articles).
for testing, I think anything that *works*, is fine.

## Conclusion

so we managed to handle basic updates that are coming into our account, we didn't do much, but we did the hard part, after this its going to be much more easier.
if you did not find what you were looking for here, you might want to take a look at these links:

- [documentation](https://wiz0u.github.io/WTelegramClient/)
- [examples](https://wiz0u.github.io/WTelegramClient/EXAMPLES)
- [GitHub repo](https://github.com/wiz0u/WTelegramClient)

if you still didn't find it, don't worry, leave a comment and tell us what could be the topic of the next article!

_all the code written in this article is available [here](https://github.com/MrAliSalehi/UpdatesWtc)._