Skip to content
This repository was archived by the owner on Mar 2, 2024. It is now read-only.

Commit 98856db

Browse files
tulirAlejandro Herrera
and
Alejandro Herrera
committedDec 12, 2021
Add relay mode
Closes #27 Co-authored-by: Alejandro Herrera <bherrera@ikono.com.co>
1 parent e1ace60 commit 98856db

File tree

7 files changed

+79
-21
lines changed

7 files changed

+79
-21
lines changed
 

‎mautrix_instagram/config.py

+3-2
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
from mautrix.util.config import ConfigUpdateHelper, ForbiddenKey, ForbiddenDefault
2222
from mautrix.bridge.config import BaseBridgeConfig
2323

24-
Permissions = NamedTuple("Permissions", user=bool, admin=bool, level=str)
24+
Permissions = NamedTuple("Permissions", relay=bool, user=bool, admin=bool, level=str)
2525

2626

2727
class Config(BaseBridgeConfig):
@@ -102,7 +102,8 @@ def _get_permissions(self, key: str) -> Permissions:
102102
level = self["bridge.permissions"].get(key, "")
103103
admin = level == "admin"
104104
user = level == "user" or admin
105-
return Permissions(user, admin, level)
105+
relay = level == "relay" or user
106+
return Permissions(relay, user, admin, level)
106107

107108
def get_permissions(self, mxid: UserID) -> Permissions:
108109
permissions = self["bridge.permissions"]

‎mautrix_instagram/db/portal.py

+12-11
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
from attr import dataclass
1919
import asyncpg
2020

21-
from mautrix.types import RoomID, ContentURI
21+
from mautrix.types import RoomID, ContentURI, UserID
2222
from mautrix.util.async_db import Database
2323

2424
fake_db = Database("") if TYPE_CHECKING else None
@@ -37,22 +37,23 @@ class Portal:
3737
encrypted: bool
3838
name_set: bool
3939
avatar_set: bool
40+
relay_user_id: Optional[UserID]
4041

4142
async def insert(self) -> None:
4243
q = ("INSERT INTO portal (thread_id, receiver, other_user_pk, mxid, name, avatar_url, "
43-
" encrypted, name_set, avatar_set) "
44-
"VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)")
44+
" encrypted, name_set, avatar_set, relay_user_id) "
45+
"VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)")
4546
await self.db.execute(q, self.thread_id, self.receiver, self.other_user_pk,
4647
self.mxid, self.name, self.avatar_url, self.encrypted,
47-
self.name_set, self.avatar_set)
48+
self.name_set, self.avatar_set, self.relay_user_id)
4849

4950
async def update(self) -> None:
5051
q = ("UPDATE portal SET other_user_pk=$3, mxid=$4, name=$5, avatar_url=$6, encrypted=$7,"
51-
" name_set=$8, avatar_set=$9 "
52+
" name_set=$8, avatar_set=$9, relay_user_id=$10 "
5253
"WHERE thread_id=$1 AND receiver=$2")
5354
await self.db.execute(q, self.thread_id, self.receiver, self.other_user_pk,
5455
self.mxid, self.name, self.avatar_url, self.encrypted,
55-
self.name_set, self.avatar_set)
56+
self.name_set, self.avatar_set, self.relay_user_id)
5657

