From 6caf82e975b5b38ac14b9043f020f92d94d1e006 Mon Sep 17 00:00:00 2001
From: Tulir Asokan <tulir@maunium.net>
Date: Mon, 11 May 2020 18:46:09 +0300
Subject: [PATCH] Bridge messenger member add/remove to Matrix

---
 ROADMAP.md                 |  4 ++--
 mautrix_facebook/portal.py | 21 +++++++++++++++++++--
 mautrix_facebook/user.py   | 23 ++++++++++++++++++++---
 3 files changed, 41 insertions(+), 7 deletions(-)

diff --git a/ROADMAP.md b/ROADMAP.md
index 44594e3..1adabe4 100644
--- a/ROADMAP.md
+++ b/ROADMAP.md
@@ -48,8 +48,8 @@
   * [x] Read receipts
   * [ ] Admin status
   * [ ] Membership actions
-    * [ ] Add member
-    * [ ] Remove member
+    * [x] Add member
+    * [x] Remove member
     * [ ] Leave
   * [ ] Chat metadata changes
     * [x] Title
diff --git a/mautrix_facebook/portal.py b/mautrix_facebook/portal.py
index aa76912..bd286d5 100644
--- a/mautrix_facebook/portal.py
+++ b/mautrix_facebook/portal.py
@@ -13,7 +13,7 @@
 #
 # 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 Dict, Deque, Optional, Tuple, Union, Set, Iterator, TYPE_CHECKING
+from typing import Dict, Deque, Optional, Tuple, Union, Set, Iterator, List, TYPE_CHECKING
 from collections import deque
 import asyncio
 
@@ -797,13 +797,30 @@ class Portal(BasePortal):
             return
         reaction = DBReaction.get_by_fbid(message_id, self.fb_receiver, sender.fbid)
         if reaction:
-            self.log.debug(f"redacting {reaction.mxid}")
             try:
                 await sender.intent_for(self).redact(reaction.mx_room, reaction.mxid)
             except MForbidden:
                 await self.main_intent.redact(reaction.mx_room, reaction.mxid)
             reaction.delete()
 
+    async def handle_facebook_join(self, source: 'u.User', sender: 'p.Puppet',
+                                   users: List['p.Puppet']) -> None:
+        sender_intent = sender.intent_for(self)
+        for user in users:
+            await sender_intent.invite_user(self.mxid, user.mxid)
+            await user.intent_for(self).join_room_by_id(self.mxid)
+
+    async def handle_facebook_leave(self, source: 'u.User', sender: 'p.Puppet', removed: 'p.Puppet'
+                                    ) -> None:
+        if sender == removed:
+            await removed.intent_for(self).leave_room(self.mxid)
+        else:
+            try:
+                await sender.intent_for(self).kick_user(self.mxid, removed.mxid)
+            except MForbidden:
+                await self.main_intent.kick_user(self.mxid, removed.mxid,
+                                                 reason=f"Kicked by {sender.name}")
+
     # endregion
     # region Getters
 
diff --git a/mautrix_facebook/user.py b/mautrix_facebook/user.py
index 1bfad54..8ea747c 100644
--- a/mautrix_facebook/user.py
+++ b/mautrix_facebook/user.py
@@ -319,6 +319,8 @@ class User:
             fbchat.ReactionEvent: self.on_reaction,
             fbchat.Presence: self.on_presence,
             fbchat.Typing: self.on_typing,
+            fbchat.PeopleAdded: self.on_members_added,
+            fbchat.PersonRemoved: self.on_member_removed,
         }
         self.log.debug("Starting fbchat listener")
         async for event in self.listener.listen():
@@ -358,9 +360,8 @@ class User:
         await portal.handle_facebook_message(self, puppet, evt.message)
 
     async def on_title_change(self, evt: fbchat.TitleSet) -> None:
-        # TODO this check is probably useless, users' titles don't change
-        fb_receiver = self.fbid if isinstance(evt.thread, fbchat.User) else None
-        portal = po.Portal.get_by_thread(evt.thread, fb_receiver)
+        assert isinstance(evt.thread, fbchat.Group)
+        portal = po.Portal.get_by_thread(evt.thread)
         if not portal:
             return
         sender = pu.Puppet.get_by_fbid(evt.author.id)
@@ -425,6 +426,22 @@ class User:
             puppet = pu.Puppet.get_by_fbid(evt.author.id)
             await puppet.intent.set_typing(portal.mxid, is_typing=evt.status, timeout=120000)
 
+    async def on_members_added(self, evt: fbchat.PeopleAdded) -> None:
+        assert isinstance(evt.thread, fbchat.Group)
+        portal = po.Portal.get_by_thread(evt.thread)
+        if portal.mxid:
+            sender = pu.Puppet.get_by_fbid(evt.author.id)
+            users = [pu.Puppet.get_by_fbid(user.id) for user in evt.added]
+            await portal.handle_facebook_join(self, sender, users)
+
+    async def on_member_removed(self, evt: fbchat.PersonRemoved) -> None:
+        assert isinstance(evt.thread, fbchat.Group)
+        portal = po.Portal.get_by_thread(evt.thread)
+        if portal.mxid:
+            sender = pu.Puppet.get_by_fbid(evt.author.id)
+            user = pu.Puppet.get_by_fbid(evt.removed.id)
+            await portal.handle_facebook_leave(self, sender, user)
+
     # endregion
 
 
-- 
GitLab