2022-12-27 23:53:28 -05:00
|
|
|
package routes
|
|
|
|
|
|
|
|
import (
|
2022-12-29 13:54:37 -05:00
|
|
|
"anonymousoverflow/config"
|
2022-12-28 18:51:14 -05:00
|
|
|
"anonymousoverflow/src/utils"
|
2022-12-27 23:53:28 -05:00
|
|
|
"fmt"
|
2022-12-28 23:56:14 -05:00
|
|
|
"html"
|
2022-12-27 23:53:28 -05:00
|
|
|
"html/template"
|
2022-12-28 11:56:17 -05:00
|
|
|
"os"
|
2022-12-28 18:51:14 -05:00
|
|
|
"regexp"
|
2022-12-29 13:45:04 -05:00
|
|
|
"strconv"
|
2022-12-27 23:53:28 -05:00
|
|
|
"strings"
|
|
|
|
|
2022-12-29 13:06:14 -05:00
|
|
|
"anonymousoverflow/src/types"
|
|
|
|
|
2022-12-27 23:53:28 -05:00
|
|
|
"github.com/PuerkitoBio/goquery"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
|
|
"github.com/go-resty/resty/v2"
|
|
|
|
)
|
|
|
|
|
2022-12-28 18:51:14 -05:00
|
|
|
var codeBlockRegex = regexp.MustCompile(`(?s)<pre><code>(.+?)<\/code><\/pre>`)
|
2022-12-28 19:10:14 -05:00
|
|
|
var questionCodeBlockRegex = regexp.MustCompile(`(?s)<pre class=".+"><code( class=".+")?>(.+?)</code></pre>`)
|
2022-12-28 18:51:14 -05:00
|
|
|
|
2023-02-04 13:05:10 -05:00
|
|
|
var soSortValues = map[string]string{
|
|
|
|
"votes": "scoredesc",
|
|
|
|
"trending": "trending",
|
|
|
|
"newest": "modifieddesc",
|
|
|
|
"oldest": "createdasc",
|
|
|
|
}
|
|
|
|
|
2022-12-27 23:53:28 -05:00
|
|
|
func ViewQuestion(c *gin.Context) {
|
|
|
|
client := resty.New()
|
|
|
|
|
|
|
|
questionId := c.Param("id")
|
2022-12-29 13:45:04 -05:00
|
|
|
if _, err := strconv.Atoi(questionId); err != nil {
|
|
|
|
c.HTML(400, "home.html", gin.H{
|
|
|
|
"errorMessage": "Invalid question ID",
|
|
|
|
"theme": c.MustGet("theme").(string),
|
2022-12-29 13:54:37 -05:00
|
|
|
"version": config.Version,
|
2022-12-29 13:45:04 -05:00
|
|
|
})
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2022-12-27 23:53:28 -05:00
|
|
|
questionTitle := c.Param("title")
|
|
|
|
|
2023-02-04 13:05:10 -05:00
|
|
|
sortValue := c.Query("sort_by")
|
|
|
|
if sortValue == "" {
|
|
|
|
sortValue = "votes"
|
|
|
|
}
|
|
|
|
|
|
|
|
soSortValue, ok := soSortValues[sortValue]
|
|
|
|
if !ok {
|
|
|
|
soSortValue = soSortValues["votes"]
|
|
|
|
}
|
|
|
|
|
|
|
|
soLink := fmt.Sprintf("https://stackoverflow.com/questions/%s/%s?answertab=%s", questionId, questionTitle, soSortValue)
|
2022-12-27 23:53:28 -05:00
|
|
|
|
|
|
|
resp, err := client.R().Get(soLink)
|
|
|
|
if err != nil {
|
2022-12-29 13:45:04 -05:00
|
|
|
c.HTML(500, "home.html", gin.H{
|
|
|
|
"errorMessage": "Unable to fetch question data",
|
|
|
|
"theme": c.MustGet("theme").(string),
|
2022-12-29 13:54:37 -05:00
|
|
|
"version": config.Version,
|
2022-12-29 13:45:04 -05:00
|
|
|
})
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if resp.StatusCode() != 200 {
|
|
|
|
c.HTML(500, "home.html", gin.H{
|
|
|
|
"errorMessage": "Received a non-OK status code",
|
|
|
|
"theme": c.MustGet("theme").(string),
|
2022-12-29 13:54:37 -05:00
|
|
|
"version": config.Version,
|
2022-12-29 13:45:04 -05:00
|
|
|
})
|
|
|
|
return
|
2022-12-27 23:53:28 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
respBody := resp.String()
|
|
|
|
|
|
|
|
respBodyReader := strings.NewReader(respBody)
|
|
|
|
|
|
|
|
doc, err := goquery.NewDocumentFromReader(respBodyReader)
|
|
|
|
if err != nil {
|
2022-12-29 13:45:04 -05:00
|
|
|
c.HTML(500, "home.html", gin.H{
|
|
|
|
"errorMessage": "Unable to parse question data",
|
|
|
|
"theme": c.MustGet("theme").(string),
|
2022-12-29 13:54:37 -05:00
|
|
|
"version": config.Version,
|
2022-12-29 13:45:04 -05:00
|
|
|
})
|
|
|
|
return
|
2022-12-27 23:53:28 -05:00
|
|
|
}
|
|
|
|
|
2022-12-29 13:06:14 -05:00
|
|
|
newFilteredQuestion := types.FilteredQuestion{}
|
|
|
|
|
2022-12-27 23:53:28 -05:00
|
|
|
questionTextParent := doc.Find("h1.fs-headline1")
|
|
|
|
|
|
|
|
questionText := questionTextParent.Children().First().Text()
|
|
|
|
|
2022-12-29 13:06:14 -05:00
|
|
|
newFilteredQuestion.Title = questionText
|
|
|
|
|
2022-12-28 22:54:13 -05:00
|
|
|
questionPostLayout := doc.Find("div.post-layout").First()
|
|
|
|
|
2023-02-04 13:40:57 -05:00
|
|
|
questionTags := utils.GetPostTags(questionPostLayout)
|
|
|
|
newFilteredQuestion.Tags = questionTags
|
|
|
|
|
2022-12-27 23:53:28 -05:00
|
|
|
questionBodyParent := doc.Find("div.s-prose")
|
|
|
|
|
|
|
|
questionBodyParentHTML, err := questionBodyParent.Html()
|
|
|
|
if err != nil {
|
2022-12-29 13:45:04 -05:00
|
|
|
c.HTML(500, "home.html", gin.H{
|
|
|
|
"errorMessage": "Unable to parse question body",
|
|
|
|
"theme": c.MustGet("theme").(string),
|
2022-12-29 13:54:37 -05:00
|
|
|
"version": config.Version,
|
2022-12-29 13:45:04 -05:00
|
|
|
})
|
|
|
|
return
|
2022-12-27 23:53:28 -05:00
|
|
|
}
|
|
|
|
|
2023-02-02 19:05:08 -05:00
|
|
|
newFilteredQuestion.Body = template.HTML(utils.ReplaceImgTags(questionBodyParentHTML))
|
2022-12-29 13:06:14 -05:00
|
|
|
|
|
|
|
questionBodyText := questionBodyParent.Text()
|
|
|
|
|
|
|
|
// remove all whitespace to create the shortened body desc
|
|
|
|
shortenedBody := strings.TrimSpace(questionBodyText)
|
|
|
|
|
|
|
|
// remove all newlines
|
|
|
|
shortenedBody = strings.ReplaceAll(shortenedBody, "\n", " ")
|
|
|
|
|
|
|
|
// get the first 50 chars
|
|
|
|
shortenedBody = shortenedBody[:50]
|
|
|
|
|
|
|
|
newFilteredQuestion.ShortenedBody = shortenedBody
|
|
|
|
|
|
|
|
comments := utils.FindAndReturnComments(questionBodyParentHTML, questionPostLayout)
|
2022-12-29 13:11:33 -05:00
|
|
|
newFilteredQuestion.Comments = comments
|
2022-12-28 22:54:13 -05:00
|
|
|
|
2022-12-28 19:10:14 -05:00
|
|
|
// parse any code blocks and highlight them
|
|
|
|
answerCodeBlocks := questionCodeBlockRegex.FindAllString(questionBodyParentHTML, -1)
|
|
|
|
for _, codeBlock := range answerCodeBlocks {
|
|
|
|
codeBlock = utils.StripBlockTags(codeBlock)
|
|
|
|
|
|
|
|
// syntax highlight
|
|
|
|
highlightedCodeBlock := utils.HighlightSyntaxViaContent(codeBlock)
|
|
|
|
|
|
|
|
// replace the code block with the highlighted code block
|
|
|
|
questionBodyParentHTML = strings.Replace(questionBodyParentHTML, codeBlock, highlightedCodeBlock, 1)
|
|
|
|
}
|
|
|
|
|
2022-12-27 23:53:28 -05:00
|
|
|
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
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
2022-12-29 13:06:14 -05:00
|
|
|
newFilteredQuestion.Timestamp = questionTimestamp
|
|
|
|
|
2022-12-27 23:53:28 -05:00
|
|
|
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
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
2022-12-29 13:06:14 -05:00
|
|
|
newFilteredQuestion.AuthorName = questionAuthor
|
|
|
|
newFilteredQuestion.AuthorURL = questionAuthorURL
|
|
|
|
|
|
|
|
answers := []types.FilteredAnswer{}
|
2022-12-27 23:53:28 -05:00
|
|
|
|
|
|
|
doc.Find("div.answer").Each(func(i int, s *goquery.Selection) {
|
2022-12-29 13:06:14 -05:00
|
|
|
newFilteredAnswer := types.FilteredAnswer{}
|
|
|
|
|
2022-12-27 23:53:28 -05:00
|
|
|
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()
|
|
|
|
|
2022-12-28 23:56:14 -05:00
|
|
|
voteCount := html.EscapeString(voteCell.Find("div.js-vote-count").Text())
|
2022-12-27 23:53:28 -05:00
|
|
|
|
2022-12-29 13:06:14 -05:00
|
|
|
newFilteredAnswer.Upvotes = voteCount
|
|
|
|
newFilteredAnswer.IsAccepted = s.HasClass("accepted-answer")
|
2022-12-27 23:53:28 -05:00
|
|
|
|
|
|
|
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()
|
|
|
|
|
2022-12-28 23:56:14 -05:00
|
|
|
answerAuthorURL = html.EscapeString(answerAuthor.AttrOr("href", ""))
|
|
|
|
answerAuthorName = html.EscapeString(answerAuthor.Text())
|
|
|
|
answerTimestamp = html.EscapeString(s.Find("span.relativetime").Text())
|
2022-12-27 23:53:28 -05:00
|
|
|
})
|
|
|
|
|
2022-12-29 13:06:14 -05:00
|
|
|
newFilteredAnswer.AuthorName = answerAuthorName
|
|
|
|
newFilteredAnswer.AuthorURL = answerAuthorURL
|
|
|
|
newFilteredAnswer.Timestamp = answerTimestamp
|
2022-12-27 23:53:28 -05:00
|
|
|
|
2022-12-28 18:51:14 -05:00
|
|
|
// parse any code blocks and highlight them
|
|
|
|
answerCodeBlocks := codeBlockRegex.FindAllString(answerBodyHTML, -1)
|
|
|
|
for _, codeBlock := range answerCodeBlocks {
|
|
|
|
codeBlock = utils.StripBlockTags(codeBlock)
|
|
|
|
|
|
|
|
// syntax highlight
|
|
|
|
highlightedCodeBlock := utils.HighlightSyntaxViaContent(codeBlock)
|
|
|
|
|
|
|
|
// replace the code block with the highlighted code block
|
|
|
|
answerBodyHTML = strings.Replace(answerBodyHTML, codeBlock, highlightedCodeBlock, 1)
|
|
|
|
}
|
2022-12-27 23:53:28 -05:00
|
|
|
|
2022-12-29 13:06:14 -05:00
|
|
|
comments = utils.FindAndReturnComments(answerBodyHTML, postLayout)
|
|
|
|
|
|
|
|
newFilteredAnswer.Comments = comments
|
2023-02-02 19:05:08 -05:00
|
|
|
newFilteredAnswer.Body = template.HTML(utils.ReplaceImgTags(answerBodyHTML))
|
2022-12-28 22:48:02 -05:00
|
|
|
|
2022-12-29 13:06:14 -05:00
|
|
|
answers = append(answers, newFilteredAnswer)
|
2022-12-27 23:53:28 -05:00
|
|
|
})
|
|
|
|
|
2022-12-28 11:33:26 -05:00
|
|
|
imagePolicy := "'self' https:"
|
2022-12-28 00:10:09 -05:00
|
|
|
|
|
|
|
if c.MustGet("disable_images").(bool) {
|
2022-12-28 11:33:26 -05:00
|
|
|
imagePolicy = "'self'"
|
2022-12-28 00:10:09 -05:00
|
|
|
}
|
|
|
|
|
2022-12-27 23:53:28 -05:00
|
|
|
c.HTML(200, "question.html", gin.H{
|
2022-12-29 13:06:14 -05:00
|
|
|
"question": newFilteredQuestion,
|
|
|
|
"answers": answers,
|
|
|
|
"imagePolicy": imagePolicy,
|
|
|
|
"theme": c.MustGet("theme").(string),
|
|
|
|
"currentUrl": fmt.Sprintf("%s/questions/%s/%s", os.Getenv("APP_URL"), questionId, questionTitle),
|
2023-02-04 13:05:10 -05:00
|
|
|
"sortValue": sortValue,
|
2022-12-27 23:53:28 -05:00
|
|
|
})
|
|
|
|
|
|
|
|
}
|