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 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
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
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
+