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

Commit

Permalink
added support for personal spaces
Browse files Browse the repository at this point in the history
  • Loading branch information
eyJhb committed Mar 7, 2023
1 parent 1e760d3 commit 90667ea
Show file tree
Hide file tree
Showing 7 changed files with 137 additions and 4 deletions.
1 change: 1 addition & 0 deletions mautrix_facebook/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ def do_update(self, helper: ConfigUpdateHelper) -> None:
copy("bridge.sync_direct_chat_list")
copy("bridge.double_puppet_server_map")
copy("bridge.double_puppet_allow_discovery")
copy("bridge.enable_space_per_user")
if "bridge.login_shared_secret" in self:
base["bridge.login_shared_secret_map"] = {
base["homeserver.domain"]: self["bridge.login_shared_secret"]
Expand Down
1 change: 1 addition & 0 deletions mautrix_facebook/db/upgrade/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,5 @@
v09_portal_infinite_backfill,
v10_user_thread_sync_status,
v11_user_thread_sync_done_flag,
v12_space_per_user,
)
23 changes: 23 additions & 0 deletions mautrix_facebook/db/upgrade/v12_space_per_user.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# mautrix-facebook - A Matrix-Facebook Messenger puppeting bridge.
# Copyright (C) 2022 Tulir Asokan, Sumner Evans
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# 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 mautrix.util.async_db import Connection

from . import upgrade_table


@upgrade_table.register(description="Store space in user table")
async def upgrade_v12(conn: Connection) -> None:
await conn.execute('ALTER TABLE "user" ADD COLUMN space_room TEXT')
9 changes: 6 additions & 3 deletions mautrix_facebook/db/user.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ class User:
fbid: int | None
state: AndroidState | None
notice_room: RoomID | None
space_room: RoomID | None
seq_id: int | None
connect_token_hash: bytes | None
oldest_backfilled_thread_ts: int | None
Expand All @@ -59,6 +60,7 @@ def _from_row(cls, row: Record | None) -> User | None:
"fbid",
"state",
"notice_room",
"space_room",
"seq_id",
"connect_token_hash",
"oldest_backfilled_thread_ts",
Expand Down Expand Up @@ -92,6 +94,7 @@ def _values(self):
self.fbid,
self._state_json,
self.notice_room,
self.space_room,
self.seq_id,
self.connect_token_hash,
self.oldest_backfilled_thread_ts,
Expand All @@ -102,7 +105,7 @@ def _values(self):
async def insert(self) -> None:
q = f"""
INSERT INTO "user" ({self._columns})
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)
"""
await self.db.execute(q, *self._values)

Expand All @@ -112,8 +115,8 @@ async def delete(self) -> None:
async def save(self) -> None:
q = """
UPDATE "user"
SET fbid=$2, state=$3, notice_room=$4, seq_id=$5, connect_token_hash=$6,
oldest_backfilled_thread_ts=$7, total_backfilled_portals=$8, thread_sync_completed=$9
SET fbid=$2, state=$3, notice_room=$4, space_room=$5, seq_id=$6, connect_token_hash=$7,
oldest_backfilled_thread_ts=$8, total_backfilled_portals=$9, thread_sync_completed=$10
WHERE mxid=$1
"""
await self.db.execute(q, *self._values)
Expand Down
3 changes: 3 additions & 0 deletions mautrix_facebook/example-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,9 @@ bridge:
# Localpart template of MXIDs for Facebook users.
# {userid} is replaced with the user ID of the Facebook user.
username_template: "facebook_{userid}"
# Whether or not to enable creating a space per user and inviting the user
# (as well as all of the puppets) to that space.
enable_space_per_user: true
# Displayname template for Facebook users.
# {displayname} is replaced with the display name of the Facebook user
# as defined below in displayname_preference.
Expand Down
56 changes: 56 additions & 0 deletions mautrix_facebook/portal.py
Original file line number Diff line number Diff line change
Expand Up @@ -487,6 +487,19 @@ async def _update_participant(
await puppet.intent_for(self).ensure_joined(self.mxid, bot=self.main_intent)
if puppet.fbid in nick_map and not puppet.is_real_user:
await self.sync_per_room_nick(puppet, nick_map[puppet.fbid])

if source.space_room:
try:
await self.az.intent.invite_user(
source.space_room, puppet.custom_mxid or puppet.mxid
)
await puppet.intent.join_room_by_id(source.space_room)
except Exception as e:
self.log.warning(
f"Failed to invite and join puppet {puppet.fbid} to "
f"space {source.space_room}: {e}"
)

return changed

async def _update_participants(self, source: u.User, info: graphql.Thread) -> bool:
Expand Down Expand Up @@ -529,6 +542,14 @@ async def _update_matrix_room(
if did_join and self.is_direct:
await source.update_direct_chats({self.main_intent.mxid: [self.mxid]})

if source.space_room and self.mxid:
await self.az.intent.send_state_event(
source.space_room,
EventType.SPACE_CHILD,
{"via": [self.config["homeserver.domain"]], "suggested": True},
state_key=str(self.mxid),
)

info = await self.update_info(source, info)
if not info:
self.log.warning("Canceling _update_matrix_room as update_info didn't return info")
Expand Down Expand Up @@ -692,6 +713,19 @@ async def _create_matrix_room(
await self.az.intent.ensure_joined(self.mxid)
except Exception:
self.log.warning(f"Failed to add bridge bot to new private chat {self.mxid}")

if source.space_room:
try:
await self.az.intent.send_state_event(
source.space_room,
EventType.SPACE_CHILD,
{"via": [self.config["homeserver.domain"]], "suggested": True},
state_key=str(self.mxid),
)
await self.az.intent.invite_user(source.space_room, source.mxid)
except Exception:
self.log.warning(f"Failed to add chat {self.mxid} to user's space")

await self.save()
self.log.debug(f"Matrix room created: {self.mxid}")
self.by_mxid[self.mxid] = self
Expand All @@ -711,6 +745,20 @@ async def _create_matrix_room(
exc_info=True,
)

if self.is_direct and puppet:
try:
did_join = await puppet.intent.join_room_by_id(self.mxid)
if did_join:
await source.update_direct_chats({self.main_intent.mxid: [self.mxid]})
if source.space_room:
await self.az.intent.invite_user(source.space_room, puppet.custom_mxid)
await puppet.intent.join_room_by_id(source.space_room)
except MatrixError:
self.log.debug(
"Failed to join custom puppet into newly created portal",
exc_info=True,
)

if not self.is_direct:
await self._update_participants(source, info)

Expand Down Expand Up @@ -1465,6 +1513,14 @@ async def handle_matrix_leave(self, user: u.User) -> None:
f"{user.mxid} was the recipient of this portal. Cleaning up and deleting..."
)
await self.cleanup_and_delete()

if user.space_room:
await self.az.intent.send_state_event(
user.space_room,
EventType.SPACE_CHILD,
{},
state_key=str(self.mxid),
)
else:
self.log.debug(f"{user.mxid} left portal to {self.fbid}")

Expand Down
48 changes: 47 additions & 1 deletion mautrix_facebook/user.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,15 +30,17 @@
from maufbapi.mqtt import Connect, Disconnect, MQTTNotConnected, MQTTNotLoggedIn, ProxyUpdate
from maufbapi.types import graphql, mqtt as mqtt_t
from maufbapi.types.graphql.responses import Message, Thread
from mautrix.bridge import BaseUser, async_getter_lock
from mautrix.bridge import BaseUser, async_getter_lock, portal
from mautrix.errors import MNotFound
from mautrix.types import (
EventID,
EventType,
MessageType,
PresenceState,
PushActionType,
PushRuleKind,
PushRuleScope,
RoomDirectoryVisibility,
RoomID,
TextMessageEventContent,
UserID,
Expand Down Expand Up @@ -115,6 +117,7 @@ class User(DBUser, BaseUser):
seq_id: int | None

_notice_room_lock: asyncio.Lock
_space_room_lock: asyncio.Lock
_notice_send_lock: asyncio.Lock
is_admin: bool
permission_level: str
Expand All @@ -137,6 +140,7 @@ def __init__(
fbid: int | None = None,
state: AndroidState | None = None,
notice_room: RoomID | None = None,
space_room: RoomID | None = None,
seq_id: int | None = None,
connect_token_hash: bytes | None = None,
oldest_backfilled_thread_ts: int | None = None,
Expand All @@ -148,6 +152,7 @@ def __init__(
fbid=fbid,
state=state,
notice_room=notice_room,
space_room=space_room,
seq_id=seq_id,
connect_token_hash=connect_token_hash,
oldest_backfilled_thread_ts=oldest_backfilled_thread_ts,
Expand Down Expand Up @@ -531,6 +536,8 @@ async def post_login(self, is_startup: bool, from_login: bool = False) -> None:
except Exception:
self.log.exception("Failed to automatically enable custom puppet")

await self._create_or_update_space()

# Backfill requests are handled synchronously so as not to overload the homeserver.
# Users can configure their backfill stages to be more or less aggressive with backfilling
# to try and avoid getting banned.
Expand Down Expand Up @@ -986,6 +993,45 @@ async def get_portal_with(self, puppet: pu.Puppet, create: bool = True) -> po.Po
puppet.fbid, fb_receiver=self.fbid, create=create, fb_type=ThreadType.USER
)

async def _create_or_update_space(self):
if not self.config["bridge.enable_space_per_user"]:
return

avatar_state_event_content = {"url": self.config["appservice.bot_avatar"]}
name_state_event_content = {"name": "Facebook"} # TODO template

if self.space_room:
await self.az.intent.send_state_event(
self.space_room, EventType.ROOM_AVATAR, avatar_state_event_content
)
await self.az.intent.send_state_event(
self.space_room, EventType.ROOM_NAME, name_state_event_content
)
else:
self.log.debug(f"Creating space for {self.fbid}, inviting {self.mxid}")
room = await self.az.intent.create_room(
is_direct=False,
invitees=[self.mxid],
creation_content={"type": "m.space"},
initial_state=[
{
"type": str(EventType.ROOM_NAME),
"content": name_state_event_content,
},
{
"type": str(EventType.ROOM_AVATAR),
"content": avatar_state_event_content,
},
],
)
self.space_room = room
await self.save()
self.log.debug(f"Created space {room}")
try:
await self.az.intent.ensure_joined(room)
except Exception:
self.log.warning(f"Failed to add bridge bot to new space {room}")

# region Facebook event handling

def start_listen(self) -> None:
Expand Down

0 comments on commit 90667ea

Please sign in to comment.