mirror of
https://github.com/EvolutionAPI/evolution-client-python.git
synced 2025-12-13 11:59:33 -06:00
Merge a53948d4bf into 043a7f9b3c
This commit is contained in:
commit
4c50674803
30
.gitignore
vendored
30
.gitignore
vendored
@ -1,5 +1,31 @@
|
|||||||
# Python-generated files
|
# Python-generated files
|
||||||
__pycache__/
|
__pycache__/
|
||||||
*.cpython*.pyc
|
*.cpython*.pyc
|
||||||
evolutionapi.egg-info/
|
*.pyc
|
||||||
dist/
|
*.pyo
|
||||||
|
*.pyd
|
||||||
|
|
||||||
|
# Distribution / packaging
|
||||||
|
dist/
|
||||||
|
build/
|
||||||
|
*.egg-info/
|
||||||
|
.eggs/
|
||||||
|
|
||||||
|
# Virtual environments
|
||||||
|
.venv/
|
||||||
|
venv/
|
||||||
|
env/
|
||||||
|
ENV/
|
||||||
|
|
||||||
|
# uv
|
||||||
|
.uv/
|
||||||
|
|
||||||
|
# Ruff
|
||||||
|
.ruff_cache/
|
||||||
|
|
||||||
|
# IDE
|
||||||
|
.vscode/
|
||||||
|
.idea/
|
||||||
|
*.swp
|
||||||
|
*.swo
|
||||||
|
*~
|
||||||
58
README.md
58
README.md
@ -1242,4 +1242,60 @@ The WebSocket Manager has robust error handling:
|
|||||||
3. Use logging for debugging and monitoring
|
3. Use logging for debugging and monitoring
|
||||||
4. Consider implementing a heartbeat mechanism if needed
|
4. Consider implementing a heartbeat mechanism if needed
|
||||||
5. Keep your API token secure and don't expose it in logs
|
5. Keep your API token secure and don't expose it in logs
|
||||||
6. Keep your API token secure and don't expose it in logs
|
|
||||||
|
## Contributing
|
||||||
|
|
||||||
|
This project uses Python >= 3.8 and uv:
|
||||||
|
|
||||||
|
### Setup Development Environment
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -LsSf https://astral.sh/uv/install.sh | sh
|
||||||
|
|
||||||
|
git clone https://github.com/EvolutionAPI/evolution-client-python.git
|
||||||
|
cd evolution-client-python
|
||||||
|
|
||||||
|
uv sync
|
||||||
|
```
|
||||||
|
|
||||||
|
### Code Quality
|
||||||
|
|
||||||
|
This project uses [ruff](https://docs.astral.sh/ruff/) for linting and formatting:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Format code
|
||||||
|
uv run ruff format
|
||||||
|
|
||||||
|
# Check linting
|
||||||
|
uv run ruff check
|
||||||
|
|
||||||
|
# Fix auto-fixable issues
|
||||||
|
uv run ruff check --fix
|
||||||
|
```
|
||||||
|
|
||||||
|
### Building and Publishing
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Build the package
|
||||||
|
uv build
|
||||||
|
|
||||||
|
# Publish to PyPI (requires credentials)
|
||||||
|
uv publish
|
||||||
|
|
||||||
|
# Or use the provided script
|
||||||
|
./publish.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
### Adding Dependencies
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Add a runtime dependency
|
||||||
|
uv add package-name
|
||||||
|
|
||||||
|
# Add a development dependency
|
||||||
|
uv add --dev package-name
|
||||||
|
```
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
MIT
|
||||||
|
|||||||
@ -1,81 +0,0 @@
|
|||||||
import requests
|
|
||||||
from .exceptions import EvolutionAuthenticationError, EvolutionNotFoundError, EvolutionAPIError
|
|
||||||
from .services.instance import InstanceService
|
|
||||||
from .services.instance_operations import InstanceOperationsService
|
|
||||||
from .services.message import MessageService
|
|
||||||
from .services.call import CallService
|
|
||||||
from .services.chat import ChatService
|
|
||||||
from .services.label import LabelService
|
|
||||||
from .services.profile import ProfileService
|
|
||||||
from .services.group import GroupService
|
|
||||||
class EvolutionClient:
|
|
||||||
"""
|
|
||||||
Cliente para interagir com a API Evolution.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
base_url (str): A URL base do servidor da API Evolution.
|
|
||||||
api_token (str): O token de autenticação para acessar a API.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, base_url: str, api_token: str):
|
|
||||||
self.base_url = base_url.rstrip('/')
|
|
||||||
self.api_token = api_token
|
|
||||||
self.instances = InstanceService(self)
|
|
||||||
self.instance_operations = InstanceOperationsService(self)
|
|
||||||
self.messages = MessageService(self)
|
|
||||||
self.calls = CallService(self)
|
|
||||||
self.chat = ChatService(self)
|
|
||||||
self.label = LabelService(self)
|
|
||||||
self.profile = ProfileService(self)
|
|
||||||
self.group = GroupService(self)
|
|
||||||
|
|
||||||
def _get_headers(self, instance_token: str = None):
|
|
||||||
return {
|
|
||||||
'apikey': instance_token or self.api_token,
|
|
||||||
'Content-Type': 'application/json'
|
|
||||||
}
|
|
||||||
|
|
||||||
def _get_full_url(self, endpoint):
|
|
||||||
return f'{self.base_url}/{endpoint}'
|
|
||||||
|
|
||||||
def _handle_response(self, response):
|
|
||||||
if response.status_code == 401:
|
|
||||||
raise EvolutionAuthenticationError('Falha na autenticação.')
|
|
||||||
elif response.status_code == 404:
|
|
||||||
raise EvolutionNotFoundError('Recurso não encontrado.')
|
|
||||||
elif response.ok:
|
|
||||||
try:
|
|
||||||
return response.json()
|
|
||||||
except ValueError:
|
|
||||||
return response.content
|
|
||||||
else:
|
|
||||||
error_detail = ''
|
|
||||||
try:
|
|
||||||
error_detail = f' - {response.json()}'
|
|
||||||
except:
|
|
||||||
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."""
|
|
||||||
url = self._get_full_url(endpoint)
|
|
||||||
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):
|
|
||||||
"""Faz uma requisição POST."""
|
|
||||||
url = self._get_full_url(endpoint)
|
|
||||||
response = requests.post(url, headers=self._get_headers(instance_token), json=data)
|
|
||||||
return self._handle_response(response)
|
|
||||||
|
|
||||||
def put(self, endpoint, data=None):
|
|
||||||
"""Faz uma requisição PUT."""
|
|
||||||
url = self._get_full_url(endpoint)
|
|
||||||
response = requests.put(url, headers=self.headers, json=data)
|
|
||||||
return self._handle_response(response)
|
|
||||||
|
|
||||||
def delete(self, endpoint: str, instance_token: str = None):
|
|
||||||
"""Faz uma requisição DELETE."""
|
|
||||||
url = self._get_full_url(endpoint)
|
|
||||||
response = requests.delete(url, headers=self._get_headers(instance_token))
|
|
||||||
return self._handle_response(response)
|
|
||||||
@ -1,11 +0,0 @@
|
|||||||
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
|
|
||||||
@ -1,16 +0,0 @@
|
|||||||
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
|
|
||||||
)
|
|
||||||
@ -1,93 +0,0 @@
|
|||||||
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
|
|
||||||
)
|
|
||||||
|
|
||||||
class MessageKey:
|
|
||||||
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
|
|
||||||
):
|
|
||||||
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
|
|
||||||
):
|
|
||||||
self.lastMessage = last_message
|
|
||||||
self.chat = chat
|
|
||||||
self.archive = archive
|
|
||||||
|
|
||||||
class UnreadChat:
|
|
||||||
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
|
|
||||||
):
|
|
||||||
self.message = message
|
|
||||||
self.convertToMp4 = convert_to_mp4
|
|
||||||
|
|
||||||
class UpdateMessage:
|
|
||||||
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
|
|
||||||
):
|
|
||||||
self.number = number
|
|
||||||
self.delay = delay
|
|
||||||
self.presence = presence
|
|
||||||
@ -1,39 +0,0 @@
|
|||||||
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
|
|
||||||
@ -1,59 +0,0 @@
|
|||||||
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):
|
|
||||||
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"):
|
|
||||||
self.chatwootAccountId = accountId
|
|
||||||
self.chatwootToken = token
|
|
||||||
self.chatwootUrl = url
|
|
||||||
self.chatwootSignMsg = signMsg
|
|
||||||
self.chatwootReopenConversation = reopenConversation
|
|
||||||
self.chatwootConversationPending = conversationPending
|
|
||||||
self.chatwootImportContacts = importContacts
|
|
||||||
self.chatwootNameInbox = nameInbox
|
|
||||||
self.chatwootMergeBrazilContacts = mergeBrazilContacts
|
|
||||||
self.chatwootImportMessages = importMessages
|
|
||||||
self.chatwootDaysLimitImportMessages = daysLimitImportMessages
|
|
||||||
self.chatwootOrganization = organization
|
|
||||||
self.chatwootLogo = logo
|
|
||||||
|
|
||||||
class InstanceConfig:
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
instanceName: str,
|
|
||||||
integration: str = None,
|
|
||||||
token: str = None,
|
|
||||||
number: str = None,
|
|
||||||
qrcode: bool = None,
|
|
||||||
rejectCall: bool = None,
|
|
||||||
msgCall: str = None,
|
|
||||||
groupsIgnore: bool = None,
|
|
||||||
alwaysOnline: bool = None,
|
|
||||||
readMessages: bool = None,
|
|
||||||
readStatus: bool = None,
|
|
||||||
syncFullHistory: bool = None
|
|
||||||
):
|
|
||||||
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
|
|
||||||
@ -1,21 +0,0 @@
|
|||||||
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"]
|
|
||||||
):
|
|
||||||
if action not in ["add", "remove"]:
|
|
||||||
raise ValueError("action deve ser 'add' ou 'remove'")
|
|
||||||
|
|
||||||
super().__init__(
|
|
||||||
number=number,
|
|
||||||
labelId=label_id,
|
|
||||||
action=action
|
|
||||||
)
|
|
||||||
@ -1,254 +0,0 @@
|
|||||||
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
|
|
||||||
BRYNDAN_WRITE = 3
|
|
||||||
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,
|
|
||||||
number: str,
|
|
||||||
text: str,
|
|
||||||
delay: Optional[int] = None,
|
|
||||||
quoted: Optional[QuotedMessage] = None,
|
|
||||||
linkPreview: Optional[bool] = None,
|
|
||||||
mentionsEveryOne: Optional[bool] = None,
|
|
||||||
mentioned: Optional[List[str]] = None
|
|
||||||
):
|
|
||||||
super().__init__(
|
|
||||||
number=number,
|
|
||||||
text=text,
|
|
||||||
delay=delay,
|
|
||||||
quoted=quoted.__dict__ if quoted else None,
|
|
||||||
linkPreview=linkPreview,
|
|
||||||
mentionsEveryOne=mentionsEveryOne,
|
|
||||||
mentioned=mentioned
|
|
||||||
)
|
|
||||||
|
|
||||||
class MediaMessage(BaseMessage):
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
number: str,
|
|
||||||
mediatype: str,
|
|
||||||
mimetype: str,
|
|
||||||
caption: str,
|
|
||||||
media: str,
|
|
||||||
fileName: str,
|
|
||||||
delay: Optional[int] = None,
|
|
||||||
quoted: Optional[QuotedMessage] = None,
|
|
||||||
mentionsEveryOne: Optional[bool] = None,
|
|
||||||
mentioned: Optional[List[str]] = None
|
|
||||||
):
|
|
||||||
super().__init__(
|
|
||||||
number=number,
|
|
||||||
mediatype=mediatype,
|
|
||||||
mimetype=mimetype,
|
|
||||||
caption=caption,
|
|
||||||
media=media,
|
|
||||||
fileName=fileName,
|
|
||||||
delay=delay,
|
|
||||||
quoted=quoted.__dict__ if quoted else None,
|
|
||||||
mentionsEveryOne=mentionsEveryOne,
|
|
||||||
mentioned=mentioned
|
|
||||||
)
|
|
||||||
|
|
||||||
class StatusMessage(BaseMessage):
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
type: StatusType,
|
|
||||||
content: str,
|
|
||||||
caption: Optional[str] = None,
|
|
||||||
backgroundColor: Optional[str] = None,
|
|
||||||
font: Optional[FontType] = None,
|
|
||||||
allContacts: bool = False,
|
|
||||||
statusJidList: Optional[List[str]] = None
|
|
||||||
):
|
|
||||||
super().__init__(
|
|
||||||
type=type.value,
|
|
||||||
content=content,
|
|
||||||
caption=caption,
|
|
||||||
backgroundColor=backgroundColor,
|
|
||||||
font=font.value if font else None,
|
|
||||||
allContacts=allContacts,
|
|
||||||
statusJidList=statusJidList
|
|
||||||
)
|
|
||||||
|
|
||||||
class LocationMessage(BaseMessage):
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
number: str,
|
|
||||||
name: str,
|
|
||||||
address: str,
|
|
||||||
latitude: float,
|
|
||||||
longitude: float,
|
|
||||||
delay: Optional[int] = None,
|
|
||||||
quoted: Optional[QuotedMessage] = None
|
|
||||||
):
|
|
||||||
super().__init__(
|
|
||||||
number=number,
|
|
||||||
name=name,
|
|
||||||
address=address,
|
|
||||||
latitude=latitude,
|
|
||||||
longitude=longitude,
|
|
||||||
delay=delay,
|
|
||||||
quoted=quoted.__dict__ if quoted else None
|
|
||||||
)
|
|
||||||
|
|
||||||
class Contact(BaseMessage):
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
fullName: str,
|
|
||||||
wuid: str,
|
|
||||||
phoneNumber: str,
|
|
||||||
organization: Optional[str] = None,
|
|
||||||
email: Optional[str] = None,
|
|
||||||
url: Optional[str] = None
|
|
||||||
):
|
|
||||||
super().__init__(
|
|
||||||
fullName=fullName,
|
|
||||||
wuid=wuid,
|
|
||||||
phoneNumber=phoneNumber,
|
|
||||||
organization=organization,
|
|
||||||
email=email,
|
|
||||||
url=url
|
|
||||||
)
|
|
||||||
|
|
||||||
class ContactMessage(BaseMessage):
|
|
||||||
def __init__(self, number: str, contact: List[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,
|
|
||||||
number: str,
|
|
||||||
name: str,
|
|
||||||
selectableCount: int,
|
|
||||||
values: List[str],
|
|
||||||
delay: Optional[int] = None,
|
|
||||||
quoted: Optional[QuotedMessage] = None
|
|
||||||
):
|
|
||||||
super().__init__(
|
|
||||||
number=number,
|
|
||||||
name=name,
|
|
||||||
selectableCount=selectableCount,
|
|
||||||
values=values,
|
|
||||||
delay=delay,
|
|
||||||
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
|
|
||||||
)
|
|
||||||
|
|
||||||
class ListSection(BaseMessage):
|
|
||||||
def __init__(self, title: str, rows: List[ListRow]):
|
|
||||||
super().__init__(
|
|
||||||
title=title,
|
|
||||||
rows=[r.__dict__ for r in rows]
|
|
||||||
)
|
|
||||||
|
|
||||||
class ListMessage(BaseMessage):
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
number: str,
|
|
||||||
title: str,
|
|
||||||
description: str,
|
|
||||||
buttonText: str,
|
|
||||||
footerText: str,
|
|
||||||
sections: List[ListSection],
|
|
||||||
delay: Optional[int] = None,
|
|
||||||
quoted: Optional[QuotedMessage] = None
|
|
||||||
):
|
|
||||||
super().__init__(
|
|
||||||
number=number,
|
|
||||||
title=title,
|
|
||||||
description=description,
|
|
||||||
buttonText=buttonText,
|
|
||||||
footerText=footerText,
|
|
||||||
sections=[s.__dict__ for s in sections],
|
|
||||||
delay=delay,
|
|
||||||
quoted=quoted.__dict__ if quoted else None
|
|
||||||
)
|
|
||||||
|
|
||||||
class Button(BaseMessage):
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
type: str,
|
|
||||||
displayText: str,
|
|
||||||
id: Optional[str] = None,
|
|
||||||
copyCode: Optional[str] = None,
|
|
||||||
url: Optional[str] = None,
|
|
||||||
phoneNumber: Optional[str] = None,
|
|
||||||
currency: Optional[str] = None,
|
|
||||||
name: Optional[str] = None,
|
|
||||||
keyType: Optional[str] = None,
|
|
||||||
key: Optional[str] = None
|
|
||||||
):
|
|
||||||
super().__init__(
|
|
||||||
type=type,
|
|
||||||
displayText=displayText,
|
|
||||||
id=id,
|
|
||||||
copyCode=copyCode,
|
|
||||||
url=url,
|
|
||||||
phoneNumber=phoneNumber,
|
|
||||||
currency=currency,
|
|
||||||
name=name,
|
|
||||||
keyType=keyType,
|
|
||||||
key=key
|
|
||||||
)
|
|
||||||
|
|
||||||
class ButtonMessage(BaseMessage):
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
number: str,
|
|
||||||
title: str,
|
|
||||||
description: str,
|
|
||||||
footer: str,
|
|
||||||
buttons: List[Button],
|
|
||||||
delay: Optional[int] = None,
|
|
||||||
quoted: Optional[QuotedMessage] = None
|
|
||||||
):
|
|
||||||
super().__init__(
|
|
||||||
number=number,
|
|
||||||
title=title,
|
|
||||||
description=description,
|
|
||||||
footer=footer,
|
|
||||||
buttons=[b.__dict__ for b in buttons],
|
|
||||||
delay=delay,
|
|
||||||
quoted=quoted.__dict__ if quoted else None
|
|
||||||
)
|
|
||||||
@ -1,9 +0,0 @@
|
|||||||
from enum import Enum
|
|
||||||
|
|
||||||
class PresenceStatus(Enum):
|
|
||||||
AVAILABLE = "available"
|
|
||||||
UNAVAILABLE = "unavailable"
|
|
||||||
|
|
||||||
class PresenceConfig:
|
|
||||||
def __init__(self, presence: PresenceStatus):
|
|
||||||
self.presence = presence.value
|
|
||||||
@ -1,60 +0,0 @@
|
|||||||
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,
|
|
||||||
number: str,
|
|
||||||
):
|
|
||||||
super().__init__(
|
|
||||||
number=number,
|
|
||||||
)
|
|
||||||
|
|
||||||
class ProfileName(BaseProfile):
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
name: str,
|
|
||||||
):
|
|
||||||
super().__init__(
|
|
||||||
name=name,
|
|
||||||
)
|
|
||||||
|
|
||||||
class ProfileStatus(BaseProfile):
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
status: str,
|
|
||||||
):
|
|
||||||
super().__init__(
|
|
||||||
status=status,
|
|
||||||
)
|
|
||||||
|
|
||||||
class ProfilePicture(BaseProfile):
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
picture: str,
|
|
||||||
):
|
|
||||||
super().__init__(
|
|
||||||
picture=picture,
|
|
||||||
)
|
|
||||||
|
|
||||||
class PrivacySettings(BaseProfile):
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
readreceipts: Literal["all", "none"],
|
|
||||||
profile: Literal["all", "contacts", "contact_blacklist", "none"],
|
|
||||||
status: Literal["all", "contacts", "contact_blacklist", "none"],
|
|
||||||
online: Literal["all", "match_last_seen"],
|
|
||||||
last: Literal["all", "contacts", "contact_blacklist", "none"],
|
|
||||||
groupadd: Literal["all", "contacts", "contact_blacklist"],
|
|
||||||
):
|
|
||||||
super().__init__(
|
|
||||||
readreceipts=readreceipts,
|
|
||||||
profile=profile,
|
|
||||||
status=status,
|
|
||||||
online=online,
|
|
||||||
last=last,
|
|
||||||
groupadd=groupadd,
|
|
||||||
)
|
|
||||||
@ -1,13 +0,0 @@
|
|||||||
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
|
|
||||||
)
|
|
||||||
@ -1,69 +0,0 @@
|
|||||||
from typing import Union, BinaryIO
|
|
||||||
from ..models.chat import *
|
|
||||||
|
|
||||||
class ChatService:
|
|
||||||
def __init__(self, client):
|
|
||||||
self.client = client
|
|
||||||
|
|
||||||
def check_is_whatsapp_numbers(self, instance_id: str, data: CheckIsWhatsappNumber, instance_token: str):
|
|
||||||
return self.client.post(
|
|
||||||
f'chat/checkIsWhatsappNumber/{instance_id}',
|
|
||||||
data=data.__dict__,
|
|
||||||
instance_token=instance_token
|
|
||||||
)
|
|
||||||
|
|
||||||
def mark_message_as_read(self, instance_id: str, messages: List[ReadMessage], instance_token: str):
|
|
||||||
return self.client.post(
|
|
||||||
f'chat/markMessageAsRead/{instance_id}',
|
|
||||||
data={"readMessages": [m.__dict__ for m in messages]},
|
|
||||||
instance_token=instance_token
|
|
||||||
)
|
|
||||||
|
|
||||||
def archive_chat(self, instance_id: str, data: ArchiveChat, instance_token: str):
|
|
||||||
return self.client.post(
|
|
||||||
f'chat/archiveChat/{instance_id}',
|
|
||||||
data=data.__dict__,
|
|
||||||
instance_token=instance_token
|
|
||||||
)
|
|
||||||
|
|
||||||
def mark_chat_unread(self, instance_id: str, data: UnreadChat, instance_token: str):
|
|
||||||
return self.client.post(
|
|
||||||
f'chat/markChatUnread/{instance_id}',
|
|
||||||
data=data.__dict__,
|
|
||||||
instance_token=instance_token
|
|
||||||
)
|
|
||||||
|
|
||||||
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__,
|
|
||||||
instance_token=instance_token
|
|
||||||
)
|
|
||||||
|
|
||||||
def fetch_profile_picture_url(self, instance_id: str, data: ProfilePicture, instance_token: str):
|
|
||||||
return self.client.post(
|
|
||||||
f'chat/fetchProfilePictureUrl/{instance_id}',
|
|
||||||
data=data.__dict__,
|
|
||||||
instance_token=instance_token
|
|
||||||
)
|
|
||||||
|
|
||||||
def get_base64_from_media_message(self, instance_id: str, data: MediaMessage, instance_token: str):
|
|
||||||
return self.client.post(
|
|
||||||
f'chat/getBase64FromMediaMessage/{instance_id}',
|
|
||||||
data=data.__dict__,
|
|
||||||
instance_token=instance_token
|
|
||||||
)
|
|
||||||
|
|
||||||
def update_message(self, instance_id: str, data: UpdateMessage, instance_token: str):
|
|
||||||
return self.client.post(
|
|
||||||
f'chat/updateMessage/{instance_id}',
|
|
||||||
data=data.__dict__,
|
|
||||||
instance_token=instance_token
|
|
||||||
)
|
|
||||||
|
|
||||||
def send_presence(self, instance_id: str, data: Presence, instance_token: str):
|
|
||||||
return self.client.post(
|
|
||||||
f'chat/sendPresence/{instance_id}',
|
|
||||||
data=data.__dict__,
|
|
||||||
instance_token=instance_token
|
|
||||||
)
|
|
||||||
@ -1,117 +0,0 @@
|
|||||||
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
|
|
||||||
)
|
|
||||||
|
|
||||||
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}',
|
|
||||||
params={'groupJid': group_jid},
|
|
||||||
data=data.__dict__,
|
|
||||||
instance_token=instance_token
|
|
||||||
)
|
|
||||||
|
|
||||||
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}',
|
|
||||||
params={'groupJid': group_jid},
|
|
||||||
data=data.__dict__,
|
|
||||||
instance_token=instance_token
|
|
||||||
)
|
|
||||||
|
|
||||||
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}',
|
|
||||||
params={'groupJid': group_jid},
|
|
||||||
data=data.__dict__,
|
|
||||||
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}',
|
|
||||||
params={'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}',
|
|
||||||
params={'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
|
|
||||||
)
|
|
||||||
|
|
||||||
def get_invite_info(self, instance_id: str, invite_code: str, instance_token: str):
|
|
||||||
return self.client.get(
|
|
||||||
f'group/inviteInfo/{instance_id}',
|
|
||||||
params={'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}',
|
|
||||||
params={'groupJid': group_jid},
|
|
||||||
instance_token=instance_token
|
|
||||||
)
|
|
||||||
|
|
||||||
def fetch_all_groups(self, instance_id: str, instance_token: str, get_participants: bool = False):
|
|
||||||
return self.client.get(
|
|
||||||
f'group/fetchAllGroups/{instance_id}',
|
|
||||||
params={'getParticipants': get_participants},
|
|
||||||
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}',
|
|
||||||
params={'groupJid': group_jid},
|
|
||||||
instance_token=instance_token
|
|
||||||
)
|
|
||||||
|
|
||||||
def update_participant(self, instance_id: str, group_jid: str, data: UpdateParticipant, instance_token: str):
|
|
||||||
return self.client.post(
|
|
||||||
f'group/updateParticipant/{instance_id}',
|
|
||||||
params={'groupJid': group_jid},
|
|
||||||
data=data.__dict__,
|
|
||||||
instance_token=instance_token
|
|
||||||
)
|
|
||||||
|
|
||||||
def update_setting(self, instance_id: str, group_jid: str, data: UpdateSetting, instance_token: str):
|
|
||||||
return self.client.post(
|
|
||||||
f'group/updateSetting/{instance_id}',
|
|
||||||
params={'groupJid': group_jid},
|
|
||||||
data=data.__dict__,
|
|
||||||
instance_token=instance_token
|
|
||||||
)
|
|
||||||
|
|
||||||
def toggle_ephemeral(self, instance_id: str, group_jid: str, data: ToggleEphemeral, instance_token: str):
|
|
||||||
return self.client.post(
|
|
||||||
f'group/toggleEphemeral/{instance_id}',
|
|
||||||
params={'groupJid': group_jid},
|
|
||||||
data=data.__dict__,
|
|
||||||
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}',
|
|
||||||
params={'groupJid': group_jid},
|
|
||||||
instance_token=instance_token
|
|
||||||
)
|
|
||||||
@ -1,9 +0,0 @@
|
|||||||
class InstanceService:
|
|
||||||
def __init__(self, client):
|
|
||||||
self.client = client
|
|
||||||
|
|
||||||
def fetch_instances(self):
|
|
||||||
return self.client.get('instance/fetchInstances')
|
|
||||||
|
|
||||||
def create_instance(self, config):
|
|
||||||
return self.client.post('instance/create', data=config.__dict__)
|
|
||||||
@ -1,28 +0,0 @@
|
|||||||
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)
|
|
||||||
|
|
||||||
def restart(self, instance_id: str, instance_token: str):
|
|
||||||
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}',
|
|
||||||
data=config.__dict__,
|
|
||||||
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)
|
|
||||||
|
|
||||||
def logout(self, instance_id: str, instance_token: str):
|
|
||||||
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)
|
|
||||||
@ -1,19 +0,0 @@
|
|||||||
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
|
|
||||||
)
|
|
||||||
|
|
||||||
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
|
|
||||||
)
|
|
||||||
@ -1,111 +0,0 @@
|
|||||||
from typing import Union, BinaryIO
|
|
||||||
from ..models.message import *
|
|
||||||
|
|
||||||
class MessageService:
|
|
||||||
def __init__(self, client):
|
|
||||||
self.client = client
|
|
||||||
|
|
||||||
def send_text(self, instance_id: str, message: TextMessage, instance_token: str):
|
|
||||||
return self.client.post(
|
|
||||||
f'message/sendText/{instance_id}',
|
|
||||||
data=message.__dict__,
|
|
||||||
instance_token=instance_token
|
|
||||||
)
|
|
||||||
|
|
||||||
def send_media(self, instance_id: str, message: MediaMessage, instance_token: str, file: BinaryIO = None):
|
|
||||||
payload = {
|
|
||||||
'data': message.__dict__,
|
|
||||||
'instance_token': instance_token
|
|
||||||
}
|
|
||||||
|
|
||||||
if file:
|
|
||||||
payload['files'] = {'file': file}
|
|
||||||
|
|
||||||
return self.client.post(
|
|
||||||
f'message/sendMedia/{instance_id}',
|
|
||||||
**payload
|
|
||||||
)
|
|
||||||
|
|
||||||
def send_ptv(self, instance_id: str, message: dict, instance_token: str, file: BinaryIO = None):
|
|
||||||
payload = {
|
|
||||||
'data': message,
|
|
||||||
'instance_token': instance_token
|
|
||||||
}
|
|
||||||
|
|
||||||
if file:
|
|
||||||
payload['files'] = {'file': file}
|
|
||||||
|
|
||||||
return self.client.post(
|
|
||||||
f'message/sendPtv/{instance_id}',
|
|
||||||
**payload
|
|
||||||
)
|
|
||||||
|
|
||||||
def send_whatsapp_audio(self, instance_id: str, message: dict, instance_token: str, file: BinaryIO = None):
|
|
||||||
payload = {
|
|
||||||
'data': message,
|
|
||||||
'instance_token': instance_token
|
|
||||||
}
|
|
||||||
|
|
||||||
if file:
|
|
||||||
payload['files'] = {'file': file}
|
|
||||||
|
|
||||||
return self.client.post(
|
|
||||||
f'message/sendWhatsAppAudio/{instance_id}',
|
|
||||||
**payload
|
|
||||||
)
|
|
||||||
|
|
||||||
def send_status(self, instance_id: str, message: StatusMessage, instance_token: str):
|
|
||||||
return self.client.post(
|
|
||||||
f'message/sendStatus/{instance_id}',
|
|
||||||
data=message.__dict__,
|
|
||||||
instance_token=instance_token
|
|
||||||
)
|
|
||||||
|
|
||||||
def send_sticker(self, instance_id: str, message: dict, instance_token: str):
|
|
||||||
return self.client.post(
|
|
||||||
f'message/sendSticker/{instance_id}',
|
|
||||||
data=message,
|
|
||||||
instance_token=instance_token
|
|
||||||
)
|
|
||||||
|
|
||||||
def send_location(self, instance_id: str, message: LocationMessage, instance_token: str):
|
|
||||||
return self.client.post(
|
|
||||||
f'message/sendLocation/{instance_id}',
|
|
||||||
data=message.__dict__,
|
|
||||||
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}',
|
|
||||||
data=message.__dict__,
|
|
||||||
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}',
|
|
||||||
data=message.__dict__,
|
|
||||||
instance_token=instance_token
|
|
||||||
)
|
|
||||||
|
|
||||||
def send_poll(self, instance_id: str, message: PollMessage, instance_token: str):
|
|
||||||
return self.client.post(
|
|
||||||
f'message/sendPoll/{instance_id}',
|
|
||||||
data=message.__dict__,
|
|
||||||
instance_token=instance_token
|
|
||||||
)
|
|
||||||
|
|
||||||
def send_list(self, instance_id: str, message: ListMessage, instance_token: str):
|
|
||||||
return self.client.post(
|
|
||||||
f'message/sendList/{instance_id}',
|
|
||||||
data=message.__dict__,
|
|
||||||
instance_token=instance_token
|
|
||||||
)
|
|
||||||
|
|
||||||
def send_buttons(self, instance_id: str, message: ButtonMessage, instance_token: str):
|
|
||||||
return self.client.post(
|
|
||||||
f'message/sendButtons/{instance_id}',
|
|
||||||
data=message.__dict__,
|
|
||||||
instance_token=instance_token
|
|
||||||
)
|
|
||||||
@ -1,60 +0,0 @@
|
|||||||
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}',
|
|
||||||
data=data.__dict__,
|
|
||||||
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
|
|
||||||
)
|
|
||||||
|
|
||||||
def update_profile_name(self, instance_id: str, data: ProfileName, instance_token: str):
|
|
||||||
return self.client.post(
|
|
||||||
f'chat/updateProfileName/{instance_id}',
|
|
||||||
data=data.__dict__,
|
|
||||||
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}',
|
|
||||||
data=data.__dict__,
|
|
||||||
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}',
|
|
||||||
data=data.__dict__,
|
|
||||||
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
|
|
||||||
)
|
|
||||||
|
|
||||||
def fetch_privacy_settings(self, instance_id: str, instance_token: str):
|
|
||||||
return self.client.get(
|
|
||||||
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}',
|
|
||||||
data=data.__dict__,
|
|
||||||
instance_token=instance_token
|
|
||||||
)
|
|
||||||
@ -1,138 +0,0 @@
|
|||||||
import requests
|
|
||||||
from requests_toolbelt import MultipartEncoder
|
|
||||||
from .exceptions import EvolutionAuthenticationError, EvolutionNotFoundError, EvolutionAPIError
|
|
||||||
from .services.instance import InstanceService
|
|
||||||
from .services.instance_operations import InstanceOperationsService
|
|
||||||
from .services.message import MessageService
|
|
||||||
from .services.call import CallService
|
|
||||||
from .services.chat import ChatService
|
|
||||||
from .services.label import LabelService
|
|
||||||
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.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
base_url (str): A URL base do servidor da API Evolution.
|
|
||||||
api_token (str): O token de autenticação para acessar a API.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, base_url: str, api_token: str):
|
|
||||||
self.base_url = base_url.rstrip('/')
|
|
||||||
self.api_token = api_token
|
|
||||||
self.instances = InstanceService(self)
|
|
||||||
self.instance_operations = InstanceOperationsService(self)
|
|
||||||
self.messages = MessageService(self)
|
|
||||||
self.calls = CallService(self)
|
|
||||||
self.chat = ChatService(self)
|
|
||||||
self.label = LabelService(self)
|
|
||||||
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'
|
|
||||||
}
|
|
||||||
|
|
||||||
def _get_full_url(self, endpoint):
|
|
||||||
return f'{self.base_url}/{endpoint}'
|
|
||||||
|
|
||||||
def _handle_response(self, response):
|
|
||||||
if response.status_code == 401:
|
|
||||||
raise EvolutionAuthenticationError('Falha na autenticação.')
|
|
||||||
elif response.status_code == 404:
|
|
||||||
raise EvolutionNotFoundError('Recurso não encontrado.')
|
|
||||||
elif response.ok:
|
|
||||||
try:
|
|
||||||
return response.json()
|
|
||||||
except ValueError:
|
|
||||||
return response.content
|
|
||||||
else:
|
|
||||||
error_detail = ''
|
|
||||||
try:
|
|
||||||
error_detail = f' - {response.json()}'
|
|
||||||
except:
|
|
||||||
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."""
|
|
||||||
url = self._get_full_url(endpoint)
|
|
||||||
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}'
|
|
||||||
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']
|
|
||||||
|
|
||||||
# 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')
|
|
||||||
|
|
||||||
# Adiciona o arquivo
|
|
||||||
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
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
response = requests.post(
|
|
||||||
url,
|
|
||||||
headers=headers,
|
|
||||||
json=data
|
|
||||||
)
|
|
||||||
|
|
||||||
return response.json()
|
|
||||||
|
|
||||||
def put(self, endpoint, data=None):
|
|
||||||
"""Faz uma requisição PUT."""
|
|
||||||
url = self._get_full_url(endpoint)
|
|
||||||
response = requests.put(url, headers=self.headers, json=data)
|
|
||||||
return self._handle_response(response)
|
|
||||||
|
|
||||||
def delete(self, endpoint: str, instance_token: str = None):
|
|
||||||
"""Faz uma requisição DELETE."""
|
|
||||||
url = self._get_full_url(endpoint)
|
|
||||||
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:
|
|
||||||
"""
|
|
||||||
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
|
|
||||||
"""
|
|
||||||
return WebSocketManager(
|
|
||||||
base_url=self.base_url,
|
|
||||||
instance_id=instance_id,
|
|
||||||
api_token=api_token,
|
|
||||||
max_retries=max_retries,
|
|
||||||
retry_delay=retry_delay
|
|
||||||
)
|
|
||||||
@ -1,11 +0,0 @@
|
|||||||
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
|
|
||||||
@ -1,16 +0,0 @@
|
|||||||
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
|
|
||||||
)
|
|
||||||
@ -1,93 +0,0 @@
|
|||||||
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
|
|
||||||
)
|
|
||||||
|
|
||||||
class MessageKey:
|
|
||||||
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
|
|
||||||
):
|
|
||||||
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
|
|
||||||
):
|
|
||||||
self.lastMessage = last_message
|
|
||||||
self.chat = chat
|
|
||||||
self.archive = archive
|
|
||||||
|
|
||||||
class UnreadChat:
|
|
||||||
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
|
|
||||||
):
|
|
||||||
self.message = message
|
|
||||||
self.convertToMp4 = convert_to_mp4
|
|
||||||
|
|
||||||
class UpdateMessage:
|
|
||||||
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
|
|
||||||
):
|
|
||||||
self.number = number
|
|
||||||
self.delay = delay
|
|
||||||
self.presence = presence
|
|
||||||
@ -1,39 +0,0 @@
|
|||||||
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
|
|
||||||
@ -1,59 +0,0 @@
|
|||||||
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):
|
|
||||||
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"):
|
|
||||||
self.chatwootAccountId = accountId
|
|
||||||
self.chatwootToken = token
|
|
||||||
self.chatwootUrl = url
|
|
||||||
self.chatwootSignMsg = signMsg
|
|
||||||
self.chatwootReopenConversation = reopenConversation
|
|
||||||
self.chatwootConversationPending = conversationPending
|
|
||||||
self.chatwootImportContacts = importContacts
|
|
||||||
self.chatwootNameInbox = nameInbox
|
|
||||||
self.chatwootMergeBrazilContacts = mergeBrazilContacts
|
|
||||||
self.chatwootImportMessages = importMessages
|
|
||||||
self.chatwootDaysLimitImportMessages = daysLimitImportMessages
|
|
||||||
self.chatwootOrganization = organization
|
|
||||||
self.chatwootLogo = logo
|
|
||||||
|
|
||||||
class InstanceConfig:
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
instanceName: str,
|
|
||||||
integration: str = None,
|
|
||||||
token: str = None,
|
|
||||||
number: str = None,
|
|
||||||
qrcode: bool = None,
|
|
||||||
rejectCall: bool = None,
|
|
||||||
msgCall: str = None,
|
|
||||||
groupsIgnore: bool = None,
|
|
||||||
alwaysOnline: bool = None,
|
|
||||||
readMessages: bool = None,
|
|
||||||
readStatus: bool = None,
|
|
||||||
syncFullHistory: bool = None
|
|
||||||
):
|
|
||||||
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
|
|
||||||
@ -1,21 +0,0 @@
|
|||||||
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"]
|
|
||||||
):
|
|
||||||
if action not in ["add", "remove"]:
|
|
||||||
raise ValueError("action deve ser 'add' ou 'remove'")
|
|
||||||
|
|
||||||
super().__init__(
|
|
||||||
number=number,
|
|
||||||
labelId=label_id,
|
|
||||||
action=action
|
|
||||||
)
|
|
||||||
@ -1,260 +0,0 @@
|
|||||||
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
|
|
||||||
BRYNDAN_WRITE = 3
|
|
||||||
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,
|
|
||||||
number: str,
|
|
||||||
text: str,
|
|
||||||
delay: Optional[int] = None,
|
|
||||||
quoted: Optional[QuotedMessage] = None,
|
|
||||||
linkPreview: Optional[bool] = None,
|
|
||||||
mentionsEveryOne: Optional[bool] = None,
|
|
||||||
mentioned: Optional[List[str]] = None
|
|
||||||
):
|
|
||||||
super().__init__(
|
|
||||||
number=number,
|
|
||||||
text=text,
|
|
||||||
delay=delay,
|
|
||||||
quoted=quoted.__dict__ if quoted else None,
|
|
||||||
linkPreview=linkPreview,
|
|
||||||
mentionsEveryOne=mentionsEveryOne,
|
|
||||||
mentioned=mentioned
|
|
||||||
)
|
|
||||||
|
|
||||||
class MediaMessage(BaseMessage):
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
number: str,
|
|
||||||
media: dict = None,
|
|
||||||
mediatype: Optional[str] = None,
|
|
||||||
caption: str = None,
|
|
||||||
mimetype: str = None,
|
|
||||||
fileName: str = None,
|
|
||||||
delay: Optional[Union[int, float, str]] = None,
|
|
||||||
quoted: Optional[QuotedMessage] = None,
|
|
||||||
mentionsEveryOne: Optional[bool] = 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
|
|
||||||
}
|
|
||||||
|
|
||||||
if delay is not None:
|
|
||||||
data['delay'] = delay
|
|
||||||
|
|
||||||
if media and 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,
|
|
||||||
type: StatusType,
|
|
||||||
content: str,
|
|
||||||
caption: Optional[str] = None,
|
|
||||||
backgroundColor: Optional[str] = None,
|
|
||||||
font: Optional[FontType] = None,
|
|
||||||
allContacts: bool = False,
|
|
||||||
statusJidList: Optional[List[str]] = None
|
|
||||||
):
|
|
||||||
super().__init__(
|
|
||||||
type=type.value,
|
|
||||||
content=content,
|
|
||||||
caption=caption,
|
|
||||||
backgroundColor=backgroundColor,
|
|
||||||
font=font.value if font else None,
|
|
||||||
allContacts=allContacts,
|
|
||||||
statusJidList=statusJidList
|
|
||||||
)
|
|
||||||
|
|
||||||
class LocationMessage(BaseMessage):
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
number: str,
|
|
||||||
name: str,
|
|
||||||
address: str,
|
|
||||||
latitude: float,
|
|
||||||
longitude: float,
|
|
||||||
delay: Optional[int] = None,
|
|
||||||
quoted: Optional[QuotedMessage] = None
|
|
||||||
):
|
|
||||||
super().__init__(
|
|
||||||
number=number,
|
|
||||||
name=name,
|
|
||||||
address=address,
|
|
||||||
latitude=latitude,
|
|
||||||
longitude=longitude,
|
|
||||||
delay=delay,
|
|
||||||
quoted=quoted.__dict__ if quoted else None
|
|
||||||
)
|
|
||||||
|
|
||||||
class Contact(BaseMessage):
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
fullName: str,
|
|
||||||
wuid: str,
|
|
||||||
phoneNumber: str,
|
|
||||||
organization: Optional[str] = None,
|
|
||||||
email: Optional[str] = None,
|
|
||||||
url: Optional[str] = None
|
|
||||||
):
|
|
||||||
super().__init__(
|
|
||||||
fullName=fullName,
|
|
||||||
wuid=wuid,
|
|
||||||
phoneNumber=phoneNumber,
|
|
||||||
organization=organization,
|
|
||||||
email=email,
|
|
||||||
url=url
|
|
||||||
)
|
|
||||||
|
|
||||||
class ContactMessage(BaseMessage):
|
|
||||||
def __init__(self, number: str, contact: List[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,
|
|
||||||
number: str,
|
|
||||||
name: str,
|
|
||||||
selectableCount: int,
|
|
||||||
values: List[str],
|
|
||||||
delay: Optional[int] = None,
|
|
||||||
quoted: Optional[QuotedMessage] = None
|
|
||||||
):
|
|
||||||
super().__init__(
|
|
||||||
number=number,
|
|
||||||
name=name,
|
|
||||||
selectableCount=selectableCount,
|
|
||||||
values=values,
|
|
||||||
delay=delay,
|
|
||||||
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
|
|
||||||
)
|
|
||||||
|
|
||||||
class ListSection(BaseMessage):
|
|
||||||
def __init__(self, title: str, rows: List[ListRow]):
|
|
||||||
super().__init__(
|
|
||||||
title=title,
|
|
||||||
rows=[r.__dict__ for r in rows]
|
|
||||||
)
|
|
||||||
|
|
||||||
class ListMessage(BaseMessage):
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
number: str,
|
|
||||||
title: str,
|
|
||||||
description: str,
|
|
||||||
buttonText: str,
|
|
||||||
footerText: str,
|
|
||||||
sections: List[ListSection],
|
|
||||||
delay: Optional[int] = None,
|
|
||||||
quoted: Optional[QuotedMessage] = None
|
|
||||||
):
|
|
||||||
super().__init__(
|
|
||||||
number=number,
|
|
||||||
title=title,
|
|
||||||
description=description,
|
|
||||||
buttonText=buttonText,
|
|
||||||
footerText=footerText,
|
|
||||||
sections=[s.__dict__ for s in sections],
|
|
||||||
delay=delay,
|
|
||||||
quoted=quoted.__dict__ if quoted else None
|
|
||||||
)
|
|
||||||
|
|
||||||
class Button(BaseMessage):
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
type: str,
|
|
||||||
displayText: str,
|
|
||||||
id: Optional[str] = None,
|
|
||||||
copyCode: Optional[str] = None,
|
|
||||||
url: Optional[str] = None,
|
|
||||||
phoneNumber: Optional[str] = None,
|
|
||||||
currency: Optional[str] = None,
|
|
||||||
name: Optional[str] = None,
|
|
||||||
keyType: Optional[str] = None,
|
|
||||||
key: Optional[str] = None
|
|
||||||
):
|
|
||||||
super().__init__(
|
|
||||||
type=type,
|
|
||||||
displayText=displayText,
|
|
||||||
id=id,
|
|
||||||
copyCode=copyCode,
|
|
||||||
url=url,
|
|
||||||
phoneNumber=phoneNumber,
|
|
||||||
currency=currency,
|
|
||||||
name=name,
|
|
||||||
keyType=keyType,
|
|
||||||
key=key
|
|
||||||
)
|
|
||||||
|
|
||||||
class ButtonMessage(BaseMessage):
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
number: str,
|
|
||||||
title: str,
|
|
||||||
description: str,
|
|
||||||
footer: str,
|
|
||||||
buttons: List[Button],
|
|
||||||
delay: Optional[int] = None,
|
|
||||||
quoted: Optional[QuotedMessage] = None
|
|
||||||
):
|
|
||||||
super().__init__(
|
|
||||||
number=number,
|
|
||||||
title=title,
|
|
||||||
description=description,
|
|
||||||
footer=footer,
|
|
||||||
buttons=[b.__dict__ for b in buttons],
|
|
||||||
delay=delay,
|
|
||||||
quoted=quoted.__dict__ if quoted else None
|
|
||||||
)
|
|
||||||
@ -1,9 +0,0 @@
|
|||||||
from enum import Enum
|
|
||||||
|
|
||||||
class PresenceStatus(Enum):
|
|
||||||
AVAILABLE = "available"
|
|
||||||
UNAVAILABLE = "unavailable"
|
|
||||||
|
|
||||||
class PresenceConfig:
|
|
||||||
def __init__(self, presence: PresenceStatus):
|
|
||||||
self.presence = presence.value
|
|
||||||
@ -1,60 +0,0 @@
|
|||||||
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,
|
|
||||||
number: str,
|
|
||||||
):
|
|
||||||
super().__init__(
|
|
||||||
number=number,
|
|
||||||
)
|
|
||||||
|
|
||||||
class ProfileName(BaseProfile):
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
name: str,
|
|
||||||
):
|
|
||||||
super().__init__(
|
|
||||||
name=name,
|
|
||||||
)
|
|
||||||
|
|
||||||
class ProfileStatus(BaseProfile):
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
status: str,
|
|
||||||
):
|
|
||||||
super().__init__(
|
|
||||||
status=status,
|
|
||||||
)
|
|
||||||
|
|
||||||
class ProfilePicture(BaseProfile):
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
picture: str,
|
|
||||||
):
|
|
||||||
super().__init__(
|
|
||||||
picture=picture,
|
|
||||||
)
|
|
||||||
|
|
||||||
class PrivacySettings(BaseProfile):
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
readreceipts: Literal["all", "none"],
|
|
||||||
profile: Literal["all", "contacts", "contact_blacklist", "none"],
|
|
||||||
status: Literal["all", "contacts", "contact_blacklist", "none"],
|
|
||||||
online: Literal["all", "match_last_seen"],
|
|
||||||
last: Literal["all", "contacts", "contact_blacklist", "none"],
|
|
||||||
groupadd: Literal["all", "contacts", "contact_blacklist"],
|
|
||||||
):
|
|
||||||
super().__init__(
|
|
||||||
readreceipts=readreceipts,
|
|
||||||
profile=profile,
|
|
||||||
status=status,
|
|
||||||
online=online,
|
|
||||||
last=last,
|
|
||||||
groupadd=groupadd,
|
|
||||||
)
|
|
||||||
@ -1,20 +0,0 @@
|
|||||||
from typing import List, Optional
|
|
||||||
from dataclasses import dataclass
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class WebSocketConfig:
|
|
||||||
enabled: bool
|
|
||||||
events: List[str]
|
|
||||||
|
|
||||||
def __init__(self, enabled: bool, events: List[str]):
|
|
||||||
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', [])
|
|
||||||
@ -1,13 +0,0 @@
|
|||||||
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
|
|
||||||
)
|
|
||||||
@ -1,122 +0,0 @@
|
|||||||
from typing import Union, BinaryIO, Optional
|
|
||||||
from ..models.chat import *
|
|
||||||
|
|
||||||
class ChatService:
|
|
||||||
def __init__(self, client):
|
|
||||||
self.client = client
|
|
||||||
|
|
||||||
def check_is_whatsapp_numbers(self, instance_id: str, data: CheckIsWhatsappNumber, instance_token: str):
|
|
||||||
return self.client.post(
|
|
||||||
f'chat/checkIsWhatsappNumber/{instance_id}',
|
|
||||||
data=data.__dict__,
|
|
||||||
instance_token=instance_token
|
|
||||||
)
|
|
||||||
|
|
||||||
def mark_message_as_read(self, instance_id: str, messages: List[ReadMessage], instance_token: str):
|
|
||||||
return self.client.post(
|
|
||||||
f'chat/markMessageAsRead/{instance_id}',
|
|
||||||
data={"readMessages": [m.__dict__ for m in messages]},
|
|
||||||
instance_token=instance_token
|
|
||||||
)
|
|
||||||
|
|
||||||
def archive_chat(self, instance_id: str, data: ArchiveChat, instance_token: str):
|
|
||||||
return self.client.post(
|
|
||||||
f'chat/archiveChat/{instance_id}',
|
|
||||||
data=data.__dict__,
|
|
||||||
instance_token=instance_token
|
|
||||||
)
|
|
||||||
|
|
||||||
def mark_chat_unread(self, instance_id: str, data: UnreadChat, instance_token: str):
|
|
||||||
return self.client.post(
|
|
||||||
f'chat/markChatUnread/{instance_id}',
|
|
||||||
data=data.__dict__,
|
|
||||||
instance_token=instance_token
|
|
||||||
)
|
|
||||||
|
|
||||||
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__,
|
|
||||||
instance_token=instance_token
|
|
||||||
)
|
|
||||||
|
|
||||||
def fetch_profile_picture_url(self, instance_id: str, data: ProfilePicture, instance_token: str):
|
|
||||||
return self.client.post(
|
|
||||||
f'chat/fetchProfilePictureUrl/{instance_id}',
|
|
||||||
data=data.__dict__,
|
|
||||||
instance_token=instance_token
|
|
||||||
)
|
|
||||||
|
|
||||||
def get_base64_from_media_message(self, instance_id: str, data: MediaMessage, instance_token: str):
|
|
||||||
return self.client.post(
|
|
||||||
f'chat/getBase64FromMediaMessage/{instance_id}',
|
|
||||||
data=data.__dict__,
|
|
||||||
instance_token=instance_token
|
|
||||||
)
|
|
||||||
|
|
||||||
def update_message(self, instance_id: str, data: UpdateMessage, instance_token: str):
|
|
||||||
return self.client.post(
|
|
||||||
f'chat/updateMessage/{instance_id}',
|
|
||||||
data=data.__dict__,
|
|
||||||
instance_token=instance_token
|
|
||||||
)
|
|
||||||
|
|
||||||
def send_presence(self, instance_id: str, data: Presence, instance_token: str):
|
|
||||||
return self.client.post(
|
|
||||||
f'chat/sendPresence/{instance_id}',
|
|
||||||
data=data.__dict__,
|
|
||||||
instance_token=instance_token
|
|
||||||
)
|
|
||||||
|
|
||||||
def get_messages(
|
|
||||||
self,
|
|
||||||
instance_id: str,
|
|
||||||
remote_jid: str,
|
|
||||||
instance_token: str,
|
|
||||||
message_id: Optional[str] = None,
|
|
||||||
whatsapp_message_id: Optional[str] = None,
|
|
||||||
from_me: Optional[bool] = None,
|
|
||||||
message_type: Optional[str] = None,
|
|
||||||
source: Optional[str] = None,
|
|
||||||
timestamp_start: Optional[str] = None,
|
|
||||||
timestamp_end: Optional[str] = None,
|
|
||||||
page: int = 1,
|
|
||||||
offset: int = 50
|
|
||||||
):
|
|
||||||
'''
|
|
||||||
Obtém mensagens de um chat com filtros opcionais
|
|
||||||
|
|
||||||
Args:
|
|
||||||
timestamp_start: Data inicial no formato ISO (ex: "2025-01-16T00:00:00Z")
|
|
||||||
timestamp_end: Data final no formato ISO (ex: "2025-01-16T23:59:59Z")
|
|
||||||
'''
|
|
||||||
where = {"key": {"remoteJid": remote_jid}}
|
|
||||||
|
|
||||||
if message_id:
|
|
||||||
where["id"] = message_id
|
|
||||||
if whatsapp_message_id:
|
|
||||||
where["key"]["id"] = whatsapp_message_id
|
|
||||||
if from_me is not None:
|
|
||||||
where["key"]["fromMe"] = from_me
|
|
||||||
if message_type:
|
|
||||||
where["messageType"] = message_type
|
|
||||||
if source:
|
|
||||||
where["source"] = source
|
|
||||||
if timestamp_start or timestamp_end:
|
|
||||||
where["messageTimestamp"] = {}
|
|
||||||
if timestamp_start:
|
|
||||||
where["messageTimestamp"]["gte"] = timestamp_start
|
|
||||||
if timestamp_end:
|
|
||||||
where["messageTimestamp"]["lte"] = timestamp_end
|
|
||||||
|
|
||||||
payload = {
|
|
||||||
"where": where,
|
|
||||||
"page": page,
|
|
||||||
"offset": offset,
|
|
||||||
}
|
|
||||||
|
|
||||||
return self.client.post(
|
|
||||||
f'chat/findMessages/{instance_id}',
|
|
||||||
data=payload,
|
|
||||||
instance_token=instance_token,
|
|
||||||
)
|
|
||||||
@ -1,105 +0,0 @@
|
|||||||
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
|
|
||||||
)
|
|
||||||
|
|
||||||
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}',
|
|
||||||
data=data.__dict__,
|
|
||||||
instance_token=instance_token
|
|
||||||
)
|
|
||||||
|
|
||||||
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}',
|
|
||||||
data=data.__dict__,
|
|
||||||
instance_token=instance_token
|
|
||||||
)
|
|
||||||
|
|
||||||
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}',
|
|
||||||
data=data.__dict__,
|
|
||||||
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
|
|
||||||
)
|
|
||||||
|
|
||||||
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
|
|
||||||
)
|
|
||||||
|
|
||||||
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
|
|
||||||
)
|
|
||||||
|
|
||||||
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
|
|
||||||
)
|
|
||||||
|
|
||||||
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
|
|
||||||
)
|
|
||||||
|
|
||||||
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
|
|
||||||
)
|
|
||||||
|
|
||||||
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}',
|
|
||||||
data=data.__dict__,
|
|
||||||
instance_token=instance_token
|
|
||||||
)
|
|
||||||
|
|
||||||
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}',
|
|
||||||
data=data.__dict__,
|
|
||||||
instance_token=instance_token
|
|
||||||
)
|
|
||||||
|
|
||||||
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}',
|
|
||||||
data=data.__dict__,
|
|
||||||
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
|
|
||||||
)
|
|
||||||
@ -1,9 +0,0 @@
|
|||||||
class InstanceService:
|
|
||||||
def __init__(self, client):
|
|
||||||
self.client = client
|
|
||||||
|
|
||||||
def fetch_instances(self):
|
|
||||||
return self.client.get('instance/fetchInstances')
|
|
||||||
|
|
||||||
def create_instance(self, config):
|
|
||||||
return self.client.post('instance/create', data=config.__dict__)
|
|
||||||
@ -1,28 +0,0 @@
|
|||||||
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)
|
|
||||||
|
|
||||||
def restart(self, instance_id: str, instance_token: str):
|
|
||||||
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}',
|
|
||||||
data=config.__dict__,
|
|
||||||
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)
|
|
||||||
|
|
||||||
def logout(self, instance_id: str, instance_token: str):
|
|
||||||
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)
|
|
||||||
@ -1,19 +0,0 @@
|
|||||||
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
|
|
||||||
)
|
|
||||||
|
|
||||||
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
|
|
||||||
)
|
|
||||||
@ -1,205 +0,0 @@
|
|||||||
from typing import Union, BinaryIO
|
|
||||||
from ..models.message import *
|
|
||||||
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
|
|
||||||
|
|
||||||
# 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
|
|
||||||
)
|
|
||||||
|
|
||||||
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'),
|
|
||||||
}
|
|
||||||
|
|
||||||
# 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')
|
|
||||||
|
|
||||||
# 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)
|
|
||||||
else:
|
|
||||||
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
|
|
||||||
|
|
||||||
# Fazer a requisição diretamente
|
|
||||||
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):
|
|
||||||
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')
|
|
||||||
else:
|
|
||||||
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)
|
|
||||||
else:
|
|
||||||
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}'
|
|
||||||
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):
|
|
||||||
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')
|
|
||||||
else:
|
|
||||||
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)
|
|
||||||
else:
|
|
||||||
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}'
|
|
||||||
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}',
|
|
||||||
data=message.__dict__,
|
|
||||||
instance_token=instance_token
|
|
||||||
)
|
|
||||||
|
|
||||||
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')
|
|
||||||
else:
|
|
||||||
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)
|
|
||||||
else:
|
|
||||||
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}'
|
|
||||||
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'])
|
|
||||||
|
|
||||||
return self.client.post(
|
|
||||||
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}',
|
|
||||||
data=message.__dict__,
|
|
||||||
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}',
|
|
||||||
data=message.__dict__,
|
|
||||||
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'])
|
|
||||||
|
|
||||||
return self.client.post(
|
|
||||||
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'])
|
|
||||||
|
|
||||||
return self.client.post(
|
|
||||||
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'])
|
|
||||||
|
|
||||||
return self.client.post(
|
|
||||||
f'message/sendButtons/{instance_id}',
|
|
||||||
data=data,
|
|
||||||
instance_token=instance_token
|
|
||||||
)
|
|
||||||
@ -1,60 +0,0 @@
|
|||||||
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}',
|
|
||||||
data=data.__dict__,
|
|
||||||
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
|
|
||||||
)
|
|
||||||
|
|
||||||
def update_profile_name(self, instance_id: str, data: ProfileName, instance_token: str):
|
|
||||||
return self.client.post(
|
|
||||||
f'chat/updateProfileName/{instance_id}',
|
|
||||||
data=data.__dict__,
|
|
||||||
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}',
|
|
||||||
data=data.__dict__,
|
|
||||||
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}',
|
|
||||||
data=data.__dict__,
|
|
||||||
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
|
|
||||||
)
|
|
||||||
|
|
||||||
def fetch_privacy_settings(self, instance_id: str, instance_token: str):
|
|
||||||
return self.client.get(
|
|
||||||
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}',
|
|
||||||
data=data.__dict__,
|
|
||||||
instance_token=instance_token
|
|
||||||
)
|
|
||||||
@ -1,174 +0,0 @@
|
|||||||
import socketio
|
|
||||||
from typing import Callable, Dict, Any
|
|
||||||
import logging
|
|
||||||
import time
|
|
||||||
from typing import Optional
|
|
||||||
from ..models.websocket import WebSocketConfig, WebSocketInfo
|
|
||||||
|
|
||||||
class WebSocketService:
|
|
||||||
def __init__(self, client):
|
|
||||||
self.client = client
|
|
||||||
|
|
||||||
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
|
|
||||||
)
|
|
||||||
|
|
||||||
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
|
|
||||||
)
|
|
||||||
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):
|
|
||||||
"""
|
|
||||||
Initialize the WebSocket manager
|
|
||||||
|
|
||||||
Args:
|
|
||||||
base_url (str): Base URL of the API
|
|
||||||
instance_id (str): Instance ID
|
|
||||||
api_token (str): API authentication token
|
|
||||||
max_retries (int): Maximum number of reconnection attempts
|
|
||||||
retry_delay (float): Initial delay between attempts in seconds
|
|
||||||
"""
|
|
||||||
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
|
|
||||||
)
|
|
||||||
|
|
||||||
# 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)
|
|
||||||
|
|
||||||
# Register global handler in instance-specific namespace
|
|
||||||
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}")
|
|
||||||
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
|
|
||||||
self.logger.info(f"Attempting to reconnect in {delay:.2f} seconds...")
|
|
||||||
time.sleep(delay)
|
|
||||||
self.connect()
|
|
||||||
self.retry_count += 1
|
|
||||||
except Exception as e:
|
|
||||||
self.logger.error(f"Error during reconnection attempt: {str(e)}", exc_info=True)
|
|
||||||
if self.retry_count < self.max_retries:
|
|
||||||
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}")
|
|
||||||
self._handlers[event](raw_data)
|
|
||||||
else:
|
|
||||||
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
|
|
||||||
)
|
|
||||||
|
|
||||||
# Join instance-specific room
|
|
||||||
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
|
|
||||||
247
env/bin/Activate.ps1
vendored
247
env/bin/Activate.ps1
vendored
@ -1,247 +0,0 @@
|
|||||||
<#
|
|
||||||
.Synopsis
|
|
||||||
Activate a Python virtual environment for the current PowerShell session.
|
|
||||||
|
|
||||||
.Description
|
|
||||||
Pushes the python executable for a virtual environment to the front of the
|
|
||||||
$Env:PATH environment variable and sets the prompt to signify that you are
|
|
||||||
in a Python virtual environment. Makes use of the command line switches as
|
|
||||||
well as the `pyvenv.cfg` file values present in the virtual environment.
|
|
||||||
|
|
||||||
.Parameter VenvDir
|
|
||||||
Path to the directory that contains the virtual environment to activate. The
|
|
||||||
default value for this is the parent of the directory that the Activate.ps1
|
|
||||||
script is located within.
|
|
||||||
|
|
||||||
.Parameter Prompt
|
|
||||||
The prompt prefix to display when this virtual environment is activated. By
|
|
||||||
default, this prompt is the name of the virtual environment folder (VenvDir)
|
|
||||||
surrounded by parentheses and followed by a single space (ie. '(.venv) ').
|
|
||||||
|
|
||||||
.Example
|
|
||||||
Activate.ps1
|
|
||||||
Activates the Python virtual environment that contains the Activate.ps1 script.
|
|
||||||
|
|
||||||
.Example
|
|
||||||
Activate.ps1 -Verbose
|
|
||||||
Activates the Python virtual environment that contains the Activate.ps1 script,
|
|
||||||
and shows extra information about the activation as it executes.
|
|
||||||
|
|
||||||
.Example
|
|
||||||
Activate.ps1 -VenvDir C:\Users\MyUser\Common\.venv
|
|
||||||
Activates the Python virtual environment located in the specified location.
|
|
||||||
|
|
||||||
.Example
|
|
||||||
Activate.ps1 -Prompt "MyPython"
|
|
||||||
Activates the Python virtual environment that contains the Activate.ps1 script,
|
|
||||||
and prefixes the current prompt with the specified string (surrounded in
|
|
||||||
parentheses) while the virtual environment is active.
|
|
||||||
|
|
||||||
.Notes
|
|
||||||
On Windows, it may be required to enable this Activate.ps1 script by setting the
|
|
||||||
execution policy for the user. You can do this by issuing the following PowerShell
|
|
||||||
command:
|
|
||||||
|
|
||||||
PS C:\> Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser
|
|
||||||
|
|
||||||
For more information on Execution Policies:
|
|
||||||
https://go.microsoft.com/fwlink/?LinkID=135170
|
|
||||||
|
|
||||||
#>
|
|
||||||
Param(
|
|
||||||
[Parameter(Mandatory = $false)]
|
|
||||||
[String]
|
|
||||||
$VenvDir,
|
|
||||||
[Parameter(Mandatory = $false)]
|
|
||||||
[String]
|
|
||||||
$Prompt
|
|
||||||
)
|
|
||||||
|
|
||||||
<# Function declarations --------------------------------------------------- #>
|
|
||||||
|
|
||||||
<#
|
|
||||||
.Synopsis
|
|
||||||
Remove all shell session elements added by the Activate script, including the
|
|
||||||
addition of the virtual environment's Python executable from the beginning of
|
|
||||||
the PATH variable.
|
|
||||||
|
|
||||||
.Parameter NonDestructive
|
|
||||||
If present, do not remove this function from the global namespace for the
|
|
||||||
session.
|
|
||||||
|
|
||||||
#>
|
|
||||||
function global:deactivate ([switch]$NonDestructive) {
|
|
||||||
# Revert to original values
|
|
||||||
|
|
||||||
# The prior prompt:
|
|
||||||
if (Test-Path -Path Function:_OLD_VIRTUAL_PROMPT) {
|
|
||||||
Copy-Item -Path Function:_OLD_VIRTUAL_PROMPT -Destination Function:prompt
|
|
||||||
Remove-Item -Path Function:_OLD_VIRTUAL_PROMPT
|
|
||||||
}
|
|
||||||
|
|
||||||
# The prior PYTHONHOME:
|
|
||||||
if (Test-Path -Path Env:_OLD_VIRTUAL_PYTHONHOME) {
|
|
||||||
Copy-Item -Path Env:_OLD_VIRTUAL_PYTHONHOME -Destination Env:PYTHONHOME
|
|
||||||
Remove-Item -Path Env:_OLD_VIRTUAL_PYTHONHOME
|
|
||||||
}
|
|
||||||
|
|
||||||
# The prior PATH:
|
|
||||||
if (Test-Path -Path Env:_OLD_VIRTUAL_PATH) {
|
|
||||||
Copy-Item -Path Env:_OLD_VIRTUAL_PATH -Destination Env:PATH
|
|
||||||
Remove-Item -Path Env:_OLD_VIRTUAL_PATH
|
|
||||||
}
|
|
||||||
|
|
||||||
# Just remove the VIRTUAL_ENV altogether:
|
|
||||||
if (Test-Path -Path Env:VIRTUAL_ENV) {
|
|
||||||
Remove-Item -Path env:VIRTUAL_ENV
|
|
||||||
}
|
|
||||||
|
|
||||||
# Just remove VIRTUAL_ENV_PROMPT altogether.
|
|
||||||
if (Test-Path -Path Env:VIRTUAL_ENV_PROMPT) {
|
|
||||||
Remove-Item -Path env:VIRTUAL_ENV_PROMPT
|
|
||||||
}
|
|
||||||
|
|
||||||
# Just remove the _PYTHON_VENV_PROMPT_PREFIX altogether:
|
|
||||||
if (Get-Variable -Name "_PYTHON_VENV_PROMPT_PREFIX" -ErrorAction SilentlyContinue) {
|
|
||||||
Remove-Variable -Name _PYTHON_VENV_PROMPT_PREFIX -Scope Global -Force
|
|
||||||
}
|
|
||||||
|
|
||||||
# Leave deactivate function in the global namespace if requested:
|
|
||||||
if (-not $NonDestructive) {
|
|
||||||
Remove-Item -Path function:deactivate
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
<#
|
|
||||||
.Description
|
|
||||||
Get-PyVenvConfig parses the values from the pyvenv.cfg file located in the
|
|
||||||
given folder, and returns them in a map.
|
|
||||||
|
|
||||||
For each line in the pyvenv.cfg file, if that line can be parsed into exactly
|
|
||||||
two strings separated by `=` (with any amount of whitespace surrounding the =)
|
|
||||||
then it is considered a `key = value` line. The left hand string is the key,
|
|
||||||
the right hand is the value.
|
|
||||||
|
|
||||||
If the value starts with a `'` or a `"` then the first and last character is
|
|
||||||
stripped from the value before being captured.
|
|
||||||
|
|
||||||
.Parameter ConfigDir
|
|
||||||
Path to the directory that contains the `pyvenv.cfg` file.
|
|
||||||
#>
|
|
||||||
function Get-PyVenvConfig(
|
|
||||||
[String]
|
|
||||||
$ConfigDir
|
|
||||||
) {
|
|
||||||
Write-Verbose "Given ConfigDir=$ConfigDir, obtain values in pyvenv.cfg"
|
|
||||||
|
|
||||||
# Ensure the file exists, and issue a warning if it doesn't (but still allow the function to continue).
|
|
||||||
$pyvenvConfigPath = Join-Path -Resolve -Path $ConfigDir -ChildPath 'pyvenv.cfg' -ErrorAction Continue
|
|
||||||
|
|
||||||
# An empty map will be returned if no config file is found.
|
|
||||||
$pyvenvConfig = @{ }
|
|
||||||
|
|
||||||
if ($pyvenvConfigPath) {
|
|
||||||
|
|
||||||
Write-Verbose "File exists, parse `key = value` lines"
|
|
||||||
$pyvenvConfigContent = Get-Content -Path $pyvenvConfigPath
|
|
||||||
|
|
||||||
$pyvenvConfigContent | ForEach-Object {
|
|
||||||
$keyval = $PSItem -split "\s*=\s*", 2
|
|
||||||
if ($keyval[0] -and $keyval[1]) {
|
|
||||||
$val = $keyval[1]
|
|
||||||
|
|
||||||
# Remove extraneous quotations around a string value.
|
|
||||||
if ("'""".Contains($val.Substring(0, 1))) {
|
|
||||||
$val = $val.Substring(1, $val.Length - 2)
|
|
||||||
}
|
|
||||||
|
|
||||||
$pyvenvConfig[$keyval[0]] = $val
|
|
||||||
Write-Verbose "Adding Key: '$($keyval[0])'='$val'"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return $pyvenvConfig
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
<# Begin Activate script --------------------------------------------------- #>
|
|
||||||
|
|
||||||
# Determine the containing directory of this script
|
|
||||||
$VenvExecPath = Split-Path -Parent $MyInvocation.MyCommand.Definition
|
|
||||||
$VenvExecDir = Get-Item -Path $VenvExecPath
|
|
||||||
|
|
||||||
Write-Verbose "Activation script is located in path: '$VenvExecPath'"
|
|
||||||
Write-Verbose "VenvExecDir Fullname: '$($VenvExecDir.FullName)"
|
|
||||||
Write-Verbose "VenvExecDir Name: '$($VenvExecDir.Name)"
|
|
||||||
|
|
||||||
# Set values required in priority: CmdLine, ConfigFile, Default
|
|
||||||
# First, get the location of the virtual environment, it might not be
|
|
||||||
# VenvExecDir if specified on the command line.
|
|
||||||
if ($VenvDir) {
|
|
||||||
Write-Verbose "VenvDir given as parameter, using '$VenvDir' to determine values"
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
Write-Verbose "VenvDir not given as a parameter, using parent directory name as VenvDir."
|
|
||||||
$VenvDir = $VenvExecDir.Parent.FullName.TrimEnd("\\/")
|
|
||||||
Write-Verbose "VenvDir=$VenvDir"
|
|
||||||
}
|
|
||||||
|
|
||||||
# Next, read the `pyvenv.cfg` file to determine any required value such
|
|
||||||
# as `prompt`.
|
|
||||||
$pyvenvCfg = Get-PyVenvConfig -ConfigDir $VenvDir
|
|
||||||
|
|
||||||
# Next, set the prompt from the command line, or the config file, or
|
|
||||||
# just use the name of the virtual environment folder.
|
|
||||||
if ($Prompt) {
|
|
||||||
Write-Verbose "Prompt specified as argument, using '$Prompt'"
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
Write-Verbose "Prompt not specified as argument to script, checking pyvenv.cfg value"
|
|
||||||
if ($pyvenvCfg -and $pyvenvCfg['prompt']) {
|
|
||||||
Write-Verbose " Setting based on value in pyvenv.cfg='$($pyvenvCfg['prompt'])'"
|
|
||||||
$Prompt = $pyvenvCfg['prompt'];
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
Write-Verbose " Setting prompt based on parent's directory's name. (Is the directory name passed to venv module when creating the virtual environment)"
|
|
||||||
Write-Verbose " Got leaf-name of $VenvDir='$(Split-Path -Path $venvDir -Leaf)'"
|
|
||||||
$Prompt = Split-Path -Path $venvDir -Leaf
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Write-Verbose "Prompt = '$Prompt'"
|
|
||||||
Write-Verbose "VenvDir='$VenvDir'"
|
|
||||||
|
|
||||||
# Deactivate any currently active virtual environment, but leave the
|
|
||||||
# deactivate function in place.
|
|
||||||
deactivate -nondestructive
|
|
||||||
|
|
||||||
# Now set the environment variable VIRTUAL_ENV, used by many tools to determine
|
|
||||||
# that there is an activated venv.
|
|
||||||
$env:VIRTUAL_ENV = $VenvDir
|
|
||||||
|
|
||||||
if (-not $Env:VIRTUAL_ENV_DISABLE_PROMPT) {
|
|
||||||
|
|
||||||
Write-Verbose "Setting prompt to '$Prompt'"
|
|
||||||
|
|
||||||
# Set the prompt to include the env name
|
|
||||||
# Make sure _OLD_VIRTUAL_PROMPT is global
|
|
||||||
function global:_OLD_VIRTUAL_PROMPT { "" }
|
|
||||||
Copy-Item -Path function:prompt -Destination function:_OLD_VIRTUAL_PROMPT
|
|
||||||
New-Variable -Name _PYTHON_VENV_PROMPT_PREFIX -Description "Python virtual environment prompt prefix" -Scope Global -Option ReadOnly -Visibility Public -Value $Prompt
|
|
||||||
|
|
||||||
function global:prompt {
|
|
||||||
Write-Host -NoNewline -ForegroundColor Green "($_PYTHON_VENV_PROMPT_PREFIX) "
|
|
||||||
_OLD_VIRTUAL_PROMPT
|
|
||||||
}
|
|
||||||
$env:VIRTUAL_ENV_PROMPT = $Prompt
|
|
||||||
}
|
|
||||||
|
|
||||||
# Clear PYTHONHOME
|
|
||||||
if (Test-Path -Path Env:PYTHONHOME) {
|
|
||||||
Copy-Item -Path Env:PYTHONHOME -Destination Env:_OLD_VIRTUAL_PYTHONHOME
|
|
||||||
Remove-Item -Path Env:PYTHONHOME
|
|
||||||
}
|
|
||||||
|
|
||||||
# Add the venv to the PATH
|
|
||||||
Copy-Item -Path Env:PATH -Destination Env:_OLD_VIRTUAL_PATH
|
|
||||||
$Env:PATH = "$VenvExecDir$([System.IO.Path]::PathSeparator)$Env:PATH"
|
|
||||||
69
env/bin/activate
vendored
69
env/bin/activate
vendored
@ -1,69 +0,0 @@
|
|||||||
# This file must be used with "source bin/activate" *from bash*
|
|
||||||
# you cannot run it directly
|
|
||||||
|
|
||||||
deactivate () {
|
|
||||||
# reset old environment variables
|
|
||||||
if [ -n "${_OLD_VIRTUAL_PATH:-}" ] ; then
|
|
||||||
PATH="${_OLD_VIRTUAL_PATH:-}"
|
|
||||||
export PATH
|
|
||||||
unset _OLD_VIRTUAL_PATH
|
|
||||||
fi
|
|
||||||
if [ -n "${_OLD_VIRTUAL_PYTHONHOME:-}" ] ; then
|
|
||||||
PYTHONHOME="${_OLD_VIRTUAL_PYTHONHOME:-}"
|
|
||||||
export PYTHONHOME
|
|
||||||
unset _OLD_VIRTUAL_PYTHONHOME
|
|
||||||
fi
|
|
||||||
|
|
||||||
# This should detect bash and zsh, which have a hash command that must
|
|
||||||
# be called to get it to forget past commands. Without forgetting
|
|
||||||
# past commands the $PATH changes we made may not be respected
|
|
||||||
if [ -n "${BASH:-}" -o -n "${ZSH_VERSION:-}" ] ; then
|
|
||||||
hash -r 2> /dev/null
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ -n "${_OLD_VIRTUAL_PS1:-}" ] ; then
|
|
||||||
PS1="${_OLD_VIRTUAL_PS1:-}"
|
|
||||||
export PS1
|
|
||||||
unset _OLD_VIRTUAL_PS1
|
|
||||||
fi
|
|
||||||
|
|
||||||
unset VIRTUAL_ENV
|
|
||||||
unset VIRTUAL_ENV_PROMPT
|
|
||||||
if [ ! "${1:-}" = "nondestructive" ] ; then
|
|
||||||
# Self destruct!
|
|
||||||
unset -f deactivate
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
# unset irrelevant variables
|
|
||||||
deactivate nondestructive
|
|
||||||
|
|
||||||
VIRTUAL_ENV="/home/davidson/Projects/evolution_client/python/env"
|
|
||||||
export VIRTUAL_ENV
|
|
||||||
|
|
||||||
_OLD_VIRTUAL_PATH="$PATH"
|
|
||||||
PATH="$VIRTUAL_ENV/bin:$PATH"
|
|
||||||
export PATH
|
|
||||||
|
|
||||||
# unset PYTHONHOME if set
|
|
||||||
# this will fail if PYTHONHOME is set to the empty string (which is bad anyway)
|
|
||||||
# could use `if (set -u; : $PYTHONHOME) ;` in bash
|
|
||||||
if [ -n "${PYTHONHOME:-}" ] ; then
|
|
||||||
_OLD_VIRTUAL_PYTHONHOME="${PYTHONHOME:-}"
|
|
||||||
unset PYTHONHOME
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ -z "${VIRTUAL_ENV_DISABLE_PROMPT:-}" ] ; then
|
|
||||||
_OLD_VIRTUAL_PS1="${PS1:-}"
|
|
||||||
PS1="(env) ${PS1:-}"
|
|
||||||
export PS1
|
|
||||||
VIRTUAL_ENV_PROMPT="(env) "
|
|
||||||
export VIRTUAL_ENV_PROMPT
|
|
||||||
fi
|
|
||||||
|
|
||||||
# This should detect bash and zsh, which have a hash command that must
|
|
||||||
# be called to get it to forget past commands. Without forgetting
|
|
||||||
# past commands the $PATH changes we made may not be respected
|
|
||||||
if [ -n "${BASH:-}" -o -n "${ZSH_VERSION:-}" ] ; then
|
|
||||||
hash -r 2> /dev/null
|
|
||||||
fi
|
|
||||||
26
env/bin/activate.csh
vendored
26
env/bin/activate.csh
vendored
@ -1,26 +0,0 @@
|
|||||||
# This file must be used with "source bin/activate.csh" *from csh*.
|
|
||||||
# You cannot run it directly.
|
|
||||||
# Created by Davide Di Blasi <davidedb@gmail.com>.
|
|
||||||
# Ported to Python 3.3 venv by Andrew Svetlov <andrew.svetlov@gmail.com>
|
|
||||||
|
|
||||||
alias deactivate 'test $?_OLD_VIRTUAL_PATH != 0 && setenv PATH "$_OLD_VIRTUAL_PATH" && unset _OLD_VIRTUAL_PATH; rehash; test $?_OLD_VIRTUAL_PROMPT != 0 && set prompt="$_OLD_VIRTUAL_PROMPT" && unset _OLD_VIRTUAL_PROMPT; unsetenv VIRTUAL_ENV; unsetenv VIRTUAL_ENV_PROMPT; test "\!:*" != "nondestructive" && unalias deactivate'
|
|
||||||
|
|
||||||
# Unset irrelevant variables.
|
|
||||||
deactivate nondestructive
|
|
||||||
|
|
||||||
setenv VIRTUAL_ENV "/home/davidson/Projects/evolution_client/python/env"
|
|
||||||
|
|
||||||
set _OLD_VIRTUAL_PATH="$PATH"
|
|
||||||
setenv PATH "$VIRTUAL_ENV/bin:$PATH"
|
|
||||||
|
|
||||||
|
|
||||||
set _OLD_VIRTUAL_PROMPT="$prompt"
|
|
||||||
|
|
||||||
if (! "$?VIRTUAL_ENV_DISABLE_PROMPT") then
|
|
||||||
set prompt = "(env) $prompt"
|
|
||||||
setenv VIRTUAL_ENV_PROMPT "(env) "
|
|
||||||
endif
|
|
||||||
|
|
||||||
alias pydoc python -m pydoc
|
|
||||||
|
|
||||||
rehash
|
|
||||||
69
env/bin/activate.fish
vendored
69
env/bin/activate.fish
vendored
@ -1,69 +0,0 @@
|
|||||||
# This file must be used with "source <venv>/bin/activate.fish" *from fish*
|
|
||||||
# (https://fishshell.com/); you cannot run it directly.
|
|
||||||
|
|
||||||
function deactivate -d "Exit virtual environment and return to normal shell environment"
|
|
||||||
# reset old environment variables
|
|
||||||
if test -n "$_OLD_VIRTUAL_PATH"
|
|
||||||
set -gx PATH $_OLD_VIRTUAL_PATH
|
|
||||||
set -e _OLD_VIRTUAL_PATH
|
|
||||||
end
|
|
||||||
if test -n "$_OLD_VIRTUAL_PYTHONHOME"
|
|
||||||
set -gx PYTHONHOME $_OLD_VIRTUAL_PYTHONHOME
|
|
||||||
set -e _OLD_VIRTUAL_PYTHONHOME
|
|
||||||
end
|
|
||||||
|
|
||||||
if test -n "$_OLD_FISH_PROMPT_OVERRIDE"
|
|
||||||
set -e _OLD_FISH_PROMPT_OVERRIDE
|
|
||||||
# prevents error when using nested fish instances (Issue #93858)
|
|
||||||
if functions -q _old_fish_prompt
|
|
||||||
functions -e fish_prompt
|
|
||||||
functions -c _old_fish_prompt fish_prompt
|
|
||||||
functions -e _old_fish_prompt
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
set -e VIRTUAL_ENV
|
|
||||||
set -e VIRTUAL_ENV_PROMPT
|
|
||||||
if test "$argv[1]" != "nondestructive"
|
|
||||||
# Self-destruct!
|
|
||||||
functions -e deactivate
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Unset irrelevant variables.
|
|
||||||
deactivate nondestructive
|
|
||||||
|
|
||||||
set -gx VIRTUAL_ENV "/home/davidson/Projects/evolution_client/python/env"
|
|
||||||
|
|
||||||
set -gx _OLD_VIRTUAL_PATH $PATH
|
|
||||||
set -gx PATH "$VIRTUAL_ENV/bin" $PATH
|
|
||||||
|
|
||||||
# Unset PYTHONHOME if set.
|
|
||||||
if set -q PYTHONHOME
|
|
||||||
set -gx _OLD_VIRTUAL_PYTHONHOME $PYTHONHOME
|
|
||||||
set -e PYTHONHOME
|
|
||||||
end
|
|
||||||
|
|
||||||
if test -z "$VIRTUAL_ENV_DISABLE_PROMPT"
|
|
||||||
# fish uses a function instead of an env var to generate the prompt.
|
|
||||||
|
|
||||||
# Save the current fish_prompt function as the function _old_fish_prompt.
|
|
||||||
functions -c fish_prompt _old_fish_prompt
|
|
||||||
|
|
||||||
# With the original prompt function renamed, we can override with our own.
|
|
||||||
function fish_prompt
|
|
||||||
# Save the return status of the last command.
|
|
||||||
set -l old_status $status
|
|
||||||
|
|
||||||
# Output the venv prompt; color taken from the blue of the Python logo.
|
|
||||||
printf "%s%s%s" (set_color 4B8BBE) "(env) " (set_color normal)
|
|
||||||
|
|
||||||
# Restore the return status of the previous command.
|
|
||||||
echo "exit $old_status" | .
|
|
||||||
# Output the original/"old" prompt.
|
|
||||||
_old_fish_prompt
|
|
||||||
end
|
|
||||||
|
|
||||||
set -gx _OLD_FISH_PROMPT_OVERRIDE "$VIRTUAL_ENV"
|
|
||||||
set -gx VIRTUAL_ENV_PROMPT "(env) "
|
|
||||||
end
|
|
||||||
8
env/bin/docutils
vendored
8
env/bin/docutils
vendored
@ -1,8 +0,0 @@
|
|||||||
#!/home/davidson/Projects/evolution_client/python/env/bin/python
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
import re
|
|
||||||
import sys
|
|
||||||
from docutils.__main__ import main
|
|
||||||
if __name__ == '__main__':
|
|
||||||
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
|
||||||
sys.exit(main())
|
|
||||||
8
env/bin/keyring
vendored
8
env/bin/keyring
vendored
@ -1,8 +0,0 @@
|
|||||||
#!/home/davidson/Projects/evolution_client/python/env/bin/python
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
import re
|
|
||||||
import sys
|
|
||||||
from keyring.cli import main
|
|
||||||
if __name__ == '__main__':
|
|
||||||
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
|
||||||
sys.exit(main())
|
|
||||||
8
env/bin/markdown-it
vendored
8
env/bin/markdown-it
vendored
@ -1,8 +0,0 @@
|
|||||||
#!/home/davidson/Projects/evolution_client/python/env/bin/python
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
import re
|
|
||||||
import sys
|
|
||||||
from markdown_it.cli.parse import main
|
|
||||||
if __name__ == '__main__':
|
|
||||||
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
|
||||||
sys.exit(main())
|
|
||||||
8
env/bin/normalizer
vendored
8
env/bin/normalizer
vendored
@ -1,8 +0,0 @@
|
|||||||
#!/home/davidson/Projects/evolution_client/python/env/bin/python
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
import re
|
|
||||||
import sys
|
|
||||||
from charset_normalizer.cli import cli_detect
|
|
||||||
if __name__ == '__main__':
|
|
||||||
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
|
||||||
sys.exit(cli_detect())
|
|
||||||
8
env/bin/pip
vendored
8
env/bin/pip
vendored
@ -1,8 +0,0 @@
|
|||||||
#!/home/davidson/Projects/evolution_client/python/env/bin/python
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
import re
|
|
||||||
import sys
|
|
||||||
from pip._internal.cli.main import main
|
|
||||||
if __name__ == '__main__':
|
|
||||||
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
|
||||||
sys.exit(main())
|
|
||||||
8
env/bin/pip3
vendored
8
env/bin/pip3
vendored
@ -1,8 +0,0 @@
|
|||||||
#!/home/davidson/Projects/evolution_client/python/env/bin/python
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
import re
|
|
||||||
import sys
|
|
||||||
from pip._internal.cli.main import main
|
|
||||||
if __name__ == '__main__':
|
|
||||||
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
|
||||||
sys.exit(main())
|
|
||||||
8
env/bin/pip3.10
vendored
8
env/bin/pip3.10
vendored
@ -1,8 +0,0 @@
|
|||||||
#!/home/davidson/Projects/evolution_client/python/env/bin/python
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
import re
|
|
||||||
import sys
|
|
||||||
from pip._internal.cli.main import main
|
|
||||||
if __name__ == '__main__':
|
|
||||||
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
|
||||||
sys.exit(main())
|
|
||||||
8
env/bin/pkginfo
vendored
8
env/bin/pkginfo
vendored
@ -1,8 +0,0 @@
|
|||||||
#!/home/davidson/Projects/evolution_client/python/env/bin/python
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
import re
|
|
||||||
import sys
|
|
||||||
from pkginfo.commandline import main
|
|
||||||
if __name__ == '__main__':
|
|
||||||
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
|
||||||
sys.exit(main())
|
|
||||||
8
env/bin/pygmentize
vendored
8
env/bin/pygmentize
vendored
@ -1,8 +0,0 @@
|
|||||||
#!/home/davidson/Projects/evolution_client/python/env/bin/python
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
import re
|
|
||||||
import sys
|
|
||||||
from pygments.cmdline import main
|
|
||||||
if __name__ == '__main__':
|
|
||||||
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
|
||||||
sys.exit(main())
|
|
||||||
1
env/bin/python
vendored
1
env/bin/python
vendored
@ -1 +0,0 @@
|
|||||||
/usr/bin/python
|
|
||||||
1
env/bin/python3
vendored
1
env/bin/python3
vendored
@ -1 +0,0 @@
|
|||||||
python
|
|
||||||
1
env/bin/python3.10
vendored
1
env/bin/python3.10
vendored
@ -1 +0,0 @@
|
|||||||
python
|
|
||||||
8
env/bin/rst2html
vendored
8
env/bin/rst2html
vendored
@ -1,8 +0,0 @@
|
|||||||
#!/home/davidson/Projects/evolution_client/python/env/bin/python
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
import re
|
|
||||||
import sys
|
|
||||||
from docutils.core import rst2html
|
|
||||||
if __name__ == '__main__':
|
|
||||||
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
|
||||||
sys.exit(rst2html())
|
|
||||||
8
env/bin/rst2html4
vendored
8
env/bin/rst2html4
vendored
@ -1,8 +0,0 @@
|
|||||||
#!/home/davidson/Projects/evolution_client/python/env/bin/python
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
import re
|
|
||||||
import sys
|
|
||||||
from docutils.core import rst2html4
|
|
||||||
if __name__ == '__main__':
|
|
||||||
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
|
||||||
sys.exit(rst2html4())
|
|
||||||
8
env/bin/rst2html5
vendored
8
env/bin/rst2html5
vendored
@ -1,8 +0,0 @@
|
|||||||
#!/home/davidson/Projects/evolution_client/python/env/bin/python
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
import re
|
|
||||||
import sys
|
|
||||||
from docutils.core import rst2html5
|
|
||||||
if __name__ == '__main__':
|
|
||||||
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
|
||||||
sys.exit(rst2html5())
|
|
||||||
8
env/bin/rst2latex
vendored
8
env/bin/rst2latex
vendored
@ -1,8 +0,0 @@
|
|||||||
#!/home/davidson/Projects/evolution_client/python/env/bin/python
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
import re
|
|
||||||
import sys
|
|
||||||
from docutils.core import rst2latex
|
|
||||||
if __name__ == '__main__':
|
|
||||||
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
|
||||||
sys.exit(rst2latex())
|
|
||||||
8
env/bin/rst2man
vendored
8
env/bin/rst2man
vendored
@ -1,8 +0,0 @@
|
|||||||
#!/home/davidson/Projects/evolution_client/python/env/bin/python
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
import re
|
|
||||||
import sys
|
|
||||||
from docutils.core import rst2man
|
|
||||||
if __name__ == '__main__':
|
|
||||||
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
|
||||||
sys.exit(rst2man())
|
|
||||||
8
env/bin/rst2odt
vendored
8
env/bin/rst2odt
vendored
@ -1,8 +0,0 @@
|
|||||||
#!/home/davidson/Projects/evolution_client/python/env/bin/python
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
import re
|
|
||||||
import sys
|
|
||||||
from docutils.core import rst2odt
|
|
||||||
if __name__ == '__main__':
|
|
||||||
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
|
||||||
sys.exit(rst2odt())
|
|
||||||
8
env/bin/rst2pseudoxml
vendored
8
env/bin/rst2pseudoxml
vendored
@ -1,8 +0,0 @@
|
|||||||
#!/home/davidson/Projects/evolution_client/python/env/bin/python
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
import re
|
|
||||||
import sys
|
|
||||||
from docutils.core import rst2pseudoxml
|
|
||||||
if __name__ == '__main__':
|
|
||||||
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
|
||||||
sys.exit(rst2pseudoxml())
|
|
||||||
8
env/bin/rst2s5
vendored
8
env/bin/rst2s5
vendored
@ -1,8 +0,0 @@
|
|||||||
#!/home/davidson/Projects/evolution_client/python/env/bin/python
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
import re
|
|
||||||
import sys
|
|
||||||
from docutils.core import rst2s5
|
|
||||||
if __name__ == '__main__':
|
|
||||||
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
|
||||||
sys.exit(rst2s5())
|
|
||||||
8
env/bin/rst2xetex
vendored
8
env/bin/rst2xetex
vendored
@ -1,8 +0,0 @@
|
|||||||
#!/home/davidson/Projects/evolution_client/python/env/bin/python
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
import re
|
|
||||||
import sys
|
|
||||||
from docutils.core import rst2xetex
|
|
||||||
if __name__ == '__main__':
|
|
||||||
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
|
||||||
sys.exit(rst2xetex())
|
|
||||||
8
env/bin/rst2xml
vendored
8
env/bin/rst2xml
vendored
@ -1,8 +0,0 @@
|
|||||||
#!/home/davidson/Projects/evolution_client/python/env/bin/python
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
import re
|
|
||||||
import sys
|
|
||||||
from docutils.core import rst2xml
|
|
||||||
if __name__ == '__main__':
|
|
||||||
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
|
||||||
sys.exit(rst2xml())
|
|
||||||
8
env/bin/twine
vendored
8
env/bin/twine
vendored
@ -1,8 +0,0 @@
|
|||||||
#!/home/davidson/Projects/evolution_client/python/env/bin/python
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
import re
|
|
||||||
import sys
|
|
||||||
from twine.__main__ import main
|
|
||||||
if __name__ == '__main__':
|
|
||||||
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
|
||||||
sys.exit(main())
|
|
||||||
8
env/bin/wheel
vendored
8
env/bin/wheel
vendored
@ -1,8 +0,0 @@
|
|||||||
#!/home/davidson/Projects/evolution_client/python/env/bin/python
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
import re
|
|
||||||
import sys
|
|
||||||
from wheel.cli import main
|
|
||||||
if __name__ == '__main__':
|
|
||||||
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
|
||||||
sys.exit(main())
|
|
||||||
8
env/bin/wsdump
vendored
8
env/bin/wsdump
vendored
@ -1,8 +0,0 @@
|
|||||||
#!/home/davidson/Projects/evolution_client/python/env/bin/python
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
import re
|
|
||||||
import sys
|
|
||||||
from websocket._wsdump import main
|
|
||||||
if __name__ == '__main__':
|
|
||||||
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
|
||||||
sys.exit(main())
|
|
||||||
@ -1 +0,0 @@
|
|||||||
pip
|
|
||||||
@ -1,25 +0,0 @@
|
|||||||
Copyright 2012-2018 Dmitry Shachnev <mitya57@gmail.com>
|
|
||||||
All rights reserved.
|
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
|
||||||
modification, are permitted provided that the following conditions are met:
|
|
||||||
|
|
||||||
1. Redistributions of source code must retain the above copyright notice, this
|
|
||||||
list of conditions and the following disclaimer.
|
|
||||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
||||||
this list of conditions and the following disclaimer in the documentation
|
|
||||||
and/or other materials provided with the distribution.
|
|
||||||
3. Neither the name of the University nor the names of its contributors may be
|
|
||||||
used to endorse or promote products derived from this software without
|
|
||||||
specific prior written permission.
|
|
||||||
|
|
||||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
|
||||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
|
||||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
||||||
DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
|
|
||||||
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
|
||||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
|
||||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
|
||||||
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
||||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
||||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
@ -1,114 +0,0 @@
|
|||||||
Metadata-Version: 2.1
|
|
||||||
Name: SecretStorage
|
|
||||||
Version: 3.3.3
|
|
||||||
Summary: Python bindings to FreeDesktop.org Secret Service API
|
|
||||||
Home-page: https://github.com/mitya57/secretstorage
|
|
||||||
Author: Dmitry Shachnev
|
|
||||||
Author-email: mitya57@gmail.com
|
|
||||||
License: BSD 3-Clause License
|
|
||||||
Platform: Linux
|
|
||||||
Classifier: Development Status :: 5 - Production/Stable
|
|
||||||
Classifier: License :: OSI Approved :: BSD License
|
|
||||||
Classifier: Operating System :: POSIX
|
|
||||||
Classifier: Programming Language :: Python
|
|
||||||
Classifier: Programming Language :: Python :: 3 :: Only
|
|
||||||
Classifier: Programming Language :: Python :: 3.6
|
|
||||||
Classifier: Programming Language :: Python :: 3.7
|
|
||||||
Classifier: Programming Language :: Python :: 3.8
|
|
||||||
Classifier: Programming Language :: Python :: 3.9
|
|
||||||
Classifier: Programming Language :: Python :: 3.10
|
|
||||||
Classifier: Topic :: Security
|
|
||||||
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
||||||
Requires-Python: >=3.6
|
|
||||||
Description-Content-Type: text/x-rst
|
|
||||||
License-File: LICENSE
|
|
||||||
Requires-Dist: cryptography (>=2.0)
|
|
||||||
Requires-Dist: jeepney (>=0.6)
|
|
||||||
|
|
||||||
.. image:: https://github.com/mitya57/secretstorage/workflows/tests/badge.svg
|
|
||||||
:target: https://github.com/mitya57/secretstorage/actions
|
|
||||||
:alt: GitHub Actions status
|
|
||||||
.. image:: https://codecov.io/gh/mitya57/secretstorage/branch/master/graph/badge.svg
|
|
||||||
:target: https://codecov.io/gh/mitya57/secretstorage
|
|
||||||
:alt: Coverage status
|
|
||||||
.. image:: https://readthedocs.org/projects/secretstorage/badge/?version=latest
|
|
||||||
:target: https://secretstorage.readthedocs.io/en/latest/
|
|
||||||
:alt: ReadTheDocs status
|
|
||||||
|
|
||||||
Module description
|
|
||||||
==================
|
|
||||||
|
|
||||||
This module provides a way for securely storing passwords and other secrets.
|
|
||||||
|
|
||||||
It uses D-Bus `Secret Service`_ API that is supported by GNOME Keyring,
|
|
||||||
KWallet (since version 5.97) and KeePassXC.
|
|
||||||
|
|
||||||
The main classes provided are ``secretstorage.Item``, representing a secret
|
|
||||||
item (that has a *label*, a *secret* and some *attributes*) and
|
|
||||||
``secretstorage.Collection``, a place items are stored in.
|
|
||||||
|
|
||||||
SecretStorage supports most of the functions provided by Secret Service,
|
|
||||||
including creating and deleting items and collections, editing items,
|
|
||||||
locking and unlocking collections (asynchronous unlocking is also supported).
|
|
||||||
|
|
||||||
The documentation can be found on `secretstorage.readthedocs.io`_.
|
|
||||||
|
|
||||||
.. _`Secret Service`: https://specifications.freedesktop.org/secret-service/
|
|
||||||
.. _`secretstorage.readthedocs.io`: https://secretstorage.readthedocs.io/en/latest/
|
|
||||||
|
|
||||||
Building the module
|
|
||||||
===================
|
|
||||||
|
|
||||||
.. note::
|
|
||||||
SecretStorage 3.x supports Python 3.6 and newer versions.
|
|
||||||
If you have an older version of Python, install SecretStorage 2.x::
|
|
||||||
|
|
||||||
pip install "SecretStorage < 3"
|
|
||||||
|
|
||||||
SecretStorage requires these packages to work:
|
|
||||||
|
|
||||||
* Jeepney_
|
|
||||||
* `python-cryptography`_
|
|
||||||
|
|
||||||
To build SecretStorage, use this command::
|
|
||||||
|
|
||||||
python3 setup.py build
|
|
||||||
|
|
||||||
If you have Sphinx_ installed, you can also build the documentation::
|
|
||||||
|
|
||||||
python3 setup.py build_sphinx
|
|
||||||
|
|
||||||
.. _Jeepney: https://pypi.org/project/jeepney/
|
|
||||||
.. _`python-cryptography`: https://pypi.org/project/cryptography/
|
|
||||||
.. _Sphinx: http://sphinx-doc.org/
|
|
||||||
|
|
||||||
Testing the module
|
|
||||||
==================
|
|
||||||
|
|
||||||
First, make sure that you have the Secret Service daemon installed.
|
|
||||||
The `GNOME Keyring`_ is the reference server-side implementation for the
|
|
||||||
Secret Service specification.
|
|
||||||
|
|
||||||
.. _`GNOME Keyring`: https://download.gnome.org/sources/gnome-keyring/
|
|
||||||
|
|
||||||
Then, start the daemon and unlock the ``default`` collection, if needed.
|
|
||||||
The testsuite will fail to run if the ``default`` collection exists and is
|
|
||||||
locked. If it does not exist, the testsuite can also use the temporary
|
|
||||||
``session`` collection, as provided by the GNOME Keyring.
|
|
||||||
|
|
||||||
Then, run the Python unittest module::
|
|
||||||
|
|
||||||
python3 -m unittest discover -s tests
|
|
||||||
|
|
||||||
If you want to run the tests in an isolated or headless environment, run
|
|
||||||
this command in a D-Bus session::
|
|
||||||
|
|
||||||
dbus-run-session -- python3 -m unittest discover -s tests
|
|
||||||
|
|
||||||
Get the code
|
|
||||||
============
|
|
||||||
|
|
||||||
SecretStorage is available under BSD license. The source code can be found
|
|
||||||
on GitHub_.
|
|
||||||
|
|
||||||
.. _GitHub: https://github.com/mitya57/secretstorage
|
|
||||||
@ -1,21 +0,0 @@
|
|||||||
SecretStorage-3.3.3.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
|
|
||||||
SecretStorage-3.3.3.dist-info/LICENSE,sha256=cPa_yndjPDXvohgyjtpUhtcFTCkU1hggmA43h5dSCiU,1504
|
|
||||||
SecretStorage-3.3.3.dist-info/METADATA,sha256=ZScD5voEgjme04wlw9OZigESMxLa2xG_eaIeZ_IMqJI,4027
|
|
||||||
SecretStorage-3.3.3.dist-info/RECORD,,
|
|
||||||
SecretStorage-3.3.3.dist-info/WHEEL,sha256=G16H4A3IeoQmnOrYV4ueZGKSjhipXx8zc8nu9FGlvMA,92
|
|
||||||
SecretStorage-3.3.3.dist-info/top_level.txt,sha256=hveSi1OWGaEt3kEVbjmZ0M-ASPxi6y-nTPVa-d3c0B4,14
|
|
||||||
secretstorage/__init__.py,sha256=W1p-HB1Qh12Dv6K8ml0Wj_MzN09_dEeezJjQZAHf-O4,3364
|
|
||||||
secretstorage/__pycache__/__init__.cpython-310.pyc,,
|
|
||||||
secretstorage/__pycache__/collection.cpython-310.pyc,,
|
|
||||||
secretstorage/__pycache__/defines.cpython-310.pyc,,
|
|
||||||
secretstorage/__pycache__/dhcrypto.cpython-310.pyc,,
|
|
||||||
secretstorage/__pycache__/exceptions.cpython-310.pyc,,
|
|
||||||
secretstorage/__pycache__/item.cpython-310.pyc,,
|
|
||||||
secretstorage/__pycache__/util.cpython-310.pyc,,
|
|
||||||
secretstorage/collection.py,sha256=lHwSOkFO5sRspgcUBoBI8ZG2au2bcUSO6X64ksVdnsQ,9436
|
|
||||||
secretstorage/defines.py,sha256=DzUrEWzSvBlN8kK2nVXnLGlCZv7HWNyfN1AYqRmjKGE,807
|
|
||||||
secretstorage/dhcrypto.py,sha256=BiuDoNvNmd8i7Ul4ENouRnbqFE3SUmTUSAn6RVvn7Tg,2578
|
|
||||||
secretstorage/exceptions.py,sha256=1uUZXTua4jRZf4PKDIT2SVWcSKP2lP97s8r3eJZudio,1655
|
|
||||||
secretstorage/item.py,sha256=3niFSjOzwrB2hV1jrg78RXgBsTrpw44852VpZHXUpeE,5813
|
|
||||||
secretstorage/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
||||||
secretstorage/util.py,sha256=vHu01QaooMQ5sRdRDFX9pg7rrzfJWF9vg0plm3Zg0Po,6755
|
|
||||||
@ -1,5 +0,0 @@
|
|||||||
Wheel-Version: 1.0
|
|
||||||
Generator: bdist_wheel (0.37.1)
|
|
||||||
Root-Is-Purelib: true
|
|
||||||
Tag: py3-none-any
|
|
||||||
|
|
||||||
@ -1 +0,0 @@
|
|||||||
secretstorage
|
|
||||||
Binary file not shown.
Binary file not shown.
@ -1,132 +0,0 @@
|
|||||||
import sys
|
|
||||||
import os
|
|
||||||
import re
|
|
||||||
import importlib
|
|
||||||
import warnings
|
|
||||||
|
|
||||||
|
|
||||||
is_pypy = '__pypy__' in sys.builtin_module_names
|
|
||||||
|
|
||||||
|
|
||||||
warnings.filterwarnings('ignore',
|
|
||||||
r'.+ distutils\b.+ deprecated',
|
|
||||||
DeprecationWarning)
|
|
||||||
|
|
||||||
|
|
||||||
def warn_distutils_present():
|
|
||||||
if 'distutils' not in sys.modules:
|
|
||||||
return
|
|
||||||
if is_pypy and sys.version_info < (3, 7):
|
|
||||||
# PyPy for 3.6 unconditionally imports distutils, so bypass the warning
|
|
||||||
# https://foss.heptapod.net/pypy/pypy/-/blob/be829135bc0d758997b3566062999ee8b23872b4/lib-python/3/site.py#L250
|
|
||||||
return
|
|
||||||
warnings.warn(
|
|
||||||
"Distutils was imported before Setuptools, but importing Setuptools "
|
|
||||||
"also replaces the `distutils` module in `sys.modules`. This may lead "
|
|
||||||
"to undesirable behaviors or errors. To avoid these issues, avoid "
|
|
||||||
"using distutils directly, ensure that setuptools is installed in the "
|
|
||||||
"traditional way (e.g. not an editable install), and/or make sure "
|
|
||||||
"that setuptools is always imported before distutils.")
|
|
||||||
|
|
||||||
|
|
||||||
def clear_distutils():
|
|
||||||
if 'distutils' not in sys.modules:
|
|
||||||
return
|
|
||||||
warnings.warn("Setuptools is replacing distutils.")
|
|
||||||
mods = [name for name in sys.modules if re.match(r'distutils\b', name)]
|
|
||||||
for name in mods:
|
|
||||||
del sys.modules[name]
|
|
||||||
|
|
||||||
|
|
||||||
def enabled():
|
|
||||||
"""
|
|
||||||
Allow selection of distutils by environment variable.
|
|
||||||
"""
|
|
||||||
which = os.environ.get('SETUPTOOLS_USE_DISTUTILS', 'stdlib')
|
|
||||||
return which == 'local'
|
|
||||||
|
|
||||||
|
|
||||||
def ensure_local_distutils():
|
|
||||||
clear_distutils()
|
|
||||||
|
|
||||||
# With the DistutilsMetaFinder in place,
|
|
||||||
# perform an import to cause distutils to be
|
|
||||||
# loaded from setuptools._distutils. Ref #2906.
|
|
||||||
add_shim()
|
|
||||||
importlib.import_module('distutils')
|
|
||||||
remove_shim()
|
|
||||||
|
|
||||||
# check that submodules load as expected
|
|
||||||
core = importlib.import_module('distutils.core')
|
|
||||||
assert '_distutils' in core.__file__, core.__file__
|
|
||||||
|
|
||||||
|
|
||||||
def do_override():
|
|
||||||
"""
|
|
||||||
Ensure that the local copy of distutils is preferred over stdlib.
|
|
||||||
|
|
||||||
See https://github.com/pypa/setuptools/issues/417#issuecomment-392298401
|
|
||||||
for more motivation.
|
|
||||||
"""
|
|
||||||
if enabled():
|
|
||||||
warn_distutils_present()
|
|
||||||
ensure_local_distutils()
|
|
||||||
|
|
||||||
|
|
||||||
class DistutilsMetaFinder:
|
|
||||||
def find_spec(self, fullname, path, target=None):
|
|
||||||
if path is not None:
|
|
||||||
return
|
|
||||||
|
|
||||||
method_name = 'spec_for_{fullname}'.format(**locals())
|
|
||||||
method = getattr(self, method_name, lambda: None)
|
|
||||||
return method()
|
|
||||||
|
|
||||||
def spec_for_distutils(self):
|
|
||||||
import importlib.abc
|
|
||||||
import importlib.util
|
|
||||||
|
|
||||||
class DistutilsLoader(importlib.abc.Loader):
|
|
||||||
|
|
||||||
def create_module(self, spec):
|
|
||||||
return importlib.import_module('setuptools._distutils')
|
|
||||||
|
|
||||||
def exec_module(self, module):
|
|
||||||
pass
|
|
||||||
|
|
||||||
return importlib.util.spec_from_loader('distutils', DistutilsLoader())
|
|
||||||
|
|
||||||
def spec_for_pip(self):
|
|
||||||
"""
|
|
||||||
Ensure stdlib distutils when running under pip.
|
|
||||||
See pypa/pip#8761 for rationale.
|
|
||||||
"""
|
|
||||||
if self.pip_imported_during_build():
|
|
||||||
return
|
|
||||||
clear_distutils()
|
|
||||||
self.spec_for_distutils = lambda: None
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def pip_imported_during_build():
|
|
||||||
"""
|
|
||||||
Detect if pip is being imported in a build script. Ref #2355.
|
|
||||||
"""
|
|
||||||
import traceback
|
|
||||||
return any(
|
|
||||||
frame.f_globals['__file__'].endswith('setup.py')
|
|
||||||
for frame, line in traceback.walk_stack(None)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
DISTUTILS_FINDER = DistutilsMetaFinder()
|
|
||||||
|
|
||||||
|
|
||||||
def add_shim():
|
|
||||||
sys.meta_path.insert(0, DISTUTILS_FINDER)
|
|
||||||
|
|
||||||
|
|
||||||
def remove_shim():
|
|
||||||
try:
|
|
||||||
sys.meta_path.remove(DISTUTILS_FINDER)
|
|
||||||
except ValueError:
|
|
||||||
pass
|
|
||||||
Binary file not shown.
Binary file not shown.
@ -1 +0,0 @@
|
|||||||
__import__('_distutils_hack').do_override()
|
|
||||||
@ -1 +0,0 @@
|
|||||||
pip
|
|
||||||
@ -1,17 +0,0 @@
|
|||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
of this software and associated documentation files (the "Software"), to
|
|
||||||
deal in the Software without restriction, including without limitation the
|
|
||||||
rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
|
||||||
sell copies of the Software, and to permit persons to whom the Software is
|
|
||||||
furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in
|
|
||||||
all copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
||||||
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
|
||||||
IN THE SOFTWARE.
|
|
||||||
@ -1,46 +0,0 @@
|
|||||||
Metadata-Version: 2.1
|
|
||||||
Name: backports.tarfile
|
|
||||||
Version: 1.2.0
|
|
||||||
Summary: Backport of CPython tarfile module
|
|
||||||
Author-email: "Jason R. Coombs" <jaraco@jaraco.com>
|
|
||||||
Project-URL: Homepage, https://github.com/jaraco/backports.tarfile
|
|
||||||
Classifier: Development Status :: 5 - Production/Stable
|
|
||||||
Classifier: Intended Audience :: Developers
|
|
||||||
Classifier: License :: OSI Approved :: MIT License
|
|
||||||
Classifier: Programming Language :: Python :: 3
|
|
||||||
Classifier: Programming Language :: Python :: 3 :: Only
|
|
||||||
Requires-Python: >=3.8
|
|
||||||
Description-Content-Type: text/x-rst
|
|
||||||
License-File: LICENSE
|
|
||||||
Provides-Extra: docs
|
|
||||||
Requires-Dist: sphinx >=3.5 ; extra == 'docs'
|
|
||||||
Requires-Dist: jaraco.packaging >=9.3 ; extra == 'docs'
|
|
||||||
Requires-Dist: rst.linker >=1.9 ; extra == 'docs'
|
|
||||||
Requires-Dist: furo ; extra == 'docs'
|
|
||||||
Requires-Dist: sphinx-lint ; extra == 'docs'
|
|
||||||
Provides-Extra: testing
|
|
||||||
Requires-Dist: pytest !=8.1.*,>=6 ; extra == 'testing'
|
|
||||||
Requires-Dist: pytest-checkdocs >=2.4 ; extra == 'testing'
|
|
||||||
Requires-Dist: pytest-cov ; extra == 'testing'
|
|
||||||
Requires-Dist: pytest-enabler >=2.2 ; extra == 'testing'
|
|
||||||
Requires-Dist: jaraco.test ; extra == 'testing'
|
|
||||||
Requires-Dist: pytest !=8.0.* ; extra == 'testing'
|
|
||||||
|
|
||||||
.. image:: https://img.shields.io/pypi/v/backports.tarfile.svg
|
|
||||||
:target: https://pypi.org/project/backports.tarfile
|
|
||||||
|
|
||||||
.. image:: https://img.shields.io/pypi/pyversions/backports.tarfile.svg
|
|
||||||
|
|
||||||
.. image:: https://github.com/jaraco/backports.tarfile/actions/workflows/main.yml/badge.svg
|
|
||||||
:target: https://github.com/jaraco/backports.tarfile/actions?query=workflow%3A%22tests%22
|
|
||||||
:alt: tests
|
|
||||||
|
|
||||||
.. image:: https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/charliermarsh/ruff/main/assets/badge/v2.json
|
|
||||||
:target: https://github.com/astral-sh/ruff
|
|
||||||
:alt: Ruff
|
|
||||||
|
|
||||||
.. .. image:: https://readthedocs.org/projects/backportstarfile/badge/?version=latest
|
|
||||||
.. :target: https://backportstarfile.readthedocs.io/en/latest/?badge=latest
|
|
||||||
|
|
||||||
.. image:: https://img.shields.io/badge/skeleton-2024-informational
|
|
||||||
:target: https://blog.jaraco.com/skeleton
|
|
||||||
@ -1,16 +0,0 @@
|
|||||||
backports.tarfile-1.2.0.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
|
|
||||||
backports.tarfile-1.2.0.dist-info/LICENSE,sha256=htoPAa6uRjSKPD1GUZXcHOzN55956HdppkuNoEsqR0E,1023
|
|
||||||
backports.tarfile-1.2.0.dist-info/METADATA,sha256=ghXFTq132dxaEIolxr3HK1mZqm9iyUmaRANZQSr6WlE,2020
|
|
||||||
backports.tarfile-1.2.0.dist-info/RECORD,,
|
|
||||||
backports.tarfile-1.2.0.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
|
|
||||||
backports.tarfile-1.2.0.dist-info/top_level.txt,sha256=cGjaLMOoBR1FK0ApojtzWVmViTtJ7JGIK_HwXiEsvtU,10
|
|
||||||
backports/__init__.py,sha256=iOEMwnlORWezdO8-2vxBIPSR37D7JGjluZ8f55vzxls,81
|
|
||||||
backports/__pycache__/__init__.cpython-310.pyc,,
|
|
||||||
backports/tarfile/__init__.py,sha256=Pwf2qUIfB0SolJPCKcx3vz3UEu_aids4g4sAfxy94qg,108491
|
|
||||||
backports/tarfile/__main__.py,sha256=Yw2oGT1afrz2eBskzdPYL8ReB_3liApmhFkN2EbDmc4,59
|
|
||||||
backports/tarfile/__pycache__/__init__.cpython-310.pyc,,
|
|
||||||
backports/tarfile/__pycache__/__main__.cpython-310.pyc,,
|
|
||||||
backports/tarfile/compat/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
||||||
backports/tarfile/compat/__pycache__/__init__.cpython-310.pyc,,
|
|
||||||
backports/tarfile/compat/__pycache__/py38.cpython-310.pyc,,
|
|
||||||
backports/tarfile/compat/py38.py,sha256=iYkyt_gvWjLzGUTJD9TuTfMMjOk-ersXZmRlvQYN2qE,568
|
|
||||||
@ -1,5 +0,0 @@
|
|||||||
Wheel-Version: 1.0
|
|
||||||
Generator: bdist_wheel (0.43.0)
|
|
||||||
Root-Is-Purelib: true
|
|
||||||
Tag: py3-none-any
|
|
||||||
|
|
||||||
@ -1 +0,0 @@
|
|||||||
backports
|
|
||||||
@ -1 +0,0 @@
|
|||||||
__path__ = __import__('pkgutil').extend_path(__path__, __name__) # type: ignore
|
|
||||||
Binary file not shown.
File diff suppressed because it is too large
Load Diff
@ -1,5 +0,0 @@
|
|||||||
from . import main
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
main()
|
|
||||||
Binary file not shown.
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user