A Telegram client that monitors crypto signal channels, parses incoming signals, and forwards them to your channel in a clean, formatted template — with premium emoji support and random ad injection.
reader (Telethon user client)
└── listens to 8 source channels
└── deduplicates messages by ID
└── routes messages to channel-specific parsers
└── passes parsed signal dict to sender
sender (Telethon premium user client)
└── formats signal into template with premium emojis + random ad
└── sends to target channel (main or test, controlled via /dev)
└── listens to admin private messages for control commands
└── accepts manual signals and replies with formatted output
Two independent Telethon clients run in the same async event loop:
- reader — your regular account that is a member of the source channels
- sender — a premium account that posts formatted signals to your channel and accepts control commands via private message
| # | Channel | Parser |
|---|---|---|
| 1 | GCR VVIP | parsers/gcr.py |
| 2 | Charlotte / CryptoMermaids | parsers/charlotte.py |
| 3 | Monk / CryptoMonk_Japan | parsers/monk.py |
| 4 | SafeCall / Crypto_Safe_Calls | parsers/safecall.py |
| 5 | BitcoinBulls | parsers/bitcoinbulls.py |
| 6 | BullsSignal | parsers/bullssignal.py |
| 7 | SpartaCrypto | parsers/spartacrypto.py |
| 8 | CryptoAman | parsers/cryptoaman.py |
copyBot/
├── main.py # boots both clients, registers command handlers
├── listener.py # deduplication, routing, parser dispatch
├── sender.py # formats and sends signals to target channel
├── state.py # shared mutable state (active flags, target channels)
├── emojies.py # premium emoji constants
├── secrets.py # loads credentials from environment variables
├── setup.sh # setup and upgrade script
├── parsers/
│ ├── __init__.py
│ ├── monk.py
│ ├── gcr.py
│ ├── charlotte.py
│ ├── safecall.py
│ ├── bitcoinbulls.py
│ ├── bullssignal.py
│ ├── spartacrypto.py
│ └── cryptoaman.py
├── Dockerfile
├── docker-compose.yml
├── .env # your actual credentials (never commit this)
├── .env.example # template for credentials
└── .gitignore
- Docker + Docker Compose
- A Telegram account (reader) that is a member of the source channels
- A Telegram premium account (sender) that is an admin of your target channel
- API credentials from my.telegram.org for both accounts
git clone https://github.com/ali-moments/simpleCopyBot.git
cd simpleCopyBot
cp .env.example .envEdit .env with your credentials:
APP_ID=12345678
APP_HASH=your_reader_account_app_hash
PREMIUM_APP_ID=87654321
PREMIUM_APP_HASH=your_premium_account_app_hash
MAIN_CHANNEL=-1001234567890
TEST_CHANNEL=-1009876543210
ADMINS=1122334455,5566778899
PYTHONUNBUFFERED=1Then run the setup script:
bash setup.shThe script will:
- Check for
.envand abort if missing - Create empty session files to prevent Docker from mounting them as directories
- Build the Docker image
- Run interactive authentication for both accounts (reader + sender)
- Start the container in the background
Press
Ctrl+Conce you see✅ Both clients running— the script will then start the container automatically.
bash setup.sh upgradeThis will stop the container, pull latest code, rebuild the image, and restart — without losing your session files.
- Go to https://my.telegram.org
- Log in with the account's phone number
- Click API development tools
- Fill in any app name and select Desktop as platform
- Copy
api_idandapi_hash
Repeat for both accounts (reader and sender).
Forward any message to @userinfobot — it returns the ID. Channel IDs are negative numbers starting with -100. User IDs are plain positive numbers (use these for ADMINS).
# View logs
docker compose logs -f
# Stop
docker compose down
# Restart with rebuild
docker compose down && docker compose up -d --buildSend private messages directly to the sender (premium) account on Telegram:
| Command | Action |
|---|---|
/on |
Resume forwarding all signals (global kill switch) |
/off |
Pause forwarding all signals (global kill switch) |
/on [num] |
Enable a specific channel by number |
/off [num] |
Disable a specific channel by number |
/panel |
Show status of all channels, dev mode, and manual signal target |
/gcr |
Toggle GCR-only mode (enables GCR, disables all others — and vice versa) |
/dev |
Toggle dev mode — switches auto signal target between main and test channel |
/dasti |
Toggle manual signal target between main and test channel |
/help |
Show full command reference in Persian |
/test |
Show premium emoji reference message |
After every toggle command, the bot replies with the current /panel state automatically.
Send any raw signal message directly to the sender account in private chat. If it parses successfully:
- The bot replies with
سیگنال دریافت شد✅ - Sends the formatted signal to the manual signal target channel (
/dasticontrols this) - Also replies with the formatted output so you can preview it
If it fails to parse, the bot replies with پیام حاوی سیگنال نیست❌.
Each parsed signal is passed internally as a dict:
{
"symbol": "SUI/USDT",
"direction": "LONG", # or SHORT
"leverage": "20X",
"entries": [0.7191], # 1 or 2 prices
"targets": [0.71, 0.70], # variable length
"stop_loss": 0.7302,
"source": "monk"
}💎 #SYMBOL | DIRECTION 🔼/🔽
ENTRY ورود
📥 EN: price~
TARGETS حد سود
TARGETS✅: tp1 - tp2 - tp3 - ...
STOPLOSS حد ضرر
🔖 SL: price
⚠️ از 5 درصد مارجین و اهرم LEVERAGEx استفاده کنید و بعد از تارگت اول سیو سود و ریسک فری کنید 💎
[random Ourbit ad with link]
👑 @Royal_frx | رویال
Section headers are rendered as Telegram blockquotes. All emojis are premium custom emojis. A random Ourbit ad is injected into every signal.
-
Create
parsers/new_channel.pywith aparse(text: str) -> dict | Nonefunction — returnNonefor non-signal messages -
Add to
listener.py:
from parsers.new_channel import parse as new_parse
CHANNELS = {
-1001234567890: new_parse,
}- Add to
state.py:
CHANNELS = {
9: (-1001234567890, "New Channel Name"),
}CHANNEL_ACTIVE is auto-generated from CHANNELS — it will default to True.
- Premium emojis require the sender account to be a Telegram Premium subscriber and an admin of the target channel
- Duplicate message protection uses a fixed-size deque (last 1000 IDs in listener, last 250 in manual handler) — prevents double-forwarding on reconnects
- Both clients use
connection_retries=Noneandauto_reconnect=Truefor resilience on poor connections - The parser strips
**markdown artifacts that Telethon adds around premium emojis in raw message text state.pyholds all shared mutable state to avoid circular imports betweenmain.pyandlistener.py- Session files persist across container restarts via Docker volumes — authentication is only needed once