172 lines
6.9 KiB
Python
172 lines
6.9 KiB
Python
import uuid
|
||
import logging
|
||
import requests
|
||
from odoo import _, models
|
||
from odoo.exceptions import UserError
|
||
|
||
_logger = logging.getLogger(__name__)
|
||
|
||
|
||
class TierChannelMatrixHelper(models.AbstractModel):
|
||
"""Helper for Matrix channel operations using per-user credentials from res.users.
|
||
|
||
Credentials stored on res.users: tier_matrix_homeserver, tier_matrix_access_token, tier_matrix_room_id.
|
||
"""
|
||
_name = 'tier.channel.matrix.helper'
|
||
_description = 'Matrix Channel Helper'
|
||
|
||
def _get_user_matrix_config(self, user=None):
|
||
if not user:
|
||
user = self.env.user
|
||
return {
|
||
'homeserver_url': user.tier_matrix_homeserver,
|
||
'access_token': user.tier_matrix_access_token,
|
||
'room_id': user.tier_matrix_room_id,
|
||
}
|
||
|
||
def channel_authenticate(self, user=None):
|
||
"""Verify Matrix credentials for the given user."""
|
||
config = self._get_user_matrix_config(user)
|
||
if not all([config['homeserver_url'], config['access_token']]):
|
||
raise UserError(_('Matrix credentials not configured for this user.'))
|
||
|
||
url = f"{config['homeserver_url'].rstrip('/')}/_matrix/client/v3/account/whoami"
|
||
headers = {'Authorization': f"Bearer {config['access_token']}"}
|
||
try:
|
||
resp = requests.get(url, headers=headers, timeout=10)
|
||
if resp.status_code == 200:
|
||
return True
|
||
raise UserError(
|
||
_('Matrix authentication failed (HTTP %s): %s') % (resp.status_code, resp.text)
|
||
)
|
||
except requests.exceptions.RequestException as e:
|
||
raise UserError(_('Cannot connect to Matrix homeserver: %s') % str(e))
|
||
|
||
# =========================================================================
|
||
# ! ОТПРАВКА СООБЩЕНИЯ НА MATRIX
|
||
# =========================================================================
|
||
|
||
def channel_send_message(self, message, user=None):
|
||
"""Send a mail.message to Matrix using user's credentials."""
|
||
if not user:
|
||
user = self.env.user
|
||
|
||
config = self._get_user_matrix_config(user)
|
||
|
||
if not config['homeserver_url']:
|
||
print(
|
||
f"[tier_matrix] ! ОТПРАВКА СООБЩЕНИЯ НА MATRIX: "
|
||
f"msg_id={message.id} — У пользователя нет homeserver URL (tier_matrix_homeserver)"
|
||
)
|
||
return
|
||
if not config['access_token']:
|
||
print(
|
||
f"[tier_matrix] ! ОТПРАВКА СООБЩЕНИЯ НА MATRIX: "
|
||
f"msg_id={message.id} — У пользователя нет access token (tier_matrix_access_token)"
|
||
)
|
||
return
|
||
if not config['room_id']:
|
||
print(
|
||
f"[tier_matrix] ! ОТПРАВКА СООБЩЕНИЯ НА MATRIX: "
|
||
f"msg_id={message.id} — У пользователя нет room ID (tier_matrix_room_id)"
|
||
)
|
||
return
|
||
|
||
txn_id = str(uuid.uuid4())
|
||
url = (
|
||
f"{config['homeserver_url'].rstrip('/')}/_matrix/client/v3/rooms/"
|
||
f"{config['room_id']}/send/m.room.message/{txn_id}"
|
||
)
|
||
headers = {
|
||
'Authorization': f"Bearer {config['access_token']}",
|
||
'Content-Type': 'application/json',
|
||
}
|
||
# Убираем HTML теги для Matrix (plain text)
|
||
import re
|
||
body_text = re.sub(r'<[^>]+>', '', message.body or '')
|
||
|
||
payload = {
|
||
'msgtype': 'm.text',
|
||
'body': body_text,
|
||
'format': 'org.matrix.custom.html',
|
||
'formatted_body': message.body or '',
|
||
}
|
||
|
||
print(
|
||
f"[tier_matrix] ! ОТПРАВКА СООБЩЕНИЯ НА MATRIX: "
|
||
f"msg_id={message.id}, room={config['room_id']}, txn_id={txn_id}"
|
||
)
|
||
|
||
try:
|
||
resp = requests.put(url, json=payload, headers=headers, timeout=15)
|
||
if resp.status_code not in (200, 201):
|
||
print(
|
||
f"[tier_matrix] ! ОТПРАВКА СООБЩЕНИЯ НА MATRIX: "
|
||
f"msg_id={message.id} — HTTP {resp.status_code}: {resp.text}"
|
||
)
|
||
raise UserError(
|
||
_('Failed to send Matrix message (HTTP %s): %s') % (resp.status_code, resp.text)
|
||
)
|
||
event_id = resp.json().get('event_id', '')
|
||
print(
|
||
f"[tier_matrix] ! ОТПРАВКА СООБЩЕНИЯ НА MATRIX: "
|
||
f"msg_id={message.id} — SUCCESS, event_id={event_id}"
|
||
)
|
||
except requests.exceptions.RequestException as e:
|
||
print(
|
||
f"[tier_matrix] ! ОТПРАВКА СООБЩЕНИЯ НА MATRIX: "
|
||
f"msg_id={message.id} — ОШИБКА соединения: {e}"
|
||
)
|
||
raise UserError(_('Matrix send error: %s') % str(e))
|
||
|
||
# =========================================================================
|
||
# ПРИЁМ ВХОДЯЩЕГО СООБЩЕНИЯ ИЗ MATRIX
|
||
# =========================================================================
|
||
|
||
def channel_receive_message(self, payload):
|
||
"""Create a mail.message in Odoo from an incoming Matrix event.
|
||
|
||
Входящее из Matrix → создаём mail.message с тегом 'matrix'.
|
||
Это дублирует переписку внутри Odoo.
|
||
"""
|
||
body = payload.get('content', {}).get('body', '')
|
||
sender = payload.get('sender', '')
|
||
room_id = payload.get('room_id', '')
|
||
|
||
print(
|
||
f"[tier_matrix] channel_receive_message: "
|
||
f"входящее из Matrix, sender={sender}, room={room_id}, body_len={len(body)}"
|
||
)
|
||
|
||
# Ищем пользователя по room_id чтобы привязать к нужному треду
|
||
user_with_room = self.env['res.users'].sudo().search(
|
||
[('tier_matrix_room_id', '=', room_id)], limit=1
|
||
)
|
||
|
||
vals = {
|
||
'body': body,
|
||
'message_type': 'email',
|
||
'email_from': sender,
|
||
'subject': f'Matrix: {sender}',
|
||
}
|
||
|
||
# Если нашли пользователя с этой комнатой — привязываем к его партнёру
|
||
if user_with_room and user_with_room.partner_id:
|
||
vals['author_id'] = False # внешний отправитель
|
||
|
||
msg = self.env['mail.message'].sudo().create(vals)
|
||
|
||
# Помечаем тегом matrix
|
||
matrix_tag = self.env['tier.channel.tag'].search([('code', '=', 'matrix')], limit=1)
|
||
if matrix_tag:
|
||
msg.channel_tag_ids = [(4, matrix_tag.id)]
|
||
|
||
from odoo.addons.tier_communications.models.tier_router import TierRouter
|
||
TierRouter(self.env).route_incoming(msg)
|
||
|
||
print(
|
||
f"[tier_matrix] channel_receive_message: "
|
||
f"создано msg_id={msg.id} с тегом matrix"
|
||
)
|
||
return msg
|