- Stack completo con Mattermost, NocoDB y Sales Bot - Procesamiento OCR de tickets con Tesseract - Sistema de comisiones por tubos de tinte - Comandos slash /metas y /ranking - Documentación completa del proyecto Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
202 lines
6.6 KiB
Python
202 lines
6.6 KiB
Python
import requests
|
|
import logging
|
|
import os
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
class MattermostClient:
|
|
def __init__(self, url, token):
|
|
self.url = url.rstrip('/')
|
|
self.token = token
|
|
self.api_url = f"{self.url}/api/v4"
|
|
self.headers = {
|
|
'Authorization': f'Bearer {self.token}',
|
|
'Content-Type': 'application/json'
|
|
}
|
|
self.webhook_url = os.getenv('MATTERMOST_WEBHOOK_URL')
|
|
|
|
def test_connection(self):
|
|
"""Prueba la conexión con Mattermost"""
|
|
try:
|
|
response = requests.get(
|
|
f"{self.api_url}/users/me",
|
|
headers=self.headers,
|
|
timeout=10
|
|
)
|
|
response.raise_for_status()
|
|
user = response.json()
|
|
logger.info(f"Conexión exitosa con Mattermost. Bot: {user.get('username')}")
|
|
return {
|
|
'status': 'success',
|
|
'bot_username': user.get('username'),
|
|
'bot_id': user.get('id')
|
|
}
|
|
except Exception as e:
|
|
logger.error(f"Error conectando con Mattermost: {str(e)}")
|
|
return {'status': 'error', 'message': str(e)}
|
|
|
|
def get_channel_by_name(self, team_name, channel_name):
|
|
"""Obtiene información de un canal por nombre"""
|
|
try:
|
|
# Primero obtener el team
|
|
response = requests.get(
|
|
f"{self.api_url}/teams/name/{team_name}",
|
|
headers=self.headers,
|
|
timeout=10
|
|
)
|
|
response.raise_for_status()
|
|
team = response.json()
|
|
team_id = team['id']
|
|
|
|
# Luego obtener el canal
|
|
response = requests.get(
|
|
f"{self.api_url}/teams/{team_id}/channels/name/{channel_name}",
|
|
headers=self.headers,
|
|
timeout=10
|
|
)
|
|
response.raise_for_status()
|
|
return response.json()
|
|
except Exception as e:
|
|
logger.error(f"Error obteniendo canal {channel_name}: {str(e)}")
|
|
return None
|
|
|
|
def post_message(self, channel_id, message, props=None):
|
|
"""Publica un mensaje en un canal"""
|
|
try:
|
|
payload = {
|
|
'channel_id': channel_id,
|
|
'message': message
|
|
}
|
|
if props:
|
|
payload['props'] = props
|
|
|
|
response = requests.post(
|
|
f"{self.api_url}/posts",
|
|
headers=self.headers,
|
|
json=payload,
|
|
timeout=10
|
|
)
|
|
response.raise_for_status()
|
|
logger.info(f"Mensaje publicado en canal {channel_id}")
|
|
return response.json()
|
|
except Exception as e:
|
|
logger.error(f"Error publicando mensaje: {str(e)}")
|
|
return None
|
|
|
|
def post_message_webhook(self, message, username=None, icon_emoji=None):
|
|
"""Publica un mensaje usando webhook incoming"""
|
|
try:
|
|
payload = {'text': message}
|
|
if username:
|
|
payload['username'] = username
|
|
if icon_emoji:
|
|
payload['icon_emoji'] = icon_emoji
|
|
|
|
response = requests.post(
|
|
self.webhook_url,
|
|
json=payload,
|
|
timeout=10
|
|
)
|
|
response.raise_for_status()
|
|
logger.info("Mensaje publicado via webhook")
|
|
return True
|
|
except Exception as e:
|
|
logger.error(f"Error publicando via webhook: {str(e)}")
|
|
return False
|
|
|
|
def get_file(self, file_id):
|
|
"""Descarga un archivo de Mattermost"""
|
|
try:
|
|
response = requests.get(
|
|
f"{self.api_url}/files/{file_id}",
|
|
headers=self.headers,
|
|
timeout=30
|
|
)
|
|
response.raise_for_status()
|
|
return response.content
|
|
except Exception as e:
|
|
logger.error(f"Error descargando archivo {file_id}: {str(e)}")
|
|
return None
|
|
|
|
def get_file_info(self, file_id):
|
|
"""Obtiene información de un archivo"""
|
|
try:
|
|
response = requests.get(
|
|
f"{self.api_url}/files/{file_id}/info",
|
|
headers=self.headers,
|
|
timeout=10
|
|
)
|
|
response.raise_for_status()
|
|
return response.json()
|
|
except Exception as e:
|
|
logger.error(f"Error obteniendo info de archivo {file_id}: {str(e)}")
|
|
return None
|
|
|
|
def upload_file(self, channel_id, file_content, filename):
|
|
"""Sube un archivo a Mattermost"""
|
|
try:
|
|
files = {
|
|
'files': (filename, file_content)
|
|
}
|
|
data = {
|
|
'channel_id': channel_id
|
|
}
|
|
headers = {
|
|
'Authorization': f'Bearer {self.token}'
|
|
}
|
|
|
|
response = requests.post(
|
|
f"{self.api_url}/files",
|
|
headers=headers,
|
|
files=files,
|
|
data=data,
|
|
timeout=30
|
|
)
|
|
response.raise_for_status()
|
|
return response.json()
|
|
except Exception as e:
|
|
logger.error(f"Error subiendo archivo: {str(e)}")
|
|
return None
|
|
|
|
def add_reaction(self, post_id, emoji_name):
|
|
"""Agrega una reacción a un post"""
|
|
try:
|
|
# Obtener el user_id del bot
|
|
me = requests.get(
|
|
f"{self.api_url}/users/me",
|
|
headers=self.headers,
|
|
timeout=10
|
|
).json()
|
|
|
|
payload = {
|
|
'user_id': me['id'],
|
|
'post_id': post_id,
|
|
'emoji_name': emoji_name
|
|
}
|
|
|
|
response = requests.post(
|
|
f"{self.api_url}/reactions",
|
|
headers=self.headers,
|
|
json=payload,
|
|
timeout=10
|
|
)
|
|
response.raise_for_status()
|
|
return True
|
|
except Exception as e:
|
|
logger.error(f"Error agregando reacción: {str(e)}")
|
|
return False
|
|
|
|
def get_user_by_username(self, username):
|
|
"""Obtiene información de un usuario por username"""
|
|
try:
|
|
response = requests.get(
|
|
f"{self.api_url}/users/username/{username}",
|
|
headers=self.headers,
|
|
timeout=10
|
|
)
|
|
response.raise_for_status()
|
|
return response.json()
|
|
except Exception as e:
|
|
logger.error(f"Error obteniendo usuario {username}: {str(e)}")
|
|
return None
|