5758
@classmethod
5859
def _from_row(cls, row: asyncpg.Record) -> 'Portal':
@@ -61,7 +62,7 @@ def _from_row(cls, row: asyncpg.Record) -> 'Portal':
6162
@classmethod
6263
async def get_by_mxid(cls, mxid: RoomID) -> Optional['Portal']:
6364
q = ("SELECT thread_id, receiver, other_user_pk, mxid, name, avatar_url, encrypted, "
64-
" name_set, avatar_set "
65+
" name_set, avatar_set, relay_user_id "
6566
"FROM portal WHERE mxid=$1")
6667
row = await cls.db.fetchrow(q, mxid)
6768
if not row:
@@ -72,7 +73,7 @@ async def get_by_mxid(cls, mxid: RoomID) -> Optional['Portal']:
7273
async def get_by_thread_id(cls, thread_id: str, receiver: int,
7374
rec_must_match: bool = True) -> Optional['Portal']:
7475
q = ("SELECT thread_id, receiver, other_user_pk, mxid, name, avatar_url, encrypted, "
75-
" name_set, avatar_set "
76+
" name_set, avatar_set, relay_user_id "
7677
"FROM portal WHERE thread_id=$1 AND receiver=$2")
7778
if not rec_must_match:
7879
q = ("SELECT thread_id, receiver, other_user_pk, mxid, name, avatar_url, encrypted, "
@@ -86,23 +87,23 @@ async def get_by_thread_id(cls, thread_id: str, receiver: int,
8687
@classmethod
8788
async def find_private_chats_of(cls, receiver: int) -> List['Portal']:
8889
q = ("SELECT thread_id, receiver, other_user_pk, mxid, name, avatar_url, encrypted, "
89-
" name_set, avatar_set "
90+
" name_set, avatar_set, relay_user_id "
9091
"FROM portal WHERE receiver=$1 AND other_user_pk IS NOT NULL")
9192
rows = await cls.db.fetch(q, receiver)
9293
return [cls._from_row(row) for row in rows]
9394

9495
@classmethod
9596
async def find_private_chats_with(cls, other_user: int) -> List['Portal']:
9697
q = ("SELECT thread_id, receiver, other_user_pk, mxid, name, avatar_url, encrypted, "
97-
" name_set, avatar_set "
98+
" name_set, avatar_set, relay_user_id "
9899
"FROM portal WHERE other_user_pk=$1")
99100
rows = await cls.db.fetch(q, other_user)
100101
return [cls._from_row(row) for row in rows]
101102

102103
@classmethod
103104
async def all_with_room(cls) -> List['Portal']:
104105
q = ("SELECT thread_id, receiver, other_user_pk, mxid, name, avatar_url, encrypted, "
105-
" name_set, avatar_set "
106+
" name_set, avatar_set, relay_user_id "
106107
"FROM portal WHERE mxid IS NOT NULL")
107108
rows = await cls.db.fetch(q)
108109
return [cls._from_row(row) for row in rows]

‎mautrix_instagram/db/upgrade.py

+5
Original file line numberDiff line numberDiff line change
@@ -88,3 +88,8 @@ async def upgrade_v2(conn: Connection) -> None:
8888
await conn.execute("ALTER TABLE portal ADD COLUMN name_set BOOLEAN NOT NULL DEFAULT false")
8989
await conn.execute("ALTER TABLE portal ADD COLUMN avatar_set BOOLEAN NOT NULL DEFAULT false")
9090
await conn.execute("UPDATE portal SET name_set=true WHERE name<>''")
91+
92+
93+
@upgrade_table.register(description="Add relay user field to portal table")
94+
async def upgrade_v3(conn: Connection) -> None:
95+
await conn.execute("ALTER TABLE portal ADD COLUMN relay_user_id TEXT")

‎mautrix_instagram/example-config.yaml

+19
Original file line numberDiff line numberDiff line change
@@ -211,16 +211,35 @@ bridge:
211211

212212
# Permissions for using the bridge.
213213
# Permitted values:
214+
# relay - Allowed to be relayed through the bridge, no access to commands.
214215
# user - Use the bridge with puppeting.
215216
# admin - Use and administrate the bridge.
216217
# Permitted keys:
217218
# * - All Matrix users
218219
# domain - All users on that homeserver
219220
# mxid - Specific user
220221
permissions:
222+
"*": "relay"
221223
"example.com": "user"
222224
"@admin:example.com": "admin"
223225

226+
relay:
227+
# Whether relay mode should be allowed. If allowed, `!ig set-relay` can be used to turn any
228+
# authenticated user into a relaybot for that chat.
229+
enabled: false
230+
# The formats to use when sending messages to Instagram via a relay user.
231+
#
232+
# Available variables:
233+
# $sender_displayname - The display name of the sender (e.g. Example User)
234+
# $sender_username - The username (Matrix ID localpart) of the sender (e.g. exampleuser)
235+
# $sender_mxid - The Matrix ID of the sender (e.g. @exampleuser:example.com)
236+
# $message - The message content
237+
#
238+
# Note that Instagram doesn't support captions for images, so images won't include any indication of being relayed.
239+
message_formats:
240+
m.text: '$sender_displayname: $message'
241+
m.notice: '$sender_displayname: $message'
242+
m.emote: '* $sender_displayname $message'
224243

225244
# Python logging configuration.
226245
#

‎mautrix_instagram/portal.py

+38-6
Original file line numberDiff line numberDiff line change
@@ -87,9 +87,10 @@ class Portal(DBPortal, BasePortal):
8787
def __init__(self, thread_id: str, receiver: int, other_user_pk: Optional[int],
8888
mxid: Optional[RoomID] = None, name: Optional[str] = None,
8989
avatar_url: Optional[ContentURI] = None, encrypted: bool = False,
90-
name_set: bool = False, avatar_set: bool = False) -> None:
90+
name_set: bool = False, avatar_set: bool = False,
91+
relay_user_id: Optional[UserID] = None) -> None:
9192
super().__init__(thread_id, receiver, other_user_pk, mxid, name, avatar_url, encrypted,
92-
name_set, avatar_set)
93+
name_set, avatar_set, relay_user_id)
9394
self._create_room_lock = asyncio.Lock()
9495
self.log = self.log.getChild(thread_id)
9596
self._msgid_dedup = deque(maxlen=100)
@@ -103,6 +104,7 @@ def __init__(self, thread_id: str, receiver: int, other_user_pk: Optional[int],
103104
self._main_intent = None
104105
self._reaction_lock = asyncio.Lock()
105106
self._typing = set()
107+
self._relay_user = None
106108

107109
@property
108110
def is_direct(self) -> bool:
@@ -189,9 +191,20 @@ async def handle_matrix_message(self, sender: 'u.User', message: MessageEventCon
189191
msg="Fatal error in message handling (see logs for more details)",
190192
)
191193

192-
async def _handle_matrix_message(self, sender: 'u.User', message: MessageEventContent,
194+
async def _handle_matrix_message(self, orig_sender: 'u.User', message: MessageEventContent,
193195
event_id: EventID) -> None:
194-
if not sender.is_connected:
196+
sender, is_relay = await self.get_relay_sender(orig_sender, f"message {event_id}")
197+
if not sender:
198+
orig_sender.send_remote_checkpoint(
199+
status=MessageSendCheckpointStatus.PERM_FAILURE,
200+
event_id=event_id,
201+
room_id=self.mxid,
202+
event_type=EventType.ROOM_MESSAGE,
203+
message_type=message.msgtype,
204+
error="user is not logged in",
205+
)
206+
return
207+
elif not sender.is_connected:
195208
await self._send_bridge_error(
196209
sender,
197210
"You're not connected to Instagram",
@@ -201,6 +214,9 @@ async def _handle_matrix_message(self, sender: 'u.User', message: MessageEventCo
201214
confirmed=True,
202215
)
203216
return
217+
elif is_relay:
218+
await self.apply_relay_message_format(orig_sender, message)
219+
204220
request_id = sender.state.gen_client_context()
205221
self._reqid_dedup.add(request_id)
206222
self.log.debug(f"Handling Matrix message {event_id} from {sender.mxid}/{sender.igpk} "
@@ -289,6 +305,10 @@ async def handle_matrix_reaction(self, sender: 'u.User', event_id: EventID,
289305
self.log.debug(f"Ignoring reaction to unknown event {reacting_to}")
290306
return
291307

308+
if not await sender.is_logged_in():
309+
self.log.debug(f"Ignoring reaction by non-logged-in user {sender.mxid}")
310+
return
311+
292312
existing = await DBReaction.get_by_item_id(message.item_id, message.receiver, sender.igpk)
293313
if existing and existing.reaction == emoji:
294314
return
@@ -322,9 +342,19 @@ async def handle_matrix_reaction(self, sender: 'u.User', event_id: EventID,
322342
await self._upsert_reaction(existing, self.main_intent, event_id, message, sender,
323343
emoji)
324344

325-
async def handle_matrix_redaction(self, sender: 'u.User', event_id: EventID,
345+
async def handle_matrix_redaction(self, orig_sender: 'u.User', event_id: EventID,
326346
redaction_event_id: EventID) -> None:
327-
if not sender.is_connected:
347+
sender, _ = await self.get_relay_sender(orig_sender, f"redaction {event_id}")
348+
if not sender:
349+
orig_sender.send_remote_checkpoint(
350+
status=MessageSendCheckpointStatus.PERM_FAILURE,
351+
event_id=redaction_event_id,
352+
room_id=self.mxid,
353+
event_type=EventType.ROOM_REDACTION,
354+
error="user is not logged in",
355+
)
356+
return
357+
elif not sender.is_connected:
328358
await self._send_bridge_error(
329359
sender,
330360
"You're not connected to Instagram",
@@ -414,6 +444,8 @@ async def _handle_matrix_typing(self, users: Set[UserID], status: TypingStatus)
414444
await user.mqtt.indicate_activity(self.thread_id, status)
415445

416446
async def handle_matrix_leave(self, user: 'u.User') -> None:
447+
if not await user.is_logged_in():
448+
return
417449
if self.is_direct:
418450
self.log.info(f"{user.mxid} left private chat portal with {self.other_user_pk}")
419451
if user.igpk == self.receiver:

‎mautrix_instagram/user.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ def __init__(self, mxid: UserID, igpk: Optional[int] = None,
8585
self._notice_room_lock = asyncio.Lock()
8686
self._notice_send_lock = asyncio.Lock()
8787
perms = self.config.get_permissions(mxid)
88-
self.is_whitelisted, self.is_admin, self.permission_level = perms
88+
self.relay_whitelisted, self.is_whitelisted, self.is_admin, self.permission_level = perms
8989
self.client = None
9090
self.mqtt = None
9191
self.username = None

‎requirements.txt

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ commonmark>=0.8,<0.10
44
aiohttp>=3,<4
55
yarl>=1,<2
66
attrs>=20.1
7-
mautrix>=0.13.0,<0.14
7+
mautrix>=0.13.1,<0.14
88
asyncpg>=0.20,<0.26
99
pycryptodome>=3,<4
1010
paho-mqtt>=1.5,<2

0 commit comments

Comments
 (0)
This repository has been archived.