summaryrefslogtreecommitdiff
path: root/internal/platforms/linkedin/linkedin.go
blob: f6d89f51b84844f424c6e603a1b61db430ec7465 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
package linkedin

import (
	"bytes"
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"log"
	"net/http"

	"codeberg.org/snonux/gos/internal/config"
	"codeberg.org/snonux/gos/internal/entry"
	"codeberg.org/snonux/gos/internal/platforms/linkedin/oauth2"
	"codeberg.org/snonux/gos/internal/prompt"
	"github.com/fatih/color"
)

var errUnauthorized = errors.New("unauthorized access, refresh or create token?")

func Post(ctx context.Context, args config.Args, sizeLimit int, ent entry.Entry) error {
	err := post(ctx, args, sizeLimit, ent)
	if errors.Is(err, errUnauthorized) {
		log.Println(err, "=> trying to refresh LinkedIn access token")
		args.Secrets.LinkedInAccessToken = "" // Reset the token
		return post(ctx, args, sizeLimit, ent)
	}
	return err
}

func post(ctx context.Context, args config.Args, sizeLimit int, ent entry.Entry) error {
	if args.DryRun {
		log.Println("Not posting", ent, "to LinkedIn as dry-run enabled")
		return nil
	}
	personID, accessToken, err := oauth2.LinkedInCreds(ctx, args)
	if err != err {
		return err
	}
	content, err := ent.ContentWithLimit(sizeLimit)
	if err != nil {
		return err
	}
	if err := prompt.DoYouWantThis("Do you want to post this message to Linkedin?", content); err != nil {
		if errors.Is(err, prompt.ErrEditContent) {
			if err := ent.Edit(); err != nil {
				return err
			}
			return post(ctx, args, sizeLimit, ent)
		}
		return err
	}
	return callLinkedInAPI(ctx, personID, accessToken, content)
}

func callLinkedInAPI(ctx context.Context, personID, accessToken, content string) error {
	const url = "https://api.linkedin.com/v2/posts"

	post := map[string]interface{}{
		"author":     fmt.Sprintf("urn:li:person:%s", personID),
		"commentary": escapeLinkedInText(content),
		"visibility": "PUBLIC",
		"distribution": map[string]interface{}{
			"feedDistribution":               "MAIN_FEED",
			"targetEntities":                 []string{},
			"thirdPartyDistributionChannels": []string{},
		},
		"lifecycleState":            "PUBLISHED",
		"isReshareDisabledByAuthor": false,
	}

	payload, err := json.Marshal(post)
	if err != nil {
		return fmt.Errorf("Error encoding JSON:%w", err)
	}
	req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(payload))
	if err != nil {
		return fmt.Errorf("Error creating request: %w", err)
	}

	req.Header.Add("Authorization", "Bearer "+accessToken)
	req.Header.Set("Content-Type", "application/json")
	req.Header.Add("X-RestLi-Protocol-Version", "2.0.0")

	client := &http.Client{}
	resp, err := client.Do(req)
	if err != nil {
		return fmt.Errorf("Error sending request: %w", err)
	}
	defer resp.Body.Close()
	body, err := io.ReadAll(resp.Body)
	if err != nil {
		return err
	}
	color.Cyan(string(body))

	if resp.StatusCode != http.StatusCreated {
		err = fmt.Errorf("failed to post to LinkedIn. Status: %s\n", resp.Status)
		if resp.StatusCode == http.StatusUnauthorized {
			err = errors.Join(err, errUnauthorized)
		}
	}
	return err
}