diff --git a/.gitignore b/.gitignore index 8c374a4..1eadbb2 100644 --- a/.gitignore +++ b/.gitignore @@ -7,4 +7,6 @@ update.json .vscode .idea venv -*.rdb \ No newline at end of file +*.rdb +.venv +poetry.lock diff --git a/CHANGELOG.md b/CHANGELOG.md index a71cec4..277eab8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,11 @@ +## [0.2.0] + +- реорганизация кодовой базы +- удаление зависимости от aiogram +- удаление остатков зависимостей от vercel +- переход на poetry + + ## [0.0.12] - множество исправлений в роутинге сообщений diff --git a/Dockerfile b/Dockerfile index 869cc74..69611f6 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,6 +1,14 @@ -FROM python:3.11-slim +FROM python:slim + WORKDIR /app -COPY requirements.txt . -RUN pip install -r requirements.txt -COPY . . -CMD ["python", "bot/main.py"] \ No newline at end of file +COPY . /app + +RUN apt-get update && \ + apt-get install -y git curl && \ + curl -sSL https://install.python-poetry.org | python - && \ + echo "export PATH=$PATH:/root/.local/bin" >> ~/.bashrc && \ + . ~/.bashrc && \ + poetry config virtualenvs.create false && \ + poetry install --no-dev + +CMD python main.py diff --git a/README.md b/README.md index 9f03157..4f877b4 100644 --- a/README.md +++ b/README.md @@ -2,14 +2,22 @@ Для переиспользования этого кода в требуется -1. Cоздать аккаунт бота с помощью @BotFather +1. Cоздать аккаунт бота с помощью `@BotFather` -2. Аккаунт и деплой кода на vercel.com +2. Назначить бота администратором группы в его профиле, имея права назначать администраторов в группе -3. Назначить бота администратором группы в его профиле, имея права назначать администраторов в группе +3. Настроить переменные среды: -4. Настроить переменные среды: + - `BOT_TOKEN` - токен бота созданный с помощью @BotFather + - `FEEDBACK_CHAT_ID` - айди чата для обратной связи + - `REDIS_URL` - - BOT_TOKEN - токен бота созданный с помощью @BotFather - - FEEDBACK_CHAT_ID - айди чата для обратной связи - - REDIS_URL + ### Локальная разработка + + ```sh + mkdir .venv + python3.12 -m venv .venv + poetry env use .venv/bin/python3.12 + poetry update + poetry run python main.py + ``` diff --git a/api/webhook.py b/api/webhook.py deleted file mode 100644 index 05387ec..0000000 --- a/api/webhook.py +++ /dev/null @@ -1,62 +0,0 @@ -from bot.handlers.routing import handle_routing -from bot.handlers.callback_vouch import handle_button -from bot.handlers.callback_unlink import handle_unlink -from bot.handlers.handle_startup import handle_startup -from bot.handlers.handle_join_request import handle_join_request -from bot.api import register_webhook, send_message -from bot.config import FEEDBACK_CHAT_ID, WEBHOOK -from bot.state import State -from sanic.app import Sanic -from sanic.response import text -import logging - -logger = logging.getLogger(__name__) -logging.basicConfig(level=logging.INFO) - - -app = Sanic(name="welcomecenter") -app.config.LOGGING = True -app.config.REGISTERED = False -state = State() - -@app.get("/") -async def register(req): - if not app.config.REGISTERED: - logger.info(register_webhook()) - app.config.REGISTERED = True - await handle_startup() - return text("ok") - - -@app.post("/") -async def handle(req): - logger.debug(req) - try: - update = req.json - logger.debug(update) - - # видимые сообщения - msg = update.get("message", update.get("edited_message")) - if msg: - msg["edit"] = "edited_message" in update - await handle_routing(msg) - - # кнопки - elif "callback_query" in update: - data = update["callback_query"]["data"] - if data.startswith("vouch"): - await handle_button(update["callback_query"]) - elif data.startswith("unlink"): - await handle_unlink(update["callback_query"]) - - # заявки - elif "chat_join_request" in update: - logger.info("chat join request") - await handle_join_request(update["chat_join_request"]) - - except Exception: - import traceback - await send_message(FEEDBACK_CHAT_ID, f"
\n{traceback.format_exc()}\n") - traceback.print_exc() - logger.error(traceback.format_exc()) - return text("ok") diff --git a/bot/api.py b/bot/api.py index 13ddabc..8b65fb1 100644 --- a/bot/api.py +++ b/bot/api.py @@ -1,6 +1,6 @@ import aiohttp import json -from config import BOT_TOKEN, WEBHOOK +from bot.config import BOT_TOKEN, WEBHOOK import logging # Create a logger instance diff --git a/bot/handlers/callback_unlink.py b/handlers/callback_unlink.py similarity index 97% rename from bot/handlers/callback_unlink.py rename to handlers/callback_unlink.py index 8326ada..18b9b49 100644 --- a/bot/handlers/callback_unlink.py +++ b/handlers/callback_unlink.py @@ -1,9 +1,10 @@ -from api import send_message, delete_message, kick_member +from bot.api import send_message, delete_message, kick_member from handlers.command_my import handle_command_my from handlers.callback_vouch import update_button from utils.mention import userdata_extract from storage import Profile import logging + logger = logging.getLogger(__name__) logging.basicConfig(level=logging.INFO) diff --git a/bot/handlers/callback_vouch.py b/handlers/callback_vouch.py similarity index 97% rename from bot/handlers/callback_vouch.py rename to handlers/callback_vouch.py index 5dabf39..5fab9d2 100644 --- a/bot/handlers/callback_vouch.py +++ b/handlers/callback_vouch.py @@ -1,6 +1,7 @@ -from api import approve_chat_join_request, edit_replymarkup +from bot.api import approve_chat_join_request, edit_replymarkup from storage import Profile, storage import logging + logger = logging.getLogger(__name__) logging.basicConfig(level=logging.INFO) diff --git a/bot/handlers/command_ask.py b/handlers/command_ask.py similarity index 95% rename from bot/handlers/command_ask.py rename to handlers/command_ask.py index 1614236..544a322 100644 --- a/bot/handlers/command_ask.py +++ b/handlers/command_ask.py @@ -1,6 +1,6 @@ from storage import Profile from handlers.send_button import show_request_msg -from api import get_member +from bot.api import get_member import logging logger = logging.getLogger(__name__) logging.basicConfig(level=logging.INFO) diff --git a/bot/handlers/command_graph.py b/handlers/command_graph.py similarity index 92% rename from bot/handlers/command_graph.py rename to handlers/command_graph.py index 5b6809f..50caab1 100644 --- a/bot/handlers/command_graph.py +++ b/handlers/command_graph.py @@ -1,5 +1,5 @@ from utils.graph import generate_chart -from api import send_document +from bot.api import send_document from storage import storage, scan import json diff --git a/bot/handlers/command_my.py b/handlers/command_my.py similarity index 97% rename from bot/handlers/command_my.py rename to handlers/command_my.py index 94cceb3..cff976c 100644 --- a/bot/handlers/command_my.py +++ b/handlers/command_my.py @@ -1,5 +1,5 @@ from storage import Profile, scan -from api import get_member, send_message +from bot.api import get_member, send_message from utils.mention import userdata_extract import json import logging diff --git a/bot/handlers/handle_default.py b/handlers/handle_default.py similarity index 95% rename from bot/handlers/handle_default.py rename to handlers/handle_default.py index 4c4f757..1f8e565 100644 --- a/bot/handlers/handle_default.py +++ b/handlers/handle_default.py @@ -1,4 +1,4 @@ -from api import send_message, delete_message, get_chat_administrators +from bot.api import send_message, delete_message, get_chat_administrators from handlers.command_my import handle_command_my from storage import Profile, storage from handlers.send_button import show_request_msg diff --git a/bot/handlers/handle_feedback.py b/handlers/handle_feedback.py similarity index 97% rename from bot/handlers/handle_feedback.py rename to handlers/handle_feedback.py index 1050f82..e8207e4 100644 --- a/bot/handlers/handle_feedback.py +++ b/handlers/handle_feedback.py @@ -1,13 +1,15 @@ import json -from api import ( +from bot.api import ( send_message, forward_message, get_chat_administrators, ) from storage import storage -from config import FEEDBACK_CHAT_ID +from bot.config import FEEDBACK_CHAT_ID import logging + + logger = logging.getLogger(__name__) logging.basicConfig(level=logging.INFO) diff --git a/bot/handlers/handle_join_request.py b/handlers/handle_join_request.py similarity index 92% rename from bot/handlers/handle_join_request.py rename to handlers/handle_join_request.py index 69a273f..f11aa2e 100644 --- a/bot/handlers/handle_join_request.py +++ b/handlers/handle_join_request.py @@ -1,4 +1,4 @@ -from api import approve_chat_join_request, delete_message +from bot.api import approve_chat_join_request, delete_message from handlers.send_button import show_request_msg from storage import Profile, storage diff --git a/bot/handlers/handle_members_change.py b/handlers/handle_members_change.py similarity index 95% rename from bot/handlers/handle_members_change.py rename to handlers/handle_members_change.py index c74f93d..d1e3ecb 100644 --- a/bot/handlers/handle_members_change.py +++ b/handlers/handle_members_change.py @@ -1,7 +1,7 @@ from handlers.send_button import show_request_msg -from api import delete_message +from bot.api import delete_message from storage import Profile, storage -from config import FEEDBACK_CHAT_ID +from bot.config import FEEDBACK_CHAT_ID import logging logger = logging.getLogger(__name__) diff --git a/bot/handlers/handle_startup.py b/handlers/handle_startup.py similarity index 94% rename from bot/handlers/handle_startup.py rename to handlers/handle_startup.py index 6d2d261..e4766f6 100644 --- a/bot/handlers/handle_startup.py +++ b/handlers/handle_startup.py @@ -1,10 +1,11 @@ -from config import FEEDBACK_CHAT_ID +from bot.config import FEEDBACK_CHAT_ID from storage import scan, Profile -from api import approve_chat_join_request, kick_member, send_message +from bot.api import approve_chat_join_request, kick_member, send_message from handlers.callback_vouch import update_button from utils.mention import userdata_extract import logging + logger = logging.getLogger(__name__) logging.basicConfig(level=logging.INFO) diff --git a/bot/handlers/routing.py b/handlers/routing.py similarity index 97% rename from bot/handlers/routing.py rename to handlers/routing.py index 62e23a3..54be943 100644 --- a/bot/handlers/routing.py +++ b/handlers/routing.py @@ -3,7 +3,7 @@ from handlers.handle_default import handle_default from handlers.command_my import handle_command_my from handlers.command_graph import handle_command_graph from handlers.command_ask import handle_command_ask -from config import FEEDBACK_CHAT_ID +from bot.config import FEEDBACK_CHAT_ID import logging logger = logging.getLogger(__name__) diff --git a/bot/handlers/send_button.py b/handlers/send_button.py similarity index 96% rename from bot/handlers/send_button.py rename to handlers/send_button.py index 2a3536f..52ac60f 100644 --- a/bot/handlers/send_button.py +++ b/handlers/send_button.py @@ -1,7 +1,10 @@ -from api import send_message, send_photo, get_userphotos, delete_message +from bot.api import send_message, send_photo, get_userphotos, delete_message from utils.mention import mention, userdata_extract from storage import storage + import logging + + logger = logging.getLogger(__name__) logging.basicConfig(level=logging.INFO) diff --git a/bot/main.py b/main.py similarity index 65% rename from bot/main.py rename to main.py index 97c7fa9..73733eb 100644 --- a/bot/main.py +++ b/main.py @@ -1,38 +1,30 @@ import asyncio import logging +import signal import sys -import signal # Import the signal module -from aiogram import Bot, Dispatcher, Router -from aiogram.enums import ParseMode -from aiogram.filters import CommandStart -from aiogram.types import Message, ChatJoinRequest, CallbackQuery, ChatMemberUpdated -from aiogram.enums import ChatMemberStatus -from config import BOT_TOKEN, FEEDBACK_CHAT_ID +from aiohttp import web +from bot.api import send_message from handlers.routing import handle_routing from handlers.callback_unlink import handle_unlink from handlers.callback_vouch import handle_button from handlers.handle_join_request import handle_join_request from handlers.handle_startup import handle_startup from handlers.handle_members_change import handle_join, handle_left -from state import State -from storage import Profile +from storage.profile import Profile +from bot.state import State +from bot.config import FEEDBACK_CHAT_ID logger = logging.getLogger(__name__) logging.basicConfig(level=logging.INFO) -router = Router() -bot = Bot(BOT_TOKEN, parse_mode=ParseMode.HTML) -dp = Dispatcher() state = State() -@router.message(CommandStart()) -async def command_start_handler(message: Message) -> None: +async def command_start_handler(message): caption = "Напишите своё сообщение для нас" if message.from_user.llanguage_code == 'ru' else "Write your message for us" await message.answer(caption) -@router.callback_query() -async def process_callback(callback_query: CallbackQuery): +async def process_callback(callback_query): cbq = vars(callback_query) try: cbq["from"] = vars(callback_query.from_user) @@ -47,11 +39,10 @@ async def process_callback(callback_query: CallbackQuery): logger.debug(cbq) import traceback text = traceback.format_exc() - await bot.send_message(FEEDBACK_CHAT_ID, text) + await send_message(FEEDBACK_CHAT_ID, text) -@router.chat_join_request() -async def join_request_handler(update: ChatJoinRequest) -> None: +async def join_request_handler(update): print("chat join request") join_request = vars(update) try: @@ -63,11 +54,10 @@ async def join_request_handler(update: ChatJoinRequest) -> None: logger.debug(join_request) import traceback text = traceback.format_exc() - await bot.send_message(FEEDBACK_CHAT_ID, text) + await send_message(FEEDBACK_CHAT_ID, text) -@router.message() -async def all_handler(message: Message) -> None: +async def all_handler(message): msg = vars(message) try: msg["from"] = vars(message.from_user) @@ -81,11 +71,10 @@ async def all_handler(message: Message) -> None: logger.debug(msg) import traceback text = traceback.format_exc() - await bot.send_message(FEEDBACK_CHAT_ID, text) + await send_message(FEEDBACK_CHAT_ID, text) -@router.my_chat_member() -async def chat_members_change(update: ChatMemberUpdated): +async def chat_members_change(update): member_updated = vars(update) try: member_updated["chat"] = vars(update.chat) @@ -100,33 +89,38 @@ async def chat_members_change(update: ChatMemberUpdated): await handle_join(member_updated) else: logger.info("unhandled members update") - + except Exception as e: logger.error(f"[main.my_chat_member] ERROR {e}") logger.debug(member_updated) import traceback text = traceback.format_exc() - await bot.send_message(FEEDBACK_CHAT_ID, text) + await send_message(FEEDBACK_CHAT_ID, text) -async def main() -> None: - # connect router - dp.include_router(router) +async def main(request): # storage revalidation await handle_startup() - # Start event dispatching - await dp.start_polling(bot) + + # Получение обновлений + updates = await request.json() + + for update in updates["result"]: + if "message" in update: + await all_handler(update["message"]) + elif "callback_query" in update: + await process_callback(update["callback_query"]) + elif "join_chat_request" in update: + await join_request_handler(update["join_chat_request"]) + elif "my_chat_member" in update: + await chat_members_change(update["my_chat_member"]) + + return web.Response() if __name__ == "__main__": logging.basicConfig(level=logging.INFO, stream=sys.stdout) - # Define a function to handle SIGTERM - def handle_sigterm(signum, frame): - logger.info("Received SIGTERM. Shutting down gracefully...") - asyncio.get_event_loop().stop() - - # Register the SIGTERM signal handler - signal.signal(signal.SIGTERM, handle_sigterm) - - asyncio.get_event_loop().run_until_complete(main()) + app = web.Application() + app.router.add_post('/your_bot_token', main) # Замените 'your_bot_token' на реальный токен вашего бота + web.run_app(app, host='0.0.0.0', port=3000) # Выберите подходящий порт diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..e44288c --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,86 @@ +[tool.poetry] +name = "welcomecenterbot" +version = "0.2.0" +description = "telegram group helper" +authors = ["rainbowdev circle"] +license = "Open Source" +readme = "README.md" + +[tool.poetry.dependencies] +python = "^3.12" +aiohttp = "^3.9.1" +redis = "^5.0.1" + +[tool.poetry.group.dev.dependencies] +setuptools = "^69.0.2" +mypy = "^1.6.1" +black = "^23.10.1" +ruff = "^0.1.2" +isort = "^5.12.0" + +[tool.black] +line-length = 120 +target-version = ['py312'] +include = '\.pyi?$' +exclude = ''' +( + /( + \.eggs + | \.git + | \.hg + | \.mypy_cache + | \.tox + | \.venv + | _build + | buck-out + | build + | dist + )/ + | foo.py +) +''' + +[tool.isort] +multi_line_output = 3 +include_trailing_comma = true +force_grid_wrap = 0 +use_parentheses = true +ensure_newline_before_comments = true +line_length = 120 + +[tool.ruff] +select = ["E4", "E7", "E9", "F"] +ignore = [] +line-length = 120 +target-version = "py312" + +[tool.pyright] +venvPath = "." +venv = ".venv" +include = ["bot/."] +useLibraryCodeForTypes = false +disableLanguageServices = false +disableOrganizeImports = false +reportMissingImports = true +reportMissingModuleSource = "warning" +reportImportCycles = "warning" +maxMemoryForLargeFile = 4096 +pythonVersion = "3.12" +autoImportCompletions = true +useVirtualEnv = true +typeCheckingMode = "basic" +disableJediCompletion = true +disableCompletion = false +disableSnippetCompletion = false +disableGoToDefinition = false +disableRenaming = false +disableSignatureHelp = false +diagnostics = true +logLevel = "debug" +pluginSearchPaths = [] +typings = {} +mergeTypeStubPackages = false + +[build-system] +requires = ["poetry-core"] +build-backend = "poetry.core.masonry.api" diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index b4cabe7..0000000 --- a/requirements.txt +++ /dev/null @@ -1,5 +0,0 @@ -# sanic==19.6.0 -redis -asyncio -aiohttp -aiogram \ No newline at end of file diff --git a/bot/storage/__init__.py b/storage/__init__.py similarity index 97% rename from bot/storage/__init__.py rename to storage/__init__.py index 71fcc12..74d8e23 100644 --- a/bot/storage/__init__.py +++ b/storage/__init__.py @@ -1,6 +1,6 @@ from redis import Redis from storage.profile import Profile as ProfileObj -from config import REDIS_URL +from bot.config import REDIS_URL import json diff --git a/bot/storage/profile.py b/storage/profile.py similarity index 100% rename from bot/storage/profile.py rename to storage/profile.py diff --git a/bot/utils/graph.py b/utils/graph.py similarity index 100% rename from bot/utils/graph.py rename to utils/graph.py diff --git a/bot/utils/mention.py b/utils/mention.py similarity index 100% rename from bot/utils/mention.py rename to utils/mention.py diff --git a/vercel.json b/vercel.json deleted file mode 100755 index e2e757a..0000000 --- a/vercel.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "version": 2, - "functions": { - "api/webhook.py": { - "memory": 1024, - "maxDuration": 10 - } - }, - "routes": [ - { - "src": "/", - "dest": "/api/webhook.py" - } - ] -}