diff --git a/back/api/settings.py b/back/api/settings.py index b327f84..4ad1185 100644 --- a/back/api/settings.py +++ b/back/api/settings.py @@ -27,14 +27,16 @@ SECRET_KEY = "django-insecure-ruo!wst&sb8(f9)j5u4rda-w!673lj_-c0a%gx_t@)ff*q*2ze # SECURITY WARNING: don't run with debug turned on in production! DEBUG = True - +NGROK_TEMP = '357e-193-228-134-167.ngrok-free.app' ALLOWED_HOSTS = [ - '192.168.103.159', - 'back', + "localhost", + NGROK_TEMP, + "192.168.103.159", + "back", ] CORS_ALLOWED_ORIGINS = [ - 'http://localhost:3000', - 'http://192.168.103.159:3000' + "http://localhost:3000", + "http://192.168.103.159:3000", ] # Application definition @@ -43,6 +45,7 @@ INSTALLED_APPS = [ "corsheaders", "rest_framework", "inventory", + "tgbot", "django.contrib.admin", "django.contrib.auth", "django.contrib.contenttypes", @@ -177,3 +180,9 @@ LOGGING = { } ODATA_AUTH = os.environ.get("ODATA_AUTH") + +TGBOT = { + "token": os.environ.get("TG_TOKEN"), + "base_url": NGROK_TEMP, + "webhook": "webhook", +} diff --git a/back/api/urls.py b/back/api/urls.py index 906debf..7a2882a 100644 --- a/back/api/urls.py +++ b/back/api/urls.py @@ -19,13 +19,16 @@ from django.urls import include, path from rest_framework import routers from inventory import views +from tgbot import views as tgbot_views router = routers.DefaultRouter() router.register(r'api/partner', views.PartnerViewSet) router.register(r'api/element', views.ElementViewSet) router.register(r'api/inventory', views.InventoryItemViewSet) +router.register(r'api/tgbot', tgbot_views.ItemViewSet) urlpatterns = [ path('', include(router.urls)), + path('admin/', admin.site.urls), ] diff --git a/back/poetry.lock b/back/poetry.lock index 7ab348e..320a7e5 100644 --- a/back/poetry.lock +++ b/back/poetry.lock @@ -1,5 +1,27 @@ # This file is automatically @generated by Poetry 1.8.2 and should not be changed by hand. +[[package]] +name = "anyio" +version = "4.4.0" +description = "High level compatibility layer for multiple asynchronous event loop implementations" +optional = false +python-versions = ">=3.8" +files = [ + {file = "anyio-4.4.0-py3-none-any.whl", hash = "sha256:c1b2d8f46a8a812513012e1107cb0e68c17159a7a594208005a57dc776e1bdc7"}, + {file = "anyio-4.4.0.tar.gz", hash = "sha256:5aadc6a1bbb7cdb0bede386cac5e2940f5e2ff3aa20277e991cf028e0585ce94"}, +] + +[package.dependencies] +exceptiongroup = {version = ">=1.0.2", markers = "python_version < \"3.11\""} +idna = ">=2.8" +sniffio = ">=1.1" +typing-extensions = {version = ">=4.1", markers = "python_version < \"3.11\""} + +[package.extras] +doc = ["Sphinx (>=7)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme"] +test = ["anyio[trio]", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (>=0.17)"] +trio = ["trio (>=0.23)"] + [[package]] name = "asgiref" version = "3.8.1" @@ -201,6 +223,76 @@ files = [ [package.dependencies] django = ">=3.0" +[[package]] +name = "exceptiongroup" +version = "1.2.1" +description = "Backport of PEP 654 (exception groups)" +optional = false +python-versions = ">=3.7" +files = [ + {file = "exceptiongroup-1.2.1-py3-none-any.whl", hash = "sha256:5258b9ed329c5bbdd31a309f53cbfb0b155341807f6ff7606a1e801a891b29ad"}, + {file = "exceptiongroup-1.2.1.tar.gz", hash = "sha256:a4785e48b045528f5bfe627b6ad554ff32def154f42372786903b7abcfe1aa16"}, +] + +[package.extras] +test = ["pytest (>=6)"] + +[[package]] +name = "h11" +version = "0.14.0" +description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" +optional = false +python-versions = ">=3.7" +files = [ + {file = "h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761"}, + {file = "h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d"}, +] + +[[package]] +name = "httpcore" +version = "1.0.5" +description = "A minimal low-level HTTP client." +optional = false +python-versions = ">=3.8" +files = [ + {file = "httpcore-1.0.5-py3-none-any.whl", hash = "sha256:421f18bac248b25d310f3cacd198d55b8e6125c107797b609ff9b7a6ba7991b5"}, + {file = "httpcore-1.0.5.tar.gz", hash = "sha256:34a38e2f9291467ee3b44e89dd52615370e152954ba21721378a87b2960f7a61"}, +] + +[package.dependencies] +certifi = "*" +h11 = ">=0.13,<0.15" + +[package.extras] +asyncio = ["anyio (>=4.0,<5.0)"] +http2 = ["h2 (>=3,<5)"] +socks = ["socksio (==1.*)"] +trio = ["trio (>=0.22.0,<0.26.0)"] + +[[package]] +name = "httpx" +version = "0.27.0" +description = "The next generation HTTP client." +optional = false +python-versions = ">=3.8" +files = [ + {file = "httpx-0.27.0-py3-none-any.whl", hash = "sha256:71d5465162c13681bff01ad59b2cc68dd838ea1f10e51574bac27103f00c91a5"}, + {file = "httpx-0.27.0.tar.gz", hash = "sha256:a0cb88a46f32dc874e04ee956e4c2764aba2aa228f650b06788ba6bda2962ab5"}, +] + +[package.dependencies] +anyio = "*" +certifi = "*" +httpcore = "==1.*" +idna = "*" +sniffio = "*" + +[package.extras] +brotli = ["brotli", "brotlicffi"] +cli = ["click (==8.*)", "pygments (==2.*)", "rich (>=10,<14)"] +http2 = ["h2 (>=3,<5)"] +socks = ["socksio (==1.*)"] + [[package]] name = "idna" version = "3.7" @@ -447,6 +539,31 @@ files = [ [package.extras] cli = ["click (>=5.0)"] +[[package]] +name = "python-telegram-bot" +version = "21.2" +description = "We have made you a wrapper you can't refuse" +optional = false +python-versions = ">=3.8" +files = [ + {file = "python-telegram-bot-21.2.tar.gz", hash = "sha256:2ebb462a98f502727d108c00bb50c513a68ddaf9545298c42f13996a9acf8354"}, + {file = "python_telegram_bot-21.2-py3-none-any.whl", hash = "sha256:af0f45d61521126de98f5bdc8a75a9df8b93d0c35d18b018181ca7648a38b017"}, +] + +[package.dependencies] +httpx = ">=0.27,<1.0" + +[package.extras] +all = ["APScheduler (>=3.10.4,<3.11.0)", "aiolimiter (>=1.1.0,<1.2.0)", "cachetools (>=5.3.3,<5.4.0)", "cryptography (>=39.0.1)", "httpx[http2]", "httpx[socks]", "pytz (>=2018.6)", "tornado (>=6.4,<7.0)"] +callback-data = ["cachetools (>=5.3.3,<5.4.0)"] +ext = ["APScheduler (>=3.10.4,<3.11.0)", "aiolimiter (>=1.1.0,<1.2.0)", "cachetools (>=5.3.3,<5.4.0)", "pytz (>=2018.6)", "tornado (>=6.4,<7.0)"] +http2 = ["httpx[http2]"] +job-queue = ["APScheduler (>=3.10.4,<3.11.0)", "pytz (>=2018.6)"] +passport = ["cryptography (>=39.0.1)"] +rate-limiter = ["aiolimiter (>=1.1.0,<1.2.0)"] +socks = ["httpx[socks]"] +webhooks = ["tornado (>=6.4,<7.0)"] + [[package]] name = "requests" version = "2.32.3" @@ -468,6 +585,17 @@ urllib3 = ">=1.21.1,<3" socks = ["PySocks (>=1.5.6,!=1.5.7)"] use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] +[[package]] +name = "sniffio" +version = "1.3.1" +description = "Sniff out which async library your code is running under" +optional = false +python-versions = ">=3.7" +files = [ + {file = "sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2"}, + {file = "sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc"}, +] + [[package]] name = "sqlparse" version = "0.5.0" @@ -553,4 +681,4 @@ zstd = ["zstandard (>=0.18.0)"] [metadata] lock-version = "2.0" python-versions = "^3.10" -content-hash = "692728c2cf70ba9214712bdec40930c9f5680e07a1ffc39b71d6ebb71c0f0817" +content-hash = "58528b70db47be1cef118b365a2830312668b023e992f3aff0674f834f3c7acc" diff --git a/back/pyproject.toml b/back/pyproject.toml index 2ba9906..965447c 100644 --- a/back/pyproject.toml +++ b/back/pyproject.toml @@ -16,6 +16,7 @@ pillow = "^10.3.0" python-dotenv = "^1.0.1" requests = "^2.32.2" django-cors-headers = "^4.3.1" +python-telegram-bot = "^21.2" [tool.poetry.group.dev.dependencies] taskipy = "^1.12.2" diff --git a/back/tgbot/__init__.py b/back/tgbot/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/back/tgbot/admin.py b/back/tgbot/admin.py new file mode 100644 index 0000000..44b1f86 --- /dev/null +++ b/back/tgbot/admin.py @@ -0,0 +1,5 @@ +from django.contrib import admin +from .models import Item + +# Register your models here. +admin.site.register(Item) \ No newline at end of file diff --git a/back/tgbot/apps.py b/back/tgbot/apps.py new file mode 100644 index 0000000..a04d9b3 --- /dev/null +++ b/back/tgbot/apps.py @@ -0,0 +1,13 @@ +from django.apps import AppConfig +import django.conf +import requests + + +class TgbotConfig(AppConfig): + default_auto_field = "django.db.models.BigAutoField" + name = "tgbot" + + def ready(self): + from django.conf import settings + requests.get(f"https://api.telegram.org/bot{settings.TGBOT['token']}/setWebhook?url=https://{settings.TGBOT['base_url']}/api/tgbot/&drop_pending_updates=true") + diff --git a/back/tgbot/migrations/__init__.py b/back/tgbot/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/back/tgbot/models.py b/back/tgbot/models.py new file mode 100644 index 0000000..a22a518 --- /dev/null +++ b/back/tgbot/models.py @@ -0,0 +1,8 @@ +from django.db import models + +class Item(models.Model): + id = models.AutoField(primary_key=True) + name = models.CharField(max_length=255) + + def __str__(self): + return f"Tg item {self.id}" diff --git a/back/tgbot/serializers.py b/back/tgbot/serializers.py new file mode 100644 index 0000000..1d28451 --- /dev/null +++ b/back/tgbot/serializers.py @@ -0,0 +1,12 @@ +from rest_framework import serializers + +from .models import Item +import logging + +logger = logging.getLogger("root") + + +class ItemSerializer(serializers.ModelSerializer): + class Meta: + model = Item + fields = '__all__' \ No newline at end of file diff --git a/back/tgbot/tests.py b/back/tgbot/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/back/tgbot/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/back/tgbot/views.py b/back/tgbot/views.py new file mode 100644 index 0000000..99a5015 --- /dev/null +++ b/back/tgbot/views.py @@ -0,0 +1,47 @@ +import json + +from django.conf import settings +from telegram import Update +from telegram.ext import Application, CommandHandler + +from asgiref.sync import async_to_sync + +from rest_framework import viewsets +from rest_framework.response import Response + +from .models import Item +from .serializers import ItemSerializer + +import logging + +logger = logging.getLogger("root") + + +@async_to_sync +async def init_tg(): + ptb_application = ( + Application.builder().token(settings.TGBOT["token"]).updater(None).build() + ) + logger.info( + { + "app": ptb_application, + "bot": ptb_application.bot, + "handlers": ptb_application.handlers, + } + ) + return ptb_application + + +class ItemViewSet(viewsets.ViewSet): + queryset = Item.objects.all() + serializer_class = ItemSerializer + + def __init__(self, **kwargs) -> None: + self.ptb_application = init_tg() + super().__init__(**kwargs) + + @async_to_sync + async def create(self, request): + req = json.loads(request.body) + await self.ptb_application.bot.send_message(chat_id=req["message"]["from"]["id"], text='123') + return Response({"test": "create"})