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  }