style: format codebase with ruff

This commit is contained in:
Jackson Vieira
2025-12-03 13:54:30 -03:00
parent 44539123d9
commit 2421b1cf5b
21 changed files with 458 additions and 441 deletions

View File

@@ -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
)
f"call/offer/{instance_id}", data=data.__dict__, instance_token=instance_token
)

View File

@@ -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__,

View File

@@ -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
)
f"group/leaveGroup/{instance_id}?groupJid={group_jid}", instance_token=instance_token
)

View File

@@ -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__)
return self.client.post("instance/create", data=config.__dict__)

View File

@@ -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)

View File

@@ -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
)
f"label/handleLabel/{instance_id}", data=data.__dict__, instance_token=instance_token
)

View File

@@ -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
)
f"message/sendButtons/{instance_id}", data=data, instance_token=instance_token
)

View File

@@ -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
)
instance_token=instance_token,
)

View File

@@ -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
self._handlers[event] = callback