django-yadialogs 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- django_yadialogs-0.1.0/.gitignore +11 -0
- django_yadialogs-0.1.0/LICENSE +28 -0
- django_yadialogs-0.1.0/PKG-INFO +47 -0
- django_yadialogs-0.1.0/README.md +19 -0
- django_yadialogs-0.1.0/pyproject.toml +93 -0
- django_yadialogs-0.1.0/src/yadialogs/__init__.py +4 -0
- django_yadialogs-0.1.0/src/yadialogs/api.py +97 -0
- django_yadialogs-0.1.0/src/yadialogs/apps.py +14 -0
- django_yadialogs-0.1.0/src/yadialogs/dialog.py +104 -0
- django_yadialogs-0.1.0/src/yadialogs/exceptions.py +7 -0
- django_yadialogs-0.1.0/src/yadialogs/http.py +38 -0
- django_yadialogs-0.1.0/src/yadialogs/locale/empty +0 -0
- django_yadialogs-0.1.0/src/yadialogs/migrations/0001_initial.py +54 -0
- django_yadialogs-0.1.0/src/yadialogs/migrations/__init__.py +0 -0
- django_yadialogs-0.1.0/src/yadialogs/models.py +71 -0
- django_yadialogs-0.1.0/src/yadialogs/resources/__init__.py +0 -0
- django_yadialogs-0.1.0/src/yadialogs/resources/api/__init__.py +0 -0
- django_yadialogs-0.1.0/src/yadialogs/resources/api/assets.py +58 -0
- django_yadialogs-0.1.0/src/yadialogs/resources/api/status.py +65 -0
- django_yadialogs-0.1.0/src/yadialogs/resources/base.py +68 -0
- django_yadialogs-0.1.0/src/yadialogs/resources/request/__init__.py +3 -0
- django_yadialogs-0.1.0/src/yadialogs/resources/request/entities.py +122 -0
- django_yadialogs-0.1.0/src/yadialogs/resources/request/intents.py +86 -0
- django_yadialogs-0.1.0/src/yadialogs/resources/request/misc.py +141 -0
- django_yadialogs-0.1.0/src/yadialogs/resources/request/reqdata.py +211 -0
- django_yadialogs-0.1.0/src/yadialogs/resources/request/request.py +27 -0
- django_yadialogs-0.1.0/src/yadialogs/resources/response/__init__.py +4 -0
- django_yadialogs-0.1.0/src/yadialogs/resources/response/analytics.py +20 -0
- django_yadialogs-0.1.0/src/yadialogs/resources/response/button.py +42 -0
- django_yadialogs-0.1.0/src/yadialogs/resources/response/cards.py +111 -0
- django_yadialogs-0.1.0/src/yadialogs/resources/response/directives.py +103 -0
- django_yadialogs-0.1.0/src/yadialogs/resources/response/respdata.py +84 -0
- django_yadialogs-0.1.0/src/yadialogs/resources/response/response.py +32 -0
- django_yadialogs-0.1.0/src/yadialogs/settings.py +18 -0
- django_yadialogs-0.1.0/src/yadialogs/toolbox.py +5 -0
- django_yadialogs-0.1.0/src/yadialogs/urls.py +3 -0
- django_yadialogs-0.1.0/src/yadialogs/utils.py +42 -0
- django_yadialogs-0.1.0/src/yadialogs/views.py +52 -0
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
Copyright (c) 2020-2026, Igor `idle sign` Starikov
|
|
2
|
+
All rights reserved.
|
|
3
|
+
|
|
4
|
+
Redistribution and use in source and binary forms, with or without
|
|
5
|
+
modification, are permitted provided that the following conditions are met:
|
|
6
|
+
|
|
7
|
+
* Redistributions of source code must retain the above copyright notice, this
|
|
8
|
+
list of conditions and the following disclaimer.
|
|
9
|
+
|
|
10
|
+
* Redistributions in binary form must reproduce the above copyright notice,
|
|
11
|
+
this list of conditions and the following disclaimer in the documentation
|
|
12
|
+
and/or other materials provided with the distribution.
|
|
13
|
+
|
|
14
|
+
* Neither the name of the django-yadialogs nor the names of its
|
|
15
|
+
contributors may be used to endorse or promote products derived from
|
|
16
|
+
this software without specific prior written permission.
|
|
17
|
+
|
|
18
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
19
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
20
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
21
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
22
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
23
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
24
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
25
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
26
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
27
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
28
|
+
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: django-yadialogs
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Yandex Dialogs integration for Django
|
|
5
|
+
Project-URL: Homepage, https://github.com/idlesign/django-yadialogs
|
|
6
|
+
Project-URL: Documentation, https://django-yadialogs.readthedocs.io/
|
|
7
|
+
Author-email: Igor Starikov <idlesign@yandex.ru>
|
|
8
|
+
License-Expression: BSD-3-Clause
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Keywords: Django,dialogs,yandex
|
|
11
|
+
Classifier: Development Status :: 2 - Pre-Alpha
|
|
12
|
+
Classifier: Environment :: Web Environment
|
|
13
|
+
Classifier: Framework :: Django
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: License :: OSI Approved :: BSD License
|
|
16
|
+
Classifier: Operating System :: OS Independent
|
|
17
|
+
Classifier: Programming Language :: Python
|
|
18
|
+
Classifier: Programming Language :: Python :: 3
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
23
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
24
|
+
Requires-Python: >=3.10
|
|
25
|
+
Requires-Dist: django-etc>=1.2.0
|
|
26
|
+
Requires-Dist: requests
|
|
27
|
+
Description-Content-Type: text/markdown
|
|
28
|
+
|
|
29
|
+
# django-yadialogs
|
|
30
|
+
|
|
31
|
+
<https://github.com/idlesign/django-yadialogs>
|
|
32
|
+
|
|
33
|
+
[](https://pypi.python.org/pypi/django-yadialogs)
|
|
34
|
+
[](https://pypi.python.org/pypi/django-yadialogs)
|
|
35
|
+
[](https://coveralls.io/r/idlesign/django-yadialogs)
|
|
36
|
+
|
|
37
|
+
**Work in progress. Stay tuned.**
|
|
38
|
+
|
|
39
|
+
## Description
|
|
40
|
+
|
|
41
|
+
*Yandex Dialogs integration for Django*
|
|
42
|
+
|
|
43
|
+
Here will be an introductory description.
|
|
44
|
+
|
|
45
|
+
## Documentation
|
|
46
|
+
|
|
47
|
+
<https://django-yadialogs.readthedocs.io/>
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# django-yadialogs
|
|
2
|
+
|
|
3
|
+
<https://github.com/idlesign/django-yadialogs>
|
|
4
|
+
|
|
5
|
+
[](https://pypi.python.org/pypi/django-yadialogs)
|
|
6
|
+
[](https://pypi.python.org/pypi/django-yadialogs)
|
|
7
|
+
[](https://coveralls.io/r/idlesign/django-yadialogs)
|
|
8
|
+
|
|
9
|
+
**Work in progress. Stay tuned.**
|
|
10
|
+
|
|
11
|
+
## Description
|
|
12
|
+
|
|
13
|
+
*Yandex Dialogs integration for Django*
|
|
14
|
+
|
|
15
|
+
Here will be an introductory description.
|
|
16
|
+
|
|
17
|
+
## Documentation
|
|
18
|
+
|
|
19
|
+
<https://django-yadialogs.readthedocs.io/>
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "django-yadialogs"
|
|
3
|
+
dynamic = ["version"]
|
|
4
|
+
description = "Yandex Dialogs integration for Django"
|
|
5
|
+
authors = [
|
|
6
|
+
{ name = "Igor Starikov", email = "idlesign@yandex.ru" },
|
|
7
|
+
]
|
|
8
|
+
readme = "README.md"
|
|
9
|
+
license = "BSD-3-Clause"
|
|
10
|
+
license-files = ["LICENSE"]
|
|
11
|
+
requires-python = ">=3.10"
|
|
12
|
+
keywords = ["Django", "yandex", "dialogs"]
|
|
13
|
+
dependencies = [
|
|
14
|
+
"django-etc>=1.2.0",
|
|
15
|
+
"requests",
|
|
16
|
+
]
|
|
17
|
+
classifiers = [
|
|
18
|
+
"Development Status :: 2 - Pre-Alpha",
|
|
19
|
+
"Framework :: Django",
|
|
20
|
+
"Environment :: Web Environment",
|
|
21
|
+
"Intended Audience :: Developers",
|
|
22
|
+
"Operating System :: OS Independent",
|
|
23
|
+
"Programming Language :: Python",
|
|
24
|
+
"Programming Language :: Python :: 3",
|
|
25
|
+
"Programming Language :: Python :: 3.10",
|
|
26
|
+
"Programming Language :: Python :: 3.11",
|
|
27
|
+
"Programming Language :: Python :: 3.12",
|
|
28
|
+
"Programming Language :: Python :: 3.13",
|
|
29
|
+
"Programming Language :: Python :: 3.14",
|
|
30
|
+
"License :: OSI Approved :: BSD License",
|
|
31
|
+
]
|
|
32
|
+
|
|
33
|
+
[project.urls]
|
|
34
|
+
Homepage = "https://github.com/idlesign/django-yadialogs"
|
|
35
|
+
Documentation = "https://django-yadialogs.readthedocs.io/"
|
|
36
|
+
|
|
37
|
+
[dependency-groups]
|
|
38
|
+
dev = [
|
|
39
|
+
{ include-group = "docs" },
|
|
40
|
+
{ include-group = "linters" },
|
|
41
|
+
{ include-group = "tests" },
|
|
42
|
+
{ include-group = "runtime" },
|
|
43
|
+
]
|
|
44
|
+
docs = [
|
|
45
|
+
"mkdocs-apidescribed-plugin",
|
|
46
|
+
"mkdocs-material",
|
|
47
|
+
"mkdocs-navsorted-plugin",
|
|
48
|
+
]
|
|
49
|
+
linters = [
|
|
50
|
+
"ruff==0.13.1",
|
|
51
|
+
]
|
|
52
|
+
tests = [
|
|
53
|
+
"coverage",
|
|
54
|
+
"pytest",
|
|
55
|
+
"pytest-datafixtures",
|
|
56
|
+
"pytest-djangoapp>=1.6.0",
|
|
57
|
+
"pytest-responsemock",
|
|
58
|
+
]
|
|
59
|
+
runtime = [
|
|
60
|
+
"Django>=3.2.9",
|
|
61
|
+
]
|
|
62
|
+
|
|
63
|
+
[build-system]
|
|
64
|
+
requires = ["hatchling"]
|
|
65
|
+
build-backend = "hatchling.build"
|
|
66
|
+
|
|
67
|
+
[tool.hatch.version]
|
|
68
|
+
path = "src/yadialogs/__init__.py"
|
|
69
|
+
|
|
70
|
+
[tool.hatch.build.targets.wheel]
|
|
71
|
+
packages = ["src/yadialogs"]
|
|
72
|
+
|
|
73
|
+
[tool.hatch.build.targets.sdist]
|
|
74
|
+
packages = ["src/"]
|
|
75
|
+
|
|
76
|
+
[tool.pytest.ini_options]
|
|
77
|
+
testpaths = [
|
|
78
|
+
"tests",
|
|
79
|
+
]
|
|
80
|
+
addopts = "--pyargs"
|
|
81
|
+
|
|
82
|
+
[tool.coverage.run]
|
|
83
|
+
source = [
|
|
84
|
+
"src/yadialogs",
|
|
85
|
+
]
|
|
86
|
+
omit = [
|
|
87
|
+
"src/yadialogs/migrations/*",
|
|
88
|
+
]
|
|
89
|
+
|
|
90
|
+
[tool.makeapp.tests]
|
|
91
|
+
deps = [
|
|
92
|
+
"django~=${{ django-version }}.0",
|
|
93
|
+
]
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
from typing import Union, Type, List
|
|
3
|
+
|
|
4
|
+
from .http import HttpClient
|
|
5
|
+
from .resources.api.assets import Image, Sound
|
|
6
|
+
from .resources.api.status import Status, ImagesInfo, SoundsInfo
|
|
7
|
+
from .settings import API_KEY
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class _ApiRealm:
|
|
11
|
+
|
|
12
|
+
_name: str = ''
|
|
13
|
+
_asset_cls: Union[Type[Image], Type[Sound]]
|
|
14
|
+
|
|
15
|
+
def __init__(self, *, api: 'DialogsApi'):
|
|
16
|
+
self._api = api
|
|
17
|
+
|
|
18
|
+
def _get_url_base(self, *, dialog_id: str) -> str:
|
|
19
|
+
return f'{self._api._url_base}/skills/{dialog_id}/{self._name}'
|
|
20
|
+
|
|
21
|
+
def get_info(self) -> Union[ImagesInfo, SoundsInfo]:
|
|
22
|
+
"""Возвращает информацию о хранилище ресурсов (изображений или звукозаписей)."""
|
|
23
|
+
return getattr(self._api.get_status(), self._name)
|
|
24
|
+
|
|
25
|
+
def upload(self, *, dialog_id: str, fpath: Union[Path, str]):
|
|
26
|
+
"""Загружает указанный файл ресурса (изображения или звукозаписи.)
|
|
27
|
+
|
|
28
|
+
:param dialog_id: Идентификатор диалога.
|
|
29
|
+
:param fpath: Путь до файла.
|
|
30
|
+
|
|
31
|
+
"""
|
|
32
|
+
response = self._api._client.upload(self._get_url_base(dialog_id=dialog_id), fpath=fpath)
|
|
33
|
+
return self._asset_cls(dialog_id=dialog_id, raw=response[self._name.rstrip('s')])
|
|
34
|
+
|
|
35
|
+
def list(self, *, dialog_id: str) -> Union[List[Image], List[Sound]]:
|
|
36
|
+
"""Возвращает список с объектами ресурсов (изображений или звукозаписей).
|
|
37
|
+
|
|
38
|
+
:param dialog_id: Идентификатор диалога.
|
|
39
|
+
|
|
40
|
+
"""
|
|
41
|
+
response = self._api._client.get(self._get_url_base(dialog_id=dialog_id))
|
|
42
|
+
asset_cls = self._asset_cls
|
|
43
|
+
return [asset_cls(dialog_id=dialog_id, raw=asset) for asset in response[self._name]]
|
|
44
|
+
|
|
45
|
+
def delete(self, *, asset: Union[str, Image, Sound], dialog_id: str = '') -> bool:
|
|
46
|
+
"""Удаляет указанный ресурс.
|
|
47
|
+
|
|
48
|
+
:param asset: Объект ресурса или его идентификатор.
|
|
49
|
+
:param dialog_id: Идентификатор диалога.
|
|
50
|
+
|
|
51
|
+
"""
|
|
52
|
+
dialog_id = dialog_id or asset.dialog_id
|
|
53
|
+
return self._api._client.delete(f'{self._get_url_base(dialog_id=dialog_id)}/{asset}')['result'] == 'ok'
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class _ImagesApi(_ApiRealm):
|
|
57
|
+
"""Интерфейс для доступа к данным хранилища изображений."""
|
|
58
|
+
|
|
59
|
+
_name: str = 'images'
|
|
60
|
+
_asset_cls: Type[Image] = Image
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class _SoundsApi(_ApiRealm):
|
|
64
|
+
"""Интерфейс для доступа к данным хранилища звукозаписей."""
|
|
65
|
+
|
|
66
|
+
_name: str = 'sounds'
|
|
67
|
+
_asset_cls: Type[Sound] = Sound
|
|
68
|
+
|
|
69
|
+
def get(self, *, dialog_id: str, asset_id: str) -> Union[Image, Sound]:
|
|
70
|
+
"""Возвращает объект ресурса с информацией о нём.
|
|
71
|
+
|
|
72
|
+
:param dialog_id: Идентификатор диалога.
|
|
73
|
+
:param asset_id: Идентификатор ресурса.
|
|
74
|
+
|
|
75
|
+
"""
|
|
76
|
+
response = self._api._client.get(f'{self._get_url_base(dialog_id=dialog_id)}/{asset_id}')
|
|
77
|
+
return self._asset_cls(dialog_id=dialog_id, raw=response[self._name.rstrip('s')])
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
class DialogsApi:
|
|
81
|
+
"""Предоставляет интерфейс для взаимодействия с API Диалогов."""
|
|
82
|
+
|
|
83
|
+
_url_base: str = 'https://dialogs.yandex.net/api/v1'
|
|
84
|
+
|
|
85
|
+
def __init__(self, *, token: str = ''):
|
|
86
|
+
self._client = HttpClient(token=token or API_KEY)
|
|
87
|
+
|
|
88
|
+
self.images = _ImagesApi(api=self)
|
|
89
|
+
"""Интерфейс для доступа к хранилищу изображений."""
|
|
90
|
+
|
|
91
|
+
self.sounds = _SoundsApi(api=self)
|
|
92
|
+
"""Интерфейс для доступа к хранилищу звукозаписей."""
|
|
93
|
+
|
|
94
|
+
def get_status(self) -> Status:
|
|
95
|
+
"""Возвращает объект со статусной информацией."""
|
|
96
|
+
response = self._client.get(f'{self._url_base}/status')
|
|
97
|
+
return Status(response)
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
from django.apps import AppConfig
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class YadialogsConfig(AppConfig):
|
|
5
|
+
"""Конфигурация приложения."""
|
|
6
|
+
|
|
7
|
+
name = 'yadialogs'
|
|
8
|
+
verbose_name = 'Яндекс.Диалоги'
|
|
9
|
+
default_auto_field = 'django.db.models.AutoField'
|
|
10
|
+
|
|
11
|
+
def ready(self):
|
|
12
|
+
"""Инициализирует приложение."""
|
|
13
|
+
from .toolbox import autodiscover_dialogs
|
|
14
|
+
autodiscover_dialogs()
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
from typing import Dict, Type, Optional, Union
|
|
2
|
+
|
|
3
|
+
from django.http import HttpRequest
|
|
4
|
+
|
|
5
|
+
from .exceptions import HandledRequestException
|
|
6
|
+
from .resources.request import Request, TypeRequestData
|
|
7
|
+
from .resources.response import Response
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class Dialog:
|
|
11
|
+
"""Base for dialogs."""
|
|
12
|
+
|
|
13
|
+
id: str = ''
|
|
14
|
+
"""Identifier given to a dialog after its registration."""
|
|
15
|
+
|
|
16
|
+
alias: str = ''
|
|
17
|
+
"""Convenience alias for a dialog."""
|
|
18
|
+
|
|
19
|
+
dialogs: Dict[str, Dict[str, Type['Dialog']]] = {
|
|
20
|
+
'alias': {},
|
|
21
|
+
'id': {},
|
|
22
|
+
}
|
|
23
|
+
"""All registered dialogs."""
|
|
24
|
+
|
|
25
|
+
verify_id: bool = True
|
|
26
|
+
"""Perform an additional check verifying
|
|
27
|
+
that a dialog ID in request matches the dialog ID from this class.
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
debug: bool = False
|
|
31
|
+
|
|
32
|
+
def __init__(self, data: dict, *, http_request: HttpRequest):
|
|
33
|
+
"""
|
|
34
|
+
:param data: Data from Dialogs server.
|
|
35
|
+
:param http_request: Source Django request object.
|
|
36
|
+
|
|
37
|
+
"""
|
|
38
|
+
self.request = Request(raw=data)
|
|
39
|
+
self.http_request = http_request
|
|
40
|
+
|
|
41
|
+
def __init_subclass__(cls):
|
|
42
|
+
|
|
43
|
+
super().__init_subclass__()
|
|
44
|
+
|
|
45
|
+
if cls.id and cls.alias:
|
|
46
|
+
cls.dialogs['alias'][cls.alias] = cls
|
|
47
|
+
cls.dialogs['id'][cls.id] = cls
|
|
48
|
+
|
|
49
|
+
@classmethod
|
|
50
|
+
def handle_request(cls, data: dict, *, http_request: HttpRequest) -> dict:
|
|
51
|
+
"""Handles request from Dialogs server.
|
|
52
|
+
|
|
53
|
+
:param data: Data from server.
|
|
54
|
+
:param http_request: Source Django request object.
|
|
55
|
+
|
|
56
|
+
"""
|
|
57
|
+
dialog = cls(data, http_request=http_request)
|
|
58
|
+
|
|
59
|
+
try:
|
|
60
|
+
|
|
61
|
+
if cls.verify_id and (dialog_id := dialog.request.session.dialog_id) != dialog.id:
|
|
62
|
+
raise HandledRequestException(
|
|
63
|
+
f"Requested dialog ID \"{dialog_id}\" doesn't match the ID from \"{dialog.alias}\"."
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
response = dialog.process(data=dialog.request.data)
|
|
67
|
+
|
|
68
|
+
if isinstance(response, str):
|
|
69
|
+
response = dialog.response_from_request(text=response)
|
|
70
|
+
|
|
71
|
+
except HandledRequestException as e:
|
|
72
|
+
response = dialog.response_for_exception(e)
|
|
73
|
+
|
|
74
|
+
return response.serialize()
|
|
75
|
+
|
|
76
|
+
@classmethod
|
|
77
|
+
def get(cls, dialog: str, by_id: bool = False) -> Type['Dialog'] | None:
|
|
78
|
+
"""Get dialog type by its alias or id.
|
|
79
|
+
|
|
80
|
+
:param dialog: Dialog alias or id.
|
|
81
|
+
|
|
82
|
+
:param by_id: Get by id instead of an alias.
|
|
83
|
+
|
|
84
|
+
"""
|
|
85
|
+
return cls.dialogs['id' if by_id else 'alias'].get(dialog)
|
|
86
|
+
|
|
87
|
+
def process(self, *, data: TypeRequestData, **kwargs) -> Union[Response, str]:
|
|
88
|
+
"""Should implement server request processing and return a response.
|
|
89
|
+
Response could be a new Response object
|
|
90
|
+
or just a string to generate Response object for.
|
|
91
|
+
"""
|
|
92
|
+
raise HandledRequestException(f'Request processing is not implemented for "{self.alias}" dialog.')
|
|
93
|
+
|
|
94
|
+
def response_for_exception(self, e: Exception) -> Response:
|
|
95
|
+
return Response(f'Error: {e}', end_session=True)
|
|
96
|
+
|
|
97
|
+
def response_from_request(self, *, text: str = '', end_session: bool = False) -> Response:
|
|
98
|
+
|
|
99
|
+
response = Response(text, end_session=end_session)
|
|
100
|
+
|
|
101
|
+
if states := self.request.states:
|
|
102
|
+
response.states = states
|
|
103
|
+
|
|
104
|
+
return response
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
from typing import Callable, Union
|
|
3
|
+
|
|
4
|
+
from requests import Session
|
|
5
|
+
|
|
6
|
+
from . import VERSION as VERSION_STR
|
|
7
|
+
from .settings import API_TIMEOUT
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class HttpClient:
|
|
11
|
+
"""Клиент для соверщения HTTP запросов."""
|
|
12
|
+
|
|
13
|
+
timeout: int = API_TIMEOUT
|
|
14
|
+
|
|
15
|
+
user_agent: str = ''
|
|
16
|
+
|
|
17
|
+
def __init__(self, *, token: str):
|
|
18
|
+
session = Session()
|
|
19
|
+
session.headers.update({
|
|
20
|
+
'Authorization': f'OAuth {token}',
|
|
21
|
+
'User-Agent': f'django-yadialogs/{VERSION_STR}',
|
|
22
|
+
})
|
|
23
|
+
self.session = session
|
|
24
|
+
|
|
25
|
+
def _request(self, *, method: Callable, url, **kwargs) -> dict:
|
|
26
|
+
response = method(url, **{'timeout': self.timeout, **kwargs})
|
|
27
|
+
response.raise_for_status()
|
|
28
|
+
data = response.json()
|
|
29
|
+
return data
|
|
30
|
+
|
|
31
|
+
def delete(self, url: str) -> dict:
|
|
32
|
+
return self._request(method=self.session.delete, url=url)
|
|
33
|
+
|
|
34
|
+
def upload(self, url: str, *, fpath: Union[Path, str]) -> dict:
|
|
35
|
+
return self._request(method=self.session.post, url=url, files={'file': Path(fpath).open('rb')})
|
|
36
|
+
|
|
37
|
+
def get(self, url: str) -> dict:
|
|
38
|
+
return self._request(method=self.session.get, url=url)
|
|
File without changes
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# Generated by Django 6.1.3 on 2026-11-10 08:13
|
|
2
|
+
|
|
3
|
+
import django.core.files.storage
|
|
4
|
+
import yadialogs.models
|
|
5
|
+
from django.db import migrations, models
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class Migration(migrations.Migration):
|
|
9
|
+
|
|
10
|
+
initial = True
|
|
11
|
+
|
|
12
|
+
dependencies = [
|
|
13
|
+
]
|
|
14
|
+
|
|
15
|
+
operations = [
|
|
16
|
+
migrations.CreateModel(
|
|
17
|
+
name='Image',
|
|
18
|
+
fields=[
|
|
19
|
+
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
20
|
+
('rid', models.CharField(help_text='Идентификатор ресурса', max_length=50, verbose_name='ID')),
|
|
21
|
+
('dt_uploaded', models.DateTimeField(db_column='db_upl', verbose_name='Загружен')),
|
|
22
|
+
('status', models.IntegerField(choices=[(1, 'Загружен'), (2, 'Ошибка')], default=1, verbose_name='Статус')),
|
|
23
|
+
('status_hint', models.TextField(help_text='Подсказка для состояния.', verbose_name='Подсказка')),
|
|
24
|
+
('dialog_alias', models.CharField(db_column='dalias', db_index=True, help_text='Псевдоним диалога', max_length=36, verbose_name='Диалог')),
|
|
25
|
+
('alias', models.CharField(default=None, help_text='Краткое имя для удобной адресации ресурса', max_length=50, null=True, unique=True, verbose_name='Псевдоним')),
|
|
26
|
+
('group', models.UUIDField(db_column='grp', default=None, help_text='Идентификатор, объединяющий группу ресурсов', null=True, verbose_name='Группа')),
|
|
27
|
+
('file', models.FileField(storage=django.core.files.storage.FileSystemStorage(location='/tmp'), upload_to=yadialogs.models.get_upload, verbose_name='Файл')),
|
|
28
|
+
('size', models.IntegerField(verbose_name='Размер файла')),
|
|
29
|
+
],
|
|
30
|
+
options={
|
|
31
|
+
'verbose_name': 'Изображение',
|
|
32
|
+
'verbose_name_plural': 'Изображения',
|
|
33
|
+
},
|
|
34
|
+
),
|
|
35
|
+
migrations.CreateModel(
|
|
36
|
+
name='Sound',
|
|
37
|
+
fields=[
|
|
38
|
+
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
39
|
+
('rid', models.CharField(help_text='Идентификатор ресурса', max_length=50, verbose_name='ID')),
|
|
40
|
+
('dt_uploaded', models.DateTimeField(db_column='db_upl', verbose_name='Загружен')),
|
|
41
|
+
('status', models.IntegerField(choices=[(1, 'Загружен'), (2, 'Ошибка')], default=1, verbose_name='Статус')),
|
|
42
|
+
('status_hint', models.TextField(help_text='Подсказка для состояния.', verbose_name='Подсказка')),
|
|
43
|
+
('dialog_alias', models.CharField(db_column='dalias', db_index=True, help_text='Псевдоним диалога', max_length=36, verbose_name='Диалог')),
|
|
44
|
+
('alias', models.CharField(default=None, help_text='Краткое имя для удобной адресации ресурса', max_length=50, null=True, unique=True, verbose_name='Псевдоним')),
|
|
45
|
+
('group', models.UUIDField(db_column='grp', default=None, help_text='Идентификатор, объединяющий группу ресурсов', null=True, verbose_name='Группа')),
|
|
46
|
+
('file', models.FileField(storage=django.core.files.storage.FileSystemStorage(location='/tmp'), upload_to=yadialogs.models.get_upload, verbose_name='Файл')),
|
|
47
|
+
('size', models.IntegerField(verbose_name='Размер файла')),
|
|
48
|
+
],
|
|
49
|
+
options={
|
|
50
|
+
'verbose_name': 'Звук',
|
|
51
|
+
'verbose_name_plural': 'Звуки',
|
|
52
|
+
},
|
|
53
|
+
),
|
|
54
|
+
]
|
|
File without changes
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
from django.core.files.storage import FileSystemStorage
|
|
2
|
+
from django.db import models
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
TmpStorage = FileSystemStorage(location='/tmp')
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def get_upload(instance: 'Asset', filename):
|
|
9
|
+
return 'some'
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class Asset(models.Model):
|
|
13
|
+
"""Ресурс. Базовая модель."""
|
|
14
|
+
|
|
15
|
+
_api_realm: str = ''
|
|
16
|
+
|
|
17
|
+
class Statuses(models.IntegerChoices):
|
|
18
|
+
"""Статусы ресурсов."""
|
|
19
|
+
|
|
20
|
+
UPLOADED = 1, 'Загружен'
|
|
21
|
+
ERROR = 2, 'Ошибка'
|
|
22
|
+
|
|
23
|
+
rid = models.CharField('ID', max_length=50, help_text='Идентификатор ресурса')
|
|
24
|
+
|
|
25
|
+
dt_uploaded = models.DateTimeField('Загружен', db_column='db_upl')
|
|
26
|
+
|
|
27
|
+
status = models.IntegerField('Статус', choices=Statuses.choices, default=Statuses.UPLOADED)
|
|
28
|
+
status_hint = models.TextField('Подсказка', help_text='Подсказка для состояния.')
|
|
29
|
+
|
|
30
|
+
dialog_alias = models.CharField(
|
|
31
|
+
'Диалог', max_length=36, db_index=True, db_column='dalias',
|
|
32
|
+
help_text='Псевдоним диалога')
|
|
33
|
+
|
|
34
|
+
alias = models.CharField(
|
|
35
|
+
'Псевдоним', max_length=50, null=True, default=None, unique=True,
|
|
36
|
+
help_text='Краткое имя для удобной адресации ресурса')
|
|
37
|
+
|
|
38
|
+
group = models.UUIDField(
|
|
39
|
+
'Группа', null=True, default=None, db_column='grp',
|
|
40
|
+
help_text='Идентификатор, объединяющий группу ресурсов')
|
|
41
|
+
|
|
42
|
+
file = models.FileField(
|
|
43
|
+
'Файл', upload_to=get_upload, storage=TmpStorage)
|
|
44
|
+
|
|
45
|
+
size = models.IntegerField('Размер файла')
|
|
46
|
+
|
|
47
|
+
class Meta:
|
|
48
|
+
abstract = True
|
|
49
|
+
|
|
50
|
+
def __str__(self):
|
|
51
|
+
return f'{self.rid} [{self.alias}]'
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class Image(Asset):
|
|
55
|
+
"""Изображение."""
|
|
56
|
+
|
|
57
|
+
_api_realm: str = 'images'
|
|
58
|
+
|
|
59
|
+
class Meta:
|
|
60
|
+
verbose_name = 'Изображение'
|
|
61
|
+
verbose_name_plural = 'Изображения'
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
class Sound(Asset):
|
|
65
|
+
"""Звукозапись."""
|
|
66
|
+
|
|
67
|
+
_api_realm: str = 'sounds'
|
|
68
|
+
|
|
69
|
+
class Meta:
|
|
70
|
+
verbose_name = 'Звук'
|
|
71
|
+
verbose_name_plural = 'Звуки'
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
from typing import TYPE_CHECKING
|
|
2
|
+
|
|
3
|
+
from django.utils.dateparse import parse_datetime
|
|
4
|
+
|
|
5
|
+
if TYPE_CHECKING:
|
|
6
|
+
from datetime import datetime
|
|
7
|
+
|
|
8
|
+
from ..base import Container
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class _Asset(Container):
|
|
12
|
+
|
|
13
|
+
def __init__(self, *, dialog_id: str, raw: dict):
|
|
14
|
+
|
|
15
|
+
super().__init__(raw)
|
|
16
|
+
|
|
17
|
+
self.dialog_id = dialog_id
|
|
18
|
+
"""Идентификатор диалога."""
|
|
19
|
+
|
|
20
|
+
self.id: str = raw['id']
|
|
21
|
+
"""Идентификатор ресурса."""
|
|
22
|
+
|
|
23
|
+
self.size: int = raw['size'] or 0
|
|
24
|
+
"""Размер ресурса, байтов."""
|
|
25
|
+
|
|
26
|
+
self.uploaded: datetime = parse_datetime(raw['createdAt'])
|
|
27
|
+
"""Дата и время загрузки ресурса."""
|
|
28
|
+
|
|
29
|
+
def __str__(self) -> str:
|
|
30
|
+
return self.id
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class Image(_Asset):
|
|
34
|
+
"""Изображение."""
|
|
35
|
+
|
|
36
|
+
def __init__(self, *, dialog_id: str, raw: dict):
|
|
37
|
+
|
|
38
|
+
super().__init__(dialog_id=dialog_id, raw=raw)
|
|
39
|
+
|
|
40
|
+
self.orig_url: str = raw.get('origUrl') or ''
|
|
41
|
+
"""URL изображения (если оно было загружено используя URL)."""
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class Sound(_Asset):
|
|
45
|
+
"""Звукозапись."""
|
|
46
|
+
|
|
47
|
+
def __init__(self, *, dialog_id: str, raw: dict):
|
|
48
|
+
|
|
49
|
+
super().__init__(dialog_id=dialog_id, raw=raw)
|
|
50
|
+
|
|
51
|
+
self.orig_name: str = raw['originalName']
|
|
52
|
+
"""Оригинальное имя файла."""
|
|
53
|
+
|
|
54
|
+
self.is_processed: bool = raw['isProcessed']
|
|
55
|
+
"""Флаг законченности обработки."""
|
|
56
|
+
|
|
57
|
+
self.error: str = raw['error'] or ''
|
|
58
|
+
"""Описание ошибки обработки."""
|