mautrix-signal/mautrix_signal/signal.py
2020-09-28 02:03:47 +03:00

118 lines
4.9 KiB
Python

# mautrix-signal - A Matrix-Signal puppeting bridge
# Copyright (C) 2020 Tulir Asokan
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
from typing import Optional, List, TYPE_CHECKING
import asyncio
import logging
from mausignald import SignaldClient
from mausignald.types import (Message, MessageData, Receipt, TypingNotification, OwnReadReceipt,
Address, ReceiptType)
from mautrix.util.logging import TraceLogger
from .db import Message as DBMessage
from . import user as u, portal as po, puppet as pu
if TYPE_CHECKING:
from .__main__ import SignalBridge
class SignalHandler(SignaldClient):
log: TraceLogger = logging.getLogger("mau.signal")
loop: asyncio.AbstractEventLoop
def __init__(self, bridge: 'SignalBridge') -> None:
super().__init__(bridge.config["signal.socket_path"], loop=bridge.loop)
self.add_event_handler(Message, self.on_message)
async def on_message(self, evt: Message) -> None:
sender = await pu.Puppet.get_by_address(evt.source)
if not sender.uuid:
self.log.debug("Got message sender puppet with no UUID, not handling message")
self.log.trace("Message content: %s", evt)
return
user = await u.User.get_by_username(evt.username)
# TODO add lots of logging
if evt.data_message:
await self.handle_message(user, sender, evt.data_message)
if evt.typing:
# Typing notification from someone else
pass
if evt.receipt:
await self.handle_receipt(sender, evt.receipt)
if evt.sync_message:
if evt.sync_message.read_messages:
await self.handle_own_receipts(sender, evt.sync_message.read_messages)
if evt.sync_message.contacts:
# Contact list update?
pass
if evt.sync_message.sent:
await self.handle_message(user, sender, evt.sync_message.sent.message,
recipient_override=evt.sync_message.sent.destination)
if evt.sync_message.typing:
# Typing notification from own device
pass
@staticmethod
async def handle_message(user: 'u.User', sender: 'pu.Puppet', msg: MessageData,
recipient_override: Optional[Address] = None) -> None:
if msg.group:
portal = await po.Portal.get_by_chat_id(msg.group.group_id, receiver=user.username)
else:
portal = await po.Portal.get_by_chat_id(recipient_override.uuid
if recipient_override else sender.uuid,
receiver=user.username)
if not portal.mxid:
# TODO create room?
# TODO definitely at least log
return
if msg.reaction:
await portal.handle_signal_reaction(sender, msg.reaction)
if msg.body:
await portal.handle_signal_message(sender, msg)
@staticmethod
async def handle_own_receipts(sender: 'pu.Puppet', receipts: List[OwnReadReceipt]) -> None:
for receipt in receipts:
puppet = await pu.Puppet.get_by_address(receipt.sender, create=False)
if not puppet or not puppet.uuid:
continue
message = await DBMessage.find_by_sender_timestamp(puppet.uuid, receipt.timestamp)
if not message:
continue
portal = await po.Portal.get_by_mxid(message.mx_room)
if not portal:
continue
await sender.intent_for(portal).mark_read(portal.mxid, message.mxid)
@staticmethod
async def handle_receipt(sender: 'pu.Puppet', receipt: Receipt) -> None:
if receipt.type != ReceiptType.READ:
pass
messages = await DBMessage.find_by_timestamps(receipt.timestamps)
for message in messages:
portal = await po.Portal.get_by_mxid(message.mx_room)
await sender.intent_for(portal).mark_read(portal.mxid, message.mxid)
async def start(self) -> None:
await self.connect()
async for user in u.User.all_logged_in():
# TODO handle errors
await self.subscribe(user.username)
self.loop.create_task(user.sync())
async def stop(self) -> None:
await self.disconnect()