Skip to content

Commit ad9216e

Browse files
committed
feat: fetching Reddit API access token to avoid being rejected by network security
1 parent 4731b90 commit ad9216e

File tree

2 files changed

+97
-4
lines changed

2 files changed

+97
-4
lines changed

.goreleaser.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
project_name: glanceapp/glance
1+
project_name: s0ders/glance
22

33
checksum:
44
disable: true

internal/glance/widget-reddit.go

Lines changed: 96 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,13 @@ package glance
22

33
import (
44
"context"
5+
"encoding/base64"
6+
"encoding/json"
57
"errors"
68
"fmt"
79
"html"
810
"html/template"
11+
"io"
912
"net/http"
1013
"net/url"
1114
"strings"
@@ -19,6 +22,10 @@ var (
1922

2023
type redditWidget struct {
2124
widgetBase `yaml:",inline"`
25+
redditAccessToken string
26+
redditAppName string `yaml:"reddit-app-name"`
27+
redditClientID string `yaml:"reddit-client-id"`
28+
redditClientSecret string `yaml:"reddit-client-secret"`
2229
Posts forumPostList `yaml:"-"`
2330
Subreddit string `yaml:"subreddit"`
2431
Proxy proxyOptionsField `yaml:"proxy"`
@@ -35,6 +42,67 @@ type redditWidget struct {
3542
RequestUrlTemplate string `yaml:"request-url-template"`
3643
}
3744

45+
type redditTokenResponse struct {
46+
AccessToken string `json:"access_token"`
47+
TokenType string `json:"token_type"`
48+
ExpiresIn int `json:"expires_in"`
49+
Scope string `json:"scope"`
50+
}
51+
52+
func (widget *redditWidget) fetchRedditAccessToken() error {
53+
// Only execute if a matching configuration is provider
54+
if widget.redditAppName == "" || widget.redditClientID == "" || widget.redditClientSecret == "" {
55+
return nil
56+
}
57+
58+
auth := base64.StdEncoding.EncodeToString([]byte(widget.redditClientID + ":" + widget.redditClientSecret))
59+
60+
// Prepare form data
61+
data := url.Values{}
62+
data.Set("grant_type", "client_credentials")
63+
64+
// Create request
65+
req, err := http.NewRequest("POST", "https://www.reddit.com/api/v1/access_token", strings.NewReader(data.Encode()))
66+
if err != nil {
67+
return err
68+
}
69+
70+
// Set headers
71+
req.Header.Add("Authorization", "Basic "+auth)
72+
req.Header.Add("User-Agent", fmt.Sprintf("%s/1.0", widget.redditAppName))
73+
req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
74+
75+
// Make the request
76+
client := &http.Client{}
77+
resp, err := client.Do(req)
78+
if err != nil {
79+
return fmt.Errorf("querying Reddit API: %w", err)
80+
}
81+
defer resp.Body.Close()
82+
83+
// Read response body
84+
body, err := io.ReadAll(resp.Body)
85+
if err != nil {
86+
return fmt.Errorf("reading response body: %w", err)
87+
}
88+
89+
// Check for error status code
90+
if resp.StatusCode != http.StatusOK {
91+
return fmt.Errorf("API request failed with status %d: %s", resp.StatusCode, string(body))
92+
}
93+
94+
// Parse JSON response
95+
var tokenResp redditTokenResponse
96+
err = json.Unmarshal(body, &tokenResp)
97+
if err != nil {
98+
return fmt.Errorf("unmarshalling Reddit API response: %w", err)
99+
}
100+
101+
widget.redditAccessToken = tokenResp.AccessToken
102+
103+
return nil
104+
}
105+
38106
func (widget *redditWidget) initialize() error {
39107
if widget.Subreddit == "" {
40108
return errors.New("subreddit is required")
@@ -62,6 +130,10 @@ func (widget *redditWidget) initialize() error {
62130
}
63131
}
64132

133+
if err := widget.fetchRedditAccessToken(); err != nil {
134+
return fmt.Errorf("fetching Reddit API access token: %w", err)
135+
}
136+
65137
widget.
66138
withTitle("r/" + widget.Subreddit).
67139
withTitleURL("https://www.reddit.com/r/" + widget.Subreddit + "/").
@@ -97,6 +169,8 @@ func (widget *redditWidget) update(ctx context.Context) {
97169
widget.RequestUrlTemplate,
98170
widget.Proxy.client,
99171
widget.ShowFlairs,
172+
widget.redditAppName,
173+
widget.redditAccessToken,
100174
)
101175

102176
if !widget.canContinueUpdateAfterHandlingErr(err) {
@@ -172,6 +246,8 @@ func fetchSubredditPosts(
172246
requestUrlTemplate string,
173247
proxyClient *http.Client,
174248
showFlairs bool,
249+
redditAppName string,
250+
redditAccessToken string,
175251
) (forumPostList, error) {
176252
query := url.Values{}
177253
var requestUrl string
@@ -185,10 +261,18 @@ func fetchSubredditPosts(
185261
query.Set("t", topPeriod)
186262
}
187263

264+
var baseURL string
265+
266+
if redditAccessToken != "" {
267+
baseURL = "https://oauth.reddit.com"
268+
} else {
269+
baseURL = "https://www.reddit.com"
270+
}
271+
188272
if search != "" {
189-
requestUrl = fmt.Sprintf("https://www.reddit.com/search.json?%s", query.Encode())
273+
requestUrl = fmt.Sprintf("%s/search.json?%s", baseURL, query.Encode())
190274
} else {
191-
requestUrl = fmt.Sprintf("https://www.reddit.com/r/%s/%s.json?%s", subreddit, sort, query.Encode())
275+
requestUrl = fmt.Sprintf("%s/r/%s/%s.json?%s", baseURL, subreddit, sort, query.Encode())
192276
}
193277

194278
var client requestDoer = defaultHTTPClient
@@ -205,7 +289,16 @@ func fetchSubredditPosts(
205289
}
206290

207291
// Required to increase rate limit, otherwise Reddit randomly returns 429 even after just 2 requests
208-
setBrowserUserAgentHeader(request)
292+
if redditAppName == "" {
293+
setBrowserUserAgentHeader(request)
294+
} else {
295+
request.Header.Set("User-Agent", fmt.Sprintf("%s/1.0", redditAppName))
296+
}
297+
298+
if redditAccessToken != "" {
299+
request.Header.Set("Authorization", fmt.Sprintf("Bearer %s", redditAccessToken))
300+
}
301+
209302
responseJson, err := decodeJsonFromRequest[subredditResponseJson](client, request)
210303
if err != nil {
211304
return nil, err

0 commit comments

Comments
 (0)