github.com/status-im/status-go@v1.1.0/protocol/linkpreview_unfurler_status.go (about) 1 package protocol 2 3 import ( 4 "fmt" 5 6 "go.uber.org/zap" 7 8 "github.com/status-im/status-go/api/multiformat" 9 "github.com/status-im/status-go/images" 10 "github.com/status-im/status-go/protocol/common" 11 "github.com/status-im/status-go/protocol/common/shard" 12 "github.com/status-im/status-go/protocol/communities" 13 ) 14 15 type StatusUnfurler struct { 16 m *Messenger 17 logger *zap.Logger 18 url string 19 } 20 21 func NewStatusUnfurler(URL string, messenger *Messenger, logger *zap.Logger) *StatusUnfurler { 22 return &StatusUnfurler{ 23 m: messenger, 24 logger: logger.With(zap.String("url", URL)), 25 url: URL, 26 } 27 } 28 29 func updateThumbnail(image *images.IdentityImage, thumbnail *common.LinkPreviewThumbnail) error { 30 if image.IsEmpty() { 31 return nil 32 } 33 34 width, height, err := images.GetImageDimensions(image.Payload) 35 if err != nil { 36 return fmt.Errorf("failed to get image dimensions: %w", err) 37 } 38 39 dataURI, err := image.GetDataURI() 40 if err != nil { 41 return fmt.Errorf("failed to get data uri: %w", err) 42 } 43 44 thumbnail.Width = width 45 thumbnail.Height = height 46 thumbnail.DataURI = dataURI 47 48 return nil 49 } 50 51 func (u *StatusUnfurler) buildContactData(publicKey string) (*common.StatusContactLinkPreview, error) { 52 // contactID == "0x" + secp251k1 compressed public key as hex-encoded string 53 contactID, err := multiformat.DeserializeCompressedKey(publicKey) 54 if err != nil { 55 return nil, err 56 } 57 58 contact := u.m.GetContactByID(contactID) 59 60 // If no contact found locally, fetch it from waku 61 if contact == nil { 62 contact, err = u.m.FetchContact(contactID, true) 63 if err != nil { 64 return nil, fmt.Errorf("failed to request contact info from mailserver for public key '%s': %w", publicKey, err) 65 } 66 if contact == nil { 67 return nil, fmt.Errorf("contact wasn't found at the store node %s", publicKey) 68 } 69 } 70 71 c := &common.StatusContactLinkPreview{ 72 PublicKey: contactID, 73 DisplayName: contact.DisplayName, 74 Description: contact.Bio, 75 } 76 77 if image, ok := contact.Images[images.SmallDimName]; ok { 78 if err = updateThumbnail(&image, &c.Icon); err != nil { 79 u.logger.Warn("unfurling status link: failed to set contact thumbnail", zap.Error(err)) 80 } 81 } 82 83 return c, nil 84 } 85 86 func (u *StatusUnfurler) buildCommunityData(communityID string, shard *shard.Shard) (*communities.Community, *common.StatusCommunityLinkPreview, error) { 87 // This automatically checks the database 88 community, err := u.m.FetchCommunity(&FetchCommunityRequest{ 89 CommunityKey: communityID, 90 Shard: shard, 91 TryDatabase: true, 92 WaitForResponse: true, 93 }) 94 95 if err != nil { 96 return nil, nil, fmt.Errorf("failed to get community info for communityID '%s': %w", communityID, err) 97 } 98 99 if community == nil { 100 return community, nil, fmt.Errorf("community info fetched, but it is empty") 101 } 102 103 statusCommunityLinkPreviews, err := community.ToStatusLinkPreview() 104 if err != nil { 105 return nil, nil, fmt.Errorf("failed to get status community link preview for communityID '%s': %w", communityID, err) 106 } 107 108 return community, statusCommunityLinkPreviews, nil 109 } 110 111 func (u *StatusUnfurler) buildChannelData(channelUUID string, communityID string, communityShard *shard.Shard) (*common.StatusCommunityChannelLinkPreview, error) { 112 community, communityData, err := u.buildCommunityData(communityID, communityShard) 113 if err != nil { 114 return nil, fmt.Errorf("failed to build channel community data: %w", err) 115 } 116 117 channel, ok := community.Chats()[channelUUID] 118 if !ok { 119 return nil, fmt.Errorf("channel with channelID '%s' not found in community '%s'", channelUUID, communityID) 120 } 121 122 return &common.StatusCommunityChannelLinkPreview{ 123 ChannelUUID: channelUUID, 124 Emoji: channel.Identity.Emoji, 125 DisplayName: channel.Identity.DisplayName, 126 Description: channel.Identity.Description, 127 Color: channel.Identity.Color, 128 Community: communityData, 129 }, nil 130 } 131 132 func (u *StatusUnfurler) Unfurl() (*common.StatusLinkPreview, error) { 133 preview := new(common.StatusLinkPreview) 134 preview.URL = u.url 135 136 resp, err := ParseSharedURL(u.url) 137 if err != nil { 138 return nil, fmt.Errorf("failed to parse shared url: %w", err) 139 } 140 141 // If a URL has been successfully parsed, 142 // any further errors should not be returned, only logged. 143 144 if resp.Contact != nil { 145 preview.Contact, err = u.buildContactData(resp.Contact.PublicKey) 146 if err != nil { 147 return nil, fmt.Errorf("error when building contact data: %w", err) 148 } 149 return preview, nil 150 } 151 152 // NOTE: Currently channel data comes together with community data, 153 // both `Community` and `Channel` fields will be present. 154 // So we check for Channel first, then Community. 155 156 if resp.Channel != nil { 157 if resp.Community == nil { 158 return preview, fmt.Errorf("channel community can't be empty") 159 } 160 preview.Channel, err = u.buildChannelData(resp.Channel.ChannelUUID, resp.Community.CommunityID, resp.Shard) 161 if err != nil { 162 return nil, fmt.Errorf("error when building channel data: %w", err) 163 } 164 return preview, nil 165 } 166 167 if resp.Community != nil { 168 _, preview.Community, err = u.buildCommunityData(resp.Community.CommunityID, resp.Shard) 169 if err != nil { 170 return nil, fmt.Errorf("error when building community data: %w", err) 171 } 172 return preview, nil 173 } 174 175 return nil, fmt.Errorf("shared url does not contain contact, community or channel data") 176 }