commit 52ca3999adf90127f7e6e274e1c5f705dfa4424b Author: profitroll Date: Sat Apr 1 14:25:41 2023 +0200 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5557dd1 --- /dev/null +++ b/.gitignore @@ -0,0 +1,158 @@ +# ---> Python +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# poetry +# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control +#poetry.lock + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# PyCharm +# JetBrains specific template is maintainted in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# and can be added to the global gitignore or merged into this file. For a more nuclear +# option (not recommended) you can uncomment the following to ignore the entire idea folder. +#.idea/ + +# Custom +config.json +*.session +*.session-journal \ No newline at end of file diff --git a/.vscode/PyrogramBotBase_linux.code-workspace b/.vscode/PyrogramBotBase_linux.code-workspace new file mode 100644 index 0000000..db94971 --- /dev/null +++ b/.vscode/PyrogramBotBase_linux.code-workspace @@ -0,0 +1,11 @@ +{ + "folders": [ + { + "path": ".." + } + ], + "settings": { + "editor.formatOnSave": true, + "editor.defaultFormatter": "ms-python.black-formatter" + } +} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..4b74074 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,4 @@ +{ + "python.analysis.typeCheckingMode": "basic", + "python.formatting.provider": "black" +} \ No newline at end of file diff --git a/config_example.json b/config_example.json new file mode 100644 index 0000000..bd95ff1 --- /dev/null +++ b/config_example.json @@ -0,0 +1,12 @@ +{ + "bot": { + "api_id": 0, + "api_hash": "", + "bot_token": "", + "workers": 1 + }, + "disabled_plugins": [], + "reports": { + "chat_id": 0 + } +} \ No newline at end of file diff --git a/main.py b/main.py new file mode 100644 index 0000000..e1563d8 --- /dev/null +++ b/main.py @@ -0,0 +1,42 @@ +import asyncio +import logging +from os import getpid + +from pyrogram.sync import idle + +from modules.app import PyroClient + +logging.basicConfig( + level=logging.INFO, + format="%(name)s.%(funcName)s | %(levelname)s | %(message)s", + datefmt="[%X]", +) + +logger = logging.getLogger(__name__) + +try: + import uvloop + + uvloop.install() +except ImportError: + pass + + +async def main(): + client = PyroClient() + + try: + await client.start() + await idle() + except KeyboardInterrupt: + logger.warning(f"Forcefully shutting down with PID {getpid()}...") + finally: + await client.stop() + + +if __name__ == "__main__": + event_policy = asyncio.get_event_loop_policy() + event_loop = event_policy.new_event_loop() + asyncio.set_event_loop(event_loop) + event_loop.run_until_complete(main()) + event_loop.close() diff --git a/modules/app.py b/modules/app.py new file mode 100644 index 0000000..7765981 --- /dev/null +++ b/modules/app.py @@ -0,0 +1,60 @@ +import logging +from os import getpid +from time import time + +import pyrogram +from pyrogram.client import Client +from pyrogram.errors import BadRequest +from pyrogram.raw.all import layer +from ujson import loads + +from modules.utils import config_get + +logger = logging.getLogger(__name__) + + +class PyroClient(Client): + def __init__(self): + with open("config.json", "r", encoding="utf-8") as f: + config = loads(f.read()) + super().__init__( + name="bot_client", + api_id=config["bot"]["api_id"], + api_hash=config["bot"]["api_hash"], + bot_token=config["bot"]["bot_token"], + workers=config["bot"]["workers"], + plugins=dict(root="plugins", exclude=config["disabled_plugins"]), + sleep_threshold=120, + ) + + async def start(self): + await super().start() + + self.start_time = time() + + logger.info( + "Bot is running with Pyrogram v%s (Layer %s) and has started as @%s on PID %s.", + pyrogram.__version__, + layer, + self.me.username, + getpid(), + ) + + try: + await self.send_message( + chat_id=await config_get("chat_id", "reports"), + text=f"Bot started PID `{getpid()}`", + ) + except BadRequest: + logger.warning("Unable to send message to report chat.") + + async def stop(self): + try: + await self.send_message( + chat_id=await config_get("chat_id", "reports"), + text=f"Bot stopped with PID `{getpid()}`", + ) + except BadRequest: + logger.warning("Unable to send message to report chat.") + await super().stop() + logger.warning(f"Bot stopped with PID {getpid()}.") diff --git a/modules/utils.py b/modules/utils.py new file mode 100644 index 0000000..5739e27 --- /dev/null +++ b/modules/utils.py @@ -0,0 +1,35 @@ +from typing import Any +import aiofiles +from ujson import loads, dumps + + +async def json_read(path: str) -> Any: + async with aiofiles.open(path, mode="r", encoding="utf-8") as f: + data = await f.read() + return loads(data) + + +async def json_write(data: Any, path: str) -> None: + async with aiofiles.open(path, mode="w", encoding="utf-8") as f: + await f.write(dumps(data, ensure_ascii=False, escape_forward_slashes=False)) + + +async def config_get(key: str, *path: str) -> Any: + this_key = await json_read("config.json") + for dict_key in path: + this_key = this_key[dict_key] + return this_key[key] + + +async def config_set(key: str, value: Any, *path: str) -> None: + this_dict = await json_read("config.json") + string = "this_dict" + for arg in path: + string += f'["{arg}"]' + if type(value) in [str]: + string += f'["{key}"] = "{value}"' + else: + string += f'["{key}"] = {value}' + exec(string) + await json_write(this_dict, "config.json") + return diff --git a/plugins/handler.py b/plugins/handler.py new file mode 100644 index 0000000..604e36c --- /dev/null +++ b/plugins/handler.py @@ -0,0 +1,8 @@ +from pyrogram.client import Client +from pyrogram.types import Message +from pyrogram import filters + + +@Client.on_message(filters.text & filters.private) +async def echo_reversed(app: Client, message: Message): + await message.reply(message.text[::-1]) diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..3542fb0 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,6 @@ +apscheduler~=3.10.1 +pyrogram~=2.0.102 +aiofiles~=23.1.0 +tgcrypto==1.2.5 +uvloop==0.17.0 +ujson==5.7.0 \ No newline at end of file