├── .env-example ├── .github └── images │ └── demo.png ├── .gitignore ├── Dockerfile ├── INSTALL.bat ├── INSTALL.sh ├── README-RU.md ├── README.md ├── START.bat ├── START.sh ├── bot ├── __init__.py ├── config │ ├── __init__.py │ ├── config.py │ └── proxies.txt ├── core │ ├── __init__.py │ ├── claimer.py │ ├── headers.py │ └── registrator.py ├── exceptions │ └── __init__.py └── utils │ ├── __init__.py │ ├── launcher.py │ └── logger.py ├── docker-compose.yml ├── main.py └── requirements.txt /.env-example: -------------------------------------------------------------------------------- 1 | API_ID= 2 | API_HASH= 3 | 4 | SLEEP_BETWEEN_START= 5 | ERRORS_BEFORE_STOP= 6 | USE_PROXY_FROM_FILE= 7 | DEBUG_MODE= -------------------------------------------------------------------------------- /.github/images/demo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Alexell/MMProBumpBot/5f080ad80a49c6f1e110b6ed4bc6158a64c55891/.github/images/demo.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | share/python-wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | MANIFEST 28 | 29 | # PyInstaller 30 | # Usually these files are written by a python script from a template 31 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 32 | *.manifest 33 | *.spec 34 | 35 | # Installer logs 36 | pip-log.txt 37 | pip-delete-this-directory.txt 38 | 39 | # Unit test / coverage reports 40 | htmlcov/ 41 | .tox/ 42 | .nox/ 43 | .coverage 44 | .coverage.* 45 | .cache 46 | nosetests.xml 47 | coverage.xml 48 | *.cover 49 | *.py,cover 50 | .hypothesis/ 51 | .pytest_cache/ 52 | cover/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # DB 65 | sessions/ 66 | 67 | # Flask stuff: 68 | instance/ 69 | .webassets-cache 70 | 71 | # Scrapy stuff: 72 | .scrapy 73 | 74 | # Sphinx documentation 75 | docs/_build/ 76 | 77 | # PyBuilder 78 | .pybuilder/ 79 | target/ 80 | 81 | # Jupyter Notebook 82 | .ipynb_checkpoints 83 | 84 | # IPython 85 | profile_default/ 86 | ipython_config.py 87 | 88 | # pyenv 89 | # For a library or package, you might want to ignore these files since the code is 90 | # intended to run in multiple environments; otherwise, check them in: 91 | # .python-version 92 | 93 | # pipenv 94 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 95 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 96 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 97 | # install all needed dependencies. 98 | #Pipfile.lock 99 | 100 | # poetry 101 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. 102 | # This is especially recommended for binary packages to ensure reproducibility, and is more 103 | # commonly ignored for libraries. 104 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control 105 | #poetry.lock 106 | 107 | # pdm 108 | # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. 109 | #pdm.lock 110 | # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it 111 | # in version control. 112 | # https://pdm.fming.dev/#use-with-ide 113 | .pdm.toml 114 | 115 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm 116 | __pypackages__/ 117 | 118 | # Celery stuff 119 | celerybeat-schedule 120 | celerybeat.pid 121 | 122 | # SageMath parsed files 123 | *.sage.py 124 | 125 | # Environments 126 | .env 127 | .venv 128 | env/ 129 | venv/ 130 | ENV/ 131 | env.bak/ 132 | venv.bak/ 133 | 134 | # Spyder project settings 135 | .spyderproject 136 | .spyproject 137 | 138 | # Rope project settings 139 | .ropeproject 140 | 141 | # mkdocs documentation 142 | /site 143 | 144 | # mypy 145 | .mypy_cache/ 146 | .dmypy.json 147 | dmypy.json 148 | 149 | # Pyre type checker 150 | .pyre/ 151 | 152 | # pytype static type analyzer 153 | .pytype/ 154 | 155 | # Cython debug symbols 156 | cython_debug/ 157 | 158 | # PyCharm 159 | # JetBrains specific template is maintained in a separate JetBrains.gitignore that can 160 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore 161 | # and can be added to the global gitignore or merged into this file. For a more nuclear 162 | # option (not recommended) you can uncomment the following to ignore the entire idea folder. 163 | .idea/ 164 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.10.11-alpine3.18 2 | 3 | WORKDIR /app 4 | 5 | COPY requirements.txt requirements.txt 6 | 7 | RUN pip3 install --upgrade pip setuptools wheel 8 | RUN pip3 install --no-warn-script-location --no-cache-dir -r requirements.txt 9 | 10 | COPY . . 11 | 12 | ENTRYPOINT ["python3", "main.py"] 13 | CMD ["-a", "2"] -------------------------------------------------------------------------------- /INSTALL.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | echo Creating virtual environment... 3 | python -m venv venv 4 | echo Activating virtual environment... 5 | call venv\Scripts\activate 6 | echo Installing dependencies... 7 | pip install -r requirements.txt 8 | echo Copying .env-example to .env... 9 | copy .env-example .env 10 | echo Please edit the .env file to add your API_ID and API_HASH. 11 | pause 12 | -------------------------------------------------------------------------------- /INSTALL.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | install_python() { 4 | echo "Select the Python version to install:" 5 | echo "1) Python 3.10" 6 | echo "2) Python 3.11" 7 | echo "3) Python 3.12" 8 | read -p "Enter the number of your choice: " choice 9 | 10 | case $choice in 11 | 1) version="3.10" ;; 12 | 2) version="3.11" ;; 13 | 3) version="3.12" ;; 14 | *) echo "Invalid choice"; exit 1 ;; 15 | esac 16 | 17 | if command -v apt-get &> /dev/null; then 18 | sudo apt-get update 19 | sudo apt-get install -y python$version python$version-venv python$version-pip 20 | elif command -v yum &> /dev/null; then 21 | sudo yum install -y https://repo.ius.io/ius-release-el$(rpm -E %{rhel}).rpm 22 | sudo yum install -y python$version python$version-venv python$version-pip 23 | elif command -v dnf &> /dev/null; then 24 | sudo dnf install -y python$version python$version-venv python$version-pip 25 | else 26 | echo "Package manager not supported. Please install Python manually." 27 | exit 1 28 | fi 29 | } 30 | 31 | if ! command -v python3 &> /dev/null; then 32 | install_python 33 | fi 34 | 35 | echo "Creating virtual environment..." 36 | python3 -m venv venv 37 | 38 | echo "Activating virtual environment..." 39 | source venv/bin/activate 40 | 41 | echo "Installing dependencies..." 42 | pip install -r requirements.txt 43 | 44 | echo "Copying .env-example to .env..." 45 | cp .env-example .env 46 | 47 | echo "Please edit the .env file to add your API_ID and API_HASH." 48 | read -p "Press any key to continue..." -------------------------------------------------------------------------------- /README-RU.md: -------------------------------------------------------------------------------- 1 | # Бот для [MMPro Bump](https://alexell.pro/cc/mmpro) 2 | 3 | ![img1](.github/images/demo.png) 4 | 5 | > 🇺🇸 README in english available [here](README.md) 6 | 7 | ## Функционал 8 | | Функция | Поддерживается | 9 | |----------------------------------------------------------------|:---------------:| 10 | | Многопоточность | ✅ | 11 | | Привязка прокси к сессии | ✅ | 12 | | Получение ежедневной награды | ✅ | 13 | | Получение награды за друзей | ✅ | 14 | | Получение награды за задания | ✅ | 15 | | Автоматический фарминг | ✅ | 16 | | Автоматические тапы с учетом бустов | ✅ | 17 | | Docker | ✅ | 18 | 19 | ## [Настройки](https://github.com/Alexell/MMProBumpBot/blob/main/.env-example) 20 | | Опция | Описание | 21 | |-------------------------|----------------------------------------------------------------------------| 22 | | **API_ID / API_HASH** | Данные платформы, с которой запускать сессию Telegram (сток - Android) | 23 | | **SLEEP_BETWEEN_START** | Задержка перед запуском каждой сессии (напр. [20, 360]) | 24 | | **ERRORS_BEFORE_STOP** | Количество неудачных запросов, по достижению которых, бот остановится | 25 | | **USE_PROXY_FROM_FILE** | Использовать-ли прокси из файла `bot/config/proxies.txt` (True / False) | 26 | 27 | **API_ID** и **API_HASH** вы можете получить после создания приложения на [my.telegram.org/apps](https://my.telegram.org/apps) 28 | 29 | ## Быстрый старт 30 | ### Windows 31 | 1. Убедитесь, что у вас установлен **Python 3.10** или более новая версия. 32 | 2. Используйте `INSTALL.bat` для установки, затем укажите ваши API_ID и API_HASH в .env 33 | 3. Используйте `START.bat` для запуска бота (или в консоли: `python main.py`) 34 | 35 | ### Linux 36 | 1. Клонируйте репозиторий: `git clone https://github.com/Alexell/MMProBumpBot.git && cd MMProBumpBot` 37 | 2. Выполните установку: `chmod +x INSTALL.sh START.sh && ./INSTALL.sh`, затем укажите ваши API_ID и API_HASH в .env 38 | 3. Используйте `./START.sh` для запуска бота (или в консоли: `python3 main.py`) 39 | 40 | ## Запуск в Docker 41 | ``` 42 | $ git clone https://github.com/Alexell/MMProBumpBot.git 43 | $ cd MMProBumpBot 44 | $ cp .env-example .env 45 | $ nano .env # укажите ваши API_ID и API_HASH, остальное можно оставить по умолчанию 46 | ``` 47 | ### Docker Compose (рекомендуется) 48 | ``` 49 | $ docker-compose run bot -a 1 # первый запуск для авторизации (переопределяем аргументы) 50 | $ docker-compose start # запуск в фоновом режиме (аргументы по умолчанию: -a 2) 51 | ``` 52 | ### Docker 53 | ``` 54 | $ docker build -t mmpro_bump_bot . 55 | $ docker run --name MMProBumpBot -v .:/app -it mmpro_bump_bot -a 1 # первый запуск для авторизации 56 | $ docker rm MMProBumpBot # удаляем контейнер для пересоздания с аргументами по умолчанию 57 | $ docker run -d --restart unless-stopped --name MMProBumpBot -v .:/app mmpro_bump_bot # запуск в фоновом режиме (аргументы по умолчанию: -a 2) 58 | ``` 59 | 60 | ## Ручная установка 61 | Вы можете скачать [**Репозиторий**](https://github.com/Alexell/MMProBumpBot) клонированием на вашу систему и установкой необходимых зависимостей: 62 | ``` 63 | $ git clone https://github.com/Alexell/MMProBumpBot.git 64 | $ cd MMProBumpBot 65 | 66 | # Linux 67 | $ python3 -m venv venv 68 | $ source venv/bin/activate 69 | $ pip3 install -r requirements.txt 70 | $ cp .env-example .env 71 | $ nano .env # укажите ваши API_ID и API_HASH, остальное можно оставить по умолчанию 72 | $ python3 main.py 73 | 74 | # Windows (сначала установите Python 3.10 или более новую версию) 75 | > python -m venv venv 76 | > venv\Scripts\activate 77 | > pip install -r requirements.txt 78 | > copy .env-example .env 79 | > # укажите ваши API_ID и API_HASH, остальное можно оставить по умолчанию 80 | > python main.py 81 | ``` 82 | 83 | Также для быстрого запуска вы можете использовать аргументы: 84 | ``` 85 | $ python3 main.py --action (1/2) 86 | # или 87 | $ python3 main.py -a (1/2) 88 | 89 | # 1 - создать сессию 90 | # 2 - запустить бот 91 | ``` 92 | 93 | ## Запуск бота в фоновом режиме (Linux) 94 | ``` 95 | $ cd MMProBumpBot 96 | 97 | # с логированием 98 | $ setsid venv/bin/python3 main.py --action 2 >> app.log 2>&1 & 99 | 100 | # без логирования 101 | $ setsid venv/bin/python3 main.py --action 2 > /dev/null 2>&1 & 102 | 103 | # Теперь вы можете закрыть консоль и бот продолжит свою работу. 104 | ``` 105 | 106 | ### Найти процесс бота 107 | ``` 108 | $ ps aux | grep "python3 main.py" | grep -v grep 109 | ``` -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Bot for [MMPro Bump](https://alexell.pro/cc/mmpro) 2 | 3 | ![img1](.github/images/demo.png) 4 | 5 | > 🇷🇺 README на русском доступен [здесь](README-RU.md) 6 | 7 | ## Functionality 8 | | Feature | Supported | 9 | |----------------------------------------------------------------|:----------:| 10 | | Multithreading | ✅ | 11 | | Binding a proxy to a session | ✅ | 12 | | Claim daily grant | ✅ | 13 | | Claim reward for friends | ✅ | 14 | | Claim reward for tasks | ✅ | 15 | | Automatic farming | ✅ | 16 | | Automatic taps that account for enabled boosts | ✅ | 17 | | Docker | ✅ | 18 | 19 | ## [Options](https://github.com/Alexell/MMProBumpBot/blob/main/.env-example) 20 | | Option | Description | 21 | |-------------------------|----------------------------------------------------------------------------| 22 | | **API_ID / API_HASH** | Platform data from which to launch a Telegram session (stock - Android) | 23 | | **SLEEP_BETWEEN_START** | Sleep before start each session (e.g. [20, 360]) | 24 | | **ERRORS_BEFORE_STOP** | The number of failed requests after which the bot will stop | 25 | | **USE_PROXY_FROM_FILE** | Whether to use proxy from the `bot/config/proxies.txt` file (True / False) | 26 | 27 | You can obtain the **API_ID** and **API_HASH** after creating an application at [my.telegram.org/apps](https://my.telegram.org/apps) 28 | 29 | ## Quick start 30 | ### Windows 31 | 1. Ensure you have **Python 3.10** or a newer version installed. 32 | 2. Use `INSTALL.bat` to install, then specify your API_ID and API_HASH in the .env file. 33 | 3. Use `START.bat` to launch the bot (or in the console: `python main.py`). 34 | 35 | ### Linux 36 | 1. Clone the repository: `git clone https://github.com/Alexell/MMProBumpBot.git && cd MMProBumpBot` 37 | 2. Run the installation: `chmod +x INSTALL.sh START.sh && ./INSTALL.sh`, then specify your API_ID and API_HASH in the .env file. 38 | 3. Use `./START.sh` to run the bot (or in the console: `python3 main.py`). 39 | 40 | ## Running in Docker 41 | ``` 42 | $ git clone https://github.com/Alexell/MMProBumpBot.git 43 | $ cd MMProBumpBot 44 | $ cp .env-example .env 45 | $ nano .env # specify your API_ID and API_HASH, the rest can be left as default 46 | ``` 47 | ### Docker Compose (recommended) 48 | ``` 49 | $ docker-compose run bot -a 1 # first run for authorization (override arguments) 50 | $ docker-compose start # start in background mode (default arguments: -a 2) 51 | ``` 52 | ### Docker 53 | ``` 54 | $ docker build -t mmpro_bump_bot . 55 | $ docker run --name MMProBumpBot -v .:/app -it mmpro_bump_bot -a 1 # first run for authorization 56 | $ docker rm MMProBumpBot # remove container to recreate with default arguments 57 | $ docker run -d --restart unless-stopped --name MMProBumpBot -v .:/app mmpro_bump_bot # start in background mode (default arguments: -a 2) 58 | ``` 59 | 60 | ## Manual installation 61 | You can download [**Repository**](https://github.com/Alexell/MMProBumpBot) by cloning it to your system and installing the necessary dependencies: 62 | ``` 63 | $ git clone https://github.com/Alexell/MMProBumpBot.git 64 | $ cd MMProBumpBot 65 | 66 | # Linux 67 | $ python3 -m venv venv 68 | $ source venv/bin/activate 69 | $ pip3 install -r requirements.txt 70 | $ cp .env-example .env 71 | $ nano .env # specify your API_ID and API_HASH, the rest can be left as default 72 | $ python3 main.py 73 | 74 | # Windows (first, install Python 3.10 or a newer version) 75 | > python -m venv venv 76 | > venv\Scripts\activate 77 | > pip install -r requirements.txt 78 | > copy .env-example .env 79 | > # specify your API_ID and API_HASH, the rest can be left as default 80 | > python main.py 81 | ``` 82 | 83 | Also for quick launch you can use arguments: 84 | ``` 85 | $ python3 main.py --action (1/2) 86 | # or 87 | $ python3 main.py -a (1/2) 88 | 89 | # 1 - Create session 90 | # 2 - Run bot 91 | ``` 92 | 93 | ## Running a bot in the background (Linux) 94 | ``` 95 | $ cd MMProBumpBot 96 | 97 | # with logging 98 | $ setsid venv/bin/python3 main.py --action 2 >> app.log 2>&1 & 99 | 100 | # without logging 101 | $ setsid venv/bin/python3 main.py --action 2 > /dev/null 2>&1 & 102 | 103 | # Now you can close the console, and the bot will continue its work. 104 | ``` 105 | 106 | ### Find the bot process 107 | ``` 108 | $ ps aux | grep "python3 main.py" | grep -v grep 109 | ``` -------------------------------------------------------------------------------- /START.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | echo Activating virtual environment... 3 | call venv\Scripts\activate 4 | echo Starting the bot... 5 | python main.py 6 | pause 7 | -------------------------------------------------------------------------------- /START.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | echo "Activating virtual environment..." 4 | source venv/bin/activate 5 | 6 | echo "Starting the bot..." 7 | python main.py 8 | 9 | echo "Press any key to continue..." 10 | read -n 1 -s 11 | -------------------------------------------------------------------------------- /bot/__init__.py: -------------------------------------------------------------------------------- 1 | __version__ = '1.0' 2 | -------------------------------------------------------------------------------- /bot/config/__init__.py: -------------------------------------------------------------------------------- 1 | from .config import settings 2 | -------------------------------------------------------------------------------- /bot/config/config.py: -------------------------------------------------------------------------------- 1 | from pydantic_settings import BaseSettings, SettingsConfigDict 2 | from bot.utils import logger 3 | 4 | 5 | class Settings(BaseSettings): 6 | model_config = SettingsConfigDict(env_file=".env", env_ignore_empty=True) 7 | 8 | API_ID: int 9 | API_HASH: str 10 | 11 | SLEEP_BETWEEN_START: list[int] = [20, 360] 12 | ERRORS_BEFORE_STOP: int = 3 13 | USE_PROXY_FROM_FILE: bool = False 14 | DEBUG_MODE: bool = False 15 | 16 | 17 | try: 18 | settings = Settings() 19 | except Exception as error: 20 | logger.error(error) 21 | settings = False 22 | -------------------------------------------------------------------------------- /bot/config/proxies.txt: -------------------------------------------------------------------------------- 1 | type://user:pass@ip:port 2 | type://user:pass:ip:port 3 | type://ip:port:user:pass 4 | type://ip:port@user:pass 5 | type://ip:port -------------------------------------------------------------------------------- /bot/core/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Alexell/MMProBumpBot/5f080ad80a49c6f1e110b6ed4bc6158a64c55891/bot/core/__init__.py -------------------------------------------------------------------------------- /bot/core/claimer.py: -------------------------------------------------------------------------------- 1 | import asyncio, aiohttp, random, math, hashlib, hmac, json, traceback 2 | from time import time, strftime, localtime 3 | from urllib.parse import quote, unquote 4 | from typing import Any, Dict, List 5 | from aiohttp_proxy import ProxyConnector 6 | from better_proxy import Proxy 7 | from pyrogram import Client 8 | from pyrogram.errors import Unauthorized, UserDeactivated, AuthKeyUnregistered 9 | from pyrogram.raw.functions.messages import RequestWebView 10 | 11 | from bot.config import settings 12 | from bot.utils import logger 13 | from bot.exceptions import InvalidSession 14 | from .headers import headers 15 | 16 | class Claimer: 17 | def __init__(self, tg_client: Client): 18 | self.session_name = tg_client.name 19 | self.tg_client = tg_client 20 | self.user_id = None 21 | self.api_url = 'https://api.mmbump.pro/v1' 22 | self.errors = 0 23 | 24 | async def get_tg_web_data(self, proxy: str | None) -> str: 25 | if proxy: 26 | proxy = Proxy.from_str(proxy) 27 | proxy_dict = dict( 28 | scheme=proxy.protocol, 29 | hostname=proxy.host, 30 | port=proxy.port, 31 | username=proxy.login, 32 | password=proxy.password 33 | ) 34 | else: 35 | proxy_dict = None 36 | 37 | self.tg_client.proxy = proxy_dict 38 | 39 | try: 40 | if not self.tg_client.is_connected: 41 | try: 42 | await self.tg_client.connect() 43 | if self.user_id is None: 44 | user = await self.tg_client.get_me() 45 | self.user_id = user.id 46 | self.http_client.headers["user_auth"] = str(self.user_id) 47 | headers["user_auth"] = str(self.user_id) 48 | except (Unauthorized, UserDeactivated, AuthKeyUnregistered): 49 | raise InvalidSession(self.session_name) 50 | web_view = await self.tg_client.invoke(RequestWebView( 51 | peer=await self.tg_client.resolve_peer('MMproBump_bot'), 52 | bot=await self.tg_client.resolve_peer('MMproBump_bot'), 53 | platform='android', 54 | from_bot_menu=False, 55 | url='https://api.mmbump.pro/' 56 | )) 57 | auth_url = web_view.url 58 | tg_web_data = unquote( 59 | string=auth_url.split('tgWebAppData=', maxsplit=1)[1].split('&tgWebAppVersion', maxsplit=1)[0]) 60 | if self.tg_client.is_connected: 61 | await self.tg_client.disconnect() 62 | 63 | return tg_web_data 64 | 65 | except InvalidSession as error: 66 | raise error 67 | 68 | except Exception as error: 69 | logger.error(f"{self.session_name} | Unknown error during Authorization: {error}" + (f"\nTraceback: {traceback.format_exc()}" if settings.DEBUG_MODE else "")) 70 | await asyncio.sleep(delay=3) 71 | 72 | async def login(self, init_data: str) -> str: 73 | url = self.api_url + '/loginJwt' 74 | try: 75 | await self.http_client.options(url) 76 | json_data = {"initData": init_data} 77 | response = await self.http_client.post(url, json=json_data) 78 | response.raise_for_status() 79 | response_text = await response.text() 80 | if settings.DEBUG_MODE: 81 | print(f"Login response:\n{response_text}") 82 | if self.isValidJson(response_text): 83 | response_json = json.loads(response_text) 84 | token = response_json.get('access_token', '') 85 | return token 86 | return False 87 | except Exception as error: 88 | logger.error(f"{self.session_name} | Unknown error when log in: {error}" + (f"\nTraceback: {traceback.format_exc()}" if settings.DEBUG_MODE else "")) 89 | self.errors += 1 90 | await asyncio.sleep(delay=3) 91 | return False 92 | 93 | async def refresh_token(self) -> str | bool: 94 | url = self.api_url + '/auth/refresh' 95 | try: 96 | await self.http_client.options(url) 97 | response = await self.http_client.post(url) 98 | response.raise_for_status() 99 | response_text = await response.text() 100 | if settings.DEBUG_MODE: 101 | print(f"Refresh auth tokens response:\n{response_text}") 102 | if self.isValidJson(response_text): 103 | response_json = json.loads(response_text) 104 | self.access_token = response_json.get('access', '') 105 | return True if self.access_token != '' else False 106 | return False 107 | except Exception as error: 108 | logger.error(f"{self.session_name} | Unknown error when Refresh auth tokens: {error}" + (f"\nTraceback: {traceback.format_exc()}" if settings.DEBUG_MODE else "")) 109 | await asyncio.sleep(delay=3) 110 | return False 111 | 112 | async def get_profile(self) -> Dict[str, Any]: 113 | url = self.api_url + '/farming' 114 | try: 115 | await self.http_client.options(url) 116 | response = await self.http_client.post(url) 117 | response.raise_for_status() 118 | response_text = await response.text() 119 | if settings.DEBUG_MODE: 120 | print(f"Profile Data response:\n{response_text}") 121 | if self.isValidJson(response_text): 122 | response_json = json.loads(response_text) 123 | return response_json 124 | return False 125 | except Exception as error: 126 | logger.error(f"{self.session_name} | Unknown error when getting Profile Data: {error}" + (f"\nTraceback: {traceback.format_exc()}" if settings.DEBUG_MODE else "")) 127 | self.errors += 1 128 | await asyncio.sleep(delay=3) 129 | return {} 130 | 131 | async def daily_grant(self) -> bool: 132 | url = self.api_url + '/grant-day/claim' 133 | url_reset = self.api_url + '/grant-day/reset' 134 | try: 135 | json_data = {} 136 | data_list = [] 137 | json_data['hash'] = await self.create_hash(data_list) 138 | await self.http_client.options(url) 139 | response = await self.http_client.post(url, json=json_data) 140 | #response.raise_for_status() 141 | if response.status == 400: 142 | await self.http_client.options(url_reset) 143 | await self.http_client.post(url_reset) 144 | await asyncio.sleep(delay=2) 145 | json_data['hash'] = await self.create_hash(data_list) 146 | response = await self.http_client.post(url) 147 | response_text = await response.text() 148 | if settings.DEBUG_MODE: 149 | print(f"Daily grant response:\n{response_text}") 150 | if self.isValidJson(response_text): 151 | response_json = json.loads(response_text) 152 | balance = response_json.get('balance', False) 153 | if balance is not False: 154 | self.balance = int(balance) 155 | return True 156 | return False 157 | except Exception as error: 158 | logger.error(f"{self.session_name} | Unknown error when getting daily grant: {error}" + (f"\nTraceback: {traceback.format_exc()}" if settings.DEBUG_MODE else "")) 159 | self.errors += 1 160 | await asyncio.sleep(delay=3) 161 | return False 162 | 163 | async def friends_claim(self) -> bool: 164 | url_friends = self.api_url + '/friends' 165 | url_claim = self.api_url + '/friends/claim' 166 | try: 167 | json_data = {'offset': 0, 'limit': 20} 168 | data_list = [json_data] 169 | json_data['hash'] = await self.create_hash(data_list) 170 | await self.http_client.options(url_friends) 171 | response = await self.http_client.post(url_friends, json=json_data) 172 | response.raise_for_status() 173 | response_text = await response.text() 174 | if settings.DEBUG_MODE: 175 | print(f"Friends response:\n{response_text}") 176 | if not self.isValidJson(response_text): return False 177 | response_json = json.loads(response_text) 178 | friend_claim = int(response_json.get('friend_claim', 0)) 179 | if friend_claim > 0: 180 | await asyncio.sleep(delay=2) 181 | logger.info(f"{self.session_name} | Friends reward available") 182 | json_data = {} 183 | data_list = [] 184 | json_data['hash'] = await self.create_hash(data_list) 185 | await self.http_client.options(url_claim) 186 | response = await self.http_client.post(url_claim, json=json_data) 187 | #response.raise_for_status() 188 | if response.status == 200: # Sometimes server errors occur 189 | response_text = await response.text() 190 | if settings.DEBUG_MODE: 191 | print(f"Friends claim response:\n{response_text}") 192 | if self.isValidJson(response_text): 193 | response_json = json.loads(response_text) 194 | balance = response_json.get('balance', False) 195 | if balance is not False: 196 | logger.success(f"{self.session_name} | Friends reward claimed") 197 | self.balance = int(balance) 198 | self.errors = 0 199 | return True 200 | return False 201 | else: return False 202 | except Exception as error: 203 | logger.error(f"{self.session_name} | Unknown error when claiming friends reward: {error}" + (f"\nTraceback: {traceback.format_exc()}" if settings.DEBUG_MODE else "")) 204 | self.errors += 1 205 | await asyncio.sleep(delay=3) 206 | return False 207 | 208 | async def send_claim(self, taps: int) -> bool: 209 | url = self.api_url + '/farming/finish' 210 | try: 211 | json_data = {"tapCount":taps} 212 | data_list = [json_data] 213 | json_data['hash'] = await self.create_hash(data_list) 214 | await self.http_client.options(url) 215 | response = await self.http_client.post(url, json=json_data) 216 | response.raise_for_status() 217 | response_text = await response.text() 218 | if settings.DEBUG_MODE: 219 | print(f"Claiming response:\n{response_text}") 220 | if self.isValidJson(response_text): 221 | response_json = json.loads(response_text) 222 | balance = response_json.get('balance', False) 223 | if balance is not False: 224 | self.balance = int(balance) 225 | return True 226 | return False 227 | except Exception as error: 228 | logger.error(f"{self.session_name} | Unknown error when Claiming: {error}" + (f"\nTraceback: {traceback.format_exc()}" if settings.DEBUG_MODE else "")) 229 | self.errors += 1 230 | await asyncio.sleep(delay=3) 231 | return False 232 | 233 | async def start_farming(self) -> bool: 234 | url = self.api_url + '/farming/start' 235 | await asyncio.sleep(delay=6) 236 | try: 237 | json_data = {"status":"inProgress"} 238 | data_list = [json_data] 239 | json_data['hash'] = await self.create_hash(data_list) 240 | await self.http_client.options(url) 241 | response = await self.http_client.post(url, json=json_data) 242 | response.raise_for_status() 243 | response_text = await response.text() 244 | if settings.DEBUG_MODE: 245 | print(f"Login response:\n{response_text}") 246 | if self.isValidJson(response_text): 247 | response_json = json.loads(response_text) 248 | status = response_json.get('status', False) 249 | if status is False: return False 250 | else: return True 251 | return False 252 | except Exception as error: 253 | logger.error(f"{self.session_name} | Unknown error when Start Farming: {error}" + (f"\nTraceback: {traceback.format_exc()}" if settings.DEBUG_MODE else "")) 254 | self.errors += 1 255 | await asyncio.sleep(delay=3) 256 | return False 257 | 258 | async def perform_tasks(self) -> None: 259 | url = self.api_url + '/task-list' 260 | try: 261 | json_data = {} 262 | data_list = [] 263 | json_data['hash'] = await self.create_hash(data_list) 264 | await self.http_client.options(url) 265 | response = await self.http_client.post(url, json=json_data) 266 | response.raise_for_status() 267 | response_text = await response.text() 268 | if settings.DEBUG_MODE: 269 | print(f"Tasks response:\n{response_text}") 270 | if not self.isValidJson(response_text): return 271 | response_json = json.loads(response_text) 272 | completed = 0 273 | for task in response_json: 274 | if completed == 2: break # perform a maximum of 2 tasks in a row 275 | if int(task['is_active']) == 0: continue 276 | if task['type'] == 'tonkeeper_wallet': continue # ignore task with connecting Tonkeeper wallet 277 | if '//forms.gle' in task['url']: continue 278 | if '//t.me' in task['url']: 279 | continue # ignore all Telegram tasks (they are verified on the server side) 280 | if task['status'] == 'possible': 281 | logger.info(f"{self.session_name} | Try to perform task {task['id']}") 282 | await asyncio.sleep(random.randint(4, 8)) 283 | json_data2 = {"id":task['id']} 284 | data_list2 = [json_data2] 285 | json_data2['hash'] = await self.create_hash(data_list2) 286 | response2 = await self.http_client.post(f"{url}/complete", json=json_data2) 287 | response2.raise_for_status() 288 | response_text2 = await response2.text() 289 | if settings.DEBUG_MODE: 290 | print(f"Complete task response:\n{response_text2}") 291 | if self.isValidJson(response_text2): 292 | response_json2 = json.loads(response_text2) 293 | status = response_json2.get('task', {}).get('status', False) 294 | if status == 'granted': 295 | logger.success(f"{self.session_name} | Task {task['id']} completed. Reward claimed.") 296 | await asyncio.sleep(random.randint(2, 4)) 297 | completed += 1 298 | self.errors = 0 299 | else: 300 | logger.info(f"{self.session_name} | Failed to perform task {task['id']}") 301 | except Exception as error: 302 | logger.error(f"{self.session_name} | Unknown error while Performing tasks: {error}" + (f"\nTraceback: {traceback.format_exc()}" if settings.DEBUG_MODE else "")) 303 | self.errors += 1 304 | await asyncio.sleep(delay=3) 305 | 306 | async def check_proxy(self, proxy: Proxy) -> None: 307 | try: 308 | response = await self.http_client.get(url='https://httpbin.org/ip', timeout=aiohttp.ClientTimeout(5)) 309 | ip = (await response.json()).get('origin') 310 | logger.info(f"{self.session_name} | Proxy IP: {ip}") 311 | except Exception as error: 312 | logger.error(f"{self.session_name} | Proxy: {proxy} | Error: {error}") 313 | 314 | async def check_daily_grant(self, start_time: int | None, cur_time: int, day: int | None) -> tuple[bool, int]: 315 | if start_time is None or day is None: 316 | logger.info(f"{self.session_name} | First daily grant available") 317 | return True, 0 318 | 319 | seconds = cur_time - start_time 320 | days = seconds / 86400 321 | if days > day: 322 | logger.info(f"{self.session_name} | Daily grant available") 323 | return True, 0 324 | else: 325 | next_grant_time = start_time + (day * 86400) 326 | time_to_wait = next_grant_time - cur_time 327 | logger.info(f"{self.session_name} | Next daily grant: {strftime('%Y-%m-%d %H:%M:%S', localtime(next_grant_time))}") 328 | return False, time_to_wait 329 | 330 | async def calculate_taps(self, farm: int, boost: int | bool) -> int: 331 | if isinstance(boost, int) and boost > 0: 332 | full_farm = farm * boost 333 | else: 334 | full_farm = farm 335 | 336 | perc = random.randint(100, 200) 337 | taps = int(full_farm * (perc / 100)) 338 | return taps 339 | 340 | async def create_hash(self, data_list: List[Dict[str, Any]], secret_key: str = 'super-key') -> str: 341 | params = [] 342 | for item in data_list: 343 | for key, value in item.items(): 344 | params.append(f"{key}={quote(str(value))}") 345 | 346 | time_param = f"time={math.ceil(time() / 60)}" 347 | if params: 348 | complete_str = '&'.join(params + [time_param]) 349 | else: 350 | complete_str = time_param 351 | hashed = hmac.new(secret_key.encode(), complete_str.encode(), hashlib.sha256) 352 | return hashed.hexdigest() 353 | 354 | def isValidJson(self, text: str) -> bool: 355 | try: 356 | json.loads(text) 357 | return True 358 | except ValueError: 359 | return False 360 | 361 | async def run(self, proxy: str | None) -> None: 362 | access_token_created_time = 0 363 | proxy_conn = ProxyConnector().from_url(proxy) if proxy else None 364 | 365 | async with aiohttp.ClientSession(headers=headers, connector=proxy_conn) as http_client: 366 | self.http_client = http_client 367 | if proxy: 368 | await self.check_proxy(proxy=proxy) 369 | 370 | self.authorized = False 371 | while True: 372 | if self.errors >= settings.ERRORS_BEFORE_STOP: 373 | logger.error(f"{self.session_name} | Too many errors. Bot stopped.") 374 | break 375 | try: 376 | if not self.authorized: 377 | tg_web_data = await self.get_tg_web_data(proxy=proxy) 378 | access_token = await self.login(init_data=tg_web_data) 379 | if access_token is not False: 380 | self.authorized = True 381 | self.access_token = access_token 382 | self.http_client.headers['Authorization'] = 'Bearer ' + access_token 383 | headers['Authorization'] = 'Bearer ' + access_token 384 | access_token_created_time = time() 385 | else: continue 386 | 387 | if time() - access_token_created_time >= 3600: 388 | self.authorized = False 389 | continue 390 | #refresh_success = await self.refresh_token() 391 | #if refresh_success: 392 | # self.http_client.headers['Authorization'] = 'Bearer ' + self.access_token 393 | # headers['Authorization'] = 'Bearer ' + self.access_token 394 | # access_token_created_time = time() 395 | #else: 396 | # self.authorized = False 397 | # continue 398 | 399 | profile = await self.get_profile() 400 | info = profile['info'] 401 | farm = info['farm'] 402 | boost = info.get('boost', False) 403 | if boost: boost = int(boost[1:]) 404 | system_time = profile['system_time'] 405 | self.balance = profile['balance'] 406 | day_grant_first = profile.get('day_grant_first', None) 407 | day_grant_day = profile.get('day_grant_day', None) 408 | session = profile['session'] 409 | status = session['status'] 410 | if status == 'inProgress': 411 | start_time = session['start_at'] 412 | 413 | # Log current balance 414 | logger.info(f"{self.session_name} | Balance: {self.balance}") 415 | 416 | daily_grant_awail, daily_grant_wait = await self.check_daily_grant(start_time=day_grant_first, cur_time=system_time, day=day_grant_day) 417 | if daily_grant_awail: 418 | if await self.daily_grant(): 419 | logger.success(f"{self.session_name} | Daily grant claimed.") 420 | self.errors = 0 421 | continue 422 | 423 | await asyncio.sleep(random.randint(2, 4)) 424 | await self.friends_claim() 425 | 426 | await asyncio.sleep(random.randint(2, 4)) 427 | await self.perform_tasks() 428 | 429 | # Log current balance 430 | logger.info(f"{self.session_name} | Balance: {self.balance}") 431 | 432 | if status == 'await': 433 | logger.info(f"{self.session_name} | Farm not active. Starting farming.") 434 | if await self.start_farming(): 435 | logger.success(f"{self.session_name} | Farming started successfully.") 436 | self.errors = 0 437 | continue 438 | else: 439 | time_elapsed = system_time - start_time 440 | claim_wait = (6 * 3600) - time_elapsed 441 | if claim_wait > 0: 442 | if daily_grant_wait > 0 and daily_grant_wait < claim_wait: 443 | hours = daily_grant_wait // 3600 444 | minutes = (daily_grant_wait % 3600) // 60 445 | logger.info(f"{self.session_name} | Farming active. Waiting for {hours} hours and {minutes} minutes before claiming daily grant.") 446 | await asyncio.sleep(daily_grant_wait) 447 | continue 448 | else: 449 | hours = claim_wait // 3600 450 | minutes = (claim_wait % 3600) // 60 451 | logger.info(f"{self.session_name} | Farming active. Waiting for {hours} hours and {minutes} minutes before claiming and restarting.") 452 | await asyncio.sleep(claim_wait) 453 | continue 454 | 455 | logger.info(f"{self.session_name} | Time to claim and restart farming.") 456 | taps = await self.calculate_taps(farm=farm, boost=boost) 457 | if await self.send_claim(taps=taps): 458 | logger.success(f"{self.session_name} | Claim successful.") 459 | self.errors = 0 460 | if await self.start_farming(): 461 | logger.success(f"{self.session_name} | Farming restarted successfully.") 462 | self.errors = 0 463 | 464 | # Log current balance 465 | logger.info(f"{self.session_name} | Balance: {self.balance}") 466 | 467 | except InvalidSession as error: 468 | raise error 469 | except Exception as error: 470 | logger.error(f"{self.session_name} | Unknown error: {error}" + (f"\nTraceback: {traceback.format_exc()}" if settings.DEBUG_MODE else "")) 471 | self.errors += 1 472 | await asyncio.sleep(delay=3) 473 | else: 474 | logger.info(f"Sleep 1 min") 475 | await asyncio.sleep(delay=60) 476 | 477 | async def run_claimer(tg_client: Client, proxy: str | None): 478 | try: 479 | await Claimer(tg_client=tg_client).run(proxy=proxy) 480 | except InvalidSession: 481 | logger.error(f"{tg_client.name} | Invalid Session") 482 | -------------------------------------------------------------------------------- /bot/core/headers.py: -------------------------------------------------------------------------------- 1 | headers = { 2 | 'Accept': '*/*', 3 | 'Accept-Language': 'en-US,en;q=0.9,uk-UA;q=0.8,uk;q=0.7,ru;q=0.6,zh-CN;q=0.5,zh;q=0.4', 4 | 'Connection': 'keep-alive', 5 | 'Origin': 'https://mmbump.pro', 6 | 'Referer': 'https://mmbump.pro/', 7 | 'Sec-Fetch-Dest': 'empty', 8 | 'Sec-Fetch-Mode': 'cors', 9 | 'Sec-Fetch-Site': 'same-site', 10 | 'User-Agent': 'Mozilla/5.0 (Linux; Android 14; 2210132G Build/UKQ1.230804.001; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/126.0.6478.71 Mobile Safari/537.36', 11 | 'sec-ch-ua': '"Chromium";v="126", "Google Chrome";v="126", "Not;A Brand";v="99"', 12 | 'sec-ch-ua-mobile': '?1', 13 | 'sec-ch-ua-platform': '"Android"', 14 | } 15 | -------------------------------------------------------------------------------- /bot/core/registrator.py: -------------------------------------------------------------------------------- 1 | from pyrogram import Client 2 | 3 | from bot.config import settings 4 | from bot.utils import logger 5 | 6 | 7 | async def register_sessions() -> None: 8 | API_ID = settings.API_ID 9 | API_HASH = settings.API_HASH 10 | 11 | if not API_ID or not API_HASH: 12 | raise ValueError("API_ID and API_HASH not found in the .env file.") 13 | 14 | session_name = input('\nEnter the session name (press Enter to exit): ') 15 | 16 | if not session_name: 17 | return None 18 | 19 | session = Client( 20 | name=session_name, 21 | api_id=API_ID, 22 | api_hash=API_HASH, 23 | workdir="sessions/" 24 | ) 25 | 26 | async with session: 27 | user_data = await session.get_me() 28 | 29 | logger.success(f'Session added successfully @{user_data.username} | {user_data.first_name} {user_data.last_name}') 30 | -------------------------------------------------------------------------------- /bot/exceptions/__init__.py: -------------------------------------------------------------------------------- 1 | class InvalidSession(BaseException): 2 | ... 3 | -------------------------------------------------------------------------------- /bot/utils/__init__.py: -------------------------------------------------------------------------------- 1 | from .logger import logger 2 | from . import launcher 3 | 4 | 5 | import os 6 | 7 | if not os.path.exists('sessions'): 8 | os.mkdir('sessions') 9 | -------------------------------------------------------------------------------- /bot/utils/launcher.py: -------------------------------------------------------------------------------- 1 | import os 2 | import glob 3 | import asyncio 4 | import argparse 5 | import random 6 | from itertools import cycle 7 | from pathlib import Path 8 | 9 | from pyrogram import Client 10 | from better_proxy import Proxy 11 | 12 | from bot.config import settings 13 | from bot.utils import logger 14 | from bot.core.claimer import run_claimer 15 | from bot.core.registrator import register_sessions 16 | 17 | 18 | start_text = """ 19 | 20 | 21 | ███ ███ ███ ███ ██████ ██████ ██████ ██████ ██ ██ ███ ███ ██████ 22 | ████ ████ ████ ████ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ████ ████ ██ ██ 23 | ██ ████ ██ ██ ████ ██ ██████ ██████ ██ ██ ██████ ██ ██ ██ ████ ██ ██████ 24 | ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ 25 | ██ ██ ██ ██ ██ ██ ██ ██████ ██████ ██████ ██ ██ ██ 26 | 27 | 28 | Select an action: 29 | 30 | 1. Create session 31 | 2. Run bot 32 | """ 33 | 34 | 35 | def get_session_names() -> list[str]: 36 | session_path = Path('sessions') 37 | session_files = session_path.glob('*.session') 38 | session_names = sorted([file.stem for file in session_files]) 39 | return session_names 40 | 41 | def get_proxies() -> list[Proxy]: 42 | if settings.USE_PROXY_FROM_FILE: 43 | with open(file='bot/config/proxies.txt', encoding='utf-8-sig') as file: 44 | proxies = sorted([Proxy.from_str(proxy=row.strip()).as_url for row in file if row.strip()]) 45 | else: 46 | proxies = [] 47 | 48 | return proxies 49 | 50 | async def get_tg_clients() -> list[Client]: 51 | session_names = get_session_names() 52 | 53 | if not session_names: 54 | raise FileNotFoundError("Not found session files") 55 | 56 | tg_clients = [Client( 57 | name=session_name, 58 | api_id=settings.API_ID, 59 | api_hash=settings.API_HASH, 60 | workdir='sessions/', 61 | plugins=dict(root='bot/plugins') 62 | ) for session_name in session_names] 63 | 64 | return tg_clients 65 | 66 | async def run_bot_with_delay(tg_client, proxy, delay): 67 | if delay > 0: 68 | logger.info(f"{tg_client.name} | Wait {delay} seconds before start") 69 | await asyncio.sleep(delay) 70 | await run_claimer(tg_client=tg_client, proxy=proxy) 71 | 72 | async def run_clients(tg_clients: list[Client]): 73 | proxies = get_proxies() 74 | proxies_cycle = cycle(proxies) if proxies else cycle([None]) 75 | tasks = [] 76 | delay = 0 77 | for index, tg_client in enumerate(tg_clients): 78 | if index > 0: 79 | delay = random.randint(*settings.SLEEP_BETWEEN_START) 80 | proxy = next(proxies_cycle) 81 | task = asyncio.create_task(run_bot_with_delay(tg_client=tg_client, proxy=proxy, delay=delay)) 82 | tasks.append(task) 83 | await asyncio.gather(*tasks) 84 | 85 | async def process() -> None: 86 | if not settings: 87 | logger.warning(f"Please fix the above errors in the .env file") 88 | return 89 | parser = argparse.ArgumentParser() 90 | parser.add_argument('-a', '--action', type=int, help='Action to perform') 91 | 92 | logger.info(f"Detected {len(get_session_names())} sessions | {len(get_proxies())} proxies") 93 | 94 | action = parser.parse_args().action 95 | 96 | if not action: 97 | print(start_text) 98 | 99 | while True: 100 | action = input("> ") 101 | 102 | if not action.isdigit(): 103 | logger.warning("Action must be number") 104 | elif action not in ['1', '2']: 105 | logger.warning("Action must be 1 or 2") 106 | else: 107 | action = int(action) 108 | break 109 | 110 | if action == 1: 111 | await register_sessions() 112 | elif action == 2: 113 | tg_clients = await get_tg_clients() 114 | 115 | await run_clients(tg_clients=tg_clients) -------------------------------------------------------------------------------- /bot/utils/logger.py: -------------------------------------------------------------------------------- 1 | import sys 2 | from loguru import logger 3 | 4 | 5 | logger.remove() 6 | logger.add(sink=sys.stdout, format="{time:YYYY-MM-DD HH:mm:ss}" 7 | " | {level: <8}" 8 | " | {line}" 9 | " - {message}") 10 | logger = logger.opt(colors=True) 11 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | services: 3 | bot: 4 | container_name: 'MMProBumpBot' 5 | stop_signal: SIGINT 6 | build: 7 | context: . 8 | working_dir: /app 9 | volumes: 10 | - .:/app 11 | entrypoint: "python3 main.py" 12 | command: ["-a", "2"] 13 | restart: unless-stopped 14 | env_file: .env -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | from contextlib import suppress 3 | 4 | from bot.utils.launcher import process 5 | 6 | 7 | async def main(): 8 | await process() 9 | 10 | 11 | if __name__ == '__main__': 12 | with suppress(KeyboardInterrupt): 13 | asyncio.run(main()) 14 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Alexell/MMProBumpBot/5f080ad80a49c6f1e110b6ed4bc6158a64c55891/requirements.txt --------------------------------------------------------------------------------