From 2421b1cf5b1ed3122ac0fba36437c9a8cbce94c9 Mon Sep 17 00:00:00 2001 From: Jackson Vieira Date: Wed, 3 Dec 2025 13:54:30 -0300 Subject: [PATCH] style: format codebase with ruff --- evolutionapi/client.py | 82 ++++--- evolutionapi/exceptions.py | 5 + evolutionapi/models/call.py | 14 +- evolutionapi/models/chat.py | 69 ++---- evolutionapi/models/group.py | 10 +- evolutionapi/models/instance.py | 47 ++-- evolutionapi/models/label.py | 17 +- evolutionapi/models/message.py | 96 ++++---- evolutionapi/models/presence.py | 4 +- evolutionapi/models/profile.py | 8 +- evolutionapi/models/websocket.py | 6 +- evolutionapi/services/call.py | 7 +- evolutionapi/services/chat.py | 8 +- evolutionapi/services/group.py | 91 ++++---- evolutionapi/services/instance.py | 4 +- evolutionapi/services/instance_operations.py | 15 +- evolutionapi/services/label.py | 12 +- evolutionapi/services/message.py | 218 ++++++++++--------- evolutionapi/services/profile.py | 35 ++- evolutionapi/services/websocket.py | 96 ++++---- test_evolution.py | 55 ++--- 21 files changed, 458 insertions(+), 441 deletions(-) diff --git a/evolutionapi/client.py b/evolutionapi/client.py index 237f925..8a0150e 100644 --- a/evolutionapi/client.py +++ b/evolutionapi/client.py @@ -11,6 +11,7 @@ from .services.profile import ProfileService from .services.group import GroupService from .services.websocket import WebSocketService, WebSocketManager + class EvolutionClient: """ Cliente para interagir com a API Evolution. @@ -21,7 +22,7 @@ class EvolutionClient: """ def __init__(self, base_url: str, api_token: str): - self.base_url = base_url.rstrip('/') + self.base_url = base_url.rstrip("/") self.api_token = api_token self.instances = InstanceService(self) self.instance_operations = InstanceOperationsService(self) @@ -32,33 +33,30 @@ class EvolutionClient: self.profile = ProfileService(self) self.group = GroupService(self) self.websocket = WebSocketService(self) - + def _get_headers(self, instance_token: str = None): - return { - 'apikey': instance_token or self.api_token, - 'Content-Type': 'application/json' - } + return {"apikey": instance_token or self.api_token, "Content-Type": "application/json"} def _get_full_url(self, endpoint): - return f'{self.base_url}/{endpoint}' + return f"{self.base_url}/{endpoint}" def _handle_response(self, response): if response.status_code == 401: - raise EvolutionAuthenticationError('Falha na autenticação.') + raise EvolutionAuthenticationError("Falha na autenticação.") elif response.status_code == 404: - raise EvolutionNotFoundError('Recurso não encontrado.') + raise EvolutionNotFoundError("Recurso não encontrado.") elif response.ok: try: return response.json() except ValueError: return response.content else: - error_detail = '' + error_detail = "" try: - error_detail = f' - {response.json()}' + error_detail = f" - {response.json()}" except: - error_detail = f' - {response.text}' - raise EvolutionAPIError(f'Erro na requisição: {response.status_code}{error_detail}') + error_detail = f" - {response.text}" + raise EvolutionAPIError(f"Erro na requisição: {response.status_code}{error_detail}") def get(self, endpoint: str, instance_token: str = None): """Faz uma requisição GET.""" @@ -66,42 +64,40 @@ class EvolutionClient: response = requests.get(url, headers=self._get_headers(instance_token)) return self._handle_response(response) - def post(self, endpoint: str, data: dict = None, instance_token: str = None, files: dict = None): - url = f'{self.base_url}/{endpoint}' + def post( + self, endpoint: str, data: dict = None, instance_token: str = None, files: dict = None + ): + url = f"{self.base_url}/{endpoint}" headers = self._get_headers(instance_token) - + if files: # Remove o Content-Type do header quando enviando arquivos - if 'Content-Type' in headers: - del headers['Content-Type'] - + if "Content-Type" in headers: + del headers["Content-Type"] + # Prepara os campos do multipart fields = {} - + # Adiciona os campos do data for key, value in data.items(): - fields[key] = str(value) if not isinstance(value, (int, float)) else (None, str(value), 'text/plain') - + fields[key] = ( + str(value) + if not isinstance(value, (int, float)) + else (None, str(value), "text/plain") + ) + # Adiciona o arquivo - file_tuple = files['file'] - fields['file'] = (file_tuple[0], file_tuple[1], file_tuple[2]) - + file_tuple = files["file"] + fields["file"] = (file_tuple[0], file_tuple[1], file_tuple[2]) + # Cria o multipart encoder multipart = MultipartEncoder(fields=fields) - headers['Content-Type'] = multipart.content_type - - response = requests.post( - url, - headers=headers, - data=multipart - ) + headers["Content-Type"] = multipart.content_type + + response = requests.post(url, headers=headers, data=multipart) else: - response = requests.post( - url, - headers=headers, - json=data - ) - + response = requests.post(url, headers=headers, json=data) + return response.json() def put(self, endpoint, data=None): @@ -116,16 +112,18 @@ class EvolutionClient: response = requests.delete(url, headers=self._get_headers(instance_token)) return self._handle_response(response) - def create_websocket(self, instance_id: str, api_token: str, max_retries: int = 5, retry_delay: float = 1.0) -> WebSocketManager: + def create_websocket( + self, instance_id: str, api_token: str, max_retries: int = 5, retry_delay: float = 1.0 + ) -> WebSocketManager: """ Create a WebSocket manager for the specified instance. - + Args: instance_id (str): The instance ID api_token (str): The API token max_retries (int): Maximum number of reconnection attempts retry_delay (float): Initial delay between attempts in seconds - + Returns: WebSocketManager: The WebSocket manager instance """ @@ -134,5 +132,5 @@ class EvolutionClient: instance_id=instance_id, api_token=api_token, max_retries=max_retries, - retry_delay=retry_delay + retry_delay=retry_delay, ) diff --git a/evolutionapi/exceptions.py b/evolutionapi/exceptions.py index ee7e177..1758d1a 100644 --- a/evolutionapi/exceptions.py +++ b/evolutionapi/exceptions.py @@ -1,11 +1,16 @@ class EvolutionAPIError(Exception): """Erro genérico da API Evolution.""" + pass + class EvolutionAuthenticationError(EvolutionAPIError): """Erro de autenticação com a API Evolution.""" + pass + class EvolutionNotFoundError(EvolutionAPIError): """Recurso não encontrado na API Evolution.""" + pass diff --git a/evolutionapi/models/call.py b/evolutionapi/models/call.py index 991463b..8c996ca 100644 --- a/evolutionapi/models/call.py +++ b/evolutionapi/models/call.py @@ -2,15 +2,7 @@ class BaseCall: def __init__(self, **kwargs): self.__dict__.update({k: v for k, v in kwargs.items() if v is not None}) + class FakeCall(BaseCall): - def __init__( - self, - number: str, - isVideo: bool, - callDuration: int - ): - super().__init__( - number=number, - isVideo=isVideo, - callDuration=callDuration - ) \ No newline at end of file + def __init__(self, number: str, isVideo: bool, callDuration: int): + super().__init__(number=number, isVideo=isVideo, callDuration=callDuration) diff --git a/evolutionapi/models/chat.py b/evolutionapi/models/chat.py index 4e985b3..fa665ff 100644 --- a/evolutionapi/models/chat.py +++ b/evolutionapi/models/chat.py @@ -1,93 +1,64 @@ from typing import List, Optional, Dict, Any + class BaseChat: def __init__(self, **kwargs): self.__dict__.update({k: v for k, v in kwargs.items() if v is not None}) + class CheckIsWhatsappNumber(BaseChat): - def __init__( - self, - numbers: List[str] - ): - super().__init__( - numbers=numbers - ) + def __init__(self, numbers: List[str]): + super().__init__(numbers=numbers) + class MessageKey: - def __init__( - self, - remote_jid: str, - from_me: bool, - id: str, - participant: Optional[str] = None - ): + def __init__(self, remote_jid: str, from_me: bool, id: str, participant: Optional[str] = None): self.remoteJid = remote_jid self.fromMe = from_me self.id = id self.participant = participant + class ReadMessage: - def __init__( - self, - remote_jid: str, - from_me: bool, - id: str - ): + def __init__(self, remote_jid: str, from_me: bool, id: str): self.remoteJid = remote_jid self.fromMe = from_me self.id = id + class ArchiveChat: - def __init__( - self, - last_message: Dict[str, Any], - chat: str, - archive: bool - ): + def __init__(self, last_message: Dict[str, Any], chat: str, archive: bool): self.lastMessage = last_message self.chat = chat self.archive = archive + class UnreadChat: - def __init__( - self, - last_message: Dict[str, Any], - chat: str - ): + def __init__(self, last_message: Dict[str, Any], chat: str): self.lastMessage = last_message self.chat = chat + class ProfilePicture: def __init__(self, number: str): self.number = number + class MediaMessage: - def __init__( - self, - message: Dict[str, Any], - convert_to_mp4: bool = False - ): + def __init__(self, message: Dict[str, Any], convert_to_mp4: bool = False): self.message = message self.convertToMp4 = convert_to_mp4 + class UpdateMessage: - def __init__( - self, - number: str, - key: Dict[str, Any], - text: str - ): + def __init__(self, number: str, key: Dict[str, Any], text: str): self.number = number self.key = key self.text = text + class Presence: - def __init__( - self, - number: str, - delay: int, - presence: str - ): + def __init__(self, number: str, delay: int, presence: str): self.number = number self.delay = delay - self.presence = presence \ No newline at end of file + self.presence = presence diff --git a/evolutionapi/models/group.py b/evolutionapi/models/group.py index 5a21460..d1a4077 100644 --- a/evolutionapi/models/group.py +++ b/evolutionapi/models/group.py @@ -1,39 +1,47 @@ from typing import List, Optional, Literal from dataclasses import dataclass + @dataclass class CreateGroup: subject: str participants: List[str] description: Optional[str] = None + @dataclass class GroupPicture: image: str + @dataclass class GroupSubject: subject: str + @dataclass class GroupDescription: description: str + @dataclass class GroupInvite: groupJid: str description: str numbers: List[str] + @dataclass class UpdateParticipant: action: Literal["add", "remove", "promote", "demote"] participants: List[str] + @dataclass class UpdateSetting: action: Literal["announcement", "not_announcement", "locked", "unlocked"] + @dataclass class ToggleEphemeral: - expiration: int \ No newline at end of file + expiration: int diff --git a/evolutionapi/models/instance.py b/evolutionapi/models/instance.py index 102debc..f91b930 100644 --- a/evolutionapi/models/instance.py +++ b/evolutionapi/models/instance.py @@ -1,27 +1,45 @@ from typing import Optional, List, Dict + class WebhookConfig: - def __init__(self, url: str = None, byEvents: bool = False, base64: bool = True, - headers: Dict = None, events: List[str] = None): + def __init__( + self, + url: str = None, + byEvents: bool = False, + base64: bool = True, + headers: Dict = None, + events: List[str] = None, + ): self.url = url self.byEvents = byEvents self.base64 = base64 self.headers = headers self.events = events + class EventsConfig: def __init__(self, enabled: bool = True, events: List[str] = None): self.enabled = enabled self.events = events + class ChatwootConfig: - def __init__(self, accountId: str = None, token: str = None, url: str = None, - signMsg: bool = True, reopenConversation: bool = True, - conversationPending: bool = False, importContacts: bool = True, - nameInbox: str = "evolution", mergeBrazilContacts: bool = True, - importMessages: bool = True, daysLimitImportMessages: int = 3, - organization: str = "Evolution Bot", - logo: str = "https://evolution-api.com/files/evolution-api-favicon.png"): + def __init__( + self, + accountId: str = None, + token: str = None, + url: str = None, + signMsg: bool = True, + reopenConversation: bool = True, + conversationPending: bool = False, + importContacts: bool = True, + nameInbox: str = "evolution", + mergeBrazilContacts: bool = True, + importMessages: bool = True, + daysLimitImportMessages: int = 3, + organization: str = "Evolution Bot", + logo: str = "https://evolution-api.com/files/evolution-api-favicon.png", + ): self.chatwootAccountId = accountId self.chatwootToken = token self.chatwootUrl = url @@ -36,6 +54,7 @@ class ChatwootConfig: self.chatwootOrganization = organization self.chatwootLogo = logo + class InstanceConfig: def __init__( self, @@ -50,10 +69,10 @@ class InstanceConfig: alwaysOnline: bool = None, readMessages: bool = None, readStatus: bool = None, - syncFullHistory: bool = None + syncFullHistory: bool = None, ): - self.__dict__['instanceName'] = instanceName - + self.__dict__["instanceName"] = instanceName + for key, value in locals().items(): - if key != 'self' and key != 'instanceName' and value is not None: - self.__dict__[key] = value \ No newline at end of file + if key != "self" and key != "instanceName" and value is not None: + self.__dict__[key] = value diff --git a/evolutionapi/models/label.py b/evolutionapi/models/label.py index cc59334..ea6c539 100644 --- a/evolutionapi/models/label.py +++ b/evolutionapi/models/label.py @@ -1,21 +1,14 @@ from typing import Literal + class BaseLabel: def __init__(self, **kwargs): self.__dict__.update({k: v for k, v in kwargs.items() if v is not None}) + class HandleLabel(BaseLabel): - def __init__( - self, - number: str, - label_id: str, - action: Literal["add", "remove"] - ): + def __init__(self, number: str, label_id: str, action: Literal["add", "remove"]): if action not in ["add", "remove"]: raise ValueError("action deve ser 'add' ou 'remove'") - - super().__init__( - number=number, - labelId=label_id, - action=action - ) \ No newline at end of file + + super().__init__(number=number, labelId=label_id, action=action) diff --git a/evolutionapi/models/message.py b/evolutionapi/models/message.py index cd4a82a..599a9cf 100644 --- a/evolutionapi/models/message.py +++ b/evolutionapi/models/message.py @@ -2,17 +2,20 @@ from enum import Enum from typing import List, Optional, Union from dataclasses import dataclass + class MediaType(Enum): IMAGE = "image" VIDEO = "video" DOCUMENT = "document" + class StatusType(Enum): TEXT = "text" IMAGE = "image" VIDEO = "video" AUDIO = "audio" + class FontType(Enum): SERIF = 1 NORICAN_REGULAR = 2 @@ -20,14 +23,17 @@ class FontType(Enum): BEBASNEUE_REGULAR = 4 OSWALD_HEAVY = 5 + class BaseMessage: def __init__(self, **kwargs): self.__dict__.update({k: v for k, v in kwargs.items() if v is not None}) + class QuotedMessage(BaseMessage): def __init__(self, key: dict, message: Optional[dict] = None): super().__init__(key=key, message=message) + class TextMessage(BaseMessage): def __init__( self, @@ -37,7 +43,7 @@ class TextMessage(BaseMessage): quoted: Optional[QuotedMessage] = None, linkPreview: Optional[bool] = None, mentionsEveryOne: Optional[bool] = None, - mentioned: Optional[List[str]] = None + mentioned: Optional[List[str]] = None, ): super().__init__( number=number, @@ -46,9 +52,10 @@ class TextMessage(BaseMessage): quoted=quoted.__dict__ if quoted else None, linkPreview=linkPreview, mentionsEveryOne=mentionsEveryOne, - mentioned=mentioned + mentioned=mentioned, ) + class MediaMessage(BaseMessage): def __init__( self, @@ -61,27 +68,28 @@ class MediaMessage(BaseMessage): delay: Optional[Union[int, float, str]] = None, quoted: Optional[QuotedMessage] = None, mentionsEveryOne: Optional[bool] = None, - mentioned: Optional[List[str]] = None + mentioned: Optional[List[str]] = None, ): data = { - 'number': number, - 'mediatype': mediatype, - 'caption': caption, - 'mimetype': mimetype, - 'fileName': fileName, - 'quoted': quoted.__dict__ if quoted else None, - 'mentionsEveryOne': mentionsEveryOne, - 'mentioned': mentioned + "number": number, + "mediatype": mediatype, + "caption": caption, + "mimetype": mimetype, + "fileName": fileName, + "quoted": quoted.__dict__ if quoted else None, + "mentionsEveryOne": mentionsEveryOne, + "mentioned": mentioned, } - + if delay is not None: - data['delay'] = delay - + data["delay"] = delay + if media and media != {}: - data['media'] = media - + data["media"] = media + super().__init__(**{k: v for k, v in data.items() if v is not None}) + class StatusMessage(BaseMessage): def __init__( self, @@ -91,7 +99,7 @@ class StatusMessage(BaseMessage): backgroundColor: Optional[str] = None, font: Optional[FontType] = None, allContacts: bool = False, - statusJidList: Optional[List[str]] = None + statusJidList: Optional[List[str]] = None, ): super().__init__( type=type.value, @@ -100,9 +108,10 @@ class StatusMessage(BaseMessage): backgroundColor=backgroundColor, font=font.value if font else None, allContacts=allContacts, - statusJidList=statusJidList + statusJidList=statusJidList, ) + class LocationMessage(BaseMessage): def __init__( self, @@ -112,7 +121,7 @@ class LocationMessage(BaseMessage): latitude: float, longitude: float, delay: Optional[int] = None, - quoted: Optional[QuotedMessage] = None + quoted: Optional[QuotedMessage] = None, ): super().__init__( number=number, @@ -121,9 +130,10 @@ class LocationMessage(BaseMessage): latitude=latitude, longitude=longitude, delay=delay, - quoted=quoted.__dict__ if quoted else None + quoted=quoted.__dict__ if quoted else None, ) + class Contact(BaseMessage): def __init__( self, @@ -132,7 +142,7 @@ class Contact(BaseMessage): phoneNumber: str, organization: Optional[str] = None, email: Optional[str] = None, - url: Optional[str] = None + url: Optional[str] = None, ): super().__init__( fullName=fullName, @@ -140,20 +150,20 @@ class Contact(BaseMessage): phoneNumber=phoneNumber, organization=organization, email=email, - url=url + url=url, ) + class ContactMessage(BaseMessage): def __init__(self, number: str, contact: List[Contact]): - super().__init__( - number=number, - contact=[c.__dict__ for c in contact] - ) + super().__init__(number=number, contact=[c.__dict__ for c in contact]) + class ReactionMessage(BaseMessage): def __init__(self, key: dict, reaction: str): super().__init__(key=key, reaction=reaction) + class PollMessage(BaseMessage): def __init__( self, @@ -162,7 +172,7 @@ class PollMessage(BaseMessage): selectableCount: int, values: List[str], delay: Optional[int] = None, - quoted: Optional[QuotedMessage] = None + quoted: Optional[QuotedMessage] = None, ): super().__init__( number=number, @@ -170,23 +180,19 @@ class PollMessage(BaseMessage): selectableCount=selectableCount, values=values, delay=delay, - quoted=quoted.__dict__ if quoted else None + quoted=quoted.__dict__ if quoted else None, ) + class ListRow(BaseMessage): def __init__(self, title: str, description: str, rowId: str): - super().__init__( - title=title, - description=description, - rowId=rowId - ) + super().__init__(title=title, description=description, rowId=rowId) + class ListSection(BaseMessage): def __init__(self, title: str, rows: List[ListRow]): - super().__init__( - title=title, - rows=[r.__dict__ for r in rows] - ) + super().__init__(title=title, rows=[r.__dict__ for r in rows]) + class ListMessage(BaseMessage): def __init__( @@ -198,7 +204,7 @@ class ListMessage(BaseMessage): footerText: str, sections: List[ListSection], delay: Optional[int] = None, - quoted: Optional[QuotedMessage] = None + quoted: Optional[QuotedMessage] = None, ): super().__init__( number=number, @@ -208,9 +214,10 @@ class ListMessage(BaseMessage): footerText=footerText, sections=[s.__dict__ for s in sections], delay=delay, - quoted=quoted.__dict__ if quoted else None + quoted=quoted.__dict__ if quoted else None, ) + class Button(BaseMessage): def __init__( self, @@ -223,7 +230,7 @@ class Button(BaseMessage): currency: Optional[str] = None, name: Optional[str] = None, keyType: Optional[str] = None, - key: Optional[str] = None + key: Optional[str] = None, ): super().__init__( type=type, @@ -235,9 +242,10 @@ class Button(BaseMessage): currency=currency, name=name, keyType=keyType, - key=key + key=key, ) + class ButtonMessage(BaseMessage): def __init__( self, @@ -247,7 +255,7 @@ class ButtonMessage(BaseMessage): footer: str, buttons: List[Button], delay: Optional[int] = None, - quoted: Optional[QuotedMessage] = None + quoted: Optional[QuotedMessage] = None, ): super().__init__( number=number, @@ -256,5 +264,5 @@ class ButtonMessage(BaseMessage): footer=footer, buttons=[b.__dict__ for b in buttons], delay=delay, - quoted=quoted.__dict__ if quoted else None - ) \ No newline at end of file + quoted=quoted.__dict__ if quoted else None, + ) diff --git a/evolutionapi/models/presence.py b/evolutionapi/models/presence.py index d245b57..4c1d295 100644 --- a/evolutionapi/models/presence.py +++ b/evolutionapi/models/presence.py @@ -1,9 +1,11 @@ from enum import Enum + class PresenceStatus(Enum): AVAILABLE = "available" UNAVAILABLE = "unavailable" + class PresenceConfig: def __init__(self, presence: PresenceStatus): - self.presence = presence.value \ No newline at end of file + self.presence = presence.value diff --git a/evolutionapi/models/profile.py b/evolutionapi/models/profile.py index 39441b3..2fd2c73 100644 --- a/evolutionapi/models/profile.py +++ b/evolutionapi/models/profile.py @@ -1,9 +1,11 @@ from typing import Literal + class BaseProfile: def __init__(self, **kwargs): self.__dict__.update({k: v for k, v in kwargs.items() if v is not None}) + class FetchProfile(BaseProfile): def __init__( self, @@ -13,6 +15,7 @@ class FetchProfile(BaseProfile): number=number, ) + class ProfileName(BaseProfile): def __init__( self, @@ -22,6 +25,7 @@ class ProfileName(BaseProfile): name=name, ) + class ProfileStatus(BaseProfile): def __init__( self, @@ -31,6 +35,7 @@ class ProfileStatus(BaseProfile): status=status, ) + class ProfilePicture(BaseProfile): def __init__( self, @@ -40,6 +45,7 @@ class ProfilePicture(BaseProfile): picture=picture, ) + class PrivacySettings(BaseProfile): def __init__( self, @@ -57,4 +63,4 @@ class PrivacySettings(BaseProfile): online=online, last=last, groupadd=groupadd, - ) \ No newline at end of file + ) diff --git a/evolutionapi/models/websocket.py b/evolutionapi/models/websocket.py index 56fbea3..f4649e7 100644 --- a/evolutionapi/models/websocket.py +++ b/evolutionapi/models/websocket.py @@ -1,6 +1,7 @@ from typing import List, Optional from dataclasses import dataclass + @dataclass class WebSocketConfig: enabled: bool @@ -10,11 +11,12 @@ class WebSocketConfig: self.enabled = enabled self.events = events + @dataclass class WebSocketInfo: enabled: bool events: List[str] def __init__(self, **kwargs): - self.enabled = kwargs.get('enabled', False) - self.events = kwargs.get('events', []) \ No newline at end of file + self.enabled = kwargs.get("enabled", False) + self.events = kwargs.get("events", []) diff --git a/evolutionapi/services/call.py b/evolutionapi/services/call.py index 4e1b2bd..7ea43c9 100644 --- a/evolutionapi/services/call.py +++ b/evolutionapi/services/call.py @@ -1,13 +1,12 @@ from typing import Union, BinaryIO from ..models.call import * + class CallService: def __init__(self, client): self.client = client def fake_call(self, instance_id: str, data: FakeCall, instance_token: str): return self.client.post( - f'call/offer/{instance_id}', - data=data.__dict__, - instance_token=instance_token - ) \ No newline at end of file + f"call/offer/{instance_id}", data=data.__dict__, instance_token=instance_token + ) diff --git a/evolutionapi/services/chat.py b/evolutionapi/services/chat.py index faa1aa2..33cef84 100644 --- a/evolutionapi/services/chat.py +++ b/evolutionapi/services/chat.py @@ -38,9 +38,7 @@ class ChatService: instance_token=instance_token, ) - def delete_message_for_everyone( - self, instance_id: str, data: MessageKey, instance_token: str - ): + def delete_message_for_everyone(self, instance_id: str, data: MessageKey, instance_token: str): return self.client.delete( f"chat/deleteMessageForEveryone/{instance_id}", data=data.__dict__, @@ -65,9 +63,7 @@ class ChatService: instance_token=instance_token, ) - def update_message( - self, instance_id: str, data: UpdateMessage, instance_token: str - ): + def update_message(self, instance_id: str, data: UpdateMessage, instance_token: str): return self.client.post( f"chat/updateMessage/{instance_id}", data=data.__dict__, diff --git a/evolutionapi/services/group.py b/evolutionapi/services/group.py index 3b27006..32b6d52 100644 --- a/evolutionapi/services/group.py +++ b/evolutionapi/services/group.py @@ -1,105 +1,110 @@ from typing import Optional from ..models.group import * + class GroupService: def __init__(self, client): self.client = client def create_group(self, instance_id: str, data: CreateGroup, instance_token: str): return self.client.post( - f'group/create/{instance_id}', - data=data.__dict__, - instance_token=instance_token + f"group/create/{instance_id}", data=data.__dict__, instance_token=instance_token ) - def update_group_picture(self, instance_id: str, group_jid: str, data: GroupPicture, instance_token: str): + def update_group_picture( + self, instance_id: str, group_jid: str, data: GroupPicture, instance_token: str + ): return self.client.post( - f'group/updateGroupPicture/{instance_id}?groupJid={group_jid}', + f"group/updateGroupPicture/{instance_id}?groupJid={group_jid}", data=data.__dict__, - instance_token=instance_token + instance_token=instance_token, ) - def update_group_subject(self, instance_id: str, group_jid: str, data: GroupSubject, instance_token: str): + def update_group_subject( + self, instance_id: str, group_jid: str, data: GroupSubject, instance_token: str + ): return self.client.post( - f'group/updateGroupSubject/{instance_id}?groupJid={group_jid}', + f"group/updateGroupSubject/{instance_id}?groupJid={group_jid}", data=data.__dict__, - instance_token=instance_token + instance_token=instance_token, ) - def update_group_description(self, instance_id: str, group_jid: str, data: GroupDescription, instance_token: str): + def update_group_description( + self, instance_id: str, group_jid: str, data: GroupDescription, instance_token: str + ): return self.client.post( - f'group/updateGroupDescription/{instance_id}?groupJid={group_jid}', + f"group/updateGroupDescription/{instance_id}?groupJid={group_jid}", data=data.__dict__, - instance_token=instance_token + instance_token=instance_token, ) def get_invite_code(self, instance_id: str, group_jid: str, instance_token: str): return self.client.get( - f'group/inviteCode/{instance_id}?groupJid={group_jid}', - instance_token=instance_token + f"group/inviteCode/{instance_id}?groupJid={group_jid}", instance_token=instance_token ) def revoke_invite_code(self, instance_id: str, group_jid: str, instance_token: str): return self.client.post( - f'group/revokeInviteCode/{instance_id}?groupJid={group_jid}', - instance_token=instance_token + f"group/revokeInviteCode/{instance_id}?groupJid={group_jid}", + instance_token=instance_token, ) def send_invite(self, instance_id: str, data: GroupInvite, instance_token: str): return self.client.post( - f'group/sendInvite/{instance_id}', - data=data.__dict__, - instance_token=instance_token + f"group/sendInvite/{instance_id}", data=data.__dict__, instance_token=instance_token ) def get_invite_info(self, instance_id: str, invite_code: str, instance_token: str): return self.client.get( - f'group/inviteInfo/{instance_id}?inviteCode={invite_code}', - instance_token=instance_token + f"group/inviteInfo/{instance_id}?inviteCode={invite_code}", + instance_token=instance_token, ) def get_group_info(self, instance_id: str, group_jid: str, instance_token: str): return self.client.get( - f'group/findGroupInfos/{instance_id}?groupJid={group_jid}', - instance_token=instance_token + f"group/findGroupInfos/{instance_id}?groupJid={group_jid}", + instance_token=instance_token, ) - def fetch_all_groups(self, instance_id: str, instance_token: str, get_participants: bool = False): - url = f'group/fetchAllGroups/{instance_id}?getParticipants={str(get_participants).lower()}' - return self.client.get( - url, - instance_token=instance_token - ) + def fetch_all_groups( + self, instance_id: str, instance_token: str, get_participants: bool = False + ): + url = f"group/fetchAllGroups/{instance_id}?getParticipants={str(get_participants).lower()}" + return self.client.get(url, instance_token=instance_token) def get_participants(self, instance_id: str, group_jid: str, instance_token: str): return self.client.get( - f'group/participants/{instance_id}?groupJid={group_jid}', - instance_token=instance_token + f"group/participants/{instance_id}?groupJid={group_jid}", instance_token=instance_token ) - def update_participant(self, instance_id: str, group_jid: str, data: UpdateParticipant, instance_token: str): + def update_participant( + self, instance_id: str, group_jid: str, data: UpdateParticipant, instance_token: str + ): return self.client.post( - f'group/updateParticipant/{instance_id}?groupJid={group_jid}', + f"group/updateParticipant/{instance_id}?groupJid={group_jid}", data=data.__dict__, - instance_token=instance_token + instance_token=instance_token, ) - def update_setting(self, instance_id: str, group_jid: str, data: UpdateSetting, instance_token: str): + def update_setting( + self, instance_id: str, group_jid: str, data: UpdateSetting, instance_token: str + ): return self.client.post( - f'group/updateSetting/{instance_id}?groupJid={group_jid}', + f"group/updateSetting/{instance_id}?groupJid={group_jid}", data=data.__dict__, - instance_token=instance_token + instance_token=instance_token, ) - def toggle_ephemeral(self, instance_id: str, group_jid: str, data: ToggleEphemeral, instance_token: str): + def toggle_ephemeral( + self, instance_id: str, group_jid: str, data: ToggleEphemeral, instance_token: str + ): return self.client.post( - f'group/toggleEphemeral/{instance_id}?groupJid={group_jid}', + f"group/toggleEphemeral/{instance_id}?groupJid={group_jid}", data=data.__dict__, - instance_token=instance_token + instance_token=instance_token, ) def leave_group(self, instance_id: str, group_jid: str, instance_token: str): return self.client.delete( - f'group/leaveGroup/{instance_id}?groupJid={group_jid}', - instance_token=instance_token - ) \ No newline at end of file + f"group/leaveGroup/{instance_id}?groupJid={group_jid}", instance_token=instance_token + ) diff --git a/evolutionapi/services/instance.py b/evolutionapi/services/instance.py index 099c8bb..678755f 100644 --- a/evolutionapi/services/instance.py +++ b/evolutionapi/services/instance.py @@ -3,7 +3,7 @@ class InstanceService: self.client = client def fetch_instances(self): - return self.client.get('instance/fetchInstances') + return self.client.get("instance/fetchInstances") def create_instance(self, config): - return self.client.post('instance/create', data=config.__dict__) \ No newline at end of file + return self.client.post("instance/create", data=config.__dict__) diff --git a/evolutionapi/services/instance_operations.py b/evolutionapi/services/instance_operations.py index c59016f..60dcf5a 100644 --- a/evolutionapi/services/instance_operations.py +++ b/evolutionapi/services/instance_operations.py @@ -1,28 +1,29 @@ from ..models.presence import PresenceStatus, PresenceConfig + class InstanceOperationsService: def __init__(self, client): self.client = client def connect(self, instance_id: str, instance_token: str): - return self.client.get(f'instance/connect/{instance_id}', instance_token) + return self.client.get(f"instance/connect/{instance_id}", instance_token) def restart(self, instance_id: str, instance_token: str): - return self.client.post(f'instance/restart/{instance_id}', instance_token=instance_token) + return self.client.post(f"instance/restart/{instance_id}", instance_token=instance_token) def set_presence(self, instance_id: str, presence: PresenceStatus, instance_token: str): config = PresenceConfig(presence) return self.client.post( - f'instance/setPresence/{instance_id}', + f"instance/setPresence/{instance_id}", data=config.__dict__, - instance_token=instance_token + instance_token=instance_token, ) def get_connection_state(self, instance_id: str, instance_token: str): - return self.client.get(f'instance/connectionState/{instance_id}', instance_token) + return self.client.get(f"instance/connectionState/{instance_id}", instance_token) def logout(self, instance_id: str, instance_token: str): - return self.client.delete(f'instance/logout/{instance_id}', instance_token) + return self.client.delete(f"instance/logout/{instance_id}", instance_token) def delete(self, instance_id: str, instance_token: str): - return self.client.delete(f'instance/delete/{instance_id}', instance_token) + return self.client.delete(f"instance/delete/{instance_id}", instance_token) diff --git a/evolutionapi/services/label.py b/evolutionapi/services/label.py index ed87c91..09294a5 100644 --- a/evolutionapi/services/label.py +++ b/evolutionapi/services/label.py @@ -1,19 +1,15 @@ from typing import Union, BinaryIO from ..models.label import * + class LabelService: def __init__(self, client): self.client = client def find_labels(self, instance_id: str, instance_token: str): - return self.client.get( - f'label/findLabels/{instance_id}', - instance_token=instance_token - ) + return self.client.get(f"label/findLabels/{instance_id}", instance_token=instance_token) def handle_label(self, instance_id: str, data: HandleLabel, instance_token: str): return self.client.post( - f'label/handleLabel/{instance_id}', - data=data.__dict__, - instance_token=instance_token - ) \ No newline at end of file + f"label/handleLabel/{instance_id}", data=data.__dict__, instance_token=instance_token + ) diff --git a/evolutionapi/services/message.py b/evolutionapi/services/message.py index 79efaae..e38901e 100644 --- a/evolutionapi/services/message.py +++ b/evolutionapi/services/message.py @@ -4,202 +4,210 @@ from requests_toolbelt import MultipartEncoder import mimetypes import requests + class MessageService: def __init__(self, client): self.client = client def send_text(self, instance_id: str, message: TextMessage, instance_token: str): # Preparar os dados como JSON - data = { - 'number': message.number, - 'text': message.text - } - - if hasattr(message, 'delay') and message.delay is not None: - data['delay'] = message.delay - + data = {"number": message.number, "text": message.text} + + if hasattr(message, "delay") and message.delay is not None: + data["delay"] = message.delay + # Usar o método post do cliente que já trata JSON corretamente return self.client.post( - f'message/sendText/{instance_id}', - data=data, - instance_token=instance_token + f"message/sendText/{instance_id}", data=data, instance_token=instance_token ) - def send_media(self, instance_id: str, message: MediaMessage, instance_token: str, file: Union[BinaryIO, str] = None): + def send_media( + self, + instance_id: str, + message: MediaMessage, + instance_token: str, + file: Union[BinaryIO, str] = None, + ): # Preparar os dados do formulário fields = { - 'number': (None, message.number, 'text/plain'), - 'mediatype': (None, message.mediatype, 'text/plain'), - 'mimetype': (None, message.mimetype, 'text/plain'), - 'caption': (None, message.caption, 'text/plain'), - 'fileName': (None, message.fileName, 'text/plain'), + "number": (None, message.number, "text/plain"), + "mediatype": (None, message.mediatype, "text/plain"), + "mimetype": (None, message.mimetype, "text/plain"), + "caption": (None, message.caption, "text/plain"), + "fileName": (None, message.fileName, "text/plain"), } - + # Adicionar delay apenas se existir - if hasattr(message, 'delay') and message.delay is not None: - fields['delay'] = (None, str(message.delay), 'text/plain; type=number') - + if hasattr(message, "delay") and message.delay is not None: + fields["delay"] = (None, str(message.delay), "text/plain; type=number") + # Adicionar o arquivo se fornecido if file: if isinstance(file, str): - mime_type = mimetypes.guess_type(file)[0] or 'application/octet-stream' - fields['file'] = ('file', open(file, 'rb'), mime_type) + mime_type = mimetypes.guess_type(file)[0] or "application/octet-stream" + fields["file"] = ("file", open(file, "rb"), mime_type) else: - fields['file'] = ('file', file, 'application/octet-stream') - + fields["file"] = ("file", file, "application/octet-stream") + # Criar o multipart encoder multipart = MultipartEncoder(fields=fields) - + # Preparar os headers headers = self.client._get_headers(instance_token) - headers['Content-Type'] = multipart.content_type - + headers["Content-Type"] = multipart.content_type + # Fazer a requisição diretamente - url = f'{self.client.base_url}/message/sendMedia/{instance_id}' - response = requests.post( - url, - headers=headers, - data=multipart - ) - + url = f"{self.client.base_url}/message/sendMedia/{instance_id}" + response = requests.post(url, headers=headers, data=multipart) + return response.json() - def send_ptv(self, instance_id: str, message: dict, instance_token: str, file: Union[BinaryIO, str] = None): + def send_ptv( + self, + instance_id: str, + message: dict, + instance_token: str, + file: Union[BinaryIO, str] = None, + ): fields = {} - + # Adiciona todos os campos do message como text/plain for key, value in message.items(): - if key == 'delay' and value is not None: - fields[key] = (None, str(value), 'text/plain; type=number') + if key == "delay" and value is not None: + fields[key] = (None, str(value), "text/plain; type=number") else: - fields[key] = (None, str(value), 'text/plain') - + fields[key] = (None, str(value), "text/plain") + if file: if isinstance(file, str): - mime_type = mimetypes.guess_type(file)[0] or 'application/octet-stream' - fields['file'] = ('file', open(file, 'rb'), mime_type) + mime_type = mimetypes.guess_type(file)[0] or "application/octet-stream" + fields["file"] = ("file", open(file, "rb"), mime_type) else: - fields['file'] = ('file', file, 'application/octet-stream') - + fields["file"] = ("file", file, "application/octet-stream") + multipart = MultipartEncoder(fields=fields) headers = self.client._get_headers(instance_token) - headers['Content-Type'] = multipart.content_type - - url = f'{self.client.base_url}/message/sendPtv/{instance_id}' + headers["Content-Type"] = multipart.content_type + + url = f"{self.client.base_url}/message/sendPtv/{instance_id}" response = requests.post(url, headers=headers, data=multipart) return response.json() - def send_whatsapp_audio(self, instance_id: str, message: dict, instance_token: str, file: Union[BinaryIO, str] = None): + def send_whatsapp_audio( + self, + instance_id: str, + message: dict, + instance_token: str, + file: Union[BinaryIO, str] = None, + ): fields = {} - + # Adiciona todos os campos do message como text/plain for key, value in message.items(): - if key == 'delay' and value is not None: - fields[key] = (None, str(value), 'text/plain; type=number') + if key == "delay" and value is not None: + fields[key] = (None, str(value), "text/plain; type=number") else: - fields[key] = (None, str(value), 'text/plain') - + fields[key] = (None, str(value), "text/plain") + if file: if isinstance(file, str): - mime_type = mimetypes.guess_type(file)[0] or 'application/octet-stream' - fields['file'] = ('file', open(file, 'rb'), mime_type) + mime_type = mimetypes.guess_type(file)[0] or "application/octet-stream" + fields["file"] = ("file", open(file, "rb"), mime_type) else: - fields['file'] = ('file', file, 'application/octet-stream') - + fields["file"] = ("file", file, "application/octet-stream") + multipart = MultipartEncoder(fields=fields) headers = self.client._get_headers(instance_token) - headers['Content-Type'] = multipart.content_type - - url = f'{self.client.base_url}/message/sendWhatsAppAudio/{instance_id}' + headers["Content-Type"] = multipart.content_type + + url = f"{self.client.base_url}/message/sendWhatsAppAudio/{instance_id}" response = requests.post(url, headers=headers, data=multipart) return response.json() def send_status(self, instance_id: str, message: StatusMessage, instance_token: str): return self.client.post( - f'message/sendStatus/{instance_id}', + f"message/sendStatus/{instance_id}", data=message.__dict__, - instance_token=instance_token + instance_token=instance_token, ) - def send_sticker(self, instance_id: str, message: dict, instance_token: str, file: Union[BinaryIO, str] = None): + def send_sticker( + self, + instance_id: str, + message: dict, + instance_token: str, + file: Union[BinaryIO, str] = None, + ): fields = {} - + # Adiciona todos os campos do message como text/plain for key, value in message.items(): - if key == 'delay' and value is not None: - fields[key] = (None, str(value), 'text/plain; type=number') + if key == "delay" and value is not None: + fields[key] = (None, str(value), "text/plain; type=number") else: - fields[key] = (None, str(value), 'text/plain') - + fields[key] = (None, str(value), "text/plain") + if file: if isinstance(file, str): - mime_type = mimetypes.guess_type(file)[0] or 'application/octet-stream' - fields['file'] = ('file', open(file, 'rb'), mime_type) + mime_type = mimetypes.guess_type(file)[0] or "application/octet-stream" + fields["file"] = ("file", open(file, "rb"), mime_type) else: - fields['file'] = ('file', file, 'application/octet-stream') - + fields["file"] = ("file", file, "application/octet-stream") + multipart = MultipartEncoder(fields=fields) headers = self.client._get_headers(instance_token) - headers['Content-Type'] = multipart.content_type - - url = f'{self.client.base_url}/message/sendSticker/{instance_id}' + headers["Content-Type"] = multipart.content_type + + url = f"{self.client.base_url}/message/sendSticker/{instance_id}" response = requests.post(url, headers=headers, data=multipart) return response.json() def send_location(self, instance_id: str, message: LocationMessage, instance_token: str): data = message.__dict__.copy() - if 'delay' in data and data['delay'] is not None: - data['delay'] = int(data['delay']) - + if "delay" in data and data["delay"] is not None: + data["delay"] = int(data["delay"]) + return self.client.post( - f'message/sendLocation/{instance_id}', - data=data, - instance_token=instance_token + f"message/sendLocation/{instance_id}", data=data, instance_token=instance_token ) def send_contact(self, instance_id: str, message: ContactMessage, instance_token: str): return self.client.post( - f'message/sendContact/{instance_id}', + f"message/sendContact/{instance_id}", data=message.__dict__, - instance_token=instance_token + instance_token=instance_token, ) def send_reaction(self, instance_id: str, message: ReactionMessage, instance_token: str): return self.client.post( - f'message/sendReaction/{instance_id}', + f"message/sendReaction/{instance_id}", data=message.__dict__, - instance_token=instance_token + instance_token=instance_token, ) def send_poll(self, instance_id: str, message: PollMessage, instance_token: str): data = message.__dict__.copy() - if 'delay' in data and data['delay'] is not None: - data['delay'] = int(data['delay']) - + if "delay" in data and data["delay"] is not None: + data["delay"] = int(data["delay"]) + return self.client.post( - f'message/sendPoll/{instance_id}', - data=data, - instance_token=instance_token + f"message/sendPoll/{instance_id}", data=data, instance_token=instance_token ) def send_list(self, instance_id: str, message: ListMessage, instance_token: str): data = message.__dict__.copy() - if 'delay' in data and data['delay'] is not None: - data['delay'] = int(data['delay']) - + if "delay" in data and data["delay"] is not None: + data["delay"] = int(data["delay"]) + return self.client.post( - f'message/sendList/{instance_id}', - data=data, - instance_token=instance_token + f"message/sendList/{instance_id}", data=data, instance_token=instance_token ) def send_buttons(self, instance_id: str, message: ButtonMessage, instance_token: str): data = message.__dict__.copy() - if 'delay' in data and data['delay'] is not None: - data['delay'] = int(data['delay']) - + if "delay" in data and data["delay"] is not None: + data["delay"] = int(data["delay"]) + return self.client.post( - f'message/sendButtons/{instance_id}', - data=data, - instance_token=instance_token - ) \ No newline at end of file + f"message/sendButtons/{instance_id}", data=data, instance_token=instance_token + ) diff --git a/evolutionapi/services/profile.py b/evolutionapi/services/profile.py index b0da8ab..8d50383 100644 --- a/evolutionapi/services/profile.py +++ b/evolutionapi/services/profile.py @@ -1,60 +1,57 @@ from typing import Union, BinaryIO from ..models.profile import * + class ProfileService: def __init__(self, client): self.client = client def fetch_business_profile(self, instance_id: str, data: FetchProfile, instance_token: str): return self.client.post( - f'chat/fetchBusinessProfile/{instance_id}', + f"chat/fetchBusinessProfile/{instance_id}", data=data.__dict__, - instance_token=instance_token + instance_token=instance_token, ) def fetch_profile(self, instance_id: str, data: FetchProfile, instance_token: str): return self.client.post( - f'chat/fetchProfile/{instance_id}', - data=data.__dict__, - instance_token=instance_token + f"chat/fetchProfile/{instance_id}", data=data.__dict__, instance_token=instance_token ) def update_profile_name(self, instance_id: str, data: ProfileName, instance_token: str): return self.client.post( - f'chat/updateProfileName/{instance_id}', + f"chat/updateProfileName/{instance_id}", data=data.__dict__, - instance_token=instance_token + instance_token=instance_token, ) def update_profile_status(self, instance_id: str, data: ProfileStatus, instance_token: str): return self.client.post( - f'chat/updateProfileStatus/{instance_id}', + f"chat/updateProfileStatus/{instance_id}", data=data.__dict__, - instance_token=instance_token + instance_token=instance_token, ) def update_profile_picture(self, instance_id: str, data: ProfilePicture, instance_token: str): return self.client.post( - f'chat/updateProfilePicture/{instance_id}', + f"chat/updateProfilePicture/{instance_id}", data=data.__dict__, - instance_token=instance_token + instance_token=instance_token, ) def remove_profile_picture(self, instance_id: str, instance_token: str): return self.client.delete( - f'chat/removeProfilePicture/{instance_id}', - instance_token=instance_token + f"chat/removeProfilePicture/{instance_id}", instance_token=instance_token ) - + def fetch_privacy_settings(self, instance_id: str, instance_token: str): return self.client.get( - f'chat/fetchPrivacySettings/{instance_id}', - instance_token=instance_token + f"chat/fetchPrivacySettings/{instance_id}", instance_token=instance_token ) def update_privacy_settings(self, instance_id: str, data: PrivacySettings, instance_token: str): return self.client.post( - f'chat/updatePrivacySettings/{instance_id}', + f"chat/updatePrivacySettings/{instance_id}", data=data.__dict__, - instance_token=instance_token - ) \ No newline at end of file + instance_token=instance_token, + ) diff --git a/evolutionapi/services/websocket.py b/evolutionapi/services/websocket.py index 6839881..e1af45d 100644 --- a/evolutionapi/services/websocket.py +++ b/evolutionapi/services/websocket.py @@ -5,6 +5,7 @@ import time from typing import Optional from ..models.websocket import WebSocketConfig, WebSocketInfo + class WebSocketService: def __init__(self, client): self.client = client @@ -12,43 +13,46 @@ class WebSocketService: def set_websocket(self, instance_id: str, config: WebSocketConfig, instance_token: str): """ Configure WebSocket settings for an instance - + Args: instance_id (str): The instance ID config (WebSocketConfig): The WebSocket configuration instance_token (str): The instance token - + Returns: dict: The response from the API """ return self.client.post( - f'websocket/set/{instance_id}', - data=config.__dict__, - instance_token=instance_token + f"websocket/set/{instance_id}", data=config.__dict__, instance_token=instance_token ) def find_websocket(self, instance_id: str, instance_token: str) -> WebSocketInfo: """ Get WebSocket settings for an instance - + Args: instance_id (str): The instance ID instance_token (str): The instance token - + Returns: WebSocketInfo: The WebSocket information """ - response = self.client.get( - f'websocket/find/{instance_id}', - instance_token=instance_token - ) + response = self.client.get(f"websocket/find/{instance_id}", instance_token=instance_token) return WebSocketInfo(**response) + class WebSocketManager: - def __init__(self, base_url: str, instance_id: str, api_token: str, max_retries: int = 5, retry_delay: float = 1.0): + def __init__( + self, + base_url: str, + instance_id: str, + api_token: str, + max_retries: int = 5, + retry_delay: float = 1.0, + ): """ Initialize the WebSocket manager - + Args: base_url (str): Base URL of the API instance_id (str): Instance ID @@ -56,58 +60,60 @@ class WebSocketManager: max_retries (int): Maximum number of reconnection attempts retry_delay (float): Initial delay between attempts in seconds """ - self.base_url = base_url.rstrip('/') + self.base_url = base_url.rstrip("/") self.instance_id = instance_id self.api_token = api_token self.max_retries = max_retries self.retry_delay = retry_delay self.retry_count = 0 self.should_reconnect = True - + # Socket.IO configuration self.sio = socketio.Client( ssl_verify=False, # For local development logger=False, engineio_logger=False, - request_timeout=30 + request_timeout=30, ) - + # Configure class logger to INFO self.logger = logging.getLogger(__name__) self.logger.setLevel(logging.INFO) - + # Dictionary to store registered handlers self._handlers = {} - + # Configure event handlers - self.sio.on('connect', self._on_connect) - self.sio.on('disconnect', self._on_disconnect) - self.sio.on('error', self._on_error) - + self.sio.on("connect", self._on_connect) + self.sio.on("disconnect", self._on_disconnect) + self.sio.on("error", self._on_error) + # Register global handler in instance-specific namespace - self.sio.on('*', self._handle_event, namespace=f'/{self.instance_id}') - + self.sio.on("*", self._handle_event, namespace=f"/{self.instance_id}") + def _on_connect(self): """Handler for connection event""" self.logger.info("Socket.IO connected") self.retry_count = 0 # Reset retry counter after successful connection - + def _on_disconnect(self): """Handler for disconnection event""" - self.logger.warning(f"Socket.IO disconnected. Attempt {self.retry_count + 1}/{self.max_retries}") + self.logger.warning( + f"Socket.IO disconnected. Attempt {self.retry_count + 1}/{self.max_retries}" + ) if self.should_reconnect and self.retry_count < self.max_retries: self._attempt_reconnect() else: self.logger.error("Maximum number of reconnection attempts reached") - + def _on_error(self, error): """Handler for error events""" self.logger.error(f"Socket.IO error: {str(error)}", exc_info=True) - + def _attempt_reconnect(self): """Attempt to reconnect with exponential backoff""" try: - delay = self.retry_delay * (2 ** self.retry_count) # Exponential backoff + delay = self.retry_delay * (2**self.retry_count) # Exponential backoff self.logger.info(f"Attempting to reconnect in {delay:.2f} seconds...") time.sleep(delay) self.connect() @@ -118,18 +124,18 @@ class WebSocketManager: self._attempt_reconnect() else: self.logger.error("All reconnection attempts failed") - + def _handle_event(self, event, *args): """Global handler for all events""" # Only process registered events if event in self._handlers: self.logger.debug(f"Event received in namespace /{self.instance_id}: {event}") self.logger.debug(f"Event data: {args}") - + try: # Extract event data raw_data = args[0] if args else {} - + # Ensure we're passing the correct object to the callback if isinstance(raw_data, dict): self.logger.debug(f"Calling handler for {event} with data: {raw_data}") @@ -138,37 +144,39 @@ class WebSocketManager: self.logger.error(f"Invalid data received for event {event}: {raw_data}") except Exception as e: self.logger.error(f"Error processing event {event}: {str(e)}", exc_info=True) - + def connect(self): """Connect to Socket.IO server""" try: # Connect only to instance namespace with authentication header self.sio.connect( f"{self.base_url}?apikey={self.api_token}", - transports=['websocket'], - namespaces=[f'/{self.instance_id}'], - wait_timeout=30 + transports=["websocket"], + namespaces=[f"/{self.instance_id}"], + wait_timeout=30, ) - + # Join instance-specific room - self.sio.emit('subscribe', {'instance': self.instance_id}, namespace=f'/{self.instance_id}') - + self.sio.emit( + "subscribe", {"instance": self.instance_id}, namespace=f"/{self.instance_id}" + ) + except Exception as e: self.logger.error(f"Error connecting to Socket.IO: {str(e)}", exc_info=True) raise - + def disconnect(self): """Disconnect from Socket.IO server""" self.should_reconnect = False # Prevent reconnection attempts if self.sio.connected: self.sio.disconnect() - + def on(self, event: str, callback: Callable): """ Register a callback for a specific event - + Args: event (str): Event name callback (Callable): Function to be called when the event occurs """ - self._handlers[event] = callback \ No newline at end of file + self._handlers[event] = callback diff --git a/test_evolution.py b/test_evolution.py index 954bd88..4a68073 100644 --- a/test_evolution.py +++ b/test_evolution.py @@ -7,16 +7,14 @@ import logging # Configuração do logging logging.basicConfig( - level=logging.DEBUG, - format='%(asctime)s - %(name)s - %(levelname)s - %(message)s' + level=logging.DEBUG, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s" ) logger = logging.getLogger(__name__) print("Iniciando cliente") client = EvolutionClient( - base_url='http://localhost:8081', - api_token='429683C4C977415CAAFCCE10F7D57E11' + base_url="http://localhost:8081", api_token="429683C4C977415CAAFCCE10F7D57E11" ) instance_token = "82D55E57CBBC-48A5-98FB-E99655AE7148" @@ -49,8 +47,8 @@ websocket_config = WebSocketConfig( "LABELS_ASSOCIATION", "CALL", "TYPEBOT_START", - "TYPEBOT_CHANGE_STATUS" - ] + "TYPEBOT_CHANGE_STATUS", + ], ) # Configurando WebSocket para a instância @@ -66,60 +64,65 @@ logger.info(f"Eventos configurados: {websocket_info.events}") # Criando gerenciador WebSocket usando o cliente logger.info("Criando gerenciador WebSocket...") websocket_manager = client.create_websocket( - instance_id=instance_id, - api_token=instance_token, - max_retries=5, - retry_delay=1.0 + instance_id=instance_id, api_token=instance_token, max_retries=5, retry_delay=1.0 ) - + + def on_message(data): """Handler para evento de mensagens""" try: - if 'data' in data: - message_data = data['data'] + if "data" in data: + message_data = data["data"] logger.info("=== Mensagem Recebida ===") logger.info(f"De: {message_data['key']['remoteJid']}") logger.info(f"Tipo: {message_data['messageType']}") - + # Extrai o conteúdo baseado no tipo da mensagem - if 'message' in message_data: - if 'conversation' in message_data['message']: + if "message" in message_data: + if "conversation" in message_data["message"]: logger.info(f"Conteúdo: {message_data['message']['conversation']}") - elif 'extendedTextMessage' in message_data['message']: - logger.info(f"Conteúdo: {message_data['message']['extendedTextMessage']['text']}") - elif 'imageMessage' in message_data['message']: - logger.info(f"Conteúdo: [Imagem] {message_data['message']['imageMessage'].get('caption', '')}") + elif "extendedTextMessage" in message_data["message"]: + logger.info( + f"Conteúdo: {message_data['message']['extendedTextMessage']['text']}" + ) + elif "imageMessage" in message_data["message"]: + logger.info( + f"Conteúdo: [Imagem] {message_data['message']['imageMessage'].get('caption', '')}" + ) else: logger.info(f"Conteúdo: {message_data['message']}") - + logger.info("=======================") except Exception as e: logger.error(f"Erro ao processar mensagem: {e}", exc_info=True) + def on_qrcode(data): """Handler para evento de QR Code""" logger.info("=== QR Code Atualizado ===") logger.info(f"QR Code: {data}") logger.info("=======================") + def on_connection(data): """Handler para evento de conexão""" logger.info("=== Status de Conexão ===") logger.info(f"Status: {data}") logger.info("=======================") + logger.info("Registrando handlers de eventos...") # Registrando handlers de eventos -websocket_manager.on('messages.upsert', on_message) -websocket_manager.on('qrcode.updated', on_qrcode) -websocket_manager.on('connection.update', on_connection) +websocket_manager.on("messages.upsert", on_message) +websocket_manager.on("qrcode.updated", on_qrcode) +websocket_manager.on("connection.update", on_connection) try: logger.info("Iniciando conexão WebSocket...") # Conectando ao WebSocket websocket_manager.connect() - + # Mantendo o programa rodando para receber eventos logger.info("Aguardando eventos...") while True: @@ -153,4 +156,4 @@ finally: # response = client.messages.send_media(instance_id, media_message, instance_token, "arquivo.pdf") # print("Mensagem de mídia enviada") -# print(response) \ No newline at end of file +# print(response)