From 8a5634a6a1c9b9235f843f58cef995cf7824cd3d Mon Sep 17 00:00:00 2001 From: Tulir Asokan <tulir@maunium.net> Date: Fri, 9 Aug 2019 23:13:34 +0300 Subject: [PATCH] Update to new mautrix-python version --- ...c8d5_store_custom_puppet_next_batch_in_.py | 26 +++++++++++ mautrix_facebook/context.py | 19 ++++---- mautrix_facebook/db/puppet.py | 9 ++-- mautrix_facebook/formatter/from_matrix.py | 14 +++--- mautrix_facebook/matrix.py | 35 ++++++++------- mautrix_facebook/portal.py | 10 ++--- mautrix_facebook/puppet.py | 43 +++++++++++-------- mautrix_facebook/user.py | 4 +- setup.py | 2 +- 9 files changed, 99 insertions(+), 63 deletions(-) create mode 100644 alembic/versions/8e0f1142c8d5_store_custom_puppet_next_batch_in_.py diff --git a/alembic/versions/8e0f1142c8d5_store_custom_puppet_next_batch_in_.py b/alembic/versions/8e0f1142c8d5_store_custom_puppet_next_batch_in_.py new file mode 100644 index 0000000..8b2029f --- /dev/null +++ b/alembic/versions/8e0f1142c8d5_store_custom_puppet_next_batch_in_.py @@ -0,0 +1,26 @@ +"""Store custom puppet next_batch in database + +Revision ID: 8e0f1142c8d5 +Revises: 1a1ea46dc3e1 +Create Date: 2019-08-09 23:04:40.838488 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = "8e0f1142c8d5" +down_revision = "1a1ea46dc3e1" +branch_labels = None +depends_on = None + + +def upgrade(): + with op.batch_alter_table("puppet") as batch_op: + batch_op.add_column(sa.Column("next_batch", sa.String(255), nullable=True)) + + +def downgrade(): + with op.batch_alter_table("puppet") as batch_op: + batch_op.drop_column("next_batch") diff --git a/mautrix_facebook/context.py b/mautrix_facebook/context.py index 29e5327..8c67c3d 100644 --- a/mautrix_facebook/context.py +++ b/mautrix_facebook/context.py @@ -14,29 +14,28 @@ # 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, Tuple, TYPE_CHECKING +from asyncio import AbstractEventLoop -if TYPE_CHECKING: - import asyncio +from mautrix.appservice import AppService - from mautrix.appservice import AppService +from .config import Config - from .config import Config +if TYPE_CHECKING: from .matrix import MatrixHandler class Context: - az: 'AppService' - config: 'Config' - loop: 'asyncio.AbstractEventLoop' + az: AppService + config: Config + loop: AbstractEventLoop mx: Optional['MatrixHandler'] - def __init__(self, az: 'AppService', config: 'Config', loop: 'asyncio.AbstractEventLoop' - ) -> None: + def __init__(self, az: AppService, config: Config, loop: AbstractEventLoop) -> None: self.az = az self.config = config self.loop = loop self.mx = None @property - def core(self) -> Tuple['AppService', 'Config', 'asyncio.AbstractEventLoop']: + def core(self) -> Tuple[AppService, Config, AbstractEventLoop]: return self.az, self.config, self.loop diff --git a/mautrix_facebook/db/puppet.py b/mautrix_facebook/db/puppet.py index fb3bf41..9e97904 100644 --- a/mautrix_facebook/db/puppet.py +++ b/mautrix_facebook/db/puppet.py @@ -19,7 +19,7 @@ from sqlalchemy import Column, String, Text, Boolean from sqlalchemy.sql import expression from sqlalchemy.engine.result import RowProxy -from mautrix.types import UserID +from mautrix.types import UserID, SyncToken from mautrix.bridge.db.base import Base @@ -33,12 +33,13 @@ class Puppet(Base): custom_mxid: UserID = Column(String(255), nullable=True) access_token: str = Column(Text, nullable=True) + next_batch: SyncToken = Column(String(255), nullable=True) @classmethod def scan(cls, row: RowProxy) -> Optional['Puppet']: - fbid, name, photo_id, matrix_registered, custom_mxid, access_token = row + fbid, name, photo_id, matrix_registered, custom_mxid, access_token, next_batch = row return cls(fbid=fbid, name=name, photo_id=photo_id, matrix_registered=matrix_registered, - custom_mxid=custom_mxid, access_token=access_token) + custom_mxid=custom_mxid, access_token=access_token, next_batch=next_batch) @classmethod def get_by_fbid(cls, fbid: str) -> Optional['Puppet']: @@ -65,4 +66,4 @@ class Puppet(Base): conn.execute(self.t.insert().values( fbid=self.fbid, name=self.name, photo_id=self.photo_id, matrix_registered=self.matrix_registered, custom_mxid=self.custom_mxid, - access_token=self.access_token)) + access_token=self.access_token, next_batch=self.next_batch)) diff --git a/mautrix_facebook/formatter/from_matrix.py b/mautrix_facebook/formatter/from_matrix.py index 1983917..defd2e3 100644 --- a/mautrix_facebook/formatter/from_matrix.py +++ b/mautrix_facebook/formatter/from_matrix.py @@ -19,14 +19,14 @@ from fbchat.models import Message, Mention from mautrix.types import TextMessageEventContent, Format, UserID, RoomID, RelationType from mautrix.util.formatter import (MatrixParser as BaseMatrixParser, MarkdownString, EntityString, - Entity, EntityType) + SimpleEntity, EntityType) from .. import puppet as pu, user as u from ..db import Message as DBMessage -class FacebookFormatString(EntityString, MarkdownString): - def _mention_to_entity(self, mxid: UserID) -> Optional[Entity]: +class FacebookFormatString(EntityString[SimpleEntity, EntityType], MarkdownString): + def _mention_to_entity(self, mxid: UserID) -> Optional[SimpleEntity]: user = u.User.get_by_mxid(mxid, create=False) if user and user.fbid: fbid = user.fbid @@ -36,8 +36,8 @@ class FacebookFormatString(EntityString, MarkdownString): fbid = puppet.fbid else: return None - return Entity(type=EntityType.USER_MENTION, offset=0, length=len(self.text), - extra_info={"user_id": mxid, "fbid": fbid}) + return SimpleEntity(type=EntityType.USER_MENTION, offset=0, length=len(self.text), + extra_info={"user_id": mxid, "fbid": fbid}) def format(self, entity_type: EntityType, **kwargs) -> 'FacebookFormatString': prefix = suffix = "" @@ -74,7 +74,7 @@ class FacebookFormatString(EntityString, MarkdownString): return self -class MatrixParser(BaseMatrixParser): +class MatrixParser(BaseMatrixParser[FacebookFormatString]): fs = FacebookFormatString @classmethod @@ -91,7 +91,7 @@ def matrix_to_facebook(content: TextMessageEventContent, room_id: RoomID) -> Mes content.trim_reply_fallback() reply_to_id = message.fbid if content.format == Format.HTML and content.formatted_body: - parsed: FacebookFormatString = MatrixParser.parse(content.formatted_body) + parsed = MatrixParser.parse(content.formatted_body) text = parsed.text mentions = [Mention(thread_id=mention.extra_info['fbid'], offset=mention.offset, length=mention.length) diff --git a/mautrix_facebook/matrix.py b/mautrix_facebook/matrix.py index 1102976..2ee796d 100644 --- a/mautrix_facebook/matrix.py +++ b/mautrix_facebook/matrix.py @@ -13,12 +13,13 @@ # # 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 List, TYPE_CHECKING +from typing import List, Union, TYPE_CHECKING from fbchat.models import ThreadType from mautrix.types import (EventID, RoomID, UserID, Event, EventType, MessageEvent, StateEvent, RedactionEvent, PresenceEventContent, ReceiptEvent, PresenceState, - ReactionEvent, ReactionEventContent, RelationType) + ReactionEvent, ReactionEventContent, RelationType, PresenceEvent, + TypingEvent) from mautrix.errors import MatrixError from mautrix.bridge import BaseMatrixHandler @@ -41,8 +42,8 @@ class MatrixHandler(BaseMatrixHandler): async def get_user(self, user_id: UserID) -> 'u.User': return u.User.get_by_mxid(user_id) - async def handle_puppet_invite(self, room_id: RoomID, puppet: 'pu.Puppet', invited_by: 'u.User' - ) -> None: + async def handle_puppet_invite(self, room_id: RoomID, puppet: 'pu.Puppet', + invited_by: 'u.User', event_id: EventID) -> None: intent = puppet.default_mxid_intent self.log.debug(f"{invited_by.mxid} invited puppet for {puppet.fbid} to {room_id}") if not await invited_by.is_logged_in(): @@ -92,12 +93,13 @@ class MatrixHandler(BaseMatrixHandler): portal.save() await intent.send_notice(room_id, "Portal to private chat created.") - async def handle_invite(self, room_id: RoomID, user_id: UserID, invited_by: 'u.User') -> None: + async def handle_invite(self, room_id: RoomID, user_id: UserID, invited_by: 'u.User', + event_id: EventID) -> None: # TODO handle puppet and user invites for group chats # The rest can probably be ignored pass - async def handle_join(self, room_id: RoomID, user_id: UserID) -> None: + async def handle_join(self, room_id: RoomID, user_id: UserID, event_id: EventID) -> None: user = u.User.get_by_mxid(user_id) portal = po.Portal.get_by_mxid(room_id) @@ -117,7 +119,7 @@ class MatrixHandler(BaseMatrixHandler): self.log.debug(f"{user} joined {room_id}") # await portal.join_matrix(user, event_id) - async def handle_leave(self, room_id: RoomID, user_id: UserID) -> None: + async def handle_leave(self, room_id: RoomID, user_id: UserID, event_id: EventID) -> None: portal = po.Portal.get_by_mxid(room_id) if not portal: return @@ -193,11 +195,20 @@ class MatrixHandler(BaseMatrixHandler): await user.markAsRead(portal.fbid) def filter_matrix_event(self, evt: Event) -> bool: - if not isinstance(evt, (MessageEvent, StateEvent)): - return False + if not isinstance(evt, (ReactionEvent, RedactionEvent, MessageEvent, StateEvent)): + return True return (evt.sender == self.az.bot_mxid or pu.Puppet.get_id_from_mxid(evt.sender) is not None) + async def handle_ephemeral_event(self, evt: Union[ReceiptEvent, PresenceEvent, TypingEvent] + ) -> None: + if evt.type == EventType.PRESENCE: + await self.handle_presence(evt.sender, evt.content) + elif evt.type == EventType.TYPING: + await self.handle_typing(evt.room_id, evt.content.user_ids) + elif evt.type == EventType.RECEIPT: + await self.handle_receipt(evt) + async def handle_event(self, evt: Event) -> None: if evt.type == EventType.ROOM_REDACTION: evt: RedactionEvent @@ -205,9 +216,3 @@ class MatrixHandler(BaseMatrixHandler): elif evt.type == EventType.REACTION: evt: ReactionEvent await self.handle_reaction(evt.room_id, evt.sender, evt.event_id, evt.content) - elif evt.type == EventType.PRESENCE: - await self.handle_presence(evt.sender, evt.content) - elif evt.type == EventType.TYPING: - await self.handle_typing(evt.room_id, evt.content.user_ids) - elif evt.type == EventType.RECEIPT: - await self.handle_receipt(evt) diff --git a/mautrix_facebook/portal.py b/mautrix_facebook/portal.py index e058b76..08292d1 100644 --- a/mautrix_facebook/portal.py +++ b/mautrix_facebook/portal.py @@ -351,7 +351,7 @@ class Portal: elif message.msgtype == MessageType.LOCATION: fbid = await self._handle_matrix_location(sender, message) else: - self.log.warn(f"Unsupported msgtype in {message}") + self.log.warning(f"Unsupported msgtype in {message}") return if not fbid: return @@ -453,8 +453,8 @@ class Portal: if self.invite_own_puppet_to_pm: await self.main_intent.invite_user(self.mxid, sender.mxid) elif self.az.state_store.get_membership(self.mxid, sender.mxid) != Membership.JOIN: - self.log.warn(f"Ignoring own {mid} in private chat because own puppet is not in" - " room.") + self.log.warning(f"Ignoring own {mid} in private chat because own puppet is not in" + " room.") return False return True @@ -485,7 +485,7 @@ class Portal: if not event_ids and message.text: event_ids = [await self._handle_facebook_text(intent, message)] else: - self.log.warn(f"Unhandled Messenger message: {message}") + self.log.warning(f"Unhandled Messenger message: {message}") DBMessage.bulk_create(fbid=message.uid, fb_receiver=self.fb_receiver, mx_room=self.mxid, event_ids=[event_id for event_id in event_ids if event_id]) await source.markAsDelivered(self.fbid, message.uid) @@ -556,7 +556,7 @@ class Portal: content.relates_to = self._get_facebook_reply(reply_to) event_id = await intent.send_message(self.mxid, content) else: - self.log.warn(f"Unsupported attachment type: {attachment}") + self.log.warning(f"Unsupported attachment type: {attachment}") return None self._last_bridged_mxid = event_id return event_id diff --git a/mautrix_facebook/puppet.py b/mautrix_facebook/puppet.py index d399bf2..88ba493 100644 --- a/mautrix_facebook/puppet.py +++ b/mautrix_facebook/puppet.py @@ -14,15 +14,15 @@ # 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, Dict, Iterator, Iterable, Awaitable, TYPE_CHECKING -from string import Template import logging import asyncio import attr from fbchat.models import User as FBUser -from mautrix.types import UserID, RoomID +from mautrix.types import UserID, RoomID, SyncToken from mautrix.appservice import AppService, IntentAPI from mautrix.bridge.custom_puppet import CustomPuppetMixin +from mautrix.util.simple_template import SimpleTemplate from .config import Config from .db import Puppet as DBPuppet @@ -40,8 +40,7 @@ class Puppet(CustomPuppetMixin): loop: asyncio.AbstractEventLoop mx: m.MatrixHandler hs_domain: str - _mxid_prefix: str - _mxid_suffix: str + mxid_template: SimpleTemplate[str] by_fbid: Dict[str, 'Puppet'] = {} by_custom_mxid: Dict[UserID, 'Puppet'] = {} @@ -54,13 +53,14 @@ class Puppet(CustomPuppetMixin): custom_mxid: UserID access_token: str + _next_batch: SyncToken _db_instance: Optional[DBPuppet] intent: IntentAPI def __init__(self, fbid: str, name: str = "", photo_id: str = "", is_registered: bool = False, - custom_mxid: UserID = "", access_token: str = "", + custom_mxid: UserID = "", access_token: str = "", next_batch: SyncToken = "", db_instance: Optional[DBPuppet] = None) -> None: self.fbid = fbid self.name = name @@ -70,6 +70,7 @@ class Puppet(CustomPuppetMixin): self.custom_mxid = custom_mxid self.access_token = access_token + self._next_batch = next_batch self._db_instance = db_instance @@ -90,7 +91,7 @@ class Puppet(CustomPuppetMixin): if not self._db_instance: self._db_instance = DBPuppet(fbid=self.fbid, name=self.name, photo_id=self.photo_id, matrix_registered=self._is_registered, - custom_mxid=self.custom_mxid, + custom_mxid=self.custom_mxid, next_batch=self._next_batch, access_token=self.access_token) return self._db_instance @@ -98,13 +99,23 @@ class Puppet(CustomPuppetMixin): def from_db(cls, db_puppet: DBPuppet) -> 'Puppet': return Puppet(fbid=db_puppet.fbid, name=db_puppet.name, photo_id=db_puppet.photo_id, is_registered=db_puppet.matrix_registered, custom_mxid=db_puppet.custom_mxid, - access_token=db_puppet.access_token, db_instance=db_puppet) + access_token=db_puppet.access_token, next_batch=db_puppet.next_batch, + db_instance=db_puppet) def save(self) -> None: self.db_instance.edit(name=self.name, photo_id=self.photo_id, matrix_registered=self._is_registered, custom_mxid=self.custom_mxid, access_token=self.access_token) + @property + def next_batch(self) -> SyncToken: + return self._next_batch + + @next_batch.setter + def next_batch(self, value: SyncToken) -> None: + self._next_batch = value + self.db_instance.edit(next_batch=self._next_batch) + # endregion @property @@ -149,7 +160,8 @@ class Puppet(CustomPuppetMixin): for preference in config["bridge.displayname_preference"]: if getattr(info, preference, None): displayname = getattr(info, preference) - return config["bridge.displayname_template"].format(displayname=displayname, **attr.asdict(info)) + return config["bridge.displayname_template"].format(displayname=displayname, + **attr.asdict(info)) async def _update_name(self, info: FBUser) -> bool: name = self._get_displayname(info) @@ -216,15 +228,11 @@ class Puppet(CustomPuppetMixin): @classmethod def get_id_from_mxid(cls, mxid: UserID) -> Optional[str]: - prefix = cls._mxid_prefix - suffix = cls._mxid_suffix - if mxid[:len(prefix)] == prefix and mxid[-len(suffix):] == suffix: - return mxid[len(prefix):-len(suffix)] - return None + return cls.mxid_template.parse(mxid) @classmethod def get_mxid_from_id(cls, fbid: str) -> UserID: - return UserID(cls._mxid_prefix + fbid + cls._mxid_suffix) + return UserID(cls.mxid_template.format_full(fbid)) @classmethod def get_all_with_custom_mxid(cls) -> Iterator['Puppet']: @@ -243,12 +251,9 @@ def init(context: 'Context') -> Iterable[Awaitable[None]]: global config Puppet.az, config, Puppet.loop = context.core Puppet.mx = context.mx - username_template = config["bridge.username_template"].lower() CustomPuppetMixin.sync_with_custom_puppets = config["bridge.sync_with_custom_puppets"] - index = username_template.index("{userid}") - length = len("{userid}") Puppet.hs_domain = config["homeserver"]["domain"] - Puppet._mxid_prefix = f"@{username_template[:index]}" - Puppet._mxid_suffix = f"{username_template[index + length:]}:{Puppet.hs_domain}" + Puppet.mxid_template = SimpleTemplate(config["bridge.username_template"], "userid", + prefix="@", suffix=f":{Puppet.hs_domain}", type=int) return (puppet.start() for puppet in Puppet.get_all_with_custom_mxid()) diff --git a/mautrix_facebook/user.py b/mautrix_facebook/user.py index 8e94cf4..6ff78a3 100644 --- a/mautrix_facebook/user.py +++ b/mautrix_facebook/user.py @@ -193,7 +193,7 @@ class User(Client): "You have two-factor authentication enabled. " "Please send the code here.") return await future - self.log.warn("Unexpected on2FACode call") + self.log.warning("Unexpected on2FACode call") # raise RuntimeError("No ongoing login command") async def onLoggedIn(self, email: str = None) -> None: @@ -209,7 +209,7 @@ class User(Client): self.save() self.listen() asyncio.ensure_future(self.post_login(), loop=self.loop) - self.log.warn("Unexpected onLoggedIn call") + self.log.warning("Unexpected onLoggedIn call") # raise RuntimeError("No ongoing login command") async def onListening(self) -> None: diff --git a/setup.py b/setup.py index faacd00..2b9751f 100644 --- a/setup.py +++ b/setup.py @@ -23,7 +23,7 @@ setuptools.setup( install_requires=[ "aiohttp>=3.0.1,<4", - "mautrix>=0.4.0.dev51,<0.5.0", + "mautrix>=0.4.0.dev52,<0.5.0", "ruamel.yaml>=0.15.94,<0.16", "commonmark>=0.8,<0.9", "python-magic>=0.4,<0.5", -- GitLab