Переглянути джерело

Add support for Matrix->Instagram images

Tulir Asokan 4 роки тому
батько
коміт
48412978bc

+ 1 - 1
ROADMAP.md

@@ -4,7 +4,7 @@
   * [ ] Message content
     * [x] Text
     * [ ] Media
-      * [ ] Images
+      * [x] Images
       * [ ] Videos
       * [ ] Voice messages
       * [ ] Locations

+ 22 - 2
mauigpapi/http/thread.py

@@ -14,9 +14,11 @@
 # 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, AsyncIterable
+from uuid import uuid4
 
 from .base import BaseAndroidAPI
-from ..types import DMInboxResponse, DMThreadResponse, Thread, ThreadItem
+from ..types import (DMInboxResponse, DMThreadResponse, Thread, ThreadItem, ThreadAction,
+                     ThreadItemType, CommandResponse)
 
 
 class ThreadAPI(BaseAndroidAPI):
@@ -47,7 +49,7 @@ class ThreadAPI(BaseAndroidAPI):
             for thread in resp.inbox.threads:
                 yield thread
 
-    async def get_thread(self, thread_id: str,  cursor: Optional[str] = None, limit: int = 10,
+    async def get_thread(self, thread_id: str, cursor: Optional[str] = None, limit: int = 10,
                          direction: str = "older", seq_id: Optional[int] = None
                          ) -> DMThreadResponse:
         query = {
@@ -74,3 +76,21 @@ class ThreadAPI(BaseAndroidAPI):
         await self.std_http_post(f"/api/v1/direct_v2/threads/{thread_id}/items/{item_id}/delete/",
                                  data={"_csrftoken": self.state.cookies.csrf_token,
                                        "_uuid": self.state.device.uuid})
+
+    async def broadcast(self, thread_id: str, item_type: ThreadItemType, signed: bool = False,
+                        client_context: Optional[str] = None, **kwargs) -> CommandResponse:
+        client_context = client_context or str(uuid4())
+        form = {
+            "action": ThreadAction.SEND_ITEM.value,
+            "send_attribution": "inbox",
+            "thread_id": thread_id,
+            "client_context": client_context,
+            "_csrftoken": self.state.cookies.csrf_token,
+            "device_id": self.state.device.id,
+            "mutation_token": client_context,
+            "_uuid": self.state.device.uuid,
+            **kwargs,
+            "offline_threading_id": client_context,
+        }
+        return await self.std_http_post(f"/api/v1/direct_v2/threads/broadcast/{item_type.value}/",
+                                        data=form, raw=not signed, response_type=CommandResponse)

+ 21 - 1
mauigpapi/http/upload.py

@@ -27,7 +27,7 @@ class UploadAPI(BaseAndroidAPI):
     async def upload_jpeg_photo(self, data: bytes, upload_id: Optional[str] = None,
                                 is_sidecar: bool = False, waterfall_id: Optional[str] = None,
                                 media_type: MediaType = MediaType.IMAGE) -> UploadPhotoResponse:
-        upload_id = upload_id or str(time.time())
+        upload_id = upload_id or str(int(time.time() * 1000))
         name = f"{upload_id}_0_{random.randint(1000000000, 9999999999)}"
         params = {
             "retry_context": json.dumps({
@@ -57,3 +57,23 @@ class UploadAPI(BaseAndroidAPI):
         }
         return await self.std_http_post(f"/rupload_igphoto/{name}", headers=headers, data=data,
                                         raw=True, response_type=UploadPhotoResponse)
+
+    async def finish_upload(self, upload_id: str, source_type: str):
+        headers = {
+            "retry_context": json.dumps({
+                "num_step_auto_retry": 0,
+                "num_reupload": 0,
+                "num_step_manual_retry": 0,
+            }),
+        }
+        req = {
+            "timezone_offset": self.state.device.timezone_offset,
+            "_csrftoken": self.state.cookies.csrf_token,
+            "source_type": source_type,
+            "_uid": self.state.cookies.user_id,
+            "device_id": self.state.device.id,
+            "_uuid": self.state.device.uuid,
+            "upload_id": upload_id,
+            "device": self.state.device.payload,
+        }
+        return await self.std_http_post("/api/v1/media/upload_finish/", headers=headers, data=req)

+ 1 - 0
mauigpapi/types/thread_item.py

@@ -30,6 +30,7 @@ class ThreadItemType(SerializableEnum):
     HASHTAG = "hashtag"
     PROFILE = "profile"
     MEDIA_SHARE = "media_share"
+    CONFIGURE_PHOTO = "configure_photo"
     LOCATION = "location"
     ACTION_LOG = "action_log"
     TITLE = "title"

+ 11 - 6
mautrix_instagram/portal.py

@@ -24,7 +24,7 @@ import magic
 from yarl import URL
 
 from mauigpapi.types import (Thread, ThreadUser, ThreadItem, RegularMediaItem, MediaType,
-                             ReactionStatus, Reaction, AnimatedMediaItem)
+                             ReactionStatus, Reaction, AnimatedMediaItem, ThreadItemType)
 from mautrix.appservice import AppService, IntentAPI
 from mautrix.bridge import BasePortal, NotificationDisabler
 from mautrix.types import (EventID, MessageEventContent, RoomID, EventType, MessageType, ImageInfo,
@@ -164,9 +164,12 @@ class Portal(DBPortal, BasePortal):
             mime_type = message.info.mimetype or magic.from_buffer(data, mime=True)
             if mime_type == "image/jpeg":
                 upload_resp = await sender.client.upload_jpeg_photo(data)
-                # TODO I don't think this works
-                resp = await sender.mqtt.send_media(self.thread_id, upload_resp.upload_id,
-                                                    client_context=request_id)
+                # TODO is it possible to do this with MQTT?
+                resp = await sender.client.broadcast(self.thread_id,
+                                                     ThreadItemType.CONFIGURE_PHOTO,
+                                                     client_context=request_id,
+                                                     upload_id=upload_resp.upload_id,
+                                                     allow_full_aspect_ratio="1")
             else:
                 # TODO add link to media for unsupported file types
                 return
@@ -278,7 +281,8 @@ class Portal(DBPortal, BasePortal):
             "image/webp": ".webp",
             "image/jpeg": ".jpg",
             "video/mp4": ".mp4"
-        }.get(info.mime_type) or mimetypes.guess_extension(info.mime_type)
+        }.get(info.mime_type)
+        extension = extension or mimetypes.guess_extension(info.mime_type) or ""
         file_name = f"{msgtype.value[2:]}{extension}"
 
         upload_mime_type = info.mime_type
@@ -304,7 +308,8 @@ class Portal(DBPortal, BasePortal):
         if item.media:
             reuploaded = await self._reupload_instagram_media(source, item.media, intent)
         elif item.animated_media:
-            reuploaded = await self._reupload_instagram_animated(source, item.animated_media, intent)
+            reuploaded = await self._reupload_instagram_animated(source, item.animated_media,
+                                                                 intent)
         else:
             reuploaded = None
         if not reuploaded: