aahnik / tgcf

The ultimate tool to automate custom telegram message forwarding. Live-syncer, Auto-poster, backup-bot, cloner, chat-forwarder, duplicator, ... Call it whatever you like! tgcf can fulfill your custom needs.
https://github.com/aahnik/tgcf/wiki
MIT License
1.31k stars 783 forks source link

After exception FloodWaitError, message is skipped #317

Closed kaushalruparel closed 1 year ago

kaushalruparel commented 2 years ago

Describe the bug After the program hits Flood Wait exeption (or other exception), the message which caused the exception is skipped forwarding. It should retry the message after sleeping for the required duration. See below

To Reproduce Should hit exceptions

Expected behavior Send Message should be retried after sleeping for the required duration

Screenshots [11/06/21 10:23:30] INFO forwarding message with id = 995 [11/06/21 10:23:32] INFO slept for 2.0 seconds INFO Sleeping for A wait of 69 seconds is required (caused by SendMessageRequest) [11/06/21 10:24:42] INFO forwarding message with id = 997 [11/06/21 10:24:44] INFO slept for 2.0 seconds [11/06/21 10:24:45] INFO forwarding message with id = 998

Here it skipped msg id 996 for which it hit the exception, it should reset the IDs appropriately so the next iteration can send the message which hit the exception.

System information: Running tgcf 0.2.11.post0 Python 3.9.7 (tags/v3.9.7:1016ef3, Aug 30 2021, 20:19:38) [MSC v.1929 64 bit (AMD64)] OS nt Platform Windows 10 ('64bit', 'WindowsPE') Intel64 Family 6 Model 142 Stepping 10, GenuineIntel

Additional context Add any other context about the problem here.

kaushalruparel commented 2 years ago

I have a solution which I tested:

        `async for message in client.iter_messages(
            src, reverse=True, offset_id=forward.offset
        ):
            if forward.end and last_id >= forward.end:
                break
            while True:
                     All the stuff
                     try:
                         ....
                     except FloodWaitError as fwe:
                          logging.info(f"Sleeping for {fwe}")
                          await asyncio.sleep(delay=fwe.seconds)
                          continue """This will continue the while loop"""
                    except Exception as err:
                         logging.exception(err)
                    break`

The last break will only happen if we do not go to except block, so we achieve our goal of retry send_message if we hit any exception

Result: [11/06/21 13:01:31] INFO forwarding message with id = 1968 [11/06/21 13:01:33] INFO slept for 2.0 seconds INFO Sleeping for A wait of 84 seconds is required (caused by SendMessageRequest) [11/06/21 13:02:58] INFO forwarding message with id = 1969 [11/06/21 13:03:00] INFO slept for 2.0 seconds

I am not putting continue in other except as we might loop forever. But I think for that except (if we hit it) we should stop the program execution altogether(?)

tissole commented 2 years ago

You're sure it skips messages? Maybe the msg was forwarded to the destination but it was not logged. Can you check your destination for that msg? Or maybe the msg 996 was not in the source chat. Can you verify?

kaushalruparel commented 2 years ago

Yes I am sure it skipped the message. The for loop will not give it the same number because it doesn't know what was the outcome of previous loop iteration. We try: and catch if any exception and move forward, right?

tissole commented 2 years ago

This error is happening all the time? What is the cause? Or does it happen randomly? I run a few tests with a few thousand msgs and all were forwarded correctly. I used no delay, so floodwaits occurred.

Is not clear to me what modifications have you made, can you provide the changed past.py file so I can run some tests?

kaushalruparel commented 2 years ago

Error happened on the day I posted this issue. I changed my past.py after that with the workaround of running it inside loop now. I am attaching the past.py that was getting used at the time of this issue.

Note that I have made some changes in config.py also and my yml file looks like below (nmesg and ldelay are additional) `admins: [] forwards:

kaushalruparel commented 2 years ago

In my case we can see that message Id 996 was not forwarded after exception. In your tests with floodwaits, is the message on which exception hit, is getting forwarded?

tissole commented 2 years ago

When you got errors you were using the original past.py or a custom one modified by you? In my tests the numbers of files from source and destination were identical, so no files were skipped. I used the original past.py with only one modification in line 40. Orginal

                if forward.end and last_id > forward.end:
                    continue

Modified

                if forward.end and last_id >= forward.end:
                    logging.info(f"reached end id {forward.end}")
                    break

The script never skipped files to me before using the unmodified past.py. There were 2 situations when I wrongly thought that has skipped files.

First, it was when the first file forwarded was one with ID5, but when I looked in the source the files 1-4 were missing, so ID5 was the first file. The second time was when the number of files from source and destination was not the same, in the destination were missing some files (see #29).

But when I checked in the source some files were removed because of DMCA, but in the channel's stats, those were still counted as files.

kaushalruparel commented 2 years ago

1) I was using modified past.py which I attached here. 2) I manually checked in the source channel the ID that was skipped was there as a file. As I was near the end ID when this happened I reset the offset and in the next run the same modified past.py forwarded the file which was skipped.

I think "caused by SendMessageRequest" is the key here. The flood wait was caused when we already were executing fwded_msg = await send_message(d, tm) command in past.py which goes to that def in another file. If the exception is hit at this place, we will not execute other steps after this line, rather we will go to the except handler and once the sleep is over we will go to the next iteration of async for. There is no way we go back to the try block once the exception is hit inside try block.

If floodwaits are hit before try: (eg. in iter_messages) I believe it has to wait to get the next message id, so we are waiting at "for loop" start itself and this will not skip message.

tissole commented 2 years ago

So the errors only appear when using modified past.py? Then how do you know that it happens with the original past.py too? Have you tested? There is a chance that your changes may have broke some things in the script. But now with the latest modification the errors still occurs?

What are nmesg and ldelay?

kaushalruparel commented 2 years ago

I don't think so. The exception is coming from SendMessageRequest, so without my customizations also it can come. The day I reported this bug, it was happening and then I put the work around of while loop and it still was happening but because of while loop, messages were not skipped as after sleeping in exception handler, while loop will again try to send the same message. It only exits while loop on success of entire try: block.

If there is a way to artificially return exception from send_message, we can test the behaviour between original and modified past.py

nmesg and ldelay are just a way to sleep (ldelay) after a bunch of messages (nmesg). So apart from normal delay, after a bunch of messages a long delay can be induced.

tissole commented 2 years ago

I will test more this week with the original past.py to see if I can catch the errors. I wish that the script could record if is skipping files.

ldelay=long delay and nmesg=number of messages, I see. Is it nmsg or nmseg? In config is nmesg but in past is nmsg. Is there an error there?

What is the logic behind it? How do you expect the script to work with these modifications, besides non-skipping files?

Till now I was using a delay of 1.62s or 1.63s. I found that this way no floodwaits are triggered and the script forwards about 2000 msgs/h which is the TG limit. See #132

kaushalruparel commented 2 years ago

nmsg I am using as a counter in past.py. nmesg is in config.py so that I can provide that from tgcf config file. Those are only used for sleeping and logging.info mainly. The core of the program is untouched by these modifications. As I said I believe where we get exception is making a difference it seems reflected by "caused by SendMessageRequest". If we can throw exception forcefully on sendmessagerequest programatically then we can ascertain easily how past.py behaves.