karboai 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.
- karboai-1.0.0/PKG-INFO +83 -0
- karboai-1.0.0/README.md +64 -0
- karboai-1.0.0/karboai/__init__.py +51 -0
- karboai-1.0.0/karboai/_const.py +19 -0
- karboai-1.0.0/karboai/client.py +78 -0
- karboai-1.0.0/karboai/dispatcher.py +65 -0
- karboai-1.0.0/karboai/errors.py +9 -0
- karboai-1.0.0/karboai/http.py +49 -0
- karboai-1.0.0/karboai/logging.py +20 -0
- karboai-1.0.0/karboai/router.py +42 -0
- karboai-1.0.0/karboai/types.py +118 -0
- karboai-1.0.0/karboai/utils.py +35 -0
- karboai-1.0.0/karboai.egg-info/PKG-INFO +83 -0
- karboai-1.0.0/karboai.egg-info/SOURCES.txt +17 -0
- karboai-1.0.0/karboai.egg-info/dependency_links.txt +1 -0
- karboai-1.0.0/karboai.egg-info/requires.txt +3 -0
- karboai-1.0.0/karboai.egg-info/top_level.txt +1 -0
- karboai-1.0.0/setup.cfg +4 -0
- karboai-1.0.0/setup.py +21 -0
karboai-1.0.0/PKG-INFO
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: karboai
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Python library for https://karboai.com
|
|
5
|
+
Home-page: https://github.com/yourusername/karboai-python
|
|
6
|
+
License: MIT
|
|
7
|
+
Requires-Python: >=3.8
|
|
8
|
+
Description-Content-Type: text/markdown
|
|
9
|
+
Requires-Dist: aiohttp>=3.9
|
|
10
|
+
Requires-Dist: python-socketio>=5.10
|
|
11
|
+
Requires-Dist: pydantic>=2.0
|
|
12
|
+
Dynamic: description
|
|
13
|
+
Dynamic: description-content-type
|
|
14
|
+
Dynamic: home-page
|
|
15
|
+
Dynamic: license
|
|
16
|
+
Dynamic: requires-dist
|
|
17
|
+
Dynamic: requires-python
|
|
18
|
+
Dynamic: summary
|
|
19
|
+
|
|
20
|
+
# KarboAI
|
|
21
|
+
|
|
22
|
+
Python library for [KarboAI](https://karboai.com) bot development.
|
|
23
|
+
|
|
24
|
+
## Install
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
pip install karboai
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Usage
|
|
31
|
+
|
|
32
|
+
```python
|
|
33
|
+
from karboai import KarboAI, Router, bold
|
|
34
|
+
|
|
35
|
+
karbo = KarboAI(token="your-token", id="your-bot-id", enable_logging=True)
|
|
36
|
+
router = Router()
|
|
37
|
+
|
|
38
|
+
@router.command("/start")
|
|
39
|
+
async def start(ctx):
|
|
40
|
+
await karbo.text(ctx.message.chat_id, bold("Welcome!"))
|
|
41
|
+
|
|
42
|
+
@router.on("message")
|
|
43
|
+
async def echo(ctx):
|
|
44
|
+
await karbo.text(ctx.message.chat_id, ctx.message.content)
|
|
45
|
+
|
|
46
|
+
karbo.bind(router)
|
|
47
|
+
karbo.attach()
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## API
|
|
51
|
+
|
|
52
|
+
| Method | Description |
|
|
53
|
+
|--------|-------------|
|
|
54
|
+
| `me()` | Bot info |
|
|
55
|
+
| `text(chat_id, content, reply_message_id?)` | Send text |
|
|
56
|
+
| `image(chat_id, images, reply_message_id?)` | Send images |
|
|
57
|
+
| `upload(buffer, file_name?)` | Upload image, returns URL |
|
|
58
|
+
| `message(chat_id, message_id)` | Get message |
|
|
59
|
+
| `members(chat_id, limit?, offset?)` | Chat members |
|
|
60
|
+
| `user(user_id)` | User info |
|
|
61
|
+
| `leave(chat_id)` | Leave chat |
|
|
62
|
+
| `kick(chat_id, user_id)` | Kick user |
|
|
63
|
+
| `attach(callback?)` | Connect to WebSocket |
|
|
64
|
+
| `bind(*routers)` | Bind routers |
|
|
65
|
+
|
|
66
|
+
## Events
|
|
67
|
+
|
|
68
|
+
`message`, `join`, `leave`, `voiceStart`, `voiceEnd`, `sticker`
|
|
69
|
+
|
|
70
|
+
## Text formatting
|
|
71
|
+
|
|
72
|
+
`bold()`, `italic()`, `centralize()`, `code()`, `strikethrough()`, `underline()`, `hyperlink(text, url)`
|
|
73
|
+
|
|
74
|
+
## Error handling
|
|
75
|
+
|
|
76
|
+
```python
|
|
77
|
+
from karboai import KarboError
|
|
78
|
+
|
|
79
|
+
try:
|
|
80
|
+
await karbo.me()
|
|
81
|
+
except KarboError as e:
|
|
82
|
+
print(e.status_code, e.name, e)
|
|
83
|
+
```
|
karboai-1.0.0/README.md
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# KarboAI
|
|
2
|
+
|
|
3
|
+
Python library for [KarboAI](https://karboai.com) bot development.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pip install karboai
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
```python
|
|
14
|
+
from karboai import KarboAI, Router, bold
|
|
15
|
+
|
|
16
|
+
karbo = KarboAI(token="your-token", id="your-bot-id", enable_logging=True)
|
|
17
|
+
router = Router()
|
|
18
|
+
|
|
19
|
+
@router.command("/start")
|
|
20
|
+
async def start(ctx):
|
|
21
|
+
await karbo.text(ctx.message.chat_id, bold("Welcome!"))
|
|
22
|
+
|
|
23
|
+
@router.on("message")
|
|
24
|
+
async def echo(ctx):
|
|
25
|
+
await karbo.text(ctx.message.chat_id, ctx.message.content)
|
|
26
|
+
|
|
27
|
+
karbo.bind(router)
|
|
28
|
+
karbo.attach()
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## API
|
|
32
|
+
|
|
33
|
+
| Method | Description |
|
|
34
|
+
|--------|-------------|
|
|
35
|
+
| `me()` | Bot info |
|
|
36
|
+
| `text(chat_id, content, reply_message_id?)` | Send text |
|
|
37
|
+
| `image(chat_id, images, reply_message_id?)` | Send images |
|
|
38
|
+
| `upload(buffer, file_name?)` | Upload image, returns URL |
|
|
39
|
+
| `message(chat_id, message_id)` | Get message |
|
|
40
|
+
| `members(chat_id, limit?, offset?)` | Chat members |
|
|
41
|
+
| `user(user_id)` | User info |
|
|
42
|
+
| `leave(chat_id)` | Leave chat |
|
|
43
|
+
| `kick(chat_id, user_id)` | Kick user |
|
|
44
|
+
| `attach(callback?)` | Connect to WebSocket |
|
|
45
|
+
| `bind(*routers)` | Bind routers |
|
|
46
|
+
|
|
47
|
+
## Events
|
|
48
|
+
|
|
49
|
+
`message`, `join`, `leave`, `voiceStart`, `voiceEnd`, `sticker`
|
|
50
|
+
|
|
51
|
+
## Text formatting
|
|
52
|
+
|
|
53
|
+
`bold()`, `italic()`, `centralize()`, `code()`, `strikethrough()`, `underline()`, `hyperlink(text, url)`
|
|
54
|
+
|
|
55
|
+
## Error handling
|
|
56
|
+
|
|
57
|
+
```python
|
|
58
|
+
from karboai import KarboError
|
|
59
|
+
|
|
60
|
+
try:
|
|
61
|
+
await karbo.me()
|
|
62
|
+
except KarboError as e:
|
|
63
|
+
print(e.status_code, e.name, e)
|
|
64
|
+
```
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
from .client import KarboAI
|
|
2
|
+
from .router import Router
|
|
3
|
+
from .types import (
|
|
4
|
+
Author,
|
|
5
|
+
Context,
|
|
6
|
+
Frame,
|
|
7
|
+
Member,
|
|
8
|
+
MembersResponse,
|
|
9
|
+
Message,
|
|
10
|
+
MessageResponse,
|
|
11
|
+
MeResponse,
|
|
12
|
+
OkResponse,
|
|
13
|
+
Reaction,
|
|
14
|
+
UploadResponse,
|
|
15
|
+
User,
|
|
16
|
+
)
|
|
17
|
+
from .utils import (
|
|
18
|
+
bold,
|
|
19
|
+
centralize,
|
|
20
|
+
code,
|
|
21
|
+
hyperlink,
|
|
22
|
+
italic,
|
|
23
|
+
strikethrough,
|
|
24
|
+
underline,
|
|
25
|
+
)
|
|
26
|
+
from .errors import KarboError
|
|
27
|
+
|
|
28
|
+
__all__ = [
|
|
29
|
+
"KarboAI",
|
|
30
|
+
"Router",
|
|
31
|
+
"KarboError",
|
|
32
|
+
"Context",
|
|
33
|
+
"Message",
|
|
34
|
+
"Author",
|
|
35
|
+
"User",
|
|
36
|
+
"Member",
|
|
37
|
+
"Frame",
|
|
38
|
+
"Reaction",
|
|
39
|
+
"MeResponse",
|
|
40
|
+
"MessageResponse",
|
|
41
|
+
"UploadResponse",
|
|
42
|
+
"MembersResponse",
|
|
43
|
+
"OkResponse",
|
|
44
|
+
"bold",
|
|
45
|
+
"italic",
|
|
46
|
+
"centralize",
|
|
47
|
+
"code",
|
|
48
|
+
"strikethrough",
|
|
49
|
+
"underline",
|
|
50
|
+
"hyperlink",
|
|
51
|
+
]
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
KARBO_API = "https://api.karboai.com"
|
|
2
|
+
|
|
3
|
+
SOCKET_TOPICS = {
|
|
4
|
+
0: "message",
|
|
5
|
+
1: "join",
|
|
6
|
+
2: "leave",
|
|
7
|
+
7: "voiceStart",
|
|
8
|
+
8: "voiceEnd",
|
|
9
|
+
12: "sticker",
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
ERRORS = {
|
|
13
|
+
400: ("KarboAI.BadRequest", "May empty message, content too long, too many images"),
|
|
14
|
+
401: ("KarboAI.Unauthorized", "Invalid bot token"),
|
|
15
|
+
403: ("KarboAI.Forbidden", "Access denied"),
|
|
16
|
+
404: ("KarboAI.NotFound", "Content doesn't exist"),
|
|
17
|
+
413: ("KarboAI.FileTooLarge", "Please upload files smaller than XX bytes length"),
|
|
18
|
+
429: ("KarboAI.TooManyRequests", "Rate limit exceeded"),
|
|
19
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Callable
|
|
4
|
+
|
|
5
|
+
from .dispatcher import Dispatcher
|
|
6
|
+
from .http import Http
|
|
7
|
+
from .logging import setup_logging
|
|
8
|
+
from .router import Router
|
|
9
|
+
from .types import (
|
|
10
|
+
MembersResponse,
|
|
11
|
+
Message,
|
|
12
|
+
MessageResponse,
|
|
13
|
+
MeResponse,
|
|
14
|
+
OkResponse,
|
|
15
|
+
UploadResponse,
|
|
16
|
+
User,
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class KarboAI:
|
|
21
|
+
def __init__(self, token: str, id: str, enable_logging: bool = False):
|
|
22
|
+
self._token = token
|
|
23
|
+
self._id = id
|
|
24
|
+
self._http = Http(token)
|
|
25
|
+
self._dispatcher = Dispatcher(self)
|
|
26
|
+
setup_logging(enable_logging)
|
|
27
|
+
|
|
28
|
+
@property
|
|
29
|
+
def id(self) -> str:
|
|
30
|
+
return self._id
|
|
31
|
+
|
|
32
|
+
async def me(self) -> MeResponse:
|
|
33
|
+
return await self._http.get("/bot/me", MeResponse)
|
|
34
|
+
|
|
35
|
+
async def text(self, chat_id: str, content: str, reply_message_id: str | None = None) -> MessageResponse:
|
|
36
|
+
return await self._send(chat_id=chat_id, content=content, reply_message_id=reply_message_id)
|
|
37
|
+
|
|
38
|
+
async def image(self, chat_id: str, images: list[str], reply_message_id: str | None = None) -> MessageResponse:
|
|
39
|
+
return await self._send(chat_id=chat_id, images=images, reply_message_id=reply_message_id)
|
|
40
|
+
|
|
41
|
+
async def upload(self, buffer: bytes, file_name: str = "file.png") -> str:
|
|
42
|
+
resp = await self._http.upload("/bot/upload/image", buffer, file_name, UploadResponse)
|
|
43
|
+
return resp.url
|
|
44
|
+
|
|
45
|
+
async def message(self, chat_id: str, message_id: str) -> Message:
|
|
46
|
+
return await self._http.get(f"/bot/chat/{chat_id}/message/{message_id}", Message)
|
|
47
|
+
|
|
48
|
+
async def members(self, chat_id: str, limit: int = 100, offset: int = 0) -> MembersResponse:
|
|
49
|
+
return await self._http.get(f"/bot/chat/{chat_id}/members?limit={limit}&offset={offset}", MembersResponse)
|
|
50
|
+
|
|
51
|
+
async def user(self, user_id: str) -> User:
|
|
52
|
+
return await self._http.get(f"/bot/user/{user_id}", User)
|
|
53
|
+
|
|
54
|
+
async def leave(self, chat_id: str) -> bool:
|
|
55
|
+
resp = await self._http.post(f"/bot/leave-chat/{chat_id}", {}, OkResponse)
|
|
56
|
+
return resp.ok
|
|
57
|
+
|
|
58
|
+
async def kick(self, chat_id: str, user_id: str) -> bool:
|
|
59
|
+
resp = await self._http.post(f"/bot/chat/{chat_id}/kick", {"user_id": user_id}, OkResponse)
|
|
60
|
+
return resp.ok
|
|
61
|
+
|
|
62
|
+
def attach(self, callback: Callable[[], None] | None = None) -> None:
|
|
63
|
+
async def _cb():
|
|
64
|
+
pass
|
|
65
|
+
self._dispatcher.attach(self._token, callback or _cb)
|
|
66
|
+
|
|
67
|
+
def bind(self, *routers: Router) -> None:
|
|
68
|
+
self._dispatcher.bind(*routers)
|
|
69
|
+
|
|
70
|
+
async def _send(self, *, chat_id: str, content: str | None = None, images: list[str] | None = None, reply_message_id: str | None = None) -> MessageResponse:
|
|
71
|
+
body: dict = {"chat_id": chat_id}
|
|
72
|
+
if content:
|
|
73
|
+
body["content"] = content
|
|
74
|
+
if images:
|
|
75
|
+
body["images"] = images
|
|
76
|
+
if reply_message_id:
|
|
77
|
+
body["reply_message_id"] = reply_message_id
|
|
78
|
+
return await self._http.post("/bot/send-message", body, MessageResponse)
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING
|
|
4
|
+
|
|
5
|
+
import socketio
|
|
6
|
+
|
|
7
|
+
from ._const import KARBO_API, SOCKET_TOPICS
|
|
8
|
+
from .logging import logger
|
|
9
|
+
from .router import Router
|
|
10
|
+
from .types import Context, Message, _Listener
|
|
11
|
+
|
|
12
|
+
if TYPE_CHECKING:
|
|
13
|
+
from .client import KarboAI
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class Dispatcher:
|
|
17
|
+
def __init__(self, client: KarboAI):
|
|
18
|
+
self._client = client
|
|
19
|
+
self._socket = None
|
|
20
|
+
self._listeners: dict[str, list[_Listener]] = {}
|
|
21
|
+
|
|
22
|
+
def bind(self, *routers: Router) -> None:
|
|
23
|
+
for router in routers:
|
|
24
|
+
for event, items in router.listeners.items():
|
|
25
|
+
if event not in self._listeners:
|
|
26
|
+
self._listeners[event] = []
|
|
27
|
+
self._listeners[event].extend(items)
|
|
28
|
+
logger.info("bound router: %s", router.name)
|
|
29
|
+
|
|
30
|
+
def attach(self, token: str, callback) -> None:
|
|
31
|
+
sio = socketio.AsyncClient()
|
|
32
|
+
|
|
33
|
+
@sio.on("connect")
|
|
34
|
+
async def _on_connect():
|
|
35
|
+
logger.info("connected to KarboAI")
|
|
36
|
+
await callback()
|
|
37
|
+
|
|
38
|
+
@sio.on("new_message")
|
|
39
|
+
async def _on_message(data):
|
|
40
|
+
msg = Message.model_validate(data)
|
|
41
|
+
if msg.author.user_id == self._client.id:
|
|
42
|
+
return
|
|
43
|
+
|
|
44
|
+
event = SOCKET_TOPICS.get(msg.type, "message")
|
|
45
|
+
logger.info(
|
|
46
|
+
"event=%s chatId=%s userId=%s type=%d",
|
|
47
|
+
event, msg.chat_id, msg.author.user_id, msg.type,
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
for listener in self._listeners.get(event, []):
|
|
51
|
+
ok = True
|
|
52
|
+
for mw in listener.middlewares:
|
|
53
|
+
if not await mw(Context(self._client, msg)):
|
|
54
|
+
ok = False
|
|
55
|
+
break
|
|
56
|
+
if ok:
|
|
57
|
+
await listener.callback(Context(self._client, msg))
|
|
58
|
+
|
|
59
|
+
self._socket = sio
|
|
60
|
+
sio.connect(
|
|
61
|
+
KARBO_API,
|
|
62
|
+
socketio_path="/bot/ws",
|
|
63
|
+
transports=["websocket"],
|
|
64
|
+
auth={"bot_token": token},
|
|
65
|
+
)
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import json
|
|
2
|
+
from typing import Type, TypeVar
|
|
3
|
+
|
|
4
|
+
import aiohttp
|
|
5
|
+
from pydantic import BaseModel
|
|
6
|
+
|
|
7
|
+
from ._const import KARBO_API
|
|
8
|
+
from .errors import KarboError
|
|
9
|
+
from .logging import logger
|
|
10
|
+
|
|
11
|
+
T = TypeVar("T", bound=BaseModel)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class Http:
|
|
15
|
+
def __init__(self, token: str):
|
|
16
|
+
self._headers = {
|
|
17
|
+
"Content-Type": "application/json",
|
|
18
|
+
"Bot-Token": token,
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
async def _request(self, method: str, path: str, schema: Type[T], **kwargs) -> T:
|
|
22
|
+
url = f"{KARBO_API}{path}"
|
|
23
|
+
async with aiohttp.ClientSession() as session:
|
|
24
|
+
async with session.request(method, url, headers=self._headers, **kwargs) as resp:
|
|
25
|
+
if not resp.ok:
|
|
26
|
+
logger.error("%s %s -> %d", method, path, resp.status)
|
|
27
|
+
raise KarboError(resp.status)
|
|
28
|
+
logger.info("%s %s -> %d", method, path, resp.status)
|
|
29
|
+
return schema.model_validate(await resp.json())
|
|
30
|
+
|
|
31
|
+
async def get(self, path: str, schema: Type[T]) -> T:
|
|
32
|
+
return await self._request("GET", path, schema)
|
|
33
|
+
|
|
34
|
+
async def post(self, path: str, body: dict, schema: Type[T]) -> T:
|
|
35
|
+
return await self._request("POST", path, schema, data=json.dumps(body))
|
|
36
|
+
|
|
37
|
+
async def upload(self, path: str, buffer: bytes, file_name: str, schema: Type[T]) -> T:
|
|
38
|
+
import mimetypes
|
|
39
|
+
|
|
40
|
+
form = aiohttp.FormData()
|
|
41
|
+
form.add_field(
|
|
42
|
+
"file",
|
|
43
|
+
buffer,
|
|
44
|
+
filename=file_name,
|
|
45
|
+
content_type=mimetypes.guess_type(file_name)[0] or "image/png",
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
headers = {k: v for k, v in self._headers.items() if k != "Content-Type"}
|
|
49
|
+
return await self._request("POST", path, schema, data=form, headers=headers)
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
|
|
3
|
+
logger = logging.getLogger("karboai")
|
|
4
|
+
_logger_init = False
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def setup_logging(enabled: bool = False) -> None:
|
|
8
|
+
global _logger_init
|
|
9
|
+
if _logger_init:
|
|
10
|
+
return
|
|
11
|
+
_logger_init = True
|
|
12
|
+
|
|
13
|
+
logger.disabled = not enabled
|
|
14
|
+
if enabled:
|
|
15
|
+
handler = logging.StreamHandler()
|
|
16
|
+
handler.setFormatter(logging.Formatter(
|
|
17
|
+
"%(asctime)s - %(name)s - %(levelname)s - %(message)s"
|
|
18
|
+
))
|
|
19
|
+
logger.addHandler(handler)
|
|
20
|
+
logger.setLevel(logging.INFO)
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from .types import Context, EventHandler, Middleware, _Listener
|
|
4
|
+
from .utils import _router_name
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def _cmd_middleware(prefix: str) -> Middleware:
|
|
8
|
+
async def _mw(ctx: Context) -> bool | None:
|
|
9
|
+
return ctx.message.content.startswith(prefix) or None
|
|
10
|
+
return _mw
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class Router:
|
|
14
|
+
def __init__(self, name: str | None = None):
|
|
15
|
+
self._name = name or _router_name()
|
|
16
|
+
self._listeners: dict[str, list[_Listener]] = {}
|
|
17
|
+
self._middlewares: list[Middleware] = []
|
|
18
|
+
|
|
19
|
+
@property
|
|
20
|
+
def name(self) -> str:
|
|
21
|
+
return self._name
|
|
22
|
+
|
|
23
|
+
@property
|
|
24
|
+
def listeners(self) -> dict[str, list[_Listener]]:
|
|
25
|
+
return self._listeners
|
|
26
|
+
|
|
27
|
+
def pre(self, middleware: Middleware) -> None:
|
|
28
|
+
self._middlewares.append(middleware)
|
|
29
|
+
|
|
30
|
+
def on(self, event: str, callback: EventHandler) -> None:
|
|
31
|
+
if event not in self._listeners:
|
|
32
|
+
self._listeners[event] = []
|
|
33
|
+
self._listeners[event].append(
|
|
34
|
+
_Listener(callback, list(self._middlewares))
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
def command(self, prefix: str, callback: EventHandler) -> None:
|
|
38
|
+
if "message" not in self._listeners:
|
|
39
|
+
self._listeners["message"] = []
|
|
40
|
+
self._listeners["message"].append(
|
|
41
|
+
_Listener(callback, [_cmd_middleware(prefix)] + list(self._middlewares))
|
|
42
|
+
)
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from enum import Enum
|
|
4
|
+
from typing import TYPE_CHECKING, Any, Awaitable, Callable
|
|
5
|
+
|
|
6
|
+
from pydantic import BaseModel
|
|
7
|
+
|
|
8
|
+
if TYPE_CHECKING:
|
|
9
|
+
from .client import KarboAI
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class BotStatus(str, Enum):
|
|
13
|
+
NOT_OFFICIAL = "NOT_OFFICIAL"
|
|
14
|
+
OFFICIAL = "OFFICIAL"
|
|
15
|
+
BANNED = "BANNED"
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class Frame(BaseModel):
|
|
19
|
+
frame_id: str
|
|
20
|
+
file: str
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class Reaction(BaseModel):
|
|
24
|
+
reaction: str
|
|
25
|
+
is_sticker: bool
|
|
26
|
+
count: int
|
|
27
|
+
me: bool
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class Author(BaseModel):
|
|
31
|
+
user_id: str
|
|
32
|
+
nickname: str
|
|
33
|
+
avatar_url: str
|
|
34
|
+
avatar_frame: Frame | None = None
|
|
35
|
+
role: int | None = None
|
|
36
|
+
app_role: int | None = None
|
|
37
|
+
panel_color: str | None = None
|
|
38
|
+
level: int | None = None
|
|
39
|
+
nickname_color: str | None = None
|
|
40
|
+
nickname_emoji: str | None = None
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class User(BaseModel):
|
|
44
|
+
user_id: str
|
|
45
|
+
nickname: str
|
|
46
|
+
avatar: str
|
|
47
|
+
role: int
|
|
48
|
+
short_info: str
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class Member(BaseModel):
|
|
52
|
+
user_id: str
|
|
53
|
+
nickname: str
|
|
54
|
+
avatar: str
|
|
55
|
+
role: int
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class Message(BaseModel):
|
|
59
|
+
message_id: str
|
|
60
|
+
chat_id: str
|
|
61
|
+
content: str
|
|
62
|
+
created_time: int
|
|
63
|
+
type: int
|
|
64
|
+
reply_message_id: str | None = None
|
|
65
|
+
audio: str | None = None
|
|
66
|
+
sticker: str | None = None
|
|
67
|
+
images: list[str] = []
|
|
68
|
+
author: Author
|
|
69
|
+
audio_duration_ms: int | None = None
|
|
70
|
+
waveform: list[Any] | None = None
|
|
71
|
+
video_note: str | None = None
|
|
72
|
+
video_note_duration_ms: int | None = None
|
|
73
|
+
transparent: bool | None = None
|
|
74
|
+
bubble_id: str | None = None
|
|
75
|
+
bubble_version: int | None = None
|
|
76
|
+
reactions: list[Reaction] | None = None
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
class MeResponse(BaseModel):
|
|
80
|
+
bot_id: str
|
|
81
|
+
name: str
|
|
82
|
+
status: BotStatus
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
class MessageResponse(BaseModel):
|
|
86
|
+
message_id: str
|
|
87
|
+
created_time: int
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
class UploadResponse(BaseModel):
|
|
91
|
+
url: str
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
class MembersResponse(BaseModel):
|
|
95
|
+
items: list[User]
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
class OkResponse(BaseModel):
|
|
99
|
+
ok: bool
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
class Context:
|
|
103
|
+
__slots__ = ("karbo", "message")
|
|
104
|
+
|
|
105
|
+
def __init__(self, karbo: KarboAI, message: Message):
|
|
106
|
+
self.karbo = karbo
|
|
107
|
+
self.message = message
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
EventHandler = Callable[[Context], Awaitable[None]]
|
|
111
|
+
Middleware = Callable[[Context], Awaitable[bool | None]]
|
|
112
|
+
ConnectCallback = Callable[[], Awaitable[None]]
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
class _Listener:
|
|
116
|
+
def __init__(self, callback: EventHandler, middlewares: list[Middleware]):
|
|
117
|
+
self.callback = callback
|
|
118
|
+
self.middlewares = middlewares
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import random
|
|
2
|
+
import string
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def bold(text: str) -> str:
|
|
6
|
+
return f"**{text}**"
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def italic(text: str) -> str:
|
|
10
|
+
return f"__{text}__"
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def centralize(text: str) -> str:
|
|
14
|
+
return f"[C]{text}"
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def code(text: str) -> str:
|
|
18
|
+
return f"`{text}`"
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def strikethrough(text: str) -> str:
|
|
22
|
+
return f"~~{text}~~"
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def underline(text: str) -> str:
|
|
26
|
+
return f"++{text}++"
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def hyperlink(text: str, url: str) -> str:
|
|
30
|
+
return f"[{text}]({url})"
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _router_name() -> str:
|
|
34
|
+
rand = "".join(random.choices(string.ascii_lowercase + string.digits, k=7))
|
|
35
|
+
return f"router-{rand}"
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: karboai
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Python library for https://karboai.com
|
|
5
|
+
Home-page: https://github.com/yourusername/karboai-python
|
|
6
|
+
License: MIT
|
|
7
|
+
Requires-Python: >=3.8
|
|
8
|
+
Description-Content-Type: text/markdown
|
|
9
|
+
Requires-Dist: aiohttp>=3.9
|
|
10
|
+
Requires-Dist: python-socketio>=5.10
|
|
11
|
+
Requires-Dist: pydantic>=2.0
|
|
12
|
+
Dynamic: description
|
|
13
|
+
Dynamic: description-content-type
|
|
14
|
+
Dynamic: home-page
|
|
15
|
+
Dynamic: license
|
|
16
|
+
Dynamic: requires-dist
|
|
17
|
+
Dynamic: requires-python
|
|
18
|
+
Dynamic: summary
|
|
19
|
+
|
|
20
|
+
# KarboAI
|
|
21
|
+
|
|
22
|
+
Python library for [KarboAI](https://karboai.com) bot development.
|
|
23
|
+
|
|
24
|
+
## Install
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
pip install karboai
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Usage
|
|
31
|
+
|
|
32
|
+
```python
|
|
33
|
+
from karboai import KarboAI, Router, bold
|
|
34
|
+
|
|
35
|
+
karbo = KarboAI(token="your-token", id="your-bot-id", enable_logging=True)
|
|
36
|
+
router = Router()
|
|
37
|
+
|
|
38
|
+
@router.command("/start")
|
|
39
|
+
async def start(ctx):
|
|
40
|
+
await karbo.text(ctx.message.chat_id, bold("Welcome!"))
|
|
41
|
+
|
|
42
|
+
@router.on("message")
|
|
43
|
+
async def echo(ctx):
|
|
44
|
+
await karbo.text(ctx.message.chat_id, ctx.message.content)
|
|
45
|
+
|
|
46
|
+
karbo.bind(router)
|
|
47
|
+
karbo.attach()
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## API
|
|
51
|
+
|
|
52
|
+
| Method | Description |
|
|
53
|
+
|--------|-------------|
|
|
54
|
+
| `me()` | Bot info |
|
|
55
|
+
| `text(chat_id, content, reply_message_id?)` | Send text |
|
|
56
|
+
| `image(chat_id, images, reply_message_id?)` | Send images |
|
|
57
|
+
| `upload(buffer, file_name?)` | Upload image, returns URL |
|
|
58
|
+
| `message(chat_id, message_id)` | Get message |
|
|
59
|
+
| `members(chat_id, limit?, offset?)` | Chat members |
|
|
60
|
+
| `user(user_id)` | User info |
|
|
61
|
+
| `leave(chat_id)` | Leave chat |
|
|
62
|
+
| `kick(chat_id, user_id)` | Kick user |
|
|
63
|
+
| `attach(callback?)` | Connect to WebSocket |
|
|
64
|
+
| `bind(*routers)` | Bind routers |
|
|
65
|
+
|
|
66
|
+
## Events
|
|
67
|
+
|
|
68
|
+
`message`, `join`, `leave`, `voiceStart`, `voiceEnd`, `sticker`
|
|
69
|
+
|
|
70
|
+
## Text formatting
|
|
71
|
+
|
|
72
|
+
`bold()`, `italic()`, `centralize()`, `code()`, `strikethrough()`, `underline()`, `hyperlink(text, url)`
|
|
73
|
+
|
|
74
|
+
## Error handling
|
|
75
|
+
|
|
76
|
+
```python
|
|
77
|
+
from karboai import KarboError
|
|
78
|
+
|
|
79
|
+
try:
|
|
80
|
+
await karbo.me()
|
|
81
|
+
except KarboError as e:
|
|
82
|
+
print(e.status_code, e.name, e)
|
|
83
|
+
```
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
setup.py
|
|
3
|
+
karboai/__init__.py
|
|
4
|
+
karboai/_const.py
|
|
5
|
+
karboai/client.py
|
|
6
|
+
karboai/dispatcher.py
|
|
7
|
+
karboai/errors.py
|
|
8
|
+
karboai/http.py
|
|
9
|
+
karboai/logging.py
|
|
10
|
+
karboai/router.py
|
|
11
|
+
karboai/types.py
|
|
12
|
+
karboai/utils.py
|
|
13
|
+
karboai.egg-info/PKG-INFO
|
|
14
|
+
karboai.egg-info/SOURCES.txt
|
|
15
|
+
karboai.egg-info/dependency_links.txt
|
|
16
|
+
karboai.egg-info/requires.txt
|
|
17
|
+
karboai.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
karboai
|
karboai-1.0.0/setup.cfg
ADDED
karboai-1.0.0/setup.py
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
from setuptools import setup, find_packages
|
|
2
|
+
|
|
3
|
+
with open("README.md", "r", encoding="utf-8") as f:
|
|
4
|
+
long_description = f.read()
|
|
5
|
+
|
|
6
|
+
setup(
|
|
7
|
+
name="karboai",
|
|
8
|
+
version="1.0.0",
|
|
9
|
+
description="Python library for https://karboai.com",
|
|
10
|
+
long_description=long_description,
|
|
11
|
+
long_description_content_type="text/markdown",
|
|
12
|
+
url="https://github.com/yourusername/karboai-python",
|
|
13
|
+
packages=find_packages(),
|
|
14
|
+
python_requires=">=3.8",
|
|
15
|
+
install_requires=[
|
|
16
|
+
"aiohttp>=3.9",
|
|
17
|
+
"python-socketio>=5.10",
|
|
18
|
+
"pydantic>=2.0",
|
|
19
|
+
],
|
|
20
|
+
license="MIT",
|
|
21
|
+
)
|