diff --git a/.gitignore b/.gitignore index 194f318b..2b39d89e 100644 --- a/.gitignore +++ b/.gitignore @@ -4,13 +4,18 @@ users botlog.txt cookies.txt rclone.conf +rclone/ config.env *.json *.pickle *.pyc .netrc .vscode +.idea/ +.DS_Store pyrogram.session pyrogram_session.session Thumbnails/* +*.tar +bot/Gilang* diff --git a/Dockerfile b/Dockerfile index 20a61c83..e9268a8a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -3,6 +3,9 @@ FROM sammax23/rcmltb WORKDIR /usr/src/app RUN chmod 777 /usr/src/app +# Install latest rclone with pixeldrain support +RUN curl -fsSL https://rclone.org/install.sh | bash + COPY requirements.txt . RUN pip3 install --no-cache-dir -r requirements.txt diff --git a/README.md b/README.md index 6eba463b..007b2027 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,98 @@ An Rclone Mirror-Leech Telegram Bot to transfer to and from many clouds. Based on [mirror-leech-telegram-bot](https://github.com/anasty17/mirror-leech-telegram-bot) with rclone support added, and other features and changes from base code. +This is a fork of [Sam-Max/rcmltb](https://github.com/Sam-Max/rcmltb) with additional improvements and bug fixes. -**NOTE**: Base repository added recently its own rclone implementation. +**NOTE**: Base repository added recently its own rclone implementation. +--- + +## 🍎 ARM64 Support (Apple Silicon / Raspberry Pi / ARM Servers) + +**Running on ARM64?** Use the [`arm64` branch](https://github.com/cybercyberz/rcmltb/tree/arm64) for native support: + +```bash +git clone -b arm64 https://github.com/cybercyberz/rcmltb.git +``` + +The ARM64 branch includes: +- Native ARM64 Dockerfile (Apple M1/M2/M3, Raspberry Pi 4/5, ARM cloud servers) +- All features work except MEGA downloads (SDK not available for ARM64) +- Bug fixes backported from master + +--- + +## 🆕 Fork Improvements & Changelog + +This fork includes the following enhancements over the original repository: + +### v1.1.0 - Private Channel Batch Fix (2024-12-24) + +#### 🐛 Bug Fixes +- **Fixed "Peer id invalid" error for private channel batch operations**: The original bot would fail with `BAD_REQUEST: Peer id invalid` error when using `/mb` (mirror batch) or `/lb` (leech batch) commands with private/restricted channel links. + - **Root Cause**: Pyrogram requires the peer (channel) to be in its internal cache before it can fetch messages. For private channels, this cache wasn't being populated. + - **Solution**: The fix now iterates through user dialogs to find and cache the channel peer before attempting to fetch messages from private channels. + +#### 🔧 Enhanced Error Handling +- **Improved error messages for batch commands**: When batch operations fail on private channels, the bot now provides more descriptive error messages including the actual exception, making it easier to diagnose issues. +- **Added channel validation**: The bot now checks if the channel exists in user's dialogs and provides a clear message if the channel is not found. + +#### 📝 Documentation +- Added comprehensive guide for generating User Session String +- Added step-by-step instructions for private channel batch operations +- Updated README with fork improvements and changelog + +--- + +## 📱 How to Use Private Channel Batch Operations + +To download/mirror files from **private or restricted Telegram channels**, you need to set up a User Session String. This allows the bot to access channels that your Telegram account has joined. + +### Step 1: Generate User Session String + +1. **Install requirements** (if not already installed): + ```bash + pip3 install pyrogram + ``` + +2. **Run the session generator script**: + ```bash + python3 session_generator.py + ``` + +3. **Enter your credentials when prompted**: + - `API_ID`: Get from https://my.telegram.org + - `API_HASH`: Get from https://my.telegram.org + - You will receive a verification code on your Telegram app - enter it when prompted + +4. **Copy the generated session string**: The script will output a long string starting with `BQ...` - this is your session string. + +### Step 2: Configure the Bot + +Add your session string to `config.env`: +```env +USER_SESSION_STRING = "your_session_string_here" +``` + +### Step 3: Join the Private Channel + +Make sure the Telegram account (whose session string you generated) has **joined the private channel** you want to download from. + +### Step 4: Use Batch Commands + +Now you can use batch commands with private channel links: + +``` +/mb https://t.me/c/1234567890/100 https://t.me/c/1234567890/150 +``` + +This will mirror all files from message 100 to 150 from the private channel. + +**Commands:** +- `/mb` or `/mirror_batch` - Mirror files from private channel to cloud +- `/lb` or `/leech_batch` - Leech files from private channel to Telegram + +--- ## Features: diff --git a/bot/__init__.py b/bot/__init__.py index d2a2cc19..e06529a4 100644 --- a/bot/__init__.py +++ b/bot/__init__.py @@ -2,6 +2,7 @@ __author__ = "Sam-Max" from uvloop import install +import asyncio from asyncio import Lock from socket import setdefaulttimeout from logging import getLogger, FileHandler, StreamHandler, INFO, basicConfig @@ -23,6 +24,7 @@ faulthandler_enable() install() +asyncio.set_event_loop(asyncio.new_event_loop()) setdefaulttimeout(600) diff --git a/bot/helper/ext_utils/rclone_utils.py b/bot/helper/ext_utils/rclone_utils.py index 064f3344..ea8b483d 100644 --- a/bot/helper/ext_utils/rclone_utils.py +++ b/bot/helper/ext_utils/rclone_utils.py @@ -23,7 +23,8 @@ async def is_remote_selected(user_id, message): - if CustomFilters.sudo_filter("", message): + # Use sync CustomFilters.sudo() instead of async sudo_filter to avoid coroutine warning + if CustomFilters.sudo(user_id): if DEFAULT_OWNER_REMOTE := config_dict["DEFAULT_OWNER_REMOTE"]: update_rclone_data("MIRROR_SELECT_REMOTE", DEFAULT_OWNER_REMOTE, user_id) return True diff --git a/bot/helper/mirror_leech_utils/upload_utils/rclone_mirror.py b/bot/helper/mirror_leech_utils/upload_utils/rclone_mirror.py index 32eb29b1..a3002c5d 100644 --- a/bot/helper/mirror_leech_utils/upload_utils/rclone_mirror.py +++ b/bot/helper/mirror_leech_utils/upload_utils/rclone_mirror.py @@ -100,7 +100,7 @@ async def upload( "-P", ] - is_gdrive = is_gdrive_remote(remote, conf_path) + is_gdrive = await is_gdrive_remote(remote, conf_path) await setRcloneFlags(cmd, "upload") if ospath.isdir(path): diff --git a/bot/helper/telegram_helper/filters.py b/bot/helper/telegram_helper/filters.py index 8aa6275f..6dfc64c0 100644 --- a/bot/helper/telegram_helper/filters.py +++ b/bot/helper/telegram_helper/filters.py @@ -34,3 +34,8 @@ async def custom_sudo_filter(self, client, update): sudo_filter = create(custom_sudo_filter) + @staticmethod + def sudo(user_id): + """Check if user_id is owner or sudo user""" + return user_id == OWNER_ID or (user_id in user_data and user_data[user_id].get("is_sudo", False)) + diff --git a/bot/modules/batch.py b/bot/modules/batch.py index 4cec0f1d..986e6385 100644 --- a/bot/modules/batch.py +++ b/bot/modules/batch.py @@ -153,9 +153,20 @@ async def download(message, link, multi, isLeech, value=0): try: client = app chat = int("-100" + link.split("/")[-2]) + # Search for the channel in dialogs to populate peer cache + found = False + async for dialog in app.get_dialogs(): + if dialog.chat.id == chat: + found = True + break + if not found: + await sendMessage("Could not find the channel in your chats. Make sure you joined it!", message) + return msg = await app.get_messages(chat, msg_id) - except Exception: - await sendMessage("Make sure you joined the channel!!", message) + except Exception as e: + from bot import LOGGER + LOGGER.error(f"Error accessing private channel: {e}") + await sendMessage(f"Make sure you joined the channel!! Error: {e}", message) return else: client = bot diff --git a/bot/modules/tasks_listener.py b/bot/modules/tasks_listener.py index 46da49bf..cbabe0f7 100644 --- a/bot/modules/tasks_listener.py +++ b/bot/modules/tasks_listener.py @@ -522,7 +522,7 @@ async def onUploadComplete( ) else: cmd = ["rclone", "link", f"--config={rclone_config}", rclone_path] - res, code = await cmd_exec(cmd) + res, err, code = await cmd_exec(cmd) if code == 0: buttons.url_buildbutton("Cloud Link 🔗", res) else: