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