Эх сурвалжийг харах

Add support for Instagram->Matrix voice messages

Tulir Asokan 4 жил өмнө
parent
commit
e0f3cafcf6

+ 1 - 1
ROADMAP.md

@@ -21,7 +21,7 @@
       * [x] Images
       * [x] Images
       * [x] Videos
       * [x] Videos
       * [x] Gifs
       * [x] Gifs
-      * [ ] Voice messages
+      * [x] Voice messages
       * [ ] Locations
       * [ ] Locations
   * [x] Message unsend
   * [x] Message unsend
   * [x] Message reactions
   * [x] Message reactions

+ 3 - 3
mauigpapi/types/thread_item.py

@@ -230,7 +230,7 @@ class AudioInfo(SerializableAttrs['AudioInfo']):
     audio_src: str
     audio_src: str
     duration: int
     duration: int
     waveform_data: List[int]
     waveform_data: List[int]
-    waveform_sampling_frequence_hz: int
+    waveform_sampling_frequency_hz: int
 
 
 
 
 @dataclass(kw_only=True)
 @dataclass(kw_only=True)
@@ -240,8 +240,8 @@ class VoiceMediaData(SerializableAttrs['VoiceMediaData']):
     organic_tracking_token: str
     organic_tracking_token: str
     user: UserIdentifier
     user: UserIdentifier
     # TODO enum?
     # TODO enum?
-    product_type: str = "direct_audio"
-    media_type: MediaType = MediaType.AUDIO
+    product_type: str  # "direct_audio"
+    media_type: MediaType  # MediaType.AUDIO
 
 
 
 
 @dataclass(kw_only=True)
 @dataclass(kw_only=True)

+ 19 - 8
mautrix_instagram/portal.py

@@ -23,11 +23,12 @@ import asyncio
 import magic
 import magic
 
 
 from mauigpapi.types import (Thread, ThreadUser, ThreadItem, RegularMediaItem, MediaType,
 from mauigpapi.types import (Thread, ThreadUser, ThreadItem, RegularMediaItem, MediaType,
-                             ReactionStatus, Reaction, AnimatedMediaItem, ThreadItemType)
+                             ReactionStatus, Reaction, AnimatedMediaItem, ThreadItemType,
+                             VoiceMediaItem)
 from mautrix.appservice import AppService, IntentAPI
 from mautrix.appservice import AppService, IntentAPI
 from mautrix.bridge import BasePortal, NotificationDisabler
 from mautrix.bridge import BasePortal, NotificationDisabler
 from mautrix.types import (EventID, MessageEventContent, RoomID, EventType, MessageType, ImageInfo,
 from mautrix.types import (EventID, MessageEventContent, RoomID, EventType, MessageType, ImageInfo,
-                           VideoInfo, MediaMessageEventContent, TextMessageEventContent, FileInfo,
+                           VideoInfo, MediaMessageEventContent, TextMessageEventContent, AudioInfo,
                            ContentURI, EncryptedFile)
                            ContentURI, EncryptedFile)
 from mautrix.errors import MatrixError, MForbidden
 from mautrix.errors import MatrixError, MForbidden
 from mautrix.util.simple_lock import SimpleLock
 from mautrix.util.simple_lock import SimpleLock
@@ -47,6 +48,7 @@ except ImportError:
 
 
 StateBridge = EventType.find("m.bridge", EventType.Class.STATE)
 StateBridge = EventType.find("m.bridge", EventType.Class.STATE)
 StateHalfShotBridge = EventType.find("uk.half-shot.bridge", EventType.Class.STATE)
 StateHalfShotBridge = EventType.find("uk.half-shot.bridge", EventType.Class.STATE)
+FileInfo = Union[AudioInfo, ImageInfo, VideoInfo]
 ReuploadedMediaInfo = NamedTuple('ReuploadedMediaInfo', mxc=Optional[ContentURI], url=str,
 ReuploadedMediaInfo = NamedTuple('ReuploadedMediaInfo', mxc=Optional[ContentURI], url=str,
                                  decryption_info=Optional[EncryptedFile], msgtype=MessageType,
                                  decryption_info=Optional[EncryptedFile], msgtype=MessageType,
                                  file_name=str, info=FileInfo)
                                  file_name=str, info=FileInfo)
