Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement Spaces #100

Open
wants to merge 3 commits 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
81 changes: 81 additions & 0 deletions .github/workflows/docker-publish.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
name: Docker

# This workflow uses actions that are not certified by GitHub.
# They are provided by a third-party and are governed by
# separate terms of service, privacy policy, and support
# documentation.

on:
push:
branches: [ "master" ]
# Publish semver tags as releases.
tags: [ 'v*.*.*' ]
pull_request:
branches: [ "master" ]

env:
# Use docker.io for Docker Hub if empty
REGISTRY: ghcr.io
# github.repository as <account>/<repo>
IMAGE_NAME: ${{ github.repository }}


jobs:
build:

runs-on: ubuntu-latest
permissions:
contents: read
packages: write
# This is used to complete the identity challenge
# with sigstore/fulcio when running outside of PRs.
id-token: write

steps:
- name: Checkout repository
uses: actions/checkout@v4

# Install the cosign tool except on PR
# https://github.com/sigstore/cosign-installer
- name: Install cosign
if: github.event_name != 'pull_request'
uses: sigstore/cosign-installer@6e04d228eb30da1757ee4e1dd75a0ec73a653e06 #v3.1.1
with:
cosign-release: 'v2.1.1'

# Set up BuildKit Docker container builder to be able to build
# multi-platform images and export cache
# https://github.com/docker/setup-buildx-action
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@f95db51fddba0c2d1ec667646a06c2ce06100226 # v3.0.0

# Login against a Docker registry except on PR
# https://github.com/docker/login-action
- name: Log into registry ${{ env.REGISTRY }}
if: github.event_name != 'pull_request'
uses: docker/login-action@343f7c4344506bcbf9b4de18042ae17996df046d # v3.0.0
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

# Extract metadata (tags, labels) for Docker
# https://github.com/docker/metadata-action
- name: Extract Docker metadata
id: meta
uses: docker/metadata-action@96383f45573cb7f253c731d3b3ab81c87ef81934 # v5.0.0
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}

# Build and push Docker image with Buildx (don't push on PR)
# https://github.com/docker/build-push-action
- name: Build and push Docker image
id: build-and-push
uses: docker/build-push-action@0565240e2d4ab88bba5387d719585280857ece09 # v5.0.0
with:
context: .
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
2 changes: 1 addition & 1 deletion mautrix_googlechat/commands/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
from . import auth
from . import auth, spaces
60 changes: 60 additions & 0 deletions mautrix_googlechat/commands/spaces.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import logging

from mautrix.bridge.commands import CommandEvent,HelpSection, command_handler
from mautrix.types import EventType

from .. import puppet as pu
from .. import portal as po


SECTION_SPACES = HelpSection("Miscellaneous", 30, "")

@command_handler(
needs_auth=True,
management_only=False,
help_section=SECTION_SPACES,
help_text="Synchronize your personal filtering space",
)
async def sync_space(evt: CommandEvent):
if not evt.bridge.config["bridge.space_support.enable"]:
await evt.reply("Spaces are not enabled on this instance of the bridge")
return

await evt.sender.create_or_update_space()

if not evt.sender.space_mxid:
await evt.reply("Failed to create or update space")
return

async for portal in po.Portal.all():
# Print the portal id and number of peer_type=chat portals
if not portal.mxid:
logging.debug(f"Portal {portal} has no mxid")
continue

logging.debug(f"Adding chat {portal.mxid} to user's space ({evt.sender.space_mxid})")
try:
await evt.bridge.az.intent.send_state_event(
evt.sender.space_mxid,
EventType.SPACE_CHILD,
{"via": [evt.bridge.config["homeserver.domain"]], "suggested": True},
state_key=str(portal.mxid),
)
except Exception:
logging.warning(
f"Failed to add chat {portal.mxid} to user's space ({evt.sender.space_mxid})"
)
# This will probably not work with gchat
#if portal.peer_type not in ("chat", "channel"):
# logging.debug(f"Adding puppet {portal.tgid} to user's space")
# puppet = await pu.Puppet.get_by_tgid(portal.tgid, create=False)
# if not puppet:
# continue
# try:
# await puppet.intent.ensure_joined(evt.sender.space_mxid)
# except Exception as e:
# logging.warning(
# f"Failed to join {puppet.mxid} to user's space ({evt.sender.space_mxid}): {e}"
# )

await evt.reply("Synced space")
2 changes: 2 additions & 0 deletions mautrix_googlechat/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,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
6 changes: 6 additions & 0 deletions mautrix_googlechat/db/puppet.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,12 @@ async def get_all_with_custom_mxid(cls) -> list[Puppet]:
q = f"SELECT {cls.columns} FROM puppet WHERE custom_mxid<>''"
rows = await cls.db.fetch(q)
return [cls._from_row(row) for row in rows]

@classmethod
async def all(cls) -> list[Puppet]:
q = f"SELECT {cls.columns} FROM puppet''"
rows = await cls.db.fetch(q)
return [cls._from_row(row) for row in rows]

@property
def _values(self):
Expand Down
1 change: 1 addition & 0 deletions mautrix_googlechat/db/upgrade/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,5 @@
v08_web_app_auth,
v09_web_app_ua,
v10_store_microsecond_timestamp,
v11_add_space_mxid_to_user,
)
30 changes: 30 additions & 0 deletions mautrix_googlechat/db/upgrade/v11_add_space_mxid_to_user.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# mautrix-googlechat - A Matrix-Google Chat puppeting bridge
# Copyright (C) 2023 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="Add space MXID to User")
async def upgrade_v6(conn: Connection):
create_table_queries = [
"""
ALTER TABLE "user" ADD COLUMN space_mxid TEXT
""",
]

