diff --git a/docker-compose-release.yaml b/docker-compose-release.yaml index 29fcbc0..745d972 100644 --- a/docker-compose-release.yaml +++ b/docker-compose-release.yaml @@ -7,6 +7,8 @@ services: - release.env volumes: - database:/var/lib/postgresql/data + networks: + - traefik redis: image: 'bitnami/redis:latest' restart: unless-stopped @@ -16,11 +18,21 @@ services: - redis-db:/bitnami/redis/data env_file: - release.env + networks: + - traefik olgram: image: ghcr.io/civsocit/olgram/bot:stable restart: unless-stopped + networks: + - traefik labels: - 'com.centurylinklabs.watchtower.enable="true"' + - "traefik.enable=true" + - "traefik.http.routers.static.rule=Host(`test.civsoc.it`)" + - "traefik.http.routers.static.tls=true" + - "traefik.http.routers.static.tls.certresolver=le" + - "traefik.http.routers.static.entrypoints=websecure" + - "traefik.docker.network=traefik" env_file: - release.env depends_on: @@ -32,8 +44,40 @@ services: - /var/run/docker.sock:/var/run/docker.sock - ./config.json:/config.json command: --interval 30 + networks: + - traefik + + traefik: + image: traefik:v2.4 + container_name: olgram_traefik + restart: unless-stopped + ports: + - "80:80" + - "443:443" + networks: + - traefik + volumes: + - /etc/localtime:/etc/localtime:ro + - /var/run/docker.sock:/var/run/docker.sock:ro + - ./acme:/acme + command: + - --entrypoints.web.address=:80 + - --entrypoints.websecure.address=:443 + - --providers.docker=true + - --providers.docker.endpoint=unix:///var/run/docker.sock + - --providers.docker.exposedByDefault=false + - --providers.docker.network=traefik + - --certificatesresolvers.le.acme.email=youmustfly@civsoc.it + - --certificatesresolvers.le.acme.storage=/acme/acme.json + - --certificatesresolvers.le.acme.tlschallenge=false + - --certificatesresolvers.le.acme.httpchallenge=true + - --certificatesresolvers.le.acme.httpchallenge.entrypoint=web volumes: database: redis-db: + +networks: + traefik: + driver: bridge diff --git a/docker-entrypoint.sh b/docker-entrypoint.sh index c3031b0..c06e805 100755 --- a/docker-entrypoint.sh +++ b/docker-entrypoint.sh @@ -1,5 +1,6 @@ #!/bin/sh set -e +sleep 20 aerich upgrade python main.py \ No newline at end of file diff --git a/main.py b/main.py index 0c26372..588d934 100644 --- a/main.py +++ b/main.py @@ -9,6 +9,8 @@ import olgram.commands.start # noqa: F401 import olgram.commands.menu # noqa: F401 import olgram.commands.bot_actions # noqa: F401 +from server.server import main as server_main + async def init_database(): await Tortoise.init(config=TORTOISE_ORM) @@ -22,6 +24,7 @@ def main(): loop.run_until_complete(init_database()) loop.create_task(dp.start_polling()) + loop.create_task(server_main().start()) loop.run_forever() diff --git a/olgram/migrations/models/1_20210909220512_update.sql b/olgram/migrations/models/1_20210909220512_update.sql new file mode 100644 index 0000000..646bf76 --- /dev/null +++ b/olgram/migrations/models/1_20210909220512_update.sql @@ -0,0 +1,5 @@ +-- upgrade -- +CREATE EXTENSION IF NOT EXISTS "uuid-ossp"; +ALTER TABLE "bot" ADD "code" UUID NOT NULL DEFAULT uuid_generate_v4(); +-- downgrade -- +ALTER TABLE "bot" DROP COLUMN "code"; diff --git a/olgram/models/models.py b/olgram/models/models.py index 4461459..735196e 100644 --- a/olgram/models/models.py +++ b/olgram/models/models.py @@ -1,6 +1,6 @@ from tortoise.models import Model from tortoise import fields - +from uuid import uuid4 from textwrap import dedent @@ -9,6 +9,7 @@ class Bot(Model): token = fields.CharField(max_length=50, unique=True) owner = fields.ForeignKeyField("models.User", related_name="bots") name = fields.CharField(max_length=33) + code = fields.UUIDField(default=uuid4, index=True) start_text = fields.TextField(default=dedent(""" Здравствуйте! Напишите ваш вопрос и мы ответим вам в ближайшее время. diff --git a/olgram/settings.py b/olgram/settings.py index c2e4b4e..4325a5c 100644 --- a/olgram/settings.py +++ b/olgram/settings.py @@ -38,6 +38,14 @@ class ServerSettings(AbstractSettings): def hook_port(cls) -> int: return int(cls._get_env("WEBHOOK_PORT")) + @classmethod + def app_host(cls) -> str: + return "olgram" + + @classmethod + def app_port(cls) -> int: + return 80 + class BotSettings(AbstractSettings): @classmethod diff --git a/server/server.py b/server/server.py index ad128dc..771c605 100644 --- a/server/server.py +++ b/server/server.py @@ -1,26 +1,31 @@ -from aiogram import Bot -import hashlib +from aiogram import Bot as AioBot, Dispatcher +from aiogram.dispatcher.webhook import SendMessage, WebhookRequestHandler +from olgram.models.models import Bot +from aiohttp import web +import asyncio +import aiohttp + from olgram.settings import ServerSettings -def path_for_bot(token: str) -> str: - return "/" + hashlib.md5(token.encode("UTF-8")).hexdigest() +def path_for_bot(bot: Bot) -> str: + return "/" + bot.code -def url_for_bot(token: str) -> str: - return f"https://{ServerSettings.hook_host()}:{ServerSettings.hook_port()}" + path_for_bot(token) +def url_for_bot(bot: Bot) -> str: + return f"https://{ServerSettings.hook_host()}:{ServerSettings.hook_port()}" + path_for_bot(bot) -async def register_token(token: str) -> bool: +async def register_token(bot: Bot) -> bool: """ Зарегистрировать токен - :param token: токен + :param bot: Бот :return: получилось ли """ - bot = Bot(token) - res = await bot.set_webhook(url_for_bot(token)) - await bot.session.close() + a_bot = AioBot(bot.token) + res = await a_bot.set_webhook(url_for_bot(bot)) + await a_bot.session.close() return res @@ -30,5 +35,42 @@ async def unregister_token(token: str): :param token: токен :return: """ - bot = Bot(token) + bot = AioBot(token) await bot.delete_webhook() + + +async def cmd_start(message, *args, **kwargs): + return SendMessage(chat_id=message.chat.id, text=f'Hi from webhook, bot {message.via_bot}', + reply_to_message_id=message.message_id) + + +class CustomRequestHandler(WebhookRequestHandler): + def get_dispatcher(self): + """ + Get Dispatcher instance from environment + + :return: :class:`aiogram.Dispatcher` + """ + key = self.request.url.path[1:] + + bot = await Bot.filter(code=key).first() + if not bot: + return None + + dp = Dispatcher(AioBot(bot.token)) + + dp.register_message_handler(cmd_start, commands=['start']) + + return dp + + +def main(): + loop = asyncio.get_event_loop() + + app = web.Application() + app.router.add_route('*', r"/{name}", CustomRequestHandler, name='webhook_handler') + + runner = aiohttp.web.AppRunner(app) + loop.run_until_complete(runner.setup()) + site = aiohttp.web.TCPSite(runner, host=ServerSettings.app_host(), port=ServerSettings.app_port()) + return site