github.com/goreleaser/goreleaser@v1.25.1/internal/pipe/opencollective/opencollective.go (about) 1 package opencollective 2 3 import ( 4 "bytes" 5 "encoding/json" 6 "fmt" 7 "net/http" 8 9 "github.com/caarlos0/env/v9" 10 "github.com/caarlos0/log" 11 "github.com/goreleaser/goreleaser/internal/tmpl" 12 "github.com/goreleaser/goreleaser/pkg/context" 13 ) 14 15 const ( 16 defaultTitleTemplate = `{{ .Tag }}` 17 defaultMessageTemplate = `{{ .ProjectName }} {{ .Tag }} is out!<br/>Check it out at <a href="{{ .ReleaseURL }}">{{ .ReleaseURL }}</a>` 18 endpoint = "https://api.opencollective.com/graphql/v2" 19 ) 20 21 type Pipe struct{} 22 23 func (Pipe) String() string { return "opencollective" } 24 25 func (Pipe) Skip(ctx *context.Context) bool { 26 return !ctx.Config.Announce.OpenCollective.Enabled || ctx.Config.Announce.OpenCollective.Slug == "" 27 } 28 29 type Config struct { 30 Token string `env:"OPENCOLLECTIVE_TOKEN,notEmpty"` 31 } 32 33 func (Pipe) Default(ctx *context.Context) error { 34 if ctx.Config.Announce.OpenCollective.TitleTemplate == "" { 35 ctx.Config.Announce.OpenCollective.TitleTemplate = defaultTitleTemplate 36 } 37 if ctx.Config.Announce.OpenCollective.MessageTemplate == "" { 38 ctx.Config.Announce.OpenCollective.MessageTemplate = defaultMessageTemplate 39 } 40 return nil 41 } 42 43 func (Pipe) Announce(ctx *context.Context) error { 44 title, err := tmpl.New(ctx).Apply(ctx.Config.Announce.OpenCollective.TitleTemplate) 45 if err != nil { 46 return fmt.Errorf("opencollective: %w", err) 47 } 48 html, err := tmpl.New(ctx).Apply(ctx.Config.Announce.OpenCollective.MessageTemplate) 49 if err != nil { 50 return fmt.Errorf("opencollective: %w", err) 51 } 52 53 var cfg Config 54 if err := env.Parse(&cfg); err != nil { 55 return fmt.Errorf("opencollective: %w", err) 56 } 57 58 log.Infof("posting: %q | %q", title, html) 59 60 id, err := createUpdate(ctx, title, html, ctx.Config.Announce.OpenCollective.Slug, cfg.Token) 61 if err != nil { 62 return fmt.Errorf("opencollective: %w", err) 63 } 64 65 if err := publishUpdate(ctx, id, cfg.Token); err != nil { 66 return fmt.Errorf("opencollective: %w", err) 67 } 68 69 return nil 70 } 71 72 type payload struct { 73 Query string `json:"query"` 74 Variables map[string]any `json:"variables"` 75 } 76 77 func createUpdate(ctx *context.Context, title, html, slug, token string) (string, error) { 78 mutation := `mutation ( 79 $update: UpdateCreateInput! 80 ) { 81 createUpdate(update: $update) { 82 id 83 } 84 }` 85 payload := payload{ 86 Query: mutation, 87 Variables: map[string]any{ 88 "update": map[string]any{ 89 "title": title, 90 "html": html, 91 "account": map[string]any{ 92 "slug": slug, 93 }, 94 }, 95 }, 96 } 97 98 resp, err := doMutation(ctx, payload, token) 99 if err != nil { 100 return "", err 101 } 102 defer resp.Body.Close() 103 104 //nolint:tagliatelle 105 var envelope struct { 106 Data struct { 107 CreateUpdate struct { 108 ID string `json:"id"` 109 } `json:"createUpdate"` 110 } `json:"data"` 111 } 112 if err := json.NewDecoder(resp.Body).Decode(&envelope); err != nil { 113 return "", fmt.Errorf("could not decode JSON response: %w", err) 114 } 115 116 return envelope.Data.CreateUpdate.ID, nil 117 } 118 119 func publishUpdate(ctx *context.Context, id, token string) error { 120 mutation := `mutation ( 121 $id: String! 122 $audience: UpdateAudience 123 ) { 124 publishUpdate(id: $id, notificationAudience: $audience) { 125 id 126 } 127 }` 128 payload := payload{ 129 Query: mutation, 130 Variables: map[string]any{ 131 "id": id, 132 "audience": "ALL", 133 }, 134 } 135 136 resp, err := doMutation(ctx, payload, token) 137 if err != nil { 138 return err 139 } 140 defer resp.Body.Close() 141 142 return err 143 } 144 145 func doMutation(ctx *context.Context, payload payload, token string) (*http.Response, error) { 146 p, err := json.Marshal(payload) 147 if err != nil { 148 return nil, fmt.Errorf("could not marshal payload: %w", err) 149 } 150 151 req, err := http.NewRequestWithContext(ctx, http.MethodPost, endpoint, bytes.NewReader(p)) 152 if err != nil { 153 return nil, fmt.Errorf("could not create request: %w", err) 154 } 155 req.Header.Set("Personal-Token", token) 156 req.Header.Set("Content-Type", "application/json") 157 158 resp, err := http.DefaultClient.Do(req) 159 if err != nil { 160 return nil, fmt.Errorf("could not send request to opencollective: %w", err) 161 } 162 163 if resp.StatusCode != http.StatusOK { 164 return resp, fmt.Errorf("incorrect response from opencollective: %s", resp.Status) 165 } 166 167 return resp, nil 168 }