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 }