zyncedpy 1.0.3__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.
- zyncedpy-1.0.3/PKG-INFO +76 -0
- zyncedpy-1.0.3/README.md +41 -0
- zyncedpy-1.0.3/setup.cfg +4 -0
- zyncedpy-1.0.3/setup.py +36 -0
- zyncedpy-1.0.3/zyncedpy/__init__.py +7 -0
- zyncedpy-1.0.3/zyncedpy/client/__init__.py +6 -0
- zyncedpy-1.0.3/zyncedpy/client/base_client.py +25 -0
- zyncedpy-1.0.3/zyncedpy/client/client.py +63 -0
- zyncedpy-1.0.3/zyncedpy/client/rest_client.py +90 -0
- zyncedpy-1.0.3/zyncedpy/client/websocket_client.py +83 -0
- zyncedpy-1.0.3/zyncedpy/events/__init__.py +3 -0
- zyncedpy-1.0.3/zyncedpy/events/event_emitter.py +44 -0
- zyncedpy-1.0.3/zyncedpy/events/handlers/__init__.py +10 -0
- zyncedpy-1.0.3/zyncedpy/events/handlers/member_joined.py +8 -0
- zyncedpy-1.0.3/zyncedpy/events/handlers/member_left.py +8 -0
- zyncedpy-1.0.3/zyncedpy/events/handlers/message_create.py +8 -0
- zyncedpy-1.0.3/zyncedpy/events/handlers/private_message_create.py +8 -0
- zyncedpy-1.0.3/zyncedpy/events/handlers/user_invite.py +8 -0
- zyncedpy-1.0.3/zyncedpy/managers/__init__.py +5 -0
- zyncedpy-1.0.3/zyncedpy/managers/base_manager.py +42 -0
- zyncedpy-1.0.3/zyncedpy/managers/channel_manager.py +18 -0
- zyncedpy-1.0.3/zyncedpy/managers/guild_manager.py +11 -0
- zyncedpy-1.0.3/zyncedpy/structures/__init__.py +12 -0
- zyncedpy-1.0.3/zyncedpy/structures/base.py +8 -0
- zyncedpy-1.0.3/zyncedpy/structures/base_channel.py +44 -0
- zyncedpy-1.0.3/zyncedpy/structures/channel.py +123 -0
- zyncedpy-1.0.3/zyncedpy/structures/client_user.py +51 -0
- zyncedpy-1.0.3/zyncedpy/structures/guild.py +6 -0
- zyncedpy-1.0.3/zyncedpy/structures/message.py +26 -0
- zyncedpy-1.0.3/zyncedpy/structures/private_message.py +14 -0
- zyncedpy-1.0.3/zyncedpy/types.py +55 -0
- zyncedpy-1.0.3/zyncedpy/utils/__init__.py +3 -0
- zyncedpy-1.0.3/zyncedpy/utils/logger.py +52 -0
- zyncedpy-1.0.3/zyncedpy.egg-info/PKG-INFO +76 -0
- zyncedpy-1.0.3/zyncedpy.egg-info/SOURCES.txt +36 -0
- zyncedpy-1.0.3/zyncedpy.egg-info/dependency_links.txt +1 -0
- zyncedpy-1.0.3/zyncedpy.egg-info/requires.txt +4 -0
- zyncedpy-1.0.3/zyncedpy.egg-info/top_level.txt +1 -0
zyncedpy-1.0.3/PKG-INFO
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: zyncedpy
|
|
3
|
+
Version: 1.0.3
|
|
4
|
+
Summary: ZyncedPy is a Python module that allows you to create bots for Zynced chat platform.
|
|
5
|
+
Home-page: https://github.com/Emsa001/zyncedpy
|
|
6
|
+
Author: Emanuel Scura
|
|
7
|
+
License: MIT
|
|
8
|
+
Keywords: sdk,bot,websocket,api,chat,zynced
|
|
9
|
+
Classifier: Development Status :: 4 - Beta
|
|
10
|
+
Classifier: Intended Audience :: Developers
|
|
11
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
12
|
+
Classifier: Operating System :: OS Independent
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Requires-Python: >=3.8
|
|
20
|
+
Description-Content-Type: text/markdown
|
|
21
|
+
Requires-Dist: requests>=2.25.0
|
|
22
|
+
Requires-Dist: python-socketio>=5.0.0
|
|
23
|
+
Requires-Dist: python-engineio>=4.0.0
|
|
24
|
+
Requires-Dist: dataclasses-json>=0.5.0
|
|
25
|
+
Dynamic: author
|
|
26
|
+
Dynamic: classifier
|
|
27
|
+
Dynamic: description
|
|
28
|
+
Dynamic: description-content-type
|
|
29
|
+
Dynamic: home-page
|
|
30
|
+
Dynamic: keywords
|
|
31
|
+
Dynamic: license
|
|
32
|
+
Dynamic: requires-dist
|
|
33
|
+
Dynamic: requires-python
|
|
34
|
+
Dynamic: summary
|
|
35
|
+
|
|
36
|
+
# ZyncedPy
|
|
37
|
+
|
|
38
|
+
ZyncedPy is a Python module that allows you to create bots for Zynced chat platform.
|
|
39
|
+
|
|
40
|
+
## Installation
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
pip install zyncedpy
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Usage
|
|
47
|
+
|
|
48
|
+
```python
|
|
49
|
+
from zyncedpy import Client
|
|
50
|
+
|
|
51
|
+
client = Client("your_token_here")
|
|
52
|
+
|
|
53
|
+
@client.on("ready")
|
|
54
|
+
def on_ready(data):
|
|
55
|
+
print(f"Bot is ready! Logged in as {data['user'].username}")
|
|
56
|
+
|
|
57
|
+
@client.on("messageCreate")
|
|
58
|
+
def on_message(message):
|
|
59
|
+
if message.content == "!ping":
|
|
60
|
+
message.reply("Pong!")
|
|
61
|
+
|
|
62
|
+
client.login()
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## Features
|
|
66
|
+
|
|
67
|
+
- WebSocket and REST API support
|
|
68
|
+
- Event-driven architecture
|
|
69
|
+
- Channel and guild management
|
|
70
|
+
- Message handling
|
|
71
|
+
- User management
|
|
72
|
+
- Invite system
|
|
73
|
+
|
|
74
|
+
## License
|
|
75
|
+
|
|
76
|
+
MIT
|
zyncedpy-1.0.3/README.md
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# ZyncedPy
|
|
2
|
+
|
|
3
|
+
ZyncedPy is a Python module that allows you to create bots for Zynced chat platform.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pip install zyncedpy
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
```python
|
|
14
|
+
from zyncedpy import Client
|
|
15
|
+
|
|
16
|
+
client = Client("your_token_here")
|
|
17
|
+
|
|
18
|
+
@client.on("ready")
|
|
19
|
+
def on_ready(data):
|
|
20
|
+
print(f"Bot is ready! Logged in as {data['user'].username}")
|
|
21
|
+
|
|
22
|
+
@client.on("messageCreate")
|
|
23
|
+
def on_message(message):
|
|
24
|
+
if message.content == "!ping":
|
|
25
|
+
message.reply("Pong!")
|
|
26
|
+
|
|
27
|
+
client.login()
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Features
|
|
31
|
+
|
|
32
|
+
- WebSocket and REST API support
|
|
33
|
+
- Event-driven architecture
|
|
34
|
+
- Channel and guild management
|
|
35
|
+
- Message handling
|
|
36
|
+
- User management
|
|
37
|
+
- Invite system
|
|
38
|
+
|
|
39
|
+
## License
|
|
40
|
+
|
|
41
|
+
MIT
|
zyncedpy-1.0.3/setup.cfg
ADDED
zyncedpy-1.0.3/setup.py
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
from setuptools import setup, find_packages
|
|
2
|
+
|
|
3
|
+
with open("README.md", "r", encoding="utf-8") as fh:
|
|
4
|
+
long_description = fh.read()
|
|
5
|
+
|
|
6
|
+
setup(
|
|
7
|
+
name="zyncedpy",
|
|
8
|
+
version="1.0.3",
|
|
9
|
+
author="Emanuel Scura",
|
|
10
|
+
description="ZyncedPy is a Python module that allows you to create bots for Zynced chat platform.",
|
|
11
|
+
long_description=long_description,
|
|
12
|
+
long_description_content_type="text/markdown",
|
|
13
|
+
url="https://github.com/Emsa001/zyncedpy",
|
|
14
|
+
packages=find_packages(),
|
|
15
|
+
classifiers=[
|
|
16
|
+
"Development Status :: 4 - Beta",
|
|
17
|
+
"Intended Audience :: Developers",
|
|
18
|
+
"License :: OSI Approved :: MIT License",
|
|
19
|
+
"Operating System :: OS Independent",
|
|
20
|
+
"Programming Language :: Python :: 3",
|
|
21
|
+
"Programming Language :: Python :: 3.8",
|
|
22
|
+
"Programming Language :: Python :: 3.9",
|
|
23
|
+
"Programming Language :: Python :: 3.10",
|
|
24
|
+
"Programming Language :: Python :: 3.11",
|
|
25
|
+
"Programming Language :: Python :: 3.12",
|
|
26
|
+
],
|
|
27
|
+
python_requires=">=3.8",
|
|
28
|
+
install_requires=[
|
|
29
|
+
"requests>=2.25.0",
|
|
30
|
+
"python-socketio>=5.0.0",
|
|
31
|
+
"python-engineio>=4.0.0",
|
|
32
|
+
"dataclasses-json>=0.5.0",
|
|
33
|
+
],
|
|
34
|
+
keywords=["sdk", "bot", "websocket", "api", "chat", "zynced"],
|
|
35
|
+
license="MIT",
|
|
36
|
+
)
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
from ..events import TypedEmitter
|
|
2
|
+
from .rest_client import RestClient
|
|
3
|
+
from .websocket_client import WebSocketClient
|
|
4
|
+
from ..types import ClientOptions, ClientLogger
|
|
5
|
+
from ..utils import resolve_logger
|
|
6
|
+
from typing import Optional
|
|
7
|
+
|
|
8
|
+
class BaseClient(TypedEmitter):
|
|
9
|
+
def __init__(self, token: str, options: Optional[ClientOptions] = None):
|
|
10
|
+
super().__init__()
|
|
11
|
+
if options is None:
|
|
12
|
+
options = ClientOptions()
|
|
13
|
+
|
|
14
|
+
self.logger: ClientLogger = resolve_logger(options.logger)
|
|
15
|
+
self._rest = RestClient(token, 'https://zynced.app/api', self.logger)
|
|
16
|
+
self._ws = WebSocketClient(token, self.logger)
|
|
17
|
+
|
|
18
|
+
def get_rest(self) -> RestClient:
|
|
19
|
+
return self._rest
|
|
20
|
+
|
|
21
|
+
def get_ws(self) -> WebSocketClient:
|
|
22
|
+
return self._ws
|
|
23
|
+
|
|
24
|
+
def destroy(self):
|
|
25
|
+
self._ws.disconnect()
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
from .base_client import BaseClient
|
|
2
|
+
from ..managers import ChannelManager, GuildManager
|
|
3
|
+
from ..structures import ClientUser, Channel
|
|
4
|
+
from ..types import ClientOptions
|
|
5
|
+
from ..events.handlers import (
|
|
6
|
+
handle_private_message_create, handle_message_create,
|
|
7
|
+
handle_member_joined, handle_member_left, handle_user_invite
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
class Client(BaseClient):
|
|
11
|
+
def __init__(self, token: str, options: ClientOptions = None):
|
|
12
|
+
super().__init__(token, options)
|
|
13
|
+
|
|
14
|
+
self.channels = ChannelManager(self)
|
|
15
|
+
self.guilds = GuildManager(self)
|
|
16
|
+
self.user = ClientUser(self)
|
|
17
|
+
|
|
18
|
+
self._setup_event_handlers()
|
|
19
|
+
|
|
20
|
+
def _setup_event_handlers(self):
|
|
21
|
+
self.get_ws().on('privateMessageCreate', lambda data: handle_private_message_create(self, data))
|
|
22
|
+
self.get_ws().on('messageCreate', lambda data: handle_message_create(self, data))
|
|
23
|
+
self.get_ws().on('memberJoined', lambda data: handle_member_joined(self, data))
|
|
24
|
+
self.get_ws().on('memberLeft', lambda data: handle_member_left(self, data))
|
|
25
|
+
self.get_ws().on('userInvite', lambda data: handle_user_invite(self, data))
|
|
26
|
+
|
|
27
|
+
async def login(self):
|
|
28
|
+
self.logger.info('Starting login flow')
|
|
29
|
+
await self.get_ws().connect()
|
|
30
|
+
|
|
31
|
+
response = self.get_rest().get('/data')
|
|
32
|
+
data = response.json()
|
|
33
|
+
|
|
34
|
+
self.user._patch(data['me'])
|
|
35
|
+
|
|
36
|
+
for raw_channel in data['userList']['channels']:
|
|
37
|
+
manager = self.channels if raw_channel['group_id'] else self.guilds
|
|
38
|
+
manager._add(raw_channel)
|
|
39
|
+
|
|
40
|
+
self.emit('ready', {
|
|
41
|
+
'user': self.user,
|
|
42
|
+
'channels': self.channels,
|
|
43
|
+
'guilds': self.guilds,
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
self.logger.info({
|
|
47
|
+
'channels': self.channels.size,
|
|
48
|
+
'guilds': self.guilds.size,
|
|
49
|
+
'userId': self.user.id,
|
|
50
|
+
}, 'Client ready')
|
|
51
|
+
|
|
52
|
+
def channel(self, id: str) -> Channel:
|
|
53
|
+
return self.channels.get(id)
|
|
54
|
+
|
|
55
|
+
async def join_guild(self, guild_id: str):
|
|
56
|
+
response = self.get_rest().post(f'/channels/join/{guild_id}')
|
|
57
|
+
return response.json()
|
|
58
|
+
|
|
59
|
+
async def accept_invite(self, from_user: str):
|
|
60
|
+
response = self.get_rest().post('/invite/accept', json={
|
|
61
|
+
'from_user': from_user,
|
|
62
|
+
})
|
|
63
|
+
return response.json()
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import requests
|
|
2
|
+
from typing import Optional, Dict, Any
|
|
3
|
+
from ..types import ClientLogger
|
|
4
|
+
|
|
5
|
+
class RestClient:
|
|
6
|
+
def __init__(self, cookies: str, base_url: Optional[str] = None, logger: Optional[ClientLogger] = None):
|
|
7
|
+
self.cookies = cookies
|
|
8
|
+
self.base_url = base_url or 'https://zynced.app/api'
|
|
9
|
+
self.logger = logger
|
|
10
|
+
|
|
11
|
+
self.session = requests.Session()
|
|
12
|
+
self.session.headers.update({
|
|
13
|
+
'User-Agent': 'ZyncedPy/1.0',
|
|
14
|
+
'Accept': '*/*',
|
|
15
|
+
'Accept-Encoding': 'gzip, deflate, br',
|
|
16
|
+
'Connection': 'keep-alive',
|
|
17
|
+
'Cookie': self.cookies,
|
|
18
|
+
'Sec-Fetch-Dest': 'empty',
|
|
19
|
+
'Sec-Fetch-Mode': 'cors',
|
|
20
|
+
'Sec-Fetch-Site': 'same-origin',
|
|
21
|
+
'Pragma': 'no-cache',
|
|
22
|
+
'Cache-Control': 'no-cache',
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
def _log_request(self, method: str, url: str):
|
|
26
|
+
if self.logger:
|
|
27
|
+
self.logger.debug(f"REST request: {method} {url}")
|
|
28
|
+
|
|
29
|
+
def _log_response(self, method: str, url: str, status: int):
|
|
30
|
+
if self.logger:
|
|
31
|
+
self.logger.debug(f"REST response: {method} {url} {status}")
|
|
32
|
+
|
|
33
|
+
def _log_error(self, method: str, url: str, status: Optional[int], message: str):
|
|
34
|
+
if self.logger:
|
|
35
|
+
self.logger.error(f"REST request failed: {method} {url} {status} {message}")
|
|
36
|
+
|
|
37
|
+
def get(self, endpoint: str, **kwargs):
|
|
38
|
+
url = f"{self.base_url}{endpoint}"
|
|
39
|
+
self._log_request('GET', url)
|
|
40
|
+
try:
|
|
41
|
+
response = self.session.get(url, **kwargs)
|
|
42
|
+
self._log_response('GET', url, response.status_code)
|
|
43
|
+
return response
|
|
44
|
+
except Exception as e:
|
|
45
|
+
self._log_error('GET', url, None, str(e))
|
|
46
|
+
raise
|
|
47
|
+
|
|
48
|
+
def post(self, endpoint: str, data=None, json=None, **kwargs):
|
|
49
|
+
url = f"{self.base_url}{endpoint}"
|
|
50
|
+
self._log_request('POST', url)
|
|
51
|
+
try:
|
|
52
|
+
response = self.session.post(url, data=data, json=json, **kwargs)
|
|
53
|
+
self._log_response('POST', url, response.status_code)
|
|
54
|
+
return response
|
|
55
|
+
except Exception as e:
|
|
56
|
+
self._log_error('POST', url, None, str(e))
|
|
57
|
+
raise
|
|
58
|
+
|
|
59
|
+
def put(self, endpoint: str, data=None, json=None, **kwargs):
|
|
60
|
+
url = f"{self.base_url}{endpoint}"
|
|
61
|
+
self._log_request('PUT', url)
|
|
62
|
+
try:
|
|
63
|
+
response = self.session.put(url, data=data, json=json, **kwargs)
|
|
64
|
+
self._log_response('PUT', url, response.status_code)
|
|
65
|
+
return response
|
|
66
|
+
except Exception as e:
|
|
67
|
+
self._log_error('PUT', url, None, str(e))
|
|
68
|
+
raise
|
|
69
|
+
|
|
70
|
+
def patch(self, endpoint: str, data=None, json=None, **kwargs):
|
|
71
|
+
url = f"{self.base_url}{endpoint}"
|
|
72
|
+
self._log_request('PATCH', url)
|
|
73
|
+
try:
|
|
74
|
+
response = self.session.patch(url, data=data, json=json, **kwargs)
|
|
75
|
+
self._log_response('PATCH', url, response.status_code)
|
|
76
|
+
return response
|
|
77
|
+
except Exception as e:
|
|
78
|
+
self._log_error('PATCH', url, None, str(e))
|
|
79
|
+
raise
|
|
80
|
+
|
|
81
|
+
def delete(self, endpoint: str, **kwargs):
|
|
82
|
+
url = f"{self.base_url}{endpoint}"
|
|
83
|
+
self._log_request('DELETE', url)
|
|
84
|
+
try:
|
|
85
|
+
response = self.session.delete(url, **kwargs)
|
|
86
|
+
self._log_response('DELETE', url, response.status_code)
|
|
87
|
+
return response
|
|
88
|
+
except Exception as e:
|
|
89
|
+
self._log_error('DELETE', url, None, str(e))
|
|
90
|
+
raise
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import socketio
|
|
2
|
+
from typing import Optional, Any
|
|
3
|
+
from ..types import ClientLogger
|
|
4
|
+
from ..events import TypedEmitter
|
|
5
|
+
|
|
6
|
+
class WebSocketClient(TypedEmitter):
|
|
7
|
+
def __init__(self, cookies: str, logger: ClientLogger):
|
|
8
|
+
super().__init__()
|
|
9
|
+
self.cookies = cookies
|
|
10
|
+
self.logger = logger
|
|
11
|
+
self.socket: Optional[socketio.AsyncClient] = None
|
|
12
|
+
self._connected = False
|
|
13
|
+
|
|
14
|
+
async def connect(self):
|
|
15
|
+
self.socket = socketio.AsyncClient()
|
|
16
|
+
|
|
17
|
+
# Set up event handlers
|
|
18
|
+
@self.socket.on('connect')
|
|
19
|
+
async def on_connect():
|
|
20
|
+
self._connected = True
|
|
21
|
+
self.logger.info(f"WebSocket connected, socket ID: {self.socket.sid}")
|
|
22
|
+
|
|
23
|
+
@self.socket.on('disconnect')
|
|
24
|
+
async def on_disconnect():
|
|
25
|
+
self._connected = False
|
|
26
|
+
self.logger.info("WebSocket disconnected")
|
|
27
|
+
|
|
28
|
+
@self.socket.on('connect_error')
|
|
29
|
+
async def on_connect_error(err):
|
|
30
|
+
self.logger.error(f"WebSocket connection error: {err}")
|
|
31
|
+
|
|
32
|
+
@self.socket.on('*')
|
|
33
|
+
async def catch_all(event, data):
|
|
34
|
+
await self._handle_message(event, data)
|
|
35
|
+
|
|
36
|
+
# Connect with headers
|
|
37
|
+
await self.socket.connect(
|
|
38
|
+
'https://zynced.app',
|
|
39
|
+
transports=['websocket'],
|
|
40
|
+
headers={
|
|
41
|
+
'User-Agent': 'ZyncedPy/1.0',
|
|
42
|
+
'Accept': '*/*',
|
|
43
|
+
'Accept-Encoding': 'gzip, deflate, br, zstd',
|
|
44
|
+
'Origin': 'https://zynced.app',
|
|
45
|
+
'Connection': 'Upgrade',
|
|
46
|
+
'Cookie': self.cookies,
|
|
47
|
+
'Sec-Fetch-Dest': 'empty',
|
|
48
|
+
'Sec-Fetch-Mode': 'websocket',
|
|
49
|
+
'Sec-Fetch-Site': 'same-origin',
|
|
50
|
+
'Pragma': 'no-cache',
|
|
51
|
+
'Cache-Control': 'no-cache',
|
|
52
|
+
'Upgrade': 'websocket',
|
|
53
|
+
}
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
async def disconnect(self):
|
|
57
|
+
if self.socket:
|
|
58
|
+
await self.socket.disconnect()
|
|
59
|
+
self._connected = False
|
|
60
|
+
|
|
61
|
+
async def send(self, name: str, data: Any):
|
|
62
|
+
if self.socket and self._connected:
|
|
63
|
+
await self.socket.emit(name, data)
|
|
64
|
+
self.logger.info(f"WebSocket sent: {name} {data}")
|
|
65
|
+
|
|
66
|
+
async def _handle_message(self, event: str, payload: Any):
|
|
67
|
+
self.logger.info(f"WebSocket received: {event} {payload}")
|
|
68
|
+
|
|
69
|
+
if event == 'newMessage':
|
|
70
|
+
self.emit('privateMessageCreate', payload)
|
|
71
|
+
elif event == 'newChannelMessage':
|
|
72
|
+
self.emit('messageCreate', payload)
|
|
73
|
+
elif event == 'status-update':
|
|
74
|
+
if payload.get('type') == 'member_joined':
|
|
75
|
+
self.emit('memberJoined', payload)
|
|
76
|
+
elif payload.get('type') == 'member_left':
|
|
77
|
+
self.emit('memberLeft', payload)
|
|
78
|
+
elif event == 'invite-event':
|
|
79
|
+
if payload.get('type') == 'new_invite':
|
|
80
|
+
self.emit('userInvite', payload)
|
|
81
|
+
elif event == 'channelMessages':
|
|
82
|
+
# Internal event
|
|
83
|
+
self.emit('channelMessages', payload)
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
from typing import Dict, List, Callable, Any, Optional
|
|
2
|
+
|
|
3
|
+
class TypedEmitter:
|
|
4
|
+
def __init__(self):
|
|
5
|
+
self._listeners: Dict[str, List[Callable]] = {}
|
|
6
|
+
|
|
7
|
+
def on(self, event: str, listener: Callable) -> 'TypedEmitter':
|
|
8
|
+
if event not in self._listeners:
|
|
9
|
+
self._listeners[event] = []
|
|
10
|
+
self._listeners[event].append(listener)
|
|
11
|
+
return self
|
|
12
|
+
|
|
13
|
+
def once(self, event: str, listener: Callable) -> 'TypedEmitter':
|
|
14
|
+
def wrapper(*args, **kwargs):
|
|
15
|
+
self.off(event, wrapper)
|
|
16
|
+
listener(*args, **kwargs)
|
|
17
|
+
|
|
18
|
+
self.on(event, wrapper)
|
|
19
|
+
return self
|
|
20
|
+
|
|
21
|
+
def emit(self, event: str, *args, **kwargs) -> bool:
|
|
22
|
+
if event in self._listeners:
|
|
23
|
+
for listener in self._listeners[event]:
|
|
24
|
+
try:
|
|
25
|
+
listener(*args, **kwargs)
|
|
26
|
+
except Exception as e:
|
|
27
|
+
print(f"Error in event listener for {event}: {e}")
|
|
28
|
+
return True
|
|
29
|
+
return False
|
|
30
|
+
|
|
31
|
+
def off(self, event: str, listener: Callable) -> 'TypedEmitter':
|
|
32
|
+
if event in self._listeners:
|
|
33
|
+
try:
|
|
34
|
+
self._listeners[event].remove(listener)
|
|
35
|
+
except ValueError:
|
|
36
|
+
pass
|
|
37
|
+
return self
|
|
38
|
+
|
|
39
|
+
def remove_all_listeners(self, event: Optional[str] = None) -> 'TypedEmitter':
|
|
40
|
+
if event:
|
|
41
|
+
self._listeners.pop(event, None)
|
|
42
|
+
else:
|
|
43
|
+
self._listeners.clear()
|
|
44
|
+
return self
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
from .message_create import handle_message_create
|
|
2
|
+
from .member_joined import handle_member_joined
|
|
3
|
+
from .member_left import handle_member_left
|
|
4
|
+
from .user_invite import handle_user_invite
|
|
5
|
+
from .private_message_create import handle_private_message_create
|
|
6
|
+
|
|
7
|
+
__all__ = [
|
|
8
|
+
"handle_message_create", "handle_member_joined",
|
|
9
|
+
"handle_member_left", "handle_user_invite", "handle_private_message_create"
|
|
10
|
+
]
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
from typing import Dict, Iterator, List, Optional, TypeVar, Generic, TYPE_CHECKING
|
|
2
|
+
|
|
3
|
+
if TYPE_CHECKING:
|
|
4
|
+
from ..client import Client
|
|
5
|
+
|
|
6
|
+
T = TypeVar('T')
|
|
7
|
+
|
|
8
|
+
class BaseManager(Generic[T]):
|
|
9
|
+
def __init__(self, client: 'Client'):
|
|
10
|
+
self.cache: Dict[str, T] = {}
|
|
11
|
+
self.client = client
|
|
12
|
+
|
|
13
|
+
def resolve(self, id: str) -> Optional[T]:
|
|
14
|
+
return self.cache.get(id)
|
|
15
|
+
|
|
16
|
+
def get(self, id: str) -> T:
|
|
17
|
+
entity = self.resolve(id)
|
|
18
|
+
if entity is None:
|
|
19
|
+
raise ValueError(f"Entity {id} not in cache")
|
|
20
|
+
return entity
|
|
21
|
+
|
|
22
|
+
def has(self, id: str) -> bool:
|
|
23
|
+
return id in self.cache
|
|
24
|
+
|
|
25
|
+
@property
|
|
26
|
+
def size(self) -> int:
|
|
27
|
+
return len(self.cache)
|
|
28
|
+
|
|
29
|
+
def values(self) -> Iterator[T]:
|
|
30
|
+
return iter(self.cache.values())
|
|
31
|
+
|
|
32
|
+
def find(self, fn) -> Optional[T]:
|
|
33
|
+
for entity in self.cache.values():
|
|
34
|
+
if fn(entity):
|
|
35
|
+
return entity
|
|
36
|
+
return None
|
|
37
|
+
|
|
38
|
+
def filter(self, fn) -> List[T]:
|
|
39
|
+
return [entity for entity in self.cache.values() if fn(entity)]
|
|
40
|
+
|
|
41
|
+
def _add(self, data) -> T:
|
|
42
|
+
raise NotImplementedError
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
from typing import Optional
|
|
2
|
+
from .base_manager import BaseManager
|
|
3
|
+
from ..structures import Channel, RawChannel
|
|
4
|
+
|
|
5
|
+
class ChannelManager(BaseManager[Channel]):
|
|
6
|
+
def _add(self, data: RawChannel) -> Channel:
|
|
7
|
+
if self.has(data.id):
|
|
8
|
+
return self.cache[data.id]
|
|
9
|
+
|
|
10
|
+
channel = Channel(self.client, data)
|
|
11
|
+
self.cache[channel.id] = channel
|
|
12
|
+
return channel
|
|
13
|
+
|
|
14
|
+
def find_one_by_name(self, name: str) -> Optional[Channel]:
|
|
15
|
+
return self.find(lambda channel: channel.name == name)
|
|
16
|
+
|
|
17
|
+
def find_all_by_name(self, name: str) -> list[Channel]:
|
|
18
|
+
return self.filter(lambda channel: channel.name == name)
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
from .base_manager import BaseManager
|
|
2
|
+
from ..structures import Guild, RawChannel
|
|
3
|
+
|
|
4
|
+
class GuildManager(BaseManager[Guild]):
|
|
5
|
+
def _add(self, data: RawChannel) -> Guild:
|
|
6
|
+
if self.has(data.id):
|
|
7
|
+
return self.cache[data.id]
|
|
8
|
+
|
|
9
|
+
guild = Guild(self.client, data)
|
|
10
|
+
self.cache[guild.id] = guild
|
|
11
|
+
return guild
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
from .base import Base
|
|
2
|
+
from .base_channel import BaseChannel, RawChannel
|
|
3
|
+
from .channel import Channel
|
|
4
|
+
from .client_user import ClientUser, RawClientUser
|
|
5
|
+
from .guild import Guild
|
|
6
|
+
from .message import Message
|
|
7
|
+
from .private_message import PrivateMessage
|
|
8
|
+
|
|
9
|
+
__all__ = [
|
|
10
|
+
"Base", "BaseChannel", "RawChannel", "Channel",
|
|
11
|
+
"ClientUser", "RawClientUser", "Guild", "Message", "PrivateMessage"
|
|
12
|
+
]
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
from typing import Optional, TYPE_CHECKING
|
|
2
|
+
from dataclasses import dataclass
|
|
3
|
+
from .base import Base
|
|
4
|
+
|
|
5
|
+
if TYPE_CHECKING:
|
|
6
|
+
from ..client import Client
|
|
7
|
+
|
|
8
|
+
@dataclass
|
|
9
|
+
class RawChannel:
|
|
10
|
+
id: str
|
|
11
|
+
name: str
|
|
12
|
+
description: str
|
|
13
|
+
group_id: Optional[str]
|
|
14
|
+
category_id: Optional[str]
|
|
15
|
+
position: Optional[int]
|
|
16
|
+
relative_to_category: Optional[str]
|
|
17
|
+
created_at: str
|
|
18
|
+
blocked: bool
|
|
19
|
+
avatar: str
|
|
20
|
+
accept_joins: bool
|
|
21
|
+
type: str # 'text' | 'voice'
|
|
22
|
+
owner: str
|
|
23
|
+
user_joined_at: str
|
|
24
|
+
lastMessageAt: int
|
|
25
|
+
|
|
26
|
+
class BaseChannel(Base):
|
|
27
|
+
def __init__(self, client: 'Client', data: RawChannel):
|
|
28
|
+
super().__init__(client)
|
|
29
|
+
|
|
30
|
+
self.id = data.id
|
|
31
|
+
self.name = data.name
|
|
32
|
+
self.group_id = data.group_id
|
|
33
|
+
self.description = data.description
|
|
34
|
+
self.category_id = data.category_id
|
|
35
|
+
self.position = data.position
|
|
36
|
+
self.relative_to_category = data.relative_to_category
|
|
37
|
+
self.created_at = data.created_at
|
|
38
|
+
self.blocked = data.blocked
|
|
39
|
+
self.avatar = data.avatar
|
|
40
|
+
self.accept_joins = data.accept_joins
|
|
41
|
+
self.type = data.type
|
|
42
|
+
self.owner = data.owner
|
|
43
|
+
self.user_joined_at = data.user_joined_at
|
|
44
|
+
self.last_message_at = data.lastMessageAt
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import base64
|
|
2
|
+
import mimetypes
|
|
3
|
+
import os
|
|
4
|
+
from typing import List, Optional, TYPE_CHECKING
|
|
5
|
+
|
|
6
|
+
if TYPE_CHECKING:
|
|
7
|
+
from ..client import Client
|
|
8
|
+
from ..types import MessageAttachment
|
|
9
|
+
|
|
10
|
+
from .base_channel import BaseChannel, RawChannel
|
|
11
|
+
from .message import Message
|
|
12
|
+
|
|
13
|
+
class Channel(BaseChannel):
|
|
14
|
+
def __init__(self, client: 'Client', data: RawChannel):
|
|
15
|
+
super().__init__(client, data)
|
|
16
|
+
self.messages: List[Message] = []
|
|
17
|
+
|
|
18
|
+
def _set_messages(self, raw_messages: list):
|
|
19
|
+
self.messages.clear()
|
|
20
|
+
for msg in raw_messages:
|
|
21
|
+
self.messages.append(Message(self.client, msg))
|
|
22
|
+
|
|
23
|
+
def _add_message(self, message: Message) -> int:
|
|
24
|
+
self.messages.append(message)
|
|
25
|
+
return len(self.messages)
|
|
26
|
+
|
|
27
|
+
async def subscribe(self):
|
|
28
|
+
await self.client.get_ws().send('syncChannelRooms', [self.id])
|
|
29
|
+
|
|
30
|
+
def handler(data):
|
|
31
|
+
if data.get('channelId') != self.id:
|
|
32
|
+
return
|
|
33
|
+
|
|
34
|
+
self.client.get_ws().off('channelMessages', handler)
|
|
35
|
+
self._set_messages(data.get('messages', []))
|
|
36
|
+
|
|
37
|
+
self.client.get_ws().on('channelMessages', handler)
|
|
38
|
+
await self.client.get_ws().send('getChannelMessages', [self.id, {
|
|
39
|
+
"channelId": self.id,
|
|
40
|
+
"_t": int(__import__('time').time() * 1000)
|
|
41
|
+
}])
|
|
42
|
+
|
|
43
|
+
async def send(self, message: str, attachments: Optional[List] = None):
|
|
44
|
+
if attachments is None:
|
|
45
|
+
attachments = []
|
|
46
|
+
|
|
47
|
+
if message:
|
|
48
|
+
await self.client.get_ws().send('sendChannelMessage', {
|
|
49
|
+
"channelId": self.id,
|
|
50
|
+
"message": {
|
|
51
|
+
"text": message,
|
|
52
|
+
"from": self.client.user.username if self.client.user else None,
|
|
53
|
+
},
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
for attachment in attachments:
|
|
57
|
+
if attachment.type == 'gif':
|
|
58
|
+
await self._send_gif(attachment.url)
|
|
59
|
+
else:
|
|
60
|
+
await self._send_file(attachment.url)
|
|
61
|
+
|
|
62
|
+
return self
|
|
63
|
+
|
|
64
|
+
async def reply(self, message: str, attachments: Optional[List], reply_to_index: int, reply_author: str, reply_text: str):
|
|
65
|
+
if attachments is None:
|
|
66
|
+
attachments = []
|
|
67
|
+
|
|
68
|
+
await self.client.get_ws().send('sendChannelMessage', {
|
|
69
|
+
"channelId": self.id,
|
|
70
|
+
"message": {
|
|
71
|
+
"text": message,
|
|
72
|
+
"from": self.client.user.username if self.client.user else None,
|
|
73
|
+
"reply_to_index": reply_to_index,
|
|
74
|
+
"reply_author": reply_author,
|
|
75
|
+
"reply_text": reply_text
|
|
76
|
+
},
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
for attachment in attachments:
|
|
80
|
+
if attachment.type == 'gif':
|
|
81
|
+
await self._send_gif(attachment.url)
|
|
82
|
+
else:
|
|
83
|
+
await self._send_file(attachment.url)
|
|
84
|
+
|
|
85
|
+
return self
|
|
86
|
+
|
|
87
|
+
async def _send_gif(self, url: str):
|
|
88
|
+
await self.client.get_ws().send('sendChannelMessage', {
|
|
89
|
+
"channelId": self.id,
|
|
90
|
+
"message": {
|
|
91
|
+
"text": __import__('json').dumps({
|
|
92
|
+
"text": '',
|
|
93
|
+
"messageType": 'gif-link',
|
|
94
|
+
"gifUrl": url,
|
|
95
|
+
"sender": self.client.user.username if self.client.user else None,
|
|
96
|
+
}),
|
|
97
|
+
"from": self.client.user.username if self.client.user else None,
|
|
98
|
+
},
|
|
99
|
+
})
|
|
100
|
+
return self
|
|
101
|
+
|
|
102
|
+
async def _send_file(self, file_path: str):
|
|
103
|
+
if not os.path.exists(file_path):
|
|
104
|
+
raise FileNotFoundError(f"File not found: {file_path}")
|
|
105
|
+
|
|
106
|
+
file_name = os.path.basename(file_path)
|
|
107
|
+
|
|
108
|
+
with open(file_path, 'rb') as f:
|
|
109
|
+
file_buffer = f.read()
|
|
110
|
+
|
|
111
|
+
file_base64 = base64.b64encode(file_buffer).decode('utf-8')
|
|
112
|
+
mime_type = mimetypes.guess_type(file_path)[0] or 'application/octet-stream'
|
|
113
|
+
|
|
114
|
+
data = {
|
|
115
|
+
"channelId": self.id,
|
|
116
|
+
"text": '',
|
|
117
|
+
"fileName": file_name,
|
|
118
|
+
"mimeType": mime_type,
|
|
119
|
+
"fileBase64": file_base64,
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
await self.client.get_ws().send('uploadChannelMessageFile', data)
|
|
123
|
+
return self
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
from typing import Optional, TYPE_CHECKING
|
|
2
|
+
from dataclasses import dataclass
|
|
3
|
+
from .base import Base
|
|
4
|
+
|
|
5
|
+
if TYPE_CHECKING:
|
|
6
|
+
from ..client import Client
|
|
7
|
+
|
|
8
|
+
@dataclass
|
|
9
|
+
class RawClientUser:
|
|
10
|
+
userId: str
|
|
11
|
+
username: str
|
|
12
|
+
avatar: str
|
|
13
|
+
status: str
|
|
14
|
+
bannerUrl: str
|
|
15
|
+
onlineStatus: str
|
|
16
|
+
isPremium: bool
|
|
17
|
+
|
|
18
|
+
class ClientUser(Base):
|
|
19
|
+
def __init__(self, client: 'Client', data: Optional[RawClientUser] = None):
|
|
20
|
+
super().__init__(client)
|
|
21
|
+
self.id = data.userId if data else ''
|
|
22
|
+
self.username = data.username if data else ''
|
|
23
|
+
self.avatar = data.avatar if data else ''
|
|
24
|
+
self.status = data.status if data else ''
|
|
25
|
+
self.banner_url = data.bannerUrl if data else ''
|
|
26
|
+
self.online_status = data.onlineStatus if data else ''
|
|
27
|
+
self.is_premium = data.isPremium if data else False
|
|
28
|
+
|
|
29
|
+
def _patch(self, data: dict):
|
|
30
|
+
if 'userId' in data:
|
|
31
|
+
self.id = data['userId']
|
|
32
|
+
if 'username' in data:
|
|
33
|
+
self.username = data['username']
|
|
34
|
+
if 'avatar' in data:
|
|
35
|
+
self.avatar = data['avatar']
|
|
36
|
+
if 'status' in data:
|
|
37
|
+
self.status = data['status']
|
|
38
|
+
if 'bannerUrl' in data:
|
|
39
|
+
self.banner_url = data['bannerUrl']
|
|
40
|
+
if 'onlineStatus' in data:
|
|
41
|
+
self.online_status = data['onlineStatus']
|
|
42
|
+
if 'isPremium' in data:
|
|
43
|
+
self.is_premium = data['isPremium']
|
|
44
|
+
return self
|
|
45
|
+
|
|
46
|
+
async def set_status(self, status: str):
|
|
47
|
+
await self.client.get_ws().send('updateProfileStatus', {'status': status})
|
|
48
|
+
self.status = status
|
|
49
|
+
|
|
50
|
+
def __str__(self):
|
|
51
|
+
return f"{self.username}#{self.id}"
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
from datetime import datetime
|
|
2
|
+
from typing import Optional, List, TYPE_CHECKING
|
|
3
|
+
|
|
4
|
+
if TYPE_CHECKING:
|
|
5
|
+
from ..client import Client
|
|
6
|
+
from ..types import MessageAttachment
|
|
7
|
+
|
|
8
|
+
class Message:
|
|
9
|
+
def __init__(self, client: 'Client', data: dict):
|
|
10
|
+
self._client = client
|
|
11
|
+
|
|
12
|
+
channel = self._client.channel(data['channelId'])
|
|
13
|
+
self.id = channel._add_message(self)
|
|
14
|
+
self.guild_id = data.get('groupId')
|
|
15
|
+
self.channel_id = data['channelId']
|
|
16
|
+
self.content = data['message']['text']
|
|
17
|
+
self.author = data['message']['from']
|
|
18
|
+
self.date = datetime.fromisoformat(data['message']['date'].replace('Z', '+00:00'))
|
|
19
|
+
self.is_reply = bool(data['message'].get('reply_to_index'))
|
|
20
|
+
|
|
21
|
+
def reply(self, content: str, attachments: Optional[List] = None):
|
|
22
|
+
if attachments is None:
|
|
23
|
+
attachments = []
|
|
24
|
+
channel = self._client.channel(self.channel_id)
|
|
25
|
+
if channel:
|
|
26
|
+
channel.reply(content, attachments, self.id, self.author, self.content)
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
from datetime import datetime
|
|
2
|
+
from typing import TYPE_CHECKING
|
|
3
|
+
|
|
4
|
+
if TYPE_CHECKING:
|
|
5
|
+
from ..client import Client
|
|
6
|
+
|
|
7
|
+
class PrivateMessage:
|
|
8
|
+
def __init__(self, client: 'Client', data: dict):
|
|
9
|
+
self._client = client
|
|
10
|
+
|
|
11
|
+
self.chatmode = data['message']['chatmode']
|
|
12
|
+
self.content = data['message']['text']
|
|
13
|
+
self.author = data['from']
|
|
14
|
+
self.date = datetime.fromisoformat(data['message']['timestamp'].replace('Z', '+00:00'))
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
from typing import Optional, Union, Dict, Any, Protocol
|
|
2
|
+
import logging
|
|
3
|
+
|
|
4
|
+
class ClientLogger(Protocol):
|
|
5
|
+
def debug(self, message: str, *args, **kwargs) -> None: ...
|
|
6
|
+
def info(self, message: str, *args, **kwargs) -> None: ...
|
|
7
|
+
def warn(self, message: str, *args, **kwargs) -> None: ...
|
|
8
|
+
def error(self, message: str, *args, **kwargs) -> None: ...
|
|
9
|
+
|
|
10
|
+
ClientLoggerOption = Union[bool, ClientLogger, Dict[str, Any]]
|
|
11
|
+
|
|
12
|
+
class ClientOptions:
|
|
13
|
+
def __init__(self, logger: Optional[ClientLoggerOption] = None):
|
|
14
|
+
self.logger = logger
|
|
15
|
+
|
|
16
|
+
# Event types
|
|
17
|
+
class RawMemberAction:
|
|
18
|
+
def __init__(self, username: str, channelId: str):
|
|
19
|
+
self.username = username
|
|
20
|
+
self.channel_id = channelId
|
|
21
|
+
|
|
22
|
+
class RawSfuCapabilities:
|
|
23
|
+
def __init__(self, codecs: list, headerExtensions: list):
|
|
24
|
+
self.codecs = codecs
|
|
25
|
+
self.header_extensions = headerExtensions
|
|
26
|
+
|
|
27
|
+
class RawUserInvite:
|
|
28
|
+
def __init__(self, type: str, inviteId: str, channelId: str, inviterUsername: str):
|
|
29
|
+
self.type = type
|
|
30
|
+
self.invite_id = inviteId
|
|
31
|
+
self.channel_id = channelId
|
|
32
|
+
self.inviter_username = inviterUsername
|
|
33
|
+
|
|
34
|
+
# Message attachment types
|
|
35
|
+
MessageType = str # 'gif' | 'file'
|
|
36
|
+
|
|
37
|
+
class MessageAttachment:
|
|
38
|
+
def __init__(self, type: MessageType, url: str, name: Optional[str] = None):
|
|
39
|
+
self.type = type
|
|
40
|
+
self.url = url
|
|
41
|
+
self.name = name
|
|
42
|
+
|
|
43
|
+
# Event data types
|
|
44
|
+
from typing import TYPE_CHECKING
|
|
45
|
+
if TYPE_CHECKING:
|
|
46
|
+
from .structures import ClientUser, Message, PrivateMessage
|
|
47
|
+
from .managers import ChannelManager, GuildManager
|
|
48
|
+
|
|
49
|
+
class ClientEvents:
|
|
50
|
+
ready: Dict[str, Any] # {'user': ClientUser, 'channels': ChannelManager, 'guilds': GuildManager}
|
|
51
|
+
messageCreate: 'Message'
|
|
52
|
+
privateMessageCreate: 'PrivateMessage'
|
|
53
|
+
memberJoined: RawMemberAction
|
|
54
|
+
memberLeft: RawMemberAction
|
|
55
|
+
userInvite: RawUserInvite
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from typing import Optional, Union, Dict, Any
|
|
3
|
+
from ..types import ClientLogger, ClientLoggerOption
|
|
4
|
+
|
|
5
|
+
def noop(*args, **kwargs):
|
|
6
|
+
pass
|
|
7
|
+
|
|
8
|
+
silent_logger = type('SilentLogger', (), {
|
|
9
|
+
'debug': noop,
|
|
10
|
+
'info': noop,
|
|
11
|
+
'warn': noop,
|
|
12
|
+
'error': noop,
|
|
13
|
+
})()
|
|
14
|
+
|
|
15
|
+
def is_logger_like(value: Any) -> bool:
|
|
16
|
+
return (
|
|
17
|
+
hasattr(value, 'debug') and callable(getattr(value, 'debug')) and
|
|
18
|
+
hasattr(value, 'info') and callable(getattr(value, 'info')) and
|
|
19
|
+
hasattr(value, 'warn') and callable(getattr(value, 'warn')) and
|
|
20
|
+
hasattr(value, 'error') and callable(getattr(value, 'error'))
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
def resolve_logger(logger: Optional[ClientLoggerOption] = None) -> ClientLogger:
|
|
24
|
+
if logger is False or logger is None:
|
|
25
|
+
return silent_logger
|
|
26
|
+
|
|
27
|
+
if logger is True:
|
|
28
|
+
logger_instance = logging.getLogger('zyncedpy')
|
|
29
|
+
logger_instance.setLevel(logging.DEBUG)
|
|
30
|
+
handler = logging.StreamHandler()
|
|
31
|
+
formatter = logging.Formatter('%(name)s - %(levelname)s - %(message)s')
|
|
32
|
+
handler.setFormatter(formatter)
|
|
33
|
+
logger_instance.addHandler(handler)
|
|
34
|
+
return logger_instance
|
|
35
|
+
|
|
36
|
+
if is_logger_like(logger):
|
|
37
|
+
return logger
|
|
38
|
+
|
|
39
|
+
# If it's a dict, assume it's logging config
|
|
40
|
+
if isinstance(logger, dict):
|
|
41
|
+
logger_instance = logging.getLogger('zyncedpy')
|
|
42
|
+
logger_instance.setLevel(logger.get('level', logging.DEBUG))
|
|
43
|
+
handler = logging.StreamHandler()
|
|
44
|
+
if 'format' in logger:
|
|
45
|
+
formatter = logging.Formatter(logger['format'])
|
|
46
|
+
else:
|
|
47
|
+
formatter = logging.Formatter('%(name)s - %(levelname)s - %(message)s')
|
|
48
|
+
handler.setFormatter(formatter)
|
|
49
|
+
logger_instance.addHandler(handler)
|
|
50
|
+
return logger_instance
|
|
51
|
+
|
|
52
|
+
return silent_logger
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: zyncedpy
|
|
3
|
+
Version: 1.0.3
|
|
4
|
+
Summary: ZyncedPy is a Python module that allows you to create bots for Zynced chat platform.
|
|
5
|
+
Home-page: https://github.com/Emsa001/zyncedpy
|
|
6
|
+
Author: Emanuel Scura
|
|
7
|
+
License: MIT
|
|
8
|
+
Keywords: sdk,bot,websocket,api,chat,zynced
|
|
9
|
+
Classifier: Development Status :: 4 - Beta
|
|
10
|
+
Classifier: Intended Audience :: Developers
|
|
11
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
12
|
+
Classifier: Operating System :: OS Independent
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Requires-Python: >=3.8
|
|
20
|
+
Description-Content-Type: text/markdown
|
|
21
|
+
Requires-Dist: requests>=2.25.0
|
|
22
|
+
Requires-Dist: python-socketio>=5.0.0
|
|
23
|
+
Requires-Dist: python-engineio>=4.0.0
|
|
24
|
+
Requires-Dist: dataclasses-json>=0.5.0
|
|
25
|
+
Dynamic: author
|
|
26
|
+
Dynamic: classifier
|
|
27
|
+
Dynamic: description
|
|
28
|
+
Dynamic: description-content-type
|
|
29
|
+
Dynamic: home-page
|
|
30
|
+
Dynamic: keywords
|
|
31
|
+
Dynamic: license
|
|
32
|
+
Dynamic: requires-dist
|
|
33
|
+
Dynamic: requires-python
|
|
34
|
+
Dynamic: summary
|
|
35
|
+
|
|
36
|
+
# ZyncedPy
|
|
37
|
+
|
|
38
|
+
ZyncedPy is a Python module that allows you to create bots for Zynced chat platform.
|
|
39
|
+
|
|
40
|
+
## Installation
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
pip install zyncedpy
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Usage
|
|
47
|
+
|
|
48
|
+
```python
|
|
49
|
+
from zyncedpy import Client
|
|
50
|
+
|
|
51
|
+
client = Client("your_token_here")
|
|
52
|
+
|
|
53
|
+
@client.on("ready")
|
|
54
|
+
def on_ready(data):
|
|
55
|
+
print(f"Bot is ready! Logged in as {data['user'].username}")
|
|
56
|
+
|
|
57
|
+
@client.on("messageCreate")
|
|
58
|
+
def on_message(message):
|
|
59
|
+
if message.content == "!ping":
|
|
60
|
+
message.reply("Pong!")
|
|
61
|
+
|
|
62
|
+
client.login()
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## Features
|
|
66
|
+
|
|
67
|
+
- WebSocket and REST API support
|
|
68
|
+
- Event-driven architecture
|
|
69
|
+
- Channel and guild management
|
|
70
|
+
- Message handling
|
|
71
|
+
- User management
|
|
72
|
+
- Invite system
|
|
73
|
+
|
|
74
|
+
## License
|
|
75
|
+
|
|
76
|
+
MIT
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
setup.py
|
|
3
|
+
zyncedpy/__init__.py
|
|
4
|
+
zyncedpy/types.py
|
|
5
|
+
zyncedpy.egg-info/PKG-INFO
|
|
6
|
+
zyncedpy.egg-info/SOURCES.txt
|
|
7
|
+
zyncedpy.egg-info/dependency_links.txt
|
|
8
|
+
zyncedpy.egg-info/requires.txt
|
|
9
|
+
zyncedpy.egg-info/top_level.txt
|
|
10
|
+
zyncedpy/client/__init__.py
|
|
11
|
+
zyncedpy/client/base_client.py
|
|
12
|
+
zyncedpy/client/client.py
|
|
13
|
+
zyncedpy/client/rest_client.py
|
|
14
|
+
zyncedpy/client/websocket_client.py
|
|
15
|
+
zyncedpy/events/__init__.py
|
|
16
|
+
zyncedpy/events/event_emitter.py
|
|
17
|
+
zyncedpy/events/handlers/__init__.py
|
|
18
|
+
zyncedpy/events/handlers/member_joined.py
|
|
19
|
+
zyncedpy/events/handlers/member_left.py
|
|
20
|
+
zyncedpy/events/handlers/message_create.py
|
|
21
|
+
zyncedpy/events/handlers/private_message_create.py
|
|
22
|
+
zyncedpy/events/handlers/user_invite.py
|
|
23
|
+
zyncedpy/managers/__init__.py
|
|
24
|
+
zyncedpy/managers/base_manager.py
|
|
25
|
+
zyncedpy/managers/channel_manager.py
|
|
26
|
+
zyncedpy/managers/guild_manager.py
|
|
27
|
+
zyncedpy/structures/__init__.py
|
|
28
|
+
zyncedpy/structures/base.py
|
|
29
|
+
zyncedpy/structures/base_channel.py
|
|
30
|
+
zyncedpy/structures/channel.py
|
|
31
|
+
zyncedpy/structures/client_user.py
|
|
32
|
+
zyncedpy/structures/guild.py
|
|
33
|
+
zyncedpy/structures/message.py
|
|
34
|
+
zyncedpy/structures/private_message.py
|
|
35
|
+
zyncedpy/utils/__init__.py
|
|
36
|
+
zyncedpy/utils/logger.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
zyncedpy
|