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

added support for personal spaces #291

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions mautrix_facebook/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,8 @@ 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.space_support.enable")
copy("bridge.space_support.name")
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 @@ -15,4 +15,5 @@
v10_user_thread_sync_status,
v11_user_thread_sync_done_flag,
v12_puppet_contact_info_set,
v13_space_per_user,
)
23 changes: 23 additions & 0 deletions mautrix_facebook/db/upgrade/v13_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_v13(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
7 changes: 7 additions & 0 deletions mautrix_facebook/example-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,13 @@ bridge:
# Localpart template of MXIDs for Facebook users.
# {userid} is replaced with the user ID of the Facebook user.
username_template: "facebook_{userid}"
# Settings for creating a space for every user.
space_support:
# 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: false
# The name of the space
name: "Facebook"
# 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 @@ -502,6 +502,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 @@ -544,6 +557,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 @@ -709,6 +730,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 @@ -728,6 +762,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 @@ -1563,6 +1611,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 @@ -37,15 +37,17 @@
)
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 @@ -127,6 +129,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 @@ -152,6 +155,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 @@ -163,6 +167,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 @@ -537,6 +542,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 @@ -997,6 +1004,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.space_support.enable"]:
return

avatar_state_event_content = {"url": self.config["appservice.bot_avatar"]}
name_state_event_content = {"name": self.config["bridge.space_support.name"]}

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
Loading