xc-bot 1.0.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.
- xc_bot-1.0.0/LICENSE +21 -0
- xc_bot-1.0.0/PKG-INFO +138 -0
- xc_bot-1.0.0/README.md +101 -0
- xc_bot-1.0.0/pyproject.toml +64 -0
- xc_bot-1.0.0/setup.cfg +4 -0
- xc_bot-1.0.0/setup.py +15 -0
- xc_bot-1.0.0/tests/test_api.py +92 -0
- xc_bot-1.0.0/tests/test_bot.py +84 -0
- xc_bot-1.0.0/tests/test_handlers.py +111 -0
- xc_bot-1.0.0/xc_bot/__init__.py +42 -0
- xc_bot-1.0.0/xc_bot/api.py +175 -0
- xc_bot-1.0.0/xc_bot/bot.py +156 -0
- xc_bot-1.0.0/xc_bot/exceptions.py +33 -0
- xc_bot-1.0.0/xc_bot/grpc_client.py +59 -0
- xc_bot-1.0.0/xc_bot/handlers/__init__.py +4 -0
- xc_bot-1.0.0/xc_bot/handlers/handler.py +84 -0
- xc_bot-1.0.0/xc_bot/pb/__init__.py +0 -0
- xc_bot-1.0.0/xc_bot/pb/bot_service_pb2.py +44 -0
- xc_bot-1.0.0/xc_bot/pb/bot_service_pb2_grpc.py +145 -0
- xc_bot-1.0.0/xc_bot/types/__init__.py +14 -0
- xc_bot-1.0.0/xc_bot/types/message.py +98 -0
- xc_bot-1.0.0/xc_bot/types/update.py +46 -0
- xc_bot-1.0.0/xc_bot/types/user.py +42 -0
- xc_bot-1.0.0/xc_bot/webhook/__init__.py +4 -0
- xc_bot-1.0.0/xc_bot/webhook/server.py +99 -0
- xc_bot-1.0.0/xc_bot.egg-info/PKG-INFO +138 -0
- xc_bot-1.0.0/xc_bot.egg-info/SOURCES.txt +28 -0
- xc_bot-1.0.0/xc_bot.egg-info/dependency_links.txt +1 -0
- xc_bot-1.0.0/xc_bot.egg-info/requires.txt +10 -0
- xc_bot-1.0.0/xc_bot.egg-info/top_level.txt +1 -0
xc_bot-1.0.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Xaneo Team
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
xc_bot-1.0.0/PKG-INFO
ADDED
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: xc-bot
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Python SDK for XaneoConnect Bot API
|
|
5
|
+
Author: Xaneo Team
|
|
6
|
+
Author-email: Xaneo Team <ltpddwk@gmail.com>
|
|
7
|
+
License: MIT
|
|
8
|
+
Project-URL: Homepage, https://github.com/saneome2/xc-bot
|
|
9
|
+
Project-URL: Documentation, https://xaneo.com/bot-api
|
|
10
|
+
Project-URL: Repository, https://github.com/saneome2/xc-bot
|
|
11
|
+
Keywords: xaneo,bot,api,sdk,async
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
21
|
+
Classifier: Framework :: AsyncIO
|
|
22
|
+
Requires-Python: >=3.8
|
|
23
|
+
Description-Content-Type: text/markdown
|
|
24
|
+
License-File: LICENSE
|
|
25
|
+
Requires-Dist: aiohttp>=3.8.0
|
|
26
|
+
Requires-Dist: grpcio>=1.60.0
|
|
27
|
+
Requires-Dist: protobuf>=4.25.0
|
|
28
|
+
Provides-Extra: dev
|
|
29
|
+
Requires-Dist: pytest>=7.0.0; extra == "dev"
|
|
30
|
+
Requires-Dist: pytest-asyncio>=0.21.0; extra == "dev"
|
|
31
|
+
Requires-Dist: aioresponses>=0.7.0; extra == "dev"
|
|
32
|
+
Requires-Dist: black>=23.0.0; extra == "dev"
|
|
33
|
+
Requires-Dist: mypy>=1.0.0; extra == "dev"
|
|
34
|
+
Dynamic: author
|
|
35
|
+
Dynamic: license-file
|
|
36
|
+
Dynamic: requires-python
|
|
37
|
+
|
|
38
|
+
# xc-bot
|
|
39
|
+
|
|
40
|
+
Python SDK for XaneoConnect Bot API
|
|
41
|
+
|
|
42
|
+
## Installation
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
pip install xc-bot
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Quick Start
|
|
49
|
+
|
|
50
|
+
```python
|
|
51
|
+
from xc_bot import Bot
|
|
52
|
+
|
|
53
|
+
bot = Bot("xbot_12345_abc...")
|
|
54
|
+
|
|
55
|
+
@bot.command('start')
|
|
56
|
+
async def start(message):
|
|
57
|
+
await message.reply("Hello! I'm a bot.")
|
|
58
|
+
|
|
59
|
+
bot.run()
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## Features
|
|
63
|
+
|
|
64
|
+
- Async-only API (Python 3.8+)
|
|
65
|
+
- Decorator-based handlers: `@bot.command()`, `@bot.hears()`, `@bot.message()`
|
|
66
|
+
- Message object with `.reply()`, `.edit()`, `.delete()` methods
|
|
67
|
+
- Webhook server with HMAC signature verification
|
|
68
|
+
- Automatic retry with exponential backoff
|
|
69
|
+
|
|
70
|
+
## API Methods
|
|
71
|
+
|
|
72
|
+
| Method | Description |
|
|
73
|
+
| -------------------------------- | --------------------- |
|
|
74
|
+
| `get_me()` | Get bot information |
|
|
75
|
+
| `send_message(chat_id, text)` | Send a message |
|
|
76
|
+
| `edit_message(message_id, text)` | Edit a message |
|
|
77
|
+
| `delete_message(message_id)` | Delete a message |
|
|
78
|
+
| `get_chat(chat_id)` | Get chat info |
|
|
79
|
+
| `get_chat_members(chat_id)` | Get chat members |
|
|
80
|
+
| `leave_chat(chat_id)` | Leave a chat |
|
|
81
|
+
| `set_commands(commands)` | Register bot commands |
|
|
82
|
+
| `set_webhook(url)` | Set webhook URL |
|
|
83
|
+
| `delete_webhook()` | Remove webhook |
|
|
84
|
+
|
|
85
|
+
## Handlers
|
|
86
|
+
|
|
87
|
+
### Commands
|
|
88
|
+
|
|
89
|
+
```python
|
|
90
|
+
@bot.command(['start', 'help'])
|
|
91
|
+
async def start_handler(message):
|
|
92
|
+
await message.reply("Welcome!")
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### Regex
|
|
96
|
+
|
|
97
|
+
```python
|
|
98
|
+
@bot.hears(r"^/echo (.+)$")
|
|
99
|
+
async def echo_handler(message, match):
|
|
100
|
+
await message.reply(match.group(1))
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### All Messages
|
|
104
|
+
|
|
105
|
+
```python
|
|
106
|
+
@bot.message
|
|
107
|
+
async def log_message(message):
|
|
108
|
+
print(f"[{message.chat_id}] {message.text}")
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
## Webhook
|
|
112
|
+
|
|
113
|
+
```python
|
|
114
|
+
from xc_bot import Bot, WebhookServer
|
|
115
|
+
|
|
116
|
+
bot = Bot("xbot_token")
|
|
117
|
+
|
|
118
|
+
@bot.command("start")
|
|
119
|
+
async def start(message):
|
|
120
|
+
await message.reply("Hello!")
|
|
121
|
+
|
|
122
|
+
# Set webhook
|
|
123
|
+
await bot.set_webhook("https://your-server.com/webhook", secret="your-secret")
|
|
124
|
+
|
|
125
|
+
# Start webhook server
|
|
126
|
+
server = WebhookServer(bot, port=8443, secret="your-secret")
|
|
127
|
+
server.run()
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
## Getting a Token
|
|
131
|
+
|
|
132
|
+
1. Open chat with @BotConstructor in XaneoConnect
|
|
133
|
+
2. Send `/newbot`
|
|
134
|
+
3. Follow the instructions
|
|
135
|
+
|
|
136
|
+
## License
|
|
137
|
+
|
|
138
|
+
MIT
|
xc_bot-1.0.0/README.md
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
# xc-bot
|
|
2
|
+
|
|
3
|
+
Python SDK for XaneoConnect Bot API
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pip install xc-bot
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Quick Start
|
|
12
|
+
|
|
13
|
+
```python
|
|
14
|
+
from xc_bot import Bot
|
|
15
|
+
|
|
16
|
+
bot = Bot("xbot_12345_abc...")
|
|
17
|
+
|
|
18
|
+
@bot.command('start')
|
|
19
|
+
async def start(message):
|
|
20
|
+
await message.reply("Hello! I'm a bot.")
|
|
21
|
+
|
|
22
|
+
bot.run()
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Features
|
|
26
|
+
|
|
27
|
+
- Async-only API (Python 3.8+)
|
|
28
|
+
- Decorator-based handlers: `@bot.command()`, `@bot.hears()`, `@bot.message()`
|
|
29
|
+
- Message object with `.reply()`, `.edit()`, `.delete()` methods
|
|
30
|
+
- Webhook server with HMAC signature verification
|
|
31
|
+
- Automatic retry with exponential backoff
|
|
32
|
+
|
|
33
|
+
## API Methods
|
|
34
|
+
|
|
35
|
+
| Method | Description |
|
|
36
|
+
| -------------------------------- | --------------------- |
|
|
37
|
+
| `get_me()` | Get bot information |
|
|
38
|
+
| `send_message(chat_id, text)` | Send a message |
|
|
39
|
+
| `edit_message(message_id, text)` | Edit a message |
|
|
40
|
+
| `delete_message(message_id)` | Delete a message |
|
|
41
|
+
| `get_chat(chat_id)` | Get chat info |
|
|
42
|
+
| `get_chat_members(chat_id)` | Get chat members |
|
|
43
|
+
| `leave_chat(chat_id)` | Leave a chat |
|
|
44
|
+
| `set_commands(commands)` | Register bot commands |
|
|
45
|
+
| `set_webhook(url)` | Set webhook URL |
|
|
46
|
+
| `delete_webhook()` | Remove webhook |
|
|
47
|
+
|
|
48
|
+
## Handlers
|
|
49
|
+
|
|
50
|
+
### Commands
|
|
51
|
+
|
|
52
|
+
```python
|
|
53
|
+
@bot.command(['start', 'help'])
|
|
54
|
+
async def start_handler(message):
|
|
55
|
+
await message.reply("Welcome!")
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### Regex
|
|
59
|
+
|
|
60
|
+
```python
|
|
61
|
+
@bot.hears(r"^/echo (.+)$")
|
|
62
|
+
async def echo_handler(message, match):
|
|
63
|
+
await message.reply(match.group(1))
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### All Messages
|
|
67
|
+
|
|
68
|
+
```python
|
|
69
|
+
@bot.message
|
|
70
|
+
async def log_message(message):
|
|
71
|
+
print(f"[{message.chat_id}] {message.text}")
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## Webhook
|
|
75
|
+
|
|
76
|
+
```python
|
|
77
|
+
from xc_bot import Bot, WebhookServer
|
|
78
|
+
|
|
79
|
+
bot = Bot("xbot_token")
|
|
80
|
+
|
|
81
|
+
@bot.command("start")
|
|
82
|
+
async def start(message):
|
|
83
|
+
await message.reply("Hello!")
|
|
84
|
+
|
|
85
|
+
# Set webhook
|
|
86
|
+
await bot.set_webhook("https://your-server.com/webhook", secret="your-secret")
|
|
87
|
+
|
|
88
|
+
# Start webhook server
|
|
89
|
+
server = WebhookServer(bot, port=8443, secret="your-secret")
|
|
90
|
+
server.run()
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
## Getting a Token
|
|
94
|
+
|
|
95
|
+
1. Open chat with @BotConstructor in XaneoConnect
|
|
96
|
+
2. Send `/newbot`
|
|
97
|
+
3. Follow the instructions
|
|
98
|
+
|
|
99
|
+
## License
|
|
100
|
+
|
|
101
|
+
MIT
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61.0", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "xc-bot"
|
|
7
|
+
version = "1.0.0"
|
|
8
|
+
description = "Python SDK for XaneoConnect Bot API"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
authors = [{name = "Xaneo Team", email = "ltpddwk@gmail.com"}]
|
|
11
|
+
license = {text = "MIT"}
|
|
12
|
+
requires-python = ">=3.8"
|
|
13
|
+
dependencies = [
|
|
14
|
+
"aiohttp>=3.8.0",
|
|
15
|
+
"grpcio>=1.60.0",
|
|
16
|
+
"protobuf>=4.25.0",
|
|
17
|
+
]
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
keywords = ["xaneo", "bot", "api", "sdk", "async"]
|
|
21
|
+
classifiers = [
|
|
22
|
+
"Development Status :: 4 - Beta",
|
|
23
|
+
"Intended Audience :: Developers",
|
|
24
|
+
"License :: OSI Approved :: MIT License",
|
|
25
|
+
"Programming Language :: Python :: 3",
|
|
26
|
+
"Programming Language :: Python :: 3.8",
|
|
27
|
+
"Programming Language :: Python :: 3.9",
|
|
28
|
+
"Programming Language :: Python :: 3.10",
|
|
29
|
+
"Programming Language :: Python :: 3.11",
|
|
30
|
+
"Programming Language :: Python :: 3.12",
|
|
31
|
+
"Framework :: AsyncIO",
|
|
32
|
+
]
|
|
33
|
+
|
|
34
|
+
[project.urls]
|
|
35
|
+
Homepage = "https://github.com/saneome2/xc-bot"
|
|
36
|
+
Documentation = "https://xaneo.com/bot-api"
|
|
37
|
+
Repository = "https://github.com/saneome2/xc-bot"
|
|
38
|
+
|
|
39
|
+
[project.optional-dependencies]
|
|
40
|
+
dev = [
|
|
41
|
+
"pytest>=7.0.0",
|
|
42
|
+
"pytest-asyncio>=0.21.0",
|
|
43
|
+
"aioresponses>=0.7.0",
|
|
44
|
+
"black>=23.0.0",
|
|
45
|
+
"mypy>=1.0.0",
|
|
46
|
+
]
|
|
47
|
+
|
|
48
|
+
[tool.setuptools.packages.find]
|
|
49
|
+
where = ["."]
|
|
50
|
+
include = ["xc_bot*"]
|
|
51
|
+
|
|
52
|
+
[tool.pytest.ini_options]
|
|
53
|
+
asyncio_mode = "auto"
|
|
54
|
+
testpaths = ["tests"]
|
|
55
|
+
|
|
56
|
+
[tool.mypy]
|
|
57
|
+
python_version = "3.8"
|
|
58
|
+
warn_return_any = true
|
|
59
|
+
warn_unused_configs = true
|
|
60
|
+
disallow_untyped_defs = true
|
|
61
|
+
|
|
62
|
+
[tool.black]
|
|
63
|
+
line-length = 100
|
|
64
|
+
target-version = ["py38"]
|
xc_bot-1.0.0/setup.cfg
ADDED
xc_bot-1.0.0/setup.py
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
from setuptools import setup, find_packages
|
|
2
|
+
|
|
3
|
+
setup(
|
|
4
|
+
name="xc-bot",
|
|
5
|
+
version="1.0.0",
|
|
6
|
+
description="Python SDK for XaneoConnect Bot API with gRPC support",
|
|
7
|
+
author="Xaneo Team",
|
|
8
|
+
packages=find_packages(include=["xc_bot", "xc_bot.*"]),
|
|
9
|
+
install_requires=[
|
|
10
|
+
"aiohttp>=3.8.0",
|
|
11
|
+
"grpcio>=1.60.0",
|
|
12
|
+
"protobuf>=4.25.0",
|
|
13
|
+
],
|
|
14
|
+
python_requires=">=3.8",
|
|
15
|
+
)
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
"""Tests for API client"""
|
|
2
|
+
import pytest
|
|
3
|
+
from aioresponses import aioresponses
|
|
4
|
+
|
|
5
|
+
from xc_bot.api import ApiClient
|
|
6
|
+
from xc_bot.exceptions import ApiError, RateLimitError, WebhookConflictError
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@pytest.mark.asyncio
|
|
10
|
+
async def test_get_me():
|
|
11
|
+
client = ApiClient("xbot_123_test", "https://test.com/bot")
|
|
12
|
+
|
|
13
|
+
with aioresponses() as m:
|
|
14
|
+
m.get(
|
|
15
|
+
"https://test.com/bot/getMe",
|
|
16
|
+
payload={"ok": True, "result": {"id": 123, "username": "test_bot"}},
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
result = await client.get_me()
|
|
20
|
+
assert result["id"] == 123
|
|
21
|
+
assert result["username"] == "test_bot"
|
|
22
|
+
|
|
23
|
+
await client.close()
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@pytest.mark.asyncio
|
|
27
|
+
async def test_send_message():
|
|
28
|
+
client = ApiClient("xbot_123_test", "https://test.com/bot")
|
|
29
|
+
|
|
30
|
+
with aioresponses() as m:
|
|
31
|
+
m.post(
|
|
32
|
+
"https://test.com/bot/sendMessage",
|
|
33
|
+
payload={"ok": True, "result": {"message_id": 1, "chat_id": "personal_1_2"}},
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
result = await client.send_message("personal_1_2", "Hello!")
|
|
37
|
+
assert result["message_id"] == 1
|
|
38
|
+
|
|
39
|
+
await client.close()
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@pytest.mark.asyncio
|
|
43
|
+
async def test_api_error():
|
|
44
|
+
client = ApiClient("xbot_123_test", "https://test.com/bot")
|
|
45
|
+
|
|
46
|
+
with aioresponses() as m:
|
|
47
|
+
m.get(
|
|
48
|
+
"https://test.com/bot/getMe",
|
|
49
|
+
payload={"ok": False, "error_code": 401, "description": "Unauthorized"},
|
|
50
|
+
status=401,
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
with pytest.raises(ApiError) as exc_info:
|
|
54
|
+
await client.get_me()
|
|
55
|
+
|
|
56
|
+
assert exc_info.value.error_code == 401
|
|
57
|
+
|
|
58
|
+
await client.close()
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
@pytest.mark.asyncio
|
|
62
|
+
async def test_rate_limit_error():
|
|
63
|
+
client = ApiClient("xbot_123_test", "https://test.com/bot")
|
|
64
|
+
|
|
65
|
+
with aioresponses() as m:
|
|
66
|
+
m.get(
|
|
67
|
+
"https://test.com/bot/getMe",
|
|
68
|
+
payload={"ok": False, "error_code": 429, "description": "Too many requests"},
|
|
69
|
+
status=429,
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
with pytest.raises(RateLimitError):
|
|
73
|
+
await client.get_me()
|
|
74
|
+
|
|
75
|
+
await client.close()
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
@pytest.mark.asyncio
|
|
79
|
+
async def test_webhook_conflict_error():
|
|
80
|
+
client = ApiClient("xbot_123_test", "https://test.com/bot")
|
|
81
|
+
|
|
82
|
+
with aioresponses() as m:
|
|
83
|
+
m.post(
|
|
84
|
+
"https://test.com/bot/getUpdates",
|
|
85
|
+
payload={"ok": False, "error_code": 409, "description": "Webhook active"},
|
|
86
|
+
status=409,
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
with pytest.raises(WebhookConflictError):
|
|
90
|
+
await client.get_updates()
|
|
91
|
+
|
|
92
|
+
await client.close()
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
"""Tests for Bot class"""
|
|
2
|
+
import pytest
|
|
3
|
+
from aioresponses import aioresponses
|
|
4
|
+
|
|
5
|
+
from xc_bot import Bot
|
|
6
|
+
from xc_bot.types import BotAccount
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@pytest.mark.asyncio
|
|
10
|
+
async def test_bot_get_me(bot):
|
|
11
|
+
with aioresponses() as m:
|
|
12
|
+
m.get(
|
|
13
|
+
"https://test.xaneo.com/bot/getMe",
|
|
14
|
+
payload={
|
|
15
|
+
"ok": True,
|
|
16
|
+
"result": {
|
|
17
|
+
"id": 12345,
|
|
18
|
+
"username": "test_bot",
|
|
19
|
+
"display_name": "Test Bot",
|
|
20
|
+
"can_join_groups": True,
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
result = await bot.get_me()
|
|
26
|
+
assert isinstance(result, BotAccount)
|
|
27
|
+
assert result.id == 12345
|
|
28
|
+
assert result.username == "test_bot"
|
|
29
|
+
|
|
30
|
+
await bot.close()
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@pytest.mark.asyncio
|
|
34
|
+
async def test_bot_send_message(bot):
|
|
35
|
+
with aioresponses() as m:
|
|
36
|
+
m.post(
|
|
37
|
+
"https://test.xaneo.com/bot/sendMessage",
|
|
38
|
+
payload={
|
|
39
|
+
"ok": True,
|
|
40
|
+
"result": {"message_id": 1, "chat_id": "personal_1_2", "date": 1234567890},
|
|
41
|
+
},
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
message = await bot.send_message("personal_1_2", "Hello!")
|
|
45
|
+
assert message.id == 1
|
|
46
|
+
assert message.chat_id == "personal_1_2"
|
|
47
|
+
|
|
48
|
+
await bot.close()
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
@pytest.mark.asyncio
|
|
52
|
+
async def test_bot_command_registration():
|
|
53
|
+
bot = Bot("xbot_test", "https://test.com/bot")
|
|
54
|
+
|
|
55
|
+
@bot.command("start")
|
|
56
|
+
async def start_handler(message):
|
|
57
|
+
pass
|
|
58
|
+
|
|
59
|
+
assert "start" in bot._handlers._command_handlers
|
|
60
|
+
await bot.close()
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
@pytest.mark.asyncio
|
|
64
|
+
async def test_bot_hears_registration():
|
|
65
|
+
bot = Bot("xbot_test", "https://test.com/bot")
|
|
66
|
+
|
|
67
|
+
@bot.hears(r"^/echo (.+)$")
|
|
68
|
+
async def echo_handler(message, match):
|
|
69
|
+
pass
|
|
70
|
+
|
|
71
|
+
assert len(bot._handlers._regex_handlers) == 1
|
|
72
|
+
await bot.close()
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
@pytest.mark.asyncio
|
|
76
|
+
async def test_bot_message_registration():
|
|
77
|
+
bot = Bot("xbot_test", "https://test.com/bot")
|
|
78
|
+
|
|
79
|
+
@bot.message
|
|
80
|
+
async def all_messages(message):
|
|
81
|
+
pass
|
|
82
|
+
|
|
83
|
+
assert len(bot._handlers._message_handlers) == 1
|
|
84
|
+
await bot.close()
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
"""Tests for handler system"""
|
|
2
|
+
import pytest
|
|
3
|
+
|
|
4
|
+
from xc_bot import Bot
|
|
5
|
+
from xc_bot.handlers import HandlerRegistry
|
|
6
|
+
from xc_bot.types import Message, User
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@pytest.fixture
|
|
10
|
+
def handler_registry():
|
|
11
|
+
return HandlerRegistry()
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@pytest.fixture
|
|
15
|
+
def sample_message():
|
|
16
|
+
return Message(
|
|
17
|
+
id=1,
|
|
18
|
+
chat_id="personal_1_2",
|
|
19
|
+
author=User(id=1, username="user"),
|
|
20
|
+
text="/start",
|
|
21
|
+
created_at=None,
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@pytest.mark.asyncio
|
|
26
|
+
async def test_command_handler(handler_registry, sample_message):
|
|
27
|
+
called = False
|
|
28
|
+
|
|
29
|
+
async def start_handler(message):
|
|
30
|
+
nonlocal called
|
|
31
|
+
called = True
|
|
32
|
+
|
|
33
|
+
handler_registry.add_command("start", start_handler)
|
|
34
|
+
await handler_registry.process(sample_message)
|
|
35
|
+
|
|
36
|
+
assert called
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@pytest.mark.asyncio
|
|
40
|
+
async def test_regex_handler(handler_registry):
|
|
41
|
+
message = Message(
|
|
42
|
+
id=1,
|
|
43
|
+
chat_id="test",
|
|
44
|
+
author=User(id=1, username="user"),
|
|
45
|
+
text="/echo hello world",
|
|
46
|
+
created_at=None,
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
captured_match = None
|
|
50
|
+
|
|
51
|
+
async def echo_handler(message, match):
|
|
52
|
+
nonlocal captured_match
|
|
53
|
+
captured_match = match
|
|
54
|
+
|
|
55
|
+
handler_registry.add_regex(r"^/echo (.+)$", echo_handler)
|
|
56
|
+
await handler_registry.process(message)
|
|
57
|
+
|
|
58
|
+
assert captured_match is not None
|
|
59
|
+
assert captured_match.group(1) == "hello world"
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
@pytest.mark.asyncio
|
|
63
|
+
async def test_message_handler(handler_registry):
|
|
64
|
+
message = Message(
|
|
65
|
+
id=1,
|
|
66
|
+
chat_id="test",
|
|
67
|
+
author=User(id=1, username="user"),
|
|
68
|
+
text="Hello",
|
|
69
|
+
created_at=None,
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
called = False
|
|
73
|
+
|
|
74
|
+
async def all_handler(message):
|
|
75
|
+
nonlocal called
|
|
76
|
+
called = True
|
|
77
|
+
|
|
78
|
+
handler_registry.add_message(all_handler)
|
|
79
|
+
await handler_registry.process(message)
|
|
80
|
+
|
|
81
|
+
assert called
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
@pytest.mark.asyncio
|
|
85
|
+
async def test_command_priority(handler_registry):
|
|
86
|
+
command_called = False
|
|
87
|
+
message_called = False
|
|
88
|
+
|
|
89
|
+
async def start_handler(message):
|
|
90
|
+
nonlocal command_called
|
|
91
|
+
command_called = True
|
|
92
|
+
|
|
93
|
+
async def all_handler(message):
|
|
94
|
+
nonlocal message_called
|
|
95
|
+
message_called = True
|
|
96
|
+
|
|
97
|
+
handler_registry.add_command("start", start_handler)
|
|
98
|
+
handler_registry.add_message(all_handler)
|
|
99
|
+
|
|
100
|
+
message = Message(
|
|
101
|
+
id=1,
|
|
102
|
+
chat_id="test",
|
|
103
|
+
author=User(id=1, username="user"),
|
|
104
|
+
text="/start",
|
|
105
|
+
created_at=None,
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
await handler_registry.process(message)
|
|
109
|
+
|
|
110
|
+
assert command_called
|
|
111
|
+
assert not message_called
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"""xc-bot - Python SDK for XaneoConnect Bot API"""
|
|
2
|
+
|
|
3
|
+
__version__ = "1.0.0"
|
|
4
|
+
|
|
5
|
+
from .bot import Bot
|
|
6
|
+
from .grpc_client import GrpcBotClient
|
|
7
|
+
from .exceptions import (
|
|
8
|
+
XcBotError,
|
|
9
|
+
ApiError,
|
|
10
|
+
NetworkError,
|
|
11
|
+
RateLimitError,
|
|
12
|
+
WebhookConflictError,
|
|
13
|
+
)
|
|
14
|
+
from .types import (
|
|
15
|
+
User,
|
|
16
|
+
BotAccount,
|
|
17
|
+
Message,
|
|
18
|
+
Chat,
|
|
19
|
+
ChatType,
|
|
20
|
+
Update,
|
|
21
|
+
UpdateType,
|
|
22
|
+
)
|
|
23
|
+
from .webhook import WebhookServer
|
|
24
|
+
|
|
25
|
+
__all__ = [
|
|
26
|
+
"Bot",
|
|
27
|
+
"GrpcBotClient",
|
|
28
|
+
"WebhookServer",
|
|
29
|
+
"XcBotError",
|
|
30
|
+
"ApiError",
|
|
31
|
+
"NetworkError",
|
|
32
|
+
"RateLimitError",
|
|
33
|
+
"WebhookConflictError",
|
|
34
|
+
"User",
|
|
35
|
+
"BotAccount",
|
|
36
|
+
"Message",
|
|
37
|
+
"Chat",
|
|
38
|
+
"ChatType",
|
|
39
|
+
"Update",
|
|
40
|
+
"UpdateType",
|
|
41
|
+
]
|
|
42
|
+
|