for query in create_table_queries:
await conn.execute(query)
14 changes: 8 additions & 6 deletions mautrix_googlechat/db/user.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ class User:
user_agent: str | None
notice_room: RoomID | None
revision: int | None
space_mxid: RoomID | None

@classmethod
def _from_row(cls, row: Record | None) -> User | None:
Expand All @@ -51,7 +52,7 @@ def _from_row(cls, row: Record | None) -> User | None:
@classmethod
async def all_logged_in(cls) -> list[User]:
q = (
'SELECT mxid, gcid, cookies, user_agent, notice_room, revision FROM "user" '
'SELECT mxid, gcid, cookies, user_agent, notice_room, revision, space_mxid FROM "user" '
"WHERE cookies IS NOT NULL"
)
rows = await cls.db.fetch(q)
Expand All @@ -60,15 +61,15 @@ async def all_logged_in(cls) -> list[User]:
@classmethod
async def get_by_gcid(cls, gcid: str) -> User | None:
q = """
SELECT mxid, gcid, cookies, user_agent, notice_room, revision FROM "user" WHERE gcid=$1
SELECT mxid, gcid, cookies, user_agent, notice_room, revision, space_mxid FROM "user" WHERE gcid=$1
"""
row = await cls.db.fetchrow(q, gcid)
return cls._from_row(row)

@classmethod
async def get_by_mxid(cls, mxid: UserID) -> User | None:
q = """
SELECT mxid, gcid, cookies, user_agent, notice_room, revision FROM "user" WHERE mxid=$1
SELECT mxid, gcid, cookies, user_agent, notice_room, revision, space_mxid FROM "user" WHERE mxid=$1
"""
row = await cls.db.fetchrow(q, mxid)
return cls._from_row(row)
Expand All @@ -82,12 +83,13 @@ def _values(self):
self.user_agent,
self.notice_room,
self.revision,
self.space_mxid,
)

async def insert(self) -> None:
q = (
'INSERT INTO "user" (mxid, gcid, cookies, user_agent, notice_room, revision) '
"VALUES ($1, $2, $3, $4, $5, $6)"
'INSERT INTO "user" (mxid, gcid, cookies, user_agent, notice_room, revision, space_mxid) '
"VALUES ($1, $2, $3, $4, $5, $6, $7)"
)
await self.db.execute(q, *self._values)

Expand All @@ -96,7 +98,7 @@ async def delete(self) -> None:

async def save(self) -> None:
q = (
'UPDATE "user" SET gcid=$2, cookies=$3, user_agent=$4, notice_room=$5, revision=$6 '
'UPDATE "user" SET gcid=$2, cookies=$3, user_agent=$4, notice_room=$5, revision=$6, space_mxid=$7 '
"WHERE mxid=$1"
)
await self.db.execute(q, *self._values)
Expand Down
9 changes: 8 additions & 1 deletion mautrix_googlechat/example-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,13 @@ bridge:
# Displayname template for Google Chat users.
# {full_name}, {first_name}, {last_name} and {email} are replaced with names.
displayname_template: "{full_name} (Google Chat)"
# 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: "Google Chat"

# The prefix for commands. Only required in non-management rooms.
command_prefix: "!gc"
Expand Down Expand Up @@ -172,7 +179,7 @@ bridge:
# verified - Require manual per-device verification
# (currently only possible by modifying the `trust` column in the `crypto_device` database table).
verification_levels:
# Minimum level for which the bridge should send keys to when bridging messages from Telegram to Matrix.
# Minimum level for which the bridge should send keys to when bridging messages from Google Chat to Matrix.
receive: unverified
# Minimum level that the bridge should accept for incoming Matrix messages.
send: unverified
Expand Down
27 changes: 27 additions & 0 deletions mautrix_googlechat/portal.py
Original file line number Diff line number Diff line change
Expand Up @@ -571,6 +571,13 @@ async def _update_matrix_room(self, source: u.User, info: ChatInfo | None = None
did_join = await puppet.intent.ensure_joined(self.mxid)
if did_join and self.is_direct:
await source.update_direct_chats({self.main_intent.mxid: [self.mxid]})
if source.space_mxid and self.mxid:
await self.az.intent.send_state_event(
source.space_mxid,
EventType.SPACE_CHILD,
{"via": [self.config["homeserver.domain"]], "suggested": True},
state_key=str(self.mxid),
)
await self.update_info(source, info)

async def update_matrix_room(self, source: u.User, info: ChatInfo | None = None) -> None:
Expand Down Expand Up @@ -702,6 +709,18 @@ async def _create_matrix_room(self, source: u.User, info: ChatInfo | None = None
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_mxid:
self.log.debug(f"Adding chat {self.mxid} to user's space")
try:
await self.az.intent.send_state_event(
source.space_mxid,
EventType.SPACE_CHILD,
{"via": [self.config["homeserver.domain"]], "suggested": True},
state_key=str(self.mxid),
)
self.log.debug(f"Added chat {self.mxid} to user's space")
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 Down Expand Up @@ -1126,6 +1145,14 @@ async def handle_matrix_leave(self, user: u.User) -> None:
" cleaning up and deleting..."
)
await self.cleanup_and_delete()

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

Expand Down
Loading