浏览代码

voice messages: bridge to native Matrix messages

Sumner Evans 3 年之前
父节点
当前提交
5f956ed5f5
共有 1 个文件被更改,包括 39 次插入20 次删除
  1. 39 20
      mautrix_instagram/portal.py

+ 39 - 20
mautrix_instagram/portal.py

@@ -14,7 +14,7 @@
 # You should have received a copy of the GNU Affero General Public License
 # 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/>.
 # along with this program.  If not, see <https://www.gnu.org/licenses/>.
 from typing import (Dict, Tuple, Optional, List, Deque, Set, Any, Union, AsyncGenerator,
 from typing import (Dict, Tuple, Optional, List, Deque, Set, Any, Union, AsyncGenerator,
-                    Awaitable, NamedTuple, Callable, TYPE_CHECKING, cast)
+                    Awaitable, Callable, TYPE_CHECKING, cast)
 from collections import deque
 from collections import deque
 from io import BytesIO
 from io import BytesIO
 import mimetypes
 import mimetypes
@@ -22,20 +22,22 @@ import asyncio
 
 
 import asyncpg
 import asyncpg
 import magic
 import magic
+from mautrix.types.event.message import Audio, ExtensibleFile, Voice
 from mautrix.util.message_send_checkpoint import MessageSendCheckpointStatus
 from mautrix.util.message_send_checkpoint import MessageSendCheckpointStatus
 
 
 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, ExpiredMediaItem, MessageSyncMessage, ReelShareType,
                              VoiceMediaItem, ExpiredMediaItem, MessageSyncMessage, ReelShareType,
-                             TypingStatus, ThreadUserLastSeenAt, MediaShareItem)
+                             TypingStatus, ThreadUserLastSeenAt, MediaShareItem, ReelMediaShareItem)
 from mautrix.appservice import AppService, IntentAPI
 from mautrix.appservice import AppService, IntentAPI
 from mautrix.bridge import BasePortal, NotificationDisabler, async_getter_lock
 from mautrix.bridge import BasePortal, NotificationDisabler, async_getter_lock
 from mautrix.types import (EventID, MessageEventContent, RoomID, EventType, MessageType, ImageInfo,
 from mautrix.types import (EventID, MessageEventContent, RoomID, EventType, MessageType, ImageInfo,
                            VideoInfo, MediaMessageEventContent, TextMessageEventContent, AudioInfo,
                            VideoInfo, MediaMessageEventContent, TextMessageEventContent, AudioInfo,
-                           ContentURI, EncryptedFile, LocationMessageEventContent, Format, UserID)
+                           ContentURI, LocationMessageEventContent, Format, UserID)
 from mautrix.errors import MatrixError, MForbidden, MNotFound, SessionNotFound
 from mautrix.errors import MatrixError, MForbidden, MNotFound, SessionNotFound
 from mautrix.util.simple_lock import SimpleLock
 from mautrix.util.simple_lock import SimpleLock
 
 
+
 from .db import Portal as DBPortal, Message as DBMessage, Reaction as DBReaction
 from .db import Portal as DBPortal, Message as DBMessage, Reaction as DBReaction
 from .config import Config
 from .config import Config
 from . import user as u, puppet as p, matrix as m
 from . import user as u, puppet as p, matrix as m
@@ -56,11 +58,15 @@ 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]
 FileInfo = Union[AudioInfo, ImageInfo, VideoInfo]
-ReuploadedMediaInfo = NamedTuple('ReuploadedMediaInfo', mxc=Optional[ContentURI], url=str,
-                                 decryption_info=Optional[EncryptedFile], msgtype=MessageType,
-                                 file_name=str, info=FileInfo)
-MediaData = Union[RegularMediaItem, ExpiredMediaItem]
-MediaUploadFunc = Callable[['u.User', MediaData, IntentAPI], Awaitable[ReuploadedMediaInfo]]
+MediaData = Union[
+    AnimatedMediaItem,
+    ExpiredMediaItem,
+    MediaShareItem,
+    ReelMediaShareItem,
+    RegularMediaItem,
+    VoiceMediaItem,
+]
+MediaUploadFunc = Callable[['u.User', MediaData, IntentAPI], Awaitable[MediaMessageEventContent]]
 
 
 
 
 class Portal(DBPortal, BasePortal):
 class Portal(DBPortal, BasePortal):
@@ -432,7 +438,7 @@ class Portal(DBPortal, BasePortal):
     # region Instagram event handling
     # region Instagram event handling
 
 
     async def _reupload_instagram_media(self, source: 'u.User', media: RegularMediaItem,
     async def _reupload_instagram_media(self, source: 'u.User', media: RegularMediaItem,
-                                        intent: IntentAPI) -> ReuploadedMediaInfo:
+                                        intent: IntentAPI) -> MediaMessageEventContent:
         if media.media_type == MediaType.IMAGE:
         if media.media_type == MediaType.IMAGE:
             image = media.best_image
             image = media.best_image
             if not image:
             if not image:
