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
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"anonymousoverflow/src/middleware"
|
||||||
"html/template"
|
"anonymousoverflow/src/routes"
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/PuerkitoBio/goquery"
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
|
|
||||||
"github.com/go-resty/resty/v2"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
|
||||||
client := resty.New()
|
|
||||||
|
|
||||||
r := gin.Default()
|
r := gin.Default()
|
||||||
|
|
||||||
r.LoadHTMLGlob("templates/*")
|
r.LoadHTMLGlob("templates/*")
|
||||||
r.Static("/static", "./public")
|
r.Static("/static", "./public")
|
||||||
|
|
||||||
|
r.Use(middleware.OptionsMiddleware())
|
||||||
|
|
||||||
r.GET("/robots.txt", func(c *gin.Context) {
|
r.GET("/robots.txt", func(c *gin.Context) {
|
||||||
c.String(200, "User-agent: *\nDisallow: /")
|
c.String(200, "User-agent: *\nDisallow: /")
|
||||||
})
|
})
|
||||||
|
|
||||||
r.GET("/", func(c *gin.Context) {
|
r.GET("/", routes.GetHome)
|
||||||
c.HTML(200, "home.html", gin.H{})
|
|
||||||
})
|
|
||||||
|
|
||||||
type urlConversionRequest struct {
|
r.POST("/", routes.PostHome)
|
||||||
URL string `form:"url" binding:"required"`
|
|
||||||
}
|
|
||||||
|
|
||||||
r.POST("/", func(c *gin.Context) {
|
r.GET("/questions/:id/:title", routes.ViewQuestion)
|
||||||
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/")))
|
|
||||||
|
|
||||||
})
|
|
||||||
|
|
||||||
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")
|
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