refactor: split into files
This commit is contained in:
parent
e0a4b2363f
commit
076f1387f0
184
main.go
184
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 <div class="answer-meta accepted">Accepted Answer</div> to the top of the answer
|
||||
answerBodyHTML = fmt.Sprintf(`<div class="answer-meta accepted">Accepted Answer - %s Upvotes</div>`, voteCount) + answerBodyHTML
|
||||
} else {
|
||||
// add <div class="answer-meta">%s Upvotes</div> to the top of the answer
|
||||
answerBodyHTML = fmt.Sprintf(`<div class="answer-meta">%s Upvotes</div>`, 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 <div class="answer-author">Answered %s by %s</div> to the bottom of the answer
|
||||
answerBodyHTML += fmt.Sprintf(`<div class="answer-author-parent"><div class="answer-author">Answered at %s by <a href="https://stackoverflow.com/%s" target="_blank" rel="noopener noreferrer">%s</a></div></div>`, 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")
|
||||
}
|
||||
|
23
src/middleware/options.go
Normal file
23
src/middleware/options.go
Normal file
@ -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()
|
||||
}
|
||||
}
|
41
src/routes/home.go
Normal file
41
src/routes/home.go
Normal file
@ -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/")))
|
||||
|
||||
}
|
153
src/routes/question.go
Normal file
153
src/routes/question.go
Normal file
@ -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 <div class="answer-meta accepted">Accepted Answer</div> to the top of the answer
|
||||
answerBodyHTML = fmt.Sprintf(`<div class="answer-meta accepted">Accepted Answer - %s Upvotes</div>`, voteCount) + answerBodyHTML
|
||||
} else {
|
||||
// add <div class="answer-meta">%s Upvotes</div> to the top of the answer
|
||||
answerBodyHTML = fmt.Sprintf(`<div class="answer-meta">%s Upvotes</div>`, 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 <div class="answer-author">Answered %s by %s</div> to the bottom of the answer
|
||||
answerBodyHTML += fmt.Sprintf(`<div class="answer-author-parent"><div class="answer-author">Answered at %s by <a href="https://stackoverflow.com/%s" target="_blank" rel="noopener noreferrer">%s</a></div></div>`, 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,
|
||||
})
|
||||
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user