Skip to content

Commit

Permalink
implement handle_puppet_group_invite
Browse files Browse the repository at this point in the history
  • Loading branch information
maltee1 committed Aug 24, 2022
1 parent bdf4351 commit b41f818
Show file tree
Hide file tree
Showing 7 changed files with 118 additions and 42 deletions.
2 changes: 1 addition & 1 deletion ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@
* [ ] Provisioning API for logging in
* [x] Linking as secondary device
* [ ] Registering as primary device
* [x] Private chat creation by inviting Matrix puppet of Signal user to new room
* [x] Private chat/group creation by inviting Matrix puppet of Signal user to new room
* [x] Option to use own Matrix account for messages sent from other Signal clients
* [x] Automatic login with shared secret
* [x] Manual login with `login-matrix`
Expand Down
42 changes: 1 addition & 41 deletions mautrix_signal/commands/signal.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@

from mausignald.errors import UnknownIdentityKey, UnregisteredUserError
from mausignald.types import Address, GroupID, TrustLevel
from mautrix.appservice import IntentAPI
from mautrix.bridge.commands import SECTION_ADMIN, HelpSection, command_handler
from mautrix.types import (
ContentURI,
Expand All @@ -37,6 +36,7 @@
from ..util import normalize_number, user_has_power_level
from .auth import make_qr
from .typehint import CommandEvent
from .util import get_initial_state

try:
import PIL as _
Expand Down Expand Up @@ -350,7 +350,6 @@ async def create(evt: CommandEvent) -> EventID:
await warn_missing_power(levels, evt)

await portal.create_signal_group(evt.sender, levels, join_rule)
await evt.reply(f"Signal chat created. ID: {portal.chat_id}")


@command_handler(
Expand Down Expand Up @@ -531,45 +530,6 @@ async def _locked_confirm_bridge(
return await evt.reply("Bridging complete. Portal synchronization should begin momentarily.")


async def get_initial_state(
intent: IntentAPI, room_id: RoomID
) -> tuple[
str | None,
str | None,
PowerLevelStateEventContent | None,
bool,
ContentURI | None,
JoinRule | None,
]:
state = await intent.get_state(room_id)
title: str | None = None
about: str | None = None
levels: PowerLevelStateEventContent | None = None
encrypted: bool = False
avatar_url: ContentURI | None = None
join_rule: JoinRule | None = None
for event in state:
try:
if event.type == EventType.ROOM_NAME:
title = event.content.name
elif event.type == EventType.ROOM_TOPIC:
about = event.content.topic
elif event.type == EventType.ROOM_POWER_LEVELS:
levels = event.content
elif event.type == EventType.ROOM_CANONICAL_ALIAS:
title = title or event.content.canonical_alias
elif event.type == EventType.ROOM_ENCRYPTION:
encrypted = True
elif event.type == EventType.ROOM_AVATAR:
avatar_url = event.content.url
elif event.type == EventType.ROOM_JOIN_RULES:
join_rule = event.content.join_rule
except KeyError:
# Some state event probably has empty content
pass
return title, about, levels, encrypted, avatar_url


async def warn_missing_power(levels: PowerLevelStateEventContent, evt: CommandEvent) -> None:
bot_pl = levels.get_user_level(evt.az.bot_mxid)
if bot_pl < levels.get_event_level(EventType.ROOM_POWER_LEVELS):
Expand Down
58 changes: 58 additions & 0 deletions mautrix_signal/commands/util.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
# mautrix-signal - A Matrix-Signal puppeting bridge
# Copyright (C) 2021 Tulir Asokan
#
# 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 __future__ import annotations

from mautrix.appservice import IntentAPI
from mautrix.types import ContentURI, EventType, JoinRule, PowerLevelStateEventContent, RoomID


async def get_initial_state(
intent: IntentAPI, room_id: RoomID
) -> tuple[
str | None,
str | None,
PowerLevelStateEventContent | None,
bool,
ContentURI | None,
JoinRule | None,
]:
state = await intent.get_state(room_id)
title: str | None = None
about: str | None = None
levels: PowerLevelStateEventContent | None = None
encrypted: bool = False
avatar_url: ContentURI | None = None
join_rule: JoinRule | None = None
for event in state:
try:
if event.type == EventType.ROOM_NAME:
title = event.content.name
elif event.type == EventType.ROOM_TOPIC:
about = event.content.topic
elif event.type == EventType.ROOM_POWER_LEVELS:
levels = event.content
elif event.type == EventType.ROOM_CANONICAL_ALIAS:
title = title or event.content.canonical_alias
elif event.type == EventType.ROOM_ENCRYPTION:
encrypted = True
elif event.type == EventType.ROOM_AVATAR:
avatar_url = event.content.url
elif event.type == EventType.ROOM_JOIN_RULES:
join_rule = event.content.join_rule
except KeyError:
# Some state event probably has empty content
pass
return title, about, levels, encrypted, avatar_url, join_rule
1 change: 1 addition & 0 deletions mautrix_signal/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,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.create_group_on_invite")
if self["bridge.login_shared_secret"]:
base["bridge.login_shared_secret_map"] = {
base["homeserver.domain"]: self["bridge.login_shared_secret"]
Expand Down
3 changes: 3 additions & 0 deletions mautrix_signal/example-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,9 @@ bridge:
periodic_sync: 0
# Should leaving the room on Matrix make the user leave on Signal?
bridge_matrix_leave: true
# Should the bridge auto-create a group chat on Signal when a ghost is invited to a room?
# Requires the user to have sufficient power level and double puppeting enabled.
create_group_on_invite: true

# Provisioning API part of the web server for automated portal creation and fetching information.
# Used by things like mautrix-manager (https://github.com/tulir/mautrix-manager).
Expand Down
53 changes: 53 additions & 0 deletions mautrix_signal/matrix.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

from typing import TYPE_CHECKING

from mausignald.types import GroupID
from mautrix.bridge import BaseMatrixHandler, RejectMatrixInvite
from mautrix.types import (
Event,
Expand All @@ -36,6 +37,7 @@
)

from . import portal as po, puppet as pu, signal as s, user as u
from .commands.util import get_initial_state
from .db import Message as DBMessage

if TYPE_CHECKING:
Expand All @@ -54,6 +56,57 @@ def __init__(self, bridge: "SignalBridge") -> None:

super().__init__(bridge=bridge)

async def handle_puppet_group_invite(
self,
room_id: RoomID,
puppet: pu.Puppet,
invited_by: u.User,
evt: StateEvent,
members: list[UserID],
) -> None:
double_puppet = await pu.Puppet.get_by_custom_mxid(invited_by.mxid)
if (
not double_puppet
or self.az.bot_mxid in members
or not self.config["bridge.create_group_on_invite"]
):
if self.az.bot_mxid not in members:
await puppet.default_mxid_intent.leave_room(
room_id,
reason="This ghost does not join multi-user rooms without the bridge bot.",
)
else:
await puppet.default_mxid_intent.send_notice(
room_id,
"This ghost will remain inactive "
"until a Signal Group is created for this room.",
)
return

await double_puppet.intent.invite_user(room_id, self.az.bot_mxid)

title, about, levels, encrypted, avatar_url, join_rule = await get_initial_state(
double_puppet.intent, room_id
)

portal = po.Portal(
chat_id=GroupID(""),
mxid=evt.room_id,
name=title,
topic=about or "",
encrypted=encrypted,
receiver="",
avatar_url=avatar_url,
)
await portal.az.intent.ensure_joined(room_id)
levels = await portal.az.intent.get_power_levels(room_id)
invited_by_level = levels.get_user_level(invited_by.mxid)
if invited_by_level > levels.get_user_level(self.az.bot_mxid):
levels.users[self.az.bot_mxid] = 100 if invited_by_level >= 100 else invited_by_level
await double_puppet.intent.set_power_levels(room_id, levels)

await portal.create_signal_group(invited_by, levels, join_rule)

async def handle_invite(
self, room_id: RoomID, user_id: UserID, inviter: u.User, event_id: EventID
) -> None:
Expand Down
1 change: 1 addition & 0 deletions mautrix_signal/portal.py
Original file line number Diff line number Diff line change
Expand Up @@ -1794,6 +1794,7 @@ async def create_signal_group(
await self.handle_matrix_join_rules(source, join_rule)
await self.update()
await self.update_bridge_info()
await self.main_intent.send_notice(self.mxid, f"Signal group created. ID: {self.chat_id}")

async def bridge_signal_group(
self, source: u.User, levels: PowerLevelStateEventContent
Expand Down

0 comments on commit b41f818

Please sign in to comment.