diff --git a/pkg/connector/capabilities.go b/pkg/connector/capabilities.go index aa6ca5e..38c32a1 100644 --- a/pkg/connector/capabilities.go +++ b/pkg/connector/capabilities.go @@ -47,7 +47,7 @@ func (*LinkedInConnector) GetCapabilities() *bridgev2.NetworkGeneralCapabilities } func (*LinkedInConnector) GetBridgeInfoVersion() (info, capabilities int) { - return 1, 8 + return 1, 9 } const MaxTextLength = 8000 @@ -61,7 +61,7 @@ func supportedIfFFmpeg() event.CapabilitySupportLevel { } func capID() string { - base := "fi.mau.linkedin.capabilities.2025_12_10" + base := "fi.mau.linkedin.capabilities.2026_01_18" if ffmpeg.Supported() { return base + "+ffmpeg" } @@ -160,5 +160,9 @@ func (*LinkedInClient) GetCapabilities(ctx context.Context, portal *bridgev2.Por TypingNotifications: true, DeleteChat: true, State: stateCaps, + MemberActions: map[event.MemberAction]event.CapabilitySupportLevel{ + event.MemberActionInvite: event.CapLevelFullySupported, + event.MemberActionKick: event.CapLevelFullySupported, + }, } } diff --git a/pkg/connector/chatinfo.go b/pkg/connector/chatinfo.go index 9bfc60b..59b2751 100644 --- a/pkg/connector/chatinfo.go +++ b/pkg/connector/chatinfo.go @@ -14,6 +14,8 @@ import ( "go.mau.fi/mautrix-linkedin/pkg/linkedingo" ) +var moderatorPL = 50 + func (l *LinkedInClient) GetChatInfo(ctx context.Context, portal *bridgev2.Portal) (*bridgev2.ChatInfo, error) { // This is not supported. All of the info should already be populated with // the information we get on a per-message basis. @@ -87,10 +89,15 @@ func (l *LinkedInClient) conversationToChatInfo(conv linkedingo.Conversation) (c for _, participant := range conv.ConversationParticipants { userInChat = userInChat || networkid.UserID(participant.EntityURN.ID()) == l.userID sender := l.makeSender(participant) + powerLevel := 0 + if sender.IsFromMe { + powerLevel = moderatorPL + } ci.Members.MemberMap[sender.Sender] = bridgev2.ChatMember{ EventSender: sender, Membership: event.MembershipJoin, UserInfo: ptr.Ptr(l.getMessagingParticipantUserInfo(participant)), + PowerLevel: &powerLevel, } } diff --git a/pkg/connector/handlematrix.go b/pkg/connector/handlematrix.go index 50ae8f3..a810012 100644 --- a/pkg/connector/handlematrix.go +++ b/pkg/connector/handlematrix.go @@ -39,13 +39,14 @@ import ( ) var ( + _ bridgev2.DeleteChatHandlingNetworkAPI = (*LinkedInClient)(nil) _ bridgev2.EditHandlingNetworkAPI = (*LinkedInClient)(nil) + _ bridgev2.MembershipHandlingNetworkAPI = (*LinkedInClient)(nil) _ bridgev2.ReactionHandlingNetworkAPI = (*LinkedInClient)(nil) _ bridgev2.RedactionHandlingNetworkAPI = (*LinkedInClient)(nil) _ bridgev2.ReadReceiptHandlingNetworkAPI = (*LinkedInClient)(nil) _ bridgev2.RoomNameHandlingNetworkAPI = (*LinkedInClient)(nil) _ bridgev2.TypingHandlingNetworkAPI = (*LinkedInClient)(nil) - _ bridgev2.DeleteChatHandlingNetworkAPI = (*LinkedInClient)(nil) ) func getMediaFilename(content *event.MessageEventContent) (filename string) { @@ -291,3 +292,27 @@ func (l *LinkedInClient) HandleMatrixRoomName(ctx context.Context, msg *bridgev2 } return true, nil } + +func (l *LinkedInClient) HandleMatrixMembership(ctx context.Context, msg *bridgev2.MatrixMembershipChange) (*bridgev2.MatrixMembershipResult, error) { + if msg.Portal.RoomType == database.RoomTypeDM { + return nil, errors.New("cannot change members for DM") + } + + var participants []linkedingo.URN + switch target := msg.Target.(type) { + case *bridgev2.Ghost: + participants = []linkedingo.URN{linkedingo.NewURN(target.ID)} + case *bridgev2.UserLogin: + participants = []linkedingo.URN{linkedingo.NewURN(target.ID)} + } + + var err error + switch msg.Type { + case bridgev2.Invite: + err = l.client.AddParticipants(ctx, linkedingo.NewURN(msg.Portal.ID), participants) + case bridgev2.Kick: + err = l.client.RemoveParticipants(ctx, linkedingo.NewURN(msg.Portal.ID), participants) + } + + return nil, err +} diff --git a/pkg/linkedingo/conversations.go b/pkg/linkedingo/conversations.go index 6ed19bb..269cf4c 100644 --- a/pkg/linkedingo/conversations.go +++ b/pkg/linkedingo/conversations.go @@ -226,3 +226,39 @@ func (c *Client) RenameConversation(ctx context.Context, conversationURN URN, ti _, err := req.Do(ctx, nil) return err } + +func (c *Client) manageParticipants(ctx context.Context, conversationURN URN, participants []URN, action string) error { + payload := ParticipantsPayload{ + ConversationURN: conversationURN, + Participants: participants, + } + _, err := c.newAuthedRequest(http.MethodPost, linkedInMessagingDashMessengerConversationsURL). + WithJSONPayload(payload). + WithQueryParam("action", action). + WithCSRF(). + WithContentType(contentTypePlaintextUTF8). + WithXLIHeaders(). + Do(ctx, nil) + return err +} + +type ParticipantsPayload struct { + ConversationURN URN `json:"conversationUrn,omitempty"` + Participants []URN `json:"participants,omitempty"` +} + +func (c *Client) AddParticipants(ctx context.Context, conversationURN URN, participants []URN) error { + prefix := "urn:li:fsd_profile" + for i, p := range participants { + participants[i] = p.WithPrefix(prefix) + } + return c.manageParticipants(ctx, conversationURN, participants, "addParticipants") +} + +func (c *Client) RemoveParticipants(ctx context.Context, conversationURN URN, participants []URN) error { + prefix := "urn:li:msg_messagingParticipant:urn:li:fsd_profile" + for i, p := range participants { + participants[i] = p.WithPrefix(prefix) + } + return c.manageParticipants(ctx, conversationURN, participants, "removeParticipants") +}