diff --git a/docs/configuration.md b/docs/configuration.md index 6bd1bc6..8508165 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -746,6 +746,7 @@ Example: | collapse-after | integer | no | 5 | | comments-url-template | string | no | https://www.reddit.com/{POST-PATH} | | request-url-template | string | no | | +| proxy | string or multiple parameters | no | | | sort-by | string | no | hot | | top-period | string | no | day | | search | string | no | | @@ -807,7 +808,7 @@ r/selfhosted/comments/bsp01i/welcome_to_rselfhosted_please_read_this_first/ `{SUBREDDIT}` - the subreddit name ##### `request-url-template` -A custom request url that will be used to fetch the data instead. This is useful when you're hosting Glance on a VPS and Reddit is blocking the requests, and you want to route it through an HTTP proxy. +A custom request URL that will be used to fetch the data. This is useful when you're hosting Glance on a VPS where Reddit is blocking the requests and you want to route them through a proxy that accepts the URL as either a part of the path or a query parameter. Placeholders: @@ -818,6 +819,29 @@ https://proxy/{REQUEST-URL} https://your.proxy/?url={REQUEST-URL} ``` +##### `proxy` +A custom HTTP/HTTPS proxy URL that will be used to fetch the data. This is useful when you're hosting Glance on a VPS where Reddit is blocking the requests and you want to bypass the restriction by routing the requests through a proxy. Example: + +``` +proxy: http://user:pass@proxy.com:8080 +proxy: https://user:pass@proxy.com:443 +``` + +Alternatively, you can specify the proxy URL as well as additional options by using multiple parameters: + +```yaml +proxy: + url: http://proxy.com:8080 + allow-insecure: true + timeout: 10s +``` + +###### `allow-insecure` +When set to `true`, allows the use of insecure connections such as when the proxy has a self-signed certificate. + +###### `timeout` +The maximum time to wait for a response from the proxy. The value is a string and must be a number followed by one of s, m, h, d. Example: `10s` for 10 seconds, `1m` for 1 minute, etc + ##### `sort-by` Can be used to specify the order in which the posts should get returned. Possible values are `hot`, `new`, `top` and `rising`. diff --git a/internal/glance/config-fields.go b/internal/glance/config-fields.go index 6c6f0c5..8aaac85 100644 --- a/internal/glance/config-fields.go +++ b/internal/glance/config-fields.go @@ -1,7 +1,10 @@ package glance import ( + "crypto/tls" "fmt" + "net/http" + "net/url" "regexp" "strconv" "strings" @@ -169,3 +172,50 @@ func (i *customIconField) UnmarshalYAML(node *yaml.Node) error { *i = newCustomIconField(value) return nil } + +type proxyOptionsField struct { + URL string `yaml:"url"` + AllowInsecure bool `yaml:"allow-insecure"` + Timeout durationField `yaml:"timeout"` + client *http.Client `yaml:"-"` +} + +func (p *proxyOptionsField) UnmarshalYAML(node *yaml.Node) error { + type proxyOptionsFieldAlias proxyOptionsField + alias := (*proxyOptionsFieldAlias)(p) + var proxyURL string + + if err := node.Decode(&proxyURL); err != nil { + if err := node.Decode(alias); err != nil { + return err + } + } + + if proxyURL == "" && p.URL == "" { + return nil + } + + if p.URL != "" { + proxyURL = p.URL + } + + parsedUrl, err := url.Parse(proxyURL) + if err != nil { + return fmt.Errorf("parsing proxy URL: %v", err) + } + + var timeout = defaultClientTimeout + if p.Timeout > 0 { + timeout = time.Duration(p.Timeout) + } + + p.client = &http.Client{ + Timeout: timeout, + Transport: &http.Transport{ + Proxy: http.ProxyURL(parsedUrl), + TLSClientConfig: &tls.Config{InsecureSkipVerify: p.AllowInsecure}, + }, + } + + return nil +} diff --git a/internal/glance/widget-reddit.go b/internal/glance/widget-reddit.go index 1ef1147..5025772 100644 --- a/internal/glance/widget-reddit.go +++ b/internal/glance/widget-reddit.go @@ -19,19 +19,20 @@ var ( type redditWidget struct { widgetBase `yaml:",inline"` - Posts forumPostList `yaml:"-"` - Subreddit string `yaml:"subreddit"` - Style string `yaml:"style"` - ShowThumbnails bool `yaml:"show-thumbnails"` - ShowFlairs bool `yaml:"show-flairs"` - SortBy string `yaml:"sort-by"` - TopPeriod string `yaml:"top-period"` - Search string `yaml:"search"` - ExtraSortBy string `yaml:"extra-sort-by"` - CommentsUrlTemplate string `yaml:"comments-url-template"` - Limit int `yaml:"limit"` - CollapseAfter int `yaml:"collapse-after"` - RequestUrlTemplate string `yaml:"request-url-template"` + Posts forumPostList `yaml:"-"` + Subreddit string `yaml:"subreddit"` + Proxy proxyOptionsField `yaml:"proxy"` + Style string `yaml:"style"` + ShowThumbnails bool `yaml:"show-thumbnails"` + ShowFlairs bool `yaml:"show-flairs"` + SortBy string `yaml:"sort-by"` + TopPeriod string `yaml:"top-period"` + Search string `yaml:"search"` + ExtraSortBy string `yaml:"extra-sort-by"` + CommentsUrlTemplate string `yaml:"comments-url-template"` + Limit int `yaml:"limit"` + CollapseAfter int `yaml:"collapse-after"` + RequestUrlTemplate string `yaml:"request-url-template"` } func (widget *redditWidget) initialize() error { @@ -94,6 +95,7 @@ func (widget *redditWidget) update(ctx context.Context) { widget.Search, widget.CommentsUrlTemplate, widget.RequestUrlTemplate, + widget.Proxy.client, widget.ShowFlairs, ) @@ -161,7 +163,16 @@ func templateRedditCommentsURL(template, subreddit, postId, postPath string) str return template } -func fetchSubredditPosts(subreddit, sort, topPeriod, search, commentsUrlTemplate, requestUrlTemplate string, showFlairs bool) (forumPostList, error) { +func fetchSubredditPosts( + subreddit, + sort, + topPeriod, + search, + commentsUrlTemplate, + requestUrlTemplate string, + proxyClient *http.Client, + showFlairs bool, +) (forumPostList, error) { query := url.Values{} var requestUrl string @@ -180,8 +191,12 @@ func fetchSubredditPosts(subreddit, sort, topPeriod, search, commentsUrlTemplate requestUrl = fmt.Sprintf("https://www.reddit.com/r/%s/%s.json?%s", subreddit, sort, query.Encode()) } + var client requestDoer = defaultHTTPClient + if requestUrlTemplate != "" { requestUrl = strings.ReplaceAll(requestUrlTemplate, "{REQUEST-URL}", requestUrl) + } else if proxyClient != nil { + client = proxyClient } request, err := http.NewRequest("GET", requestUrl, nil) @@ -191,7 +206,7 @@ func fetchSubredditPosts(subreddit, sort, topPeriod, search, commentsUrlTemplate // Required to increase rate limit, otherwise Reddit randomly returns 429 even after just 2 requests setBrowserUserAgentHeader(request) - responseJson, err := decodeJsonFromRequest[subredditResponseJson](defaultHTTPClient, request) + responseJson, err := decodeJsonFromRequest[subredditResponseJson](client, request) if err != nil { return nil, err }