AnonymousOverflow/src/routes/question.go

283 lines
7.4 KiB
Go
Raw Normal View History

2022-12-27 23:53:28 -05:00
package routes
import (
2022-12-29 13:54:37 -05:00
"anonymousoverflow/config"
"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"
"os"
"regexp"
"strconv"
2022-12-27 23:53:28 -05:00
"strings"
"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"
)
var codeBlockRegex = regexp.MustCompile(`(?s)<pre><code>(.+?)<\/code><\/pre>`)
var questionCodeBlockRegex = regexp.MustCompile(`(?s)<pre class=".+"><code( class=".+")?>(.+?)</code></pre>`)
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")
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,
})
return
}
2022-12-27 23:53:28 -05:00
questionTitle := c.Param("title")
sortValue := c.Query("sort_by")
if sortValue == "" {
sortValue = "votes"
}
soSortValue, ok := soSortValues[sortValue]
if !ok {
soSortValue = soSortValues["votes"]
}
sub := c.Param("sub")
domain := "stackoverflow.com"
if sub != "" {
domain = fmt.Sprintf("%s.stackexchange.com", sub)
}
soLink := fmt.Sprintf("https://%s/questions/%s/%s?answertab=%s", domain, questionId, questionTitle, soSortValue)
2022-12-27 23:53:28 -05:00
resp, err := client.R().Get(soLink)
if err != nil {
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,
})
return
}
2023-02-14 20:16:30 -05:00
defer resp.RawResponse.Body.Close()
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,
})
return
2022-12-27 23:53:28 -05:00
}
respBody := resp.String()
respBodyReader := strings.NewReader(respBody)
doc, err := goquery.NewDocumentFromReader(respBodyReader)
if err != nil {
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,
})
return
2022-12-27 23:53:28 -05:00
}
newFilteredQuestion := types.FilteredQuestion{}
2022-12-27 23:53:28 -05:00
questionTextParent := doc.Find("h1.fs-headline1")
questionText := questionTextParent.Children().First().Text()
newFilteredQuestion.Title = questionText
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 {
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,
})
return
2022-12-27 23:53:28 -05:00
}
2023-02-02 19:05:08 -05:00
newFilteredQuestion.Body = template.HTML(utils.ReplaceImgTags(questionBodyParentHTML))
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
// 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
}
})
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
}
})
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) {
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
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) {
questionAuthorDetails := s.Find("div.user-details")
2022-12-27 23:53:28 -05:00
if questionAuthorDetails.Length() > 0 {
questionAuthor := questionAuthorDetails.Find("a").First()
answerAuthorName = html.EscapeString(questionAuthor.Text())
answerAuthorURL = html.EscapeString(questionAuthor.AttrOr("href", ""))
2022-12-27 23:53:28 -05:00
}
2022-12-28 23:56:14 -05:00
answerTimestamp = html.EscapeString(s.Find("span.relativetime").Text())
2022-12-27 23:53:28 -05:00
})
answerId, _ := s.Attr("data-answerid")
newFilteredAnswer.ID = answerId
newFilteredAnswer.AuthorName = answerAuthorName
newFilteredAnswer.AuthorURL = answerAuthorURL
newFilteredAnswer.Timestamp = answerTimestamp
2022-12-27 23:53:28 -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
comments = utils.FindAndReturnComments(answerBodyHTML, postLayout)
newFilteredAnswer.Comments = comments
2023-02-02 19:05:08 -05:00
newFilteredAnswer.Body = template.HTML(utils.ReplaceImgTags(answerBodyHTML))
answers = append(answers, newFilteredAnswer)
2022-12-27 23:53:28 -05:00
})
2022-12-28 11:33:26 -05:00
imagePolicy := "'self' https:"
if c.MustGet("disable_images").(bool) {
2022-12-28 11:33:26 -05:00
imagePolicy = "'self'"
}
2022-12-27 23:53:28 -05:00
c.HTML(200, "question.html", gin.H{
"question": newFilteredQuestion,
"answers": answers,
"imagePolicy": imagePolicy,
"theme": c.MustGet("theme").(string),
"currentUrl": fmt.Sprintf("%s/questions/%s/%s", os.Getenv("APP_URL"), questionId, questionTitle),
"sortValue": sortValue,
2022-12-27 23:53:28 -05:00
})
}