diff --git a/src/middleware/ratelimit.go b/src/middleware/ratelimit.go index d83c901..cfedfb2 100644 --- a/src/middleware/ratelimit.go +++ b/src/middleware/ratelimit.go @@ -2,6 +2,7 @@ package middleware import ( "anonymousoverflow/config" + "anonymousoverflow/src/utils" "os" "strings" "sync" @@ -45,9 +46,8 @@ func Ratelimit() gin.HandlerFunc { // if they exceed 30 requests in 1 minute, return a 429 if val.(int) > 30 { - c.HTML(429, "home.html", gin.H{ + utils.Render(c, 429, "home", gin.H{ "errorMessage": "You have exceeded the request limit. Please try again in a minute.", - "version": config.Version, }) c.Abort() return diff --git a/src/routes/home.go b/src/routes/home.go index 78202e1..bc514c8 100644 --- a/src/routes/home.go +++ b/src/routes/home.go @@ -1,7 +1,6 @@ package routes import ( - "anonymousoverflow/config" "anonymousoverflow/src/utils" "fmt" "regexp" @@ -11,11 +10,7 @@ import ( ) func GetHome(c *gin.Context) { - theme := utils.GetThemeFromEnv() - c.HTML(200, "home.html", gin.H{ - "version": config.Version, - "theme": theme, - }) + utils.Render(c, 200, "home", nil) } type urlConversionRequest struct { @@ -62,7 +57,7 @@ func PostHome(c *gin.Context) { body := urlConversionRequest{} if err := c.ShouldBind(&body); err != nil { - c.HTML(400, "home.html", gin.H{ + utils.Render(c, 400, "home", gin.H{ "errorMessage": "Invalid request body", }) return @@ -71,10 +66,8 @@ func PostHome(c *gin.Context) { translated := translateUrl(body.URL) if translated == "" { - theme := utils.GetThemeFromEnv() - c.HTML(400, "home.html", gin.H{ + utils.Render(c, 400, "home", gin.H{ "errorMessage": "Invalid stack overflow/exchange URL", - "theme": theme, }) return } diff --git a/src/routes/image.go b/src/routes/image.go index 252b12d..eb2023b 100644 --- a/src/routes/image.go +++ b/src/routes/image.go @@ -2,12 +2,12 @@ package routes import ( "anonymousoverflow/src/types" + "anonymousoverflow/src/utils" "fmt" "os" "time" "github.com/gin-gonic/gin" - "github.com/go-resty/resty/v2" "github.com/golang-jwt/jwt/v4" ) @@ -51,33 +51,15 @@ func GetImage(c *gin.Context) { } // download the image - var resp *resty.Response = nil - client := resty.New() - - if flareurl := os.Getenv("FLARERESOLVER"); flareurl == "" { - resp, err = client.R().Get(claims.ImageURL) - } else { - client.R().SetHeader("Content-Type", "application/json") - client.R().SetBody(struct { - Cmd string `json:"cmd"` - Url string `json:"url"` - MaxTimeout int `json:"maxTimeout"` - }{ - Cmd: "request.get", - Url: claims.ImageURL, - MaxTimeout: 60000, - }) - resp, err = client.R().Post(claims.ImageURL) - } - + body, _, headers, err := utils.GET(claims.ImageURL) if err != nil { c.AbortWithStatus(500) return } // set the content type - c.Header("Content-Type", resp.Header().Get("Content-Type")) + c.Header("Content-Type", headers.Get("Content-Type")) // write the image to the response - c.Writer.Write(resp.Body()) + c.Writer.Write(body) } diff --git a/src/routes/options.go b/src/routes/options.go index c0c8718..22edc4d 100644 --- a/src/routes/options.go +++ b/src/routes/options.go @@ -1,7 +1,6 @@ package routes import ( - "anonymousoverflow/config" "anonymousoverflow/src/utils" "fmt" @@ -14,16 +13,16 @@ func ChangeOptions(c *gin.Context) { switch name { case "images": text := "disabled" + if c.MustGet("disable_images").(bool) { text = "enabled" } + c.SetCookie("disable_images", fmt.Sprintf("%t", !c.MustGet("disable_images").(bool)), 60*60*24*365*10, "/", "", false, true) - theme := utils.GetThemeFromEnv() - c.HTML(200, "home.html", gin.H{ + utils.Render(c, 200, "home", gin.H{ "successMessage": "Images are now " + text, - "version": config.Version, - "theme": theme, }) + default: c.String(400, "400 Bad Request") } diff --git a/src/routes/question.go b/src/routes/question.go index e921215..f5791fb 100644 --- a/src/routes/question.go +++ b/src/routes/question.go @@ -3,6 +3,7 @@ package routes import ( "anonymousoverflow/config" "anonymousoverflow/src/utils" + "bytes" "fmt" "html" "html/template" @@ -15,7 +16,6 @@ import ( "github.com/PuerkitoBio/goquery" "github.com/gin-gonic/gin" - "github.com/go-resty/resty/v2" ) var codeBlockRegex = regexp.MustCompile(`(?s)
(.+?)<\/code><\/pre>`)
@@ -32,7 +32,7 @@ func ViewQuestion(c *gin.Context) {
questionId := c.Param("id")
if _, err := strconv.Atoi(questionId); err != nil {
- c.HTML(400, "home.html", gin.H{
+ utils.Render(c, 400, "home", gin.H{
"errorMessage": "Invalid question ID",
"version": config.Version,
})
@@ -53,44 +53,37 @@ func ViewQuestion(c *gin.Context) {
}
soLink := fmt.Sprintf("https://%s/questions/%s/%s?answertab=%s", domain, questionId, params.QuestionTitle, params.SoSortValue)
+ body, code, _, err := utils.GET(soLink)
- resp, err := fetchQuestionData(soLink)
-
- if resp.StatusCode() != 200 {
- c.HTML(500, "home.html", gin.H{
- "errorMessage": fmt.Sprintf("Received a non-OK status code %d", resp.StatusCode()),
- "version": config.Version,
+ if code != 200 {
+ utils.Render(c, 500, "home", gin.H{
+ "errorMessage": fmt.Sprintf("Received a non-OK status code %d", code),
})
return
}
- respBody := resp.String()
-
- respBodyReader := strings.NewReader(respBody)
+ respBodyReader := bytes.NewReader(body)
doc, err := goquery.NewDocumentFromReader(respBodyReader)
if err != nil {
- c.HTML(500, "home.html", gin.H{
+ utils.Render(c, 500, "home", gin.H{
"errorMessage": "Unable to parse question data",
- "version": config.Version,
})
return
}
newFilteredQuestion, err := extractQuestionData(doc, domain)
if err != nil {
- c.HTML(500, "home.html", gin.H{
+ utils.Render(c, 500, "home", gin.H{
"errorMessage": "Failed to extract question data",
- "version": config.Version,
})
return
}
answers, err := extractAnswersData(doc, domain)
if err != nil {
- c.HTML(500, "home.html", gin.H{
+ utils.Render(c, 500, "home", gin.H{
"errorMessage": "Failed to extract answer data",
- "version": config.Version,
})
return
}
@@ -101,18 +94,14 @@ func ViewQuestion(c *gin.Context) {
imagePolicy = "'self'"
}
- theme := utils.GetThemeFromEnv()
-
- c.HTML(200, "question.html", gin.H{
+ utils.Render(c, 200, "question", gin.H{
"question": newFilteredQuestion,
"answers": answers,
"imagePolicy": imagePolicy,
"currentUrl": fmt.Sprintf("%s%s", os.Getenv("APP_URL"), c.Request.URL.Path),
"sortValue": params.SoSortValue,
"domain": domain,
- "theme": theme,
})
-
}
type viewQuestionInputs struct {
@@ -127,9 +116,8 @@ func parseAndValidateParameters(c *gin.Context) (inputs viewQuestionInputs, err
questionId := c.Param("id")
if _, err = strconv.Atoi(questionId); err != nil {
- c.HTML(400, "home.html", gin.H{
+ utils.Render(c, 400, "home", gin.H{
"errorMessage": "Invalid question ID",
- "version": config.Version,
})
return
}
@@ -155,29 +143,6 @@ func parseAndValidateParameters(c *gin.Context) (inputs viewQuestionInputs, err
return
}
-// fetchQuestionData sends the request to StackOverflow.
-func fetchQuestionData(soLink string) (*resty.Response, error) {
- flareurl := ""
- client := resty.New()
-
- if flareurl = os.Getenv("FLARERESOLVER"); flareurl == "" {
- return client.R().Get(soLink)
- }
-
- client.R().SetHeader("Content-Type", "application/json")
- client.R().SetBody(struct {
- Cmd string `json:"cmd"`
- Url string `json:"url"`
- MaxTimeout int `json:"maxTimeout"`
- }{
- Cmd: "request.get",
- Url: soLink,
- MaxTimeout: 60000,
- })
-
- return client.R().Post(flareurl)
-}
-
// extractQuestionData parses the HTML document and extracts question data.
func extractQuestionData(doc *goquery.Document, domain string) (question types.FilteredQuestion, err error) {
// Extract the question title.
diff --git a/src/routes/shortened.go b/src/routes/shortened.go
index 494b5bc..7f37299 100644
--- a/src/routes/shortened.go
+++ b/src/routes/shortened.go
@@ -1,6 +1,7 @@
package routes
import (
+ "anonymousoverflow/src/utils"
"fmt"
"net/http"
"os"
@@ -31,14 +32,14 @@ func RedirectShortenedOverflowURL(c *gin.Context) {
}
resp, err := client.R().Get(fmt.Sprintf("https://%s/a/%s/%s", domain, id, answerId))
if err != nil {
- c.HTML(400, "home.html", gin.H{
+ utils.Render(c, 400, "home", gin.H{
"errorMessage": "Unable to fetch stack overflow URL",
})
return
}
if resp.StatusCode() != 302 {
- c.HTML(400, "home.html", gin.H{
+ utils.Render(c, 400, "home", gin.H{
"errorMessage": fmt.Sprintf("Unexpected HTTP status from origin: %d", resp.StatusCode()),
})
return
diff --git a/src/utils/render.go b/src/utils/render.go
new file mode 100644
index 0000000..efdd13d
--- /dev/null
+++ b/src/utils/render.go
@@ -0,0 +1,24 @@
+package utils
+
+import (
+ "anonymousoverflow/config"
+
+ "github.com/gin-gonic/gin"
+)
+
+func Render(c *gin.Context, code int, temp string, _data ...gin.H) {
+ var data gin.H = nil
+
+ if len(_data) > 0 {
+ data = _data[0]
+ }
+
+ if data == nil {
+ data = gin.H{}
+ }
+
+ data["version"] = config.Version
+ data["theme"] = GetThemeFromEnv()
+
+ c.HTML(code, temp+".html", data)
+}
diff --git a/src/utils/request.go b/src/utils/request.go
new file mode 100644
index 0000000..e08f760
--- /dev/null
+++ b/src/utils/request.go
@@ -0,0 +1,63 @@
+package utils
+
+import (
+ "net/http"
+ "os"
+
+ "github.com/go-resty/resty/v2"
+)
+
+// https://github.com/FlareSolverr/FlareSolverr#-requestget
+type request struct {
+ Cmd string `json:"cmd"`
+ Url string `json:"url"`
+ MaxTimeout int `json:"maxTimeout"`
+}
+
+type solution struct {
+ Status int `json:"status"`
+ Response []byte `json:"response"`
+ Headers map[string]string `json:"headers"`
+}
+
+type response struct {
+ Solution solution `json:"solution"`
+}
+
+func GET(url string) ([]byte, int, http.Header, error) {
+ var (
+ client *resty.Client = resty.New()
+ res *resty.Response = nil
+ frurl string = ""
+ err error
+ )
+
+ if frurl = os.Getenv("FLARERESOLVER"); frurl == "" {
+ if res, err := client.R().Get(url); err != nil {
+ return nil, 0, nil, err
+ } else {
+ return res.Body(), res.StatusCode(), res.Header(), nil
+ }
+ }
+
+ client.R().SetHeader("Content-Type", "application/json")
+ client.R().SetBody(request{
+ Cmd: "request.get",
+ Url: url,
+ MaxTimeout: 60000,
+ })
+ client.R().SetResult(&response{})
+
+ if res, err = client.R().Post(url); err != nil {
+ return nil, 0, nil, err
+ }
+
+ response := res.Result().(*response)
+ headers := http.Header{}
+
+ for k, v := range response.Solution.Headers {
+ headers.Add(k, v)
+ }
+
+ return response.Solution.Response, response.Solution.Status, headers, nil
+}