From 076f1387f04999463479d164eea2c83ca8790147 Mon Sep 17 00:00:00 2001 From: httpjamesm Date: Tue, 27 Dec 2022 23:53:28 -0500 Subject: [PATCH] refactor: split into files --- main.go | 184 ++------------------------------------ src/middleware/options.go | 23 +++++ src/routes/home.go | 41 +++++++++ src/routes/question.go | 153 +++++++++++++++++++++++++++++++ 4 files changed, 224 insertions(+), 177 deletions(-) create mode 100644 src/middleware/options.go create mode 100644 src/routes/home.go create mode 100644 src/routes/question.go diff --git a/main.go b/main.go index dc0d8b6..e42615e 100644 --- a/main.go +++ b/main.go @@ -1,200 +1,30 @@ package main import ( - "fmt" - "html/template" - "strings" + "anonymousoverflow/src/middleware" + "anonymousoverflow/src/routes" - "github.com/PuerkitoBio/goquery" "github.com/gin-gonic/gin" - - "github.com/go-resty/resty/v2" ) func main() { - client := resty.New() - r := gin.Default() r.LoadHTMLGlob("templates/*") r.Static("/static", "./public") + r.Use(middleware.OptionsMiddleware()) + r.GET("/robots.txt", func(c *gin.Context) { c.String(200, "User-agent: *\nDisallow: /") }) - r.GET("/", func(c *gin.Context) { - c.HTML(200, "home.html", gin.H{}) - }) + r.GET("/", routes.GetHome) - type urlConversionRequest struct { - URL string `form:"url" binding:"required"` - } + r.POST("/", routes.PostHome) - r.POST("/", func(c *gin.Context) { - body := urlConversionRequest{} + r.GET("/questions/:id/:title", routes.ViewQuestion) - if err := c.ShouldBind(&body); err != nil { - c.HTML(400, "home.html", gin.H{ - "errorMessage": "Invalid request body", - }) - return - } - - soLink := body.URL - - // validate URL - if !strings.HasPrefix(soLink, "https://stackoverflow.com/questions/") { - c.HTML(400, "home.html", gin.H{ - "errorMessage": "Invalid stack overflow URL", - }) - return - } - - // redirect to the proxied thread - c.Redirect(302, fmt.Sprintf("/questions/%s", strings.TrimPrefix(soLink, "https://stackoverflow.com/questions/"))) - - }) - - r.GET("/questions/:id/:title", func(c *gin.Context) { - questionId := c.Param("id") - questionTitle := c.Param("title") - - soLink := fmt.Sprintf("https://stackoverflow.com/questions/%s/%s", questionId, questionTitle) - - resp, err := client.R().Get(soLink) - if err != nil { - panic(err) - } - - respBody := resp.String() - - respBodyReader := strings.NewReader(respBody) - - doc, err := goquery.NewDocumentFromReader(respBodyReader) - if err != nil { - panic(err) - } - - questionTextParent := doc.Find("h1.fs-headline1") - - questionText := questionTextParent.Children().First().Text() - - questionBodyParent := doc.Find("div.s-prose") - - questionBodyParentHTML, err := questionBodyParent.Html() - if err != nil { - panic(err) - } - - questionCard := doc.Find("div.postcell") - - questionMetadata := questionCard.Find("div.user-info") - questionTimestamp := "" - questionMetadata.Find("span.relativetime").Each(func(i int, s *goquery.Selection) { - // get the second - if i == 0 { - if s.Text() != "" { - // if it's not been edited, it means it's the first - questionTimestamp = s.Text() - return - } - } - - // otherwise it's the second element - if i == 1 { - questionTimestamp = s.Text() - return - } - }) - - userDetails := questionMetadata.Find("div.user-details") - - questionAuthor := "" - questionAuthorURL := "" - - userDetails.Find("a").Each(func(i int, s *goquery.Selection) { - // get the second - if i == 0 { - if s.Text() != "" { - // if it's not been edited, it means it's the first - questionAuthor = s.Text() - questionAuthorURL, _ = s.Attr("href") - return - } - } - - // otherwise it's the second element - if i == 1 { - questionAuthor = s.Text() - questionAuthorURL, _ = s.Attr("href") - return - } - }) - - answers := []template.HTML{} - - doc.Find("div.answer").Each(func(i int, s *goquery.Selection) { - postLayout := s.Find("div.post-layout") - voteCell := postLayout.Find("div.votecell") - answerCell := postLayout.Find("div.answercell") - answerBody := answerCell.Find("div.s-prose") - answerBodyHTML, _ := answerBody.Html() - - voteCount := voteCell.Find("div.js-vote-count").Text() - - if s.HasClass("accepted-answer") { - // add
Accepted Answer
to the top of the answer - answerBodyHTML = fmt.Sprintf(`
Accepted Answer - %s Upvotes
`, voteCount) + answerBodyHTML - } else { - // add
%s Upvotes
to the top of the answer - answerBodyHTML = fmt.Sprintf(`
%s Upvotes
`, voteCount) + answerBodyHTML - } - - answerFooter := s.Find("div.mt24") - - answerAuthorURL := "" - answerAuthorName := "" - answerTimestamp := "" - - answerFooter.Find("div.post-signature").Each(func(i int, s *goquery.Selection) { - answerAuthorDetails := s.Find("div.user-details") - - if answerAuthorDetails.Length() == 0 { - return - } - - if answerAuthorDetails.Length() > 1 { - if i == 0 { - return - } - } - - answerAuthor := answerAuthorDetails.Find("a").First() - - answerAuthorURL = answerAuthor.AttrOr("href", "") - answerAuthorName = answerAuthor.Text() - answerTimestamp = s.Find("span.relativetime").Text() - }) - - // append
Answered %s by %s
to the bottom of the answer - answerBodyHTML += fmt.Sprintf(`
Answered at %s by %s
`, answerTimestamp, answerAuthorURL, answerAuthorName) - - // get the timestamp and author - - answers = append(answers, template.HTML(answerBodyHTML)) - }) - - c.HTML(200, "question.html", gin.H{ - "title": questionText, - "body": template.HTML(questionBodyParentHTML), - "timestamp": questionTimestamp, - "author": questionAuthor, - "authorURL": questionAuthorURL, - "answers": answers, - }) - - }) r.Run("localhost:8080") } diff --git a/src/middleware/options.go b/src/middleware/options.go new file mode 100644 index 0000000..d11b178 --- /dev/null +++ b/src/middleware/options.go @@ -0,0 +1,23 @@ +package middleware + +import "github.com/gin-gonic/gin" + +func OptionsMiddleware() gin.HandlerFunc { + return func(c *gin.Context) { + c.Set("disable_images", false) + + // get cookie + cookie, err := c.Cookie("disable_images") + if err != nil { + c.Next() + return + } + + // check if disable_images is equal to "true" + if cookie == "true" { + c.Set("disable_images", true) + } + + c.Next() + } +} diff --git a/src/routes/home.go b/src/routes/home.go new file mode 100644 index 0000000..4c97c1e --- /dev/null +++ b/src/routes/home.go @@ -0,0 +1,41 @@ +package routes + +import ( + "fmt" + "strings" + + "github.com/gin-gonic/gin" +) + +func GetHome(c *gin.Context) { + c.HTML(200, "home.html", gin.H{}) +} + +type urlConversionRequest struct { + URL string `form:"url" binding:"required"` +} + +func PostHome(c *gin.Context) { + body := urlConversionRequest{} + + if err := c.ShouldBind(&body); err != nil { + c.HTML(400, "home.html", gin.H{ + "errorMessage": "Invalid request body", + }) + return + } + + soLink := body.URL + + // validate URL + if !strings.HasPrefix(soLink, "https://stackoverflow.com/questions/") { + c.HTML(400, "home.html", gin.H{ + "errorMessage": "Invalid stack overflow URL", + }) + return + } + + // redirect to the proxied thread + c.Redirect(302, fmt.Sprintf("/questions/%s", strings.TrimPrefix(soLink, "https://stackoverflow.com/questions/"))) + +} diff --git a/src/routes/question.go b/src/routes/question.go new file mode 100644 index 0000000..0a3c6e4 --- /dev/null +++ b/src/routes/question.go @@ -0,0 +1,153 @@ +package routes + +import ( + "fmt" + "html/template" + "strings" + + "github.com/PuerkitoBio/goquery" + "github.com/gin-gonic/gin" + "github.com/go-resty/resty/v2" +) + +func ViewQuestion(c *gin.Context) { + client := resty.New() + + questionId := c.Param("id") + questionTitle := c.Param("title") + + soLink := fmt.Sprintf("https://stackoverflow.com/questions/%s/%s", questionId, questionTitle) + + resp, err := client.R().Get(soLink) + if err != nil { + panic(err) + } + + respBody := resp.String() + + respBodyReader := strings.NewReader(respBody) + + doc, err := goquery.NewDocumentFromReader(respBodyReader) + if err != nil { + panic(err) + } + + questionTextParent := doc.Find("h1.fs-headline1") + + questionText := questionTextParent.Children().First().Text() + + questionBodyParent := doc.Find("div.s-prose") + + questionBodyParentHTML, err := questionBodyParent.Html() + if err != nil { + panic(err) + } + + questionCard := doc.Find("div.postcell") + + questionMetadata := questionCard.Find("div.user-info") + questionTimestamp := "" + questionMetadata.Find("span.relativetime").Each(func(i int, s *goquery.Selection) { + // get the second + if i == 0 { + if s.Text() != "" { + // if it's not been edited, it means it's the first + questionTimestamp = s.Text() + return + } + } + + // otherwise it's the second element + if i == 1 { + questionTimestamp = s.Text() + return + } + }) + + userDetails := questionMetadata.Find("div.user-details") + + questionAuthor := "" + questionAuthorURL := "" + + userDetails.Find("a").Each(func(i int, s *goquery.Selection) { + // get the second + if i == 0 { + if s.Text() != "" { + // if it's not been edited, it means it's the first + questionAuthor = s.Text() + questionAuthorURL, _ = s.Attr("href") + return + } + } + + // otherwise it's the second element + if i == 1 { + questionAuthor = s.Text() + questionAuthorURL, _ = s.Attr("href") + return + } + }) + + answers := []template.HTML{} + + doc.Find("div.answer").Each(func(i int, s *goquery.Selection) { + postLayout := s.Find("div.post-layout") + voteCell := postLayout.Find("div.votecell") + answerCell := postLayout.Find("div.answercell") + answerBody := answerCell.Find("div.s-prose") + answerBodyHTML, _ := answerBody.Html() + + voteCount := voteCell.Find("div.js-vote-count").Text() + + if s.HasClass("accepted-answer") { + // add
Accepted Answer
to the top of the answer + answerBodyHTML = fmt.Sprintf(`
Accepted Answer - %s Upvotes
`, voteCount) + answerBodyHTML + } else { + // add
%s Upvotes
to the top of the answer + answerBodyHTML = fmt.Sprintf(`
%s Upvotes
`, voteCount) + answerBodyHTML + } + + answerFooter := s.Find("div.mt24") + + answerAuthorURL := "" + answerAuthorName := "" + answerTimestamp := "" + + answerFooter.Find("div.post-signature").Each(func(i int, s *goquery.Selection) { + answerAuthorDetails := s.Find("div.user-details") + + if answerAuthorDetails.Length() == 0 { + return + } + + if answerAuthorDetails.Length() > 1 { + if i == 0 { + return + } + } + + answerAuthor := answerAuthorDetails.Find("a").First() + + answerAuthorURL = answerAuthor.AttrOr("href", "") + answerAuthorName = answerAuthor.Text() + answerTimestamp = s.Find("span.relativetime").Text() + }) + + // append
Answered %s by %s
to the bottom of the answer + answerBodyHTML += fmt.Sprintf(`
Answered at %s by %s
`, answerTimestamp, answerAuthorURL, answerAuthorName) + + // get the timestamp and author + + answers = append(answers, template.HTML(answerBodyHTML)) + }) + + c.HTML(200, "question.html", gin.H{ + "title": questionText, + "body": template.HTML(questionBodyParentHTML), + "timestamp": questionTimestamp, + "author": questionAuthor, + "authorURL": questionAuthorURL, + "answers": answers, + }) + +}