Browse Source

Add support for Instagram->Matrix location messages

Tulir Asokan 4 years ago
parent
commit
aa75c2a2be
4 changed files with 43 additions and 4 deletions
  1. 1 1
      ROADMAP.md
  2. 2 1
      mauigpapi/types/__init__.py
  3. 15 0
      mauigpapi/types/thread_item.py
  4. 25 2
      mautrix_instagram/portal.py

+ 1 - 1
ROADMAP.md

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

+ 2 - 1
mauigpapi/types/__init__.py

@@ -12,7 +12,8 @@ from .thread_item import (ThreadItemType, ThreadItemActionLog, ViewMode, Creativ
                           CreateModeAttribution, ImageVersion, ImageVersions, VisualMedia, Caption,
                           CreateModeAttribution, ImageVersion, ImageVersions, VisualMedia, Caption,
                           RegularMediaItem, MediaShareItem, ReplayableMediaItem, VideoVersion,
                           RegularMediaItem, MediaShareItem, ReplayableMediaItem, VideoVersion,
                           AudioInfo, VoiceMediaItem, AnimatedMediaImage, AnimatedMediaImages,
                           AudioInfo, VoiceMediaItem, AnimatedMediaImage, AnimatedMediaImages,
-                          AnimatedMediaItem, ThreadItem, VoiceMediaData, Reaction, Reactions)
+                          AnimatedMediaItem, ThreadItem, VoiceMediaData, Reaction, Reactions,
+                          Location)
 from .thread import Thread, ThreadUser, ThreadItem, ThreadUserLastSeenAt, ThreadTheme
 from .thread import Thread, ThreadUser, ThreadItem, ThreadUserLastSeenAt, ThreadTheme
 from .mqtt import (Operation, ThreadAction, ReactionStatus, TypingStatus, CommandResponsePayload,
 from .mqtt import (Operation, ThreadAction, ReactionStatus, TypingStatus, CommandResponsePayload,
                    CommandResponse, IrisPayloadData, IrisPayload, MessageSyncMessage,
                    CommandResponse, IrisPayloadData, IrisPayload, MessageSyncMessage,

+ 15 - 0
mauigpapi/types/thread_item.py

@@ -290,6 +290,20 @@ class Reactions(SerializableAttrs['Reactions']):
     emojis: List[Reaction] = attr.ib(factory=lambda: [])
     emojis: List[Reaction] = attr.ib(factory=lambda: [])
 
 
 
 
+@dataclass
+class Location(SerializableAttrs['Location']):
+    pk: int
+    short_name: str
+    facebook_places_id: int
+    # TODO enum?
+    external_source: str  # facebook_places
+    name: str
+    address: str
+    city: str
+    lng: float
+    lat: float
+
+
 @dataclass(kw_only=True)
 @dataclass(kw_only=True)
 class ThreadItem(SerializableAttrs['ThreadItem']):
 class ThreadItem(SerializableAttrs['ThreadItem']):
     item_id: Optional[str] = None
     item_id: Optional[str] = None
@@ -308,4 +322,5 @@ class ThreadItem(SerializableAttrs['ThreadItem']):
     animated_media: Optional[AnimatedMediaItem] = None
     animated_media: Optional[AnimatedMediaItem] = None
     visual_media: Optional[VisualMedia] = None
     visual_media: Optional[VisualMedia] = None
     media_share: Optional[MediaShareItem] = None
     media_share: Optional[MediaShareItem] = None
+    location: Optional[Location] = None
     reactions: Optional[Reactions] = None
     reactions: Optional[Reactions] = None

+ 25 - 2
mautrix_instagram/portal.py

@@ -24,12 +24,12 @@ 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)
+                             VoiceMediaItem, Location)
 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, AudioInfo,
                            VideoInfo, MediaMessageEventContent, TextMessageEventContent, AudioInfo,
-                           ContentURI, EncryptedFile)
+                           ContentURI, EncryptedFile, LocationMessageEventContent, Format)
 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
 from mautrix.util.network_retry import call_with_net_retry
 from mautrix.util.network_retry import call_with_net_retry
@@ -334,6 +334,27 @@ class Portal(DBPortal, BasePortal):
         content = TextMessageEventContent(msgtype=MessageType.TEXT, body=item.text)
         content = TextMessageEventContent(msgtype=MessageType.TEXT, body=item.text)
         return await self._send_message(intent, content, timestamp=item.timestamp // 1000)
         return await self._send_message(intent, content, timestamp=item.timestamp // 1000)
 
 
+    async def _handle_instagram_location(self, intent: IntentAPI, item: ThreadItem) -> EventID:
+        loc = item.location
+        long_char = "E" if loc.lng > 0 else "W"
+        lat_char = "N" if loc.lat > 0 else "S"
+
+        body = (f"{loc.name} - {round(abs(loc.lat), 4)}° {lat_char}, "
+                f"{round(abs(loc.lng), 4)}° {long_char}")
+        url = f"https://www.openstreetmap.org/#map=15/{loc.lat}/{loc.lng}"
+
+        external_url = None
+        if loc.external_source == "facebook_places":
+            external_url = f"https://www.facebook.com/{loc.short_name}-{loc.facebook_places_id}"
+
+        content = LocationMessageEventContent(
+            msgtype=MessageType.LOCATION, geo_uri=f"geo:{loc.lat},{loc.lng}",
+            body=f"Location: {body}\n{url}", external_url=external_url)
+        content["format"] = str(Format.HTML)
+        content["formatted_body"] = f"Location: <a href='{url}'>{body}</a>"
+
+        return await self._send_message(intent, content, timestamp=item.timestamp // 1000)
+
     async def handle_instagram_item(self, source: 'u.User', sender: 'p.Puppet', item: ThreadItem,
     async def handle_instagram_item(self, source: 'u.User', sender: 'p.Puppet', item: ThreadItem,
                                     is_backfill: bool = False) -> None:
                                     is_backfill: bool = False) -> None:
         if item.client_context in self._reqid_dedup:
         if item.client_context in self._reqid_dedup:
@@ -359,6 +380,8 @@ class Portal(DBPortal, BasePortal):
             event_id = None
             event_id = None
             if item.media or item.animated_media or item.voice_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)
+            elif item.location:
+                event_id = await self._handle_instagram_location(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)
             # TODO handle other attachments
             # TODO handle other attachments