Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion pybotx/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,10 @@
FinalRecipientsListEmptyError,
StealthModeDisabledError,
)
from pybotx.client.exceptions.users import UserNotFoundError
from pybotx.client.exceptions.users import (
UserNotFoundError,
UserProfileUpdateUnavailableError,
)
from pybotx.client.smartapps_api.exceptions import SyncSmartAppEventHandlerNotFoundError
from pybotx.client.smartapps_api.smartapp_manifest import (
SmartappManifest,
Expand Down Expand Up @@ -301,6 +304,7 @@
"UserFromSearch",
"UserKinds",
"UserNotFoundError",
"UserProfileUpdateUnavailableError",
"UserSender",
"Video",
"Voice",
Expand Down
80 changes: 78 additions & 2 deletions pybotx/bot/bot.py
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,14 @@
BotXAPIUsersAsCSVRequestPayload,
UsersAsCSVMethod,
)
from pybotx.client.voex_api.get_call import (
BotXAPIGetCallRequestPayload,
GetCallMethod,
)
from pybotx.client.voex_api.get_conference import (
BotXAPIGetConferenceRequestPayload,
GetConferenceMethod,
)
from pybotx.constants import BOTX_DEFAULT_TIMEOUT, STICKER_PACKS_PER_PAGE
from pybotx.converters import optional_sequence_to_list
from pybotx.image_validators import (
Expand All @@ -236,12 +244,14 @@
from pybotx.models.attachments import IncomingFileAttachment, OutgoingAttachment
from pybotx.models.bot_account import BotAccountWithSecret
from pybotx.models.bot_catalog import BotsListItem
from pybotx.models.call import Call
from pybotx.models.chats import ChatInfo, ChatLink, ChatListItem
from pybotx.models.commands import (
BotAPISystemEvent,
BotAPIIncomingMessage,
BotCommand,
)
from pybotx.models.conference import Conference
from pybotx.models.enums import BotAPICommandTypes, ChatLinkTypes, ChatTypes
from pybotx.models.message.edit_message import EditMessage
from pybotx.models.message.markup import BubbleMarkup, KeyboardMarkup
Expand Down Expand Up @@ -1395,6 +1405,56 @@ async def pin_message(

await method.execute(payload)

async def get_call(
self,
*,
bot_id: UUID,
call_id: UUID,
) -> Call:
"""Get call.

:param bot_id: Bot which should perform the request.
:param call_id: Call id.

:return: Call.
"""
method = GetCallMethod(
bot_id,
self._httpx_client,
self._bot_accounts_storage,
)
payload = BotXAPIGetCallRequestPayload.from_domain(
call_id=call_id,
)
botx_call = await method.execute(payload)

return botx_call.to_domain()

async def get_conference(
self,
*,
bot_id: UUID,
call_id: UUID,
) -> Conference:
"""Get Conference.

:param bot_id: Bot which should perform the request.
:param call_id: Call id.

:return: Conference.
"""
method = GetConferenceMethod(
bot_id,
self._httpx_client,
self._bot_accounts_storage,
)
payload = BotXAPIGetConferenceRequestPayload.from_domain(
call_id=call_id,
)
botx_conference = await method.execute(payload)

return botx_conference.to_domain()

async def unpin_message(
self,
*,
Expand All @@ -1421,11 +1481,15 @@ async def search_user_by_emails(
*,
bot_id: UUID,
emails: list[str],
trusts_search: bool = False,
partial_response: bool = False,
) -> list[UserFromSearch]:
"""Search user by emails for search.

:param bot_id: Bot which should perform the request.
:param emails: User emails.
:param trusts_search: Search users on trusted servers.
:param partial_response: Return local results if trusted server lookup fails.

:return: Search result with user information.
"""
Expand All @@ -1435,7 +1499,11 @@ async def search_user_by_emails(
self._httpx_client,
self._bot_accounts_storage,
)
payload = BotXAPISearchUserByEmailsRequestPayload.from_domain(emails=emails)
payload = BotXAPISearchUserByEmailsRequestPayload.from_domain(
emails=emails,
trusts_search=trusts_search,
partial_response=partial_response,
)

botx_api_users_from_search = await method.execute(payload)

Expand All @@ -1447,6 +1515,8 @@ async def search_user_by_email_post(
*,
bot_id: UUID,
email: str,
trusts_search: bool = False,
partial_response: bool = False,
) -> UserFromSearch:
"""Search user by email for search.

Expand All @@ -1455,6 +1525,8 @@ async def search_user_by_email_post(

:param bot_id: Bot which should perform the request.
:param email: User email.
:param trusts_search: Search users on trusted servers.
:param partial_response: Return local results if trusted server lookup fails.

:return: User information.
"""
Expand All @@ -1464,7 +1536,11 @@ async def search_user_by_email_post(
self._httpx_client,
self._bot_accounts_storage,
)
payload = BotXAPISearchUserByEmailRequestPayload.from_domain(email=email)
payload = BotXAPISearchUserByEmailRequestPayload.from_domain(
email=email,
trusts_search=trusts_search,
partial_response=partial_response,
)

botx_api_user_from_search = await method.execute(payload)

Expand Down
4 changes: 4 additions & 0 deletions pybotx/client/exceptions/users.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,7 @@ class InvalidProfileDataError(BaseClientError):

class NoUserKindSelectedError(BaseClientError):
"""No user kind selected."""


class UserProfileUpdateUnavailableError(BaseClientError):
"""User profile update service is unavailable."""
25 changes: 22 additions & 3 deletions pybotx/client/users_api/search_user_by_email.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,27 @@
BotXAPISearchUserResponsePayload,
)
from pybotx.logger import logger
from pybotx.missing import Missing, Undefined
from pybotx.models.api_base import UnverifiedPayloadBaseModel


class BotXAPISearchUserByEmailRequestPayload(UnverifiedPayloadBaseModel):
email: str
trusts_search: Missing[bool] = Undefined
partial_response: Missing[bool] = Undefined

@classmethod
def from_domain(cls, email: str) -> "BotXAPISearchUserByEmailRequestPayload":
return cls(email=email)
def from_domain(
cls,
email: str,
trusts_search: bool = False,
partial_response: bool = False,
) -> "BotXAPISearchUserByEmailRequestPayload":
return cls(
email=email,
trusts_search=trusts_search or Undefined,
partial_response=partial_response or Undefined,
)


class SearchUserByEmailMethod(AuthorizedBotXMethod):
Expand Down Expand Up @@ -67,7 +79,14 @@ async def execute(
path = "/api/v3/botx/users/by_email"

email = payload.email
request_json = {"emails": [email]}
request_json = {
"emails": [email],
"trusts_search": payload.trusts_search,
"partial_response": payload.partial_response,
}
request_json = {
key: value for key, value in request_json.items() if value is not Undefined
}

response = await self._botx_method_call(
"POST",
Expand Down
11 changes: 10 additions & 1 deletion pybotx/client/users_api/search_user_by_emails.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,27 @@
from pybotx.client.users_api.user_from_search import (
BotXAPISearchUserByEmailsResponsePayload,
)
from pybotx.missing import Missing, Undefined
from pybotx.models.api_base import UnverifiedPayloadBaseModel


class BotXAPISearchUserByEmailsRequestPayload(UnverifiedPayloadBaseModel):
emails: list[str]
trusts_search: Missing[bool] = Undefined
partial_response: Missing[bool] = Undefined

@classmethod
def from_domain(
cls,
emails: list[str],
trusts_search: bool = False,
partial_response: bool = False,
) -> "BotXAPISearchUserByEmailsRequestPayload":
return cls(emails=emails)
return cls(
emails=emails,
trusts_search=trusts_search or Undefined,
partial_response=partial_response or Undefined,
)


class SearchUserByEmailsMethod(AuthorizedBotXMethod):
Expand Down
7 changes: 6 additions & 1 deletion pybotx/client/users_api/update_user_profile.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,11 @@

from pybotx.client.authorized_botx_method import AuthorizedBotXMethod
from pybotx.client.botx_method import response_exception_thrower
from pybotx.client.exceptions.users import InvalidProfileDataError, UserNotFoundError
from pybotx.client.exceptions.users import (
InvalidProfileDataError,
UserNotFoundError,
UserProfileUpdateUnavailableError,
)
from pybotx.missing import Missing, Undefined
from pybotx.models.api_base import UnverifiedPayloadBaseModel, VerifiedPayloadBaseModel
from pybotx.models.attachments import (
Expand Down Expand Up @@ -67,6 +71,7 @@ class UpdateUsersProfileMethod(AuthorizedBotXMethod):
**AuthorizedBotXMethod.status_handlers,
400: response_exception_thrower(InvalidProfileDataError),
404: response_exception_thrower(UserNotFoundError),
503: response_exception_thrower(UserProfileUpdateUnavailableError),
}

async def execute(
Expand Down
18 changes: 9 additions & 9 deletions pybotx/client/users_api/user_from_csv.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,17 +23,17 @@ class BotXAPIUserFromCSVResult(VerifiedPayloadBaseModel):
company: str | None = Field(alias="Company")
department: str | None = Field(alias="Department")
position: str | None = Field(alias="Position")
avatar: str | None = Field(alias="Avatar")
avatar_preview: str | None = Field(alias="Avatar preview")
office: str | None = Field(alias="Office")
avatar: str | None = Field(default=None, alias="Avatar")
avatar_preview: str | None = Field(default=None, alias="Avatar preview")
office: str | None = Field(default=None, alias="Office")
manager: str | None = Field(alias="Manager")
manager_huid: UUID | None = Field(alias="Manager HUID")
description: str | None = Field(alias="Description")
phone: str | None = Field(alias="Phone")
other_phone: str | None = Field(alias="Other phone")
ip_phone: str | None = Field(alias="IP phone")
other_ip_phone: str | None = Field(alias="Other IP phone")
personnel_number: str | None = Field(alias="Personnel number")
description: str | None = Field(default=None, alias="Description")
phone: str | None = Field(default=None, alias="Phone")
other_phone: str | None = Field(default=None, alias="Other phone")
ip_phone: str | None = Field(default=None, alias="IP phone")
other_ip_phone: str | None = Field(default=None, alias="Other IP phone")
personnel_number: str | None = Field(default=None, alias="Personnel number")

@field_validator(
"email",
Expand Down
10 changes: 9 additions & 1 deletion pybotx/client/users_api/user_from_search.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from pybotx.models.api_base import VerifiedPayloadBaseModel
from pybotx.models.enums import APIUserKinds, convert_user_kind_to_domain
from pybotx.models.users import UserFromSearch
from pydantic import Field
from pydantic import Field, field_validator


class BotXAPISearchUserResult(VerifiedPayloadBaseModel):
Expand All @@ -32,6 +32,14 @@ class BotXAPISearchUserResult(VerifiedPayloadBaseModel):
created_at: datetime | None = None
updated_at: datetime | None = None

@field_validator("ip_phone", "other_ip_phone", "other_phone", mode="before")
@classmethod
def convert_phone_to_string(cls, value: str | int | None) -> str | None:
if value is None:
return None

return str(value)


class BotXAPISearchUserResponsePayload(VerifiedPayloadBaseModel):
status: Literal["ok"]
Expand Down
Empty file.
9 changes: 9 additions & 0 deletions pybotx/client/voex_api/exceptions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
from pybotx.client.exceptions.base import BaseClientError


class ConferenceNotFoundError(BaseClientError):
"""Conference with specified call_id not found."""


class CallNotFoundError(BaseClientError):
"""Call with specified call_id not found."""
59 changes: 59 additions & 0 deletions pybotx/client/voex_api/get_call.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
from typing import Literal
from uuid import UUID

from pybotx.client.authorized_botx_method import AuthorizedBotXMethod
from pybotx.client.botx_method import response_exception_thrower
from pybotx.client.voex_api.exceptions import CallNotFoundError
from pybotx.models.api_base import UnverifiedPayloadBaseModel, VerifiedPayloadBaseModel
from pybotx.models.call import Call


class BotXAPIGetCallRequestPayload(UnverifiedPayloadBaseModel):
call_id: UUID

@classmethod
def from_domain(
cls,
call_id: UUID,
) -> "BotXAPIGetCallRequestPayload":
return cls(call_id=call_id)


class BotXAPIGetCallResult(VerifiedPayloadBaseModel):
id: UUID
members: list[UUID]


class BotXAPIGetCallResponsePayload(VerifiedPayloadBaseModel):
status: Literal["ok"]
result: BotXAPIGetCallResult

def to_domain(self) -> Call:
return Call(
id=self.result.id,
members=self.result.members,
)


class GetCallMethod(AuthorizedBotXMethod):
status_handlers = {
**AuthorizedBotXMethod.status_handlers,
404: response_exception_thrower(CallNotFoundError),
}

async def execute(
self,
payload: BotXAPIGetCallRequestPayload,
) -> BotXAPIGetCallResponsePayload:
jsonable_dict = payload.jsonable_dict()
path = f"/api/v3/botx/voex/calls/{jsonable_dict['call_id']}"

response = await self._botx_method_call(
"GET",
self._build_url(path),
)

return self._verify_and_extract_api_model(
BotXAPIGetCallResponsePayload,
response,
)
Loading
Loading