diff --git a/notifier/discord/discord.go b/notifier/discord/discord.go index a9ada45..cb809eb 100644 --- a/notifier/discord/discord.go +++ b/notifier/discord/discord.go @@ -18,6 +18,7 @@ import ( const ( requestTimeout = 10 * time.Second responseBodyLimit = 256 << 10 + userAgent = "DiscordBot (https://github.com/we11adam/uddns, 1.0)" ) type Discord struct { @@ -93,6 +94,7 @@ func (d *Discord) newHTTPClient() (*resty.Client, error) { SetTimeout(requestTimeout). SetResponseBodyLimit(responseBodyLimit). SetHeader("Content-Type", "application/json"). + SetHeader("User-Agent", userAgent). SetBaseURL(webhookURL) if d.Proxy != "" { @@ -127,24 +129,24 @@ func (d *Discord) Notify(ctx context.Context, notification notifier.Notification }, }).Post("") if err != nil { - return redact.Error(err, d.Token) + return d.redactError(err) } switch resp.StatusCode() { case http.StatusOK, http.StatusNoContent: return nil case http.StatusTooManyRequests: - return fmt.Errorf("Discord API request failed, rate limited, retry after %s", resp.Header().Get("Retry-After")) + return d.redactError(fmt.Errorf("Discord API request failed, rate limited, retry after %s", resp.Header().Get("Retry-After"))) } apiResp := apiResponse{} decodeErr := json.Unmarshal(resp.Body(), &apiResp) if !resp.IsSuccess() { - return d.apiError(resp.StatusCode(), apiResp) + return d.redactError(d.apiError(resp.StatusCode(), apiResp)) } if decodeErr != nil { - return redact.Error(fmt.Errorf("failed to decode Discord API response: %w", decodeErr), d.Token) + return d.redactError(fmt.Errorf("failed to decode Discord API response: %w", decodeErr)) } - return d.apiError(resp.StatusCode(), apiResp) + return d.redactError(d.apiError(resp.StatusCode(), apiResp)) } func (d *Discord) apiError(statusCode int, response apiResponse) error { @@ -153,3 +155,7 @@ func (d *Discord) apiError(statusCode int, response apiResponse) error { } return fmt.Errorf("Discord API request failed: HTTP status %d, code %d, message %q", statusCode, response.Code, response.Message) } + +func (d *Discord) redactError(err error) error { + return redact.Error(err, d.Token, d.URL) +} diff --git a/notifier/discord/discord_test.go b/notifier/discord/discord_test.go index cb54fdb..caf610c 100644 --- a/notifier/discord/discord_test.go +++ b/notifier/discord/discord_test.go @@ -88,6 +88,9 @@ func TestClientBaseURL(t *testing.T) { if baseURL != tt.wantBase { t.Errorf("New() BaseURL = %v, want %v", baseURL, tt.wantBase) } + if got := client.hc.Header.Get("User-Agent"); got != userAgent { + t.Errorf("New() User-Agent = %q, want %q", got, userAgent) + } } }) } @@ -111,6 +114,23 @@ func TestNotifyRedactTokenFromTransportError(t *testing.T) { testutil.AssertTokenRedacted(t, err.Error(), token) } +func TestNotifyRedactsTokenFromURLTransportError(t *testing.T) { + token := "discord-webhook-token" + discord, err := New(&Discord{ + URL: "https://discord.com/api/webhooks/123456/" + token, + }) + if err != nil { + t.Fatalf("failed to create Discord client: %v", err) + } + discord.hc.SetTransport(failingTransport{message: "request failed"}) + + err = discord.Notify(context.Background(), notifier.Notification{Message: "test"}) + if err == nil { + t.Fatal("expected transport error") + } + testutil.AssertTokenRedacted(t, err.Error(), token) +} + func TestNotifyDiscordAPIResponse(t *testing.T) { token := "discord+/token =secret" tests := []struct {