mirror of
https://github.com/httpjamesm/AnonymousOverflow.git
synced 2024-12-06 19:16:43 +01:00
Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 487a62e7aa | |||
| abfd0cfb6f | |||
| 63a30ab1cf | |||
| f1370c29b6 | |||
| d935ce72f6 | |||
| 45644a0503 | |||
| f81b726612 | |||
| c55b8b6ed5 |
+1
-1
@@ -1,3 +1,3 @@
|
|||||||
package config
|
package config
|
||||||
|
|
||||||
var Version = "1.7"
|
var Version = "1.8"
|
||||||
|
|||||||
@@ -59,6 +59,15 @@ func main() {
|
|||||||
// redirect user to the answer with the title
|
// redirect user to the answer with the title
|
||||||
c.Redirect(302, fmt.Sprintf("/questions/%s/%s#%s", c.Param("id"), c.Param("title"), c.Param("answerId")))
|
c.Redirect(302, fmt.Sprintf("/questions/%s/%s#%s", c.Param("id"), c.Param("title"), c.Param("answerId")))
|
||||||
})
|
})
|
||||||
|
r.GET("/exchange/:sub/questions/:id/:title", routes.ViewQuestion)
|
||||||
|
r.GET("/exchange/:sub/questions/:id", func(c *gin.Context) {
|
||||||
|
// redirect user to the question with the title
|
||||||
|
c.Redirect(302, fmt.Sprintf("/exchange/%s/questions/%s/placeholder", c.Param("sub"), c.Param("id")))
|
||||||
|
})
|
||||||
|
r.GET("/exchange/:sub/questions/:id/:title/:answerId", func(c *gin.Context) {
|
||||||
|
// redirect user to the answer with the title
|
||||||
|
c.Redirect(302, fmt.Sprintf("/exchange/%s/questions/%s/%s#%s", c.Param("sub"), c.Param("id"), c.Param("title"), c.Param("answerId")))
|
||||||
|
})
|
||||||
|
|
||||||
r.GET("/proxy", routes.GetImage)
|
r.GET("/proxy", routes.GetImage)
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package middleware
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"anonymousoverflow/config"
|
"anonymousoverflow/config"
|
||||||
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -12,6 +13,12 @@ var ipMap = sync.Map{}
|
|||||||
|
|
||||||
func Ratelimit() gin.HandlerFunc {
|
func Ratelimit() gin.HandlerFunc {
|
||||||
return func(c *gin.Context) {
|
return func(c *gin.Context) {
|
||||||
|
|
||||||
|
if strings.HasPrefix(c.Request.URL.Path, "/static") {
|
||||||
|
c.Next()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
ip := c.ClientIP()
|
ip := c.ClientIP()
|
||||||
|
|
||||||
// log request count as the value, ip as key
|
// log request count as the value, ip as key
|
||||||
|
|||||||
+16
-4
@@ -3,6 +3,7 @@ package routes
|
|||||||
import (
|
import (
|
||||||
"anonymousoverflow/config"
|
"anonymousoverflow/config"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
@@ -19,6 +20,8 @@ type urlConversionRequest struct {
|
|||||||
URL string `form:"url" binding:"required"`
|
URL string `form:"url" binding:"required"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var stackExchangeRegex = regexp.MustCompile(`https://(.+).stackexchange.com/questions/`)
|
||||||
|
|
||||||
func PostHome(c *gin.Context) {
|
func PostHome(c *gin.Context) {
|
||||||
body := urlConversionRequest{}
|
body := urlConversionRequest{}
|
||||||
|
|
||||||
@@ -33,15 +36,24 @@ func PostHome(c *gin.Context) {
|
|||||||
soLink := body.URL
|
soLink := body.URL
|
||||||
|
|
||||||
// validate URL
|
// validate URL
|
||||||
if !strings.HasPrefix(soLink, "https://stackoverflow.com/questions/") {
|
isStackOverflow := strings.HasPrefix(soLink, "https://stackoverflow.com/questions/")
|
||||||
|
isStackExchange := stackExchangeRegex.MatchString(soLink)
|
||||||
|
if !isStackExchange && !isStackOverflow {
|
||||||
c.HTML(400, "home.html", gin.H{
|
c.HTML(400, "home.html", gin.H{
|
||||||
"errorMessage": "Invalid stack overflow URL",
|
"errorMessage": "Invalid stack overflow/exchange URL",
|
||||||
"theme": c.MustGet("theme").(string),
|
"theme": c.MustGet("theme").(string),
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// redirect to the proxied thread
|
// if stack overflow, trim https://stackoverflow.com
|
||||||
c.Redirect(302, fmt.Sprintf("/questions/%s", strings.TrimPrefix(soLink, "https://stackoverflow.com/questions/")))
|
if isStackOverflow {
|
||||||
|
c.Redirect(302, strings.TrimPrefix(soLink, "https://stackoverflow.com"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// if stack exchange, extract the subdomain
|
||||||
|
sub := stackExchangeRegex.FindStringSubmatch(soLink)[1]
|
||||||
|
|
||||||
|
c.Redirect(302, fmt.Sprintf("/exchange/%s/%s", sub, strings.TrimPrefix(soLink, fmt.Sprintf("https://%s.stackexchange.com", sub))))
|
||||||
}
|
}
|
||||||
|
|||||||
+40
-22
@@ -53,7 +53,15 @@ func ViewQuestion(c *gin.Context) {
|
|||||||
soSortValue = soSortValues["votes"]
|
soSortValue = soSortValues["votes"]
|
||||||
}
|
}
|
||||||
|
|
||||||
soLink := fmt.Sprintf("https://stackoverflow.com/questions/%s/%s?answertab=%s", questionId, questionTitle, soSortValue)
|
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)
|
||||||
|
|
||||||
resp, err := client.R().Get(soLink)
|
resp, err := client.R().Get(soLink)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -129,7 +137,7 @@ func ViewQuestion(c *gin.Context) {
|
|||||||
|
|
||||||
newFilteredQuestion.ShortenedBody = shortenedBody
|
newFilteredQuestion.ShortenedBody = shortenedBody
|
||||||
|
|
||||||
comments := utils.FindAndReturnComments(questionBodyParentHTML, questionPostLayout)
|
comments := utils.FindAndReturnComments(questionBodyParentHTML, domain, questionPostLayout)
|
||||||
newFilteredQuestion.Comments = comments
|
newFilteredQuestion.Comments = comments
|
||||||
|
|
||||||
// parse any code blocks and highlight them
|
// parse any code blocks and highlight them
|
||||||
@@ -170,24 +178,33 @@ func ViewQuestion(c *gin.Context) {
|
|||||||
userDetails := questionMetadata.Find("div.user-details")
|
userDetails := questionMetadata.Find("div.user-details")
|
||||||
|
|
||||||
questionAuthor := ""
|
questionAuthor := ""
|
||||||
questionAuthorURL := ""
|
questionAuthorURL := fmt.Sprintf("https://%s", domain)
|
||||||
|
|
||||||
|
// check if the question has been edited
|
||||||
|
isQuestionEdited := false
|
||||||
|
questionMetadata.Find("a.js-gps-track").Each(func(i int, s *goquery.Selection) {
|
||||||
|
if strings.Contains(s.Text(), "edited") {
|
||||||
|
isQuestionEdited = true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
userDetails.Find("a").Each(func(i int, s *goquery.Selection) {
|
userDetails.Find("a").Each(func(i int, s *goquery.Selection) {
|
||||||
// get the second
|
// if question has been edited, the author is the second element.
|
||||||
if i == 0 {
|
|
||||||
if s.Text() != "" {
|
if isQuestionEdited {
|
||||||
// if it's not been edited, it means it's the first
|
if i == 1 {
|
||||||
questionAuthor = s.Text()
|
questionAuthor = s.Text()
|
||||||
questionAuthorURL, _ = s.Attr("href")
|
questionAuthorURL += s.AttrOr("href", "")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// otherwise it's the first element
|
||||||
|
if i == 0 {
|
||||||
|
questionAuthor = s.Text()
|
||||||
|
questionAuthorURL += s.AttrOr("href", "")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// otherwise it's the second element
|
|
||||||
if i == 1 {
|
|
||||||
questionAuthor = s.Text()
|
|
||||||
questionAuthorURL, _ = s.Attr("href")
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -213,17 +230,17 @@ func ViewQuestion(c *gin.Context) {
|
|||||||
|
|
||||||
answerFooter := s.Find("div.mt24")
|
answerFooter := s.Find("div.mt24")
|
||||||
|
|
||||||
answerAuthorURL := ""
|
answerAuthorURL := fmt.Sprintf("https://%s", domain)
|
||||||
answerAuthorName := ""
|
answerAuthorName := ""
|
||||||
answerTimestamp := ""
|
answerTimestamp := ""
|
||||||
|
|
||||||
answerFooter.Find("div.post-signature").Each(func(i int, s *goquery.Selection) {
|
answerFooter.Find("div.post-signature").Each(func(i int, s *goquery.Selection) {
|
||||||
questionAuthorDetails := questionMetadata.Find("div.user-details")
|
answerAuthorDetails := s.Find("div.user-details")
|
||||||
|
|
||||||
if questionAuthorDetails.Length() > 0 {
|
if answerAuthorDetails.Length() > 0 {
|
||||||
questionAuthor := questionAuthorDetails.Find("a").First()
|
questionAuthor := answerAuthorDetails.Find("a").First()
|
||||||
newFilteredQuestion.AuthorName = html.EscapeString(questionAuthor.Text())
|
answerAuthorName = html.EscapeString(questionAuthor.Text())
|
||||||
newFilteredQuestion.AuthorURL = html.EscapeString(questionAuthor.AttrOr("href", ""))
|
answerAuthorURL += html.EscapeString(questionAuthor.AttrOr("href", ""))
|
||||||
}
|
}
|
||||||
|
|
||||||
answerTimestamp = html.EscapeString(s.Find("span.relativetime").Text())
|
answerTimestamp = html.EscapeString(s.Find("span.relativetime").Text())
|
||||||
@@ -248,7 +265,7 @@ func ViewQuestion(c *gin.Context) {
|
|||||||
answerBodyHTML = strings.Replace(answerBodyHTML, codeBlock, highlightedCodeBlock, 1)
|
answerBodyHTML = strings.Replace(answerBodyHTML, codeBlock, highlightedCodeBlock, 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
comments = utils.FindAndReturnComments(answerBodyHTML, postLayout)
|
comments = utils.FindAndReturnComments(answerBodyHTML, domain, postLayout)
|
||||||
|
|
||||||
newFilteredAnswer.Comments = comments
|
newFilteredAnswer.Comments = comments
|
||||||
newFilteredAnswer.Body = template.HTML(utils.ReplaceImgTags(answerBodyHTML))
|
newFilteredAnswer.Body = template.HTML(utils.ReplaceImgTags(answerBodyHTML))
|
||||||
@@ -269,6 +286,7 @@ func ViewQuestion(c *gin.Context) {
|
|||||||
"theme": c.MustGet("theme").(string),
|
"theme": c.MustGet("theme").(string),
|
||||||
"currentUrl": fmt.Sprintf("%s/questions/%s/%s", os.Getenv("APP_URL"), questionId, questionTitle),
|
"currentUrl": fmt.Sprintf("%s/questions/%s/%s", os.Getenv("APP_URL"), questionId, questionTitle),
|
||||||
"sortValue": sortValue,
|
"sortValue": sortValue,
|
||||||
|
"domain": domain,
|
||||||
})
|
})
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,12 +2,13 @@ package utils
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"anonymousoverflow/src/types"
|
"anonymousoverflow/src/types"
|
||||||
|
"fmt"
|
||||||
"html/template"
|
"html/template"
|
||||||
|
|
||||||
"github.com/PuerkitoBio/goquery"
|
"github.com/PuerkitoBio/goquery"
|
||||||
)
|
)
|
||||||
|
|
||||||
func FindAndReturnComments(inHtml string, postLayout *goquery.Selection) (comments []types.FilteredComment) {
|
func FindAndReturnComments(inHtml, domain string, postLayout *goquery.Selection) (comments []types.FilteredComment) {
|
||||||
|
|
||||||
commentsComponent := postLayout.Find("div.js-post-comments-component")
|
commentsComponent := postLayout.Find("div.js-post-comments-component")
|
||||||
|
|
||||||
@@ -34,7 +35,7 @@ func FindAndReturnComments(inHtml string, postLayout *goquery.Selection) (commen
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
commentAuthorURL := ""
|
commentAuthorURL := fmt.Sprintf("https://%s", domain)
|
||||||
|
|
||||||
commentAuthor := commentBody.Find("span.comment-user")
|
commentAuthor := commentBody.Find("span.comment-user")
|
||||||
if commentAuthor.Length() == 0 {
|
if commentAuthor.Length() == 0 {
|
||||||
@@ -43,7 +44,7 @@ func FindAndReturnComments(inHtml string, postLayout *goquery.Selection) (commen
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
commentAuthorURL = commentAuthor.AttrOr("href", "")
|
commentAuthorURL += commentAuthor.AttrOr("href", "")
|
||||||
}
|
}
|
||||||
|
|
||||||
commentTimestamp := commentBody.Find("span.relativetime-clean").Text()
|
commentTimestamp := commentBody.Find("span.relativetime-clean").Text()
|
||||||
|
|||||||
@@ -19,7 +19,7 @@
|
|||||||
<div class="comment-author">
|
<div class="comment-author">
|
||||||
Commented {{ $comment.Timestamp }} by
|
Commented {{ $comment.Timestamp }} by
|
||||||
<a
|
<a
|
||||||
href="https://stackoverflow.com{{ $comment.AuthorURL }}"
|
href="{{ $comment.AuthorURL }}"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
>{{ $comment.AuthorName }}</a
|
>{{ $comment.AuthorName }}</a
|
||||||
|
|||||||
@@ -30,7 +30,7 @@
|
|||||||
<p class="timestamp">
|
<p class="timestamp">
|
||||||
Asked {{ .question.Timestamp }} by
|
Asked {{ .question.Timestamp }} by
|
||||||
<a
|
<a
|
||||||
href="https://stackoverflow.com{{ .question.AuthorURL }}"
|
href="{{ .question.AuthorURL }}"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
>{{ .question.AuthorName }}</a
|
>{{ .question.AuthorName }}</a
|
||||||
@@ -87,7 +87,7 @@
|
|||||||
<div class="answer-author">
|
<div class="answer-author">
|
||||||
Answered {{ $answer.Timestamp }} by
|
Answered {{ $answer.Timestamp }} by
|
||||||
<a
|
<a
|
||||||
href="https://stackoverflow.com{{ $answer.AuthorURL }}"
|
href="{{ $answer.AuthorURL }}"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
>{{ $answer.AuthorName }}</a
|
>{{ $answer.AuthorName }}</a
|
||||||
|
|||||||
Reference in New Issue
Block a user