mirror of
https://github.com/httpjamesm/AnonymousOverflow.git
synced 2024-12-06 19:16:43 +01:00
Compare commits
12 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 7e4ae9aecf | |||
| bca87a89ad | |||
| 0f67fcf66f | |||
| 4c3996542c | |||
| a701810e11 | |||
| 23b8ed8899 | |||
| bf5300706e | |||
| 1e659c550a | |||
| 2ade374482 | |||
| 94032f4f90 | |||
| 89126a7377 | |||
| 0d7355bd46 |
+1
-1
@@ -1,4 +1,4 @@
|
||||
FROM golang:1.19-alpine as build
|
||||
FROM golang:1.22.1-alpine3.19 as build
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
|
||||
@@ -6,50 +6,52 @@ This project is super lightweight by design. The UI is simple and the frontend i
|
||||
|
||||
## Screenshots
|
||||
|
||||

|
||||

|
||||
|
||||

|
||||

|
||||
|
||||

|
||||

|
||||
|
||||
## Clearnet Instances
|
||||
|
||||
| Instance URL | Region | Notes |
|
||||
| ----------------------------------------------------------------------------------------- | ------------------------------- | ------------------------------------------------------------------------------------------------ |
|
||||
| [code.whatever.social](https://code.whatever.social) | United States & The Netherlands | Operated by [Whatever Social](https://whatever.social) and [http.james](https://httpjames.space) |
|
||||
| [ao.vern.cc](https://ao.vern.cc) | United States | Operated by [vern.cc](https://vern.cc) |
|
||||
| [overflow.smnz.de](https://overflow.smnz.de) | Germany | Operated by [smnz.de](https://smnz.de) |
|
||||
| [overflow.lunar.icu](https://overflow.lunar.icu) | Germany | Operated by [lunar.icu](https://lunar.icu/) |
|
||||
| [overflow.adminforge.de](https://overflow.adminforge.de/) | Germany | Operated by [adminForge](https://adminforge.de/) |
|
||||
| [overflow.hostux.net](https://overflow.hostux.net/) | France | Operated by [Hostux](https://hostux.net/) |
|
||||
| [overflow.projectsegfau.lt](https://overflow.projectsegfau.lt/) | United States, France, India | Operated by [Project Segfault](https://projectsegfau.lt/) |
|
||||
| [code.xbdm.fun](https://code.xbdm.fun) | Germany | Operated by [xbdm.fun](https://xbdm.fun) |
|
||||
| [overflow.fascinated.cc](https://overflow.fascinated.cc/) | Germany | Operated by [fascinated.cc](https://fascinated.cc/) |
|
||||
| [ao.bloatcat.tk](https://ao.bloatcat.tk) | Iceland | Operated by [bloatcat.tk](https://bloatcat.tk) |
|
||||
| [anonoverflow.frontendfriendly.xyz](https://anonoverflow.frontendfriendly.xyz/) | United States | Operated by [frontendfriendly.xyz](https://frontendfriendly.xyz/) |
|
||||
| [ao.owo.si](https://ao.owo.si/) | Germany | Operated by [owo.si](https://owo.si/) |
|
||||
| [overflow.datura.network](https://overflow.datura.network/) | Germany | Operated by [datura.network](https://datura.network) |
|
||||
| [overflow.freedit.eu](overflow.freedit.eu) | United States | Operated by [freedit.eu](https://freedit.eu) |
|
||||
| [ao.ftw.lol](https://ao.ftw.lol) | Germany | Operated by [ftw.lol](https://ftw.lol) |
|
||||
| [anonoverflow.hyperreal.coffee](https://anonoverflow.hyperreal.coffee) | United States | Operated by [hyperreal.coffee](https://hyperreal.coffee) |
|
||||
| [a.opnxng.com](a.opnxng.com) | Singapore | Operated by [opnxng.com](https://about.opnxng.com) |
|
||||
| [overflow.sudovanilla.com](https://overflow.sudovanilla.com) | United States | Operated by [SudoVanilla](https://sudovanilla.com) |
|
||||
| [anonymousoverflow.privacyfucking.rocks](https://anonymousoverflow.privacyfucking.rocks/) | Germany | Operated by [privacyfucking.rocks](https://privacyfucking.rocks) |
|
||||
| [exchange.seitan-ayoub.lol](https://exchange.seitan-ayoub.lol) | Germany | Operated by [Seitan Ayoub](https://seitan-ayoub.lol) |
|
||||
| [overflow.r4fo.com](https://overflow.r4fo.com) | The Netherlands | Operated by [r4fo.com](https://r4fo.com) |
|
||||
| [overflow.ducks.party](https://overflow.ducks.party) | The Netherlands | Operated by [ducks.party](https://ducks.party) |
|
||||
| [ao.ngn.tf](https://ao.ngn.tf) | Turkey | Operated by [ngn](https://ngn.tf) |
|
||||
| Instance URL | Region | Notes |
|
||||
| ----------------------------------------------------------------------------------------- | ---------------------------- | ------------------------------------------------------------------------------------------------ |
|
||||
| [code.whatever.social](https://code.whatever.social) | Germany | Operated by [Whatever Social](https://whatever.social) and [http.james](https://httpjames.space) |
|
||||
| [ao.vern.cc](https://ao.vern.cc) | United States | Operated by [vern.cc](https://vern.cc) |
|
||||
| [overflow.smnz.de](https://overflow.smnz.de) | Germany | Operated by [smnz.de](https://smnz.de) |
|
||||
| [overflow.lunar.icu](https://overflow.lunar.icu) | Germany | Operated by [lunar.icu](https://lunar.icu/) |
|
||||
| [overflow.adminforge.de](https://overflow.adminforge.de/) | Germany | Operated by [adminForge](https://adminforge.de/) |
|
||||
| [overflow.hostux.net](https://overflow.hostux.net/) | France | Operated by [Hostux](https://hostux.net/) |
|
||||
| [overflow.projectsegfau.lt](https://overflow.projectsegfau.lt/) | United States, France, India | Operated by [Project Segfault](https://projectsegfau.lt/) |
|
||||
| [code.xbdm.fun](https://code.xbdm.fun) | Germany | Operated by [xbdm.fun](https://xbdm.fun) |
|
||||
| [overflow.fascinated.cc](https://overflow.fascinated.cc/) | Germany | Operated by [fascinated.cc](https://fascinated.cc/) |
|
||||
| [ao.bloat.cat](https://ao.bloat.cat) | Romania | Operated by [bloatcat.tk](https://bloat.cat) |
|
||||
| [anonoverflow.frontendfriendly.xyz](https://anonoverflow.frontendfriendly.xyz/) | United States | Operated by [frontendfriendly.xyz](https://frontendfriendly.xyz/) |
|
||||
| [ao.owo.si](https://ao.owo.si/) | Germany | Operated by [owo.si](https://owo.si/) |
|
||||
| [overflow.datura.network](https://overflow.datura.network/) | Germany | Operated by [datura.network](https://datura.network) |
|
||||
| [overflow.freedit.eu](overflow.freedit.eu) | United States | Operated by [freedit.eu](https://freedit.eu) |
|
||||
| [ao.ftw.lol](https://ao.ftw.lol) | Germany | Operated by [ftw.lol](https://ftw.lol) |
|
||||
| [anonoverflow.hyperreal.coffee](https://anonoverflow.hyperreal.coffee) | United States | Operated by [hyperreal.coffee](https://hyperreal.coffee) |
|
||||
| [a.opnxng.com](a.opnxng.com) | Singapore | Operated by [opnxng.com](https://about.opnxng.com) |
|
||||
| [overflow.sudovanilla.com](https://overflow.sudovanilla.com) | United States | Operated by [SudoVanilla](https://sudovanilla.com) |
|
||||
| [anonymousoverflow.privacyfucking.rocks](https://anonymousoverflow.privacyfucking.rocks/) | Germany | Operated by [privacyfucking.rocks](https://privacyfucking.rocks) |
|
||||
| [exchange.seitan-ayoub.lol](https://exchange.seitan-ayoub.lol) | Germany | Operated by [Seitan Ayoub](https://seitan-ayoub.lol) |
|
||||
| [overflow.r4fo.com](https://overflow.r4fo.com) | The Netherlands | Operated by [r4fo.com](https://r4fo.com) |
|
||||
| [overflow.ducks.party](https://overflow.ducks.party) | The Netherlands | Operated by [ducks.party](https://ducks.party) |
|
||||
| [ao.ngn.tf](https://ao.ngn.tf) | Turkey | Operated by [ngn](https://ngn.tf) |
|
||||
| [overflow.snine.nl](https://overflow.snine.nl) | The Netherlands | Operated by [snine](https://snine.nl) |
|
||||
|
||||
## Other Instances
|
||||
|
||||
| Instance URL | Region | Notes |
|
||||
| ---------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------- | --------------------------------------------------------- |
|
||||
| [ao.vernccvbvyi5qhfzyqengccj7lkove6bjot2xhh5kajhwvidqafczrad.onion](http://ao.vernccvbvyi5qhfzyqengccj7lkove6bjot2xhh5kajhwvidqafczrad.onion) | United States | Operated by [vern.cc](https://vern.cc) |
|
||||
| [vernmzgraj6aaoafmehupvtkkynpaa67rxcdj2kinwiy6konn6rq.b32.i2p](http://vernmzgraj6aaoafmehupvtkkynpaa67rxcdj2kinwiy6konn6rq.b32.i2p) | United States | Operated by [vern.cc](https://vern.cc) |
|
||||
| [overflow.pjsfkvpxlinjamtawaksbnnaqs2fc2mtvmozrzckxh7f3kis6yea25ad.onion](http://overflow.pjsfkvpxlinjamtawaksbnnaqs2fc2mtvmozrzckxh7f3kis6yea25ad.onion/) | Luxembourg | Operated by [Project Segfault](https://projectsegfau.lt/) |
|
||||
| [overflow.daturab6drmkhyeia4ch5gvfc2f3wgo6bhjrv3pz6n7kxmvoznlkq4yd.onion](http://overflow.daturab6drmkhyeia4ch5gvfc2f3wgo6bhjrv3pz6n7kxmvoznlkq4yd.onion/) | Germany | Operated by [datura.network](https://datura.network) |
|
||||
| [ao.pk47sgwhncn5cgidm7bofngmh7lc7ukjdpk5bjwfemmyp27ovl25ikyd.onion](http://ao.pk47sgwhncn5cgidm7bofngmh7lc7ukjdpk5bjwfemmyp27ovl25ikyd.onion/) | Germany | Operated by [owo.si](https://owo.si/) |
|
||||
| [ay7akchgdh76r4lc62hzd52z6xqoh67loototsetvqxo5o7ngo5q.b32.i2p](http://ay7akchgdh76r4lc62hzd52z6xqoh67loototsetvqxo5o7ngo5q.b32.i2p/) | Germany | Operated by [owo.si](https://owo.si/) |
|
||||
| Instance URL | Region | Notes |
|
||||
| ---------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------- | --------------------------------------------------------- |
|
||||
| [ao.vernccvbvyi5qhfzyqengccj7lkove6bjot2xhh5kajhwvidqafczrad.onion](http://ao.vernccvbvyi5qhfzyqengccj7lkove6bjot2xhh5kajhwvidqafczrad.onion) | United States | Operated by [vern.cc](https://vern.cc) |
|
||||
| [vernmzgraj6aaoafmehupvtkkynpaa67rxcdj2kinwiy6konn6rq.b32.i2p](http://vernmzgraj6aaoafmehupvtkkynpaa67rxcdj2kinwiy6konn6rq.b32.i2p) | United States | Operated by [vern.cc](https://vern.cc) |
|
||||
| [overflow.pjsfkvpxlinjamtawaksbnnaqs2fc2mtvmozrzckxh7f3kis6yea25ad.onion](http://overflow.pjsfkvpxlinjamtawaksbnnaqs2fc2mtvmozrzckxh7f3kis6yea25ad.onion/) | Luxembourg | Operated by [Project Segfault](https://projectsegfau.lt/) |
|
||||
| [overflow.daturab6drmkhyeia4ch5gvfc2f3wgo6bhjrv3pz6n7kxmvoznlkq4yd.onion](http://overflow.daturab6drmkhyeia4ch5gvfc2f3wgo6bhjrv3pz6n7kxmvoznlkq4yd.onion/) | Germany | Operated by [datura.network](https://datura.network) |
|
||||
| [ao.pk47sgwhncn5cgidm7bofngmh7lc7ukjdpk5bjwfemmyp27ovl25ikyd.onion](http://ao.pk47sgwhncn5cgidm7bofngmh7lc7ukjdpk5bjwfemmyp27ovl25ikyd.onion/) | Germany | Operated by [owo.si](https://owo.si/) |
|
||||
| [ay7akchgdh76r4lc62hzd52z6xqoh67loototsetvqxo5o7ngo5q.b32.i2p](http://ay7akchgdh76r4lc62hzd52z6xqoh67loototsetvqxo5o7ngo5q.b32.i2p/) | Germany | Operated by [owo.si](https://owo.si/) |
|
||||
| [overflow.r4focoma7gu2zdwwcjjad47ysxt634lg73sxmdbkdozanwqslho5ohyd.onion](http://overflow.r4focoma7gu2zdwwcjjad47ysxt634lg73sxmdbkdozanwqslho5ohyd.onion) | The Netherlands | Operated by [r4fo.com](https://r4fo.com) |
|
||||
|
||||
## Why use AnonymousOverflow over StackOverflow?
|
||||
|
||||
|
||||
+1
-1
@@ -1,3 +1,3 @@
|
||||
package config
|
||||
|
||||
var Version = "1.11.0"
|
||||
var Version = "1.12.0"
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 50 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 31 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 59 KiB |
+38
-20
@@ -20,7 +20,41 @@ type urlConversionRequest struct {
|
||||
URL string `form:"url" binding:"required"`
|
||||
}
|
||||
|
||||
var stackExchangeRegex = regexp.MustCompile(`https://(.+).stackexchange.com/questions/`)
|
||||
var coreRegex = regexp.MustCompile(`(?:https?://)?(?:www\.)?([^/]+)(/(?:questions|q|a)/.+)`)
|
||||
|
||||
// Will return `nil` if `rawUrl` is invalid.
|
||||
func translateUrl(rawUrl string) string {
|
||||
coreMatches := coreRegex.FindStringSubmatch(rawUrl)
|
||||
if coreMatches == nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
domain := coreMatches[1]
|
||||
rest := coreMatches[2]
|
||||
|
||||
exchange := ""
|
||||
if domain == "stackoverflow.com" {
|
||||
// No exchange parameter needed.
|
||||
} else if sub, found := strings.CutSuffix(domain, ".stackexchange.com"); found {
|
||||
if sub == "" {
|
||||
return ""
|
||||
} else if strings.Contains(sub, ".") {
|
||||
// Anything containing dots is interpreted as a full domain, so we use the correct full domain.
|
||||
exchange = domain
|
||||
} else {
|
||||
exchange = sub
|
||||
}
|
||||
} else {
|
||||
exchange = domain
|
||||
}
|
||||
|
||||
// Ensure we properly format the return string to avoid double slashes
|
||||
if exchange == "" {
|
||||
return rest
|
||||
} else {
|
||||
return fmt.Sprintf("/exchange/%s%s", exchange, rest)
|
||||
}
|
||||
}
|
||||
|
||||
func PostHome(c *gin.Context) {
|
||||
body := urlConversionRequest{}
|
||||
@@ -33,16 +67,9 @@ func PostHome(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
soLink := body.URL
|
||||
translated := translateUrl(body.URL)
|
||||
|
||||
// remove the www.
|
||||
soLink = strings.ReplaceAll(soLink, "www.", "")
|
||||
|
||||
// validate URL
|
||||
isStackOverflow := strings.HasPrefix(soLink, "https://stackoverflow.com/questions/")
|
||||
isShortenedStackOverflow := strings.HasPrefix(soLink, "https://stackoverflow.com/a/") || strings.HasPrefix(soLink, "https://stackoverflow.com/q/")
|
||||
isStackExchange := stackExchangeRegex.MatchString(soLink)
|
||||
if !isStackExchange && !isStackOverflow && !isShortenedStackOverflow {
|
||||
if translated == "" {
|
||||
c.HTML(400, "home.html", gin.H{
|
||||
"errorMessage": "Invalid stack overflow/exchange URL",
|
||||
"theme": c.MustGet("theme").(string),
|
||||
@@ -50,14 +77,5 @@ func PostHome(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
// if stack overflow, trim https://stackoverflow.com
|
||||
if isStackOverflow || isShortenedStackOverflow {
|
||||
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))))
|
||||
c.Redirect(302, translated)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
package routes
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestTranslateUrl(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
// Test with a Valid StackOverflow URL
|
||||
assert.Equal("/questions/example-question", translateUrl("https://stackoverflow.com/questions/example-question"), "StackOverflow URL should not be modified")
|
||||
|
||||
// Test with Complex Subdomain
|
||||
assert.Equal("/exchange/meta.math.stackexchange.com/q/example-question", translateUrl("https://meta.math.stackexchange.com/q/example-question"), "Complex StackExchange subdomain should be used as full exchange")
|
||||
|
||||
// Test with Non-StackExchange Domain
|
||||
assert.Equal("/exchange/example.com/questions/example-question", translateUrl("https://example.com/questions/example-question"), "Non-StackExchange domain should be detected as exchange")
|
||||
|
||||
// Test with Invalid URL
|
||||
assert.Equal("", translateUrl("This is not a URL"), "Invalid URL should return an empty string")
|
||||
|
||||
// Test with Empty String
|
||||
assert.Equal("", translateUrl(""), "Empty string should return an empty string")
|
||||
|
||||
// Test with Missing Path
|
||||
assert.Equal("", translateUrl("https://stackoverflow.com"), "URL without path should return an empty string")
|
||||
|
||||
// Test with Valid URL but Root Domain for StackExchange
|
||||
assert.Equal("", translateUrl("https://stackexchange.com"), "Root StackExchange domain without subdomain should return an empty string")
|
||||
}
|
||||
@@ -47,7 +47,9 @@ func ViewQuestion(c *gin.Context) {
|
||||
|
||||
domain := "stackoverflow.com"
|
||||
|
||||
if params.Sub != "" {
|
||||
if strings.Contains(params.Sub, ".") {
|
||||
domain = params.Sub
|
||||
} else if params.Sub != "" {
|
||||
domain = fmt.Sprintf("%s.stackexchange.com", params.Sub)
|
||||
}
|
||||
|
||||
@@ -246,7 +248,7 @@ func extractAnswersData(doc *goquery.Document, domain string) ([]types.FilteredA
|
||||
|
||||
// Process code blocks within the answer.
|
||||
processedAnswerBody := utils.ProcessHTMLBody(answerBodyHTML)
|
||||
answer.Body = template.HTML(html.UnescapeString(processedAnswerBody))
|
||||
answer.Body = template.HTML(processedAnswerBody)
|
||||
|
||||
// Extract author information and timestamp.
|
||||
extractAnswerAuthorInfo(s, &answer, domain)
|
||||
|
||||
+9
-6
@@ -14,13 +14,15 @@ import (
|
||||
|
||||
// highlightSyntaxViaContent uses Chroma to lex code content and apply the appropriate tokenizer engine.
|
||||
// If it can't find one, it defaults to JavaScript syntax highlighting.
|
||||
func highlightSyntaxViaContent(content string) (htmlOut string) {
|
||||
func highlightSyntaxViaContent(content string, lang string) (htmlOut string) {
|
||||
content = html.UnescapeString(content)
|
||||
|
||||
fallbackOut := html.EscapeString(content)
|
||||
|
||||
// identify the language
|
||||
lexer := lexers.Analyse(content)
|
||||
lexer := lexers.Get(lang)
|
||||
if lexer == nil {
|
||||
lexer = lexers.Analyse(content)
|
||||
}
|
||||
if lexer == nil {
|
||||
lexer = lexers.Get(".js")
|
||||
}
|
||||
@@ -71,19 +73,20 @@ func stripBlockTags(content string) (result string) {
|
||||
return
|
||||
}
|
||||
|
||||
var codeBlockRegex = regexp.MustCompile(`(?s)<pre><code>(.*?)<\/code><\/pre>`)
|
||||
var codeBlockRegex = regexp.MustCompile(`(?s)<pre(?:[^>]+?lang-(.+?)[\s"'])?.*?><code>(.*?)<\/code><\/pre>`)
|
||||
|
||||
// HighlightCodeBlocks uses both highlightSyntaxViaContent stripCodeBlocks and returns the newly highlighted code HTML.
|
||||
func HighlightCodeBlocks(html string) string {
|
||||
// Replace each code block with the highlighted version
|
||||
highlightedHTML := codeBlockRegex.ReplaceAllStringFunc(html, func(codeBlock string) string {
|
||||
// Extract the code content from the code block
|
||||
codeContent := codeBlockRegex.FindStringSubmatch(codeBlock)[1]
|
||||
matches := codeBlockRegex.FindStringSubmatch(codeBlock)
|
||||
lang, codeContent := matches[1], matches[2]
|
||||
|
||||
codeContent = stripBlockTags(codeContent)
|
||||
|
||||
// Highlight the code content
|
||||
highlightedCode := highlightSyntaxViaContent(codeContent)
|
||||
highlightedCode := highlightSyntaxViaContent(codeContent, lang)
|
||||
|
||||
// Replace the original code block with the highlighted version
|
||||
highlightedCodeBlock := "<pre>" + highlightedCode + "</pre>"
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<link rel="stylesheet" href="/static/comments.css" />
|
||||
<meta
|
||||
http-equiv="Content-Security-Policy"
|
||||
content="default-src 'none'; style-src 'self'; script-src 'self'; img-src {{ .imagePolicy }};"
|
||||
content="default-src 'none'; style-src 'self'; script-src 'self'; img-src {{ .imagePolicy }}; font-src 'self';"
|
||||
/>
|
||||
<meta name="description" content="{{ .question.ShortenedBody }}..." />
|
||||
{{ template "sharedHead.html" }}
|
||||
|
||||
Reference in New Issue
Block a user