wsapi-client 0.1.0__py3-none-any.whl
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.
- wsapi_client/__init__.py +12 -0
- wsapi_client/api/calls.py +16 -0
- wsapi_client/api/chats.py +21 -0
- wsapi_client/api/contacts.py +54 -0
- wsapi_client/api/groups.py +39 -0
- wsapi_client/api/instance.py +38 -0
- wsapi_client/api/media.py +15 -0
- wsapi_client/api/messages.py +125 -0
- wsapi_client/api/session.py +43 -0
- wsapi_client/api/users.py +16 -0
- wsapi_client/client.py +39 -0
- wsapi_client/events/factory.py +82 -0
- wsapi_client/exceptions.py +13 -0
- wsapi_client/http.py +123 -0
- wsapi_client/models/entities/chats/chat_archive.py +8 -0
- wsapi_client/models/entities/chats/chat_ephemeral.py +10 -0
- wsapi_client/models/entities/chats/chat_info.py +9 -0
- wsapi_client/models/entities/chats/chat_mute.py +10 -0
- wsapi_client/models/entities/chats/chat_pin.py +9 -0
- wsapi_client/models/entities/chats/chat_read.py +8 -0
- wsapi_client/models/entities/contacts/contact_business_profile.py +11 -0
- wsapi_client/models/entities/contacts/contact_info.py +15 -0
- wsapi_client/models/entities/contacts/contact_picture.py +9 -0
- wsapi_client/models/entities/groups/group_info.py +24 -0
- wsapi_client/models/entities/instance/__init__.py +3 -0
- wsapi_client/models/entities/instance/instance_settings.py +14 -0
- wsapi_client/models/entities/messages/__init__.py +3 -0
- wsapi_client/models/entities/messages/message_contact.py +9 -0
- wsapi_client/models/entities/messages/message_created.py +8 -0
- wsapi_client/models/entities/messages/message_edit.py +10 -0
- wsapi_client/models/entities/messages/message_extended_text.py +11 -0
- wsapi_client/models/entities/messages/message_media.py +22 -0
- wsapi_client/models/entities/messages/message_pin.py +11 -0
- wsapi_client/models/entities/messages/message_reaction.py +9 -0
- wsapi_client/models/entities/messages/message_reply_to.py +11 -0
- wsapi_client/models/entities/session/session_pair_code.py +9 -0
- wsapi_client/models/entities/session/session_status.py +10 -0
- wsapi_client/models/entities/users/sender.py +11 -0
- wsapi_client/models/entities/users/user_info.py +13 -0
- wsapi_client/models/events/base_event.py +11 -0
- wsapi_client/models/events/calls/call_accept_event.py +11 -0
- wsapi_client/models/events/calls/call_offer_event.py +14 -0
- wsapi_client/models/events/calls/call_terminate_event.py +12 -0
- wsapi_client/models/events/chats/chat_presence_event.py +12 -0
- wsapi_client/models/events/chats/chat_setting_event.py +19 -0
- wsapi_client/models/events/contacts/contact_event.py +10 -0
- wsapi_client/models/events/messages/message_delete_event.py +18 -0
- wsapi_client/models/events/messages/message_event.py +36 -0
- wsapi_client/models/events/messages/message_history_sync_event.py +9 -0
- wsapi_client/models/events/messages/message_read_event.py +17 -0
- wsapi_client/models/events/messages/message_star_event.py +14 -0
- wsapi_client/models/events/session/session_logged_error_event.py +9 -0
- wsapi_client/models/events/session/session_logged_in_event.py +8 -0
- wsapi_client/models/events/session/session_logged_out_event.py +8 -0
- wsapi_client/models/events/users/user_picture_event.py +11 -0
- wsapi_client/models/events/users/user_presence_event.py +11 -0
- wsapi_client/models/events/users/user_push_name_event.py +9 -0
- wsapi_client/models/events/users/user_status_event.py +9 -0
- wsapi_client/models/problem_details.py +13 -0
- wsapi_client/models/requests/calls/reject_call_request.py +8 -0
- wsapi_client/models/requests/media/media_download_request.py +17 -0
- wsapi_client/models/requests/messages/__init__.py +33 -0
- wsapi_client/models/requests/messages/message_delete_for_me_request.py +12 -0
- wsapi_client/models/requests/messages/message_delete_request.py +9 -0
- wsapi_client/models/requests/messages/message_mark_as_read_request.py +10 -0
- wsapi_client/models/requests/messages/message_request_base.py +12 -0
- wsapi_client/models/requests/messages/message_send_audio_request.py +14 -0
- wsapi_client/models/requests/messages/message_send_contact_request.py +10 -0
- wsapi_client/models/requests/messages/message_send_document_request.py +14 -0
- wsapi_client/models/requests/messages/message_send_image_request.py +15 -0
- wsapi_client/models/requests/messages/message_send_link_request.py +15 -0
- wsapi_client/models/requests/messages/message_send_location_request.py +15 -0
- wsapi_client/models/requests/messages/message_send_reaction_request.py +11 -0
- wsapi_client/models/requests/messages/message_send_sticker_request.py +13 -0
- wsapi_client/models/requests/messages/message_send_text_request.py +10 -0
- wsapi_client/models/requests/messages/message_send_video_request.py +15 -0
- wsapi_client/models/requests/messages/message_send_voice_request.py +13 -0
- wsapi_client/models/requests/messages/message_star_request.py +11 -0
- wsapi_client/sse/client.py +111 -0
- wsapi_client-0.1.0.dist-info/METADATA +166 -0
- wsapi_client-0.1.0.dist-info/RECORD +82 -0
- wsapi_client-0.1.0.dist-info/WHEEL +4 -0
wsapi_client/__init__.py
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
from .client import WSApiClient
|
|
2
|
+
from .sse.client import SSEClient, SSEConnectionState
|
|
3
|
+
from .exceptions import ApiException
|
|
4
|
+
from .http import ApiResponse
|
|
5
|
+
|
|
6
|
+
__all__ = [
|
|
7
|
+
"WSApiClient",
|
|
8
|
+
"SSEClient",
|
|
9
|
+
"SSEConnectionState",
|
|
10
|
+
"ApiException",
|
|
11
|
+
"ApiResponse",
|
|
12
|
+
]
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from ..http import WSApiHttp, ApiResponse
|
|
4
|
+
from ..models.requests.calls.reject_call_request import RejectCallRequest
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class CallsClient:
|
|
8
|
+
def __init__(self, http: WSApiHttp) -> None:
|
|
9
|
+
self._http = http
|
|
10
|
+
|
|
11
|
+
def reject(self, call_id: str, request: RejectCallRequest) -> None:
|
|
12
|
+
self._http.send_json("PUT", f"/calls/{call_id}/reject", model=None, json=request.model_dump(by_alias=True))
|
|
13
|
+
return None
|
|
14
|
+
|
|
15
|
+
def try_reject(self, call_id: str, request: RejectCallRequest) -> ApiResponse[None]:
|
|
16
|
+
return self._http.try_send_json("PUT", f"/calls/{call_id}/reject", model=None, json=request.model_dump(by_alias=True))
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
from ..http import WSApiHttp, ApiResponse
|
|
3
|
+
from ..models.entities.chats.chat_info import ChatInfo
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class ChatsClient:
|
|
7
|
+
def __init__(self, http: WSApiHttp) -> None:
|
|
8
|
+
self._http = http
|
|
9
|
+
|
|
10
|
+
def list(self) -> list[ChatInfo]:
|
|
11
|
+
return self._http.send_json("GET", "/chats", model=list[ChatInfo])
|
|
12
|
+
|
|
13
|
+
def get(self, chat_id: str) -> ChatInfo:
|
|
14
|
+
return self._http.send_json("GET", f"/chats/{chat_id}", model=ChatInfo)
|
|
15
|
+
|
|
16
|
+
# Try
|
|
17
|
+
def try_list(self) -> ApiResponse[list[ChatInfo]]:
|
|
18
|
+
return self._http.try_send_json("GET", "/chats", model=list[ChatInfo])
|
|
19
|
+
|
|
20
|
+
def try_get(self, chat_id: str) -> ApiResponse[ChatInfo]:
|
|
21
|
+
return self._http.try_send_json("GET", f"/chats/{chat_id}", model=ChatInfo)
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
from ..http import WSApiHttp, ApiResponse
|
|
3
|
+
from ..models.entities.contacts.contact_info import ContactInfo
|
|
4
|
+
from ..models.entities.contacts.contact_picture import ContactPicture
|
|
5
|
+
from ..models.entities.contacts.contact_business_profile import ContactBusinessProfile
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class ContactsClient:
|
|
9
|
+
def __init__(self, http: WSApiHttp) -> None:
|
|
10
|
+
self._http = http
|
|
11
|
+
|
|
12
|
+
# Standard methods
|
|
13
|
+
def list(self) -> list[ContactInfo]:
|
|
14
|
+
return self._http.send_json("GET", "/contacts", model=list[ContactInfo])
|
|
15
|
+
|
|
16
|
+
def get(self, contact_id: str) -> ContactInfo:
|
|
17
|
+
return self._http.send_json("GET", f"/contacts/{contact_id}", model=ContactInfo)
|
|
18
|
+
|
|
19
|
+
def get_picture(self, contact_id: str) -> ContactPicture:
|
|
20
|
+
return self._http.send_json("GET", f"/contacts/{contact_id}/picture", model=ContactPicture)
|
|
21
|
+
|
|
22
|
+
def get_business_profile(self, contact_id: str) -> ContactBusinessProfile:
|
|
23
|
+
return self._http.send_json("GET", f"/contacts/{contact_id}/business", model=ContactBusinessProfile)
|
|
24
|
+
|
|
25
|
+
def create(self, payload: dict) -> None:
|
|
26
|
+
self._http.send_json("POST", "/contacts", model=dict, json=payload)
|
|
27
|
+
|
|
28
|
+
def update(self, contact_id: str, payload: dict) -> None:
|
|
29
|
+
self._http.send_json("PUT", f"/contacts/{contact_id}", model=dict, json=payload)
|
|
30
|
+
|
|
31
|
+
def subscribe_presence(self, contact_id: str) -> None:
|
|
32
|
+
self._http.send_json("POST", f"/contacts/{contact_id}/presence", model=dict, json={})
|
|
33
|
+
|
|
34
|
+
# Try methods
|
|
35
|
+
def try_list(self) -> ApiResponse[list[ContactInfo]]:
|
|
36
|
+
return self._http.try_send_json("GET", "/contacts", model=list[ContactInfo])
|
|
37
|
+
|
|
38
|
+
def try_get(self, contact_id: str) -> ApiResponse[ContactInfo]:
|
|
39
|
+
return self._http.try_send_json("GET", f"/contacts/{contact_id}", model=ContactInfo)
|
|
40
|
+
|
|
41
|
+
def try_get_picture(self, contact_id: str) -> ApiResponse[ContactPicture]:
|
|
42
|
+
return self._http.try_send_json("GET", f"/contacts/{contact_id}/picture", model=ContactPicture)
|
|
43
|
+
|
|
44
|
+
def try_get_business_profile(self, contact_id: str) -> ApiResponse[ContactBusinessProfile]:
|
|
45
|
+
return self._http.try_send_json("GET", f"/contacts/{contact_id}/business", model=ContactBusinessProfile)
|
|
46
|
+
|
|
47
|
+
def try_create(self, payload: dict) -> ApiResponse[object]:
|
|
48
|
+
return self._http.try_send_json("POST", "/contacts", model=dict, json=payload)
|
|
49
|
+
|
|
50
|
+
def try_update(self, contact_id: str, payload: dict) -> ApiResponse[object]:
|
|
51
|
+
return self._http.try_send_json("PUT", f"/contacts/{contact_id}", model=dict, json=payload)
|
|
52
|
+
|
|
53
|
+
def try_subscribe_presence(self, contact_id: str) -> ApiResponse[object]:
|
|
54
|
+
return self._http.try_send_json("POST", f"/contacts/{contact_id}/presence", model=dict, json={})
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
from ..http import WSApiHttp, ApiResponse
|
|
3
|
+
from ..models.entities.groups.group_info import GroupInfo
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class GroupsClient:
|
|
7
|
+
def __init__(self, http: WSApiHttp) -> None:
|
|
8
|
+
self._http = http
|
|
9
|
+
|
|
10
|
+
def list(self) -> list[GroupInfo]:
|
|
11
|
+
return self._http.send_json("GET", "/groups", model=list[GroupInfo])
|
|
12
|
+
|
|
13
|
+
def get(self, group_id: str) -> GroupInfo:
|
|
14
|
+
return self._http.send_json("GET", f"/groups/{group_id}", model=GroupInfo)
|
|
15
|
+
|
|
16
|
+
def update_description(self, group_id: str, payload: dict) -> None:
|
|
17
|
+
self._http.send_json("PUT", f"/groups/{group_id}/description", model=dict, json=payload)
|
|
18
|
+
|
|
19
|
+
def update_name(self, group_id: str, payload: dict) -> None:
|
|
20
|
+
self._http.send_json("PUT", f"/groups/{group_id}/name", model=dict, json=payload)
|
|
21
|
+
|
|
22
|
+
def leave(self, group_id: str) -> None:
|
|
23
|
+
self._http.send_json("PUT", f"/groups/{group_id}/leave", model=dict)
|
|
24
|
+
|
|
25
|
+
# Try variants
|
|
26
|
+
def try_list(self) -> ApiResponse[list[GroupInfo]]:
|
|
27
|
+
return self._http.try_send_json("GET", "/groups", model=list[GroupInfo])
|
|
28
|
+
|
|
29
|
+
def try_get(self, group_id: str) -> ApiResponse[GroupInfo]:
|
|
30
|
+
return self._http.try_send_json("GET", f"/groups/{group_id}", model=GroupInfo)
|
|
31
|
+
|
|
32
|
+
def try_update_description(self, group_id: str, payload: dict) -> ApiResponse[object]:
|
|
33
|
+
return self._http.try_send_json("PUT", f"/groups/{group_id}/description", model=dict, json=payload)
|
|
34
|
+
|
|
35
|
+
def try_update_name(self, group_id: str, payload: dict) -> ApiResponse[object]:
|
|
36
|
+
return self._http.try_send_json("PUT", f"/groups/{group_id}/name", model=dict, json=payload)
|
|
37
|
+
|
|
38
|
+
def try_leave(self, group_id: str) -> ApiResponse[object]:
|
|
39
|
+
return self._http.try_send_json("PUT", f"/groups/{group_id}/leave", model=dict)
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
from typing import Optional
|
|
3
|
+
|
|
4
|
+
from pydantic import BaseModel, Field
|
|
5
|
+
|
|
6
|
+
from ..http import WSApiHttp, ApiResponse
|
|
7
|
+
from ..models.entities.instance.instance_settings import InstanceSettings
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class InstanceClient:
|
|
11
|
+
def __init__(self, http: WSApiHttp) -> None:
|
|
12
|
+
self._http = http
|
|
13
|
+
|
|
14
|
+
# Standard methods (raise ApiException)
|
|
15
|
+
def get_settings(self) -> InstanceSettings:
|
|
16
|
+
return self._http.send_json("GET", "/instance/settings", model=InstanceSettings)
|
|
17
|
+
|
|
18
|
+
def update_settings(self, settings: InstanceSettings) -> None:
|
|
19
|
+
self._http.send_json("PUT", "/instance/settings", model=dict, json=settings.model_dump(by_alias=True))
|
|
20
|
+
|
|
21
|
+
def restart(self) -> None:
|
|
22
|
+
self._http.send_json("PUT", "/instance/restart", model=dict)
|
|
23
|
+
|
|
24
|
+
def update_api_key(self) -> str:
|
|
25
|
+
return self._http.send_json("PUT", "/instance/apikey", model=str)
|
|
26
|
+
|
|
27
|
+
# Try methods (no exceptions)
|
|
28
|
+
def try_get_settings(self) -> ApiResponse[InstanceSettings]:
|
|
29
|
+
return self._http.try_send_json("GET", "/instance/settings", model=InstanceSettings)
|
|
30
|
+
|
|
31
|
+
def try_update_settings(self, settings: InstanceSettings) -> ApiResponse[object]:
|
|
32
|
+
return self._http.try_send_json("PUT", "/instance/settings", model=dict, json=settings.model_dump(by_alias=True))
|
|
33
|
+
|
|
34
|
+
def try_restart(self) -> ApiResponse[object]:
|
|
35
|
+
return self._http.try_send_json("PUT", "/instance/restart", model=dict)
|
|
36
|
+
|
|
37
|
+
def try_update_api_key(self) -> ApiResponse[str]:
|
|
38
|
+
return self._http.try_send_json("PUT", "/instance/apikey", model=str)
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from ..http import WSApiHttp, ApiResponse
|
|
4
|
+
from ..models.requests.media.media_download_request import MediaDownloadRequest
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class MediaClient:
|
|
8
|
+
def __init__(self, http: WSApiHttp) -> None:
|
|
9
|
+
self._http = http
|
|
10
|
+
|
|
11
|
+
def download(self, req: MediaDownloadRequest) -> bytes:
|
|
12
|
+
return self._http.send_bytes("POST", "/media/download", json=req.model_dump(by_alias=True))
|
|
13
|
+
|
|
14
|
+
def try_download(self, req: MediaDownloadRequest) -> ApiResponse[bytes]:
|
|
15
|
+
return self._http.try_send_bytes("POST", "/media/download", json=req.model_dump(by_alias=True))
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
from typing import Optional
|
|
3
|
+
|
|
4
|
+
from ..http import WSApiHttp, ApiResponse
|
|
5
|
+
from ..models.entities.messages.message_created import MessageCreated
|
|
6
|
+
from ..models.requests.messages import (
|
|
7
|
+
MessageSendTextRequest,
|
|
8
|
+
MessageSendImageRequest,
|
|
9
|
+
MessageSendVideoRequest,
|
|
10
|
+
MessageSendAudioRequest,
|
|
11
|
+
MessageSendVoiceRequest,
|
|
12
|
+
MessageSendStickerRequest,
|
|
13
|
+
MessageSendDocumentRequest,
|
|
14
|
+
MessageSendContactRequest,
|
|
15
|
+
MessageSendLocationRequest,
|
|
16
|
+
MessageSendLinkRequest,
|
|
17
|
+
MessageSendReactionRequest,
|
|
18
|
+
MessageMarkAsReadRequest,
|
|
19
|
+
MessageDeleteRequest,
|
|
20
|
+
MessageDeleteForMeRequest,
|
|
21
|
+
MessageStarRequest,
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class MessagesClient:
|
|
26
|
+
def __init__(self, http: WSApiHttp) -> None:
|
|
27
|
+
self._http = http
|
|
28
|
+
|
|
29
|
+
# Standard methods
|
|
30
|
+
def send_text(self, req: MessageSendTextRequest) -> MessageCreated:
|
|
31
|
+
return self._http.send_json("POST", "messages/text", model=MessageCreated, json=req.model_dump(by_alias=True))
|
|
32
|
+
|
|
33
|
+
def send_image(self, req: MessageSendImageRequest) -> MessageCreated:
|
|
34
|
+
return self._http.send_json("POST", "messages/image", model=MessageCreated, json=req.model_dump(by_alias=True))
|
|
35
|
+
|
|
36
|
+
def send_video(self, req: MessageSendVideoRequest) -> MessageCreated:
|
|
37
|
+
return self._http.send_json("POST", "messages/video", model=MessageCreated, json=req.model_dump(by_alias=True))
|
|
38
|
+
|
|
39
|
+
def send_audio(self, req: MessageSendAudioRequest) -> MessageCreated:
|
|
40
|
+
return self._http.send_json("POST", "messages/audio", model=MessageCreated, json=req.model_dump(by_alias=True))
|
|
41
|
+
|
|
42
|
+
def send_voice(self, req: MessageSendVoiceRequest) -> MessageCreated:
|
|
43
|
+
return self._http.send_json("POST", "messages/voice", model=MessageCreated, json=req.model_dump(by_alias=True))
|
|
44
|
+
|
|
45
|
+
def send_sticker(self, req: MessageSendStickerRequest) -> MessageCreated:
|
|
46
|
+
return self._http.send_json("POST", "messages/sticker", model=MessageCreated, json=req.model_dump(by_alias=True))
|
|
47
|
+
|
|
48
|
+
def send_document(self, req: MessageSendDocumentRequest) -> MessageCreated:
|
|
49
|
+
return self._http.send_json("POST", "messages/document", model=MessageCreated, json=req.model_dump(by_alias=True))
|
|
50
|
+
|
|
51
|
+
def send_contact(self, req: MessageSendContactRequest) -> MessageCreated:
|
|
52
|
+
return self._http.send_json("POST", "messages/contact", model=MessageCreated, json=req.model_dump(by_alias=True))
|
|
53
|
+
|
|
54
|
+
def send_location(self, req: MessageSendLocationRequest) -> MessageCreated:
|
|
55
|
+
return self._http.send_json("POST", "messages/location", model=MessageCreated, json=req.model_dump(by_alias=True))
|
|
56
|
+
|
|
57
|
+
def send_link(self, req: MessageSendLinkRequest) -> MessageCreated:
|
|
58
|
+
return self._http.send_json("POST", "messages/link", model=MessageCreated, json=req.model_dump(by_alias=True))
|
|
59
|
+
|
|
60
|
+
def send_reaction(self, message_id: str, req: MessageSendReactionRequest) -> MessageCreated:
|
|
61
|
+
return self._http.send_json("POST", f"messages/{message_id}/reaction", model=MessageCreated, json=req.model_dump(by_alias=True))
|
|
62
|
+
|
|
63
|
+
def edit_text(self, message_id: str, req: MessageSendTextRequest) -> MessageCreated:
|
|
64
|
+
return self._http.send_json("PUT", f"messages/{message_id}/text", model=MessageCreated, json=req.model_dump(by_alias=True))
|
|
65
|
+
|
|
66
|
+
def mark_as_read(self, message_id: str, req: MessageMarkAsReadRequest) -> None:
|
|
67
|
+
self._http.send_json("PUT", f"messages/{message_id}/read", model=None, json=req.model_dump(by_alias=True))
|
|
68
|
+
|
|
69
|
+
def star(self, message_id: str, req: MessageStarRequest) -> None:
|
|
70
|
+
self._http.send_json("PUT", f"messages/{message_id}/star", model=None, json=req.model_dump(by_alias=True))
|
|
71
|
+
|
|
72
|
+
def delete(self, message_id: str, req: MessageDeleteRequest) -> None:
|
|
73
|
+
self._http.send_json("PUT", f"messages/{message_id}/delete", model=None, json=req.model_dump(by_alias=True))
|
|
74
|
+
|
|
75
|
+
def delete_for_me(self, message_id: str, req: MessageDeleteForMeRequest) -> None:
|
|
76
|
+
self._http.send_json("PUT", f"messages/{message_id}/delete/forme", model=None, json=req.model_dump(by_alias=True))
|
|
77
|
+
|
|
78
|
+
# Try variants
|
|
79
|
+
def try_send_text(self, req: MessageSendTextRequest) -> ApiResponse[MessageCreated]:
|
|
80
|
+
return self._http.try_send_json("POST", "messages/text", model=MessageCreated, json=req.model_dump(by_alias=True))
|
|
81
|
+
|
|
82
|
+
def try_send_image(self, req: MessageSendImageRequest) -> ApiResponse[MessageCreated]:
|
|
83
|
+
return self._http.try_send_json("POST", "messages/image", model=MessageCreated, json=req.model_dump(by_alias=True))
|
|
84
|
+
|
|
85
|
+
def try_send_video(self, req: MessageSendVideoRequest) -> ApiResponse[MessageCreated]:
|
|
86
|
+
return self._http.try_send_json("POST", "messages/video", model=MessageCreated, json=req.model_dump(by_alias=True))
|
|
87
|
+
|
|
88
|
+
def try_send_audio(self, req: MessageSendAudioRequest) -> ApiResponse[MessageCreated]:
|
|
89
|
+
return self._http.try_send_json("POST", "messages/audio", model=MessageCreated, json=req.model_dump(by_alias=True))
|
|
90
|
+
|
|
91
|
+
def try_send_voice(self, req: MessageSendVoiceRequest) -> ApiResponse[MessageCreated]:
|
|
92
|
+
return self._http.try_send_json("POST", "messages/voice", model=MessageCreated, json=req.model_dump(by_alias=True))
|
|
93
|
+
|
|
94
|
+
def try_send_sticker(self, req: MessageSendStickerRequest) -> ApiResponse[MessageCreated]:
|
|
95
|
+
return self._http.try_send_json("POST", "messages/sticker", model=MessageCreated, json=req.model_dump(by_alias=True))
|
|
96
|
+
|
|
97
|
+
def try_send_document(self, req: MessageSendDocumentRequest) -> ApiResponse[MessageCreated]:
|
|
98
|
+
return self._http.try_send_json("POST", "messages/document", model=MessageCreated, json=req.model_dump(by_alias=True))
|
|
99
|
+
|
|
100
|
+
def try_send_contact(self, req: MessageSendContactRequest) -> ApiResponse[MessageCreated]:
|
|
101
|
+
return self._http.try_send_json("POST", "messages/contact", model=MessageCreated, json=req.model_dump(by_alias=True))
|
|
102
|
+
|
|
103
|
+
def try_send_location(self, req: MessageSendLocationRequest) -> ApiResponse[MessageCreated]:
|
|
104
|
+
return self._http.try_send_json("POST", "messages/location", model=MessageCreated, json=req.model_dump(by_alias=True))
|
|
105
|
+
|
|
106
|
+
def try_send_link(self, req: MessageSendLinkRequest) -> ApiResponse[MessageCreated]:
|
|
107
|
+
return self._http.try_send_json("POST", "messages/link", model=MessageCreated, json=req.model_dump(by_alias=True))
|
|
108
|
+
|
|
109
|
+
def try_send_reaction(self, message_id: str, req: MessageSendReactionRequest) -> ApiResponse[MessageCreated]:
|
|
110
|
+
return self._http.try_send_json("POST", f"messages/{message_id}/reaction", model=MessageCreated, json=req.model_dump(by_alias=True))
|
|
111
|
+
|
|
112
|
+
def try_edit_text(self, message_id: str, req: MessageSendTextRequest) -> ApiResponse[MessageCreated]:
|
|
113
|
+
return self._http.try_send_json("PUT", f"messages/{message_id}/text", model=MessageCreated, json=req.model_dump(by_alias=True))
|
|
114
|
+
|
|
115
|
+
def try_mark_as_read(self, message_id: str, req: MessageMarkAsReadRequest) -> ApiResponse[object]:
|
|
116
|
+
return self._http.try_send_json("PUT", f"messages/{message_id}/read", model=None, json=req.model_dump(by_alias=True))
|
|
117
|
+
|
|
118
|
+
def try_star(self, message_id: str, req: MessageStarRequest) -> ApiResponse[object]:
|
|
119
|
+
return self._http.try_send_json("PUT", f"messages/{message_id}/star", model=None, json=req.model_dump(by_alias=True))
|
|
120
|
+
|
|
121
|
+
def try_delete(self, message_id: str, req: MessageDeleteRequest) -> ApiResponse[object]:
|
|
122
|
+
return self._http.try_send_json("PUT", f"messages/{message_id}/delete", model=None, json=req.model_dump(by_alias=True))
|
|
123
|
+
|
|
124
|
+
def try_delete_for_me(self, message_id: str, req: MessageDeleteForMeRequest) -> ApiResponse[object]:
|
|
125
|
+
return self._http.try_send_json("PUT", f"messages/{message_id}/delete/forme", model=None, json=req.model_dump(by_alias=True))
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
from ..http import WSApiHttp, ApiResponse
|
|
3
|
+
from ..models.entities.session.session_status import SessionStatus
|
|
4
|
+
from ..models.entities.session.session_pair_code import SessionPairCode
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class SessionClient:
|
|
8
|
+
def __init__(self, http: WSApiHttp) -> None:
|
|
9
|
+
self._http = http
|
|
10
|
+
|
|
11
|
+
# Standard methods
|
|
12
|
+
def get_login_qr_image(self) -> bytes:
|
|
13
|
+
# C# returned byte[] via JSON helper, but Python will treat it as bytes if endpoint returns bytes.
|
|
14
|
+
# If API actually returns base64 JSON, we can add decoding later.
|
|
15
|
+
return self._http.send_bytes("GET", "/session/login/qr/image")
|
|
16
|
+
|
|
17
|
+
def get_login_qr_code(self) -> str:
|
|
18
|
+
return self._http.send_json("GET", "/session/login/qr/code", model=str)
|
|
19
|
+
|
|
20
|
+
def get_login_pair_code(self, phone_number: str) -> SessionPairCode:
|
|
21
|
+
return self._http.send_json("GET", f"/session/login/code/{phone_number}", model=SessionPairCode)
|
|
22
|
+
|
|
23
|
+
def logout(self) -> None:
|
|
24
|
+
self._http.send_json("POST", "/session/logout", model=dict)
|
|
25
|
+
|
|
26
|
+
def get_session_status(self) -> SessionStatus:
|
|
27
|
+
return self._http.send_json("GET", "/session/status", model=SessionStatus)
|
|
28
|
+
|
|
29
|
+
# Try methods
|
|
30
|
+
def try_get_login_qr_image(self) -> ApiResponse[bytes]:
|
|
31
|
+
return self._http.try_send_bytes("GET", "/session/login/qr/image")
|
|
32
|
+
|
|
33
|
+
def try_get_login_qr_code(self) -> ApiResponse[str]:
|
|
34
|
+
return self._http.try_send_json("GET", "/session/login/qr/code", model=str)
|
|
35
|
+
|
|
36
|
+
def try_get_login_pair_code(self, phone_number: str) -> ApiResponse[SessionPairCode]:
|
|
37
|
+
return self._http.try_send_json("GET", f"/session/login/code/{phone_number}", model=SessionPairCode)
|
|
38
|
+
|
|
39
|
+
def try_logout(self) -> ApiResponse[object]:
|
|
40
|
+
return self._http.try_send_json("POST", "/session/logout", model=dict)
|
|
41
|
+
|
|
42
|
+
def try_get_session_status(self) -> ApiResponse[SessionStatus]:
|
|
43
|
+
return self._http.try_send_json("GET", "/session/status", model=SessionStatus)
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
from typing import Optional
|
|
3
|
+
|
|
4
|
+
from ..http import WSApiHttp
|
|
5
|
+
from ..models.entities.users.user_info import UserInfo
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class UsersClient:
|
|
9
|
+
def __init__(self, http: WSApiHttp) -> None:
|
|
10
|
+
self._http = http
|
|
11
|
+
|
|
12
|
+
def get_by_id(self, user_id: str) -> Optional[UserInfo]:
|
|
13
|
+
return self._http.send_json("GET", f"/users/{user_id}", model=UserInfo)
|
|
14
|
+
|
|
15
|
+
def try_get_by_id(self, user_id: str):
|
|
16
|
+
return self._http.try_send_json("GET", f"/users/{user_id}", model=UserInfo)
|
wsapi_client/client.py
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
from typing import Optional
|
|
3
|
+
|
|
4
|
+
from .http import WSApiHttp
|
|
5
|
+
from .api.instance import InstanceClient
|
|
6
|
+
from .api.messages import MessagesClient
|
|
7
|
+
from .api.media import MediaClient
|
|
8
|
+
from .api.session import SessionClient
|
|
9
|
+
from .api.contacts import ContactsClient
|
|
10
|
+
from .api.groups import GroupsClient
|
|
11
|
+
from .api.chats import ChatsClient
|
|
12
|
+
from .api.users import UsersClient
|
|
13
|
+
from .api.calls import CallsClient
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class WSApiClient:
|
|
17
|
+
def __init__(
|
|
18
|
+
self,
|
|
19
|
+
api_key: str,
|
|
20
|
+
instance_id: str,
|
|
21
|
+
*,
|
|
22
|
+
base_url: str = "https://api.wsapi.chat",
|
|
23
|
+
timeout: float = 30.0,
|
|
24
|
+
) -> None:
|
|
25
|
+
self._http = WSApiHttp(api_key=api_key, instance_id=instance_id, base_url=base_url, timeout=timeout)
|
|
26
|
+
|
|
27
|
+
# Surface parity with .NET: expose sub-clients
|
|
28
|
+
self.instance = InstanceClient(self._http)
|
|
29
|
+
self.messages = MessagesClient(self._http)
|
|
30
|
+
self.media = MediaClient(self._http)
|
|
31
|
+
self.session = SessionClient(self._http)
|
|
32
|
+
self.contacts = ContactsClient(self._http)
|
|
33
|
+
self.groups = GroupsClient(self._http)
|
|
34
|
+
self.chats = ChatsClient(self._http)
|
|
35
|
+
self.users = UsersClient(self._http)
|
|
36
|
+
self.calls = CallsClient(self._http)
|
|
37
|
+
|
|
38
|
+
def close(self) -> None:
|
|
39
|
+
self._http.close()
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
from datetime import datetime
|
|
3
|
+
from typing import Any, Dict, Type
|
|
4
|
+
|
|
5
|
+
from pydantic import TypeAdapter
|
|
6
|
+
|
|
7
|
+
from ..models.events.base_event import BaseEvent
|
|
8
|
+
from ..models.events.messages.message_star_event import MessageStarEvent
|
|
9
|
+
from ..models.events.messages.message_read_event import MessageReadEvent
|
|
10
|
+
from ..models.events.messages.message_event import MessageEvent
|
|
11
|
+
from ..models.events.messages.message_delete_event import MessageDeleteEvent
|
|
12
|
+
from ..models.events.messages.message_history_sync_event import MessageHistorySyncEvent
|
|
13
|
+
from ..models.events.contacts.contact_event import ContactEvent
|
|
14
|
+
from ..models.events.users.user_push_name_event import UserPushNameEvent
|
|
15
|
+
from ..models.events.users.user_picture_event import UserPictureEvent
|
|
16
|
+
from ..models.events.users.user_presence_event import UserPresenceEvent
|
|
17
|
+
from ..models.events.users.user_status_event import UserStatusEvent
|
|
18
|
+
from ..models.events.calls.call_offer_event import CallOfferEvent
|
|
19
|
+
from ..models.events.calls.call_accept_event import CallAcceptEvent
|
|
20
|
+
from ..models.events.calls.call_terminate_event import CallTerminateEvent
|
|
21
|
+
from ..models.events.chats.chat_presence_event import ChatPresenceEvent
|
|
22
|
+
from ..models.events.chats.chat_setting_event import ChatSettingEvent
|
|
23
|
+
from ..models.events.session.session_logged_in_event import SessionLoggedInEvent
|
|
24
|
+
from ..models.events.session.session_logged_out_event import SessionLoggedOutEvent
|
|
25
|
+
from ..models.events.session.session_logged_error_event import SessionLoggedErrorEvent
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
_EVENT_TYPES: Dict[str, Type[Any]] = {
|
|
29
|
+
"logged_in": SessionLoggedInEvent,
|
|
30
|
+
"logged_out": SessionLoggedOutEvent,
|
|
31
|
+
"logged_error": SessionLoggedErrorEvent,
|
|
32
|
+
"chat_presence": ChatPresenceEvent,
|
|
33
|
+
"chat_setting": ChatSettingEvent,
|
|
34
|
+
"message": MessageEvent,
|
|
35
|
+
"message_delete": MessageDeleteEvent,
|
|
36
|
+
"message_history_sync": MessageHistorySyncEvent,
|
|
37
|
+
"message_read": MessageReadEvent,
|
|
38
|
+
"message_star": MessageStarEvent,
|
|
39
|
+
"contact": ContactEvent,
|
|
40
|
+
"user_push_name": UserPushNameEvent,
|
|
41
|
+
"user_picture": UserPictureEvent,
|
|
42
|
+
"user_presence": UserPresenceEvent,
|
|
43
|
+
"user_status": UserStatusEvent,
|
|
44
|
+
"call_offer": CallOfferEvent,
|
|
45
|
+
"call_accept": CallAcceptEvent,
|
|
46
|
+
"call_terminate": CallTerminateEvent,
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def parse_event(raw_json: str) -> BaseEvent:
|
|
51
|
+
import json
|
|
52
|
+
|
|
53
|
+
if not raw_json:
|
|
54
|
+
raise ValueError("JSON cannot be empty")
|
|
55
|
+
obj = json.loads(raw_json)
|
|
56
|
+
|
|
57
|
+
# Validate base props
|
|
58
|
+
if "receivedAt" not in obj or "instanceId" not in obj or "eventType" not in obj or "eventData" not in obj:
|
|
59
|
+
raise ValueError("Missing required event fields")
|
|
60
|
+
|
|
61
|
+
event_type = obj["eventType"]
|
|
62
|
+
if event_type not in _EVENT_TYPES:
|
|
63
|
+
raise ValueError(f"Unknown event type: {event_type}")
|
|
64
|
+
|
|
65
|
+
model = _EVENT_TYPES[event_type]
|
|
66
|
+
data = obj["eventData"]
|
|
67
|
+
|
|
68
|
+
# Deserialize payload
|
|
69
|
+
payload = TypeAdapter(model).validate_python(data)
|
|
70
|
+
|
|
71
|
+
# Inject base props for convenience if payload doesn’t derive BaseEvent
|
|
72
|
+
base = BaseEvent.model_validate({
|
|
73
|
+
"instanceId": obj["instanceId"],
|
|
74
|
+
"receivedAt": obj["receivedAt"],
|
|
75
|
+
"eventType": event_type,
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
# Attach base info dynamically
|
|
79
|
+
# For now, return the payload; callers can use base separately if needed.
|
|
80
|
+
# Future: create a wrapper type with .base and .data
|
|
81
|
+
payload._base = base # type: ignore[attr-defined]
|
|
82
|
+
return payload # type: ignore[return-value]
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
from typing import Optional
|
|
3
|
+
|
|
4
|
+
from .models.problem_details import ProblemDetails
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class ApiException(Exception):
|
|
8
|
+
def __init__(self, problem: ProblemDetails):
|
|
9
|
+
super().__init__(problem.detail or problem.title or "API error")
|
|
10
|
+
self.problem: ProblemDetails = problem
|
|
11
|
+
|
|
12
|
+
def __repr__(self) -> str:
|
|
13
|
+
return f"ApiException(status={self.problem.status}, title={self.problem.title!r})"
|
wsapi_client/http.py
ADDED
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
from dataclasses import dataclass
|
|
3
|
+
from typing import Any, Generic, Optional, Type, TypeVar, cast
|
|
4
|
+
|
|
5
|
+
import httpx
|
|
6
|
+
from pydantic import BaseModel, ValidationError, TypeAdapter
|
|
7
|
+
|
|
8
|
+
from .exceptions import ApiException
|
|
9
|
+
from .models.problem_details import ProblemDetails
|
|
10
|
+
|
|
11
|
+
T = TypeVar("T")
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@dataclass
|
|
15
|
+
class ApiResponse(Generic[T]):
|
|
16
|
+
result: Optional[T] = None
|
|
17
|
+
error: Optional[ProblemDetails] = None
|
|
18
|
+
|
|
19
|
+
@property
|
|
20
|
+
def is_success(self) -> bool:
|
|
21
|
+
return self.error is None
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class WSApiHttp:
|
|
25
|
+
def __init__(
|
|
26
|
+
self,
|
|
27
|
+
api_key: str,
|
|
28
|
+
instance_id: str,
|
|
29
|
+
base_url: str = "https://api.wsapi.chat",
|
|
30
|
+
timeout: float = 30.0,
|
|
31
|
+
client: Optional[httpx.Client] = None,
|
|
32
|
+
) -> None:
|
|
33
|
+
headers = {
|
|
34
|
+
"X-Api-Key": api_key,
|
|
35
|
+
"X-Instance-Id": instance_id,
|
|
36
|
+
}
|
|
37
|
+
self._client = client or httpx.Client(base_url=base_url, headers=headers, timeout=timeout)
|
|
38
|
+
|
|
39
|
+
def close(self) -> None:
|
|
40
|
+
self._client.close()
|
|
41
|
+
|
|
42
|
+
# --- core request helpers ---
|
|
43
|
+
def _handle_error(self, resp: httpx.Response) -> None:
|
|
44
|
+
# Try to parse ProblemDetails
|
|
45
|
+
problem: Optional[ProblemDetails] = None
|
|
46
|
+
try:
|
|
47
|
+
data = resp.json()
|
|
48
|
+
problem = ProblemDetails.model_validate(data)
|
|
49
|
+
except Exception:
|
|
50
|
+
# non-JSON body
|
|
51
|
+
problem = ProblemDetails(status=resp.status_code, detail=resp.text)
|
|
52
|
+
raise ApiException(problem)
|
|
53
|
+
|
|
54
|
+
def _handle_exception_to_api_response(self, exc: Exception) -> ProblemDetails:
|
|
55
|
+
# Map timeouts to 408, others to 500-like behavior
|
|
56
|
+
if isinstance(exc, httpx.TimeoutException):
|
|
57
|
+
return ProblemDetails(status=408, title="Request Timeout", detail="The request timed out")
|
|
58
|
+
if isinstance(exc, httpx.TransportError):
|
|
59
|
+
return ProblemDetails(status=500, title="Request Failed", detail=str(exc))
|
|
60
|
+
return ProblemDetails(status=500, title="Request Failed", detail=str(exc))
|
|
61
|
+
|
|
62
|
+
def _parse_json(self, data: Any, model: Type[T] | Any) -> T:
|
|
63
|
+
if isinstance(model, type) and issubclass(model, BaseModel):
|
|
64
|
+
return cast(T, model.model_validate(data))
|
|
65
|
+
adapter = TypeAdapter(model) # type: ignore[arg-type]
|
|
66
|
+
return adapter.validate_python(data) # type: ignore[return-value]
|
|
67
|
+
|
|
68
|
+
def _has_body(self, resp: httpx.Response) -> bool:
|
|
69
|
+
if resp.status_code == 204:
|
|
70
|
+
return False
|
|
71
|
+
# Some servers return 200 with empty body; if content attr is missing (mock), assume body exists
|
|
72
|
+
if hasattr(resp, "content"):
|
|
73
|
+
return resp.content is not None and len(resp.content) > 0
|
|
74
|
+
return True
|
|
75
|
+
|
|
76
|
+
# JSON response
|
|
77
|
+
def send_json(self, method: str, url: str, *, model: Type[T] | Any | None, json: Any | None = None) -> Optional[T]:
|
|
78
|
+
resp = self._client.request(method, url, json=json)
|
|
79
|
+
if 200 <= resp.status_code < 300:
|
|
80
|
+
if model is None or not self._has_body(resp):
|
|
81
|
+
return None
|
|
82
|
+
data = resp.json()
|
|
83
|
+
return self._parse_json(data, model)
|
|
84
|
+
self._handle_error(resp)
|
|
85
|
+
raise AssertionError("Unreachable")
|
|
86
|
+
|
|
87
|
+
def try_send_json(self, method: str, url: str, *, model: Type[T] | Any | None, json: Any | None = None) -> ApiResponse[Optional[T]]:
|
|
88
|
+
try:
|
|
89
|
+
resp = self._client.request(method, url, json=json)
|
|
90
|
+
if 200 <= resp.status_code < 300:
|
|
91
|
+
if model is None or not self._has_body(resp):
|
|
92
|
+
return ApiResponse(result=None)
|
|
93
|
+
data = resp.json()
|
|
94
|
+
return ApiResponse(result=self._parse_json(data, model))
|
|
95
|
+
# !2xx -> map to ProblemDetails
|
|
96
|
+
try:
|
|
97
|
+
problem = ProblemDetails.model_validate(resp.json())
|
|
98
|
+
except Exception:
|
|
99
|
+
problem = ProblemDetails(status=resp.status_code, detail=resp.text)
|
|
100
|
+
return ApiResponse(error=problem)
|
|
101
|
+
except Exception as exc:
|
|
102
|
+
return ApiResponse(error=self._handle_exception_to_api_response(exc))
|
|
103
|
+
|
|
104
|
+
# Bytes response
|
|
105
|
+
def send_bytes(self, method: str, url: str, *, json: Any | None = None) -> bytes:
|
|
106
|
+
resp = self._client.request(method, url, json=json)
|
|
107
|
+
if 200 <= resp.status_code < 300:
|
|
108
|
+
return resp.content
|
|
109
|
+
self._handle_error(resp)
|
|
110
|
+
raise AssertionError("Unreachable")
|
|
111
|
+
|
|
112
|
+
def try_send_bytes(self, method: str, url: str, *, json: Any | None = None) -> ApiResponse[bytes]:
|
|
113
|
+
try:
|
|
114
|
+
resp = self._client.request(method, url, json=json)
|
|
115
|
+
if 200 <= resp.status_code < 300:
|
|
116
|
+
return ApiResponse(result=resp.content)
|
|
117
|
+
try:
|
|
118
|
+
problem = ProblemDetails.model_validate(resp.json())
|
|
119
|
+
except Exception:
|
|
120
|
+
problem = ProblemDetails(status=resp.status_code, detail=resp.text)
|
|
121
|
+
return ApiResponse(error=problem)
|
|
122
|
+
except Exception as exc:
|
|
123
|
+
return ApiResponse(error=self._handle_exception_to_api_response(exc))
|