chatgraph 0.6.4__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- chatgraph/__init__.py +35 -0
- chatgraph/auth/credentials.py +71 -0
- chatgraph/bot/chatbot_model.py +238 -0
- chatgraph/bot/chatbot_router.py +85 -0
- chatgraph/bot/default_functions.py +24 -0
- chatgraph/cli/__init__.py +187 -0
- chatgraph/error/chatbot_error.py +57 -0
- chatgraph/error/route_error.py +26 -0
- chatgraph/gRPC/gRPCCall.py +189 -0
- chatgraph/messages/message_consumer.py +212 -0
- chatgraph/models/actions.py +138 -0
- chatgraph/models/message.py +350 -0
- chatgraph/models/userstate.py +214 -0
- chatgraph/pb/router.proto +151 -0
- chatgraph/pb/router_pb2.py +79 -0
- chatgraph/pb/router_pb2_grpc.py +902 -0
- chatgraph/services/__init__.py +0 -0
- chatgraph/services/router_http_client.py +451 -0
- chatgraph/types/background_task.py +27 -0
- chatgraph/types/end_types.py +75 -0
- chatgraph/types/route.py +104 -0
- chatgraph/types/usercall.py +229 -0
- chatgraph-0.6.4.dist-info/METADATA +521 -0
- chatgraph-0.6.4.dist-info/RECORD +27 -0
- chatgraph-0.6.4.dist-info/WHEEL +4 -0
- chatgraph-0.6.4.dist-info/entry_points.txt +3 -0
- chatgraph-0.6.4.dist-info/licenses/LICENSE +21 -0
chatgraph/__init__.py
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
from .auth.credentials import Credential
|
|
2
|
+
from .bot.chatbot_model import ChatbotApp
|
|
3
|
+
from .bot.chatbot_router import ChatbotRouter
|
|
4
|
+
from .messages.message_consumer import MessageConsumer
|
|
5
|
+
from .models.userstate import UserState, Menu
|
|
6
|
+
from .models.message import Message, Button, File, TextMessage
|
|
7
|
+
from .types.usercall import UserCall
|
|
8
|
+
from .types.end_types import (
|
|
9
|
+
RedirectResponse,
|
|
10
|
+
EndChatResponse,
|
|
11
|
+
TransferToHuman,
|
|
12
|
+
TransferToMenu,
|
|
13
|
+
)
|
|
14
|
+
from .types.route import Route
|
|
15
|
+
from .types.background_task import BackgroundTask
|
|
16
|
+
|
|
17
|
+
__all__ = [
|
|
18
|
+
"ChatbotApp",
|
|
19
|
+
"Credential",
|
|
20
|
+
"UserCall",
|
|
21
|
+
"ChatbotRouter",
|
|
22
|
+
"RedirectResponse",
|
|
23
|
+
"MessageConsumer",
|
|
24
|
+
"Route",
|
|
25
|
+
"EndChatResponse",
|
|
26
|
+
"TransferToHuman",
|
|
27
|
+
"TransferToMenu",
|
|
28
|
+
"UserState",
|
|
29
|
+
"Menu",
|
|
30
|
+
"Message",
|
|
31
|
+
"Button",
|
|
32
|
+
"File",
|
|
33
|
+
"TextMessage",
|
|
34
|
+
"BackgroundTask",
|
|
35
|
+
]
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import os
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class Credential:
|
|
5
|
+
"""
|
|
6
|
+
Classe para gerenciar credenciais de usuário e senha, com suporte a variáveis de ambiente.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
def __init__(self, username: str | None = None, password: str | None = None):
|
|
10
|
+
"""
|
|
11
|
+
Inicializa a classe Credential com um nome de usuário e senha.
|
|
12
|
+
|
|
13
|
+
Args:
|
|
14
|
+
username (str | None): O nome de usuário. Pode ser None se for carregado de uma variável de ambiente.
|
|
15
|
+
password (str | None): A senha do usuário. Pode ser None se for carregada de uma variável de ambiente.
|
|
16
|
+
"""
|
|
17
|
+
self.__username = username
|
|
18
|
+
self.__password = password
|
|
19
|
+
|
|
20
|
+
@property
|
|
21
|
+
def password(self) -> str:
|
|
22
|
+
"""
|
|
23
|
+
Retorna a senha do usuário.
|
|
24
|
+
|
|
25
|
+
Raises:
|
|
26
|
+
ValueError: Se a senha não estiver definida.
|
|
27
|
+
|
|
28
|
+
Returns:
|
|
29
|
+
str: A senha do usuário.
|
|
30
|
+
"""
|
|
31
|
+
if not self.__password:
|
|
32
|
+
raise ValueError('Senha vazia!')
|
|
33
|
+
return self.__password
|
|
34
|
+
|
|
35
|
+
@property
|
|
36
|
+
def username(self) -> str:
|
|
37
|
+
"""
|
|
38
|
+
Retorna o nome de usuário.
|
|
39
|
+
|
|
40
|
+
Raises:
|
|
41
|
+
ValueError: Se o nome de usuário não estiver definido.
|
|
42
|
+
|
|
43
|
+
Returns:
|
|
44
|
+
str: O nome de usuário.
|
|
45
|
+
"""
|
|
46
|
+
if not self.__username:
|
|
47
|
+
raise ValueError('Usuário vazio!')
|
|
48
|
+
return self.__username
|
|
49
|
+
|
|
50
|
+
@classmethod
|
|
51
|
+
def load_dotenv(cls, user_env: str = 'CHATBOT_USER', pass_env: str = 'CHATBOT_PASS') -> 'Credential':
|
|
52
|
+
"""
|
|
53
|
+
Carrega as credenciais de variáveis de ambiente e retorna uma instância da classe Credential.
|
|
54
|
+
|
|
55
|
+
Args:
|
|
56
|
+
user_env (str): Nome da variável de ambiente que armazena o nome de usuário. Padrão é 'CHATBOT_USER'.
|
|
57
|
+
pass_env (str): Nome da variável de ambiente que armazena a senha. Padrão é 'CHATBOT_PASS'.
|
|
58
|
+
|
|
59
|
+
Raises:
|
|
60
|
+
ValueError: Se o nome de usuário ou a senha não estiverem definidas nas variáveis de ambiente.
|
|
61
|
+
|
|
62
|
+
Returns:
|
|
63
|
+
Credential: Uma instância da classe Credential com as credenciais carregadas.
|
|
64
|
+
"""
|
|
65
|
+
username = os.getenv(user_env)
|
|
66
|
+
password = os.getenv(pass_env)
|
|
67
|
+
|
|
68
|
+
if not username or not password:
|
|
69
|
+
raise ValueError('Corrija as variáveis de ambiente!')
|
|
70
|
+
|
|
71
|
+
return cls(username=username, password=password)
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import inspect
|
|
3
|
+
import re
|
|
4
|
+
from functools import wraps
|
|
5
|
+
from logging import debug, error
|
|
6
|
+
from typing import Optional, Callable
|
|
7
|
+
|
|
8
|
+
from ..error.chatbot_error import ChatbotMessageError
|
|
9
|
+
from ..messages.message_consumer import MessageConsumer
|
|
10
|
+
from ..models.message import MessageTypes, Message, File
|
|
11
|
+
from ..types.usercall import UserCall
|
|
12
|
+
from ..types.end_types import (
|
|
13
|
+
RedirectResponse,
|
|
14
|
+
EndChatResponse,
|
|
15
|
+
TransferToMenu,
|
|
16
|
+
)
|
|
17
|
+
from ..types.route import Route
|
|
18
|
+
from .chatbot_router import ChatbotRouter
|
|
19
|
+
from ..types.background_task import BackgroundTask
|
|
20
|
+
from .default_functions import voltar
|
|
21
|
+
|
|
22
|
+
DEFAULT_FUNCTION: dict[str, Callable] = {
|
|
23
|
+
r'^\s*(voltar)\s*$': voltar,
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class ChatbotApp:
|
|
28
|
+
"""
|
|
29
|
+
Classe principal para a aplicação do chatbot, gerencia as rotas e a lógica de processamento de mensagens.
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
def __init__(
|
|
33
|
+
self,
|
|
34
|
+
message_consumer: Optional[MessageConsumer] = None,
|
|
35
|
+
default_functions: dict[str, Callable] = DEFAULT_FUNCTION,
|
|
36
|
+
):
|
|
37
|
+
"""
|
|
38
|
+
Inicializa a classe ChatbotApp com um estado de usuário e um consumidor de mensagens.
|
|
39
|
+
|
|
40
|
+
Args:
|
|
41
|
+
message_consumer (MessageConsumer): O consumidor de mensagens que lida com a entrada de mensagens no sistema.
|
|
42
|
+
default_functions (dict[str, callable]): Dicionário de funções padrão que podem ser usadas antes das rotas.
|
|
43
|
+
"""
|
|
44
|
+
if not message_consumer:
|
|
45
|
+
message_consumer = MessageConsumer.load_dotenv()
|
|
46
|
+
|
|
47
|
+
self.default_functions = default_functions
|
|
48
|
+
self.__message_consumer = message_consumer
|
|
49
|
+
self.__routes = {}
|
|
50
|
+
|
|
51
|
+
def include_router(self, router: ChatbotRouter) -> None:
|
|
52
|
+
"""
|
|
53
|
+
Inclui um roteador de chatbot com um prefixo nas rotas da aplicação.
|
|
54
|
+
|
|
55
|
+
Args:
|
|
56
|
+
router (ChatbotRouter): O roteador contendo as rotas a serem adicionadas.
|
|
57
|
+
"""
|
|
58
|
+
self.__routes.update(router.routes)
|
|
59
|
+
|
|
60
|
+
def route(self, route_name: str) -> Callable:
|
|
61
|
+
"""
|
|
62
|
+
Decorador para adicionar uma função como uma rota na aplicação do chatbot.
|
|
63
|
+
|
|
64
|
+
Args:
|
|
65
|
+
route_name (str): O nome da rota para a qual a função deve ser associada.
|
|
66
|
+
|
|
67
|
+
Returns:
|
|
68
|
+
function: O decorador que adiciona a função à rota especificada.
|
|
69
|
+
"""
|
|
70
|
+
route_name = route_name.strip().lower()
|
|
71
|
+
|
|
72
|
+
def decorator(func):
|
|
73
|
+
params = {}
|
|
74
|
+
signature = inspect.signature(func)
|
|
75
|
+
output_param = signature.return_annotation
|
|
76
|
+
|
|
77
|
+
for name, param in signature.parameters.items():
|
|
78
|
+
param_type = (
|
|
79
|
+
param.annotation
|
|
80
|
+
if param.annotation != inspect.Parameter.empty
|
|
81
|
+
else 'Any'
|
|
82
|
+
)
|
|
83
|
+
params[param_type] = name
|
|
84
|
+
debug(f'Parameter: {name}, Type: {param_type}')
|
|
85
|
+
|
|
86
|
+
self.__routes[route_name] = {
|
|
87
|
+
'function': func,
|
|
88
|
+
'params': params,
|
|
89
|
+
'return': output_param,
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
@wraps(func)
|
|
93
|
+
async def wrapper(*args, **kwargs):
|
|
94
|
+
return await func(*args, **kwargs)
|
|
95
|
+
|
|
96
|
+
return wrapper
|
|
97
|
+
|
|
98
|
+
return decorator
|
|
99
|
+
|
|
100
|
+
def start(self):
|
|
101
|
+
"""
|
|
102
|
+
Inicia o consumo de mensagens pelo chatbot,
|
|
103
|
+
processando cada mensagem recebida.
|
|
104
|
+
"""
|
|
105
|
+
self.__message_consumer.reprer()
|
|
106
|
+
asyncio.run(
|
|
107
|
+
self.__message_consumer.start_consume(self.process_message)
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
async def process_message(self, usercall: UserCall) -> None:
|
|
111
|
+
"""
|
|
112
|
+
Processa uma mensagem recebida, identificando a rota correspondente
|
|
113
|
+
e executando a função associada.
|
|
114
|
+
|
|
115
|
+
Args:
|
|
116
|
+
usercall (UserCall): A mensagem a ser processada.
|
|
117
|
+
|
|
118
|
+
Raises:
|
|
119
|
+
ChatbotMessageError: Se nenhuma rota for encontrada para
|
|
120
|
+
o menu atual do usuário.
|
|
121
|
+
"""
|
|
122
|
+
user_id = usercall.user_id
|
|
123
|
+
route = usercall.route.lower()
|
|
124
|
+
route_handler = route.split('.')[-1]
|
|
125
|
+
|
|
126
|
+
matchDefault = False
|
|
127
|
+
|
|
128
|
+
for regex, func in self.default_functions.items():
|
|
129
|
+
if re.match(regex, usercall.content_message):
|
|
130
|
+
matchDefault = True
|
|
131
|
+
debug(
|
|
132
|
+
f'Função padrão encontrada: {func.__name__} para a rota {route}'
|
|
133
|
+
)
|
|
134
|
+
handler = {
|
|
135
|
+
'function': func,
|
|
136
|
+
'params': {UserCall: 'usercall', Route: 'route'},
|
|
137
|
+
}
|
|
138
|
+
break
|
|
139
|
+
|
|
140
|
+
if not matchDefault:
|
|
141
|
+
handler = self.__routes.get(route_handler, None)
|
|
142
|
+
|
|
143
|
+
if not handler:
|
|
144
|
+
raise ChatbotMessageError(
|
|
145
|
+
user_id, f'Rota não encontrada para {route}!'
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
func = handler['function']
|
|
149
|
+
usercall_name = handler['params'].get(UserCall, None)
|
|
150
|
+
route_state_name = handler['params'].get(Route, None)
|
|
151
|
+
|
|
152
|
+
kwargs = {}
|
|
153
|
+
if usercall_name:
|
|
154
|
+
kwargs[usercall_name] = usercall
|
|
155
|
+
if route_state_name:
|
|
156
|
+
kwargs[route_state_name] = Route(route, list(self.__routes.keys()))
|
|
157
|
+
|
|
158
|
+
if asyncio.iscoroutinefunction(func):
|
|
159
|
+
usercall_response = await func(**kwargs)
|
|
160
|
+
else:
|
|
161
|
+
loop = asyncio.get_running_loop()
|
|
162
|
+
usercall_response = await loop.run_in_executor(
|
|
163
|
+
None, lambda: func(**kwargs)
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
if matchDefault:
|
|
167
|
+
usercall.content_message = ''
|
|
168
|
+
|
|
169
|
+
if isinstance(usercall_response, (list, tuple)):
|
|
170
|
+
for response in usercall_response:
|
|
171
|
+
await self.__process_func_response(
|
|
172
|
+
response, usercall, route=route
|
|
173
|
+
)
|
|
174
|
+
else:
|
|
175
|
+
await self.__process_func_response(
|
|
176
|
+
usercall_response, usercall, route=route
|
|
177
|
+
)
|
|
178
|
+
|
|
179
|
+
async def __process_func_response(
|
|
180
|
+
self,
|
|
181
|
+
usercall_response,
|
|
182
|
+
usercall: UserCall,
|
|
183
|
+
route: str,
|
|
184
|
+
) -> None:
|
|
185
|
+
"""
|
|
186
|
+
Processa a resposta de uma função associada a uma rota,
|
|
187
|
+
enviando mensagens ou ajustando estados.
|
|
188
|
+
|
|
189
|
+
Args:
|
|
190
|
+
usercall_response:
|
|
191
|
+
A resposta gerada pela função da rota.
|
|
192
|
+
usercall (UserCall):
|
|
193
|
+
O objeto UserCall associado à mensagem processada.
|
|
194
|
+
route (str):
|
|
195
|
+
O nome da rota atual.
|
|
196
|
+
"""
|
|
197
|
+
loop = asyncio.get_running_loop()
|
|
198
|
+
|
|
199
|
+
if isinstance(usercall_response, (MessageTypes, Message, File)):
|
|
200
|
+
# Envia o resultado como mensagem (executando a chamada síncrona no executor)
|
|
201
|
+
await usercall.send(usercall_response)
|
|
202
|
+
return
|
|
203
|
+
|
|
204
|
+
if isinstance(usercall_response, Route):
|
|
205
|
+
await usercall.set_route(usercall_response.current)
|
|
206
|
+
return
|
|
207
|
+
|
|
208
|
+
if isinstance(usercall_response, EndChatResponse):
|
|
209
|
+
await usercall.end_chat(
|
|
210
|
+
usercall_response.end_chat_id,
|
|
211
|
+
end_action_name=usercall_response.end_chat_name,
|
|
212
|
+
)
|
|
213
|
+
return
|
|
214
|
+
|
|
215
|
+
if isinstance(usercall_response, TransferToMenu):
|
|
216
|
+
await usercall.transfer_to_menu(
|
|
217
|
+
usercall_response.menu,
|
|
218
|
+
usercall_response.user_message,
|
|
219
|
+
)
|
|
220
|
+
return
|
|
221
|
+
|
|
222
|
+
if isinstance(usercall_response, RedirectResponse):
|
|
223
|
+
await usercall.set_route(usercall_response.route)
|
|
224
|
+
await self.process_message(usercall)
|
|
225
|
+
return
|
|
226
|
+
|
|
227
|
+
if not usercall_response:
|
|
228
|
+
route = route + '.' + route.split('.')[-1]
|
|
229
|
+
await usercall.set_route(route)
|
|
230
|
+
return
|
|
231
|
+
|
|
232
|
+
if isinstance(usercall_response, BackgroundTask):
|
|
233
|
+
response = await usercall_response.run()
|
|
234
|
+
await self.__process_func_response(response, usercall, route=route)
|
|
235
|
+
return
|
|
236
|
+
|
|
237
|
+
error('Tipo de retorno inválido!')
|
|
238
|
+
return None
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
|
|
2
|
+
import inspect
|
|
3
|
+
from functools import wraps
|
|
4
|
+
from logging import debug
|
|
5
|
+
|
|
6
|
+
from ..error.chatbot_error import ChatbotError
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class ChatbotRouter:
|
|
10
|
+
"""
|
|
11
|
+
Classe responsável por gerenciar e registrar as rotas do chatbot, associando-as a funções específicas.
|
|
12
|
+
|
|
13
|
+
Atributos:
|
|
14
|
+
routes (dict): Um dicionário que armazena as rotas do chatbot e suas funções associadas.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
def __init__(self):
|
|
18
|
+
"""
|
|
19
|
+
Inicializa a classe ChatbotRouter com um dicionário vazio de rotas.
|
|
20
|
+
"""
|
|
21
|
+
self.__routes = {}
|
|
22
|
+
|
|
23
|
+
@property
|
|
24
|
+
def routes(self):
|
|
25
|
+
"""
|
|
26
|
+
Retorna as rotas registradas no roteador.
|
|
27
|
+
|
|
28
|
+
Returns:
|
|
29
|
+
dict: Um dicionário contendo as rotas e funções associadas.
|
|
30
|
+
"""
|
|
31
|
+
return self.__routes
|
|
32
|
+
|
|
33
|
+
def route(self, route_name: str):
|
|
34
|
+
"""
|
|
35
|
+
Decorador para adicionar uma função como uma rota no roteador do chatbot.
|
|
36
|
+
|
|
37
|
+
Args:
|
|
38
|
+
route_name (str): O nome da rota para a qual a função deve ser associada.
|
|
39
|
+
|
|
40
|
+
Returns:
|
|
41
|
+
function: O decorador que adiciona a função à rota especificada.
|
|
42
|
+
"""
|
|
43
|
+
|
|
44
|
+
route_name = route_name.strip().lower()
|
|
45
|
+
|
|
46
|
+
def decorator(func):
|
|
47
|
+
params = dict()
|
|
48
|
+
signature = inspect.signature(func)
|
|
49
|
+
output_param = signature.return_annotation
|
|
50
|
+
|
|
51
|
+
for name, param in signature.parameters.items():
|
|
52
|
+
param_type = (
|
|
53
|
+
param.annotation
|
|
54
|
+
if param.annotation != inspect.Parameter.empty
|
|
55
|
+
else 'Any'
|
|
56
|
+
)
|
|
57
|
+
params[param_type] = name
|
|
58
|
+
debug(f'Parameter: {name}, Type: {param_type}')
|
|
59
|
+
|
|
60
|
+
self.__routes[route_name] = {
|
|
61
|
+
'function': func,
|
|
62
|
+
'params': params,
|
|
63
|
+
'return': output_param,
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
@wraps(func)
|
|
67
|
+
def wrapper(*args, **kwargs):
|
|
68
|
+
return func(*args, **kwargs)
|
|
69
|
+
|
|
70
|
+
return wrapper
|
|
71
|
+
|
|
72
|
+
return decorator
|
|
73
|
+
|
|
74
|
+
def include_router(self, router: 'ChatbotRouter'):
|
|
75
|
+
"""
|
|
76
|
+
Inclui outro roteador com um prefixo nas rotas do roteador atual.
|
|
77
|
+
|
|
78
|
+
Args:
|
|
79
|
+
router (ChatbotRouter): O roteador contendo as rotas a serem adicionadas.
|
|
80
|
+
prefix (str): O prefixo a ser adicionado às rotas do roteador.
|
|
81
|
+
|
|
82
|
+
Raises:
|
|
83
|
+
ChatbotError: Se a rota 'start' não for encontrada no roteador fornecido.
|
|
84
|
+
"""
|
|
85
|
+
self.__routes.update(router.__routes)
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
from ..types.usercall import UserCall
|
|
2
|
+
from ..models.message import Message, Button
|
|
3
|
+
from ..types.end_types import (
|
|
4
|
+
RedirectResponse,
|
|
5
|
+
EndChatResponse,
|
|
6
|
+
TransferToHuman,
|
|
7
|
+
TransferToMenu,
|
|
8
|
+
)
|
|
9
|
+
from ..types.route import Route
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
async def voltar(route: Route, userCall: UserCall) -> RedirectResponse:
|
|
13
|
+
"""
|
|
14
|
+
Função para voltar à rota anterior.
|
|
15
|
+
Args:
|
|
16
|
+
route (Route): A rota atual do chatbot.
|
|
17
|
+
usercall (UserCall): O objeto UserCall associado à chamada do usuário.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
previous = route.get_previous()
|
|
21
|
+
userCall.console.print(
|
|
22
|
+
f"Voltando rota. ({route.current}) -> ({previous.current})", style="bold yellow"
|
|
23
|
+
)
|
|
24
|
+
return RedirectResponse(previous.current_node)
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
import typer
|
|
2
|
+
from rich.console import Console
|
|
3
|
+
from rich.table import Table
|
|
4
|
+
from dotenv import load_dotenv
|
|
5
|
+
from ..gRPC.gRPCCall import WhatsappServiceClient, UserStateServiceClient
|
|
6
|
+
import os, re
|
|
7
|
+
|
|
8
|
+
load_dotenv()
|
|
9
|
+
app = typer.Typer()
|
|
10
|
+
|
|
11
|
+
@app.command()
|
|
12
|
+
def configs():
|
|
13
|
+
"""Recupera os IDs necessários."""
|
|
14
|
+
console = Console()
|
|
15
|
+
|
|
16
|
+
username = os.getenv('RABBIT_USER')
|
|
17
|
+
password = os.getenv('RABBIT_PASS')
|
|
18
|
+
url = os.getenv('RABBIT_URI')
|
|
19
|
+
queue = os.getenv('RABBIT_QUEUE')
|
|
20
|
+
prefetch = os.getenv('RABBIT_PREFETCH', 1)
|
|
21
|
+
vhost = os.getenv('RABBIT_VHOST', '/')
|
|
22
|
+
grpc = os.getenv('GRPC_URI')
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
tableRabbit = Table(show_header=True, header_style="bold magenta", title="RabbitMQ Consumer")
|
|
26
|
+
tableRabbit.add_column("Atributo", justify="center", style="cyan", no_wrap=True)
|
|
27
|
+
tableRabbit.add_column("Valor", justify="center", style="magenta")
|
|
28
|
+
|
|
29
|
+
tableRabbit.add_row("Virtual Host", vhost)
|
|
30
|
+
tableRabbit.add_row("Prefetch Count", str(prefetch))
|
|
31
|
+
tableRabbit.add_row("Queue Consume", queue)
|
|
32
|
+
tableRabbit.add_row("AMQP URL", url)
|
|
33
|
+
tableRabbit.add_row("Username", username)
|
|
34
|
+
tableRabbit.add_row("Password", "******")
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
tableGRPC = Table(show_header=True, header_style="bold magenta", title="gRPC Consumer")
|
|
38
|
+
tableGRPC.add_column("Atributo", justify="center", style="cyan", no_wrap=True)
|
|
39
|
+
tableGRPC.add_column("Valor", justify="center", style="magenta")
|
|
40
|
+
|
|
41
|
+
tableGRPC.add_row("URI", grpc)
|
|
42
|
+
|
|
43
|
+
console.print(tableGRPC, justify="center")
|
|
44
|
+
console.print(tableRabbit, justify="center")
|
|
45
|
+
|
|
46
|
+
@app.command()
|
|
47
|
+
def campaigns(regex: str = typer.Option(None, "--regex", "-r", help="Filtro regex para campanhas.")):
|
|
48
|
+
"""Recupera as campanhas cadastradas."""
|
|
49
|
+
grpc = os.getenv('GRPC_URI')
|
|
50
|
+
|
|
51
|
+
wwp = WhatsappServiceClient(grpc)
|
|
52
|
+
campaigns = wwp.get_all_campaigns()
|
|
53
|
+
|
|
54
|
+
console = Console()
|
|
55
|
+
tableGRPC = Table(show_header=True, header_style="bold magenta", title="Campaigns")
|
|
56
|
+
tableGRPC.add_column("campaign_id", justify="center", style="cyan", no_wrap=True)
|
|
57
|
+
tableGRPC.add_column("campaign_name", justify="center", style="magenta")
|
|
58
|
+
tableGRPC.add_column("last_update", justify="center", style="magenta")
|
|
59
|
+
|
|
60
|
+
# Aplicar filtro de regex, se fornecido
|
|
61
|
+
filtered_campaigns = campaigns.campaigns
|
|
62
|
+
if regex:
|
|
63
|
+
pattern = re.compile(regex, re.IGNORECASE)
|
|
64
|
+
filtered_campaigns = [
|
|
65
|
+
campaign for campaign in campaigns.campaigns
|
|
66
|
+
if pattern.search(campaign.campaign_name)
|
|
67
|
+
]
|
|
68
|
+
|
|
69
|
+
for campaign in filtered_campaigns:
|
|
70
|
+
tableGRPC.add_row(campaign.campaign_id, campaign.campaign_name, campaign.last_update)
|
|
71
|
+
|
|
72
|
+
console.print(tableGRPC, justify="center")
|
|
73
|
+
|
|
74
|
+
@app.command()
|
|
75
|
+
def tabulations(
|
|
76
|
+
regex: str = typer.Option(None, "--regex", "-r", help="Filtro regex para as tabulações.")
|
|
77
|
+
):
|
|
78
|
+
"""Recupera as tabulações cadastradas."""
|
|
79
|
+
grpc = os.getenv('GRPC_URI')
|
|
80
|
+
|
|
81
|
+
wwp = WhatsappServiceClient(grpc)
|
|
82
|
+
tabulations = wwp.get_all_tabulations()
|
|
83
|
+
|
|
84
|
+
console = Console()
|
|
85
|
+
tableGRPC = Table(show_header=True, header_style="bold magenta", title="Tabulations")
|
|
86
|
+
tableGRPC.add_column("tabulation_id", justify="center", style="cyan", no_wrap=True)
|
|
87
|
+
tableGRPC.add_column("tabulation_name", justify="center", style="magenta")
|
|
88
|
+
tableGRPC.add_column("tabulation_type", justify="center", style="magenta")
|
|
89
|
+
tableGRPC.add_column("group_name", justify="center", style="magenta")
|
|
90
|
+
tableGRPC.add_column("customer_service_survey_id", justify="center", style="magenta")
|
|
91
|
+
tableGRPC.add_column("last_update", justify="center", style="magenta")
|
|
92
|
+
|
|
93
|
+
# Aplicar filtro de regex, se fornecido
|
|
94
|
+
filtered_tabulations = tabulations.tabulations
|
|
95
|
+
if regex:
|
|
96
|
+
pattern = re.compile(regex, re.IGNORECASE)
|
|
97
|
+
filtered_tabulations = [
|
|
98
|
+
tabulation for tabulation in tabulations.tabulations
|
|
99
|
+
if (
|
|
100
|
+
pattern.search(tabulation.tabulation_id) or
|
|
101
|
+
pattern.search(tabulation.tabulation_name) or
|
|
102
|
+
pattern.search(tabulation.tabulation_type) or
|
|
103
|
+
pattern.search(tabulation.group_name) or
|
|
104
|
+
(tabulation.customer_service_survey_id and pattern.search(tabulation.customer_service_survey_id)) or
|
|
105
|
+
pattern.search(tabulation.last_update)
|
|
106
|
+
)
|
|
107
|
+
]
|
|
108
|
+
|
|
109
|
+
for tabulation in filtered_tabulations:
|
|
110
|
+
tableGRPC.add_row(
|
|
111
|
+
tabulation.tabulation_id,
|
|
112
|
+
tabulation.tabulation_name,
|
|
113
|
+
tabulation.tabulation_type,
|
|
114
|
+
tabulation.group_name,
|
|
115
|
+
tabulation.customer_service_survey_id or "N/A",
|
|
116
|
+
tabulation.last_update,
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
console.print(tableGRPC, justify="center")
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
@app.command("ustate")
|
|
123
|
+
def user_state(
|
|
124
|
+
regex: str = typer.Option(None, "--regex", "-r", help="Filtro regex para os estados do usuário.")
|
|
125
|
+
):
|
|
126
|
+
"""Recupera os UserState em operação no momento."""
|
|
127
|
+
grpc = os.getenv('GRPC_URI')
|
|
128
|
+
|
|
129
|
+
ustate = UserStateServiceClient(grpc)
|
|
130
|
+
userstates = ustate.get_all_user_states()
|
|
131
|
+
|
|
132
|
+
console = Console()
|
|
133
|
+
tableGRPC = Table(show_header=True, header_style="bold magenta", title="User States")
|
|
134
|
+
tableGRPC.add_column("user_id", justify="center", style="cyan", no_wrap=True)
|
|
135
|
+
tableGRPC.add_column("menu_id", justify="center", style="magenta")
|
|
136
|
+
tableGRPC.add_column("route", justify="center", style="magenta")
|
|
137
|
+
tableGRPC.add_column("obs", justify="center", style="magenta")
|
|
138
|
+
tableGRPC.add_column("date", justify="center", style="magenta")
|
|
139
|
+
tableGRPC.add_column("direction", justify="center", style="magenta")
|
|
140
|
+
|
|
141
|
+
# Aplicar filtro de regex, se fornecido
|
|
142
|
+
filtered_user_states = userstates.user_states
|
|
143
|
+
if regex:
|
|
144
|
+
pattern = re.compile(regex, re.IGNORECASE)
|
|
145
|
+
filtered_user_states = [
|
|
146
|
+
userstate for userstate in userstates.user_states
|
|
147
|
+
if (
|
|
148
|
+
pattern.search(userstate.user_id) or
|
|
149
|
+
pattern.search(userstate.menu_id) or
|
|
150
|
+
pattern.search(userstate.route) or
|
|
151
|
+
pattern.search(userstate.obs) or
|
|
152
|
+
pattern.search(userstate.date) or
|
|
153
|
+
pattern.search(str(userstate.direction))
|
|
154
|
+
)
|
|
155
|
+
]
|
|
156
|
+
|
|
157
|
+
for userstate in filtered_user_states:
|
|
158
|
+
tableGRPC.add_row(
|
|
159
|
+
userstate.user_id,
|
|
160
|
+
userstate.menu_id,
|
|
161
|
+
userstate.route,
|
|
162
|
+
userstate.obs,
|
|
163
|
+
userstate.date,
|
|
164
|
+
str(userstate.direction),
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
console.print(tableGRPC, justify="center")
|
|
168
|
+
|
|
169
|
+
@app.command("del-ustate")
|
|
170
|
+
def delete_user_state(user_id: str = typer.Argument(..., help="ID do UserState a ser deletado.")):
|
|
171
|
+
"""Deleta um UserState em operação no momento."""
|
|
172
|
+
grpc = os.getenv('GRPC_URI')
|
|
173
|
+
|
|
174
|
+
ustate = UserStateServiceClient(grpc)
|
|
175
|
+
|
|
176
|
+
# Chama o método para deletar o UserState usando o ID fornecido
|
|
177
|
+
try:
|
|
178
|
+
success = ustate.delete_user_state(user_id)
|
|
179
|
+
if success:
|
|
180
|
+
typer.echo(f"UserState com ID '{user_id}' deletado com sucesso.")
|
|
181
|
+
else:
|
|
182
|
+
typer.echo(f"Falha ao deletar UserState com ID '{user_id}'.", err=True)
|
|
183
|
+
except Exception as e:
|
|
184
|
+
typer.echo(f"Erro ao tentar deletar UserState: {e}", err=True)
|
|
185
|
+
|
|
186
|
+
def main():
|
|
187
|
+
app()
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
class ChatbotError(Exception):
|
|
2
|
+
"""
|
|
3
|
+
Exceção personalizada para erros gerais relacionados ao chatbot.
|
|
4
|
+
|
|
5
|
+
Atributos:
|
|
6
|
+
message (str): A mensagem de erro descrevendo o problema.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
def __init__(self, message: str):
|
|
10
|
+
"""
|
|
11
|
+
Inicializa a exceção ChatbotError com uma mensagem de erro.
|
|
12
|
+
|
|
13
|
+
Args:
|
|
14
|
+
message (str): A mensagem de erro descrevendo o problema.
|
|
15
|
+
"""
|
|
16
|
+
self.message = message
|
|
17
|
+
super().__init__(self.message)
|
|
18
|
+
|
|
19
|
+
def __str__(self):
|
|
20
|
+
"""
|
|
21
|
+
Retorna a representação em string da exceção ChatbotError.
|
|
22
|
+
|
|
23
|
+
Returns:
|
|
24
|
+
str: Uma string formatada que inclui o nome da exceção e a mensagem de erro.
|
|
25
|
+
"""
|
|
26
|
+
return f'ChatbotError: {self.message}'
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class ChatbotMessageError(Exception):
|
|
30
|
+
"""
|
|
31
|
+
Exceção personalizada para erros relacionados a mensagens de clientes no chatbot.
|
|
32
|
+
|
|
33
|
+
Atributos:
|
|
34
|
+
customer_id (str): O ID do cliente relacionado ao erro.
|
|
35
|
+
message (str): A mensagem de erro descrevendo o problema, incluindo o ID do cliente.
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
def __init__(self, customer_id: str, message: str):
|
|
39
|
+
"""
|
|
40
|
+
Inicializa a exceção ChatbotMessageError com um ID de cliente e uma mensagem de erro.
|
|
41
|
+
|
|
42
|
+
Args:
|
|
43
|
+
customer_id (str): O ID do cliente relacionado ao erro.
|
|
44
|
+
message (str): A mensagem de erro descrevendo o problema.
|
|
45
|
+
"""
|
|
46
|
+
self.customer_id = customer_id
|
|
47
|
+
self.message = f'{message} ID recebido: {customer_id}'
|
|
48
|
+
super().__init__(self.message)
|
|
49
|
+
|
|
50
|
+
def __str__(self):
|
|
51
|
+
"""
|
|
52
|
+
Retorna a representação em string da exceção ChatbotMessageError.
|
|
53
|
+
|
|
54
|
+
Returns:
|
|
55
|
+
str: Uma string formatada que inclui o nome da exceção e a mensagem de erro.
|
|
56
|
+
"""
|
|
57
|
+
return f'ChatbotMessageError: {self.message}'
|