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