@@ -269,22 +271,29 @@ class Portal(DBPortal, BasePortal):
                          width=int(media.images.fixed_height.width))
                          width=int(media.images.fixed_height.width))
         return await self._reupload_instagram_file(source, url, MessageType.IMAGE, info, intent)
         return await self._reupload_instagram_file(source, url, MessageType.IMAGE, info, intent)
 
 
+    async def _reupload_instagram_voice(self, source: 'u.User', media: VoiceMediaItem,
+                                        intent: IntentAPI) -> Optional[ReuploadedMediaInfo]:
+        url = media.media.audio.audio_src
+        info = AudioInfo(duration=media.media.audio.duration)
+        return await self._reupload_instagram_file(source, url, MessageType.AUDIO, info, intent)
+
     async def _reupload_instagram_file(self, source: 'u.User', url: str, msgtype: MessageType,
     async def _reupload_instagram_file(self, source: 'u.User', url: str, msgtype: MessageType,
                                        info: FileInfo, intent: IntentAPI
                                        info: FileInfo, intent: IntentAPI
                                        ) -> Optional[ReuploadedMediaInfo]:
                                        ) -> Optional[ReuploadedMediaInfo]:
         async with await source.client.raw_http_get(url) as resp:
         async with await source.client.raw_http_get(url) as resp:
             data = await resp.read()
             data = await resp.read()
-            info.mime_type = resp.headers["Content-Type"] or magic.from_buffer(data, mime=True)
+            info.mimetype = resp.headers["Content-Type"] or magic.from_buffer(data, mime=True)
         info.size = len(data)
         info.size = len(data)
         extension = {
         extension = {
             "image/webp": ".webp",
             "image/webp": ".webp",
             "image/jpeg": ".jpg",
             "image/jpeg": ".jpg",
-            "video/mp4": ".mp4"
-        }.get(info.mime_type)
-        extension = extension or mimetypes.guess_extension(info.mime_type) or ""
+            "video/mp4": ".mp4",
+            "audio/mp4": ".m4a",
+        }.get(info.mimetype)
+        extension = extension or mimetypes.guess_extension(info.mimetype) or ""
         file_name = f"{msgtype.value[2:]}{extension}"
         file_name = f"{msgtype.value[2:]}{extension}"
 
 
-        upload_mime_type = info.mime_type
+        upload_mime_type = info.mimetype
         upload_file_name = file_name
         upload_file_name = file_name
         decryption_info = None
         decryption_info = None
         if self.encrypted and encrypt_attachment:
         if self.encrypted and encrypt_attachment:
@@ -309,6 +318,8 @@ class Portal(DBPortal, BasePortal):
         elif item.animated_media:
         elif item.animated_media:
             reuploaded = await self._reupload_instagram_animated(source, item.animated_media,
             reuploaded = await self._reupload_instagram_animated(source, item.animated_media,
                                                                  intent)
                                                                  intent)
+        elif item.voice_media:
+            reuploaded = await self._reupload_instagram_voice(source, item.voice_media, intent)
         else:
         else:
             reuploaded = None
             reuploaded = None
         if not reuploaded:
         if not reuploaded:
@@ -346,7 +357,7 @@ class Portal(DBPortal, BasePortal):
             else:
             else:
                 intent = sender.intent_for(self)
                 intent = sender.intent_for(self)
             event_id = None
             event_id = None
-            if item.media or item.animated_media:
+            if item.media or item.animated_media or item.voice_media:
                 event_id = await self._handle_instagram_media(source, intent, item)
                 event_id = await self._handle_instagram_media(source, intent, item)
             if item.text:
             if item.text:
                 event_id = await self._handle_instagram_text(intent, item)
                 event_id = await self._handle_instagram_text(intent, item)