diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index 7c7a83e..df15747 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -30,16 +30,11 @@ jobs: run: | go install golang.org/x/tools/cmd/goimports@latest go install honnef.co/go/tools/cmd/staticcheck@latest + go install github.com/beeper/pre-commit-go/bridgelint@latest export PATH="$HOME/go/bin:$PATH" - - name: Cache pre-commit - uses: actions/cache@v6 - with: - path: ~/.cache/pre-commit - key: pre-commit-go${{ matrix.go-version }}-${{ hashFiles('.pre-commit-config.yaml') }} - - name: Run pre-commit - run: pipx run pre-commit run --all-files --show-diff-on-failure --color=always + uses: pre-commit/action@v3.0.1 test: runs-on: ubuntu-latest diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 8ea1c20..1f1fe9c 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -27,4 +27,5 @@ repos: - id: zerolog-ban-msgf - id: zerolog-use-stringer - id: bridgelint + language: system verbose: true diff --git a/pkg/connector/chatinfo.go b/pkg/connector/chatinfo.go index 4668cd7..669c015 100644 --- a/pkg/connector/chatinfo.go +++ b/pkg/connector/chatinfo.go @@ -125,9 +125,10 @@ func (s *SignalClient) contactToUserInfo(ctx context.Context, contact *types.Rec } name := s.Main.Config.FormatDisplayname(contact, username) ui.Name = &name - if s.Main.Config.UseContactAvatars && contact.ContactAvatar.Hash != "" { + contactAvatarID := networkid.AvatarID("hash:" + contact.ContactAvatar.Hash) + if s.Main.Config.UseContactAvatars && contact.ContactAvatar.Hash != "" && s.canUseContactAvatar(ctx, contact, contactAvatarID) { ui.Avatar = &bridgev2.Avatar{ - ID: networkid.AvatarID("hash:" + contact.ContactAvatar.Hash), + ID: contactAvatarID, Get: func(ctx context.Context) ([]byte, error) { if contact.ContactAvatar.Image == nil { return nil, fmt.Errorf("contact avatar not available") @@ -172,6 +173,20 @@ func (s *SignalClient) contactToUserInfo(ctx context.Context, contact *types.Rec return ui, nil } +func (s *SignalClient) canUseContactAvatar(ctx context.Context, contact *types.Recipient, avatarID networkid.AvatarID) bool { + if contact.ContactAvatar.Image != nil { + return true + } else if contact.ACI == uuid.Nil { + return false + } + ghost, err := s.Main.Bridge.GetExistingGhostByID(ctx, signalid.MakeUserID(contact.ACI)) + if err != nil { + zerolog.Ctx(ctx).Err(err).Msg("Failed to get ghost to check contact avatar") + return true + } + return ghost != nil && ghost.AvatarID == avatarID && ghost.AvatarMXC != "" && ghost.AvatarSet +} + func (s *SignalConnector) ValidateUserID(id networkid.UserID) bool { _, err := signalid.ParseUserIDAsServiceID(id) return err == nil @@ -445,11 +460,15 @@ func (s *SignalClient) GetContactList(ctx context.Context) ([]*bridgev2.ResolveI } resp := make([]*bridgev2.ResolveIdentifierResponse, len(recipients)) for i, recipient := range recipients { - recipientResp := &bridgev2.ResolveIdentifierResponse{ - Chat: s.makeCreateDMResponse(ctx, recipient, nil), + if err = ctx.Err(); err != nil { + return nil, err } + recipientResp := &bridgev2.ResolveIdentifierResponse{} var username string if recipient.ACI != uuid.Nil { + recipientResp.Chat = &bridgev2.CreateChatResponse{ + PortalKey: s.makeDMPortalKey(libsignalgo.NewACIServiceID(recipient.ACI)), + } recipientResp.UserID = signalid.MakeUserID(recipient.ACI) ghost, err := s.Main.Bridge.GetGhostByID(ctx, recipientResp.UserID) if err != nil { @@ -458,7 +477,11 @@ func (s *SignalClient) GetContactList(ctx context.Context) ([]*bridgev2.ResolveI recipientResp.Ghost = ghost username = ghost.Metadata.(*signalid.GhostMetadata).Username } else { - recipientResp.UserID = signalid.MakeUserIDFromServiceID(libsignalgo.NewPNIServiceID(recipient.PNI)) + pniServiceID := libsignalgo.NewPNIServiceID(recipient.PNI) + recipientResp.Chat = &bridgev2.CreateChatResponse{ + PortalKey: s.makeDMPortalKey(pniServiceID), + } + recipientResp.UserID = signalid.MakeUserIDFromServiceID(pniServiceID) } recipientResp.UserInfo, err = s.contactToUserInfo(ctx, recipient, username, false) if err != nil { diff --git a/pkg/msgconv/from-signal.go b/pkg/msgconv/from-signal.go index 251e9dd..0bf2d3b 100644 --- a/pkg/msgconv/from-signal.go +++ b/pkg/msgconv/from-signal.go @@ -502,6 +502,11 @@ func (mc *MessageConverter) convertAttachmentToMatrix(ctx context.Context, index func (mc *MessageConverter) convertStickerToMatrix(ctx context.Context, sticker *signalpb.DataMessage_Sticker, attMap AttachmentMap) *bridgev2.ConvertedMessagePart { converted, err := mc.reuploadAttachment(ctx, sticker.GetData(), attMap) + hasPack := len(sticker.GetPackId()) == PackIDLength && len(sticker.GetPackKey()) == PackKeyLength && !bytes.Equal(sticker.GetPackId(), zeroPackID) + if err != nil && hasPack && (errors.Is(err, signalmeow.ErrAttachmentNotFound) || errors.Is(err, ErrAttachmentNotInBackup) || errors.Is(err, ErrBackupNotSupported)) { + zerolog.Ctx(ctx).Debug().Err(err).Msg("Sticker attachment is unavailable, downloading it from the sticker pack") + converted, err = mc.reuploadStickerFromPack(ctx, sticker) + } if err != nil { zerolog.Ctx(ctx).Err(err).Msg("Failed to handle sticker") return &bridgev2.ConvertedMessagePart{ @@ -519,7 +524,7 @@ func (mc *MessageConverter) convertStickerToMatrix(ctx context.Context, sticker converted.Content.Info.Height = 200 } converted.Content.Body = sticker.GetEmoji() - if len(sticker.GetPackId()) == PackIDLength && len(sticker.GetPackKey()) == PackKeyLength && !bytes.Equal(sticker.GetPackId(), zeroPackID) { + if hasPack { converted.Content.Info.BridgedSticker = &event.BridgedSticker{ Network: StickerSourceID, ID: strconv.FormatUint(uint64(sticker.GetStickerId()), 10), @@ -532,6 +537,49 @@ func (mc *MessageConverter) convertStickerToMatrix(ctx context.Context, sticker return converted } +func (mc *MessageConverter) reuploadStickerFromPack(ctx context.Context, sticker *signalpb.DataMessage_Sticker) (*bridgev2.ConvertedMessagePart, error) { + att := sticker.GetData() + content := &event.MessageEventContent{ + Body: att.GetFileName(), + Info: &event.FileInfo{ + MimeType: att.GetContentType(), + Width: int(att.GetWidth()), + Height: int(att.GetHeight()), + }, + } + if mc.DirectMedia { + mediaID, err := signalid.DirectMediaSticker{ + PackID: sticker.GetPackId(), + PackKey: sticker.GetPackKey(), + StickerID: sticker.GetStickerId(), + }.AsMediaID() + if err != nil { + return nil, err + } + content.URL, content.File, err = mc.Bridge.GenerateContentURI(ctx, getPortal(ctx).MXID, mediaID) + if err != nil { + return nil, err + } + } else { + data, err := signalmeow.DownloadStickerPackItem(ctx, sticker.GetPackId(), sticker.GetPackKey(), sticker.GetStickerId()) + if err != nil { + return nil, fmt.Errorf("failed to download sticker from pack: %w", err) + } + if content.Info.MimeType == "" { + content.Info.MimeType = http.DetectContentType(data) + } + content.Info.Size = len(data) + content.URL, content.File, err = getIntent(ctx).UploadMedia(ctx, getPortal(ctx).MXID, data, content.Body, content.Info.MimeType) + if err != nil { + return nil, fmt.Errorf("failed to upload sticker from pack: %w", err) + } + } + return &bridgev2.ConvertedMessagePart{ + Type: event.EventMessage, + Content: content, + }, nil +} + func (mc *MessageConverter) downloadSignalLongText(ctx context.Context, att *signalpb.AttachmentPointer, attMap AttachmentMap) (*string, error) { data, err := mc.downloadAttachment(ctx, att, attMap, nil) if err != nil { diff --git a/pkg/signalmeow/web/signalwebsocket.go b/pkg/signalmeow/web/signalwebsocket.go index 7ee54c7..bade56b 100644 --- a/pkg/signalmeow/web/signalwebsocket.go +++ b/pkg/signalmeow/web/signalwebsocket.go @@ -319,6 +319,11 @@ func (s *SignalWebsocket) connectLoop( } // Succssfully connected + if resp != nil { + if alerts := resp.Header.Values("X-Signal-Alert"); len(alerts) > 0 { + log.Warn().Strs("alerts", alerts).Msg("Signal server sent alerts when opening websocket") + } + } s.pushStatus(ctx, SignalWebsocketConnectionEventConnected, nil) s.ws.Store(ws) retrying = false