Compare commits

...
10 Commits
Author SHA1 Message Date
arsam a1b7ec98de Import documentation: v3/Tele2Rub/README.md
Import the existing local project snapshot. This commit organizes existing files; it does not represent newly developed functionality.
2026-09-18 23:14:05 +03:30
arsam fc53d6f073 Import documentation: v3/Tele2Rub/GEMINI.md
Import the existing local project snapshot. This commit organizes existing files; it does not represent newly developed functionality.
2026-09-18 23:14:03 +03:30
arsam fc16c47343 Import documentation: v3/Tele2Rub/DUPLICATE.md
Import the existing local project snapshot. This commit organizes existing files; it does not represent newly developed functionality.
2026-09-18 23:14:02 +03:30
arsam 71eb469154 Import documentation: v3/Tele2Rub/AGENTS.md
Import the existing local project snapshot. This commit organizes existing files; it does not represent newly developed functionality.
2026-09-18 23:14:00 +03:30
arsam 25fe871305 Import documentation: v2/Tele2Rub/README.md
Import the existing local project snapshot. This commit organizes existing files; it does not represent newly developed functionality.
2026-09-18 23:13:58 +03:30
arsam ffb3abd9f1 Import documentation: README.md
Import the existing local project snapshot. This commit organizes existing files; it does not represent newly developed functionality.
2026-09-18 23:13:56 +03:30
arsam 172e5255ac Import source: v3/Tele2Rub/telebot
Import the existing local project snapshot. This commit organizes existing files; it does not represent newly developed functionality.
2026-09-18 23:13:55 +03:30
arsam b7b92d6cd2 Import source: v3/Tele2Rub/rub
Import the existing local project snapshot. This commit organizes existing files; it does not represent newly developed functionality.
2026-09-18 23:13:53 +03:30
arsam 025fae4433 Import source: v3/Tele2Rub/main
Import the existing local project snapshot. This commit organizes existing files; it does not represent newly developed functionality.
2026-09-18 23:13:51 +03:30
arsam 6a78f43fc9 Import source: v3/Tele2Rub/.env.example
Import the existing local project snapshot. This commit organizes existing files; it does not represent newly developed functionality.
2026-09-18 23:13:50 +03:30
10 changed files with 5094 additions and 0 deletions
+207
View File
@@ -0,0 +1,207 @@
# 🚀 Tele2Rub
انتقال خودکار فایل از تلگرام به روبیکا — سریع، ساده، بدون هزینه و دردسر
**پشتیبانی از ارسال فایل تا ۲ گیگابایت**
---
## 🧠 معرفی
**Tele2Rub**
یک ابزار سبک و کاربردی است که فایل‌ها را از بات تلگرام دریافت کرده و به صورت خودکار به **سیو مسیج (Saved Messages)** روبیکا ارسال می‌کند.
کل فرایند به صورت **صف (Queue)** انجام می‌شود تا از بروز خطا و تداخل جلوگیری شود.
---
## ⚙️ نحوه کار
```text
ارسال به روبیکا → صف پردازش → دانلود فایل → ربات تلگرام
```
* دریافت فایل از تلگرام
* ذخیره موقت در سرور
* ثبت در صف
* ارسال خودکار توسط **Worker**
---
## ✨ قابلیت‌ها
* 📥 دریافت انواع فایل از تلگرام
* 🔗 پشتیبانی دانلود از لینک مستقیم
* 📤 ارسال خودکار به روبیکا
* 🧾 ارسال همه فایل‌ها به صورت Document
* 📊 نمایش وضعیت دانلود و آپلود
* ❌ امکان حذف یک فایل از صف با `/del`
* 🧨 امکان پاکسازی کل صف با `/delall`
* 🛡 حالت Safe Mode برای ارسال فایل ها به صورت ZIP رمز دار
* ⚡سیستم صف برای جلوگیری از کرش
* 🔄 اجرای جداگانه پردازش برای پایداری بیشتر
* ⏱ محدودیت زمان آپلود برای جلوگیری از گیر کردن پردازش
(این مقدار با توجه به سرعت سرور، حجم فایل‌ها و صبر و حوصله خودتون قابل تنظیمه.)
(به‌صورت پیش‌فرض روی **۱۰ دقیقه** تنظیم شده.)
---
## 🛠 نصب سریع
ابتدا پروژه را دریافت کنید:
```bash
git clone https://github.com/caffeinexz/Tele2Rub.git
cd Tele2Rub
```
نصب وابستگی‌ها:
```bash
pip install -r requirements.txt
```
اجرای پروژه:
```bash
python3 main.py
```
---
## 🖥 نصب روی سرور
### 1. نصب پیش‌نیازها
```bash
sudo apt update
sudo apt install python3 python3-venv python3-pip git -y
```
---
### 2. دریافت پروژه
```bash
git clone https://github.com/caffeinexz/Tele2Rub.git
cd Tele2Rub
```
---
### 3. ساخت محیط مجازی
```bash
python3 -m venv venv
```
---
### 4. فعال‌سازی محیط مجازی
```bash
source venv/bin/activate
```
---
### 5. نصب وابستگی‌ها
```bash
pip install --upgrade pip
pip install -r requirements.txt
```
---
### 6. ساخت فایل تنظیمات
```bash
nano .env
```
و مقادیر زیر را وارد کنید:
```env
API_ID=عدد_API
API_HASH=کد_API
BOT_TOKEN=توکن_ربات
RUBIKA_SESSION=rubsession
```
---
### 7. اجرای دائمی (Screen)
```bash
screen -S tele2rub
source venv/bin/activate
python main.py
```
---
## ⚙️ تنظیمات
یک فایل `.env` در **ریشه پروژه** بسازید:
```env
API_ID=عدد_API
API_HASH=کد_API
BOT_TOKEN=توکن_ربات
RUBIKA_SESSION=rubsession
```
یا از فایل نمونه استفاده کنید:
```bash
cp .env.example .env
```
## 📌 دریافت API_ID و API_HASH از تلگرام
برای استفاده از پروژه، ابتدا باید API تلگرام دریافت کنید:
1. وارد سایت زیر شوید:
👉 https://my.telegram.org
2. با شماره تلگرام خود وارد شوید
3. روی **API development tools** کلیک کنید
4. فرم را به شکل زیر پر کنید:
```text
App title: tele2rub
Short name: t2r
```
5. پس از ثبت، مقادیر زیر به شما داده می‌شود:
* **API_ID**
* **API_HASH**
این مقادیر را در فایل `.env` قرار دهید.
درصورت مشکل در دریافت API ID و API HASH مقادیر در کانال تلگرام قرار گرفته
لینک : https://t.me/caffeinexz/3
---
## 🔐 اجرای اولیه
در اولین اجرا:
* شماره روبیکا را وارد کنید
* کد تایید را وارد کنید
* فایل سشن ذخیره می‌شود و در دفعات بعد نیاز نیست
---
## 📥 نحوه استفاده
1. وارد **بات تلگرام** شوید
2. فایل ارسال کنید
3. فایل به صورت خودکار در **Saved Messages روبیکا** ارسال می‌شود
---
+207
View File
@@ -0,0 +1,207 @@
# 🚀 Tele2Rub
انتقال خودکار فایل از تلگرام به روبیکا — سریع، ساده، بدون هزینه و دردسر
**پشتیبانی از ارسال فایل تا ۲ گیگابایت**
---
## 🧠 معرفی
**Tele2Rub**
یک ابزار سبک و کاربردی است که فایل‌ها را از بات تلگرام دریافت کرده و به صورت خودکار به **سیو مسیج (Saved Messages)** روبیکا ارسال می‌کند.
کل فرایند به صورت **صف (Queue)** انجام می‌شود تا از بروز خطا و تداخل جلوگیری شود.
---
## ⚙️ نحوه کار
```text
ارسال به روبیکا → صف پردازش → دانلود فایل → ربات تلگرام
```
* دریافت فایل از تلگرام
* ذخیره موقت در سرور
* ثبت در صف
* ارسال خودکار توسط **Worker**
---
## ✨ قابلیت‌ها
* 📥 دریافت انواع فایل از تلگرام
* 🔗 پشتیبانی دانلود از لینک مستقیم
* 📤 ارسال خودکار به روبیکا
* 🧾 ارسال همه فایل‌ها به صورت Document
* 📊 نمایش وضعیت دانلود و آپلود
* ❌ امکان حذف یک فایل از صف با `/del`
* 🧨 امکان پاکسازی کل صف با `/delall`
* 🛡 حالت Safe Mode برای ارسال فایل ها به صورت ZIP رمز دار
* ⚡سیستم صف برای جلوگیری از کرش
* 🔄 اجرای جداگانه پردازش برای پایداری بیشتر
* ⏱ محدودیت زمان آپلود برای جلوگیری از گیر کردن پردازش
(این مقدار با توجه به سرعت سرور، حجم فایل‌ها و صبر و حوصله خودتون قابل تنظیمه.)
(به‌صورت پیش‌فرض روی **۱۰ دقیقه** تنظیم شده.)
---
## 🛠 نصب سریع
ابتدا پروژه را دریافت کنید:
```bash
git clone https://github.com/caffeinexz/Tele2Rub.git
cd Tele2Rub
```
نصب وابستگی‌ها:
```bash
pip install -r requirements.txt
```
اجرای پروژه:
```bash
python3 main.py
```
---
## 🖥 نصب روی سرور
### 1. نصب پیش‌نیازها
```bash
sudo apt update
sudo apt install python3 python3-venv python3-pip git -y
```
---
### 2. دریافت پروژه
```bash
git clone https://github.com/caffeinexz/Tele2Rub.git
cd Tele2Rub
```
---
### 3. ساخت محیط مجازی
```bash
python3 -m venv venv
```
---
### 4. فعال‌سازی محیط مجازی
```bash
source venv/bin/activate
```
---
### 5. نصب وابستگی‌ها
```bash
pip install --upgrade pip
pip install -r requirements.txt
```
---
### 6. ساخت فایل تنظیمات
```bash
nano .env
```
و مقادیر زیر را وارد کنید:
```env
API_ID=عدد_API
API_HASH=کد_API
BOT_TOKEN=توکن_ربات
RUBIKA_SESSION=rubsession
```
---
### 7. اجرای دائمی (Screen)
```bash
screen -S tele2rub
source venv/bin/activate
python main.py
```
---
## ⚙️ تنظیمات
یک فایل `.env` در **ریشه پروژه** بسازید:
```env
API_ID=عدد_API
API_HASH=کد_API
BOT_TOKEN=توکن_ربات
RUBIKA_SESSION=rubsession
```
یا از فایل نمونه استفاده کنید:
```bash
cp .env.example .env
```
## 📌 دریافت API_ID و API_HASH از تلگرام
برای استفاده از پروژه، ابتدا باید API تلگرام دریافت کنید:
1. وارد سایت زیر شوید:
👉 https://my.telegram.org
2. با شماره تلگرام خود وارد شوید
3. روی **API development tools** کلیک کنید
4. فرم را به شکل زیر پر کنید:
```text
App title: tele2rub
Short name: t2r
```
5. پس از ثبت، مقادیر زیر به شما داده می‌شود:
* **API_ID**
* **API_HASH**
این مقادیر را در فایل `.env` قرار دهید.
درصورت مشکل در دریافت API ID و API HASH مقادیر در کانال تلگرام قرار گرفته
لینک : https://t.me/caffeinexz/3
---
## 🔐 اجرای اولیه
در اولین اجرا:
* شماره روبیکا را وارد کنید
* کد تایید را وارد کنید
* فایل سشن ذخیره می‌شود و در دفعات بعد نیاز نیست
---
## 📥 نحوه استفاده
1. وارد **بات تلگرام** شوید
2. فایل ارسال کنید
3. فایل به صورت خودکار در **Saved Messages روبیکا** ارسال می‌شود
---
+9
View File
@@ -0,0 +1,9 @@
API_ID=api_id
API_HASH=api_hash
BOT_TOKEN=bot_token
RUBIKA_SESSION=rubsession
# Optional Rubika upload tuning for large files
RUBIKA_REQUEST_TIMEOUT=180
RUBIKA_UPLOAD_RETRIES=8
RUBIKA_CHUNK_MB=1
+63
View File
@@ -0,0 +1,63 @@
# Repository Guidelines
## Project Structure & Module Organization
This repository is a small Python service that bridges Telegram bot uploads to Rubika Saved Messages. Core code lives at the repository root:
- `main.py` starts both worker processes.
- `telebot.py` handles Telegram bot input, downloads, queue updates, and user commands.
- `rub.py` consumes queued tasks and uploads files to Rubika.
- `requirements.txt` lists runtime dependencies.
- `.env.example` documents required configuration keys.
Runtime state is written to `downloads/`, `queue/`, `*.session`, `*.session-journal`, `rubsession.rp`, and `out.log`. Treat these as local artifacts; do not commit real sessions, downloaded files, logs, or secrets.
## Build, Test, and Development Commands
Create and activate a virtual environment:
```bash
python3 -m venv venv
source venv/bin/activate
```
Install dependencies:
```bash
pip install -r requirements.txt
```
Create local configuration:
```bash
cp .env.example .env
```
Run both Telegram and Rubika processes:
```bash
python3 main.py
```
Run individual components while debugging:
```bash
python3 telebot.py
python3 rub.py
```
## Coding Style & Naming Conventions
Use Python 3.10+ style. Follow the existing module pattern: constants in `UPPER_SNAKE_CASE`, functions and variables in `snake_case`, and classes in `PascalCase`. Keep paths based on `Path(__file__).resolve().parent` so scripts work from any current directory. Prefer JSONL queue records and UTF-8 file I/O, matching the current `queue/*.jsonl` format. Keep user-facing Persian status text readable and avoid changing message wording unless behavior changes require it.
## Testing Guidelines
There is currently no formal test suite. Before submitting changes, run the affected entry point locally with a disposable `.env` and verify the relevant queue flow. For queue logic, test with small files first, then direct URLs or larger files if upload behavior changed. If adding tests, place them under `tests/`, name files `test_*.py`, and prefer `pytest` for isolated helpers such as `safe_filename`, queue parsing, and retry decisions.
## Commit & Pull Request Guidelines
Recent history uses short imperative messages such as `Update README.md` and `fix disconnect session client when uploading`. Keep commits concise and focused, for example `fix empty caption handling` or `update queue cleanup`. Pull requests should include a short summary, manual test steps, affected commands, and any configuration or session implications. Include screenshots only when Telegram or Rubika message output changes.
## Security & Configuration Tips
Never commit `.env`, session files, downloaded media, or queue state containing user data. Required environment keys are `API_ID`, `API_HASH`, `BOT_TOKEN`, and `RUBIKA_SESSION`. Use `.env.example` for placeholders only.
File diff suppressed because it is too large Load Diff
+48
View File
@@ -0,0 +1,48 @@
# Tele2Rub
## Project Overview
**Tele2Rub** is a Python-based utility bot that automatically forwards files and direct download URLs from Telegram to a Rubika account's Saved Messages. It is designed with a lightweight queue system to prevent crashes and handle large files (up to 2GB) efficiently. The project is an optimized version of the original `caffeinexz/Tele2Rub`.
### Architecture & Components
- **`telebot.py`**: A Telegram bot built with `pyrogram`. It listens for incoming files or direct download links from the user, downloads the media locally to a `downloads/` folder, and registers the upload task into a local queue.
- **`rub.py`**: A worker script built with `rubpy`. It constantly polls the task queue, optionally processes the file (e.g., zipping it with a password if "Safe Mode" is enabled via `pyzipper`), and uploads the file to Rubika's Saved Messages.
- **`main.py`**: The main entry point that spawns and manages both the Telegram bot (`telebot.py`) and the Rubika worker (`rub.py`) as concurrent subprocesses.
- **Queue System**: Uses JSON Lines (`.jsonl`) files stored in the `queue/` directory (`tasks.jsonl`, `status.jsonl`, `cancelled.jsonl`, etc.) to track pending, processing, and failed uploads.
## Building and Running
### Prerequisites
- Python 3.10+
- Telegram API ID and API Hash (from my.telegram.org)
- Telegram Bot Token (from BotFather)
- Rubika Session
### Setup Instructions
1. **Clone & Virtual Environment**:
```bash
python3 -m venv venv
source venv/bin/activate
```
2. **Install Dependencies**:
```bash
pip install -r requirements.txt
```
3. **Configuration**:
Create a `.env` file in the project root with the following variables:
```env
API_ID=your_api_id
API_HASH=your_api_hash
BOT_TOKEN=your_bot_token
RUBIKA_SESSION=rubsession
```
4. **Run the Project**:
```bash
python main.py
```
*(On the first run, the Rubika worker will prompt for your phone number and SMS code to generate the session file).*
## Development Conventions
- **Queue Files**: State is managed via text files in the `queue/` directory. Be careful with concurrent reads/writes; the system primarily relies on simple file appends and whole-file rewrites.
- **Safe Mode**: Users can toggle a safe mode that encrypts files into a password-protected `.zip` before uploading to Rubika, handled using `pyzipper`.
- **Error Handling**: The Rubika upload logic contains transient error handling (e.g., for 502/503 errors) and retries up to 3 times with exponential backoff.
- **Cleanups**: Downloaded and compressed files are cleaned up asynchronously upon successful upload or fatal error.
+215
View File
@@ -0,0 +1,215 @@
# 🚀 Tele2Rub
انتقال خودکار فایل از تلگرام به روبیکا — سریع، ساده، بدون هزینه و دردسر
**پشتیبانی از ارسال فایل تا ۲ گیگابایت**
*** این یک نسخه بهینه شده از ریپازیتوری caffeinexz/Tele2Rub میباشد ***
---
## 🧠 معرفی
**Tele2Rub**
یک ابزار سبک و کاربردی است که فایل‌ها را از بات تلگرام دریافت کرده و به صورت خودکار به **سیو مسیج (Saved Messages)** روبیکا ارسال می‌کند.
کل فرایند به صورت **صف (Queue)** انجام می‌شود تا از بروز خطا و تداخل جلوگیری شود.
---
## ⚙️ نحوه کار
```text
ارسال به روبیکا → صف پردازش → دانلود فایل → ربات تلگرام
```
* دریافت فایل از تلگرام
* ذخیره موقت در سرور
* ثبت در صف
* ارسال خودکار توسط **Worker**
---
## 🔔🛠️ آپدیت
* حل باگ Caption:
- ` رفع خطای ERROR_GENERIC یا INVALID_INPUT که به دلیل ارسال کپشن‌های خالی به API روبیکا رخ می‌داد.`
---
## ✨ قابلیت‌ها
* 📥 دریافت انواع فایل از تلگرام
* 🔗 پشتیبانی دانلود از لینک مستقیم
* 📤 ارسال خودکار به روبیکا
* 🧾 ارسال همه فایل‌ها به صورت Document
* 📊 نمایش وضعیت دانلود و آپلود
* ❌ امکان حذف یک فایل از صف با `/del`
* 🧨 امکان پاکسازی کل صف با `/delall`
* 🛡 حالت Safe Mode برای ارسال فایل ها به صورت ZIP رمز دار
* ⚡سیستم صف برای جلوگیری از کرش
* 🔄 اجرای جداگانه پردازش برای پایداری بیشتر
* ⏱ محدودیت زمان آپلود برای جلوگیری از گیر کردن پردازش
(این مقدار با توجه به سرعت سرور، حجم فایل‌ها و صبر و حوصله خودتون قابل تنظیمه.)
(به‌صورت پیش‌فرض روی **۱۰ دقیقه** تنظیم شده.)
---
## 🛠 نصب سریع
ابتدا پروژه را دریافت کنید:
```bash
git clone https://github.com/DayiGorbay/Tele2Rub.git
cd Tele2Rub
```
نصب وابستگی‌ها:
```bash
pip install -r requirements.txt
```
اجرای پروژه:
```bash
python3 main.py
```
---
## 🖥 نصب روی سرور
### 1. نصب پیش‌نیازها
```bash
sudo apt update
sudo apt install python3 python3-venv python3-pip git -y
```
---
### 2. دریافت پروژه
```bash
git clone https://github.com/DayiGorbay/Tele2Rub.git
cd Tele2Rub
```
---
### 3. ساخت محیط مجازی
```bash
python3 -m venv venv
```
---
### 4. فعال‌سازی محیط مجازی
```bash
source venv/bin/activate
```
---
### 5. نصب وابستگی‌ها
```bash
pip install --upgrade pip
pip install -r requirements.txt
```
---
### 6. ساخت فایل تنظیمات
```bash
nano .env
```
و مقادیر زیر را وارد کنید:
```env
API_ID=عدد_API
API_HASH=کد_API
BOT_TOKEN=توکن_ربات
RUBIKA_SESSION=rubsession
```
---
### 7. اجرای دائمی (Screen)
```bash
screen -S tele2rub
source venv/bin/activate
python main.py
```
---
## ⚙️ تنظیمات
یک فایل `.env` در **ریشه پروژه** بسازید:
```env
API_ID=عدد_API
API_HASH=کد_API
BOT_TOKEN=توکن_ربات
RUBIKA_SESSION=rubsession
```
یا از فایل نمونه استفاده کنید:
```bash
cp .env.example .env
```
## 📌 دریافت API_ID و API_HASH از تلگرام
برای استفاده از پروژه، ابتدا باید API تلگرام دریافت کنید:
1. وارد سایت زیر شوید:
👉 https://my.telegram.org
2. با شماره تلگرام خود وارد شوید
3. روی **API development tools** کلیک کنید
4. فرم را به شکل زیر پر کنید:
```text
App title: tele2rub
Short name: t2r
```
5. پس از ثبت، مقادیر زیر به شما داده می‌شود:
* **API_ID**
* **API_HASH**
این مقادیر را در فایل `.env` قرار دهید.
درصورت مشکل در دریافت API ID و API HASH مقادیر در کانال تلگرام قرار گرفته
لینک : https://t.me/caffeinexz/3
---
## 🔐 اجرای اولیه
در اولین اجرا:
* شماره روبیکا را وارد کنید
* کد تایید را وارد کنید
* فایل سشن ذخیره می‌شود و در دفعات بعد نیاز نیست
---
## 📥 نحوه استفاده
1. وارد **بات تلگرام** شوید
2. فایل ارسال کنید
3. فایل به صورت خودکار در **Saved Messages روبیکا** ارسال می‌شود
---
+26
View File
@@ -0,0 +1,26 @@
import subprocess
import sys
from pathlib import Path
BASE_DIR = Path(__file__).resolve().parent
telegram_file = BASE_DIR / "telebot.py"
rubika_file = BASE_DIR / "rub.py"
telegram_proc = None
rubika_proc = None
try:
rubika_proc = subprocess.Popen([sys.executable, str(rubika_file)])
telegram_proc = subprocess.Popen([sys.executable, str(telegram_file)])
rubika_proc.wait()
telegram_proc.wait()
except KeyboardInterrupt:
pass
finally:
for proc in [telegram_proc, rubika_proc]:
if proc and proc.poll() is None:
proc.terminate()
+467
View File
@@ -0,0 +1,467 @@
import os
import re
import json
import time
from pathlib import Path
from typing import Optional
from dotenv import load_dotenv
from rubpy import Client as RubikaClient
import requests
import pyzipper
from urllib.parse import urlparse
load_dotenv()
SESSION = os.getenv("RUBIKA_SESSION", "rubika_session").strip()
RUBIKA_REQUEST_TIMEOUT = int(os.getenv("RUBIKA_REQUEST_TIMEOUT", "180"))
RUBIKA_UPLOAD_RETRIES = int(os.getenv("RUBIKA_UPLOAD_RETRIES", "8"))
RUBIKA_CHUNK_SIZE = int(float(os.getenv("RUBIKA_CHUNK_MB", "1")) * 1024 * 1024)
BASE_DIR = Path(__file__).resolve().parent
DOWNLOAD_DIR = BASE_DIR / "downloads"
QUEUE_DIR = BASE_DIR / "queue"
QUEUE_FILE = QUEUE_DIR / "tasks.jsonl"
PROCESSING_FILE = QUEUE_DIR / "processing.json"
FAILED_FILE = QUEUE_DIR / "failed.jsonl"
STATUS_FILE = QUEUE_DIR / "status.jsonl"
URL_DIR = DOWNLOAD_DIR / "url"
CANCEL_FILE = QUEUE_DIR / "cancelled.jsonl"
MAX_RETRIES = 3
TARGET = "me"
DOWNLOAD_DIR.mkdir(parents=True, exist_ok=True)
QUEUE_DIR.mkdir(parents=True, exist_ok=True)
URL_DIR.mkdir(parents=True, exist_ok=True)
def safe_filename(name: Optional[str]) -> str:
name = (name or "file").strip()
name = re.sub(r'[<>:"/\\|?*\x00-\x1F]', "_", name)
name = name.rstrip(". ")
return name[:200] or "file"
def pretty_size(size) -> str:
size = float(size or 0)
units = ["B", "KB", "MB", "GB"]
index = 0
while size >= 1024 and index < len(units) - 1:
size /= 1024
index += 1
return f"{size:.2f} {units[index]}"
def eta_text(seconds) -> str:
if not seconds or seconds <= 0:
return "نامشخص"
seconds = int(seconds)
h = seconds // 3600
m = (seconds % 3600) // 60
s = seconds % 60
if h:
return f"{h}h {m}m"
if m:
return f"{m}m {s}s"
return f"{s}s"
def push_status(task: dict, text: str, status: str = "working", percent: float | None = None):
payload = {
"chat_id": task.get("chat_id"),
"message_id": task.get("status_message_id"),
"status": status,
"text": text,
"percent": percent,
"time": time.time(),
}
with open(STATUS_FILE, "a", encoding="utf-8") as file:
file.write(json.dumps(payload, ensure_ascii=False) + "\n")
def is_cancelled(task: dict) -> bool:
job_id = str(task.get("job_id", ""))
if not job_id or not CANCEL_FILE.exists():
return False
with open(CANCEL_FILE, "r", encoding="utf-8") as file:
for line in file:
if not line.strip():
continue
item = json.loads(line)
if str(item.get("job_id")) == job_id:
return True
return False
def unique_path(path: Path) -> Path:
if not path.exists():
return path
stem = path.stem
suffix = path.suffix
index = 1
while True:
candidate = path.with_name(f"{stem}_{index}{suffix}")
if not candidate.exists():
return candidate
index += 1
def has_session(session_name: str) -> bool:
candidates = [
Path(session_name),
Path(f"{session_name}.session"),
Path(f"{session_name}.sqlite"),
]
return any(path.exists() for path in candidates)
def ensure_session():
if has_session(SESSION):
return
client = RubikaClient(name=SESSION)
try:
client.start()
print("Login successful.")
finally:
try:
client.disconnect()
except Exception:
pass
def build_upload_text(file_name: str, total: int, current: int, started_at: float) -> tuple[str, float]:
now = time.time()
percent = current * 100 / total if total else 0
elapsed = max(now - started_at, 1)
speed = current / elapsed
eta = (total - current) / speed if total and speed else None
text = (
f"🔼 در حال آپلود در روبیکا...\n\n"
f"فایل: `{file_name}`\n"
f"آپلود شده: `{pretty_size(current)}`"
)
if total:
text += f" از `{pretty_size(total)}`"
text += (
f"\nسرعت: `{pretty_size(speed)}/s`\n"
f"زمان باقی‌مانده: `{eta_text(eta)}`"
)
return text, percent
def send_document_streaming(file_path: str, caption: str | None = None, task: dict | None = None):
path = Path(file_path)
client = RubikaClient(
name=SESSION,
timeout=RUBIKA_REQUEST_TIMEOUT,
max_retries=RUBIKA_UPLOAD_RETRIES,
)
progress_state = {"last_update": 0}
started_at = time.time()
def upload_progress(total: int, current: int):
if not task:
return
now = time.time()
if now - progress_state["last_update"] < 5 and current < total:
return
progress_state["last_update"] = now
text, percent = build_upload_text(path.name, total, current, started_at)
push_status(task, text, "uploading", percent)
try:
client.start()
uploaded = client.upload(
str(path),
chunk=RUBIKA_CHUNK_SIZE,
callback=upload_progress,
backoff=2.0,
)
if task and is_cancelled(task):
raise RuntimeError("ارسال لغو شد.")
uploaded["type"] = "File"
uploaded["time"] = 1
uploaded["width"] = 200
uploaded["height"] = 200
uploaded["music_performer"] = ""
return client.send_message(
object_guid=TARGET,
text=caption,
file_inline=uploaded,
thumb=False,
)
finally:
try:
client.disconnect()
except Exception:
pass
def send_with_retry(file_path: str, caption: str = "", task: dict | None = None):
last_error = None
file_size = Path(file_path).stat().st_size
for attempt in range(1, MAX_RETRIES + 1):
if task and is_cancelled(task):
raise RuntimeError("ارسال لغو شد.")
try:
if task:
push_status(
task,
f"🔼 در حال آپلود در روبیکا...\n\n"
f"حجم: `{pretty_size(file_size)}`\n"
f"🔴 تلاش {attempt} از {MAX_RETRIES}\n\n"
f"برای لغو ارسال:\n"
f"`/del {task.get('job_id')}`",
"uploading"
)
return send_document_streaming(file_path, caption, task)
except Exception as e:
last_error = e
error_text = str(e).lower()
transient = any(
key in error_text
for key in [
"502", "503", "bad gateway", "timeout",
"cannot connect", "connection reset",
"temporarily unavailable",
"error uploading chunk",
"unexpected mimetype",
"server disconnected",
"try again",
]
)
if transient and attempt < MAX_RETRIES:
if task and is_cancelled(task):
raise RuntimeError("ارسال لغو شد.")
if task:
push_status(
task,
f"ارتباط با روبیکا ناپایدار بود...\n"
f"دوباره تلاش می‌کنم ({attempt + 1} از {MAX_RETRIES})",
"uploading"
)
time.sleep(min(30, 5 * attempt))
continue
break
raise last_error if last_error else RuntimeError("Upload failed.")
def download_url(task: dict) -> Path:
url = task.get("url", "").strip()
if not url:
raise RuntimeError("URL خالیه")
push_status(task, "در حال دانلود ...", "downloading", 0)
try:
resp = requests.get(url, stream=True, timeout=(10, 60), allow_redirects=True)
resp.raise_for_status()
except requests.exceptions.Timeout:
raise RuntimeError("لینک جواب نداد")
except requests.exceptions.ConnectionError:
raise RuntimeError("مشکل شبکه")
except requests.exceptions.HTTPError as e:
code = e.response.status_code if e.response else "نامشخص"
raise RuntimeError(f"دانلود انجام نشد. کد خطا: {code}")
cd = resp.headers.get("content-disposition", "")
match = re.findall(r'filename="(.+?)"', cd)
name = match[0] if match else Path(urlparse(url).path).name
name = safe_filename(name or f"file_{int(time.time())}")
if "." not in name:
name += ".bin"
target = unique_path(URL_DIR / name)
total = int(resp.headers.get("content-length") or 0)
downloaded, last_update, started = 0, 0, time.time()
with open(target, "wb") as f:
for chunk in resp.iter_content(1024 * 1024):
if not chunk:
continue
f.write(chunk)
downloaded += len(chunk)
now = time.time()
if now - last_update < 3 and downloaded < total:
continue
last_update = now
speed = downloaded / max(now - started, 1)
eta = (total - downloaded) / speed if total and speed else None
percent = downloaded * 100 / total if total else None
text = f"داره دانلود میکنه...\n\n{pretty_size(downloaded)}"
if total:
text += f" از {pretty_size(total)}"
text += f"\nسرعت: {pretty_size(speed)}/s"
if eta:
text += f"\nمونده: {eta_text(eta)}"
push_status(task, text, "downloading", percent)
if not target.exists() or target.stat().st_size == 0:
raise RuntimeError("فایل دانلود نشد")
task["file_name"] = target.name
task["file_size"] = target.stat().st_size
return target
def make_zip_with_password(file_path: Path, password: str) -> Path:
zip_path = unique_path(file_path.with_suffix(file_path.suffix + ".zip"))
with pyzipper.AESZipFile(
zip_path,
"w",
compression=pyzipper.ZIP_STORED,
encryption=pyzipper.WZ_AES,
) as zip_file:
zip_file.setpassword(password.encode("utf-8"))
zip_file.write(file_path, arcname=file_path.name)
return zip_path
def pop_first_task():
if not QUEUE_FILE.exists():
return None
with open(QUEUE_FILE, "r", encoding="utf-8") as file:
lines = [line for line in file if line.strip()]
if not lines:
return None
first_line = lines[0]
remaining = lines[1:]
with open(QUEUE_FILE, "w", encoding="utf-8") as file:
file.writelines(remaining)
return json.loads(first_line)
def save_processing(task: dict) -> None:
with open(PROCESSING_FILE, "w", encoding="utf-8") as file:
json.dump(task, file, ensure_ascii=False, indent=2)
def clear_processing() -> None:
if PROCESSING_FILE.exists():
PROCESSING_FILE.unlink()
def append_failed(task: dict, error: str) -> None:
payload = {"task": task, "error": error}
with open(FAILED_FILE, "a", encoding="utf-8") as file:
file.write(json.dumps(payload, ensure_ascii=False) + "\n")
def process_task(task: dict):
task_type = task.get("type")
caption = (task.get("caption") or "").strip()[:512]
if not caption:
caption = None
safe_mode = task.get("safe_mode", False)
zip_password = task.get("zip_password", "")
local_path: Path | None = None
if task_type == "local_file":
local_path = Path(task.get("path", ""))
if not local_path.exists():
raise RuntimeError("Local file not found.")
elif task_type == "direct_url":
local_path = download_url(task)
else:
raise RuntimeError("Unknown task type.")
if safe_mode and zip_password:
push_status(task, "در حال تبدیل به فایل zip ...", "processing")
try:
zipped = make_zip_with_password(local_path, zip_password)
finally:
try:
if local_path.exists():
local_path.unlink()
except Exception:
pass
send_path = zipped
else:
send_path = local_path
try:
if is_cancelled(task):
raise RuntimeError("ارسال لغو شد.")
send_with_retry(str(send_path), caption, task)
push_status(
task,
"فایل با موفقیت در روبیکا آپلود شد.",
"done"
)
finally:
try:
if send_path and send_path.exists():
send_path.unlink()
except Exception:
pass
def worker_loop():
ensure_session()
print("Rubika worker started.")
while True:
task = pop_first_task()
if not task:
time.sleep(0.2)
continue
save_processing(task)
try:
process_task(task)
except Exception as e:
append_failed(task, str(e))
push_status(task, f"خطا: {str(e)}", "failed")
finally:
clear_processing()
if __name__ == "__main__":
worker_loop()
+613
View File
@@ -0,0 +1,613 @@
import os
import re
import json
from pathlib import Path
from typing import Optional
from dotenv import load_dotenv
from pyrogram import Client, filters
from pyrogram.types import Message
import asyncio
import time
from urllib.parse import urlparse
from pyrogram import idle
load_dotenv()
API_ID = int(os.getenv("API_ID", "0"))
API_HASH = os.getenv("API_HASH", "").strip()
BOT_TOKEN = os.getenv("BOT_TOKEN", "").strip()
BASE_DIR = Path(__file__).resolve().parent
DOWNLOAD_DIR = BASE_DIR / "downloads"
QUEUE_DIR = BASE_DIR / "queue"
QUEUE_FILE = QUEUE_DIR / "tasks.jsonl"
STATUS_FILE = QUEUE_DIR / "status.jsonl"
SETTINGS_FILE = QUEUE_DIR / "settings.json"
DELETED_FILE = QUEUE_DIR / "deleted.jsonl"
CANCEL_FILE = QUEUE_DIR / "cancelled.jsonl"
DOWNLOAD_DIR.mkdir(parents=True, exist_ok=True)
QUEUE_DIR.mkdir(parents=True, exist_ok=True)
if not API_ID or not API_HASH or not BOT_TOKEN:
raise RuntimeError("Please set API_ID, API_HASH and BOT_TOKEN in .env")
app = Client(
"tel2rub",
api_id=API_ID,
api_hash=API_HASH,
bot_token=BOT_TOKEN,
)
def safe_filename(name: Optional[str]) -> str:
name = (name or "file.bin").strip()
name = re.sub(r'[<>:"/\\|?*\x00-\x1F]', "_", name)
name = name.rstrip(". ")
return name[:200] or "file.bin"
def split_name(filename: str) -> tuple[str, str]:
path = Path(filename)
return path.stem, path.suffix
def get_media(message: Message):
media_types = [
("document", message.document),
("video", message.video),
("audio", message.audio),
("voice", message.voice),
("photo", message.photo),
("animation", message.animation),
("video_note", message.video_note),
("sticker", message.sticker),
]
for media_type, media in media_types:
if media:
return media_type, media
return None, None
def build_download_filename(message: Message, media_type: str, media) -> str:
original_name = getattr(media, "file_name", None)
if not original_name:
file_unique_id = getattr(media, "file_unique_id", None) or "file"
default_extensions = {
"document": ".bin",
"video": ".mp4",
"audio": ".mp3",
"voice": ".ogg",
"photo": ".jpg",
"animation": ".mp4",
"video_note": ".mp4",
"sticker": ".webp",
}
original_name = f"{file_unique_id}{default_extensions.get(media_type, '.bin')}"
original_name = safe_filename(original_name)
stem, suffix = split_name(original_name)
unique_name = f"{stem}_{message.id}{suffix or '.bin'}"
return safe_filename(unique_name)
waiting_for_zip_password = False
class QueueManager:
def __init__(self):
self._cache = None
self._mtime = 0
def all(self):
mtime = QUEUE_FILE.stat().st_mtime if QUEUE_FILE.exists() else 0
if mtime == self._mtime and self._cache is not None:
return self._cache
self._cache = []
if QUEUE_FILE.exists():
with open(QUEUE_FILE, "r", encoding="utf-8") as f:
self._cache = [json.loads(l) for l in f if l.strip()]
self._mtime = mtime
return self._cache
def push(self, task):
task.setdefault("job_id", str(int(time.time() * 1000)))
with open(QUEUE_FILE, "a", encoding="utf-8") as f:
f.write(json.dumps(task, ensure_ascii=False) + "\n")
self._cache = None
def remove(self, job_id=None, message_id=None):
tasks = self.all()
kept, removed = [], None
for t in tasks:
if (job_id and str(t.get("job_id")) == str(job_id)) or \
(message_id and int(t.get("status_message_id", 0)) == message_id):
removed = t
else:
kept.append(t)
if removed:
with open(QUEUE_FILE, "w", encoding="utf-8") as f:
f.writelines(json.dumps(t, ensure_ascii=False) + "\n" for t in kept)
self._cache = None
return removed
queue = QueueManager()
def mark_deleted(task: dict):
with open(DELETED_FILE, "a", encoding="utf-8") as f:
f.write(json.dumps(task, ensure_ascii=False) + "\n")
def mark_cancelled(task: dict):
with open(CANCEL_FILE, "a", encoding="utf-8") as f:
f.write(json.dumps(task, ensure_ascii=False) + "\n")
def cancel_job(job_id: str):
with open(CANCEL_FILE, "a", encoding="utf-8") as f:
f.write(json.dumps({"job_id": str(job_id)}, ensure_ascii=False) + "\n")
def was_deleted(job_id=None, message_id=None) -> bool:
if not DELETED_FILE.exists():
return False
with open(DELETED_FILE, "r", encoding="utf-8") as f:
for line in f:
if not line.strip():
continue
item = json.loads(line)
if job_id and str(item.get("job_id")) == str(job_id):
return True
if message_id and int(item.get("status_message_id", 0)) == message_id:
return True
return False
def load_settings() -> dict:
try:
if SETTINGS_FILE.exists():
return json.loads(SETTINGS_FILE.read_text(encoding="utf-8"))
except Exception:
pass
return {"safe_mode": False, "zip_password": ""}
def save_settings(data: dict):
SETTINGS_FILE.write_text(
json.dumps(data, ensure_ascii=False, indent=2),
encoding="utf-8"
)
def is_direct_url(text: str) -> bool:
if not text:
return False
url = extract_first_url(text)
if not url:
return False
try:
parsed = urlparse(url)
except Exception:
return False
return parsed.scheme in ("http", "https") and bool(parsed.netloc)
def extract_first_url(text: str) -> Optional[str]:
if not text:
return None
match = re.search(r"https?://\S+", text)
return match.group(0) if match else None
def progress_bar(percent: float, length: int = 12) -> str:
filled = int(length * percent / 100)
return "█" * filled + "░" * (length - filled)
def pretty_size(size) -> str:
size = float(size or 0)
units = ["B", "KB", "MB", "GB"]
index = 0
while size >= 1024 and index < len(units) - 1:
size /= 1024
index += 1
return f"{size:.2f} {units[index]}"
def eta_text(seconds) -> str:
if not seconds or seconds <= 0:
return "نامشخص"
seconds = int(seconds)
h = seconds // 3600
m = (seconds % 3600) // 60
s = seconds % 60
if h:
return f"{h}h {m}m"
if m:
return f"{m}m {s}s"
return f"{s}s"
async def download_progress(current, total, status_message, file_name, started_at, state):
now = time.time()
if now - state.get("last_update", 0) < 3 and current < total:
return
state["last_update"] = now
percent = current * 100 / total if total else 0
elapsed = max(now - started_at, 1)
speed = current / elapsed
eta = (total - current) / speed if speed else None
text = (
f"📥 در حال دریافت فایل از تلگرام\n\n"
f"فایل: `{file_name}`\n"
f"حجم: `{pretty_size(total)}`\n"
f"پیشرفت: `{percent:.1f}%`\n"
f"`{progress_bar(percent)}`\n"
f"سرعت: `{pretty_size(speed)}/s`\n"
f"زمان باقی‌مانده: `{eta_text(eta)}`"
)
try:
await status_message.edit_text(text)
except Exception:
pass
async def status_watcher():
pos = 0
while True:
await asyncio.sleep(1)
if not STATUS_FILE.exists():
continue
try:
with open(STATUS_FILE, "r", encoding="utf-8") as f:
f.seek(pos)
lines = f.readlines()
pos = f.tell()
for line in lines:
if not line.strip():
continue
data = json.loads(line)
chat_id = data.get("chat_id")
msg_id = data.get("message_id")
text = data.get("text", "")
percent = data.get("percent")
if not chat_id or not msg_id:
continue
if percent is not None:
text += f"\n\n`{progress_bar(float(percent))}` `{float(percent):.1f}%`"
try:
await app.edit_message_text(chat_id, msg_id, text)
except Exception:
pass
except Exception:
pass
@app.on_message(filters.private & filters.command("start"))
async def start_handler(client: Client, message: Message):
await message.reply_text(
"سلام به ربات tele2rub خوش اومدی💙\n\n"
"برای انتقال فایل از تلگرام به روبیکا، کافیه فایل رو برام فوروارد کنی.\n"
"اگه لینک مستقیم دانلود هم بدی، فایل رو برات دانلود می‌کنم و توی روبیکا می‌فرستم.\n"
"⚠️لطفا فایل‌ها رو حداکثر ۱۰ تا ۱۰ تا ارسال کن تا از سمت روبیکا به مشکل نخوره.\n\n"
"برای دانلود از یوتیوب، اینستاگرام و... از این ربات استفاده کن: @Gozilla_bot\n"
"بعد فایل رو اینجا بفرست تا توی روبیکا برات ارسال کنم.\n\n"
"📌 راهنمای ربات:\n\n"
"-حذف از صف:\n"
"هر فایل وقتی تو صف قرار می‌گیره یه دستور لغو داره که با اون دستور می‌تونی حذفش کنی.\n"
"⚠️اگر فایل در حال آپلود باشه، لغو بعد از پایان تلاش فعلی انجام میشه.\n\n"
"-پاکسازی کل صف:\n"
"/delall\n\n"
"-حالت Safe Mode:\n"
"همه فایل‌ها با رمز دلخواهت به صورت ZIP رمزدار ارسال میشن.\n\n"
"برای فعال/غیرفعال کردن:\n"
" /safemode on\n"
" /safemode off\n\n"
"⚠️برای فایل‌های حجیم و ویدیوها بهتره Safe Mode خاموش باشه تا سریع‌تر آپلود بشن.\n\n"
"@caffeinexz"
)
@app.on_message(filters.private & filters.command("safemode"))
async def safemode_handler(client: Client, message: Message):
global waiting_for_zip_password
args = message.text.split(maxsplit=1)
if len(args) < 2:
await message.reply_text("برای تغییر وضعیت Safe Mode از `/safemode on` یا `/safemode off` استفاده کن.")
return
action = args[1].strip().lower()
settings = load_settings()
if action == "on":
settings["safe_mode"] = True
save_settings(settings)
waiting_for_zip_password = True
await message.reply_text(
"Safe Mode فعال شد.\n\n"
"لطفا رمزی که می‌خواهید روی فایل‌های ZIP قرار بگیرد را ارسال کنید.\n"
"از این به بعد فایل‌ها قبل از ارسال به روبیکا با همین رمز ZIP می‌شوند."
)
return
if action == "off":
settings["safe_mode"] = False
settings["zip_password"] = ""
save_settings(settings)
waiting_for_zip_password = False
await message.reply_text(
"Safe Mode غیرفعال شد.\n\n"
"از این به بعد فایل‌ها به‌صورت عادی ارسال می‌شوند."
)
return
await message.reply_text("دستور نامعتبر است. از `/safemode on` یا `/safemode off` استفاده کن.")
@app.on_message(filters.private & filters.command("delall"))
async def clear_queue_handler(client: Client, message: Message):
tasks = queue.all()
if not tasks:
await message.reply_text("صف خالی است.")
return
for task in tasks:
mark_deleted(task)
old_path = task.get("path")
if old_path:
try:
path = Path(old_path)
if path.exists():
path.unlink()
except Exception:
pass
try:
await client.edit_message_text(
chat_id=task["chat_id"],
message_id=task["status_message_id"],
text="این مورد از صف حذف شد."
)
except Exception:
pass
with open(QUEUE_FILE, "w", encoding="utf-8") as f:
pass
queue._cache = None
queue._mtime = 0
await message.reply_text("تمام موارد در صف پاک شد.")
@app.on_message(filters.private & filters.command("del"))
async def delete_one_handler(client: Client, message: Message):
job_id = None
reply_message_id = None
parts = message.text.split(maxsplit=1)
if len(parts) > 1:
job_id = parts[1].strip()
if message.reply_to_message:
reply_message_id = message.reply_to_message.id
tasks = queue.all()
if not tasks:
if job_id and was_deleted(job_id=job_id):
await message.reply_text("این مورد قبلاً از صف حذف شده است.")
return
if reply_message_id and was_deleted(message_id=reply_message_id):
await message.reply_text("این مورد قبلاً از صف حذف شده است.")
return
if job_id:
cancel_job(job_id)
await message.reply_text(
"لغو ثبت شد.\n\n"
)
return
await message.reply_text("موردی برای حذف در صف پیدا نشد.")
return
removed = queue.remove(job_id=job_id, message_id=reply_message_id)
if removed:
mark_deleted(removed)
old_path = removed.get("path")
if old_path:
try:
path = Path(old_path)
if path.exists():
path.unlink()
except Exception:
pass
try:
await client.edit_message_text(
chat_id=removed["chat_id"],
message_id=removed["status_message_id"],
text="این مورد از صف حذف شد."
)
except Exception:
pass
await message.reply_text("از صف حذف شد.")
return
if job_id and was_deleted(job_id=job_id):
await message.reply_text("این مورد قبلاً از صف حذف شده است.")
return
if reply_message_id and was_deleted(message_id=reply_message_id):
await message.reply_text("این مورد قبلاً از صف حذف شده است.")
return
if job_id:
cancel_job(job_id)
await message.reply_text("دستور لغو ثبت شد.")
return
@app.on_message(filters.private & filters.text & ~filters.command(["start", "safemode", "del", "delall"]))
async def text_handler(client: Client, message: Message):
global waiting_for_zip_password
text = message.text or ""
if waiting_for_zip_password:
password = text.strip()
if not password:
await message.reply_text("رمز نمی‌تواند خالی باشد. لطفاً یک رمز معتبر ارسال کنید.")
return
settings = load_settings()
settings["safe_mode"] = True
settings["zip_password"] = password
save_settings(settings)
waiting_for_zip_password = False
await message.reply_text(
"رمز ذخیره شد.\n\n"
"از این به بعد فایل‌ها قبل از ارسال به روبیکا به‌صورت ZIP رمزدار آماده می‌شوند."
)
return
url = extract_first_url(text)
if not url or not is_direct_url(url):
return
settings = load_settings()
status = await message.reply_text(
"لینک دریافت شد.\n\n"
"وضعیت: در صف دانلود قرار گرفت."
)
task = {
"type": "direct_url",
"url": url,
"chat_id": message.chat.id,
"status_message_id": status.id,
"safe_mode": settings.get("safe_mode", False),
"zip_password": settings.get("zip_password", ""),
}
queue.push(task)
await status.edit_text(
f"لینک در صف قرار گرفت.\n\n"
f"شناسه: `{task['job_id']}`\n"
f"برای حذف این مورد از صف:\n"
f"`/del {task['job_id']}`"
)
@app.on_message(
filters.private
& (
filters.document
| filters.video
| filters.audio
| filters.voice
| filters.photo
| filters.animation
| filters.video_note
| filters.sticker
)
)
async def media_handler(client: Client, message: Message):
media_type, media = get_media(message)
if not media:
await message.reply_text("فایل قابل پردازش نیست.")
return
download_name = build_download_filename(message, media_type, media)
download_path = DOWNLOAD_DIR / download_name
status = await message.reply_text(
"فایل دریافت شد.\n\n"
"وضعیت: آماده‌سازی برای دانلود از تلگرام..."
)
try:
started_at = time.time()
progress_state = {"last_update": 0}
downloaded = await client.download_media(
message,
file_name=str(download_path),
progress=download_progress,
progress_args=(status, download_name, started_at, progress_state),
)
if not downloaded:
raise RuntimeError("Download failed.")
downloaded_path = Path(downloaded)
if not downloaded_path.exists():
raise RuntimeError("Downloaded file not found.")
file_size = downloaded_path.stat().st_size
settings = load_settings()
task = {
"type": "local_file",
"path": str(downloaded_path),
"caption": message.caption or "",
"chat_id": message.chat.id,
"status_message_id": status.id,
"file_name": download_name,
"file_size": file_size,
"safe_mode": settings.get("safe_mode", False),
"zip_password": settings.get("zip_password", ""),
}
queue.push(task)
await status.edit_text(
f"در صف قرار گرفت.\n\n"
f"فایل: `{download_name}`\n"
f"حجم: `{pretty_size(file_size)}`\n"
f"شناسه: `{task['job_id']}`\n\n"
f"برای حذف این مورد از صف:\n"
f"`/del {task['job_id']}`"
)
except Exception as e:
await status.edit_text(f"خطا: {str(e)}")
def clear_old_status():
try:
if STATUS_FILE.exists():
STATUS_FILE.unlink()
except Exception:
pass
if __name__ == "__main__":
clear_old_status()
app.start()
app.loop.create_task(status_watcher())
idle()
app.stop()