Skip to content

Commit 5a363d3

Browse files
committed
handlematrix: add support for member events
1 parent e3bb26a commit 5a363d3

3 files changed

Lines changed: 146 additions & 4 deletions

File tree

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ require (
4242
golang.org/x/sync v0.20.0
4343
golang.org/x/tools v0.44.0
4444
gopkg.in/yaml.v3 v3.0.1
45-
maunium.net/go/mautrix v0.27.1-0.20260430124810-125ac2c48014
45+
maunium.net/go/mautrix v0.27.1-0.20260430160443-60db160fa51d
4646
rsc.io/qr v0.2.0
4747
)
4848

go.sum

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -236,7 +236,7 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
236236
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
237237
maunium.net/go/mauflag v1.0.0 h1:YiaRc0tEI3toYtJMRIfjP+jklH45uDHtT80nUamyD4M=
238238
maunium.net/go/mauflag v1.0.0/go.mod h1:nLivPOpTpHnpzEh8jEdSL9UqO9+/KBJFmNRlwKfkPeA=
239-
maunium.net/go/mautrix v0.27.1-0.20260430124810-125ac2c48014 h1:KwXGBWwUHYJKVTYWgbZEFcaM6uYLMvfjzHJg/TLwvKc=
240-
maunium.net/go/mautrix v0.27.1-0.20260430124810-125ac2c48014/go.mod h1:4fZ0M0xB5ZtueQI65RilX28J/3794BeK+LaCg4U61Jk=
239+
maunium.net/go/mautrix v0.27.1-0.20260430160443-60db160fa51d h1:kOxWOSl5sYsTBPgGeeEufHBNGRjlPXppmDAs8G0XIkI=
240+
maunium.net/go/mautrix v0.27.1-0.20260430160443-60db160fa51d/go.mod h1:4fZ0M0xB5ZtueQI65RilX28J/3794BeK+LaCg4U61Jk=
241241
rsc.io/qr v0.2.0 h1:6vBLea5/NRMVTz8V66gipeLycZMl/+UlFmk8DvqQ6WY=
242242
rsc.io/qr v0.2.0/go.mod h1:IF+uZjkb9fqyeF/4tlBoynqmQxUoPfWEKh921coOuXs=

pkg/connector/handlematrix.go

Lines changed: 143 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ var (
8080
_ bridgev2.DeleteChatHandlingNetworkAPI = (*TelegramClient)(nil)
8181
_ bridgev2.RoomNameHandlingNetworkAPI = (*TelegramClient)(nil)
8282
_ bridgev2.RoomAvatarHandlingNetworkAPI = (*TelegramClient)(nil)
83+
_ bridgev2.MembershipHandlingNetworkAPI = (*TelegramClient)(nil)
8384
)
8485

8586
func getMediaFilename(content *event.MessageEventContent) (filename string) {
@@ -382,7 +383,10 @@ func (tc *TelegramClient) transferMediaToTelegram(ctx context.Context, content *
382383
}, nil
383384
}
384385

