github.com/triarius/goreleaser@v1.12.5/internal/pipe/linkedin/client.go (about)

     1  package linkedin
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/json"
     6  	"fmt"
     7  	"io"
     8  	"net/http"
     9  
    10  	"github.com/triarius/goreleaser/pkg/context"
    11  	"golang.org/x/oauth2"
    12  )
    13  
    14  type oauthClientConfig struct {
    15  	Context     *context.Context
    16  	AccessToken string
    17  }
    18  
    19  type client struct {
    20  	client  *http.Client
    21  	baseURL string
    22  }
    23  
    24  type postShareText struct {
    25  	Text string `json:"text"`
    26  }
    27  
    28  type postShareRequest struct {
    29  	Text  postShareText `json:"text"`
    30  	Owner string        `json:"owner"`
    31  }
    32  
    33  func createLinkedInClient(cfg oauthClientConfig) (client, error) {
    34  	if cfg.Context == nil {
    35  		return client{}, fmt.Errorf("context is nil")
    36  	}
    37  
    38  	if cfg.AccessToken == "" {
    39  		return client{}, fmt.Errorf("empty access token")
    40  	}
    41  
    42  	config := oauth2.Config{}
    43  
    44  	c := config.Client(cfg.Context, &oauth2.Token{
    45  		AccessToken: cfg.AccessToken,
    46  	})
    47  
    48  	if c == nil {
    49  		return client{}, fmt.Errorf("client is nil")
    50  	}
    51  
    52  	return client{
    53  		client:  c,
    54  		baseURL: "https://api.linkedin.com",
    55  	}, nil
    56  }
    57  
    58  // getProfileID returns the Current Member's ID
    59  // POST Share API requires a Profile ID in the 'owner' field
    60  // Format must be in: 'urn:li:person:PROFILE_ID'
    61  // https://docs.microsoft.com/en-us/linkedin/shared/integrations/people/profile-api#retrieve-current-members-profile
    62  func (c client) getProfileID() (string, error) {
    63  	resp, err := c.client.Get(c.baseURL + "/v2/me")
    64  	if err != nil {
    65  		return "", fmt.Errorf("could not GET /v2/me: %w", err)
    66  	}
    67  
    68  	value, err := io.ReadAll(resp.Body)
    69  	if err != nil {
    70  		return "", fmt.Errorf("could not read response body: %w", err)
    71  	}
    72  
    73  	var result map[string]interface{}
    74  	err = json.Unmarshal(value, &result)
    75  	if err != nil {
    76  		return "", fmt.Errorf("could not unmarshal: %w", err)
    77  	}
    78  
    79  	if v, ok := result["id"]; ok {
    80  		return v.(string), nil
    81  	}
    82  
    83  	return "", fmt.Errorf("could not find 'id' in result: %w", err)
    84  }
    85  
    86  func (c client) Share(message string) (string, error) {
    87  	// To get Owner of the share, we need to get profile id
    88  	profileID, err := c.getProfileID()
    89  	if err != nil {
    90  		return "", fmt.Errorf("could not get profile id: %w", err)
    91  	}
    92  
    93  	req := postShareRequest{
    94  		Text: postShareText{
    95  			Text: message,
    96  		},
    97  		// Person or Organization URN
    98  		// Owner of the share. Required on create.
    99  		// https://docs.microsoft.com/en-us/linkedin/marketing/integrations/community-management/shares/share-api?tabs=http#schema
   100  		Owner: fmt.Sprintf("urn:li:person:%s", profileID),
   101  	}
   102  
   103  	reqBytes, err := json.Marshal(req)
   104  	if err != nil {
   105  		return "", fmt.Errorf("could not marshal request: %w", err)
   106  	}
   107  
   108  	// Filling only required 'owner' and 'text' field is OK
   109  	// https://docs.microsoft.com/en-us/linkedin/marketing/integrations/community-management/shares/share-api?tabs=http#sample-request-3
   110  	resp, err := c.client.Post(c.baseURL+"/v2/shares", "application/json", bytes.NewReader(reqBytes))
   111  	if err != nil {
   112  		return "", fmt.Errorf("could not POST /v2/shares: %w", err)
   113  	}
   114  
   115  	body, err := io.ReadAll(resp.Body)
   116  	if err != nil {
   117  		return "", fmt.Errorf("could not read from body: %w", err)
   118  	}
   119  
   120  	var result map[string]interface{}
   121  	err = json.Unmarshal(body, &result)
   122  	if err != nil {
   123  		return "", fmt.Errorf("could not unmarshal: %w", err)
   124  	}
   125  
   126  	// Activity URN
   127  	// URN of the activity associated with this share. Activities act as a wrapper around
   128  	// shares and articles to represent content in the LinkedIn feed. Read only.
   129  	if v, ok := result["activity"]; ok {
   130  		return fmt.Sprintf("https://www.linkedin.com/feed/update/%s", v.(string)), nil
   131  	}
   132  
   133  	return "", fmt.Errorf("could not find 'activity' in result: %w", err)
   134  }