@@ -452,21 +458,31 @@ class Portal(DBPortal, BasePortal):
         return await self._reupload_instagram_file(source, url, msgtype, info, intent)
         return await self._reupload_instagram_file(source, url, msgtype, info, intent)
 
 
     async def _reupload_instagram_animated(self, source: 'u.User', media: AnimatedMediaItem,
     async def _reupload_instagram_animated(self, source: 'u.User', media: AnimatedMediaItem,
-                                           intent: IntentAPI) -> ReuploadedMediaInfo:
+                                           intent: IntentAPI) -> MediaMessageEventContent:
         url = media.images.fixed_height.webp
         url = media.images.fixed_height.webp
         info = ImageInfo(height=int(media.images.fixed_height.height),
         info = ImageInfo(height=int(media.images.fixed_height.height),
                          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,
     async def _reupload_instagram_voice(self, source: 'u.User', media: VoiceMediaItem,
-                                        intent: IntentAPI) -> ReuploadedMediaInfo:
+                                        intent: IntentAPI) -> MediaMessageEventContent:
         url = media.media.audio.audio_src
         url = media.media.audio.audio_src
         info = AudioInfo(duration=media.media.audio.duration)
         info = AudioInfo(duration=media.media.audio.duration)
-        return await self._reupload_instagram_file(source, url, MessageType.AUDIO, info, intent)
+        waveform = [int(p * 1000) for p in media.media.audio.waveform_data]
+        content = await self._reupload_instagram_file(source, url, MessageType.AUDIO, info, intent)
+        content.extensible_file = ExtensibleFile(
+            file=content.file,
+            name=content.body,
+            mimetype=content.info.mimetype,
+            size=content.info.size,
+        )
+        content.audio = Audio(duration=media.media.audio.duration, waveform=waveform)
+        content.voice = Voice()
+        return content
 
 
     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
-                                       ) -> ReuploadedMediaInfo:
+                                       ) -> MediaMessageEventContent:
         async with await source.client.raw_http_get(url) as resp:
         async with await source.client.raw_http_get(url) as resp:
             try:
             try:
                 length = int(resp.headers["Content-Length"])
                 length = int(resp.headers["Content-Length"])
@@ -506,8 +522,14 @@ class Portal(DBPortal, BasePortal):
             decryption_info.url = mxc
             decryption_info.url = mxc
             mxc = None
             mxc = None
 
 
-        return ReuploadedMediaInfo(mxc=mxc, url=url, decryption_info=decryption_info,
-                                   file_name=file_name, msgtype=msgtype, info=info)
+        return MediaMessageEventContent(
+            body=file_name,
+            external_url=url,
+            url=mxc,
+            file=decryption_info,
+            info=info,
+            msgtype=msgtype,
+        )
 
 
     def _get_instagram_media_info(self, item: ThreadItem) -> Tuple[MediaUploadFunc, MediaData]:
     def _get_instagram_media_info(self, item: ThreadItem) -> Tuple[MediaUploadFunc, MediaData]:
         # TODO maybe use a dict and item.item_type instead of a ton of ifs
         # TODO maybe use a dict and item.item_type instead of a ton of ifs
@@ -543,17 +565,14 @@ class Portal(DBPortal, BasePortal):
                                       ) -> Optional[EventID]:
                                       ) -> Optional[EventID]:
         try:
         try:
             reupload_func, media_data = self._get_instagram_media_info(item)
             reupload_func, media_data = self._get_instagram_media_info(item)
-            reuploaded = await reupload_func(source, media_data, intent)
+            content = await reupload_func(source, media_data, intent)
         except ValueError as e:
         except ValueError as e:
             content = TextMessageEventContent(body=str(e), msgtype=MessageType.NOTICE)
             content = TextMessageEventContent(body=str(e), msgtype=MessageType.NOTICE)
         except Exception:
         except Exception:
             self.log.warning("Failed to upload media", exc_info=True)
             self.log.warning("Failed to upload media", exc_info=True)
             content = TextMessageEventContent(body="Attachment not available: failed to copy file",
             content = TextMessageEventContent(body="Attachment not available: failed to copy file",
                                               msgtype=MessageType.NOTICE)
                                               msgtype=MessageType.NOTICE)
-        else:
-            content = MediaMessageEventContent(
-                body=reuploaded.file_name, external_url=reuploaded.url, url=reuploaded.mxc,
-                file=reuploaded.decryption_info, info=reuploaded.info, msgtype=reuploaded.msgtype)
+
         await self._add_instagram_reply(content, item.replied_to_message)
         await self._add_instagram_reply(content, item.replied_to_message)
         return await self._send_message(intent, content, timestamp=item.timestamp // 1000)
         return await self._send_message(intent, content, timestamp=item.timestamp // 1000)