385-
func (tc *TelegramClient) humaniseSendError(err error) bridgev2.MessageStatus {
386+
func (tc *TelegramClient) humaniseSendError(err error) error {
387+
if err == nil {
388+
return nil
389+
}
386390
status := bridgev2.WrapErrorInStatus(err).
387391
WithErrorReason(event.MessageStatusNetworkError).
388392
WithMessage(humanise.Error(err))
@@ -1296,3 +1300,141 @@ func (tc *TelegramClient) HandleMatrixRoomAvatar(ctx context.Context, msg *bridg
12961300
return false, fmt.Errorf("unsupported peer type %s for changing room avatar", peerType)
12971301
}
12981302
}
1303+
1304+
func wrapUnsupportedError(err error) bridgev2.MessageStatus {
1305+
return bridgev2.WrapErrorInStatus(err).
1306+
WithErrorReason(event.MessageStatusUnsupported).
1307+
WithIsCertain(true).
1308+
WithSendNotice(false)
1309+
}
1310+
1311+
func (tc *TelegramClient) HandleMatrixMembership(ctx context.Context, msg *bridgev2.MatrixMembershipChange) (*bridgev2.MatrixMembershipResult, error) {
1312+
if (msg.Type.IsSelf && msg.OrigSender != nil) || msg.Type == bridgev2.ProfileChange {
1313+
return nil, nil
1314+
}
1315+
chatPeerType, chatID, _, err := ids.ParsePortalID(msg.Portal.ID)
1316+
if err != nil {
1317+
return nil, fmt.Errorf("failed to parse portal ID: %w", err)
1318+
}
1319+
var inputChannel *tg.InputChannel
1320+
switch chatPeerType {
1321+
case ids.PeerTypeUser:
1322+
return nil, nil
1323+
case ids.PeerTypeChannel:
1324+
if accessHash, err := tc.ScopedStore.GetAccessHash(ctx, ids.PeerTypeChannel, chatID); err != nil {
1325+
return nil, fmt.Errorf("failed to get access hash for channel: %w", err)
1326+
} else {
1327+
inputChannel = &tg.InputChannel{ChannelID: chatID, AccessHash: accessHash}
1328+
}
1329+
case ids.PeerTypeChat:
1330+
// ok
1331+
default:
1332+
return nil, wrapUnsupportedError(fmt.Errorf("unsupported chat peer type %s for membership changes", chatPeerType))
1333+
}
1334+
var targetPeerType ids.PeerType
1335+
var targetUserID int64
1336+
switch target := msg.Target.(type) {
1337+
case *bridgev2.UserLogin:
1338+
targetPeerType = ids.PeerTypeUser
1339+
targetUserID, err = ids.ParseUserLoginID(target.ID)
1340+
case *bridgev2.Ghost:
1341+
targetPeerType, targetUserID, err = ids.ParseUserID(target.ID)
1342+
default:
1343+
return nil, wrapUnsupportedError(fmt.Errorf("unknown membership target type %T", msg.Target))
1344+
}
1345+
if err != nil {
1346+
return nil, fmt.Errorf("failed to parse target login ID: %w", err)
1347+
}
1348+
targetAccessHash, err := tc.ScopedStore.GetAccessHash(ctx, targetPeerType, targetUserID)
1349+
if err != nil {
1350+
return nil, fmt.Errorf("failed to get access hash for target: %w", err)
1351+
}
1352+
var targetInputPeer tg.InputPeerClass
1353+
var targetInputUser *tg.InputUser
1354+
switch targetPeerType {
1355+
case ids.PeerTypeUser:
1356+
targetInputPeer = &tg.InputPeerUser{UserID: targetUserID, AccessHash: targetAccessHash}
1357+
targetInputUser = &tg.InputUser{UserID: targetUserID, AccessHash: targetAccessHash}
1358+
case ids.PeerTypeChannel:
1359+
targetInputPeer = &tg.InputPeerChannel{ChannelID: targetUserID, AccessHash: targetAccessHash}
1360+
default:
1361+
return nil, wrapUnsupportedError(fmt.Errorf("unsupported target peer type %s for membership changes", targetPeerType))
1362+
}
1363+
if chatPeerType == ids.PeerTypeChannel {
1364+
switch msg.Type {
1365+
case bridgev2.Kick, bridgev2.RejectKnock, bridgev2.RevokeInvite, bridgev2.BanLeft, bridgev2.BanJoined, bridgev2.BanInvited, bridgev2.BanKnocked:
1366+
_, err = tc.client.API().ChannelsEditBanned(ctx, &tg.ChannelsEditBannedRequest{
1367+
Channel: inputChannel,
1368+
Participant: targetInputPeer,
1369+
BannedRights: tg.ChatBannedRights{ViewMessages: true},
1370+
})
1371+
if err != nil {
1372+
err = tc.humaniseSendError(err)
1373+
} else if msg.Type == bridgev2.Kick {
1374+
// Reset permissions to default (telegram doesn't have a dedicated kick method)
1375+
_, err = tc.client.API().ChannelsEditBanned(ctx, &tg.ChannelsEditBannedRequest{
1376+
Channel: inputChannel,
1377+
Participant: targetInputPeer,
1378+
BannedRights: tg.ChatBannedRights{},
1379+
})
1380+
if err != nil {
1381+
err = tc.humaniseSendError(err)
1382+
return nil, fmt.Errorf("failed to unban user to emulate kick: %w", err)
1383+
}
1384+
}
1385+
case bridgev2.Unban:
1386+
_, err = tc.client.API().ChannelsEditBanned(ctx, &tg.ChannelsEditBannedRequest{
1387+
Channel: inputChannel,
1388+
Participant: targetInputPeer,
1389+
BannedRights: tg.ChatBannedRights{},
1390+
})
1391+
err = tc.humaniseSendError(err)
1392+
case bridgev2.Invite, bridgev2.AcceptKnock:
1393+
if targetInputUser == nil {
1394+
return nil, wrapUnsupportedError(fmt.Errorf("can't invite non-user peer type %s", targetPeerType))
1395+
}
1396+
_, err = tc.client.API().ChannelsInviteToChannel(ctx, &tg.ChannelsInviteToChannelRequest{
1397+
Channel: inputChannel,
1398+
Users: []tg.InputUserClass{targetInputUser},
1399+
})
1400+
err = tc.humaniseSendError(err)
1401+
case bridgev2.Join, bridgev2.Knock:
1402+
_, err = tc.client.API().ChannelsJoinChannel(ctx, inputChannel)
1403+
err = tc.humaniseSendError(err)
1404+
case bridgev2.Leave, bridgev2.RetractKnock, bridgev2.RejectInvite:
1405+
_, err = tc.client.API().ChannelsLeaveChannel(ctx, inputChannel)
1406+
err = tc.humaniseSendError(err)
1407+
default:
1408+
err = wrapUnsupportedError(fmt.Errorf("unsupported channel membership change type %s -> %s", msg.Type.From, msg.Type.To))
1409+
}
1410+
} else {
1411+
switch msg.Type {
1412+
case bridgev2.Kick, bridgev2.RejectKnock, bridgev2.RevokeInvite, bridgev2.BanLeft, bridgev2.BanJoined, bridgev2.BanInvited, bridgev2.BanKnocked,
1413+
bridgev2.Leave, bridgev2.RetractKnock, bridgev2.RejectInvite:
1414+
_, err = tc.client.API().MessagesDeleteChatUser(ctx, &tg.MessagesDeleteChatUserRequest{
1415+
ChatID: chatID,
1416+
UserID: targetInputUser,
1417+
})
1418+
err = tc.humaniseSendError(err)
1419+
case bridgev2.Invite, bridgev2.AcceptKnock:
1420+
if targetInputUser == nil {
1421+
return nil, wrapUnsupportedError(fmt.Errorf("can't invite non-user peer type %s", targetPeerType))
1422+
}
1423+
_, err = tc.client.API().MessagesAddChatUser(ctx, &tg.MessagesAddChatUserRequest{
1424+
ChatID: chatID,
1425+
UserID: targetInputUser,
1426+
FwdLimit: 50,
1427+
})
1428+
err = tc.humaniseSendError(err)
1429+
case bridgev2.Join, bridgev2.Knock:
1430+
// TODO could maybe join if there's a saved invite link in the bridge db?
1431+
fallthrough
1432+
case bridgev2.Unban:
1433+
// There's no way to unban in minigroups
1434+
fallthrough
1435+
default:
1436+
err = wrapUnsupportedError(fmt.Errorf("unsupported minigroup membership change type %s -> %s", msg.Type.From, msg.Type.To))
1437+
}
1438+
}
1439+
return nil, err
1440+
}

0 commit comments

Comments
 (0)