diff --git a/modules/start.py b/modules/start.py new file mode 100644 index 0000000..1877ae5 --- /dev/null +++ b/modules/start.py @@ -0,0 +1,34 @@ +# SPDX-License-Identifier: GPL-3.0-only +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, version 3 of the License. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +# Copyright (c) 2024, YeetCode Developers + +from pyrogram import filters +from pyrogram.client import Client +from pyrogram.handlers import MessageHandler +from pyrogram.types import Message + +from src.Module import ModuleBase + + +class Module(ModuleBase): + def on_load(self, app: Client): + app.add_handler(MessageHandler(start, filters.command("start"))) + + def on_shutdown(self, app: Client): + pass + + +async def start(app: Client, message: Message): + await message.reply("Hello!") diff --git a/poetry.lock b/poetry.lock index 02ddc94..a4b1002 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1971,6 +1971,24 @@ files = [ [package.extras] diagrams = ["jinja2", "railroad-diagrams"] +[[package]] +name = "Pyrogram" +version = "2.1.15" +description = "Elegant, modern and asynchronous Telegram MTProto API framework in Python for users and bots" +optional = false +python-versions = "~=3.8" +files = [ + {file = "dev.zip", hash = "sha256:3db754ec803f473cf5e5f959f118cd6d330b54a27bc35334795754fabdf42fa5"}, +] + +[package.dependencies] +pyaes = "1.6.1" +pysocks = "1.7.1" + +[package.source] +type = "url" +url = "https://github.com/KurimuzonAkuma/pyrogram/archive/refs/heads/dev.zip" + [[package]] name = "pysocks" version = "1.7.1" @@ -2060,20 +2078,6 @@ urllib3 = ">=1.21.1,<3" socks = ["PySocks (>=1.5.6,!=1.5.7)"] use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] -[[package]] -name = "rsa" -version = "4.9" -description = "Pure-Python RSA implementation" -optional = false -python-versions = ">=3.6,<4" -files = [ - {file = "rsa-4.9-py3-none-any.whl", hash = "sha256:90260d9058e514786967344d0ef75fa8727eed8a7d2e43ce9f4bcf1b536174f7"}, - {file = "rsa-4.9.tar.gz", hash = "sha256:e38464a49c6c85d7f1351b0126661487a7e0a14a50f1675ec50eb34d4f20ef21"}, -] - -[package.dependencies] -pyasn1 = ">=0.1.3" - [[package]] name = "selenium" version = "4.18.1" @@ -2200,22 +2204,70 @@ anyio = ">=3.4.0,<5" full = ["httpx (>=0.22.0)", "itsdangerous", "jinja2", "python-multipart (>=0.0.7)", "pyyaml"] [[package]] -name = "telethon" -version = "1.34.0" -description = "Full-featured Telegram client library for Python 3" -optional = false -python-versions = ">=3.5" -files = [ - {file = "Telethon-1.34.0.tar.gz", hash = "sha256:55290809a30081fa0bb5052abb7547cbb25d7fbca94f32f13c147504d521804f"}, +name = "tgcrypto" +version = "1.2.5" +description = "Fast and Portable Cryptography Extension Library for Pyrogram" +optional = false +python-versions = "~=3.7" +files = [ + {file = "TgCrypto-1.2.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:4507102377002966f35f2481830b7529e00c9bbff8c7d1e09634f984af801675"}, + {file = "TgCrypto-1.2.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:38fe25c0d79b41d7a89caba2a78dea0358e17ca73b033cefd16abed680685829"}, + {file = "TgCrypto-1.2.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c035bf8ef89846f67e77e82ea85c089b6ea30631b32e8ac1a6511b9be52ab065"}, + {file = "TgCrypto-1.2.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f594e2680daf20dbac6bf56862f567ddc3cc8d6a19757ed07faa8320ff7acee4"}, + {file = "TgCrypto-1.2.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8723a16076e229ffdf537fdb5e638227d10f44ca43e6939db1eab524de6eaed7"}, + {file = "TgCrypto-1.2.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:c1c8d974b8b2d7132364b6f0f6712b92bfe47ab9c5dcee25c70327ff68d22d95"}, + {file = "TgCrypto-1.2.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:89d9c143a1fcdb2562a4aa887152abbe9253e1979d7bebef2b489148e0bbe086"}, + {file = "TgCrypto-1.2.5-cp310-cp310-win32.whl", hash = "sha256:aa4bc1d11d4a90811c162abd45a5981f171679d1b5bd0322cd7ccd16447366a2"}, + {file = "TgCrypto-1.2.5-cp310-cp310-win_amd64.whl", hash = "sha256:39145103614c5e38fe938549742d355920f4a0778fa8259eb69c0c85ba4b1d28"}, + {file = "TgCrypto-1.2.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:59597cdb1c87eb1184088563d20b42a8f2e431e9334fed64926079044ad2a4af"}, + {file = "TgCrypto-1.2.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1283337ae75b02406dd700377b8b783e70033b548492517df6e6c4156b0ed69c"}, + {file = "TgCrypto-1.2.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1735437df0023a40e5fdd95e6b09ce806ec8f2cd2f8879023818840dfae60cab"}, + {file = "TgCrypto-1.2.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cfa17a20206532c6d2442c9d7a7f6434120bd75896ad9a3e9b9277477afa084f"}, + {file = "TgCrypto-1.2.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:48da3674474839e5619e7430ff1f98aed9f55369f3cfaef7f65511852869572e"}, + {file = "TgCrypto-1.2.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:b49e982e5b156be821a5235bd9102c00dc506a58607e2c8bd50ac872724a951f"}, + {file = "TgCrypto-1.2.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:9d9f13586065a6d86d05c16409054033a84be208acee29b49f6f194e27b08642"}, + {file = "TgCrypto-1.2.5-cp311-cp311-win32.whl", hash = "sha256:10dd3870aecb1a783c6eafd3b164b2149dbc93a9ee13feb7e6f5c58f87c24cd0"}, + {file = "TgCrypto-1.2.5-cp311-cp311-win_amd64.whl", hash = "sha256:a1beec47d6af8b509af7cf266e30f7703208076076594714005b42d2c25225b3"}, + {file = "TgCrypto-1.2.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c133ddc95ae9c6cd6ad742c4b8c30191214db8dc724268bee59a339e22b2028b"}, + {file = "TgCrypto-1.2.5-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f6537f6af3d80be67bd2625a0990ee88c6ae58d33bdb88d99591bd6e97ee7a0"}, + {file = "TgCrypto-1.2.5-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bdebbd9cffd10c42a2f60886dcab0272ddd38330d0cf7ccf026230b826573f59"}, + {file = "TgCrypto-1.2.5-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:45683659ec6475ee8ff60e12167ec19aacfd7527decafe446434fa1a7e6760a7"}, + {file = "TgCrypto-1.2.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:eafad246fd9aa63ff709a6e8c905c24fd7520ef96e33a2c3e1ccdb4fb2b2f331"}, + {file = "TgCrypto-1.2.5-cp37-cp37m-win32.whl", hash = "sha256:1445217d22101946d38ee7d628cdb3de92db4eb130183a22030c07d7888f21b0"}, + {file = "TgCrypto-1.2.5-cp37-cp37m-win_amd64.whl", hash = "sha256:b7e8402fe4023dc9666c0bc1b30fcf0d98a294e48d35f311a3eadfe105af04d4"}, + {file = "TgCrypto-1.2.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:56e1ec34e75fa2e3dcf7f74f1017d8e16c1eb8a8e031eaaa06c57f836e0d3bcc"}, + {file = "TgCrypto-1.2.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ca8814d6cc412775a43a021fce2d23e83a5336e9e9f38d0998d821cdf55c1d50"}, + {file = "TgCrypto-1.2.5-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:bb82e53f20ce5653573832f3c05fa525cc1769bdd685408b19f26d82d5e9001b"}, + {file = "TgCrypto-1.2.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0133936eac63cc9529b497d759b7d0ca21e491bb42481b40b603ee63bb8c10b7"}, + {file = "TgCrypto-1.2.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:76fe0a3ad838dcf300ac88233cbffc2ad63478eb0ae9fa671694e184d88ec1cd"}, + {file = "TgCrypto-1.2.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:7070c6e063befb6d04eab46e8b1ffbee47a497971be11496d23837fc007e7685"}, + {file = "TgCrypto-1.2.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:ed53d0c4a5e6f75d4b1cab17535afe210cd3120fb88f44a8c4562d43c2a3bb16"}, + {file = "TgCrypto-1.2.5-cp38-cp38-win32.whl", hash = "sha256:f7ec9f0a571fcc38fbee224943ed9918123f752ac19bae5c195d8322f5b20fab"}, + {file = "TgCrypto-1.2.5-cp38-cp38-win_amd64.whl", hash = "sha256:362ab28fc75e6b066e5bb15fb5296f75f4238d6c1cbcaaa1e5756cd5c168b74b"}, + {file = "TgCrypto-1.2.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:7885a75db09ce8bdba42d2c332085bfe314f232541a729808c7507ffa261ff9a"}, + {file = "TgCrypto-1.2.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:0d28aa317364a5c27317fe97a48267aa1c65c9aaf589909e97489ebe82a714e3"}, + {file = "TgCrypto-1.2.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:940974e19044dc65bcf7b9c5255173b896dff010142f3833047dc55d59cde21c"}, + {file = "TgCrypto-1.2.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:457c657dd10ffb4bbbb007132a0f6a7bee5080176a98c51f285fedf636b624cb"}, + {file = "TgCrypto-1.2.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:539bdc6b9239fb6a6b134591a998dc7f50d4dcc4fed861f80540682acc0c3802"}, + {file = "TgCrypto-1.2.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:4d70d5517d64ca952896b726d22c8a66594e6f6259ee2cb4fa134c02d0e8c3e0"}, + {file = "TgCrypto-1.2.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:90b6337d3ae4348ed14f89dd2ebf7011fa63d67a48c8a98d955a1e392176c60a"}, + {file = "TgCrypto-1.2.5-cp39-cp39-win32.whl", hash = "sha256:37c4b9be82716fbc6d2b123caef448eee28683888803db075d842327766f7624"}, + {file = "TgCrypto-1.2.5-cp39-cp39-win_amd64.whl", hash = "sha256:6e96b3a478fae977228c5750194c20a18cde402bbbea6593de424f84d4a8893b"}, + {file = "TgCrypto-1.2.5-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:4d93686e6254eb0a32a0a60e849b41a867a2770b27b48f978fd391dce2b83aeb"}, + {file = "TgCrypto-1.2.5-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7b7fe81fad7c64479c83f31fc10e79fb20a114bca414e5e17f5e0c8b363153f8"}, + {file = "TgCrypto-1.2.5-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ace308f5842a0d6c04fc1ae92cb4320b4b13bfc711031c1c18d9124697685ba0"}, + {file = "TgCrypto-1.2.5-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:36a570ecd12a428222b10ea8b5a8e0d83ea8750e4de1d5ea53d068b84341b450"}, + {file = "TgCrypto-1.2.5-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:ba9e067fd9751b3bbd7c979210431000e44f70001d921237e9c4672bf30f07bc"}, + {file = "TgCrypto-1.2.5-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80e2414f1a95087d7e46fb54cb387df66424c4ec156fb1a8d8e1d4aa38eb65cf"}, + {file = "TgCrypto-1.2.5-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:66c5b5bf701b5efc3e4c5d83439d767a3dd48f17a1d840eda6b4d1918844a8f9"}, + {file = "TgCrypto-1.2.5-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:5d27d6c414eb4775022b05fdd571090bfd92854115e86184ac1832060fbaa510"}, + {file = "TgCrypto-1.2.5-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:9b0a088ff2e05b6bbe891da936f62b99bd85202b2b9f4f57f71a408490dd518c"}, + {file = "TgCrypto-1.2.5-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f245895c7d518342089d15b5dca3cee9ffa5a0f3534db9d5a930f6a27dff4adf"}, + {file = "TgCrypto-1.2.5-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7dbf607d645c39a577a0f8571039d11ddd2dcdf9656465be75f9e0f540472444"}, + {file = "TgCrypto-1.2.5-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:d6b0c2dc84e632ce7b3d0b767cfe20967e557ad7d71ea5dbd7df2dd544323181"}, + {file = "TgCrypto-1.2.5.tar.gz", hash = "sha256:9bc2cac6fb9a12ef5b08f3dd500174fe374d89b660cce981f57e3138559cb682"}, ] -[package.dependencies] -pyaes = "*" -rsa = "*" - -[package.extras] -cryptg = ["cryptg"] - [[package]] name = "tinycss2" version = "1.2.1" @@ -2650,4 +2702,4 @@ cffi = ["cffi (>=1.11)"] [metadata] lock-version = "2.0" python-versions = "^3.10" -content-hash = "4d4ad9450914e71dd3b70a4fb11292368697030404d2bcb117958de75be6a44b" +content-hash = "8f0ceb869b7f500b6b15fb210f547508e83b2ebbe4dfc65b77c021fee456a4f8" diff --git a/pyproject.toml b/pyproject.toml index f93eb7e..08ae228 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -10,8 +10,9 @@ package-mode = false [tool.poetry.dependencies] python = "^3.10" g4f = {extras = ["all"], version = "^0.2.5.4"} -telethon = "^1.34.0" python-dotenv = "^1.0.1" +pyrogram = {url = "https://github.com/KurimuzonAkuma/pyrogram/archive/refs/heads/dev.zip"} +tgcrypto = "^1.2.5" [tool.poetry.group.dev.dependencies] black = "^24.3.0" diff --git a/scripts/add_license.py b/scripts/add_license.py index 4cbf27f..ec81d8b 100644 --- a/scripts/add_license.py +++ b/scripts/add_license.py @@ -41,7 +41,7 @@ def add_license_header(file_path) -> StatusIsOk: - with open(file_path, "r+") as f: + with open(file_path, "r+", encoding="utf-8") as f: content = f.read() if license_header in content: return False diff --git a/src/Bot.py b/src/Bot.py index aefb26f..b1c2da4 100644 --- a/src/Bot.py +++ b/src/Bot.py @@ -17,11 +17,13 @@ from os import getenv from dotenv import load_dotenv -from telethon import TelegramClient, events +from pyrogram.client import Client + +from .Module import load_modules def main() -> None: - load_dotenv() + load_dotenv(override=True) api_id = getenv("API_ID", 0) api_hash = getenv("API_HASH", "") @@ -30,10 +32,7 @@ def main() -> None: if not all([api_id, api_hash, bot_token]): raise ValueError("Could not get all required credentials from env!") - app = TelegramClient("app", int(api_id), api_hash).start(bot_token=bot_token) - - @app.on(events.NewMessage(incoming=True, pattern="/start")) - async def start(event): - await event.reply("Hello!") + app = Client("app", int(api_id), api_hash, bot_token=bot_token) - app.run_until_disconnected() + loaded_modules = load_modules(app) + app.run() diff --git a/src/Logging.py b/src/Logging.py new file mode 100644 index 0000000..bd2a61b --- /dev/null +++ b/src/Logging.py @@ -0,0 +1,68 @@ +# SPDX-License-Identifier: GPL-3.0-only +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, version 3 of the License. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +# Copyright (c) 2024, YeetCode Developers + +import logging +import os + +GLOBAL_DEBUG: bool = False +if os.getenv("TGBOT_DEBUG") is not None: + GLOBAL_DEBUG = True + +log_additional_args: dict = {"filename": "bot.log", "level": logging.INFO} +if GLOBAL_DEBUG: + log_additional_args.clear() + log_additional_args.update({"level": logging.DEBUG}) + + +# +# Adapted from https://stackoverflow.com/a/56944256 +# +class ColouredFormatter(logging.Formatter): + + grey = "\x1b[38;20m" + yellow = "\x1b[33;20m" + red = "\x1b[31;20m" + green = "\x1b[0;32m" + blue = "\x1b[0;34m" + bold_red = "\x1b[31;1m" + reset = "\x1b[0m" + format_str = "%(asctime)s [%(levelname)s] %(name)s: %(message)s" + + FORMATS = { + logging.DEBUG: blue + format_str + reset, + logging.INFO: green + format_str + reset, + logging.WARNING: yellow + format_str + reset, + logging.ERROR: red + format_str + reset, + logging.CRITICAL: bold_red + format_str + reset, + } + + def format(self, record: logging.LogRecord): + log_fmt = self.FORMATS.get(record.levelno) + formatter = logging.Formatter(log_fmt) + return formatter.format(record) + + +logging.basicConfig(format="%(asctime)s [%(levelname)s] %(name)s: %(message)s", **log_additional_args) + + +for handler in logging.root.handlers: + if issubclass(logging.StreamHandler, type(handler)): + logging.root.removeHandler(handler) + +_sh = logging.StreamHandler() +_sh.setFormatter(ColouredFormatter()) +logging.root.addHandler(_sh) +logging.getLogger(__name__).info("Coloured log output initialized") diff --git a/src/Module.py b/src/Module.py new file mode 100644 index 0000000..0015bf8 --- /dev/null +++ b/src/Module.py @@ -0,0 +1,62 @@ +# SPDX-License-Identifier: GPL-3.0-only +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, version 3 of the License. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +# Copyright (c) 2024, YeetCode Developers + +import atexit +import logging +from abc import ABC, abstractmethod +from importlib import import_module +from pathlib import Path + +from pyrogram.client import Client + +log: logging.Logger = logging.getLogger(__name__) + + +class ModuleBase(ABC): + @abstractmethod + def on_load(self, app: Client): + pass + + @abstractmethod + def on_shutdown(self, app: Client): + pass + + +def load_modules(app: Client) -> list[object]: + loaded_modules: list[object] = [] + + log.info("Searching for modules") + modules: list[Path] = list(Path("modules").rglob("*.py")) + log.info(f"Found {len(modules)} modules") + + for module in modules: + log.info(f"Loading module '{module}'") + + mdl = import_module(f"modules.{module.name.removesuffix('.py')}") + + if not hasattr(mdl, "Module"): + log.error(f"Module '{module}' does not have a Module class, cannot load") + continue + + if not issubclass(mdl.Module, ModuleBase): + log.warning(f"Module '{module}' does not inherit from ModuleBase class") + + mdl.Module().on_load(app) + atexit.register(mdl.Module().on_shutdown, app) + + loaded_modules.append(mdl) + + return loaded_modules diff --git a/src/__main__.py b/src/__main__.py index 8443201..7053337 100644 --- a/src/__main__.py +++ b/src/__main__.py @@ -14,6 +14,7 @@ # # Copyright (c) 2024, YeetCode Developers +from . import Logging from .Bot import main if __name__ == "__main__":