realchat-sdk 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.
- realchat_sdk-0.1.0/.gitignore +10 -0
- realchat_sdk-0.1.0/.python-version +1 -0
- realchat_sdk-0.1.0/PKG-INFO +114 -0
- realchat_sdk-0.1.0/README.md +89 -0
- realchat_sdk-0.1.0/pyproject.toml +39 -0
- realchat_sdk-0.1.0/realchat/__init__.py +5 -0
- realchat_sdk-0.1.0/realchat/client.py +152 -0
- realchat_sdk-0.1.0/realchat/http.py +77 -0
- realchat_sdk-0.1.0/realchat/models.py +144 -0
- realchat_sdk-0.1.0/uv.lock +784 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
3.12
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: realchat-sdk
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Python SDK for building bots on the RealChat platform
|
|
5
|
+
Project-URL: Homepage, https://github.com/838288383838383/realchat-sdk
|
|
6
|
+
Project-URL: Documentation, https://github.com/838288383838383/realchat-sdk#readme
|
|
7
|
+
Project-URL: Repository, https://github.com/838288383838383/realchat-sdk
|
|
8
|
+
Project-URL: Issues, https://github.com/838288383838383/realchat-sdk/issues
|
|
9
|
+
Author-email: Real Inc <real@realinc.dev>
|
|
10
|
+
License-Expression: MIT
|
|
11
|
+
Keywords: bot,chat,realchat,sdk,websocket
|
|
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.10
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
20
|
+
Classifier: Topic :: Communications :: Chat
|
|
21
|
+
Requires-Python: >=3.10
|
|
22
|
+
Requires-Dist: aiohttp>=3.14.1
|
|
23
|
+
Requires-Dist: websockets>=16.0
|
|
24
|
+
Description-Content-Type: text/markdown
|
|
25
|
+
|
|
26
|
+
# realchat-sdk
|
|
27
|
+
|
|
28
|
+
Python SDK for building bots on the [RealChat](https://realchat.vercel.app) platform.
|
|
29
|
+
|
|
30
|
+
## Installation
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
pip install realchat-sdk
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Quick Start
|
|
37
|
+
|
|
38
|
+
```python
|
|
39
|
+
import asyncio
|
|
40
|
+
from realchat import Client
|
|
41
|
+
|
|
42
|
+
client = Client(token="RC.your_bot_token_here")
|
|
43
|
+
|
|
44
|
+
@client.event("on_ready")
|
|
45
|
+
async def on_ready():
|
|
46
|
+
print(f"Logged in as {client.user.username}")
|
|
47
|
+
|
|
48
|
+
@client.event("on_message")
|
|
49
|
+
async def on_message(message):
|
|
50
|
+
if message.content == "!ping":
|
|
51
|
+
await message.channel.send("Pong!")
|
|
52
|
+
|
|
53
|
+
if message.content == "!hello":
|
|
54
|
+
await message.channel.send(f"Hello {message.author.username}!")
|
|
55
|
+
|
|
56
|
+
asyncio.run(client.start())
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Events
|
|
60
|
+
|
|
61
|
+
| Event | Description |
|
|
62
|
+
|-------|-------------|
|
|
63
|
+
| `on_ready` | Bot connected and authenticated |
|
|
64
|
+
| `on_message` | Message received in any channel |
|
|
65
|
+
| `on_typing` | User started typing |
|
|
66
|
+
| `on_presence` | User status changed |
|
|
67
|
+
| `on_disconnect` | WebSocket disconnected |
|
|
68
|
+
|
|
69
|
+
## REST API
|
|
70
|
+
|
|
71
|
+
```python
|
|
72
|
+
from realchat import Client
|
|
73
|
+
|
|
74
|
+
client = Client(token="RC.your_bot_token_here")
|
|
75
|
+
|
|
76
|
+
async def main():
|
|
77
|
+
# Get bot's user info
|
|
78
|
+
user = await client.http.get_me()
|
|
79
|
+
print(f"I am {user.username}")
|
|
80
|
+
|
|
81
|
+
# List servers the bot is in
|
|
82
|
+
servers = await client.http.get_servers()
|
|
83
|
+
for server in servers:
|
|
84
|
+
print(f" - {server.name} ({server.id})")
|
|
85
|
+
|
|
86
|
+
# Get channels in a server
|
|
87
|
+
channels = await client.http.get_channels(servers[0].id)
|
|
88
|
+
for channel in channels:
|
|
89
|
+
print(f" # {channel.name}")
|
|
90
|
+
|
|
91
|
+
# Send a message
|
|
92
|
+
await client.http.send_message(channel.id, "Hello from my bot!")
|
|
93
|
+
|
|
94
|
+
# Read messages
|
|
95
|
+
messages = await client.http.get_messages(channel.id, limit=10)
|
|
96
|
+
for msg in messages:
|
|
97
|
+
print(f" {msg.author.username}: {msg.content}")
|
|
98
|
+
|
|
99
|
+
await client.close()
|
|
100
|
+
|
|
101
|
+
import asyncio
|
|
102
|
+
asyncio.run(main())
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
## Getting a Bot Token
|
|
106
|
+
|
|
107
|
+
1. Go to the [Developer Portal](https://realchat.vercel.app/developer)
|
|
108
|
+
2. Create a new bot
|
|
109
|
+
3. Copy the bot token (starts with `RC.`)
|
|
110
|
+
4. Use it in your code
|
|
111
|
+
|
|
112
|
+
## License
|
|
113
|
+
|
|
114
|
+
MIT
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
# realchat-sdk
|
|
2
|
+
|
|
3
|
+
Python SDK for building bots on the [RealChat](https://realchat.vercel.app) platform.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pip install realchat-sdk
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Quick Start
|
|
12
|
+
|
|
13
|
+
```python
|
|
14
|
+
import asyncio
|
|
15
|
+
from realchat import Client
|
|
16
|
+
|
|
17
|
+
client = Client(token="RC.your_bot_token_here")
|
|
18
|
+
|
|
19
|
+
@client.event("on_ready")
|
|
20
|
+
async def on_ready():
|
|
21
|
+
print(f"Logged in as {client.user.username}")
|
|
22
|
+
|
|
23
|
+
@client.event("on_message")
|
|
24
|
+
async def on_message(message):
|
|
25
|
+
if message.content == "!ping":
|
|
26
|
+
await message.channel.send("Pong!")
|
|
27
|
+
|
|
28
|
+
if message.content == "!hello":
|
|
29
|
+
await message.channel.send(f"Hello {message.author.username}!")
|
|
30
|
+
|
|
31
|
+
asyncio.run(client.start())
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Events
|
|
35
|
+
|
|
36
|
+
| Event | Description |
|
|
37
|
+
|-------|-------------|
|
|
38
|
+
| `on_ready` | Bot connected and authenticated |
|
|
39
|
+
| `on_message` | Message received in any channel |
|
|
40
|
+
| `on_typing` | User started typing |
|
|
41
|
+
| `on_presence` | User status changed |
|
|
42
|
+
| `on_disconnect` | WebSocket disconnected |
|
|
43
|
+
|
|
44
|
+
## REST API
|
|
45
|
+
|
|
46
|
+
```python
|
|
47
|
+
from realchat import Client
|
|
48
|
+
|
|
49
|
+
client = Client(token="RC.your_bot_token_here")
|
|
50
|
+
|
|
51
|
+
async def main():
|
|
52
|
+
# Get bot's user info
|
|
53
|
+
user = await client.http.get_me()
|
|
54
|
+
print(f"I am {user.username}")
|
|
55
|
+
|
|
56
|
+
# List servers the bot is in
|
|
57
|
+
servers = await client.http.get_servers()
|
|
58
|
+
for server in servers:
|
|
59
|
+
print(f" - {server.name} ({server.id})")
|
|
60
|
+
|
|
61
|
+
# Get channels in a server
|
|
62
|
+
channels = await client.http.get_channels(servers[0].id)
|
|
63
|
+
for channel in channels:
|
|
64
|
+
print(f" # {channel.name}")
|
|
65
|
+
|
|
66
|
+
# Send a message
|
|
67
|
+
await client.http.send_message(channel.id, "Hello from my bot!")
|
|
68
|
+
|
|
69
|
+
# Read messages
|
|
70
|
+
messages = await client.http.get_messages(channel.id, limit=10)
|
|
71
|
+
for msg in messages:
|
|
72
|
+
print(f" {msg.author.username}: {msg.content}")
|
|
73
|
+
|
|
74
|
+
await client.close()
|
|
75
|
+
|
|
76
|
+
import asyncio
|
|
77
|
+
asyncio.run(main())
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
## Getting a Bot Token
|
|
81
|
+
|
|
82
|
+
1. Go to the [Developer Portal](https://realchat.vercel.app/developer)
|
|
83
|
+
2. Create a new bot
|
|
84
|
+
3. Copy the bot token (starts with `RC.`)
|
|
85
|
+
4. Use it in your code
|
|
86
|
+
|
|
87
|
+
## License
|
|
88
|
+
|
|
89
|
+
MIT
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "realchat-sdk"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "Python SDK for building bots on the RealChat platform"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
license = "MIT"
|
|
7
|
+
requires-python = ">=3.10"
|
|
8
|
+
authors = [
|
|
9
|
+
{ name = "Real Inc", email = "real@realinc.dev" },
|
|
10
|
+
]
|
|
11
|
+
keywords = ["realchat", "bot", "sdk", "chat", "websocket"]
|
|
12
|
+
classifiers = [
|
|
13
|
+
"Development Status :: 4 - Beta",
|
|
14
|
+
"Intended Audience :: Developers",
|
|
15
|
+
"License :: OSI Approved :: MIT License",
|
|
16
|
+
"Programming Language :: Python :: 3",
|
|
17
|
+
"Programming Language :: Python :: 3.10",
|
|
18
|
+
"Programming Language :: Python :: 3.11",
|
|
19
|
+
"Programming Language :: Python :: 3.12",
|
|
20
|
+
"Programming Language :: Python :: 3.13",
|
|
21
|
+
"Topic :: Communications :: Chat",
|
|
22
|
+
]
|
|
23
|
+
dependencies = [
|
|
24
|
+
"aiohttp>=3.14.1",
|
|
25
|
+
"websockets>=16.0",
|
|
26
|
+
]
|
|
27
|
+
|
|
28
|
+
[project.urls]
|
|
29
|
+
Homepage = "https://github.com/838288383838383/realchat-sdk"
|
|
30
|
+
Documentation = "https://github.com/838288383838383/realchat-sdk#readme"
|
|
31
|
+
Repository = "https://github.com/838288383838383/realchat-sdk"
|
|
32
|
+
Issues = "https://github.com/838288383838383/realchat-sdk/issues"
|
|
33
|
+
|
|
34
|
+
[build-system]
|
|
35
|
+
requires = ["hatchling"]
|
|
36
|
+
build-backend = "hatchling.build"
|
|
37
|
+
|
|
38
|
+
[tool.hatch.build.targets.wheel]
|
|
39
|
+
packages = ["realchat"]
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
import json
|
|
5
|
+
import logging
|
|
6
|
+
from typing import Any, Callable, Coroutine, Optional
|
|
7
|
+
|
|
8
|
+
import websockets
|
|
9
|
+
from websockets.client import WebSocketClientProtocol
|
|
10
|
+
|
|
11
|
+
from realchat.http import HTTPClient, RealChatAPIError
|
|
12
|
+
from realchat.models import Message, Server, Channel, User
|
|
13
|
+
|
|
14
|
+
logger = logging.getLogger("realchat")
|
|
15
|
+
|
|
16
|
+
Callback = Callable[..., Coroutine[Any, Any, Any]]
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class Client:
|
|
20
|
+
"""Main client for interacting with RealChat as a bot.
|
|
21
|
+
|
|
22
|
+
Usage:
|
|
23
|
+
from realchat import Client
|
|
24
|
+
|
|
25
|
+
client = Client(token="RC.your_bot_token_here")
|
|
26
|
+
|
|
27
|
+
@client.event("on_ready")
|
|
28
|
+
async def ready():
|
|
29
|
+
print(f"Logged in as {client.user.username}")
|
|
30
|
+
|
|
31
|
+
@client.event("on_message")
|
|
32
|
+
async def on_message(message: Message):
|
|
33
|
+
if message.content == "!ping":
|
|
34
|
+
await message.channel.send("Pong!")
|
|
35
|
+
|
|
36
|
+
await client.start()
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
def __init__(
|
|
40
|
+
self,
|
|
41
|
+
token: str,
|
|
42
|
+
*,
|
|
43
|
+
api_url: str = "https://realchat-server.onrender.com/api",
|
|
44
|
+
ws_url: str = "wss://realchat-server.onrender.com",
|
|
45
|
+
):
|
|
46
|
+
self.token = token
|
|
47
|
+
self.http = HTTPClient(api_url, token)
|
|
48
|
+
self.user: Optional[User] = None
|
|
49
|
+
self._ws: Optional[WebSocketClientProtocol] = None
|
|
50
|
+
self._events: dict[str, list[Callback]] = {}
|
|
51
|
+
self._running = False
|
|
52
|
+
|
|
53
|
+
def event(self, name: str) -> Callable[[Callback], Callback]:
|
|
54
|
+
"""Decorator to register an event handler.
|
|
55
|
+
|
|
56
|
+
Events:
|
|
57
|
+
on_ready - Called when connected
|
|
58
|
+
on_message - Called when a message is received
|
|
59
|
+
on_disconnect - Called when disconnected
|
|
60
|
+
"""
|
|
61
|
+
def decorator(func: Callback) -> Callback:
|
|
62
|
+
self._events.setdefault(name, []).append(func)
|
|
63
|
+
return func
|
|
64
|
+
return decorator
|
|
65
|
+
|
|
66
|
+
async def start(self):
|
|
67
|
+
"""Start the bot. Connects to API and WebSocket."""
|
|
68
|
+
self.user = await self.http.get_me()
|
|
69
|
+
logger.info(f"Authenticated as {self.user.username} (id: {self.user.id})")
|
|
70
|
+
await self._dispatch("on_ready")
|
|
71
|
+
self._running = True
|
|
72
|
+
await self._connect_ws()
|
|
73
|
+
|
|
74
|
+
async def close(self):
|
|
75
|
+
"""Disconnect and clean up."""
|
|
76
|
+
self._running = False
|
|
77
|
+
if self._ws:
|
|
78
|
+
await self._ws.close()
|
|
79
|
+
await self.http.close()
|
|
80
|
+
|
|
81
|
+
async def _connect_ws(self):
|
|
82
|
+
"""Connect to the WebSocket server and listen for events."""
|
|
83
|
+
while self._running:
|
|
84
|
+
try:
|
|
85
|
+
async with websockets.connect(
|
|
86
|
+
self.ws_url,
|
|
87
|
+
additional_headers={"Cookie": f"token={self.token}"},
|
|
88
|
+
) as ws:
|
|
89
|
+
self._ws = ws
|
|
90
|
+
logger.info("WebSocket connected")
|
|
91
|
+
|
|
92
|
+
# Authenticate
|
|
93
|
+
await ws.send(json.dumps({
|
|
94
|
+
"type": "auth",
|
|
95
|
+
"token": self.token,
|
|
96
|
+
}))
|
|
97
|
+
|
|
98
|
+
async for raw in ws:
|
|
99
|
+
try:
|
|
100
|
+
data = json.loads(raw)
|
|
101
|
+
await self._handle_event(data)
|
|
102
|
+
except json.JSONDecodeError:
|
|
103
|
+
logger.warning(f"Invalid JSON: {raw}")
|
|
104
|
+
|
|
105
|
+
except websockets.ConnectionClosed:
|
|
106
|
+
logger.warning("WebSocket disconnected, reconnecting in 5s...")
|
|
107
|
+
await self._dispatch("on_disconnect")
|
|
108
|
+
await asyncio.sleep(5)
|
|
109
|
+
except Exception as e:
|
|
110
|
+
logger.error(f"WebSocket error: {e}")
|
|
111
|
+
await self._dispatch("on_disconnect")
|
|
112
|
+
if self._running:
|
|
113
|
+
await asyncio.sleep(5)
|
|
114
|
+
|
|
115
|
+
async def _handle_event(self, data: dict):
|
|
116
|
+
"""Route incoming WebSocket events to handlers."""
|
|
117
|
+
event_type = data.get("type")
|
|
118
|
+
|
|
119
|
+
if event_type == "message":
|
|
120
|
+
msg = Message.from_dict(data.get("message", data))
|
|
121
|
+
await self._dispatch("on_message", msg)
|
|
122
|
+
|
|
123
|
+
elif event_type == "typing":
|
|
124
|
+
user_id = data.get("userId")
|
|
125
|
+
channel_id = data.get("channelId")
|
|
126
|
+
await self._dispatch("on_typing", user_id, channel_id)
|
|
127
|
+
|
|
128
|
+
elif event_type == "user_status":
|
|
129
|
+
user_id = data.get("userId")
|
|
130
|
+
status = data.get("status")
|
|
131
|
+
await self._dispatch("on_presence", user_id, status)
|
|
132
|
+
|
|
133
|
+
elif event_type == "error":
|
|
134
|
+
logger.error(f"Server error: {data.get('message', 'Unknown error')}")
|
|
135
|
+
|
|
136
|
+
async def _dispatch(self, event_name: str, *args):
|
|
137
|
+
"""Call all registered handlers for an event."""
|
|
138
|
+
for handler in self._events.get(event_name, []):
|
|
139
|
+
try:
|
|
140
|
+
await handler(*args)
|
|
141
|
+
except Exception as e:
|
|
142
|
+
logger.error(f"Error in {event_name} handler: {e}")
|
|
143
|
+
|
|
144
|
+
async def get_servers(self) -> list[Server]:
|
|
145
|
+
return await self.http.get_servers()
|
|
146
|
+
|
|
147
|
+
async def get_channels(self, server_id: str) -> list[Channel]:
|
|
148
|
+
return await self.http.get_channels(server_id)
|
|
149
|
+
|
|
150
|
+
async def send(self, channel_id: str, content: str) -> Message:
|
|
151
|
+
"""Send a message to a channel."""
|
|
152
|
+
return await self.http.send_message(channel_id, content)
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any, Optional
|
|
4
|
+
import aiohttp
|
|
5
|
+
|
|
6
|
+
from realchat.models import User, Server, Channel, Message
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class HTTPClient:
|
|
10
|
+
def __init__(self, base_url: str, token: str):
|
|
11
|
+
self.base_url = base_url.rstrip("/")
|
|
12
|
+
self.token = token
|
|
13
|
+
self._session: Optional[aiohttp.ClientSession] = None
|
|
14
|
+
|
|
15
|
+
async def _get_session(self) -> aiohttp.ClientSession:
|
|
16
|
+
if self._session is None or self._session.closed:
|
|
17
|
+
self._session = aiohttp.ClientSession(
|
|
18
|
+
headers={"Authorization": f"Bot {self.token}"},
|
|
19
|
+
timeout=aiohttp.ClientTimeout(total=30),
|
|
20
|
+
)
|
|
21
|
+
return self._session
|
|
22
|
+
|
|
23
|
+
async def close(self):
|
|
24
|
+
if self._session and not self._session.closed:
|
|
25
|
+
await self._session.close()
|
|
26
|
+
|
|
27
|
+
async def _request(self, method: str, path: str, **kwargs) -> Any:
|
|
28
|
+
session = await self._get_session()
|
|
29
|
+
url = f"{self.base_url}{path}"
|
|
30
|
+
async with session.request(method, url, **kwargs) as resp:
|
|
31
|
+
data = await resp.json()
|
|
32
|
+
if resp.status >= 400:
|
|
33
|
+
error_msg = data.get("error", f"HTTP {resp.status}")
|
|
34
|
+
raise RealChatAPIError(resp.status, error_msg)
|
|
35
|
+
return data
|
|
36
|
+
|
|
37
|
+
async def get_me(self) -> User:
|
|
38
|
+
data = await self._request("GET", "/auth/me")
|
|
39
|
+
return User.from_dict(data["user"])
|
|
40
|
+
|
|
41
|
+
async def get_servers(self) -> list[Server]:
|
|
42
|
+
data = await self._request("GET", "/servers")
|
|
43
|
+
return [Server.from_dict(s) for s in data["servers"]]
|
|
44
|
+
|
|
45
|
+
async def get_server(self, server_id: str) -> Server:
|
|
46
|
+
data = await self._request("GET", f"/servers/{server_id}")
|
|
47
|
+
return Server.from_dict(data["server"])
|
|
48
|
+
|
|
49
|
+
async def get_channels(self, server_id: str) -> list[Channel]:
|
|
50
|
+
data = await self._request("GET", f"/servers/{server_id}")
|
|
51
|
+
return [Channel.from_dict(c) for c in data.get("channels", [])]
|
|
52
|
+
|
|
53
|
+
async def create_channel(self, server_id: str, name: str) -> Channel:
|
|
54
|
+
data = await self._request("POST", "/channels", json={"serverId": server_id, "name": name})
|
|
55
|
+
return Channel.from_dict(data["channel"])
|
|
56
|
+
|
|
57
|
+
async def get_messages(self, channel_id: str, limit: int = 50, before: Optional[str] = None) -> list[Message]:
|
|
58
|
+
params = {"limit": str(limit)}
|
|
59
|
+
if before:
|
|
60
|
+
params["before"] = before
|
|
61
|
+
data = await self._request("GET", f"/messages/{channel_id}", params=params)
|
|
62
|
+
return [Message.from_dict(m) for m in data["messages"]]
|
|
63
|
+
|
|
64
|
+
async def send_message(self, channel_id: str, content: str) -> Message:
|
|
65
|
+
data = await self._request("POST", f"/messages/{channel_id}", json={"content": content})
|
|
66
|
+
return Message.from_dict(data["message"])
|
|
67
|
+
|
|
68
|
+
async def delete_message(self, channel_id: str, message_id: str) -> bool:
|
|
69
|
+
await self._request("DELETE", f"/messages/{channel_id}/{message_id}")
|
|
70
|
+
return True
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
class RealChatAPIError(Exception):
|
|
74
|
+
def __init__(self, status: int, message: str):
|
|
75
|
+
self.status = status
|
|
76
|
+
self.message = message
|
|
77
|
+
super().__init__(f"[{status}] {message}")
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass, field
|
|
4
|
+
from datetime import datetime
|
|
5
|
+
from typing import TYPE_CHECKING, Optional
|
|
6
|
+
|
|
7
|
+
if TYPE_CHECKING:
|
|
8
|
+
from realchat.client import Client
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@dataclass
|
|
12
|
+
class User:
|
|
13
|
+
id: str
|
|
14
|
+
email: str
|
|
15
|
+
username: str
|
|
16
|
+
nickname: Optional[str] = None
|
|
17
|
+
avatar: Optional[str] = None
|
|
18
|
+
role: str = "user"
|
|
19
|
+
status: str = "online"
|
|
20
|
+
kids_mode: bool = False
|
|
21
|
+
created_at: Optional[datetime] = None
|
|
22
|
+
|
|
23
|
+
@classmethod
|
|
24
|
+
def from_dict(cls, data: dict) -> User:
|
|
25
|
+
return cls(
|
|
26
|
+
id=data["id"],
|
|
27
|
+
email=data.get("email", ""),
|
|
28
|
+
username=data["username"],
|
|
29
|
+
nickname=data.get("nickname"),
|
|
30
|
+
avatar=data.get("avatar"),
|
|
31
|
+
role=data.get("role", "user"),
|
|
32
|
+
status=data.get("status", "online"),
|
|
33
|
+
kids_mode=data.get("kidsMode", False),
|
|
34
|
+
created_at=_parse_dt(data.get("createdAt")),
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
@dataclass
|
|
39
|
+
class Server:
|
|
40
|
+
id: str
|
|
41
|
+
name: str
|
|
42
|
+
icon: Optional[str] = None
|
|
43
|
+
invite_code: str = ""
|
|
44
|
+
owner_id: str = ""
|
|
45
|
+
created_at: Optional[datetime] = None
|
|
46
|
+
|
|
47
|
+
@classmethod
|
|
48
|
+
def from_dict(cls, data: dict) -> Server:
|
|
49
|
+
return cls(
|
|
50
|
+
id=data["id"],
|
|
51
|
+
name=data["name"],
|
|
52
|
+
icon=data.get("icon"),
|
|
53
|
+
invite_code=data.get("inviteCode", ""),
|
|
54
|
+
owner_id=data.get("ownerId", ""),
|
|
55
|
+
created_at=_parse_dt(data.get("createdAt")),
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
@dataclass
|
|
60
|
+
class Channel:
|
|
61
|
+
id: str
|
|
62
|
+
name: str
|
|
63
|
+
type: str = "text"
|
|
64
|
+
server_id: str = ""
|
|
65
|
+
created_at: Optional[datetime] = None
|
|
66
|
+
|
|
67
|
+
@classmethod
|
|
68
|
+
def from_dict(cls, data: dict) -> Channel:
|
|
69
|
+
return cls(
|
|
70
|
+
id=data["id"],
|
|
71
|
+
name=data["name"],
|
|
72
|
+
type=data.get("type", "text"),
|
|
73
|
+
server_id=data.get("serverId", ""),
|
|
74
|
+
created_at=_parse_dt(data.get("createdAt")),
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
@dataclass
|
|
79
|
+
class Message:
|
|
80
|
+
id: str
|
|
81
|
+
content: str
|
|
82
|
+
author: Optional[User] = None
|
|
83
|
+
channel_id: str = ""
|
|
84
|
+
edited: bool = False
|
|
85
|
+
created_at: Optional[datetime] = None
|
|
86
|
+
|
|
87
|
+
@classmethod
|
|
88
|
+
def from_dict(cls, data: dict) -> Message:
|
|
89
|
+
author = None
|
|
90
|
+
if "author" in data and data["author"]:
|
|
91
|
+
author = User.from_dict(data["author"])
|
|
92
|
+
return cls(
|
|
93
|
+
id=data["id"],
|
|
94
|
+
content=data["content"],
|
|
95
|
+
author=author,
|
|
96
|
+
channel_id=data.get("channelId", ""),
|
|
97
|
+
edited=data.get("edited", False),
|
|
98
|
+
created_at=_parse_dt(data.get("createdAt")),
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
@dataclass
|
|
103
|
+
class Bot:
|
|
104
|
+
id: str
|
|
105
|
+
name: str
|
|
106
|
+
description: Optional[str] = None
|
|
107
|
+
token: str = ""
|
|
108
|
+
secret: str = ""
|
|
109
|
+
public: bool = False
|
|
110
|
+
owner_id: str = ""
|
|
111
|
+
created_at: Optional[datetime] = None
|
|
112
|
+
|
|
113
|
+
@classmethod
|
|
114
|
+
def from_dict(cls, data: dict) -> Bot:
|
|
115
|
+
return cls(
|
|
116
|
+
id=data["id"],
|
|
117
|
+
name=data["name"],
|
|
118
|
+
description=data.get("description"),
|
|
119
|
+
token=data.get("token", ""),
|
|
120
|
+
secret=data.get("secret", ""),
|
|
121
|
+
public=data.get("public", False),
|
|
122
|
+
owner_id=data.get("ownerId", ""),
|
|
123
|
+
created_at=_parse_dt(data.get("createdAt")),
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def _parse_dt(value: Optional[str]) -> Optional[datetime]:
|
|
128
|
+
if not value:
|
|
129
|
+
return None
|
|
130
|
+
try:
|
|
131
|
+
return datetime.fromisoformat(value.replace("Z", "+00:00"))
|
|
132
|
+
except (ValueError, TypeError):
|
|
133
|
+
return None
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
class ChannelProxy:
|
|
137
|
+
"""Proxy that lets you do message.channel.send("content")."""
|
|
138
|
+
|
|
139
|
+
def __init__(self, channel_id: str, client: "Client"):
|
|
140
|
+
self.id = channel_id
|
|
141
|
+
self._client = client
|
|
142
|
+
|
|
143
|
+
async def send(self, content: str) -> "Message":
|
|
144
|
+
return await self._client.send(self.id, content)
|