Forráskód Böngészése

Add support for creating DMs

Tulir Asokan 3 éve
szülő
commit
bb9c471a94

+ 1 - 1
ROADMAP.md

@@ -43,7 +43,7 @@
   * [x] Automatic portal creation
     * [x] At startup
     * [x] When receiving message
-  * [ ] Private chat creation by inviting Matrix puppet of Instagram user to new room
+  * [x] Private chat creation by inviting Matrix puppet of Instagram user to new room
   * [x] Option to use own Matrix account for messages sent from other Instagram clients
   * [x] End-to-bridge encryption in Matrix rooms
 

+ 26 - 0
mauigpapi/http/thread.py

@@ -16,6 +16,7 @@
 from __future__ import annotations
 
 from typing import AsyncIterable, Type
+import json
 
 from ..types import (
     CommandResponse,
@@ -105,6 +106,31 @@ class ThreadAPI(BaseAndroidAPI):
             for item in resp.thread.items:
                 yield item
 
+    async def create_group_thread(self, recipient_users: list[int | str]) -> Thread:
+        return await self.std_http_post(
+            "/api/v1/direct_v2/create_group_thread/",
+            data={
+                "_csrftoken": self.state.cookies.csrf_token,
+                "_uuid": self.state.device.uuid,
+                "_uid": self.state.cookies.user_id,
+                "recipient_users": json.dumps(
+                    [str(user) for user in recipient_users], separators=(",", ":")
+                ),
+            },
+            response_type=Thread,
+        )
+
+    async def approve_thread(self, thread_ids: list[int | str]) -> None:
+        await self.std_http_post(
+            "/api/v1/direct_v2/threads/approve_multiple/",
+            data={
+                "thread_ids": json.dumps(
+                    [str(thread) for thread in thread_ids], separators=(",", ":")
+                ),
+                "folder": "",
+            },
+        )
+
     async def delete_item(self, thread_id: str, item_id: str) -> None:
         await self.std_http_post(
             f"/api/v1/direct_v2/threads/{thread_id}/items/{item_id}/delete/",

+ 5 - 0
mautrix_instagram/db/portal.py

@@ -128,6 +128,11 @@ class Portal:
         rows = await cls.db.fetch(q, other_user)
         return [cls._from_row(row) for row in rows]
 
+    @classmethod
+    async def find_private_chat_id(cls, receiver: int, other_user: int) -> str | None:
+        q = "SELECT thread_id FROM portal WHERE receiver=$1 AND other_user_pk=$2"
+        return await cls.db.fetchval(q, receiver, other_user)
+
     @classmethod
     async def all_with_room(cls) -> list[Portal]:
         q = (

+ 18 - 2
mautrix_instagram/portal.py

@@ -1319,6 +1319,15 @@ class Portal(DBPortal, BasePortal):
             await self.update()
         # TODO update power levels with thread.admin_user_ids
 
+    async def update_info_from_puppet(self, puppet: p.Puppet | None = None) -> None:
+        if not self.is_direct:
+            return
+        if not puppet:
+            puppet = await self.get_dm_puppet()
+        await self._update_photo_from_puppet(puppet)
+        if self.name and not self.name_set:
+            await self._update_name(self.name)
+
     async def _update_name(self, name: str) -> bool:
         if name and (self.name != name or not self.name_set):
             self.name = name
@@ -1390,10 +1399,10 @@ class Portal(DBPortal, BasePortal):
                     exc_info=True,
                 )
 
-    async def get_dm_puppet(self) -> pu.Puppet | None:
+    async def get_dm_puppet(self) -> p.Puppet | None:
         if not self.is_direct:
             return None
-        return await pu.Puppet.get_by_pk(self.other_user_pk)
+        return await p.Puppet.get_by_pk(self.other_user_pk)
 
     # endregion
     # region Backfilling
@@ -1662,6 +1671,13 @@ class Portal(DBPortal, BasePortal):
     def find_private_chats_with(cls, other_user: int) -> AsyncGenerator[Portal, None]:
         return cls._db_to_portals(super().find_private_chats_with(other_user))
 
+    @classmethod
+    async def find_private_chat(cls, receiver: int, other_user: int) -> Portal | None:
+        thread_id = await super().find_private_chat_id(receiver, other_user)
+        if not thread_id:
+            return None
+        return await cls.get_by_thread_id(thread_id, receiver=receiver, is_group=False)
+
     @classmethod
     async def _db_to_portals(cls, query: Awaitable[list[Portal]]) -> AsyncGenerator[Portal, None]:
         portals = await query

+ 11 - 2
mautrix_instagram/user.py

@@ -135,8 +135,17 @@ class User(DBUser, BaseUser):
         return await pu.Puppet.get_by_pk(self.igpk)
 
     async def get_portal_with(self, puppet: pu.Puppet, create: bool = True) -> po.Portal | None:
-        # We should probably make this work eventually, but for now, creating chats will just not
-        # work.
+        if not self.igpk:
+            return None
+        portal = await po.Portal.find_private_chat(self.igpk, puppet.pk)
+        if portal:
+            return portal
+        if create:
+            # TODO add error handling somewhere
+            thread = await self.client.create_group_thread([puppet.pk])
+            portal = await po.Portal.get_by_thread(thread, self.igpk)
+            await portal.update_info(thread, self)
+            return portal
         return None
 
     async def try_connect(self) -> None: