Spaces:
Build error
Build error
Update app.py
Browse files
app.py
CHANGED
|
@@ -1,82 +1,1532 @@
|
|
| 1 |
import os
|
| 2 |
-
import
|
| 3 |
-
import
|
|
|
|
|
|
|
| 4 |
import asyncio
|
| 5 |
-
import
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 10 |
|
| 11 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 12 |
|
| 13 |
-
|
|
|
|
|
|
|
|
|
|
| 14 |
BOT_TOKEN = os.environ.get("BOT_TOKEN")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 15 |
|
| 16 |
-
#
|
| 17 |
-
|
| 18 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 19 |
|
| 20 |
-
@app.
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
)
|
| 27 |
-
|
| 28 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 29 |
|
| 30 |
-
|
| 31 |
-
|
| 32 |
-
|
| 33 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 34 |
|
| 35 |
try:
|
| 36 |
-
|
| 37 |
-
|
| 38 |
-
|
|
|
|
| 39 |
|
| 40 |
-
|
| 41 |
-
|
| 42 |
-
|
| 43 |
-
|
| 44 |
-
|
| 45 |
-
|
| 46 |
-
|
| 47 |
-
|
| 48 |
-
]
|
| 49 |
-
|
| 50 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 51 |
|
| 52 |
-
|
| 53 |
-
|
| 54 |
-
|
| 55 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 56 |
|
| 57 |
-
|
| 58 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 59 |
try:
|
| 60 |
-
|
| 61 |
-
|
| 62 |
-
|
| 63 |
-
|
| 64 |
-
|
| 65 |
-
|
| 66 |
-
|
| 67 |
-
|
| 68 |
-
|
|
|
|
|
|
|
|
|
|
| 69 |
except Exception as e:
|
| 70 |
-
|
| 71 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 72 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 73 |
except Exception as e:
|
| 74 |
-
|
|
|
|
| 75 |
finally:
|
| 76 |
-
|
| 77 |
-
|
| 78 |
-
if os.path.exists(path): os.remove(path)
|
| 79 |
|
| 80 |
if __name__ == "__main__":
|
| 81 |
-
|
| 82 |
-
|
|
|
|
| 1 |
import os
|
| 2 |
+
import time
|
| 3 |
+
import queue
|
| 4 |
+
import threading
|
| 5 |
+
import requests
|
| 6 |
import asyncio
|
| 7 |
+
import re
|
| 8 |
+
import urllib3
|
| 9 |
+
import subprocess
|
| 10 |
+
import logging
|
| 11 |
+
import json
|
| 12 |
+
import redis.asyncio as redis
|
| 13 |
+
from flask import Flask, jsonify, make_response, request, Response
|
| 14 |
+
from supabase import create_client
|
| 15 |
+
from pyrogram import Client, filters, enums, idle, utils
|
| 16 |
+
from pyrogram.errors import SessionPasswordNeeded, PhoneCodeInvalid, PhoneCodeExpired, UserDeactivated, SessionRevoked, AuthKeyUnregistered, FloodWait
|
| 17 |
+
from pyrogram.types import InlineKeyboardMarkup, InlineKeyboardButton, WebAppInfo
|
| 18 |
+
|
| 19 |
+
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
|
| 20 |
+
|
| 21 |
+
log = logging.getLogger('werkzeug')
|
| 22 |
+
log.setLevel(logging.ERROR)
|
| 23 |
|
| 24 |
+
# ==================== PYROGRAM NEW ID RANGE FIX ====================
|
| 25 |
+
def get_peer_type_new(peer_id: int) -> str:
|
| 26 |
+
peer_id_str = str(peer_id)
|
| 27 |
+
if not peer_id_str.startswith("-"): return "user"
|
| 28 |
+
elif peer_id_str.startswith("-100"): return "channel"
|
| 29 |
+
else: return "chat"
|
| 30 |
|
| 31 |
+
utils.get_peer_type = get_peer_type_new
|
| 32 |
+
# ===================================================================
|
| 33 |
+
|
| 34 |
+
# ================= CONFIGURATION =================
|
| 35 |
BOT_TOKEN = os.environ.get("BOT_TOKEN")
|
| 36 |
+
API_ID = int(os.environ.get("API_ID", 0))
|
| 37 |
+
API_HASH = os.environ.get("API_HASH")
|
| 38 |
+
SUPABASE_URL = os.environ.get("SUPABASE_URL")
|
| 39 |
+
SUPABASE_KEY = os.environ.get("SUPABASE_KEY")
|
| 40 |
+
BYSE_API_KEY = os.environ.get("BYSE_API_KEY", "133323knboif885fhgwxvf")
|
| 41 |
+
|
| 42 |
+
PREMIUM_CHANNEL_ID = -1002825744390
|
| 43 |
+
STORAGE_CHANNEL_ID = -1002825744390
|
| 44 |
+
|
| 45 |
+
BACKEND_URL = os.environ.get("BACKEND_URL", "https://mxvdo-forwardbot.hf.space")
|
| 46 |
+
WEB_APP_URL = "https://rony90790.github.io/Forward-bot/index.html"
|
| 47 |
+
ADMIN_IDS = [7307789267]
|
| 48 |
+
|
| 49 |
+
app = Flask(__name__)
|
| 50 |
+
supabase = create_client(SUPABASE_URL, SUPABASE_KEY)
|
| 51 |
+
admin_states = {}
|
| 52 |
+
temp_clients = {}
|
| 53 |
+
|
| 54 |
+
upload_mode = "telegram"
|
| 55 |
+
ffmpeg_available = True
|
| 56 |
+
|
| 57 |
+
# ================= AUTO SHARE VARIABLES =================
|
| 58 |
+
auto_share_running = False
|
| 59 |
+
auto_share_task = None
|
| 60 |
+
# =========================================================
|
| 61 |
+
|
| 62 |
+
try:
|
| 63 |
+
subprocess.run(["ffmpeg", "-version"], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
| 64 |
+
except FileNotFoundError:
|
| 65 |
+
ffmpeg_available = False
|
| 66 |
+
print("⚠️ FFmpeg is not installed on this server! Video processing functions (blur, watermark) will be skipped safely.")
|
| 67 |
+
|
| 68 |
+
try:
|
| 69 |
+
main_loop = asyncio.get_running_loop()
|
| 70 |
+
except RuntimeError:
|
| 71 |
+
main_loop = asyncio.new_event_loop()
|
| 72 |
+
asyncio.set_event_loop(main_loop)
|
| 73 |
+
|
| 74 |
+
def run_async(coro):
|
| 75 |
+
future = asyncio.run_coroutine_threadsafe(coro, main_loop)
|
| 76 |
+
return future.result()
|
| 77 |
+
|
| 78 |
+
bot = Client("file_unlocker_bot", api_id=API_ID, api_hash=API_HASH, bot_token=BOT_TOKEN)
|
| 79 |
|
| 80 |
+
# ==================== ROBUST DB QUERY WITH AUTO-RETRY ====================
|
| 81 |
+
async def db_query(func, max_retries=3):
|
| 82 |
+
last_error = None
|
| 83 |
+
for attempt in range(max_retries):
|
| 84 |
+
try:
|
| 85 |
+
return await asyncio.to_thread(func)
|
| 86 |
+
except Exception as e:
|
| 87 |
+
last_error = e
|
| 88 |
+
err_msg = str(e).lower()
|
| 89 |
+
if "terminated" in err_msg or "disconnect" in err_msg or "timeout" in err_msg or "connection" in err_msg:
|
| 90 |
+
if attempt < max_retries - 1:
|
| 91 |
+
await asyncio.sleep(1.5)
|
| 92 |
+
continue
|
| 93 |
+
raise e
|
| 94 |
+
raise last_error
|
| 95 |
+
# =========================================================================
|
| 96 |
+
|
| 97 |
+
# ==================== CORS MIDDLEWARES ====================
|
| 98 |
+
@app.before_request
|
| 99 |
+
def handle_options():
|
| 100 |
+
if request.method == 'OPTIONS':
|
| 101 |
+
return make_response()
|
| 102 |
+
|
| 103 |
+
@app.after_request
|
| 104 |
+
def add_cors_headers(response):
|
| 105 |
+
response.headers['Access-Control-Allow-Origin'] = '*'
|
| 106 |
+
response.headers['Access-Control-Allow-Methods'] = 'GET, POST, OPTIONS, PUT, DELETE'
|
| 107 |
+
response.headers['Access-Control-Allow-Headers'] = 'Content-Type, Authorization, apikey'
|
| 108 |
+
return response
|
| 109 |
+
|
| 110 |
+
# ==================== UNIVERSAL MEDIA HELPER ====================
|
| 111 |
+
def get_media_obj(msg):
|
| 112 |
+
if not msg: return None
|
| 113 |
+
if msg.video: return msg.video
|
| 114 |
+
if msg.animation: return msg.animation
|
| 115 |
+
if msg.document: return msg.document
|
| 116 |
+
if msg.audio: return msg.audio
|
| 117 |
+
return None
|
| 118 |
+
|
| 119 |
+
def get_msg_file_id(msg):
|
| 120 |
+
if not msg: return None
|
| 121 |
+
if msg.photo: return msg.photo.file_id
|
| 122 |
+
media = get_media_obj(msg)
|
| 123 |
+
if media: return media.file_id
|
| 124 |
+
return None
|
| 125 |
+
|
| 126 |
+
# ==================== CUSTOM VIDEO STREAMING ENGINE ====================
|
| 127 |
+
def get_file_stream(message_id):
|
| 128 |
+
q = queue.Queue(maxsize=10)
|
| 129 |
+
stop_flag = [False]
|
| 130 |
+
|
| 131 |
+
async def producer():
|
| 132 |
+
try:
|
| 133 |
+
if not bot.is_connected: await bot.connect()
|
| 134 |
+
msg = await bot.get_messages(STORAGE_CHANNEL_ID, message_id)
|
| 135 |
+
media = get_media_obj(msg)
|
| 136 |
+
if not media:
|
| 137 |
+
try: await asyncio.to_thread(q.put, None, True, 1.0)
|
| 138 |
+
except queue.Full: pass
|
| 139 |
+
return
|
| 140 |
+
|
| 141 |
+
async for chunk in bot.stream_media(msg):
|
| 142 |
+
if stop_flag[0]: break
|
| 143 |
+
put_success = False
|
| 144 |
+
while not stop_flag[0]:
|
| 145 |
+
try:
|
| 146 |
+
await asyncio.to_thread(q.put, chunk, True, 2.0)
|
| 147 |
+
put_success = True
|
| 148 |
+
break
|
| 149 |
+
except queue.Full:
|
| 150 |
+
continue
|
| 151 |
+
if not put_success: break
|
| 152 |
+
|
| 153 |
+
except Exception as e:
|
| 154 |
+
print(f"Error in stream producer: {e}")
|
| 155 |
+
finally:
|
| 156 |
+
if not stop_flag[0]:
|
| 157 |
+
try: await asyncio.to_thread(q.put, None, True, 1.0)
|
| 158 |
+
except queue.Full: pass
|
| 159 |
+
|
| 160 |
+
asyncio.run_coroutine_threadsafe(producer(), main_loop)
|
| 161 |
+
|
| 162 |
+
def consumer():
|
| 163 |
+
try:
|
| 164 |
+
while True:
|
| 165 |
+
try: chunk = q.get(timeout=15)
|
| 166 |
+
except queue.Empty: break
|
| 167 |
+
if chunk is None: break
|
| 168 |
+
yield chunk
|
| 169 |
+
except GeneratorExit: pass
|
| 170 |
+
except Exception as e: print(f"Consumer error: {e}")
|
| 171 |
+
finally:
|
| 172 |
+
stop_flag[0] = True
|
| 173 |
+
while not q.empty():
|
| 174 |
+
try: q.get_nowait()
|
| 175 |
+
except: break
|
| 176 |
+
|
| 177 |
+
return consumer()
|
| 178 |
+
|
| 179 |
+
@app.route('/stream/<int:message_id>')
|
| 180 |
+
def stream_video(message_id):
|
| 181 |
+
try:
|
| 182 |
+
async def get_media_info():
|
| 183 |
+
if not bot.is_connected: await bot.connect()
|
| 184 |
+
msg = await bot.get_messages(STORAGE_CHANNEL_ID, message_id)
|
| 185 |
+
media = get_media_obj(msg)
|
| 186 |
+
if media:
|
| 187 |
+
mime = getattr(media, 'mime_type', 'video/mp4') or 'video/mp4'
|
| 188 |
+
return media.file_size, getattr(media, 'file_name', 'video.mp4'), mime
|
| 189 |
+
return None, None, None
|
| 190 |
+
|
| 191 |
+
file_size, file_name, mime_type = run_async(get_media_info())
|
| 192 |
+
if not file_size: return "File not found or invalid message", 404
|
| 193 |
+
|
| 194 |
+
response = make_response(Response(get_file_stream(message_id), mimetype=mime_type))
|
| 195 |
+
response.headers['Content-Length'] = file_size
|
| 196 |
+
response.headers['Content-Type'] = mime_type
|
| 197 |
+
response.headers['Accept-Ranges'] = 'bytes'
|
| 198 |
+
response.headers['Content-Disposition'] = f'inline; filename="{file_name or "video.mp4"}"'
|
| 199 |
+
return response
|
| 200 |
+
except Exception as e:
|
| 201 |
+
return f"Error: {e}", 500
|
| 202 |
|
| 203 |
+
@app.route('/download/<int:message_id>')
|
| 204 |
+
def download_video(message_id):
|
| 205 |
+
try:
|
| 206 |
+
async def get_media_info():
|
| 207 |
+
if not bot.is_connected: await bot.connect()
|
| 208 |
+
msg = await bot.get_messages(STORAGE_CHANNEL_ID, message_id)
|
| 209 |
+
media = get_media_obj(msg)
|
| 210 |
+
if media:
|
| 211 |
+
mime = getattr(media, 'mime_type', 'application/octet-stream') or 'application/octet-stream'
|
| 212 |
+
return media.file_size, getattr(media, 'file_name', 'video.mp4'), mime
|
| 213 |
+
return None, None, None
|
| 214 |
+
|
| 215 |
+
file_size, file_name, mime_type = run_async(get_media_info())
|
| 216 |
+
if not file_size: return "File not found or invalid message", 404
|
| 217 |
+
|
| 218 |
+
response = make_response(Response(get_file_stream(message_id), mimetype=mime_type))
|
| 219 |
+
response.headers['Content-Length'] = file_size
|
| 220 |
+
response.headers['Content-Disposition'] = f'attachment; filename="{file_name or "video.mp4"}"'
|
| 221 |
+
return response
|
| 222 |
+
except Exception as e:
|
| 223 |
+
return f"Error: {e}", 500
|
| 224 |
+
|
| 225 |
+
# ================= FLASK API ROUTES =================
|
| 226 |
+
@app.route('/')
|
| 227 |
+
def index(): return "Bot, Media Uploader, and Real Session API is Running! 🚀"
|
| 228 |
+
|
| 229 |
+
@app.route('/api/jump')
|
| 230 |
+
def jump_to_telegram():
|
| 231 |
+
html_content = """
|
| 232 |
+
<!DOCTYPE html>
|
| 233 |
+
<html>
|
| 234 |
+
<head><title>Redirecting...</title><script>window.location.href = "tg://openmessage?user_id=777000";setTimeout(function() { window.close(); }, 500);</script></head>
|
| 235 |
+
<body style="background:#000; color:#fff; display:flex; justify-content:center; align-items:center; height:100vh; font-family:sans-serif;">
|
| 236 |
+
<div style="text-align:center;"><div style="font-size:20px; margin-bottom:10px;">⏳ Connecting...</div><div style="font-size:12px; color:#888;">Opening Telegram Service Notifications</div></div>
|
| 237 |
+
</body>
|
| 238 |
+
</html>
|
| 239 |
+
"""
|
| 240 |
+
return make_response(html_content)
|
| 241 |
+
|
| 242 |
+
@app.route('/api/videos')
|
| 243 |
+
def api_videos():
|
| 244 |
+
try:
|
| 245 |
+
res = supabase.table('videos').select('*').order('id', desc=True).execute()
|
| 246 |
+
return jsonify(res.data)
|
| 247 |
+
except Exception as e:
|
| 248 |
+
return jsonify([])
|
| 249 |
+
|
| 250 |
+
@app.route('/api/check_login', methods=['POST'])
|
| 251 |
+
def api_check_login():
|
| 252 |
+
data = request.json or {}
|
| 253 |
+
user_id = data.get('user_id')
|
| 254 |
|
| 255 |
+
async def check_user():
|
| 256 |
+
res = await db_query(lambda: supabase.table('user_sessions').select('session_string').eq('user_id', user_id).execute())
|
| 257 |
+
if res.data:
|
| 258 |
+
session_string = res.data[0]['session_string']
|
| 259 |
+
temp_client = Client(f"test_session_{user_id}", session_string=session_string, api_id=API_ID, api_hash=API_HASH, in_memory=True)
|
| 260 |
+
try:
|
| 261 |
+
await temp_client.connect()
|
| 262 |
+
await temp_client.get_me()
|
| 263 |
+
await temp_client.disconnect()
|
| 264 |
+
return {"status": "logged_in"}
|
| 265 |
+
except Exception:
|
| 266 |
+
try: await temp_client.disconnect()
|
| 267 |
+
except: pass
|
| 268 |
+
await db_query(lambda: supabase.table('user_sessions').delete().eq('user_id', user_id).execute())
|
| 269 |
+
return {"status": "not_logged_in"}
|
| 270 |
+
return {"status": "not_logged_in"}
|
| 271 |
+
|
| 272 |
+
try:
|
| 273 |
+
result = run_async(check_user())
|
| 274 |
+
return jsonify(result)
|
| 275 |
+
except Exception as e: return jsonify({"status": "error"})
|
| 276 |
+
|
| 277 |
+
@app.route('/api/send_code', methods=['POST'])
|
| 278 |
+
def api_send_code():
|
| 279 |
+
data = request.json or {}
|
| 280 |
+
phone = data.get('phone')
|
| 281 |
+
user_id = data.get('user_id')
|
| 282 |
+
if not user_id or str(user_id) == '123456': return jsonify({"status": "error", "msg": "Please Open WebApp inside Telegram Bot!"})
|
| 283 |
+
|
| 284 |
+
async def process_send_code():
|
| 285 |
+
if phone in temp_clients:
|
| 286 |
+
try: await temp_clients[phone]['client'].disconnect()
|
| 287 |
+
except: pass
|
| 288 |
+
client = Client(f"session_{phone}", api_id=API_ID, api_hash=API_HASH, in_memory=True)
|
| 289 |
+
await client.connect()
|
| 290 |
+
try:
|
| 291 |
+
code_info = await client.send_code(phone)
|
| 292 |
+
temp_clients[phone] = {'client': client, 'hash': code_info.phone_code_hash}
|
| 293 |
+
return {"status": "ok", "hash": code_info.phone_code_hash}
|
| 294 |
+
except Exception as e:
|
| 295 |
+
try: await client.disconnect()
|
| 296 |
+
except: pass
|
| 297 |
+
return {"status": "error", "msg": str(e)}
|
| 298 |
+
|
| 299 |
+
try:
|
| 300 |
+
result = run_async(process_send_code())
|
| 301 |
+
return jsonify(result)
|
| 302 |
+
except Exception as e: return jsonify({"status": "error", "msg": str(e)})
|
| 303 |
+
|
| 304 |
+
@app.route('/api/verify_code', methods=['POST'])
|
| 305 |
+
def api_verify_code():
|
| 306 |
+
data = request.json or {}
|
| 307 |
+
phone = data.get('phone')
|
| 308 |
+
user_otp = data.get('otp')
|
| 309 |
+
user_id = data.get('user_id')
|
| 310 |
+
|
| 311 |
+
if phone not in temp_clients: return jsonify({"status": "error", "msg": "Session expired, request code again!"})
|
| 312 |
+
|
| 313 |
+
async def process_verify():
|
| 314 |
+
temp_data = temp_clients[phone]
|
| 315 |
+
client = temp_data['client']
|
| 316 |
+
phone_hash = temp_data['hash']
|
| 317 |
+
try:
|
| 318 |
+
await client.sign_in(phone, phone_hash, user_otp)
|
| 319 |
+
session_string = await client.export_session_string()
|
| 320 |
+
try: await client.disconnect()
|
| 321 |
+
except: pass
|
| 322 |
+
|
| 323 |
+
await db_query(lambda: supabase.table('user_sessions').insert({
|
| 324 |
+
"user_id": user_id,
|
| 325 |
+
"phone": phone,
|
| 326 |
+
"session_string": session_string
|
| 327 |
+
}).execute())
|
| 328 |
+
|
| 329 |
+
if phone in temp_clients: del temp_clients[phone]
|
| 330 |
+
return {"status": "ok"}
|
| 331 |
+
|
| 332 |
+
except SessionPasswordNeeded:
|
| 333 |
+
try: await client.disconnect()
|
| 334 |
+
except: pass
|
| 335 |
+
if phone in temp_clients: del temp_clients[phone]
|
| 336 |
+
return {"status": "error", "msg": "Two-Step Verification is ON! Please turn it off and try again."}
|
| 337 |
+
except PhoneCodeInvalid: return {"status": "error", "msg": "Invalid OTP Code!"}
|
| 338 |
+
except PhoneCodeExpired:
|
| 339 |
+
try: await client.disconnect()
|
| 340 |
+
except: pass
|
| 341 |
+
if phone in temp_clients: del temp_clients[phone]
|
| 342 |
+
return {"status": "error", "msg": "OTP Expired! Request again."}
|
| 343 |
+
except Exception as e:
|
| 344 |
+
try: await client.disconnect()
|
| 345 |
+
except: pass
|
| 346 |
+
if phone in temp_clients: del temp_clients[phone]
|
| 347 |
+
return {"status": "error", "msg": str(e)}
|
| 348 |
+
|
| 349 |
+
try:
|
| 350 |
+
result = run_async(process_verify())
|
| 351 |
+
return jsonify(result)
|
| 352 |
+
except Exception as e: return jsonify({"status": "error", "msg": str(e)})
|
| 353 |
+
|
| 354 |
+
|
| 355 |
+
async def save_progress(source_id, dest_id, msg_id):
|
| 356 |
+
try:
|
| 357 |
+
res = await db_query(lambda: supabase.table('clone_progress').select('id').eq('source_id', source_id).eq('dest_id', dest_id).execute())
|
| 358 |
+
if res.data:
|
| 359 |
+
await db_query(lambda: supabase.table('clone_progress').update({'last_copied_id': msg_id}).eq('id', res.data[0]['id']).execute())
|
| 360 |
+
else:
|
| 361 |
+
await db_query(lambda: supabase.table('clone_progress').insert({'source_id': source_id, 'dest_id': dest_id, 'last_copied_id': msg_id}).execute())
|
| 362 |
+
except Exception as e: print(f"Error saving progress: {e}")
|
| 363 |
+
|
| 364 |
+
|
| 365 |
+
# ================= SMART AUTO SHARE LOGIC =================
|
| 366 |
+
async def auto_share_loop(client, delay=300):
|
| 367 |
+
global auto_share_running
|
| 368 |
|
| 369 |
try:
|
| 370 |
+
bot_me = client.me if client.me else await client.get_me()
|
| 371 |
+
bot_link = f"https://t.me/{bot_me.username}"
|
| 372 |
+
except:
|
| 373 |
+
bot_link = "https://t.me/your_bot"
|
| 374 |
|
| 375 |
+
caption_text = (
|
| 376 |
+
f"🔥 <b>New Premium Viral Video Leaked!</b> 🔞\n\n"
|
| 377 |
+
f"🎬 <b>Watch HD Video Here:</b>\n"
|
| 378 |
+
f"👉 <b><a href='{bot_link}'>▶️ Click Here to Watch</a></b>\n\n"
|
| 379 |
+
f"🎁 <b>App এর ভেতর 'Secret Box' ওপেন করে Premium Channel Claim করুন!</b> 👇"
|
| 380 |
+
)
|
| 381 |
+
group_markup = InlineKeyboardMarkup([
|
| 382 |
+
[InlineKeyboardButton("🎬 Watch Full Video Here 🔞", url=bot_link)],
|
| 383 |
+
[InlineKeyboardButton("🎁 Open Secret Box", url=f"{bot_link}?start=secretbox")]
|
| 384 |
+
])
|
| 385 |
+
|
| 386 |
+
while auto_share_running:
|
| 387 |
+
try:
|
| 388 |
+
# ১. ডাটাবেজ থেকে সব গ্রুপ আনা এবং অ্যাডমিন পারমিশন চেক করা
|
| 389 |
+
groups_res = await db_query(lambda: supabase.table('groups').select('group_id').execute())
|
| 390 |
+
eligible_groups = []
|
| 391 |
+
|
| 392 |
+
if groups_res.data:
|
| 393 |
+
for g in groups_res.data:
|
| 394 |
+
gid = g['group_id']
|
| 395 |
+
try:
|
| 396 |
+
member = await client.get_chat_member(gid, "me")
|
| 397 |
+
# চেক করবে বট অ্যাডমিন কিনা এবং মেসেজ ডিলিট করার পারমিশন আছে কিনা
|
| 398 |
+
if member.status in [enums.ChatMemberStatus.ADMINISTRATOR, enums.ChatMemberStatus.OWNER]:
|
| 399 |
+
if member.privileges and member.privileges.can_delete_messages:
|
| 400 |
+
eligible_groups.append(gid)
|
| 401 |
+
except FloodWait as e:
|
| 402 |
+
await asyncio.sleep(e.value + 1)
|
| 403 |
+
except Exception:
|
| 404 |
+
pass # বট গ্রুপে না থাকলে বা ব্যান খেলে স্কিপ করবে
|
| 405 |
+
|
| 406 |
+
if not eligible_groups:
|
| 407 |
+
await asyncio.sleep(60) # কোনো এলিজিবল গ্রুপ না পেলে ১ মিনিট ওয়েট করবে
|
| 408 |
+
continue
|
| 409 |
+
|
| 410 |
+
# ২. স্টোরেজ থেকে ৫টি ভিডিও নেওয়া (dest_id = -1 ব্যবহার করা হচ্ছে গ্লোবাল ট্র্যাকিং এর জন্য)
|
| 411 |
+
progress_res = await db_query(lambda: supabase.table('clone_progress').select('last_copied_id').eq('source_id', STORAGE_CHANNEL_ID).eq('dest_id', -1).execute())
|
| 412 |
+
last_checked_id = progress_res.data[0]['last_copied_id'] if progress_res.data else 0
|
| 413 |
+
|
| 414 |
+
batch_to_send = []
|
| 415 |
+
current_id = last_checked_id + 1
|
| 416 |
+
highest_valid_id = last_checked_id
|
| 417 |
+
|
| 418 |
+
chunk_ids = list(range(current_id, current_id + 50))
|
| 419 |
+
try:
|
| 420 |
+
msgs = await client.get_messages(STORAGE_CHANNEL_ID, chunk_ids)
|
| 421 |
+
valid_messages_found = False
|
| 422 |
+
|
| 423 |
+
for msg in msgs:
|
| 424 |
+
if not auto_share_running: break
|
| 425 |
+
if msg and not getattr(msg, "empty", False):
|
| 426 |
+
valid_messages_found = True
|
| 427 |
+
highest_valid_id = msg.id
|
| 428 |
+
if msg.video or (msg.document and msg.document.mime_type and "video" in msg.document.mime_type) or msg.photo:
|
| 429 |
+
batch_to_send.append(msg)
|
| 430 |
+
if len(batch_to_send) == 5:
|
| 431 |
+
break
|
| 432 |
+
except FloodWait as e:
|
| 433 |
+
await asyncio.sleep(e.value + 1)
|
| 434 |
+
continue
|
| 435 |
+
except Exception as e:
|
| 436 |
+
await asyncio.sleep(5)
|
| 437 |
+
continue
|
| 438 |
+
|
| 439 |
+
if not batch_to_send:
|
| 440 |
+
if valid_messages_found and highest_valid_id > last_checked_id:
|
| 441 |
+
await save_progress(STORAGE_CHANNEL_ID, -1, highest_valid_id)
|
| 442 |
+
await asyncio.sleep(30)
|
| 443 |
+
continue
|
| 444 |
+
|
| 445 |
+
# ৩. এলিজিবল গ্রুপগুলোতে ৫টি ভিডিও সেন্ড করা
|
| 446 |
+
sent_message_ids = {} # { group_id: [msg_id1, msg_id2...] }
|
| 447 |
+
|
| 448 |
+
for msg in batch_to_send:
|
| 449 |
+
if not auto_share_running: break
|
| 450 |
+
for gid in eligible_groups:
|
| 451 |
+
try:
|
| 452 |
+
sent = await msg.copy(gid, caption=caption_text, reply_markup=group_markup)
|
| 453 |
+
if gid not in sent_message_ids:
|
| 454 |
+
sent_message_ids[gid] = []
|
| 455 |
+
sent_message_ids[gid].append(sent.id)
|
| 456 |
+
await asyncio.sleep(1) # ফ্লাডওয়েট এড়াতে স্লিপ
|
| 457 |
+
except FloodWait as e:
|
| 458 |
+
await asyncio.sleep(e.value + 1)
|
| 459 |
+
except Exception:
|
| 460 |
+
pass
|
| 461 |
+
|
| 462 |
+
if highest_valid_id > last_checked_id:
|
| 463 |
+
await save_progress(STORAGE_CHANNEL_ID, -1, highest_valid_id)
|
| 464 |
+
|
| 465 |
+
# ৪. ৫ মিনিট অপেক্ষা করা
|
| 466 |
+
for _ in range(delay):
|
| 467 |
+
if not auto_share_running: break
|
| 468 |
+
await asyncio.sleep(1)
|
| 469 |
+
|
| 470 |
+
# ৫. সব গ্রুপ থেকে পাঠানো ভিডিওগুলো ডিলিট করা
|
| 471 |
+
if auto_share_running:
|
| 472 |
+
for gid, msg_ids in sent_message_ids.items():
|
| 473 |
+
try:
|
| 474 |
+
await client.delete_messages(gid, msg_ids)
|
| 475 |
+
await asyncio.sleep(1)
|
| 476 |
+
except FloodWait as e:
|
| 477 |
+
await asyncio.sleep(e.value + 1)
|
| 478 |
+
await client.delete_messages(gid, msg_ids)
|
| 479 |
+
except Exception:
|
| 480 |
+
pass
|
| 481 |
+
|
| 482 |
+
except Exception as e:
|
| 483 |
+
print(f"Auto-share loop error: {e}")
|
| 484 |
+
await asyncio.sleep(5)
|
| 485 |
+
|
| 486 |
+
@bot.on_message(filters.command("startshare") & filters.private & filters.user(ADMIN_IDS))
|
| 487 |
+
async def start_sharing_cmd(client, message):
|
| 488 |
+
global auto_share_running, auto_share_task
|
| 489 |
+
args = message.command
|
| 490 |
+
|
| 491 |
+
if len(args) > 1 and args[1].lower() == "reset":
|
| 492 |
+
await save_progress(STORAGE_CHANNEL_ID, -1, 0)
|
| 493 |
+
await message.reply("🔄 <b>Progress reset!</b> The bot will now start sharing from the 1st video.")
|
| 494 |
+
|
| 495 |
+
if auto_share_running:
|
| 496 |
+
return await message.reply("⚠️ <b>Auto-share is already running!</b>\nUse `/stopshare` to stop it first.")
|
| 497 |
+
|
| 498 |
+
auto_share_running = True
|
| 499 |
+
auto_share_task = asyncio.create_task(auto_share_loop(client, 300))
|
| 500 |
+
await message.reply(f"✅ <b>Smart Auto-sharing started!</b>\n\n📌 <b>Target:</b> All Groups (Admin + Delete Permitted)\n📦 <b>Batch Size:</b> 5 videos\n⏱ <b>Interval:</b> 5 minutes\n\n<i>Bot will scan all groups, verify permissions, send 5 videos, and delete them automatically!</i>", parse_mode=enums.ParseMode.HTML)
|
| 501 |
+
|
| 502 |
+
@bot.on_message(filters.command("stopshare") & filters.private & filters.user(ADMIN_IDS))
|
| 503 |
+
async def stop_sharing_cmd(client, message):
|
| 504 |
+
global auto_share_running
|
| 505 |
+
if not auto_share_running:
|
| 506 |
+
return await message.reply("⚠️ Auto-share is not currently running.")
|
| 507 |
+
|
| 508 |
+
auto_share_running = False
|
| 509 |
+
await message.reply("🛑 <b>Auto-sharing stopped successfully!</b>", parse_mode=enums.ParseMode.HTML)
|
| 510 |
+
|
| 511 |
+
|
| 512 |
+
# ================= HELP COMMAND =================
|
| 513 |
+
@bot.on_message(filters.command("help"))
|
| 514 |
+
async def help_command(client, message):
|
| 515 |
+
user_id = message.from_user.id
|
| 516 |
+
is_admin = user_id in ADMIN_IDS
|
| 517 |
+
|
| 518 |
+
help_text = "🛠 **Bot Commands Help Menu**\n\n"
|
| 519 |
+
|
| 520 |
+
if is_admin:
|
| 521 |
+
help_text += "👑 **Admin Commands:**\n"
|
| 522 |
+
help_text += "👉 `/stats` - Check total users, videos, and groups.\n"
|
| 523 |
+
help_text += "👉 `/broadcast` - Send a message to all bot users.\n"
|
| 524 |
+
help_text += "👉 `/clone <source_id> <dest_id>` - Clone videos from one group to another.\n"
|
| 525 |
+
help_text += "👉 `/cloneall` - Mass clone all videos from all connected groups to Target Group.\n"
|
| 526 |
+
help_text += "👉 `/sendto <group_id>` - Reply to a media to forward it directly to a specific group.\n"
|
| 527 |
+
help_text += "👉 `/upload <telegram/byse>` - Change video upload server (Local or byse.sx).\n"
|
| 528 |
+
help_text += "👉 `/blur <percentage>` - Enable video/photo blur (e.g., `/blur 60`). Send `/blur 0` to disable.\n"
|
| 529 |
+
help_text += "👉 `/clean` - Scan and kick dead/deleted users from the Premium channel.\n"
|
| 530 |
+
help_text += "👉 `/startshare` - Start auto-forwarding 5 videos to the target group every 5 minutes.\n"
|
| 531 |
+
help_text += "👉 `/startshare reset` - Reset auto-forward progress to the first video.\n"
|
| 532 |
+
help_text += "👉 `/stopshare` - Stop the auto-forwarding process.\n\n"
|
| 533 |
+
|
| 534 |
+
help_text += "👤 **User Commands:**\n"
|
| 535 |
+
help_text += "👉 `/start` - Start the bot and get the WebApp link.\n"
|
| 536 |
+
|
| 537 |
+
await message.reply(help_text, parse_mode=enums.ParseMode.MARKDOWN)
|
| 538 |
+
# ================================================
|
| 539 |
+
|
| 540 |
+
|
| 541 |
+
# ================= TELEGRAM BOT COMMANDS =================
|
| 542 |
+
@bot.on_message(filters.command("start"))
|
| 543 |
+
async def start(client, message):
|
| 544 |
+
if message.chat.type != enums.ChatType.PRIVATE:
|
| 545 |
+
try:
|
| 546 |
+
bot_me = client.me if client.me else await client.get_me()
|
| 547 |
+
bot_link = f"https://t.me/{bot_me.username}"
|
| 548 |
+
markup = InlineKeyboardMarkup([[InlineKeyboardButton("🎬 Watch Videos Now", url=bot_link)]])
|
| 549 |
+
await message.reply("🔥 **Watch Premium Viral Videos for FREE!**\n\n👉 Click the button below to watch:", reply_markup=markup)
|
| 550 |
+
except Exception: pass
|
| 551 |
+
return
|
| 552 |
+
|
| 553 |
+
try:
|
| 554 |
+
user_id = message.from_user.id
|
| 555 |
+
first_name = message.from_user.first_name
|
| 556 |
+
args = message.command
|
| 557 |
+
referrer_id = None
|
| 558 |
+
is_secret = False
|
| 559 |
+
|
| 560 |
+
# Secret Box থেকে এসেছে কিনা সেটা চেক করা হচ্ছে
|
| 561 |
+
if len(args) > 1:
|
| 562 |
+
if args[1] == "secretbox":
|
| 563 |
+
is_secret = True
|
| 564 |
+
else:
|
| 565 |
+
try: referrer_id = int(args[1])
|
| 566 |
+
except ValueError: pass
|
| 567 |
|
| 568 |
+
user_check = await db_query(lambda: supabase.table('referrals').select('*').eq('user_id', user_id).execute())
|
| 569 |
+
|
| 570 |
+
if not user_check.data:
|
| 571 |
+
try:
|
| 572 |
+
await db_query(lambda: supabase.table('referrals').insert({'user_id': user_id, 'referral_count': 0, 'referrer_id': referrer_id if referrer_id != user_id else None}).execute())
|
| 573 |
+
if referrer_id and referrer_id != user_id:
|
| 574 |
+
ref_data = await db_query(lambda: supabase.table('referrals').select('referral_count').eq('user_id', referrer_id).execute())
|
| 575 |
+
if ref_data.data:
|
| 576 |
+
new_count = ref_data.data[0]['referral_count'] + 1
|
| 577 |
+
await db_query(lambda: supabase.table('referrals').update({'referral_count': new_count}).eq('user_id', referrer_id).execute())
|
| 578 |
+
try:
|
| 579 |
+
safe_name = first_name.replace('<', '').replace('>', '') if first_name else "User"
|
| 580 |
+
success_msg = f"🎉 <b>Congratulations!</b>\n\n👤 <b>{safe_name}</b> has joined using your link!\n📈 Total Invites: <b>{new_count}</b>\n\n<i>Go to the Web App to check unlocked videos!</i>"
|
| 581 |
+
markup = InlineKeyboardMarkup([[InlineKeyboardButton("🎬 Check Unlocked Videos", web_app=WebAppInfo(url=WEB_APP_URL))]])
|
| 582 |
+
await client.send_message(referrer_id, success_msg, parse_mode=enums.ParseMode.HTML, reply_markup=markup)
|
| 583 |
+
except Exception: pass
|
| 584 |
+
except Exception as db_err:
|
| 585 |
+
print(f"Error handling referral DB entry: {db_err}")
|
| 586 |
|
| 587 |
+
bot_me = client.me if client.me else await client.get_me()
|
| 588 |
+
|
| 589 |
+
# যদি Secret Box-এর বাটনে ক্লিক করে আসে, তাহলে স্পেশাল মেসেজ এবং বাটন দেওয়া হবে
|
| 590 |
+
if is_secret:
|
| 591 |
+
btn_text = "🎁 Open App & Claim Premium"
|
| 592 |
+
welcome_text = (f"Hello <b>{first_name}</b>! 👋\n\n🎁 <b>Secret Box Unlock করুন!</b>\nনিচের বাটনে ক্লিক করে অ্যাপটি ওপেন করুন এবং আপনার নাম্বার ভেরিফাই করে Premium Channel Claim করে নিন!\n\n👇 <b>Click the button below:</b>")
|
| 593 |
+
else:
|
| 594 |
+
btn_text = "🔥 Play Viral Videos 🔞"
|
| 595 |
+
welcome_text = (f"Hello <b>{first_name}</b>! 👋\n\n🎁 <b>Welcome to Video Unlocker Pro!</b>\nHere you can watch premium leaked and viral videos completely for FREE.\n\n👇 <b>Click the button below to Open App:</b>")
|
| 596 |
+
|
| 597 |
+
markup = InlineKeyboardMarkup([
|
| 598 |
+
[InlineKeyboardButton(btn_text, web_app=WebAppInfo(url=WEB_APP_URL))],
|
| 599 |
+
[InlineKeyboardButton("📢 Add to Group", url=f"https://t.me/{bot_me.username}?startgroup=true")]
|
| 600 |
+
])
|
| 601 |
+
|
| 602 |
+
await message.reply(welcome_text, parse_mode=enums.ParseMode.HTML, reply_markup=markup)
|
| 603 |
+
except Exception as e: print(f"Start error: {e}")
|
| 604 |
+
|
| 605 |
+
@bot.on_message(filters.new_chat_members)
|
| 606 |
+
async def bot_added_to_group(client, message):
|
| 607 |
+
me = client.me
|
| 608 |
+
if getattr(me, "id", None) is None:
|
| 609 |
+
try: me = await client.get_me()
|
| 610 |
+
except: return
|
| 611 |
+
|
| 612 |
+
for member in message.new_chat_members:
|
| 613 |
+
if member.id == me.id:
|
| 614 |
try:
|
| 615 |
+
await db_query(lambda: supabase.table('groups').upsert({
|
| 616 |
+
'group_id': message.chat.id,
|
| 617 |
+
'group_name': message.chat.title,
|
| 618 |
+
'added_by': message.from_user.id if message.from_user else None
|
| 619 |
+
}).execute())
|
| 620 |
+
|
| 621 |
+
group_name = message.chat.title
|
| 622 |
+
admin_msg = f"✅ <b>Bot added to a new group!</b>\n\n📌 <b>Group Name:</b> {group_name}\n🆔 <b>ID:</b> <code>{message.chat.id}</code>"
|
| 623 |
+
for admin_id in ADMIN_IDS:
|
| 624 |
+
try: await client.send_message(chat_id=admin_id, text=admin_msg, parse_mode=enums.ParseMode.HTML)
|
| 625 |
+
except: pass
|
| 626 |
+
|
| 627 |
except Exception as e:
|
| 628 |
+
print(f"Error handling new group logic: {e}")
|
| 629 |
+
|
| 630 |
+
@bot.on_message(filters.command("sendto") & filters.private & filters.user(ADMIN_IDS))
|
| 631 |
+
async def send_to_specific_group(client, message):
|
| 632 |
+
if not message.reply_to_message:
|
| 633 |
+
return await message.reply("❌ <b>Please reply to a message, photo, or video that you want to send.</b>\n\nExample: `/sendto -1001234567890`")
|
| 634 |
+
|
| 635 |
+
args = message.command
|
| 636 |
+
if len(args) < 2: return await message.reply("❌ <b>Group ID missing!</b>\n\nCorrect format:\n`/sendto -1003973566529`")
|
| 637 |
|
| 638 |
+
try:
|
| 639 |
+
group_id = int(args[1])
|
| 640 |
+
status = await message.reply("⏳ Sending message to group...")
|
| 641 |
+
await message.reply_to_message.copy(chat_id=group_id)
|
| 642 |
+
await status.edit_text(f"✅ <b>Successfully sent to Group ID:</b> <code>{group_id}</code>", parse_mode=enums.ParseMode.HTML)
|
| 643 |
+
except Exception as e:
|
| 644 |
+
await status.edit_text(f"❌ <b>Failed to send!</b>\nError: {e}", parse_mode=enums.ParseMode.HTML)
|
| 645 |
+
|
| 646 |
+
|
| 647 |
+
# ================= MASS CLONE FROM ALL GROUPS =================
|
| 648 |
+
async def clone_all_background(client, dest_id, status_msg):
|
| 649 |
+
try:
|
| 650 |
+
groups_res = await db_query(lambda: supabase.table('groups').select('group_id').execute())
|
| 651 |
+
if not groups_res.data:
|
| 652 |
+
return await status_msg.edit_text("❌ <b>No groups found in database.</b>", parse_mode=enums.ParseMode.HTML)
|
| 653 |
+
|
| 654 |
+
groups = [g['group_id'] for g in groups_res.data]
|
| 655 |
+
total_groups = len(groups)
|
| 656 |
+
|
| 657 |
+
await status_msg.edit_text(f"✅ Found <b>{total_groups}</b> groups.\n🚀 Starting mass cloning from oldest videos...", parse_mode=enums.ParseMode.HTML)
|
| 658 |
+
|
| 659 |
+
total_copied = 0
|
| 660 |
+
|
| 661 |
+
for index, source_id in enumerate(groups, 1):
|
| 662 |
+
if source_id == dest_id or source_id == STORAGE_CHANNEL_ID or source_id == PREMIUM_CHANNEL_ID:
|
| 663 |
+
continue
|
| 664 |
+
|
| 665 |
+
try:
|
| 666 |
+
await status_msg.edit_text(f"⏳ <b>Cloning Group {index}/{total_groups}</b>\nID: <code>{source_id}</code>\nTotal Copied So Far: <b>{total_copied}</b>", parse_mode=enums.ParseMode.HTML)
|
| 667 |
+
except Exception: pass
|
| 668 |
+
|
| 669 |
+
progress_res = await db_query(lambda: supabase.table('clone_progress').select('last_copied_id').eq('source_id', source_id).eq('dest_id', dest_id).execute())
|
| 670 |
+
last_copied_id = progress_res.data[0]['last_copied_id'] if progress_res.data else None
|
| 671 |
+
|
| 672 |
+
video_ids = []
|
| 673 |
+
retries = 3
|
| 674 |
+
while retries > 0:
|
| 675 |
+
try:
|
| 676 |
+
async for msg in client.search_messages(source_id, filter=enums.MessagesFilter.VIDEO):
|
| 677 |
+
if last_copied_id and msg.id <= last_copied_id: continue
|
| 678 |
+
video_ids.append(msg.id)
|
| 679 |
+
if len(video_ids) % 100 == 0: await asyncio.sleep(0.1)
|
| 680 |
+
break
|
| 681 |
+
except FloodWait as e:
|
| 682 |
+
await asyncio.sleep(e.value + 1)
|
| 683 |
+
except Exception as e:
|
| 684 |
+
err_msg = str(e).lower()
|
| 685 |
+
if "disconnect" in err_msg or "connection" in err_msg or "timeout" in err_msg:
|
| 686 |
+
retries -= 1
|
| 687 |
+
video_ids = []
|
| 688 |
+
await asyncio.sleep(5)
|
| 689 |
+
else:
|
| 690 |
+
break
|
| 691 |
+
|
| 692 |
+
if not video_ids:
|
| 693 |
+
continue
|
| 694 |
+
|
| 695 |
+
video_ids.reverse()
|
| 696 |
+
|
| 697 |
+
for msg_id in video_ids:
|
| 698 |
+
copy_retries = 3
|
| 699 |
+
copy_success = False
|
| 700 |
+
while copy_retries > 0:
|
| 701 |
+
try:
|
| 702 |
+
await client.copy_message(chat_id=dest_id, from_chat_id=source_id, message_id=msg_id)
|
| 703 |
+
total_copied += 1
|
| 704 |
+
await save_progress(source_id, dest_id, msg_id)
|
| 705 |
+
copy_success = True
|
| 706 |
+
break
|
| 707 |
+
except FloodWait as e:
|
| 708 |
+
await asyncio.sleep(e.value + 2)
|
| 709 |
+
except Exception as e:
|
| 710 |
+
err_msg = str(e).lower()
|
| 711 |
+
if "disconnect" in err_msg or "connection" in err_msg or "timeout" in err_msg:
|
| 712 |
+
copy_retries -= 1
|
| 713 |
+
await asyncio.sleep(3)
|
| 714 |
+
else:
|
| 715 |
+
break
|
| 716 |
+
await asyncio.sleep(2.5)
|
| 717 |
+
|
| 718 |
+
await status_msg.edit_text(f"🎉 <b>Mass Cloning Completely Finished!</b>\n\nTotal Videos Copied from all groups: <b>{total_copied}</b>", parse_mode=enums.ParseMode.HTML)
|
| 719 |
+
|
| 720 |
+
except Exception as e:
|
| 721 |
+
try: await status_msg.edit_text(f"❌ <b>Cloning Error:</b> {e}", parse_mode=enums.ParseMode.HTML)
|
| 722 |
+
except: pass
|
| 723 |
+
|
| 724 |
+
@bot.on_message(filters.command("cloneall") & filters.private & filters.user(ADMIN_IDS))
|
| 725 |
+
async def start_mass_cloning(client, message):
|
| 726 |
+
dest_id = -1003798479478
|
| 727 |
+
status_msg = await message.reply(f"⏳ <b>Initializing mass cloning task to <code>{dest_id}</code>...</b>", parse_mode=enums.ParseMode.HTML)
|
| 728 |
+
asyncio.create_task(clone_all_background(client, dest_id, status_msg))
|
| 729 |
+
|
| 730 |
+
|
| 731 |
+
async def clone_videos_background(client, source_id, dest_id, status_msg):
|
| 732 |
+
try:
|
| 733 |
+
progress_res = await db_query(lambda: supabase.table('clone_progress').select('last_copied_id').eq('source_id', source_id).eq('dest_id', dest_id).execute())
|
| 734 |
+
|
| 735 |
+
last_copied_id = None
|
| 736 |
+
if progress_res.data:
|
| 737 |
+
last_copied_id = progress_res.data[0]['last_copied_id']
|
| 738 |
+
await status_msg.edit_text(f"⏳ <b>Resuming clone task...</b>\nFound previous progress. Resuming after video ID <code>{last_copied_id}</code>...\nFetching video list from <code>{source_id}</code>...", parse_mode=enums.ParseMode.HTML)
|
| 739 |
+
else:
|
| 740 |
+
await status_msg.edit_text(f"⏳ <b>Cloning started!</b>\nFetching video list from <code>{source_id}</code>...\n<i>This might take a few minutes if the group has many videos.</i>", parse_mode=enums.ParseMode.HTML)
|
| 741 |
+
|
| 742 |
+
video_ids = []
|
| 743 |
+
retries = 5
|
| 744 |
+
while retries > 0:
|
| 745 |
+
try:
|
| 746 |
+
if not client.is_connected: await client.connect()
|
| 747 |
+
async for msg in client.search_messages(source_id, filter=enums.MessagesFilter.VIDEO):
|
| 748 |
+
if last_copied_id and msg.id <= last_copied_id: continue
|
| 749 |
+
video_ids.append(msg.id)
|
| 750 |
+
if len(video_ids) % 200 == 0: await asyncio.sleep(0.1)
|
| 751 |
+
break
|
| 752 |
+
except Exception as e:
|
| 753 |
+
err_msg = str(e).lower()
|
| 754 |
+
if "disconnect" in err_msg or "connection" in err_msg or "timeout" in err_msg or "reset" in err_msg:
|
| 755 |
+
retries -= 1
|
| 756 |
+
video_ids = []
|
| 757 |
+
await status_msg.edit_text(f"⚠️ <b>Network issue detected!</b>\nRetrying in 10 seconds... (Attempts left: {retries})\nError: <code>{e}</code>", parse_mode=enums.ParseMode.HTML)
|
| 758 |
+
await asyncio.sleep(10)
|
| 759 |
+
else: raise e
|
| 760 |
+
|
| 761 |
+
if not video_ids:
|
| 762 |
+
if last_copied_id: return await status_msg.edit_text("🎉 <b>All videos are already cloned!</b>\nNo new videos found in the source group.", parse_mode=enums.ParseMode.HTML)
|
| 763 |
+
else: return await status_msg.edit_text("❌ <b>No videos found in the source group!</b>\n(Make sure the bot is an admin with read history permission in that group).", parse_mode=enums.ParseMode.HTML)
|
| 764 |
+
|
| 765 |
+
video_ids.reverse()
|
| 766 |
+
total = len(video_ids)
|
| 767 |
+
|
| 768 |
+
if last_copied_id: await status_msg.edit_text(f"✅ Found <b>{total}</b> new videos to clone.\n🚀 Resuming background cloning from oldest to newest...", parse_mode=enums.ParseMode.HTML)
|
| 769 |
+
else: await status_msg.edit_text(f"✅ Found <b>{total}</b> videos.\n🚀 Background cloning started from oldest to newest...", parse_mode=enums.ParseMode.HTML)
|
| 770 |
+
|
| 771 |
+
success = 0
|
| 772 |
+
failed = 0
|
| 773 |
+
|
| 774 |
+
for index, msg_id in enumerate(video_ids, 1):
|
| 775 |
+
copy_success = False
|
| 776 |
+
copy_retries = 3
|
| 777 |
+
while copy_retries > 0:
|
| 778 |
+
try:
|
| 779 |
+
if not client.is_connected: await client.connect()
|
| 780 |
+
await client.copy_message(chat_id=dest_id, from_chat_id=source_id, message_id=msg_id)
|
| 781 |
+
success += 1
|
| 782 |
+
await save_progress(source_id, dest_id, msg_id)
|
| 783 |
+
copy_success = True
|
| 784 |
+
break
|
| 785 |
+
except FloodWait as e: await asyncio.sleep(e.value + 2)
|
| 786 |
+
except Exception as e:
|
| 787 |
+
err_msg = str(e).lower()
|
| 788 |
+
if "disconnect" in err_msg or "connection" in err_msg or "timeout" in err_msg or "reset" in err_msg:
|
| 789 |
+
copy_retries -= 1
|
| 790 |
+
await asyncio.sleep(5)
|
| 791 |
+
else: break
|
| 792 |
+
|
| 793 |
+
if not copy_success: failed += 1
|
| 794 |
+
if index % 20 == 0 or index == total:
|
| 795 |
+
try: await status_msg.edit_text(f"⏳ <b>Cloning in progress... (Background)</b>\n\nTotal Videos to Copy: <b>{total}</b>\n✅ Copied: <b>{success}</b>\n❌ Failed: <b>{failed}</b>\nLast Video ID: <code>{msg_id}</code>", parse_mode=enums.ParseMode.HTML)
|
| 796 |
+
except: pass
|
| 797 |
+
|
| 798 |
+
await asyncio.sleep(2.5)
|
| 799 |
+
|
| 800 |
+
await status_msg.edit_text(f"🎉 <b>Cloning Completely Finished!</b>\n\nSource: <code>{source_id}</code>\nTotal Copied: <b>{total}</b>\n✅ Successfully Copied: <b>{success}</b>\n❌ Failed: <b>{failed}</b>", parse_mode=enums.ParseMode.HTML)
|
| 801 |
+
|
| 802 |
+
except Exception as e:
|
| 803 |
+
try: await status_msg.edit_text(f"❌ <b>Cloning Error:</b> {e}", parse_mode=enums.ParseMode.HTML)
|
| 804 |
+
except: pass
|
| 805 |
+
|
| 806 |
+
@bot.on_message(filters.command("clone") & filters.private & filters.user(ADMIN_IDS))
|
| 807 |
+
async def start_cloning(client, message):
|
| 808 |
+
args = message.command
|
| 809 |
+
if len(args) != 3: return await message.reply("❌ <b>Invalid format!</b>\n\nUse: `/clone <Source_Group_ID> <Destination_Group_ID>`\nExample: `/clone -100123456789 -100987654321`", parse_mode=enums.ParseMode.HTML)
|
| 810 |
+
|
| 811 |
+
try:
|
| 812 |
+
source_id = int(args[1])
|
| 813 |
+
dest_id = int(args[2])
|
| 814 |
+
except ValueError: return await message.reply("❌ Chat IDs must be numbers.")
|
| 815 |
+
|
| 816 |
+
status_msg = await message.reply("⏳ Initializing cloning task...", parse_mode=enums.ParseMode.HTML)
|
| 817 |
+
asyncio.create_task(clone_videos_background(client, source_id, dest_id, status_msg))
|
| 818 |
+
|
| 819 |
+
|
| 820 |
+
@bot.on_message(filters.command("upload") & filters.private & filters.user(ADMIN_IDS))
|
| 821 |
+
async def set_upload_mode(client, message):
|
| 822 |
+
global upload_mode
|
| 823 |
+
args = message.command
|
| 824 |
+
if len(args) > 1:
|
| 825 |
+
mode = args[1].lower()
|
| 826 |
+
if mode in ["telegram", "tg", "local"]:
|
| 827 |
+
upload_mode = "telegram"
|
| 828 |
+
await message.reply("✅ <b>Upload server set to: Telegram</b>\nVideos will be uploaded to your own channel and streamed via Hugging Face.")
|
| 829 |
+
elif mode in ["byse", "byse.sx", "external"]:
|
| 830 |
+
upload_mode = "byse"
|
| 831 |
+
await message.reply("✅ <b>Upload server set to: Byse.sx</b>\nVideos will be uploaded to Byse.sx and streamed via their player.")
|
| 832 |
+
else: await message.reply("❌ <b>Invalid server!</b> Use `/upload telegram` or `/upload byse`.")
|
| 833 |
+
else:
|
| 834 |
+
await message.reply(f"📌 <b>Current Upload Server:</b> <code>{upload_mode.upper()}</code>\n\nTo change, use:\n👉 `/upload telegram` (Storage Channel Stream)\n👉 `/upload byse` (Byse.sx third-party player)")
|
| 835 |
+
|
| 836 |
+
@bot.on_message(filters.command("blur") & filters.private & filters.user(ADMIN_IDS))
|
| 837 |
+
async def set_blur_state(client, message):
|
| 838 |
+
try:
|
| 839 |
+
args = message.text.split()
|
| 840 |
+
if len(args) > 1 and args[1].lower() in ['0', '0%', 'off', 'cancel']:
|
| 841 |
+
if message.chat.id in admin_states:
|
| 842 |
+
admin_states[message.chat.id].pop("blur_percent", None)
|
| 843 |
+
admin_states[message.chat.id].pop("clear_percent", None)
|
| 844 |
+
await message.reply("✅ <b>Blur mode is disabled!</b>\nUploaded videos will no longer be blurred, only watermarked as before.", parse_mode=enums.ParseMode.HTML)
|
| 845 |
+
return
|
| 846 |
+
|
| 847 |
+
match = re.search(r'/blur\s+(\d+)%?(?:\s+(\d+)%?)?', message.text, re.IGNORECASE)
|
| 848 |
+
if match:
|
| 849 |
+
percent = int(match.group(1))
|
| 850 |
+
clear_percent = int(match.group(2)) if match.group(2) else 0
|
| 851 |
+
|
| 852 |
+
if percent == 0:
|
| 853 |
+
if message.chat.id in admin_states:
|
| 854 |
+
admin_states[message.chat.id].pop("blur_percent", None)
|
| 855 |
+
admin_states[message.chat.id].pop("clear_percent", None)
|
| 856 |
+
await message.reply("✅ <b>Blur mode is disabled!</b>", parse_mode=enums.ParseMode.HTML)
|
| 857 |
+
return
|
| 858 |
+
|
| 859 |
+
if message.chat.id not in admin_states: admin_states[message.chat.id] = {}
|
| 860 |
+
admin_states[message.chat.id]["blur_percent"] = percent
|
| 861 |
+
admin_states[message.chat.id]["clear_percent"] = clear_percent
|
| 862 |
+
|
| 863 |
+
clear_msg = f"and the top <b>{clear_percent}%</b> part will remain clear." if clear_percent > 0 else "The entire photo/video will be blurred."
|
| 864 |
+
reply_text = f"✅ <b>Blur set to: {percent}%</b>\n📌 {clear_msg}\n\nThis will be applied to all future uploads.\n<i>(To disable, send /blur 0)</i>"
|
| 865 |
+
await message.reply(reply_text, parse_mode=enums.ParseMode.HTML)
|
| 866 |
+
else: await message.reply("❌ <b>Invalid command!</b>\nCorrect format: `/blur 60` or `/blur 60 20`")
|
| 867 |
+
except Exception as e: print(e)
|
| 868 |
+
|
| 869 |
+
def upload_file_sync(upload_url, file_path, api_key):
|
| 870 |
+
try:
|
| 871 |
+
with open(file_path, 'rb') as f:
|
| 872 |
+
res = requests.post(upload_url, data={'key': api_key}, files={'file': f}, timeout=900)
|
| 873 |
+
return res.json() if res.status_code == 200 else {}
|
| 874 |
+
except Exception: return {}
|
| 875 |
+
|
| 876 |
+
@bot.on_message((filters.video | filters.animation | filters.photo | filters.document) & filters.private & filters.user(ADMIN_IDS))
|
| 877 |
+
async def handle_media_upload(client, message):
|
| 878 |
+
global upload_mode
|
| 879 |
+
state = admin_states.get(message.chat.id, {})
|
| 880 |
+
if state.get("step") == "broadcast":
|
| 881 |
+
await process_broadcast(client, message)
|
| 882 |
+
return
|
| 883 |
+
|
| 884 |
+
is_video = message.video or (message.document and message.document.mime_type and "video" in message.document.mime_type)
|
| 885 |
+
is_animation = message.animation or (message.document and message.document.mime_type and "gif" in message.document.mime_type)
|
| 886 |
+
is_photo = message.photo or (message.document and message.document.mime_type and "image" in message.document.mime_type)
|
| 887 |
+
|
| 888 |
+
if not (is_video or is_animation or is_photo): return
|
| 889 |
+
|
| 890 |
+
media_type = "video" if (is_video or is_animation) else "photo"
|
| 891 |
+
has_blur_caption = message.caption and "/blur" in message.caption.lower()
|
| 892 |
+
is_persistent_blur = bool(state.get("blur_percent"))
|
| 893 |
+
|
| 894 |
+
if media_type == "photo" and not (has_blur_caption or is_persistent_blur):
|
| 895 |
+
status = await message.reply("⏳ Saving thumbnail...")
|
| 896 |
+
try:
|
| 897 |
+
local_path = await message.download()
|
| 898 |
+
def upload_to_supabase():
|
| 899 |
+
with open(local_path, 'rb') as f: file_bytes = f.read()
|
| 900 |
+
file_name = f"thumb_{int(time.time())}.jpg"
|
| 901 |
+
supabase.storage.from_('thumbnails').upload(file_name, file_bytes, {"content-type": "image/jpeg"})
|
| 902 |
+
return supabase.storage.from_('thumbnails').get_public_url(file_name)
|
| 903 |
+
|
| 904 |
+
direct_link = await asyncio.to_thread(upload_to_supabase)
|
| 905 |
+
if os.path.exists(local_path): os.remove(local_path)
|
| 906 |
+
await status.edit_text(f"✅ <b>Thumbnail saved successfully!</b>\n\n<code>{direct_link}</code>", parse_mode=enums.ParseMode.HTML)
|
| 907 |
+
except Exception as e: await status.edit_text(f"⚠️ Upload Error: {e}")
|
| 908 |
+
return
|
| 909 |
+
|
| 910 |
+
raw_caption = message.caption or ""
|
| 911 |
+
blur_match = re.search(r'/blur\s+(\d+)%?(?:\s+(\d+)%?)?', raw_caption, re.IGNORECASE)
|
| 912 |
+
|
| 913 |
+
is_blur = False
|
| 914 |
+
blur_percent = 0
|
| 915 |
+
clear_percent = 0
|
| 916 |
+
clean_caption = raw_caption
|
| 917 |
+
|
| 918 |
+
if blur_match:
|
| 919 |
+
is_blur = True
|
| 920 |
+
blur_percent = int(blur_match.group(1))
|
| 921 |
+
clear_percent = int(blur_match.group(2)) if match.group(2) else 0
|
| 922 |
+
clean_caption = re.sub(r'/blur\s*\d+%?(?:\s*\d+%?)?', '', raw_caption, flags=re.IGNORECASE).strip()
|
| 923 |
+
elif state.get("blur_percent"):
|
| 924 |
+
is_blur = True
|
| 925 |
+
blur_percent = state["blur_percent"]
|
| 926 |
+
clear_percent = state.get("clear_percent", 0)
|
| 927 |
+
|
| 928 |
+
is_large_video = False
|
| 929 |
+
if media_type == "video":
|
| 930 |
+
media = get_media_obj(message)
|
| 931 |
+
duration = media.duration if media and hasattr(media, 'duration') and media.duration else 0
|
| 932 |
+
file_size = media.file_size if media and hasattr(media, 'file_size') and media.file_size else 0
|
| 933 |
+
MAX_DURATION = 7200
|
| 934 |
+
MAX_SIZE = 1900 * 1024 * 1024
|
| 935 |
+
|
| 936 |
+
if duration > MAX_DURATION or file_size > MAX_SIZE:
|
| 937 |
+
is_large_video = True
|
| 938 |
+
is_blur = False
|
| 939 |
+
|
| 940 |
+
status_msg = await message.reply("⏳ <b>Video is too large!</b> Skipping blur..." if is_large_video else "⏳ Downloading media... 0%")
|
| 941 |
+
bot_me = client.me if client.me else await client.get_me()
|
| 942 |
+
bot_link = f"https://t.me/{bot_me.username}"
|
| 943 |
+
|
| 944 |
+
original_file, watermarked_file, blurred_file = None, None, None
|
| 945 |
+
clean_upload_file, telegram_file, embed_link = None, None, None
|
| 946 |
+
|
| 947 |
+
last_update_time = time.time()
|
| 948 |
+
async def download_progress(current, total):
|
| 949 |
+
nonlocal last_update_time
|
| 950 |
+
now = time.time()
|
| 951 |
+
if now - last_update_time >= 4.0:
|
| 952 |
+
try:
|
| 953 |
+
percent = (current / total) * 100
|
| 954 |
+
await status_msg.edit_text(f"⏳ Downloading media... {percent:.1f}%")
|
| 955 |
+
last_update_time = now
|
| 956 |
+
except Exception: pass
|
| 957 |
+
|
| 958 |
+
try:
|
| 959 |
+
original_file = await message.download(progress=download_progress)
|
| 960 |
+
clean_upload_file = original_file
|
| 961 |
+
|
| 962 |
+
if media_type == "video" and not is_large_video and ffmpeg_available:
|
| 963 |
+
await status_msg.edit_text("⏳ Watermarking video... (HD + Superfast Processing)")
|
| 964 |
+
watermarked_file = f"{original_file}_wm.mp4"
|
| 965 |
+
|
| 966 |
+
has_audio = not (message.animation or (message.document and message.document.mime_type and "gif" in message.document.mime_type))
|
| 967 |
+
audio_opts = ["-an"] if not has_audio else ["-c:a", "copy"]
|
| 968 |
+
|
| 969 |
+
cmd = [
|
| 970 |
+
"ffmpeg", "-y", "-i", original_file,
|
| 971 |
+
"-vf", "drawtext=text='@mxvdo':x=W-tw-20:y=H-th-20:fontsize=22:fontcolor=white@0.7:shadowcolor=black@0.8:shadowx=2:shadowy=2:enable='gte(t,5)'",
|
| 972 |
+
"-c:v", "libx264", "-preset", "superfast", "-crf", "23",
|
| 973 |
+
"-pix_fmt", "yuv420p"
|
| 974 |
+
] + audio_opts + ["-movflags", "+faststart", watermarked_file]
|
| 975 |
+
|
| 976 |
+
process = await asyncio.create_subprocess_exec(*cmd, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE)
|
| 977 |
+
await process.communicate()
|
| 978 |
+
|
| 979 |
+
if process.returncode == 0 and os.path.exists(watermarked_file) and os.path.getsize(watermarked_file) > 0:
|
| 980 |
+
clean_upload_file = watermarked_file
|
| 981 |
+
|
| 982 |
+
storage_msg_id = None
|
| 983 |
+
if media_type in ["video", "photo"]:
|
| 984 |
+
if upload_mode == "telegram":
|
| 985 |
+
await status_msg.edit_text("⏳ Uploading Clean HD video to your storage channel...")
|
| 986 |
+
|
| 987 |
+
thumb_path_storage = None
|
| 988 |
+
if ffmpeg_available:
|
| 989 |
+
thumb_path_storage = f"{original_file}_storage_thumb.jpg"
|
| 990 |
+
proc = await asyncio.create_subprocess_exec("ffmpeg", "-y", "-i", clean_upload_file, "-vframes", "1", thumb_path_storage, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE)
|
| 991 |
+
await proc.communicate()
|
| 992 |
+
if not os.path.exists(thumb_path_storage):
|
| 993 |
+
thumb_path_storage = None
|
| 994 |
+
|
| 995 |
+
media = get_media_obj(message)
|
| 996 |
+
vid_duration = media.duration if media and hasattr(media, 'duration') and media.duration else 0
|
| 997 |
+
vid_width = media.width if media and hasattr(media, 'width') and media.width else 0
|
| 998 |
+
vid_height = media.height if media and hasattr(media, 'height') and media.height else 0
|
| 999 |
+
|
| 1000 |
+
sent_to_channel = await client.send_video(
|
| 1001 |
+
chat_id=STORAGE_CHANNEL_ID,
|
| 1002 |
+
video=clean_upload_file,
|
| 1003 |
+
caption=f"Backup of video uploaded by Admin. File: {os.path.basename(clean_upload_file)}",
|
| 1004 |
+
duration=vid_duration,
|
| 1005 |
+
width=vid_width,
|
| 1006 |
+
height=vid_height,
|
| 1007 |
+
thumb=thumb_path_storage
|
| 1008 |
+
)
|
| 1009 |
+
storage_msg_id = sent_to_channel.id
|
| 1010 |
+
|
| 1011 |
+
stream_link = f"{BACKEND_URL}/stream/{storage_msg_id}"
|
| 1012 |
+
download_link = f"{BACKEND_URL}/download/{storage_msg_id}"
|
| 1013 |
+
embed_link = stream_link
|
| 1014 |
+
else:
|
| 1015 |
+
await status_msg.edit_text("⏳ Uploading Clean HD video to byse.sx server...")
|
| 1016 |
+
api_endpoint = "https://api.byse.sx/upload/server"
|
| 1017 |
+
loop = asyncio.get_event_loop()
|
| 1018 |
+
response = await loop.run_in_executor(None, lambda: requests.get(api_endpoint, params={'key': BYSE_API_KEY}, timeout=30))
|
| 1019 |
+
result = response.json()
|
| 1020 |
+
|
| 1021 |
+
if result.get('status') == 200:
|
| 1022 |
+
upload_res = await loop.run_in_executor(None, upload_file_sync, result.get('result'), clean_upload_file, BYSE_API_KEY)
|
| 1023 |
+
if upload_res.get('status') == 200 and 'files' in upload_res and len(upload_res['files']) > 0:
|
| 1024 |
+
file_status = upload_res['files'][0].get('status', '')
|
| 1025 |
+
if "not allowed" in str(file_status).lower():
|
| 1026 |
+
await status_msg.edit_text(f"❌ byse.sx rejected the file: <code>{file_status}</code>", parse_mode=enums.ParseMode.HTML)
|
| 1027 |
+
return
|
| 1028 |
+
file_code = upload_res['files'][0].get('filecode')
|
| 1029 |
+
if file_code: embed_link = f"https://bysesayeveum.com/e/{file_code}"
|
| 1030 |
+
|
| 1031 |
+
if not embed_link:
|
| 1032 |
+
await status_msg.edit_text("❌ Uploaded to byse.sx but Embed Link not found.")
|
| 1033 |
+
return
|
| 1034 |
+
|
| 1035 |
+
stream_link = embed_link
|
| 1036 |
+
download_link = embed_link
|
| 1037 |
+
|
| 1038 |
+
if is_large_video:
|
| 1039 |
+
admin_cap = f"✅ <b>Success! (Large Video)</b>\n\n🔗 <b>Embed Link (Clean HD):</b>\n<code>{embed_link or 'N/A'}</code>\n\n📌 <i>Broadcast skipped due to large file size.</i>"
|
| 1040 |
+
await client.send_video(message.chat.id, message.video.file_id, caption=admin_cap, parse_mode=enums.ParseMode.HTML)
|
| 1041 |
+
await status_msg.delete()
|
| 1042 |
+
return
|
| 1043 |
+
|
| 1044 |
+
telegram_file = clean_upload_file
|
| 1045 |
+
if is_blur and not is_large_video and ffmpeg_available:
|
| 1046 |
+
await status_msg.edit_text(f"⏳ Applying {blur_percent}% blur for Telegram broadcast...")
|
| 1047 |
+
radius = max(2, min(20, int((blur_percent / 100.0) * 30)))
|
| 1048 |
+
ext = "jpg" if media_type == "photo" else "mp4"
|
| 1049 |
+
blurred_file = f"{original_file}_blurred.{ext}"
|
| 1050 |
+
|
| 1051 |
+
if clear_percent > 0:
|
| 1052 |
+
clear_ratio = clear_percent / 100.0
|
| 1053 |
+
ff_filter = ["-filter_complex", f"[0:v]split[v1][v2];[v2]boxblur={radius}:1[blurred];[v1]crop=iw:ih*{clear_ratio}:0:0[top];[blurred][top]overlay=0:0[vout]", "-map", "[vout]"]
|
| 1054 |
+
if media_type == "video":
|
| 1055 |
+
ff_filter.extend(["-map", "0:a?"])
|
| 1056 |
+
else:
|
| 1057 |
+
ff_filter = ["-vf", f"boxblur={radius}:1"]
|
| 1058 |
+
|
| 1059 |
+
has_audio = not (message.animation or (message.document and message.document.mime_type and "gif" in message.document.mime_type))
|
| 1060 |
+
audio_opts_blur = ["-an"] if not has_audio else []
|
| 1061 |
+
|
| 1062 |
+
if media_type == "photo":
|
| 1063 |
+
cmd_blur = ["ffmpeg", "-y", "-i", clean_upload_file] + ff_filter + [blurred_file]
|
| 1064 |
+
else:
|
| 1065 |
+
cmd_blur = ["ffmpeg", "-y", "-i", clean_upload_file] + ff_filter + ["-c:v", "libx264", "-preset", "superfast", "-crf", "23", "-pix_fmt", "yuv420p"] + audio_opts_blur + ["-movflags", "+faststart", blurred_file]
|
| 1066 |
+
|
| 1067 |
+
process_blur = await asyncio.create_subprocess_exec(*cmd_blur, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE)
|
| 1068 |
+
await process_blur.communicate()
|
| 1069 |
+
|
| 1070 |
+
if process_blur.returncode == 0 and os.path.exists(blurred_file) and os.path.getsize(blurred_file) > 0:
|
| 1071 |
+
telegram_file = blurred_file
|
| 1072 |
+
|
| 1073 |
+
await status_msg.edit_text("⏳ Preparing to broadcast to groups...")
|
| 1074 |
+
if media_type == "video":
|
| 1075 |
+
caption_text = (
|
| 1076 |
+
f"🔥 <b>New Premium Viral Video Leaked!</b> 🔞\n\n"
|
| 1077 |
+
f"🎬 <b>Watch HD Video Here:</b>\n"
|
| 1078 |
+
f"👉 <b><a href='{embed_link if is_blur else bot_link}'>▶️ Click Here to Watch</a></b>\n\n"
|
| 1079 |
+
f"🎁 <b>App এর ভেতর 'Secret Box' ওপেন করে Premium Channel Claim করুন!</b> 👇"
|
| 1080 |
+
)
|
| 1081 |
+
else:
|
| 1082 |
+
caption_text = (
|
| 1083 |
+
f"{clean_caption}\n\n🎁 <b>App এর ভেতর 'Secret Box' ওপেন করে Premium Channel Claim করুন!</b> 👇"
|
| 1084 |
+
if clean_caption else
|
| 1085 |
+
f"🔥 <b>New Premium Viral Content!</b> 🔞\n\n🎬 <b>Watch HD Video Here:</b>\n👉 <b><a href='{bot_link}'>▶️ Click Here to Watch</a></b>\n\n🎁 <b>App এর ভেতর 'Secret Box' ওপেন করে Premium Channel Claim করুন!</b> 👇"
|
| 1086 |
+
)
|
| 1087 |
+
|
| 1088 |
+
group_markup = InlineKeyboardMarkup([
|
| 1089 |
+
[InlineKeyboardButton("🎬 Watch Full Video Here 🔞", url=bot_link)],
|
| 1090 |
+
[InlineKeyboardButton("🎁 Open Secret Box", url=f"{bot_link}?start=secretbox")]
|
| 1091 |
+
])
|
| 1092 |
+
|
| 1093 |
+
admin_cap = (
|
| 1094 |
+
f"✅ <b>Upload and Processing Complete!</b>\n\n"
|
| 1095 |
+
f"🎬 <b>Stream/Watch Online Link:</b>\n<code>{stream_link}</code>\n\n"
|
| 1096 |
+
f"📥 <b>Direct Download Link:</b>\n<code>{download_link}</code>"
|
| 1097 |
+
)
|
| 1098 |
+
|
| 1099 |
+
thumb_path = None
|
| 1100 |
+
if media_type == "video" and ffmpeg_available:
|
| 1101 |
+
thumb_path = f"{original_file}_thumb.jpg"
|
| 1102 |
+
proc = await asyncio.create_subprocess_exec("ffmpeg", "-y", "-i", telegram_file, "-vframes", "1", thumb_path, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE)
|
| 1103 |
+
await proc.communicate()
|
| 1104 |
+
if not os.path.exists(thumb_path): thumb_path = None
|
| 1105 |
+
|
| 1106 |
+
try:
|
| 1107 |
+
if media_type == "photo":
|
| 1108 |
+
sent_to_admin = await client.send_photo(message.chat.id, telegram_file, caption=admin_cap, parse_mode=enums.ParseMode.HTML)
|
| 1109 |
+
else:
|
| 1110 |
+
media = get_media_obj(message)
|
| 1111 |
+
vid_duration = media.duration if media and hasattr(media, 'duration') and media.duration else 0
|
| 1112 |
+
vid_width = media.width if media and hasattr(media, 'width') and media.width else 0
|
| 1113 |
+
vid_height = media.height if media and hasattr(media, 'height') and media.height else 0
|
| 1114 |
+
|
| 1115 |
+
sent_to_admin = await client.send_video(
|
| 1116 |
+
message.chat.id,
|
| 1117 |
+
telegram_file,
|
| 1118 |
+
caption=admin_cap,
|
| 1119 |
+
parse_mode=enums.ParseMode.HTML,
|
| 1120 |
+
duration=vid_duration,
|
| 1121 |
+
width=vid_width,
|
| 1122 |
+
height=vid_height,
|
| 1123 |
+
thumb=thumb_path
|
| 1124 |
+
)
|
| 1125 |
+
|
| 1126 |
+
await save_progress(message.chat.id, 0, sent_to_admin.id)
|
| 1127 |
+
|
| 1128 |
+
except Exception as e:
|
| 1129 |
+
await message.reply(f"❌ Failed to send final file to you: {e}")
|
| 1130 |
+
return
|
| 1131 |
+
|
| 1132 |
+
tg_file_id = get_msg_file_id(sent_to_admin)
|
| 1133 |
+
if not tg_file_id:
|
| 1134 |
+
tg_file_id = get_msg_file_id(message)
|
| 1135 |
+
|
| 1136 |
+
try: await status_msg.delete()
|
| 1137 |
+
except: pass
|
| 1138 |
+
|
| 1139 |
+
try:
|
| 1140 |
+
groups_res = await db_query(lambda: supabase.table('groups').select('group_id').execute())
|
| 1141 |
+
except Exception as db_err:
|
| 1142 |
+
await message.reply(f"⚠️ Failed to fetch groups from database: {db_err}")
|
| 1143 |
+
return
|
| 1144 |
+
|
| 1145 |
+
group_ids = [g['group_id'] for g in groups_res.data]
|
| 1146 |
+
success_count, fail_count = 0, 0
|
| 1147 |
+
|
| 1148 |
+
for gid in set(group_ids):
|
| 1149 |
+
retries = 3
|
| 1150 |
+
while retries > 0:
|
| 1151 |
+
try:
|
| 1152 |
+
if not client.is_connected: await client.connect()
|
| 1153 |
+
if media_type == "photo": await client.send_photo(gid, tg_file_id, caption=caption_text, parse_mode=enums.ParseMode.HTML, reply_markup=group_markup)
|
| 1154 |
+
else: await client.send_video(gid, tg_file_id, caption=caption_text, parse_mode=enums.ParseMode.HTML, reply_markup=group_markup)
|
| 1155 |
+
success_count += 1
|
| 1156 |
+
break
|
| 1157 |
+
except FloodWait as e:
|
| 1158 |
+
await asyncio.sleep(e.value + 1)
|
| 1159 |
+
except Exception as e:
|
| 1160 |
+
err_msg = str(e).lower()
|
| 1161 |
+
if "disconnect" in err_msg or "connection" in err_msg or "timeout" in err_msg or "network" in err_msg:
|
| 1162 |
+
retries -= 1
|
| 1163 |
+
await asyncio.sleep(3)
|
| 1164 |
+
else:
|
| 1165 |
+
fail_count += 1
|
| 1166 |
+
break
|
| 1167 |
+
await asyncio.sleep(1.5)
|
| 1168 |
+
|
| 1169 |
+
retries = 3
|
| 1170 |
+
while retries > 0:
|
| 1171 |
+
try:
|
| 1172 |
+
if not client.is_connected: await client.connect()
|
| 1173 |
+
await message.reply(f"📢 <b>Broadcast Complete!</b>\n\n✅ Success: {success_count} groups\n❌ Failed: {fail_count} groups", parse_mode=enums.ParseMode.HTML)
|
| 1174 |
+
break
|
| 1175 |
+
except FloodWait as e:
|
| 1176 |
+
await asyncio.sleep(e.value + 1)
|
| 1177 |
+
except Exception as e:
|
| 1178 |
+
err_msg = str(e).lower()
|
| 1179 |
+
if "disconnect" in err_msg or "connection" in err_msg or "timeout" in err_msg:
|
| 1180 |
+
retries -= 1
|
| 1181 |
+
await asyncio.sleep(3)
|
| 1182 |
+
else: break
|
| 1183 |
+
|
| 1184 |
+
except Exception as e:
|
| 1185 |
+
try: await message.reply(f"⚠️ Error during upload/broadcast: {str(e)}")
|
| 1186 |
+
except: pass
|
| 1187 |
+
finally:
|
| 1188 |
+
for f in [original_file, watermarked_file, blurred_file, f"{original_file}_thumb.jpg" if original_file else None, f"{original_file}_storage_thumb.jpg" if original_file else None]:
|
| 1189 |
+
if f and os.path.exists(f):
|
| 1190 |
+
try: os.remove(f)
|
| 1191 |
+
except: pass
|
| 1192 |
+
|
| 1193 |
+
@bot.on_message(filters.command(["stats", "users"]) & filters.private & filters.user(ADMIN_IDS))
|
| 1194 |
+
async def bot_stats(client, message):
|
| 1195 |
+
try:
|
| 1196 |
+
users = await db_query(lambda: supabase.table('referrals').select('user_id', count='exact').execute())
|
| 1197 |
+
videos = await db_query(lambda: supabase.table('videos').select('*', count='exact').execute())
|
| 1198 |
+
groups = await db_query(lambda: supabase.table('groups').select('group_id', count='exact').execute())
|
| 1199 |
+
await message.reply(f"📊 <b>Bot Stats:</b>\n👥 Users: <code>{users.count or 0}</code>\n🎬 Videos: <code>{videos.count or 0}</code>\n📢 Groups: <code>{groups.count or 0}</code>", parse_mode=enums.ParseMode.HTML)
|
| 1200 |
+
except Exception as e: print(e)
|
| 1201 |
+
|
| 1202 |
+
@bot.on_message(filters.command("broadcast") & filters.private & filters.user(ADMIN_IDS))
|
| 1203 |
+
async def broadcast_command(client, message):
|
| 1204 |
+
admin_states[message.chat.id] = {"step": "broadcast"}
|
| 1205 |
+
await message.reply("📢 Send the message you want to broadcast. (Send /cancel to abort)")
|
| 1206 |
+
|
| 1207 |
+
async def process_broadcast(client, message):
|
| 1208 |
+
text = message.text or message.caption
|
| 1209 |
+
if text == '/cancel':
|
| 1210 |
+
admin_states.pop(message.chat.id, None)
|
| 1211 |
+
return await message.reply("❌ Cancelled.")
|
| 1212 |
+
|
| 1213 |
+
await message.reply("⏳ Broadcast started...")
|
| 1214 |
+
admin_states.pop(message.chat.id, None)
|
| 1215 |
+
|
| 1216 |
+
try:
|
| 1217 |
+
all_users, start, step = [], 0, 1000
|
| 1218 |
+
while True:
|
| 1219 |
+
res = await db_query(lambda: supabase.table('referrals').select('user_id').range(start, start + step - 1).execute())
|
| 1220 |
+
if not res.data: break
|
| 1221 |
+
all_users.extend(res.data)
|
| 1222 |
+
start += step
|
| 1223 |
+
|
| 1224 |
+
success, failed = 0, 0
|
| 1225 |
+
for u in all_users:
|
| 1226 |
+
try:
|
| 1227 |
+
await message.copy(chat_id=u['user_id'])
|
| 1228 |
+
success += 1
|
| 1229 |
+
await asyncio.sleep(0.15)
|
| 1230 |
+
except FloodWait as e:
|
| 1231 |
+
await asyncio.sleep(e.value + 1)
|
| 1232 |
+
try:
|
| 1233 |
+
await message.copy(chat_id=u['user_id'])
|
| 1234 |
+
success += 1
|
| 1235 |
+
except Exception: failed += 1
|
| 1236 |
+
except Exception: failed += 1
|
| 1237 |
+
|
| 1238 |
+
await message.reply(f"✅ Broadcast Complete!\nSuccess: {success}\nFailed: {failed}")
|
| 1239 |
+
except Exception as e: print(e)
|
| 1240 |
+
|
| 1241 |
+
@bot.on_message(filters.command("clean") & filters.private & filters.user(ADMIN_IDS))
|
| 1242 |
+
async def manual_clean_channel(client, message):
|
| 1243 |
+
await message.reply("⏳ <b>Starting channel cleanup...</b>\nChecking database users to verify active sessions. This might take a while.")
|
| 1244 |
+
try:
|
| 1245 |
+
kicked, checked = 0, 0
|
| 1246 |
+
res_users = await db_query(lambda: supabase.table('referrals').select('user_id').execute())
|
| 1247 |
+
if not res_users.data:
|
| 1248 |
+
return await message.reply("❌ No users found in database!")
|
| 1249 |
+
|
| 1250 |
+
user_ids = [u['user_id'] for u in res_users.data]
|
| 1251 |
+
|
| 1252 |
+
for user_id in set(user_ids):
|
| 1253 |
+
try:
|
| 1254 |
+
chat_member = await bot.get_chat_member(PREMIUM_CHANNEL_ID, user_id)
|
| 1255 |
+
if chat_member.status in [enums.ChatMemberStatus.MEMBER, enums.ChatMemberStatus.RESTRICTED]:
|
| 1256 |
+
checked += 1
|
| 1257 |
+
res_session = await db_query(lambda: supabase.table('user_sessions').select('session_string').eq('user_id', user_id).execute())
|
| 1258 |
+
|
| 1259 |
+
is_valid = False
|
| 1260 |
+
if res_session.data:
|
| 1261 |
+
session_string = res_session.data[0]['session_string']
|
| 1262 |
+
temp_client = Client(f"manual_chk_{user_id}", session_string=session_string, api_id=API_ID, api_hash=API_HASH, in_memory=True)
|
| 1263 |
+
try:
|
| 1264 |
+
await temp_client.connect()
|
| 1265 |
+
await temp_client.get_me()
|
| 1266 |
+
await temp_client.disconnect()
|
| 1267 |
+
is_valid = True
|
| 1268 |
+
except (SessionRevoked, AuthKeyUnregistered, UserDeactivated):
|
| 1269 |
+
try: await temp_client.disconnect()
|
| 1270 |
+
except: pass
|
| 1271 |
+
await db_query(lambda: supabase.table('user_sessions').delete().eq('user_id', user_id).execute())
|
| 1272 |
+
except Exception:
|
| 1273 |
+
try: await temp_client.disconnect()
|
| 1274 |
+
except: pass
|
| 1275 |
+
is_valid = True
|
| 1276 |
+
|
| 1277 |
+
if not is_valid:
|
| 1278 |
+
try:
|
| 1279 |
+
await client.ban_chat_member(PREMIUM_CHANNEL_ID, user_id)
|
| 1280 |
+
await client.unban_chat_member(PREMIUM_CHANNEL_ID, user_id)
|
| 1281 |
+
kicked += 1
|
| 1282 |
+
except Exception: pass
|
| 1283 |
+
|
| 1284 |
+
await asyncio.sleep(1.5)
|
| 1285 |
+
except FloodWait as e:
|
| 1286 |
+
await asyncio.sleep(e.value + 1)
|
| 1287 |
+
except Exception: pass
|
| 1288 |
+
|
| 1289 |
+
await message.reply(f"✅ <b>Cleanup Complete!</b>\n\n👥 Members checked: {checked}\n👢 Users Kicked: {kicked}")
|
| 1290 |
+
except Exception as e:
|
| 1291 |
+
await message.reply(f"❌ Error: {e}")
|
| 1292 |
+
|
| 1293 |
+
async def auto_clean_channel_loop():
|
| 1294 |
+
await asyncio.sleep(60)
|
| 1295 |
+
while True:
|
| 1296 |
+
try:
|
| 1297 |
+
res_users = await db_query(lambda: supabase.table('referrals').select('user_id').execute())
|
| 1298 |
+
if res_users.data:
|
| 1299 |
+
user_ids = [u['user_id'] for u in res_users.data]
|
| 1300 |
+
for user_id in set(user_ids):
|
| 1301 |
+
try:
|
| 1302 |
+
chat_member = await bot.get_chat_member(PREMIUM_CHANNEL_ID, user_id)
|
| 1303 |
+
if chat_member.status in [enums.ChatMemberStatus.MEMBER, enums.ChatMemberStatus.RESTRICTED]:
|
| 1304 |
+
res_session = await db_query(lambda: supabase.table('user_sessions').select('session_string').eq('user_id', user_id).execute())
|
| 1305 |
+
|
| 1306 |
+
is_valid = False
|
| 1307 |
+
if res_session.data:
|
| 1308 |
+
session_string = res_session.data[0]['session_string']
|
| 1309 |
+
temp_client = Client(f"bg_chk_{user_id}", session_string=session_string, api_id=API_ID, api_hash=API_HASH, in_memory=True)
|
| 1310 |
+
try:
|
| 1311 |
+
await temp_client.connect()
|
| 1312 |
+
await temp_client.get_me()
|
| 1313 |
+
await temp_client.disconnect()
|
| 1314 |
+
is_valid = True
|
| 1315 |
+
except (SessionRevoked, AuthKeyUnregistered, UserDeactivated):
|
| 1316 |
+
try: await temp_client.disconnect()
|
| 1317 |
+
except: pass
|
| 1318 |
+
await db_query(lambda: supabase.table('user_sessions').delete().eq('user_id', user_id).execute())
|
| 1319 |
+
except Exception:
|
| 1320 |
+
try: await temp_client.disconnect()
|
| 1321 |
+
except: pass
|
| 1322 |
+
is_valid = True
|
| 1323 |
+
|
| 1324 |
+
if not is_valid:
|
| 1325 |
+
try:
|
| 1326 |
+
await bot.ban_chat_member(PREMIUM_CHANNEL_ID, user_id)
|
| 1327 |
+
await bot.unban_chat_member(PREMIUM_CHANNEL_ID, user_id)
|
| 1328 |
+
except Exception: pass
|
| 1329 |
+
await asyncio.sleep(2)
|
| 1330 |
+
except FloodWait as e:
|
| 1331 |
+
await asyncio.sleep(e.value + 1)
|
| 1332 |
+
except Exception: pass
|
| 1333 |
+
except Exception as e:
|
| 1334 |
+
print(f"Auto clean error: {e}")
|
| 1335 |
+
await asyncio.sleep(4 * 3600)
|
| 1336 |
+
|
| 1337 |
+
@bot.on_message(filters.private & filters.user(ADMIN_IDS) & ~filters.command(["start", "stats", "users", "broadcast", "png", "addvideo", "blur", "clean", "sendto", "clone", "cloneall", "upload", "startshare", "stopshare", "help"]))
|
| 1338 |
+
async def catch_admin_steps(client, message):
|
| 1339 |
+
state = admin_states.get(message.chat.id, {})
|
| 1340 |
+
if state.get("step") == 1:
|
| 1341 |
+
if not message.text: return
|
| 1342 |
+
video_url = message.text.strip()
|
| 1343 |
+
if video_url == "/cancel":
|
| 1344 |
+
admin_states.pop(message.chat.id, None)
|
| 1345 |
+
return await message.reply("❌ Cancelled.")
|
| 1346 |
+
|
| 1347 |
+
try:
|
| 1348 |
+
await db_query(lambda: supabase.table('videos').insert({"video_url": video_url, "thumbnail_url": state["thumbnail_url"], "needed_ref": state["needed_ref"]}).execute())
|
| 1349 |
+
await message.reply("🎉 Video added successfully!")
|
| 1350 |
+
except Exception as e: print(e)
|
| 1351 |
+
finally: admin_states.pop(message.chat.id, None)
|
| 1352 |
+
elif state.get("step") == "broadcast":
|
| 1353 |
+
await process_broadcast(client, message)
|
| 1354 |
+
|
| 1355 |
+
# ==========================================
|
| 1356 |
+
# REDIS QUEUE WATERMARK MULTIPROCESSING
|
| 1357 |
+
# ==========================================
|
| 1358 |
+
REDIS_URL = os.environ.get("REDIS_URL_1")
|
| 1359 |
+
|
| 1360 |
+
processing_owners = set()
|
| 1361 |
+
MAX_CONCURRENT_VIDEOS = 5
|
| 1362 |
+
|
| 1363 |
+
async def process_single_video(task, redis_client):
|
| 1364 |
+
global processing_owners
|
| 1365 |
+
clone_token = task['clone_token']
|
| 1366 |
+
owner_id = task['owner_id']
|
| 1367 |
+
message_id = task['message_id']
|
| 1368 |
+
wm_text = task['watermark_text']
|
| 1369 |
+
task_id = f"{owner_id}_{message_id}"
|
| 1370 |
+
|
| 1371 |
+
raw_video_path = None
|
| 1372 |
+
watermarked_path = None
|
| 1373 |
+
thumb_path = f"thumb_{task_id}.jpg"
|
| 1374 |
+
clone_client = None
|
| 1375 |
+
|
| 1376 |
+
print(f"▶️ [WM] Task Started for User: {owner_id}, Msg: {message_id}")
|
| 1377 |
+
|
| 1378 |
+
try:
|
| 1379 |
+
clone_client = Client(f"clone_wm_{task_id}", bot_token=clone_token, api_id=API_ID, api_hash=API_HASH, in_memory=True)
|
| 1380 |
+
await clone_client.start()
|
| 1381 |
+
print(f"▶️ [WM] Clone Client Started Successfully")
|
| 1382 |
+
|
| 1383 |
+
msg = await clone_client.get_messages(owner_id, message_id)
|
| 1384 |
+
if not msg or not (msg.video or msg.document):
|
| 1385 |
+
raise Exception("Video not found or deleted by user.")
|
| 1386 |
+
|
| 1387 |
+
status_msg = await clone_client.send_message(owner_id, "⏳ <b>ভিডিও প্রসেসিং শুরু হয়েছে...</b>", parse_mode=enums.ParseMode.HTML)
|
| 1388 |
+
|
| 1389 |
+
raw_video_path = await clone_client.download_media(msg)
|
| 1390 |
+
print(f"▶️ [WM] Original Video Downloaded: {raw_video_path}")
|
| 1391 |
+
|
| 1392 |
+
await clone_client.edit_message_text(owner_id, status_msg.id, "⏳ <b>ওয়াটারমার্ক যুক্ত করা হচ্ছে... (High Speed CPU Mode)</b>", parse_mode=enums.ParseMode.HTML)
|
| 1393 |
+
|
| 1394 |
+
watermarked_path = f"wm_{task_id}.mp4"
|
| 1395 |
+
|
| 1396 |
+
cmd = [
|
| 1397 |
+
"ffmpeg", "-y", "-i", raw_video_path,
|
| 1398 |
+
"-vf", f"drawtext=text='{wm_text}':x=W-tw-20:y=H-th-20:fontsize=22:fontcolor=white@0.7:shadowcolor=black@0.8:shadowx=2:shadowy=2:enable='gte(t,5)'",
|
| 1399 |
+
"-c:v", "libx264", "-preset", "ultrafast", "-crf", "26", "-threads", "4",
|
| 1400 |
+
"-c:a", "copy", "-movflags", "+faststart", watermarked_path
|
| 1401 |
+
]
|
| 1402 |
+
|
| 1403 |
+
process = await asyncio.create_subprocess_exec(*cmd, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE)
|
| 1404 |
+
stdout, stderr = await process.communicate()
|
| 1405 |
+
|
| 1406 |
+
if process.returncode != 0 or not os.path.exists(watermarked_path) or os.path.getsize(watermarked_path) == 0:
|
| 1407 |
+
raise Exception("FFmpeg processing failed! The video format might be unsupported.")
|
| 1408 |
+
|
| 1409 |
+
print(f"▶️ [WM] FFmpeg Processing Complete!")
|
| 1410 |
+
|
| 1411 |
+
thumb_cmd = ["ffmpeg", "-y", "-i", watermarked_path, "-vframes", "1", thumb_path]
|
| 1412 |
+
t_proc = await asyncio.create_subprocess_exec(*thumb_cmd, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE)
|
| 1413 |
+
await t_proc.communicate()
|
| 1414 |
+
if not os.path.exists(thumb_path) or os.path.getsize(thumb_path) == 0:
|
| 1415 |
+
thumb_path = None
|
| 1416 |
+
|
| 1417 |
+
await clone_client.edit_message_text(owner_id, status_msg.id, "⏳ <b>ফাইনাল ভিডিও স্টোরেজে আপলোড হচ্ছে...</b>", parse_mode=enums.ParseMode.HTML)
|
| 1418 |
+
|
| 1419 |
+
async def backup_raw_video():
|
| 1420 |
+
try:
|
| 1421 |
+
await bot.send_video(chat_id=STORAGE_CHANNEL_ID, video=raw_video_path, caption=f"Original Backup for Clone Owner {owner_id}")
|
| 1422 |
+
except Exception: pass
|
| 1423 |
+
asyncio.create_task(backup_raw_video())
|
| 1424 |
+
|
| 1425 |
+
wm_sent = await bot.send_video(
|
| 1426 |
+
chat_id=STORAGE_CHANNEL_ID,
|
| 1427 |
+
video=watermarked_path,
|
| 1428 |
+
caption=f"Watermarked Backup for Clone Owner {owner_id}",
|
| 1429 |
+
thumb=thumb_path
|
| 1430 |
+
)
|
| 1431 |
+
|
| 1432 |
+
wm_storage_id = wm_sent.id
|
| 1433 |
+
stream_link = f"{BACKEND_URL}/stream/{wm_storage_id}"
|
| 1434 |
+
download_link = f"{BACKEND_URL}/download/{wm_storage_id}"
|
| 1435 |
+
|
| 1436 |
+
final_caption = (
|
| 1437 |
+
f"✅ <b>Watermark Successfully Added!</b>\n\n"
|
| 1438 |
+
f"🎬 <b>Stream/Watch Online Link:</b>\n<code>{stream_link}</code>\n\n"
|
| 1439 |
+
f"📥 <b>Direct Download Link:</b>\n<code>{download_link}</code>"
|
| 1440 |
+
)
|
| 1441 |
+
|
| 1442 |
+
await clone_client.send_video(
|
| 1443 |
+
chat_id=owner_id,
|
| 1444 |
+
video=watermarked_path,
|
| 1445 |
+
caption=final_caption,
|
| 1446 |
+
parse_mode=enums.ParseMode.HTML,
|
| 1447 |
+
thumb=thumb_path
|
| 1448 |
+
)
|
| 1449 |
+
|
| 1450 |
+
await clone_client.delete_messages(owner_id, status_msg.id)
|
| 1451 |
+
|
| 1452 |
+
except Exception as e:
|
| 1453 |
+
print(f"❌ [WM] Process Error: {e}")
|
| 1454 |
+
try:
|
| 1455 |
+
if clone_client and clone_client.is_connected:
|
| 1456 |
+
await clone_client.send_message(owner_id, f"❌ <b>Error processing video:</b> {e}", parse_mode=enums.ParseMode.HTML)
|
| 1457 |
+
except Exception as ex:
|
| 1458 |
+
print(f"❌ [WM] Failed to send error msg to user: {ex}")
|
| 1459 |
+
|
| 1460 |
+
finally:
|
| 1461 |
+
if raw_video_path and os.path.exists(raw_video_path): os.remove(raw_video_path)
|
| 1462 |
+
if watermarked_path and os.path.exists(watermarked_path): os.remove(watermarked_path)
|
| 1463 |
+
if thumb_path and os.path.exists(thumb_path): os.remove(thumb_path)
|
| 1464 |
+
|
| 1465 |
+
if clone_client and clone_client.is_connected:
|
| 1466 |
+
await clone_client.stop()
|
| 1467 |
+
|
| 1468 |
+
await redis_client.delete(f"wm_processing:{owner_id}")
|
| 1469 |
+
processing_owners.discard(owner_id)
|
| 1470 |
+
print(f"▶️ [WM] Task Cleaned Up and Client Stopped")
|
| 1471 |
+
|
| 1472 |
+
async def watermark_processor_loop():
|
| 1473 |
+
global processing_owners
|
| 1474 |
+
|
| 1475 |
+
if not REDIS_URL:
|
| 1476 |
+
print("⚠️ REDIS_URL missing in Hugging Space! Watermark feature disabled.")
|
| 1477 |
+
return
|
| 1478 |
+
|
| 1479 |
+
try:
|
| 1480 |
+
redis_client = redis.Redis.from_url(REDIS_URL, decode_responses=True)
|
| 1481 |
+
await redis_client.ping()
|
| 1482 |
+
print("💧 Super-Fast Redis Watermark Processor Started!")
|
| 1483 |
+
except Exception as e:
|
| 1484 |
+
print(f"❌ Redis Connection Failed in Hugging Face: {e}")
|
| 1485 |
+
return
|
| 1486 |
+
|
| 1487 |
+
try:
|
| 1488 |
+
async for key in redis_client.scan_iter("wm_processing:*"):
|
| 1489 |
+
await redis_client.delete(key)
|
| 1490 |
+
except: pass
|
| 1491 |
+
|
| 1492 |
+
while True:
|
| 1493 |
+
try:
|
| 1494 |
+
if len(processing_owners) >= MAX_CONCURRENT_VIDEOS:
|
| 1495 |
+
await asyncio.sleep(2)
|
| 1496 |
+
continue
|
| 1497 |
+
|
| 1498 |
+
result = await redis_client.rpop("watermark_task_queue")
|
| 1499 |
+
|
| 1500 |
+
if result:
|
| 1501 |
+
print(f"📥 [WM] Received New Video Task from Queue!")
|
| 1502 |
+
task = json.loads(result)
|
| 1503 |
+
owner_id = task['owner_id']
|
| 1504 |
+
|
| 1505 |
+
processing_owners.add(owner_id)
|
| 1506 |
+
asyncio.create_task(process_single_video(task, redis_client))
|
| 1507 |
+
else:
|
| 1508 |
+
await asyncio.sleep(2)
|
| 1509 |
+
|
| 1510 |
+
except Exception as e:
|
| 1511 |
+
print(f"❌ [WM] Loop Error: {e}")
|
| 1512 |
+
await asyncio.sleep(2)
|
| 1513 |
+
|
| 1514 |
+
def run_flask(): app.run(host="0.0.0.0", port=int(os.environ.get("PORT", 7860)), threaded=True)
|
| 1515 |
+
|
| 1516 |
+
async def main():
|
| 1517 |
+
try:
|
| 1518 |
+
await bot.start()
|
| 1519 |
+
print("🤖 Pyrogram Bot & Real Session API is running!")
|
| 1520 |
+
asyncio.create_task(auto_clean_channel_loop())
|
| 1521 |
+
asyncio.create_task(watermark_processor_loop())
|
| 1522 |
+
await idle()
|
| 1523 |
except Exception as e:
|
| 1524 |
+
print(f"❌ Failed to start Bot: {e}")
|
| 1525 |
+
while True: await asyncio.sleep(3600)
|
| 1526 |
finally:
|
| 1527 |
+
try: await bot.stop()
|
| 1528 |
+
except: pass
|
|
|
|
| 1529 |
|
| 1530 |
if __name__ == "__main__":
|
| 1531 |
+
threading.Thread(target=run_flask, daemon=True).start()
|
| 1532 |
+
main_loop.run_until_complete(main())
|